0001 /*
0002 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing.text;
0026
0027 import java.lang.reflect.Method;
0028
0029 import java.security.AccessController;
0030 import java.security.PrivilegedAction;
0031
0032 import java.util.Collections;
0033 import java.util.HashMap;
0034 import java.util.Hashtable;
0035 import java.util.Enumeration;
0036 import java.util.Vector;
0037 import java.util.Iterator;
0038 import java.util.Map;
0039 import java.util.Map.Entry;
0040 import java.util.Set;
0041
0042 import java.util.concurrent.*;
0043
0044 import java.io.*;
0045
0046 import java.awt.*;
0047 import java.awt.event.*;
0048 import java.awt.print.*;
0049 import java.awt.datatransfer.*;
0050 import java.awt.im.InputContext;
0051 import java.awt.im.InputMethodRequests;
0052 import java.awt.font.TextHitInfo;
0053 import java.awt.font.TextAttribute;
0054
0055 import java.awt.print.Printable;
0056 import java.awt.print.PrinterException;
0057
0058 import javax.print.PrintService;
0059 import javax.print.attribute.PrintRequestAttributeSet;
0060
0061 import java.text.*;
0062 import java.text.AttributedCharacterIterator.Attribute;
0063
0064 import javax.swing.*;
0065 import javax.swing.event.*;
0066 import javax.swing.plaf.*;
0067
0068 import javax.accessibility.*;
0069
0070 import javax.print.attribute.*;
0071
0072 import sun.awt.AppContext;
0073
0074 import sun.swing.PrintingStatus;
0075 import sun.swing.SwingUtilities2;
0076 import sun.swing.text.TextComponentPrintable;
0077
0078 /**
0079 * <code>JTextComponent</code> is the base class for swing text
0080 * components. It tries to be compatible with the
0081 * <code>java.awt.TextComponent</code> class
0082 * where it can reasonably do so. Also provided are other services
0083 * for additional flexibility (beyond the pluggable UI and bean
0084 * support).
0085 * You can find information on how to use the functionality
0086 * this class provides in
0087 * <a href="http://java.sun.com/docs/books/tutorial/uiswing/components/generaltext.html">General Rules for Using Text Components</a>,
0088 * a section in <em>The Java Tutorial.</em>
0089 *
0090 * <p>
0091 * <dl>
0092 * <dt><b><font size=+1>Caret Changes</font></b>
0093 * <dd>
0094 * The caret is a pluggable object in swing text components.
0095 * Notification of changes to the caret position and the selection
0096 * are sent to implementations of the <code>CaretListener</code>
0097 * interface that have been registered with the text component.
0098 * The UI will install a default caret unless a customized caret
0099 * has been set. <br>
0100 * By default the caret tracks all the document changes
0101 * performed on the Event Dispatching Thread and updates it's position
0102 * accordingly if an insertion occurs before or at the caret position
0103 * or a removal occurs before the caret position. <code>DefaultCaret</code>
0104 * tries to make itself visible which may lead to scrolling
0105 * of a text component within <code>JScrollPane</code>. The default caret
0106 * behavior can be changed by the {@link DefaultCaret#setUpdatePolicy} method.
0107 * <br>
0108 * <b>Note</b>: Non-editable text components also have a caret though
0109 * it may not be painted.
0110 *
0111 * <p>
0112 * <dt><b><font size=+1>Commands</font></b>
0113 * <dd>
0114 * Text components provide a number of commands that can be used
0115 * to manipulate the component. This is essentially the way that
0116 * the component expresses its capabilities. These are expressed
0117 * in terms of the swing <code>Action</code> interface,
0118 * using the <code>TextAction</code> implementation.
0119 * The set of commands supported by the text component can be
0120 * found with the {@link #getActions} method. These actions
0121 * can be bound to key events, fired from buttons, etc.
0122 *
0123 * <p>
0124 * <dt><b><font size=+1>Text Input</font></b>
0125 * <dd>
0126 * The text components support flexible and internationalized text input, using
0127 * keymaps and the input method framework, while maintaining compatibility with
0128 * the AWT listener model.
0129 * <p>
0130 * A {@link javax.swing.text.Keymap} lets an application bind key
0131 * strokes to actions.
0132 * In order to allow keymaps to be shared across multiple text components, they
0133 * can use actions that extend <code>TextAction</code>.
0134 * <code>TextAction</code> can determine which <code>JTextComponent</code>
0135 * most recently has or had focus and therefore is the subject of
0136 * the action (In the case that the <code>ActionEvent</code>
0137 * sent to the action doesn't contain the target text component as its source).
0138 * <p>
0139 * The <a href="../../../../technotes/guides/imf/spec.html">input method framework</a>
0140 * lets text components interact with input methods, separate software
0141 * components that preprocess events to let users enter thousands of
0142 * different characters using keyboards with far fewer keys.
0143 * <code>JTextComponent</code> is an <em>active client</em> of
0144 * the framework, so it implements the preferred user interface for interacting
0145 * with input methods. As a consequence, some key events do not reach the text
0146 * component because they are handled by an input method, and some text input
0147 * reaches the text component as committed text within an {@link
0148 * java.awt.event.InputMethodEvent} instead of as a key event.
0149 * The complete text input is the combination of the characters in
0150 * <code>keyTyped</code> key events and committed text in input method events.
0151 * <p>
0152 * The AWT listener model lets applications attach event listeners to
0153 * components in order to bind events to actions. Swing encourages the
0154 * use of keymaps instead of listeners, but maintains compatibility
0155 * with listeners by giving the listeners a chance to steal an event
0156 * by consuming it.
0157 * <p>
0158 * Keyboard event and input method events are handled in the following stages,
0159 * with each stage capable of consuming the event:
0160 *
0161 * <table border=1 summary="Stages of keyboard and input method event handling">
0162 * <tr>
0163 * <th id="stage"><p align="left">Stage</p></th>
0164 * <th id="ke"><p align="left">KeyEvent</p></th>
0165 * <th id="ime"><p align="left">InputMethodEvent</p></th></tr>
0166 * <tr><td headers="stage">1. </td>
0167 * <td headers="ke">input methods </td>
0168 * <td headers="ime">(generated here)</td></tr>
0169 * <tr><td headers="stage">2. </td>
0170 * <td headers="ke">focus manager </td>
0171 * <td headers="ime"></td>
0172 * </tr>
0173 * <tr>
0174 * <td headers="stage">3. </td>
0175 * <td headers="ke">registered key listeners</td>
0176 * <td headers="ime">registered input method listeners</tr>
0177 * <tr>
0178 * <td headers="stage">4. </td>
0179 * <td headers="ke"></td>
0180 * <td headers="ime">input method handling in JTextComponent</tr>
0181 * <tr>
0182 * <td headers="stage">5. </td><td headers="ke ime" colspan=2>keymap handling using the current keymap</td></tr>
0183 * <tr><td headers="stage">6. </td><td headers="ke">keyboard handling in JComponent (e.g. accelerators, component navigation, etc.)</td>
0184 * <td headers="ime"></td></tr>
0185 * </table>
0186 *
0187 * <p>
0188 * To maintain compatibility with applications that listen to key
0189 * events but are not aware of input method events, the input
0190 * method handling in stage 4 provides a compatibility mode for
0191 * components that do not process input method events. For these
0192 * components, the committed text is converted to keyTyped key events
0193 * and processed in the key event pipeline starting at stage 3
0194 * instead of in the input method event pipeline.
0195 * <p>
0196 * By default the component will create a keymap (named <b>DEFAULT_KEYMAP</b>)
0197 * that is shared by all JTextComponent instances as the default keymap.
0198 * Typically a look-and-feel implementation will install a different keymap
0199 * that resolves to the default keymap for those bindings not found in the
0200 * different keymap. The minimal bindings include:
0201 * <ul>
0202 * <li>inserting content into the editor for the
0203 * printable keys.
0204 * <li>removing content with the backspace and del
0205 * keys.
0206 * <li>caret movement forward and backward
0207 * </ul>
0208 *
0209 * <p>
0210 * <dt><b><font size=+1>Model/View Split</font></b>
0211 * <dd>
0212 * The text components have a model-view split. A text component pulls
0213 * together the objects used to represent the model, view, and controller.
0214 * The text document model may be shared by other views which act as observers
0215 * of the model (e.g. a document may be shared by multiple components).
0216 *
0217 * <p align=center><img src="doc-files/editor.gif" alt="Diagram showing interaction between Controller, Document, events, and ViewFactory"
0218 * HEIGHT=358 WIDTH=587></p>
0219 *
0220 * <p>
0221 * The model is defined by the {@link Document} interface.
0222 * This is intended to provide a flexible text storage mechanism
0223 * that tracks change during edits and can be extended to more sophisticated
0224 * models. The model interfaces are meant to capture the capabilities of
0225 * expression given by SGML, a system used to express a wide variety of
0226 * content.
0227 * Each modification to the document causes notification of the
0228 * details of the change to be sent to all observers in the form of a
0229 * {@link DocumentEvent} which allows the views to stay up to date with the model.
0230 * This event is sent to observers that have implemented the
0231 * {@link DocumentListener}
0232 * interface and registered interest with the model being observed.
0233 *
0234 * <p>
0235 * <dt><b><font size=+1>Location Information</font></b>
0236 * <dd>
0237 * The capability of determining the location of text in
0238 * the view is provided. There are two methods, {@link #modelToView}
0239 * and {@link #viewToModel} for determining this information.
0240 *
0241 * <p>
0242 * <dt><b><font size=+1>Undo/Redo support</font></b>
0243 * <dd>
0244 * Support for an edit history mechanism is provided to allow
0245 * undo/redo operations. The text component does not itself
0246 * provide the history buffer by default, but does provide
0247 * the <code>UndoableEdit</code> records that can be used in conjunction
0248 * with a history buffer to provide the undo/redo support.
0249 * The support is provided by the Document model, which allows
0250 * one to attach UndoableEditListener implementations.
0251 *
0252 * <p>
0253 * <dt><b><font size=+1>Thread Safety</font></b>
0254 * <dd>
0255 * The swing text components provide some support of thread
0256 * safe operations. Because of the high level of configurability
0257 * of the text components, it is possible to circumvent the
0258 * protection provided. The protection primarily comes from
0259 * the model, so the documentation of <code>AbstractDocument</code>
0260 * describes the assumptions of the protection provided.
0261 * The methods that are safe to call asynchronously are marked
0262 * with comments.
0263 *
0264 * <p>
0265 * <dt><b><font size=+1>Newlines</font></b>
0266 * <dd>
0267 * For a discussion on how newlines are handled, see
0268 * <a href="DefaultEditorKit.html">DefaultEditorKit</a>.
0269 *
0270 * <p>
0271 * <dt><b><font size=+1>Printing support</font></b>
0272 * <dd>
0273 * Several {@link #print print} methods are provided for basic
0274 * document printing. If more advanced printing is needed, use the
0275 * {@link #getPrintable} method.
0276 * </dl>
0277 *
0278 * <p>
0279 * <strong>Warning:</strong>
0280 * Serialized objects of this class will not be compatible with
0281 * future Swing releases. The current serialization support is
0282 * appropriate for short term storage or RMI between applications running
0283 * the same version of Swing. As of 1.4, support for long term storage
0284 * of all JavaBeans<sup><font size="-2">TM</font></sup>
0285 * has been added to the <code>java.beans</code> package.
0286 * Please see {@link java.beans.XMLEncoder}.
0287 *
0288 * @beaninfo
0289 * attribute: isContainer false
0290 *
0291 * @author Timothy Prinzing
0292 * @author Igor Kushnirskiy (printing support)
0293 * @version 1.235 05/05/07
0294 * @see Document
0295 * @see DocumentEvent
0296 * @see DocumentListener
0297 * @see Caret
0298 * @see CaretEvent
0299 * @see CaretListener
0300 * @see TextUI
0301 * @see View
0302 * @see ViewFactory
0303 */
0304 public abstract class JTextComponent extends JComponent implements
0305 Scrollable, Accessible {
0306 /**
0307 * Creates a new <code>JTextComponent</code>.
0308 * Listeners for caret events are established, and the pluggable
0309 * UI installed. The component is marked as editable. No layout manager
0310 * is used, because layout is managed by the view subsystem of text.
0311 * The document model is set to <code>null</code>.
0312 */
0313 public JTextComponent() {
0314 super ();
0315 // enable InputMethodEvent for on-the-spot pre-editing
0316 enableEvents(AWTEvent.KEY_EVENT_MASK
0317 | AWTEvent.INPUT_METHOD_EVENT_MASK);
0318 caretEvent = new MutableCaretEvent(this );
0319 addMouseListener(caretEvent);
0320 addFocusListener(caretEvent);
0321 setEditable(true);
0322 setDragEnabled(false);
0323 setLayout(null); // layout is managed by View hierarchy
0324 updateUI();
0325 }
0326
0327 /**
0328 * Fetches the user-interface factory for this text-oriented editor.
0329 *
0330 * @return the factory
0331 */
0332 public TextUI getUI() {
0333 return (TextUI) ui;
0334 }
0335
0336 /**
0337 * Sets the user-interface factory for this text-oriented editor.
0338 *
0339 * @param ui the factory
0340 */
0341 public void setUI(TextUI ui) {
0342 super .setUI(ui);
0343 }
0344
0345 /**
0346 * Reloads the pluggable UI. The key used to fetch the
0347 * new interface is <code>getUIClassID()</code>. The type of
0348 * the UI is <code>TextUI</code>. <code>invalidate</code>
0349 * is called after setting the UI.
0350 */
0351 public void updateUI() {
0352 setUI((TextUI) UIManager.getUI(this ));
0353 invalidate();
0354 }
0355
0356 /**
0357 * Adds a caret listener for notification of any changes
0358 * to the caret.
0359 *
0360 * @param listener the listener to be added
0361 * @see javax.swing.event.CaretEvent
0362 */
0363 public void addCaretListener(CaretListener listener) {
0364 listenerList.add(CaretListener.class, listener);
0365 }
0366
0367 /**
0368 * Removes a caret listener.
0369 *
0370 * @param listener the listener to be removed
0371 * @see javax.swing.event.CaretEvent
0372 */
0373 public void removeCaretListener(CaretListener listener) {
0374 listenerList.remove(CaretListener.class, listener);
0375 }
0376
0377 /**
0378 * Returns an array of all the caret listeners
0379 * registered on this text component.
0380 *
0381 * @return all of this component's <code>CaretListener</code>s
0382 * or an empty
0383 * array if no caret listeners are currently registered
0384 *
0385 * @see #addCaretListener
0386 * @see #removeCaretListener
0387 *
0388 * @since 1.4
0389 */
0390 public CaretListener[] getCaretListeners() {
0391 return (CaretListener[]) listenerList
0392 .getListeners(CaretListener.class);
0393 }
0394
0395 /**
0396 * Notifies all listeners that have registered interest for
0397 * notification on this event type. The event instance
0398 * is lazily created using the parameters passed into
0399 * the fire method. The listener list is processed in a
0400 * last-to-first manner.
0401 *
0402 * @param e the event
0403 * @see EventListenerList
0404 */
0405 protected void fireCaretUpdate(CaretEvent e) {
0406 // Guaranteed to return a non-null array
0407 Object[] listeners = listenerList.getListenerList();
0408 // Process the listeners last to first, notifying
0409 // those that are interested in this event
0410 for (int i = listeners.length - 2; i >= 0; i -= 2) {
0411 if (listeners[i] == CaretListener.class) {
0412 ((CaretListener) listeners[i + 1]).caretUpdate(e);
0413 }
0414 }
0415 }
0416
0417 /**
0418 * Associates the editor with a text document.
0419 * The currently registered factory is used to build a view for
0420 * the document, which gets displayed by the editor after revalidation.
0421 * A PropertyChange event ("document") is propagated to each listener.
0422 *
0423 * @param doc the document to display/edit
0424 * @see #getDocument
0425 * @beaninfo
0426 * description: the text document model
0427 * bound: true
0428 * expert: true
0429 */
0430 public void setDocument(Document doc) {
0431 Document old = model;
0432
0433 /*
0434 * aquire a read lock on the old model to prevent notification of
0435 * mutations while we disconnecting the old model.
0436 */
0437 try {
0438 if (old instanceof AbstractDocument) {
0439 ((AbstractDocument) old).readLock();
0440 }
0441 if (accessibleContext != null) {
0442 model
0443 .removeDocumentListener(((AccessibleJTextComponent) accessibleContext));
0444 }
0445 if (inputMethodRequestsHandler != null) {
0446 model
0447 .removeDocumentListener((DocumentListener) inputMethodRequestsHandler);
0448 }
0449 model = doc;
0450
0451 // Set the document's run direction property to match the
0452 // component's ComponentOrientation property.
0453 Boolean runDir = getComponentOrientation().isLeftToRight() ? TextAttribute.RUN_DIRECTION_LTR
0454 : TextAttribute.RUN_DIRECTION_RTL;
0455 if (runDir != doc.getProperty(TextAttribute.RUN_DIRECTION)) {
0456 doc.putProperty(TextAttribute.RUN_DIRECTION, runDir);
0457 }
0458 firePropertyChange("document", old, doc);
0459 } finally {
0460 if (old instanceof AbstractDocument) {
0461 ((AbstractDocument) old).readUnlock();
0462 }
0463 }
0464
0465 revalidate();
0466 repaint();
0467 if (accessibleContext != null) {
0468 model
0469 .addDocumentListener(((AccessibleJTextComponent) accessibleContext));
0470 }
0471 if (inputMethodRequestsHandler != null) {
0472 model
0473 .addDocumentListener((DocumentListener) inputMethodRequestsHandler);
0474 }
0475 }
0476
0477 /**
0478 * Fetches the model associated with the editor. This is
0479 * primarily for the UI to get at the minimal amount of
0480 * state required to be a text editor. Subclasses will
0481 * return the actual type of the model which will typically
0482 * be something that extends Document.
0483 *
0484 * @return the model
0485 */
0486 public Document getDocument() {
0487 return model;
0488 }
0489
0490 // Override of Component.setComponentOrientation
0491 public void setComponentOrientation(ComponentOrientation o) {
0492 // Set the document's run direction property to match the
0493 // ComponentOrientation property.
0494 Document doc = getDocument();
0495 if (doc != null) {
0496 Boolean runDir = o.isLeftToRight() ? TextAttribute.RUN_DIRECTION_LTR
0497 : TextAttribute.RUN_DIRECTION_RTL;
0498 doc.putProperty(TextAttribute.RUN_DIRECTION, runDir);
0499 }
0500 super .setComponentOrientation(o);
0501 }
0502
0503 /**
0504 * Fetches the command list for the editor. This is
0505 * the list of commands supported by the plugged-in UI
0506 * augmented by the collection of commands that the
0507 * editor itself supports. These are useful for binding
0508 * to events, such as in a keymap.
0509 *
0510 * @return the command list
0511 */
0512 public Action[] getActions() {
0513 return getUI().getEditorKit(this ).getActions();
0514 }
0515
0516 /**
0517 * Sets margin space between the text component's border
0518 * and its text. The text component's default <code>Border</code>
0519 * object will use this value to create the proper margin.
0520 * However, if a non-default border is set on the text component,
0521 * it is that <code>Border</code> object's responsibility to create the
0522 * appropriate margin space (else this property will effectively
0523 * be ignored). This causes a redraw of the component.
0524 * A PropertyChange event ("margin") is sent to all listeners.
0525 *
0526 * @param m the space between the border and the text
0527 * @beaninfo
0528 * description: desired space between the border and text area
0529 * bound: true
0530 */
0531 public void setMargin(Insets m) {
0532 Insets old = margin;
0533 margin = m;
0534 firePropertyChange("margin", old, m);
0535 invalidate();
0536 }
0537
0538 /**
0539 * Returns the margin between the text component's border and
0540 * its text.
0541 *
0542 * @return the margin
0543 */
0544 public Insets getMargin() {
0545 return margin;
0546 }
0547
0548 /**
0549 * Sets the <code>NavigationFilter</code>. <code>NavigationFilter</code>
0550 * is used by <code>DefaultCaret</code> and the default cursor movement
0551 * actions as a way to restrict the cursor movement.
0552 *
0553 * @since 1.4
0554 */
0555 public void setNavigationFilter(NavigationFilter filter) {
0556 navigationFilter = filter;
0557 }
0558
0559 /**
0560 * Returns the <code>NavigationFilter</code>. <code>NavigationFilter</code>
0561 * is used by <code>DefaultCaret</code> and the default cursor movement
0562 * actions as a way to restrict the cursor movement. A null return value
0563 * implies the cursor movement and selection should not be restricted.
0564 *
0565 * @since 1.4
0566 * @return the NavigationFilter
0567 */
0568 public NavigationFilter getNavigationFilter() {
0569 return navigationFilter;
0570 }
0571
0572 /**
0573 * Fetches the caret that allows text-oriented navigation over
0574 * the view.
0575 *
0576 * @return the caret
0577 */
0578 public Caret getCaret() {
0579 return caret;
0580 }
0581
0582 /**
0583 * Sets the caret to be used. By default this will be set
0584 * by the UI that gets installed. This can be changed to
0585 * a custom caret if desired. Setting the caret results in a
0586 * PropertyChange event ("caret") being fired.
0587 *
0588 * @param c the caret
0589 * @see #getCaret
0590 * @beaninfo
0591 * description: the caret used to select/navigate
0592 * bound: true
0593 * expert: true
0594 */
0595 public void setCaret(Caret c) {
0596 if (caret != null) {
0597 caret.removeChangeListener(caretEvent);
0598 caret.deinstall(this );
0599 }
0600 Caret old = caret;
0601 caret = c;
0602 if (caret != null) {
0603 caret.install(this );
0604 caret.addChangeListener(caretEvent);
0605 }
0606 firePropertyChange("caret", old, caret);
0607 }
0608
0609 /**
0610 * Fetches the object responsible for making highlights.
0611 *
0612 * @return the highlighter
0613 */
0614 public Highlighter getHighlighter() {
0615 return highlighter;
0616 }
0617
0618 /**
0619 * Sets the highlighter to be used. By default this will be set
0620 * by the UI that gets installed. This can be changed to
0621 * a custom highlighter if desired. The highlighter can be set to
0622 * <code>null</code> to disable it.
0623 * A PropertyChange event ("highlighter") is fired
0624 * when a new highlighter is installed.
0625 *
0626 * @param h the highlighter
0627 * @see #getHighlighter
0628 * @beaninfo
0629 * description: object responsible for background highlights
0630 * bound: true
0631 * expert: true
0632 */
0633 public void setHighlighter(Highlighter h) {
0634 if (highlighter != null) {
0635 highlighter.deinstall(this );
0636 }
0637 Highlighter old = highlighter;
0638 highlighter = h;
0639 if (highlighter != null) {
0640 highlighter.install(this );
0641 }
0642 firePropertyChange("highlighter", old, h);
0643 }
0644
0645 /**
0646 * Sets the keymap to use for binding events to
0647 * actions. Setting to <code>null</code> effectively disables
0648 * keyboard input.
0649 * A PropertyChange event ("keymap") is fired when a new keymap
0650 * is installed.
0651 *
0652 * @param map the keymap
0653 * @see #getKeymap
0654 * @beaninfo
0655 * description: set of key event to action bindings to use
0656 * bound: true
0657 */
0658 public void setKeymap(Keymap map) {
0659 Keymap old = keymap;
0660 keymap = map;
0661 firePropertyChange("keymap", old, keymap);
0662 updateInputMap(old, map);
0663 }
0664
0665 /**
0666 * Turns on or off automatic drag handling. In order to enable automatic
0667 * drag handling, this property should be set to {@code true}, and the
0668 * component's {@code TransferHandler} needs to be {@code non-null}.
0669 * The default value of the {@code dragEnabled} property is {@code false}.
0670 * <p>
0671 * The job of honoring this property, and recognizing a user drag gesture,
0672 * lies with the look and feel implementation, and in particular, the component's
0673 * {@code TextUI}. When automatic drag handling is enabled, most look and
0674 * feels (including those that subclass {@code BasicLookAndFeel}) begin a
0675 * drag and drop operation whenever the user presses the mouse button over
0676 * a selection and then moves the mouse a few pixels. Setting this property to
0677 * {@code true} can therefore have a subtle effect on how selections behave.
0678 * <p>
0679 * If a look and feel is used that ignores this property, you can still
0680 * begin a drag and drop operation by calling {@code exportAsDrag} on the
0681 * component's {@code TransferHandler}.
0682 *
0683 * @param b whether or not to enable automatic drag handling
0684 * @exception HeadlessException if
0685 * <code>b</code> is <code>true</code> and
0686 * <code>GraphicsEnvironment.isHeadless()</code>
0687 * returns <code>true</code>
0688 * @see java.awt.GraphicsEnvironment#isHeadless
0689 * @see #getDragEnabled
0690 * @see #setTransferHandler
0691 * @see TransferHandler
0692 * @since 1.4
0693 *
0694 * @beaninfo
0695 * description: determines whether automatic drag handling is enabled
0696 * bound: false
0697 */
0698 public void setDragEnabled(boolean b) {
0699 if (b && GraphicsEnvironment.isHeadless()) {
0700 throw new HeadlessException();
0701 }
0702 dragEnabled = b;
0703 }
0704
0705 /**
0706 * Returns whether or not automatic drag handling is enabled.
0707 *
0708 * @return the value of the {@code dragEnabled} property
0709 * @see #setDragEnabled
0710 * @since 1.4
0711 */
0712 public boolean getDragEnabled() {
0713 return dragEnabled;
0714 }
0715
0716 /**
0717 * Sets the drop mode for this component. For backward compatibility,
0718 * the default for this property is <code>DropMode.USE_SELECTION</code>.
0719 * Usage of <code>DropMode.INSERT</code> is recommended, however,
0720 * for an improved user experience. It offers similar behavior of dropping
0721 * between text locations, but does so without affecting the actual text
0722 * selection and caret location.
0723 * <p>
0724 * <code>JTextComponents</code> support the following drop modes:
0725 * <ul>
0726 * <li><code>DropMode.USE_SELECTION</code></li>
0727 * <li><code>DropMode.INSERT</code></li>
0728 * </ul>
0729 * <p>
0730 * The drop mode is only meaningful if this component has a
0731 * <code>TransferHandler</code> that accepts drops.
0732 *
0733 * @param dropMode the drop mode to use
0734 * @throws IllegalArgumentException if the drop mode is unsupported
0735 * or <code>null</code>
0736 * @see #getDropMode
0737 * @see #getDropLocation
0738 * @see #setTransferHandler
0739 * @see javax.swing.TransferHandler
0740 * @since 1.6
0741 */
0742 public final void setDropMode(DropMode dropMode) {
0743 if (dropMode != null) {
0744 switch (dropMode) {
0745 case USE_SELECTION:
0746 case INSERT:
0747 this .dropMode = dropMode;
0748 return;
0749 }
0750 }
0751
0752 throw new IllegalArgumentException(dropMode
0753 + ": Unsupported drop mode for text");
0754 }
0755
0756 /**
0757 * Returns the drop mode for this component.
0758 *
0759 * @return the drop mode for this component
0760 * @see #setDropMode
0761 * @since 1.6
0762 */
0763 public final DropMode getDropMode() {
0764 return dropMode;
0765 }
0766
0767 /**
0768 * Calculates a drop location in this component, representing where a
0769 * drop at the given point should insert data.
0770 * <p>
0771 * Note: This method is meant to override
0772 * <code>JComponent.dropLocationForPoint()</code>, which is package-private
0773 * in javax.swing. <code>TransferHandler</code> will detect text components
0774 * and call this method instead via reflection. It's name should therefore
0775 * not be changed.
0776 *
0777 * @param p the point to calculate a drop location for
0778 * @return the drop location, or <code>null</code>
0779 */
0780 DropLocation dropLocationForPoint(Point p) {
0781 Position.Bias[] bias = new Position.Bias[1];
0782 int index = getUI().viewToModel(this , p, bias);
0783
0784 // viewToModel currently returns null for some HTML content
0785 // when the point is within the component's top inset
0786 if (bias[0] == null) {
0787 bias[0] = Position.Bias.Forward;
0788 }
0789
0790 return new DropLocation(p, index, bias[0]);
0791 }
0792
0793 /**
0794 * Called to set or clear the drop location during a DnD operation.
0795 * In some cases, the component may need to use it's internal selection
0796 * temporarily to indicate the drop location. To help facilitate this,
0797 * this method returns and accepts as a parameter a state object.
0798 * This state object can be used to store, and later restore, the selection
0799 * state. Whatever this method returns will be passed back to it in
0800 * future calls, as the state parameter. If it wants the DnD system to
0801 * continue storing the same state, it must pass it back every time.
0802 * Here's how this is used:
0803 * <p>
0804 * Let's say that on the first call to this method the component decides
0805 * to save some state (because it is about to use the selection to show
0806 * a drop index). It can return a state object to the caller encapsulating
0807 * any saved selection state. On a second call, let's say the drop location
0808 * is being changed to something else. The component doesn't need to
0809 * restore anything yet, so it simply passes back the same state object
0810 * to have the DnD system continue storing it. Finally, let's say this
0811 * method is messaged with <code>null</code>. This means DnD
0812 * is finished with this component for now, meaning it should restore
0813 * state. At this point, it can use the state parameter to restore
0814 * said state, and of course return <code>null</code> since there's
0815 * no longer anything to store.
0816 * <p>
0817 * Note: This method is meant to override
0818 * <code>JComponent.setDropLocation()</code>, which is package-private
0819 * in javax.swing. <code>TransferHandler</code> will detect text components
0820 * and call this method instead via reflection. It's name should therefore
0821 * not be changed.
0822 *
0823 * @param location the drop location (as calculated by
0824 * <code>dropLocationForPoint</code>) or <code>null</code>
0825 * if there's no longer a valid drop location
0826 * @param state the state object saved earlier for this component,
0827 * or <code>null</code>
0828 * @param forDrop whether or not the method is being called because an
0829 * actual drop occurred
0830 * @return any saved state for this component, or <code>null</code> if none
0831 */
0832 Object setDropLocation(TransferHandler.DropLocation location,
0833 Object state, boolean forDrop) {
0834
0835 Object retVal = null;
0836 DropLocation textLocation = (DropLocation) location;
0837
0838 if (dropMode == DropMode.USE_SELECTION) {
0839 if (textLocation == null) {
0840 if (state != null) {
0841 /*
0842 * This object represents the state saved earlier.
0843 * If the caret is a DefaultCaret it will be
0844 * an Object array containing, in order:
0845 * - the saved caret mark (Integer)
0846 * - the saved caret dot (Integer)
0847 * - the saved caret visibility (Boolean)
0848 * - the saved mark bias (Position.Bias)
0849 * - the saved dot bias (Position.Bias)
0850 * If the caret is not a DefaultCaret it will
0851 * be similar, but will not contain the dot
0852 * or mark bias.
0853 */
0854 Object[] vals = (Object[]) state;
0855
0856 if (!forDrop) {
0857 if (caret instanceof DefaultCaret) {
0858 ((DefaultCaret) caret).setDot(
0859 ((Integer) vals[0]).intValue(),
0860 (Position.Bias) vals[3]);
0861 ((DefaultCaret) caret).moveDot(
0862 ((Integer) vals[1]).intValue(),
0863 (Position.Bias) vals[4]);
0864 } else {
0865 caret
0866 .setDot(((Integer) vals[0])
0867 .intValue());
0868 caret.moveDot(((Integer) vals[1])
0869 .intValue());
0870 }
0871 }
0872
0873 caret
0874 .setVisible(((Boolean) vals[2])
0875 .booleanValue());
0876 }
0877 } else {
0878 if (dropLocation == null) {
0879 boolean visible;
0880
0881 if (caret instanceof DefaultCaret) {
0882 DefaultCaret dc = (DefaultCaret) caret;
0883 visible = dc.isActive();
0884 retVal = new Object[] {
0885 Integer.valueOf(dc.getMark()),
0886 Integer.valueOf(dc.getDot()),
0887 Boolean.valueOf(visible),
0888 dc.getMarkBias(), dc.getDotBias() };
0889 } else {
0890 visible = caret.isVisible();
0891 retVal = new Object[] {
0892 Integer.valueOf(caret.getMark()),
0893 Integer.valueOf(caret.getDot()),
0894 Boolean.valueOf(visible) };
0895 }
0896
0897 caret.setVisible(true);
0898 } else {
0899 retVal = state;
0900 }
0901
0902 if (caret instanceof DefaultCaret) {
0903 ((DefaultCaret) caret).setDot(textLocation
0904 .getIndex(), textLocation.getBias());
0905 } else {
0906 caret.setDot(textLocation.getIndex());
0907 }
0908 }
0909 } else {
0910 if (textLocation == null) {
0911 if (state != null) {
0912 caret.setVisible(((Boolean) state).booleanValue());
0913 }
0914 } else {
0915 if (dropLocation == null) {
0916 boolean visible = caret instanceof DefaultCaret ? ((DefaultCaret) caret)
0917 .isActive()
0918 : caret.isVisible();
0919 retVal = Boolean.valueOf(visible);
0920 caret.setVisible(false);
0921 } else {
0922 retVal = state;
0923 }
0924 }
0925 }
0926
0927 DropLocation old = dropLocation;
0928 dropLocation = textLocation;
0929 firePropertyChange("dropLocation", old, dropLocation);
0930
0931 return retVal;
0932 }
0933
0934 /**
0935 * Returns the location that this component should visually indicate
0936 * as the drop location during a DnD operation over the component,
0937 * or {@code null} if no location is to currently be shown.
0938 * <p>
0939 * This method is not meant for querying the drop location
0940 * from a {@code TransferHandler}, as the drop location is only
0941 * set after the {@code TransferHandler}'s <code>canImport</code>
0942 * has returned and has allowed for the location to be shown.
0943 * <p>
0944 * When this property changes, a property change event with
0945 * name "dropLocation" is fired by the component.
0946 *
0947 * @return the drop location
0948 * @see #setDropMode
0949 * @see TransferHandler#canImport(TransferHandler.TransferSupport)
0950 * @since 1.6
0951 */
0952 public final DropLocation getDropLocation() {
0953 return dropLocation;
0954 }
0955
0956 /**
0957 * Updates the <code>InputMap</code>s in response to a
0958 * <code>Keymap</code> change.
0959 * @param oldKm the old <code>Keymap</code>
0960 * @param newKm the new <code>Keymap</code>
0961 */
0962 void updateInputMap(Keymap oldKm, Keymap newKm) {
0963 // Locate the current KeymapWrapper.
0964 InputMap km = getInputMap(JComponent.WHEN_FOCUSED);
0965 InputMap last = km;
0966 while (km != null && !(km instanceof KeymapWrapper)) {
0967 last = km;
0968 km = km.getParent();
0969 }
0970 if (km != null) {
0971 // Found it, tweak the InputMap that points to it, as well
0972 // as anything it points to.
0973 if (newKm == null) {
0974 if (last != km) {
0975 last.setParent(km.getParent());
0976 } else {
0977 last.setParent(null);
0978 }
0979 } else {
0980 InputMap newKM = new KeymapWrapper(newKm);
0981 last.setParent(newKM);
0982 if (last != km) {
0983 newKM.setParent(km.getParent());
0984 }
0985 }
0986 } else if (newKm != null) {
0987 km = getInputMap(JComponent.WHEN_FOCUSED);
0988 if (km != null) {
0989 // Couldn't find it.
0990 // Set the parent of WHEN_FOCUSED InputMap to be the new one.
0991 InputMap newKM = new KeymapWrapper(newKm);
0992 newKM.setParent(km.getParent());
0993 km.setParent(newKM);
0994 }
0995 }
0996
0997 // Do the same thing with the ActionMap
0998 ActionMap am = getActionMap();
0999 ActionMap lastAM = am;
1000 while (am != null && !(am instanceof KeymapActionMap)) {
1001 lastAM = am;
1002 am = am.getParent();
1003 }
1004 if (am != null) {
1005 // Found it, tweak the Actionap that points to it, as well
1006 // as anything it points to.
1007 if (newKm == null) {
1008 if (lastAM != am) {
1009 lastAM.setParent(am.getParent());
1010 } else {
1011 lastAM.setParent(null);
1012 }
1013 } else {
1014 ActionMap newAM = new KeymapActionMap(newKm);
1015 lastAM.setParent(newAM);
1016 if (lastAM != am) {
1017 newAM.setParent(am.getParent());
1018 }
1019 }
1020 } else if (newKm != null) {
1021 am = getActionMap();
1022 if (am != null) {
1023 // Couldn't find it.
1024 // Set the parent of ActionMap to be the new one.
1025 ActionMap newAM = new KeymapActionMap(newKm);
1026 newAM.setParent(am.getParent());
1027 am.setParent(newAM);
1028 }
1029 }
1030 }
1031
1032 /**
1033 * Fetches the keymap currently active in this text
1034 * component.
1035 *
1036 * @return the keymap
1037 */
1038 public Keymap getKeymap() {
1039 return keymap;
1040 }
1041
1042 /**
1043 * Adds a new keymap into the keymap hierarchy. Keymap bindings
1044 * resolve from bottom up so an attribute specified in a child
1045 * will override an attribute specified in the parent.
1046 *
1047 * @param nm the name of the keymap (must be unique within the
1048 * collection of named keymaps in the document); the name may
1049 * be <code>null</code> if the keymap is unnamed,
1050 * but the caller is responsible for managing the reference
1051 * returned as an unnamed keymap can't
1052 * be fetched by name
1053 * @param parent the parent keymap; this may be <code>null</code> if
1054 * unspecified bindings need not be resolved in some other keymap
1055 * @return the keymap
1056 */
1057 public static Keymap addKeymap(String nm, Keymap parent) {
1058 Keymap map = new DefaultKeymap(nm, parent);
1059 if (nm != null) {
1060 // add a named keymap, a class of bindings
1061 getKeymapTable().put(nm, map);
1062 }
1063 return map;
1064 }
1065
1066 /**
1067 * Removes a named keymap previously added to the document. Keymaps
1068 * with <code>null</code> names may not be removed in this way.
1069 *
1070 * @param nm the name of the keymap to remove
1071 * @return the keymap that was removed
1072 */
1073 public static Keymap removeKeymap(String nm) {
1074 return getKeymapTable().remove(nm);
1075 }
1076
1077 /**
1078 * Fetches a named keymap previously added to the document.
1079 * This does not work with <code>null</code>-named keymaps.
1080 *
1081 * @param nm the name of the keymap
1082 * @return the keymap
1083 */
1084 public static Keymap getKeymap(String nm) {
1085 return getKeymapTable().get(nm);
1086 }
1087
1088 private static HashMap<String, Keymap> getKeymapTable() {
1089 synchronized (KEYMAP_TABLE) {
1090 AppContext appContext = AppContext.getAppContext();
1091 HashMap<String, Keymap> keymapTable = (HashMap<String, Keymap>) appContext
1092 .get(KEYMAP_TABLE);
1093 if (keymapTable == null) {
1094 keymapTable = new HashMap<String, Keymap>(17);
1095 appContext.put(KEYMAP_TABLE, keymapTable);
1096 //initialize default keymap
1097 Keymap binding = addKeymap(DEFAULT_KEYMAP, null);
1098 binding
1099 .setDefaultAction(new DefaultEditorKit.DefaultKeyTypedAction());
1100 }
1101 return keymapTable;
1102 }
1103 }
1104
1105 /**
1106 * Binding record for creating key bindings.
1107 * <p>
1108 * <strong>Warning:</strong>
1109 * Serialized objects of this class will not be compatible with
1110 * future Swing releases. The current serialization support is
1111 * appropriate for short term storage or RMI between applications running
1112 * the same version of Swing. As of 1.4, support for long term storage
1113 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1114 * has been added to the <code>java.beans</code> package.
1115 * Please see {@link java.beans.XMLEncoder}.
1116 */
1117 public static class KeyBinding {
1118
1119 /**
1120 * The key.
1121 */
1122 public KeyStroke key;
1123
1124 /**
1125 * The name of the action for the key.
1126 */
1127 public String actionName;
1128
1129 /**
1130 * Creates a new key binding.
1131 *
1132 * @param key the key
1133 * @param actionName the name of the action for the key
1134 */
1135 public KeyBinding(KeyStroke key, String actionName) {
1136 this .key = key;
1137 this .actionName = actionName;
1138 }
1139 }
1140
1141 /**
1142 * <p>
1143 * Loads a keymap with a bunch of
1144 * bindings. This can be used to take a static table of
1145 * definitions and load them into some keymap. The following
1146 * example illustrates an example of binding some keys to
1147 * the cut, copy, and paste actions associated with a
1148 * JTextComponent. A code fragment to accomplish
1149 * this might look as follows:
1150 * <pre><code>
1151 *
1152 * static final JTextComponent.KeyBinding[] defaultBindings = {
1153 * new JTextComponent.KeyBinding(
1154 * KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK),
1155 * DefaultEditorKit.copyAction),
1156 * new JTextComponent.KeyBinding(
1157 * KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK),
1158 * DefaultEditorKit.pasteAction),
1159 * new JTextComponent.KeyBinding(
1160 * KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK),
1161 * DefaultEditorKit.cutAction),
1162 * };
1163 *
1164 * JTextComponent c = new JTextPane();
1165 * Keymap k = c.getKeymap();
1166 * JTextComponent.loadKeymap(k, defaultBindings, c.getActions());
1167 *
1168 * </code></pre>
1169 * The sets of bindings and actions may be empty but must be
1170 * non-<code>null</code>.
1171 *
1172 * @param map the keymap
1173 * @param bindings the bindings
1174 * @param actions the set of actions
1175 */
1176 public static void loadKeymap(Keymap map, KeyBinding[] bindings,
1177 Action[] actions) {
1178 Hashtable h = new Hashtable();
1179 for (int i = 0; i < actions.length; i++) {
1180 Action a = actions[i];
1181 String value = (String) a.getValue(Action.NAME);
1182 h.put((value != null ? value : ""), a);
1183 }
1184 for (int i = 0; i < bindings.length; i++) {
1185 Action a = (Action) h.get(bindings[i].actionName);
1186 if (a != null) {
1187 map.addActionForKeyStroke(bindings[i].key, a);
1188 }
1189 }
1190 }
1191
1192 /**
1193 * Returns true if <code>klass</code> is NOT a JTextComponent and it or
1194 * one of its superclasses (stoping at JTextComponent) overrides
1195 * <code>processInputMethodEvent</code>. It is assumed this will be
1196 * invoked from within a <code>doPrivileged</code>, and it is also
1197 * assumed <code>klass</code> extends <code>JTextComponent</code>.
1198 */
1199 private static Boolean isProcessInputMethodEventOverridden(
1200 Class klass) {
1201 if (klass == JTextComponent.class) {
1202 return Boolean.FALSE;
1203 }
1204 Boolean retValue = (Boolean) overrideMap.get(klass.getName());
1205
1206 if (retValue != null) {
1207 return retValue;
1208 }
1209 Boolean sOverriden = isProcessInputMethodEventOverridden(klass
1210 .getSuperclass());
1211
1212 if (sOverriden.booleanValue()) {
1213 // If our superclass has overriden it, then by definition klass
1214 // overrides it.
1215 overrideMap.put(klass.getName(), sOverriden);
1216 return sOverriden;
1217 }
1218 // klass's superclass didn't override it, check for an override in
1219 // klass.
1220 try {
1221 Class[] classes = new Class[1];
1222 classes[0] = InputMethodEvent.class;
1223
1224 Method m = klass.getDeclaredMethod(
1225 "processInputMethodEvent", classes);
1226 retValue = Boolean.TRUE;
1227 } catch (NoSuchMethodException nsme) {
1228 retValue = Boolean.FALSE;
1229 }
1230 overrideMap.put(klass.getName(), retValue);
1231 return retValue;
1232 }
1233
1234 /**
1235 * Fetches the current color used to render the
1236 * caret.
1237 *
1238 * @return the color
1239 */
1240 public Color getCaretColor() {
1241 return caretColor;
1242 }
1243
1244 /**
1245 * Sets the current color used to render the caret.
1246 * Setting to <code>null</code> effectively restores the default color.
1247 * Setting the color results in a PropertyChange event ("caretColor")
1248 * being fired.
1249 *
1250 * @param c the color
1251 * @see #getCaretColor
1252 * @beaninfo
1253 * description: the color used to render the caret
1254 * bound: true
1255 * preferred: true
1256 */
1257 public void setCaretColor(Color c) {
1258 Color old = caretColor;
1259 caretColor = c;
1260 firePropertyChange("caretColor", old, caretColor);
1261 }
1262
1263 /**
1264 * Fetches the current color used to render the
1265 * selection.
1266 *
1267 * @return the color
1268 */
1269 public Color getSelectionColor() {
1270 return selectionColor;
1271 }
1272
1273 /**
1274 * Sets the current color used to render the selection.
1275 * Setting the color to <code>null</code> is the same as setting
1276 * <code>Color.white</code>. Setting the color results in a
1277 * PropertyChange event ("selectionColor").
1278 *
1279 * @param c the color
1280 * @see #getSelectionColor
1281 * @beaninfo
1282 * description: color used to render selection background
1283 * bound: true
1284 * preferred: true
1285 */
1286 public void setSelectionColor(Color c) {
1287 Color old = selectionColor;
1288 selectionColor = c;
1289 firePropertyChange("selectionColor", old, selectionColor);
1290 }
1291
1292 /**
1293 * Fetches the current color used to render the
1294 * selected text.
1295 *
1296 * @return the color
1297 */
1298 public Color getSelectedTextColor() {
1299 return selectedTextColor;
1300 }
1301
1302 /**
1303 * Sets the current color used to render the selected text.
1304 * Setting the color to <code>null</code> is the same as
1305 * <code>Color.black</code>. Setting the color results in a
1306 * PropertyChange event ("selectedTextColor") being fired.
1307 *
1308 * @param c the color
1309 * @see #getSelectedTextColor
1310 * @beaninfo
1311 * description: color used to render selected text
1312 * bound: true
1313 * preferred: true
1314 */
1315 public void setSelectedTextColor(Color c) {
1316 Color old = selectedTextColor;
1317 selectedTextColor = c;
1318 firePropertyChange("selectedTextColor", old, selectedTextColor);
1319 }
1320
1321 /**
1322 * Fetches the current color used to render the
1323 * disabled text.
1324 *
1325 * @return the color
1326 */
1327 public Color getDisabledTextColor() {
1328 return disabledTextColor;
1329 }
1330
1331 /**
1332 * Sets the current color used to render the
1333 * disabled text. Setting the color fires off a
1334 * PropertyChange event ("disabledTextColor").
1335 *
1336 * @param c the color
1337 * @see #getDisabledTextColor
1338 * @beaninfo
1339 * description: color used to render disabled text
1340 * bound: true
1341 * preferred: true
1342 */
1343 public void setDisabledTextColor(Color c) {
1344 Color old = disabledTextColor;
1345 disabledTextColor = c;
1346 firePropertyChange("disabledTextColor", old, disabledTextColor);
1347 }
1348
1349 /**
1350 * Replaces the currently selected content with new content
1351 * represented by the given string. If there is no selection
1352 * this amounts to an insert of the given text. If there
1353 * is no replacement text this amounts to a removal of the
1354 * current selection.
1355 * <p>
1356 * This is the method that is used by the default implementation
1357 * of the action for inserting content that gets bound to the
1358 * keymap actions.
1359 * <p>
1360 * This method is thread safe, although most Swing methods
1361 * are not. Please see
1362 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
1363 * to Use Threads</A> for more information.
1364 *
1365 * @param content the content to replace the selection with
1366 */
1367 public void replaceSelection(String content) {
1368 Document doc = getDocument();
1369 if (doc != null) {
1370 try {
1371 boolean composedTextSaved = saveComposedText(caret
1372 .getDot());
1373 int p0 = Math.min(caret.getDot(), caret.getMark());
1374 int p1 = Math.max(caret.getDot(), caret.getMark());
1375 if (doc instanceof AbstractDocument) {
1376 ((AbstractDocument) doc).replace(p0, p1 - p0,
1377 content, null);
1378 } else {
1379 if (p0 != p1) {
1380 doc.remove(p0, p1 - p0);
1381 }
1382 if (content != null && content.length() > 0) {
1383 doc.insertString(p0, content, null);
1384 }
1385 }
1386 if (composedTextSaved) {
1387 restoreComposedText();
1388 }
1389 } catch (BadLocationException e) {
1390 UIManager.getLookAndFeel().provideErrorFeedback(
1391 JTextComponent.this );
1392 }
1393 }
1394 }
1395
1396 /**
1397 * Fetches a portion of the text represented by the
1398 * component. Returns an empty string if length is 0.
1399 *
1400 * @param offs the offset >= 0
1401 * @param len the length >= 0
1402 * @return the text
1403 * @exception BadLocationException if the offset or length are invalid
1404 */
1405 public String getText(int offs, int len)
1406 throws BadLocationException {
1407 return getDocument().getText(offs, len);
1408 }
1409
1410 /**
1411 * Converts the given location in the model to a place in
1412 * the view coordinate system.
1413 * The component must have a positive size for
1414 * this translation to be computed (i.e. layout cannot
1415 * be computed until the component has been sized). The
1416 * component does not have to be visible or painted.
1417 *
1418 * @param pos the position >= 0
1419 * @return the coordinates as a rectangle, with (r.x, r.y) as the location
1420 * in the coordinate system, or null if the component does
1421 * not yet have a positive size.
1422 * @exception BadLocationException if the given position does not
1423 * represent a valid location in the associated document
1424 * @see TextUI#modelToView
1425 */
1426 public Rectangle modelToView(int pos) throws BadLocationException {
1427 return getUI().modelToView(this , pos);
1428 }
1429
1430 /**
1431 * Converts the given place in the view coordinate system
1432 * to the nearest representative location in the model.
1433 * The component must have a positive size for
1434 * this translation to be computed (i.e. layout cannot
1435 * be computed until the component has been sized). The
1436 * component does not have to be visible or painted.
1437 *
1438 * @param pt the location in the view to translate
1439 * @return the offset >= 0 from the start of the document,
1440 * or -1 if the component does not yet have a positive
1441 * size.
1442 * @see TextUI#viewToModel
1443 */
1444 public int viewToModel(Point pt) {
1445 return getUI().viewToModel(this , pt);
1446 }
1447
1448 /**
1449 * Transfers the currently selected range in the associated
1450 * text model to the system clipboard, removing the contents
1451 * from the model. The current selection is reset. Does nothing
1452 * for <code>null</code> selections.
1453 *
1454 * @see java.awt.Toolkit#getSystemClipboard
1455 * @see java.awt.datatransfer.Clipboard
1456 */
1457 public void cut() {
1458 if (isEditable() && isEnabled()) {
1459 invokeAction("cut", TransferHandler.getCutAction());
1460 }
1461 }
1462
1463 /**
1464 * Transfers the currently selected range in the associated
1465 * text model to the system clipboard, leaving the contents
1466 * in the text model. The current selection remains intact.
1467 * Does nothing for <code>null</code> selections.
1468 *
1469 * @see java.awt.Toolkit#getSystemClipboard
1470 * @see java.awt.datatransfer.Clipboard
1471 */
1472 public void copy() {
1473 invokeAction("copy", TransferHandler.getCopyAction());
1474 }
1475
1476 /**
1477 * Transfers the contents of the system clipboard into the
1478 * associated text model. If there is a selection in the
1479 * associated view, it is replaced with the contents of the
1480 * clipboard. If there is no selection, the clipboard contents
1481 * are inserted in front of the current insert position in
1482 * the associated view. If the clipboard is empty, does nothing.
1483 *
1484 * @see #replaceSelection
1485 * @see java.awt.Toolkit#getSystemClipboard
1486 * @see java.awt.datatransfer.Clipboard
1487 */
1488 public void paste() {
1489 if (isEditable() && isEnabled()) {
1490 invokeAction("paste", TransferHandler.getPasteAction());
1491 }
1492 }
1493
1494 /**
1495 * This is a conveniance method that is only useful for
1496 * <code>cut</code>, <code>copy</code> and <code>paste</code>. If
1497 * an <code>Action</code> with the name <code>name</code> does not
1498 * exist in the <code>ActionMap</code>, this will attemp to install a
1499 * <code>TransferHandler</code> and then use <code>altAction</code>.
1500 */
1501 private void invokeAction(String name, Action altAction) {
1502 ActionMap map = getActionMap();
1503 Action action = null;
1504
1505 if (map != null) {
1506 action = map.get(name);
1507 }
1508 if (action == null) {
1509 installDefaultTransferHandlerIfNecessary();
1510 action = altAction;
1511 }
1512 action.actionPerformed(new ActionEvent(this ,
1513 ActionEvent.ACTION_PERFORMED, (String) action
1514 .getValue(Action.NAME), EventQueue
1515 .getMostRecentEventTime(),
1516 getCurrentEventModifiers()));
1517 }
1518
1519 /**
1520 * If the current <code>TransferHandler</code> is null, this will
1521 * install a new one.
1522 */
1523 private void installDefaultTransferHandlerIfNecessary() {
1524 if (getTransferHandler() == null) {
1525 if (defaultTransferHandler == null) {
1526 defaultTransferHandler = new DefaultTransferHandler();
1527 }
1528 setTransferHandler(defaultTransferHandler);
1529 }
1530 }
1531
1532 /**
1533 * Moves the caret to a new position, leaving behind a mark
1534 * defined by the last time <code>setCaretPosition</code> was
1535 * called. This forms a selection.
1536 * If the document is <code>null</code>, does nothing. The position
1537 * must be between 0 and the length of the component's text or else
1538 * an exception is thrown.
1539 *
1540 * @param pos the position
1541 * @exception IllegalArgumentException if the value supplied
1542 * for <code>position</code> is less than zero or greater
1543 * than the component's text length
1544 * @see #setCaretPosition
1545 */
1546 public void moveCaretPosition(int pos) {
1547 Document doc = getDocument();
1548 if (doc != null) {
1549 if (pos > doc.getLength() || pos < 0) {
1550 throw new IllegalArgumentException("bad position: "
1551 + 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,
1645 doc.getLength());
1646 } catch (BadLocationException e) {
1647 throw new IOException(e.getMessage());
1648 }
1649 }
1650
1651 public void removeNotify() {
1652 super .removeNotify();
1653 if (getFocusedComponent() == this ) {
1654 AppContext.getAppContext().remove(FOCUSED_COMPONENT);
1655 }
1656 }
1657
1658 // --- java.awt.TextComponent methods ------------------------
1659
1660 /**
1661 * Sets the position of the text insertion caret for the
1662 * <code>TextComponent</code>. Note that the caret tracks change,
1663 * so this may move if the underlying text of the component is changed.
1664 * If the document is <code>null</code>, does nothing. The position
1665 * must be between 0 and the length of the component's text or else
1666 * an exception is thrown.
1667 *
1668 * @param position the position
1669 * @exception IllegalArgumentException if the value supplied
1670 * for <code>position</code> is less than zero or greater
1671 * than the component's text length
1672 * @beaninfo
1673 * description: the caret position
1674 */
1675 public void setCaretPosition(int position) {
1676 Document doc = getDocument();
1677 if (doc != null) {
1678 if (position > doc.getLength() || position < 0) {
1679 throw new IllegalArgumentException("bad position: "
1680 + position);
1681 }
1682 caret.setDot(position);
1683 }
1684 }
1685
1686 /**
1687 * Returns the position of the text insertion caret for the
1688 * text component.
1689 *
1690 * @return the position of the text insertion caret for the
1691 * text component >= 0
1692 */
1693 public int getCaretPosition() {
1694 return caret.getDot();
1695 }
1696
1697 /**
1698 * Sets the text of this <code>TextComponent</code>
1699 * to the specified text. If the text is <code>null</code>
1700 * or empty, has the effect of simply deleting the old text.
1701 * When text has been inserted, the resulting caret location
1702 * is determined by the implementation of the caret class.
1703 * <p>
1704 * This method is thread safe, although most Swing methods
1705 * are not. Please see
1706 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
1707 * to Use Threads</A> for more information.
1708 *
1709 * Note that text is not a bound property, so no <code>PropertyChangeEvent
1710 * </code> is fired when it changes. To listen for changes to the text,
1711 * use <code>DocumentListener</code>.
1712 *
1713 * @param t the new text to be set
1714 * @see #getText
1715 * @see DefaultCaret
1716 * @beaninfo
1717 * description: the text of this component
1718 */
1719 public void setText(String t) {
1720 try {
1721 Document doc = getDocument();
1722 if (doc instanceof AbstractDocument) {
1723 ((AbstractDocument) doc).replace(0, doc.getLength(), t,
1724 null);
1725 } else {
1726 doc.remove(0, doc.getLength());
1727 doc.insertString(0, t, null);
1728 }
1729 } catch (BadLocationException e) {
1730 UIManager.getLookAndFeel().provideErrorFeedback(
1731 JTextComponent.this );
1732 }
1733 }
1734
1735 /**
1736 * Returns the text contained in this <code>TextComponent</code>.
1737 * If the underlying document is <code>null</code>,
1738 * will give a <code>NullPointerException</code>.
1739 *
1740 * Note that text is not a bound property, so no <code>PropertyChangeEvent
1741 * </code> is fired when it changes. To listen for changes to the text,
1742 * use <code>DocumentListener</code>.
1743 *
1744 * @return the text
1745 * @exception NullPointerException if the document is <code>null</code>
1746 * @see #setText
1747 */
1748 public String getText() {
1749 Document doc = getDocument();
1750 String txt;
1751 try {
1752 txt = doc.getText(0, doc.getLength());
1753 } catch (BadLocationException e) {
1754 txt = null;
1755 }
1756 return txt;
1757 }
1758
1759 /**
1760 * Returns the selected text contained in this
1761 * <code>TextComponent</code>. If the selection is
1762 * <code>null</code> or the document empty, returns <code>null</code>.
1763 *
1764 * @return the text
1765 * @exception IllegalArgumentException if the selection doesn't
1766 * have a valid mapping into the document for some reason
1767 * @see #setText
1768 */
1769 public String getSelectedText() {
1770 String txt = null;
1771 int p0 = Math.min(caret.getDot(), caret.getMark());
1772 int p1 = Math.max(caret.getDot(), caret.getMark());
1773 if (p0 != p1) {
1774 try {
1775 Document doc = getDocument();
1776 txt = doc.getText(p0, p1 - p0);
1777 } catch (BadLocationException e) {
1778 throw new IllegalArgumentException(e.getMessage());
1779 }
1780 }
1781 return txt;
1782 }
1783
1784 /**
1785 * Returns the boolean indicating whether this
1786 * <code>TextComponent</code> is editable or not.
1787 *
1788 * @return the boolean value
1789 * @see #setEditable
1790 */
1791 public boolean isEditable() {
1792 return editable;
1793 }
1794
1795 /**
1796 * Sets the specified boolean to indicate whether or not this
1797 * <code>TextComponent</code> should be editable.
1798 * A PropertyChange event ("editable") is fired when the
1799 * state is changed.
1800 *
1801 * @param b the boolean to be set
1802 * @see #isEditable
1803 * @beaninfo
1804 * description: specifies if the text can be edited
1805 * bound: true
1806 */
1807 public void setEditable(boolean b) {
1808 if (b != editable) {
1809 boolean oldVal = editable;
1810 editable = b;
1811 enableInputMethods(editable);
1812 firePropertyChange("editable", Boolean.valueOf(oldVal),
1813 Boolean.valueOf(editable));
1814 repaint();
1815 }
1816 }
1817
1818 /**
1819 * Returns the selected text's start position. Return 0 for an
1820 * empty document, or the value of dot if no selection.
1821 *
1822 * @return the start position >= 0
1823 */
1824 public int getSelectionStart() {
1825 int start = Math.min(caret.getDot(), caret.getMark());
1826 return start;
1827 }
1828
1829 /**
1830 * Sets the selection start to the specified position. The new
1831 * starting point is constrained to be before or at the current
1832 * selection end.
1833 * <p>
1834 * This is available for backward compatibility to code
1835 * that called this method on <code>java.awt.TextComponent</code>.
1836 * This is implemented to forward to the <code>Caret</code>
1837 * implementation which is where the actual selection is maintained.
1838 *
1839 * @param selectionStart the start position of the text >= 0
1840 * @beaninfo
1841 * description: starting location of the selection.
1842 */
1843 public void setSelectionStart(int selectionStart) {
1844 /* Route through select method to enforce consistent policy
1845 * between selectionStart and selectionEnd.
1846 */
1847 select(selectionStart, getSelectionEnd());
1848 }
1849
1850 /**
1851 * Returns the selected text's end position. Return 0 if the document
1852 * is empty, or the value of dot if there is no selection.
1853 *
1854 * @return the end position >= 0
1855 */
1856 public int getSelectionEnd() {
1857 int end = Math.max(caret.getDot(), caret.getMark());
1858 return end;
1859 }
1860
1861 /**
1862 * Sets the selection end to the specified position. The new
1863 * end point is constrained to be at or after the current
1864 * selection start.
1865 * <p>
1866 * This is available for backward compatibility to code
1867 * that called this method on <code>java.awt.TextComponent</code>.
1868 * This is implemented to forward to the <code>Caret</code>
1869 * implementation which is where the actual selection is maintained.
1870 *
1871 * @param selectionEnd the end position of the text >= 0
1872 * @beaninfo
1873 * description: ending location of the selection.
1874 */
1875 public void setSelectionEnd(int selectionEnd) {
1876 /* Route through select method to enforce consistent policy
1877 * between selectionStart and selectionEnd.
1878 */
1879 select(getSelectionStart(), selectionEnd);
1880 }
1881
1882 /**
1883 * Selects the text between the specified start and end positions.
1884 * <p>
1885 * This method sets the start and end positions of the
1886 * selected text, enforcing the restriction that the start position
1887 * must be greater than or equal to zero. The end position must be
1888 * greater than or equal to the start position, and less than or
1889 * equal to the length of the text component's text.
1890 * <p>
1891 * If the caller supplies values that are inconsistent or out of
1892 * bounds, the method enforces these constraints silently, and
1893 * without failure. Specifically, if the start position or end
1894 * position is greater than the length of the text, it is reset to
1895 * equal the text length. If the start position is less than zero,
1896 * it is reset to zero, and if the end position is less than the
1897 * start position, it is reset to the start position.
1898 * <p>
1899 * This call is provided for backward compatibility.
1900 * It is routed to a call to <code>setCaretPosition</code>
1901 * followed by a call to <code>moveCaretPosition</code>.
1902 * The preferred way to manage selection is by calling
1903 * those methods directly.
1904 *
1905 * @param selectionStart the start position of the text
1906 * @param selectionEnd the end position of the text
1907 * @see #setCaretPosition
1908 * @see #moveCaretPosition
1909 */
1910 public void select(int selectionStart, int selectionEnd) {
1911 // argument adjustment done by java.awt.TextComponent
1912 int docLength = getDocument().getLength();
1913
1914 if (selectionStart < 0) {
1915 selectionStart = 0;
1916 }
1917 if (selectionStart > docLength) {
1918 selectionStart = docLength;
1919 }
1920 if (selectionEnd > docLength) {
1921 selectionEnd = docLength;
1922 }
1923 if (selectionEnd < selectionStart) {
1924 selectionEnd = selectionStart;
1925 }
1926
1927 setCaretPosition(selectionStart);
1928 moveCaretPosition(selectionEnd);
1929 }
1930
1931 /**
1932 * Selects all the text in the <code>TextComponent</code>.
1933 * Does nothing on a <code>null</code> or empty document.
1934 */
1935 public void selectAll() {
1936 Document doc = getDocument();
1937 if (doc != null) {
1938 setCaretPosition(0);
1939 moveCaretPosition(doc.getLength());
1940 }
1941 }
1942
1943 // --- Tooltip Methods ---------------------------------------------
1944
1945 /**
1946 * Returns the string to be used as the tooltip for <code>event</code>.
1947 * This will return one of:
1948 * <ol>
1949 * <li>If <code>setToolTipText</code> has been invoked with a
1950 * non-<code>null</code>
1951 * value, it will be returned, otherwise
1952 * <li>The value from invoking <code>getToolTipText</code> on
1953 * the UI will be returned.
1954 * </ol>
1955 * By default <code>JTextComponent</code> does not register
1956 * itself with the <code>ToolTipManager</code>.
1957 * This means that tooltips will NOT be shown from the
1958 * <code>TextUI</code> unless <code>registerComponent</code> has
1959 * been invoked on the <code>ToolTipManager</code>.
1960 *
1961 * @param event the event in question
1962 * @return the string to be used as the tooltip for <code>event</code>
1963 * @see javax.swing.JComponent#setToolTipText
1964 * @see javax.swing.plaf.TextUI#getToolTipText
1965 * @see javax.swing.ToolTipManager#registerComponent
1966 */
1967 public String getToolTipText(MouseEvent event) {
1968 String retValue = super .getToolTipText(event);
1969
1970 if (retValue == null) {
1971 TextUI ui = getUI();
1972 if (ui != null) {
1973 retValue = ui.getToolTipText(this , new Point(event
1974 .getX(), event.getY()));
1975 }
1976 }
1977 return retValue;
1978 }
1979
1980 // --- Scrollable methods ---------------------------------------------
1981
1982 /**
1983 * Returns the preferred size of the viewport for a view component.
1984 * This is implemented to do the default behavior of returning
1985 * the preferred size of the component.
1986 *
1987 * @return the <code>preferredSize</code> of a <code>JViewport</code>
1988 * whose view is this <code>Scrollable</code>
1989 */
1990 public Dimension getPreferredScrollableViewportSize() {
1991 return getPreferredSize();
1992 }
1993
1994 /**
1995 * Components that display logical rows or columns should compute
1996 * the scroll increment that will completely expose one new row
1997 * or column, depending on the value of orientation. Ideally,
1998 * components should handle a partially exposed row or column by
1999 * returning the distance required to completely expose the item.
2000 * <p>
2001 * The default implementation of this is to simply return 10% of
2002 * the visible area. Subclasses are likely to be able to provide
2003 * a much more reasonable value.
2004 *
2005 * @param visibleRect the view area visible within the viewport
2006 * @param orientation either <code>SwingConstants.VERTICAL</code> or
2007 * <code>SwingConstants.HORIZONTAL</code>
2008 * @param direction less than zero to scroll up/left, greater than
2009 * zero for down/right
2010 * @return the "unit" increment for scrolling in the specified direction
2011 * @exception IllegalArgumentException for an invalid orientation
2012 * @see JScrollBar#setUnitIncrement
2013 */
2014 public int getScrollableUnitIncrement(Rectangle visibleRect,
2015 int orientation, int direction) {
2016 switch (orientation) {
2017 case SwingConstants.VERTICAL:
2018 return visibleRect.height / 10;
2019 case SwingConstants.HORIZONTAL:
2020 return visibleRect.width / 10;
2021 default:
2022 throw new IllegalArgumentException("Invalid orientation: "
2023 + orientation);
2024 }
2025 }
2026
2027 /**
2028 * Components that display logical rows or columns should compute
2029 * the scroll increment that will completely expose one block
2030 * of rows or columns, depending on the value of orientation.
2031 * <p>
2032 * The default implementation of this is to simply return the visible
2033 * area. Subclasses will likely be able to provide a much more
2034 * reasonable value.
2035 *
2036 * @param visibleRect the view area visible within the viewport
2037 * @param orientation either <code>SwingConstants.VERTICAL</code> or
2038 * <code>SwingConstants.HORIZONTAL</code>
2039 * @param direction less than zero to scroll up/left, greater than zero
2040 * for down/right
2041 * @return the "block" increment for scrolling in the specified direction
2042 * @exception IllegalArgumentException for an invalid orientation
2043 * @see JScrollBar#setBlockIncrement
2044 */
2045 public int getScrollableBlockIncrement(Rectangle visibleRect,
2046 int orientation, int direction) {
2047 switch (orientation) {
2048 case SwingConstants.VERTICAL:
2049 return visibleRect.height;
2050 case SwingConstants.HORIZONTAL:
2051 return visibleRect.width;
2052 default:
2053 throw new IllegalArgumentException("Invalid orientation: "
2054 + orientation);
2055 }
2056 }
2057
2058 /**
2059 * Returns true if a viewport should always force the width of this
2060 * <code>Scrollable</code> to match the width of the viewport.
2061 * For example a normal text view that supported line wrapping
2062 * would return true here, since it would be undesirable for
2063 * wrapped lines to disappear beyond the right
2064 * edge of the viewport. Note that returning true for a
2065 * <code>Scrollable</code> whose ancestor is a <code>JScrollPane</code>
2066 * effectively disables horizontal scrolling.
2067 * <p>
2068 * Scrolling containers, like <code>JViewport</code>,
2069 * will use this method each time they are validated.
2070 *
2071 * @return true if a viewport should force the <code>Scrollable</code>s
2072 * width to match its own
2073 */
2074 public boolean getScrollableTracksViewportWidth() {
2075 if (getParent() instanceof JViewport) {
2076 return (((JViewport) getParent()).getWidth() > getPreferredSize().width);
2077 }
2078 return false;
2079 }
2080
2081 /**
2082 * Returns true if a viewport should always force the height of this
2083 * <code>Scrollable</code> to match the height of the viewport.
2084 * For example a columnar text view that flowed text in left to
2085 * right columns could effectively disable vertical scrolling by
2086 * returning true here.
2087 * <p>
2088 * Scrolling containers, like <code>JViewport</code>,
2089 * will use this method each time they are validated.
2090 *
2091 * @return true if a viewport should force the Scrollables height
2092 * to match its own
2093 */
2094 public boolean getScrollableTracksViewportHeight() {
2095 if (getParent() instanceof JViewport) {
2096 return (((JViewport) getParent()).getHeight() > getPreferredSize().height);
2097 }
2098 return false;
2099 }
2100
2101 //////////////////
2102 // Printing Support
2103 //////////////////
2104
2105 /**
2106 * A convenience print method that displays a print dialog, and then
2107 * prints this {@code JTextComponent} in <i>interactive</i> mode with no
2108 * header or footer text. Note: this method
2109 * blocks until printing is done.
2110 * <p>
2111 * Note: In <i>headless</i> mode, no dialogs will be shown.
2112 *
2113 * <p> This method calls the full featured
2114 * {@link #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean)
2115 * print} method to perform printing.
2116 * @return {@code true}, unless printing is canceled by the user
2117 * @throws PrinterException if an error in the print system causes the job
2118 * to be aborted
2119 * @throws SecurityException if this thread is not allowed to
2120 * initiate a print job request
2121 *
2122 * @see #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean)
2123 *
2124 * @since 1.6
2125 */
2126
2127 public boolean print() throws PrinterException {
2128 return print(null, null, true, null, null, true);
2129 }
2130
2131 /**
2132 * A convenience print method that displays a print dialog, and then
2133 * prints this {@code JTextComponent} in <i>interactive</i> mode with
2134 * the specified header and footer text. Note: this method
2135 * blocks until printing is done.
2136 * <p>
2137 * Note: In <i>headless</i> mode, no dialogs will be shown.
2138 *
2139 * <p> This method calls the full featured
2140 * {@link #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean)
2141 * print} method to perform printing.
2142 * @param headerFormat the text, in {@code MessageFormat}, to be
2143 * used as the header, or {@code null} for no header
2144 * @param footerFormat the text, in {@code MessageFormat}, to be
2145 * used as the footer, or {@code null} for no footer
2146 * @return {@code true}, unless printing is canceled by the user
2147 * @throws PrinterException if an error in the print system causes the job
2148 * to be aborted
2149 * @throws SecurityException if this thread is not allowed to
2150 * initiate a print job request
2151 *
2152 * @see #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean)
2153 * @see java.text.MessageFormat
2154 * @since 1.6
2155 */
2156 public boolean print(final MessageFormat headerFormat,
2157 final MessageFormat footerFormat) throws PrinterException {
2158 return print(headerFormat, footerFormat, true, null, null, true);
2159 }
2160
2161 /**
2162 * Prints the content of this {@code JTextComponent}. Note: this method
2163 * blocks until printing is done.
2164 *
2165 * <p>
2166 * Page header and footer text can be added to the output by providing
2167 * {@code MessageFormat} arguments. The printing code requests
2168 * {@code Strings} from the formats, providing a single item which may be
2169 * included in the formatted string: an {@code Integer} representing the
2170 * current page number.
2171 *
2172 * <p>
2173 * {@code showPrintDialog boolean} parameter allows you to specify whether
2174 * a print dialog is displayed to the user. When it is, the user
2175 * may use the dialog to change printing attributes or even cancel the
2176 * print.
2177 *
2178 * <p>
2179 * {@code service} allows you to provide the initial
2180 * {@code PrintService} for the print dialog, or to specify
2181 * {@code PrintService} to print to when the dialog is not shown.
2182 *
2183 * <p>
2184 * {@code attributes} can be used to provide the
2185 * initial values for the print dialog, or to supply any needed
2186 * attributes when the dialog is not shown. {@code attributes} can
2187 * be used to control how the job will print, for example
2188 * <i>duplex</i> or <i>single-sided</i>.
2189 *
2190 * <p>
2191 * {@code interactive boolean} parameter allows you to specify
2192 * whether to perform printing in <i>interactive</i>
2193 * mode. If {@code true}, a progress dialog, with an abort option,
2194 * is displayed for the duration of printing. This dialog is
2195 * <i>modal</i> when {@code print} is invoked on the <i>Event Dispatch
2196 * Thread</i> and <i>non-modal</i> otherwise. <b>Warning</b>:
2197 * calling this method on the <i>Event Dispatch Thread</i> with {@code
2198 * interactive false} blocks <i>all</i> events, including repaints, from
2199 * being processed until printing is complete. It is only
2200 * recommended when printing from an application with no
2201 * visible GUI.
2202 *
2203 * <p>
2204 * Note: In <i>headless</i> mode, {@code showPrintDialog} and
2205 * {@code interactive} parameters are ignored and no dialogs are
2206 * shown.
2207 *
2208 * <p>
2209 * This method ensures the {@code document} is not mutated during printing.
2210 * To indicate it visually, {@code setEnabled(false)} is set for the
2211 * duration of printing.
2212 *
2213 * <p>
2214 * This method uses {@link #getPrintable} to render document content.
2215 *
2216 * <p>
2217 * This method is thread-safe, although most Swing methods are not. Please
2218 * see <A
2219 * HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">
2220 * How to Use Threads</A> for more information.
2221 *
2222 * <p>
2223 * <b>Sample Usage</b>. This code snippet shows a cross-platform print
2224 * dialog and then prints the {@code JTextComponent} in <i>interactive</i> mode
2225 * unless the user cancels the dialog:
2226 *
2227 * <pre>
2228 * textComponent.print(new MessageFormat("My text component header"),
2229 * new MessageFormat("Footer. Page - {0}"), true, null, null, true);
2230 * </pre>
2231 * <p>
2232 * Executing this code off the <i>Event Dispatch Thread</i>
2233 * performs printing on the <i>background</i>.
2234 * The following pattern might be used for <i>background</i>
2235 * printing:
2236 * <pre>
2237 * FutureTask<Boolean> future =
2238 * new FutureTask<Boolean>(
2239 * new Callable<Boolean>() {
2240 * public Boolean call() {
2241 * return textComponent.print(.....);
2242 * }
2243 * });
2244 * executor.execute(future);
2245 * </pre>
2246 *
2247 * @param headerFormat the text, in {@code MessageFormat}, to be
2248 * used as the header, or {@code null} for no header
2249 * @param footerFormat the text, in {@code MessageFormat}, to be
2250 * used as the footer, or {@code null} for no footer
2251 * @param showPrintDialog {@code true} to display a print dialog,
2252 * {@code false} otherwise
2253 * @param service initial {@code PrintService}, or {@code null} for the
2254 * default
2255 * @param attributes the job attributes to be applied to the print job, or
2256 * {@code null} for none
2257 * @param interactive whether to print in an interactive mode
2258 * @return {@code true}, unless printing is canceled by the user
2259 * @throws PrinterException if an error in the print system causes the job
2260 * to be aborted
2261 * @throws SecurityException if this thread is not allowed to
2262 * initiate a print job request
2263 *
2264 * @see #getPrintable
2265 * @see java.text.MessageFormat
2266 * @see java.awt.GraphicsEnvironment#isHeadless
2267 * @see java.util.concurrent.FutureTask
2268 *
2269 * @since 1.6
2270 */
2271 public boolean print(final MessageFormat headerFormat,
2272 final MessageFormat footerFormat,
2273 final boolean showPrintDialog, final PrintService service,
2274 final PrintRequestAttributeSet attributes,
2275 final boolean interactive) throws PrinterException {
2276
2277 final PrinterJob job = PrinterJob.getPrinterJob();
2278 final Printable printable;
2279 final PrintingStatus printingStatus;
2280 final boolean isHeadless = GraphicsEnvironment.isHeadless();
2281 final boolean isEventDispatchThread = SwingUtilities
2282 .isEventDispatchThread();
2283 final Printable textPrintable = getPrintable(headerFormat,
2284 footerFormat);
2285 if (interactive && !isHeadless) {
2286 printingStatus = PrintingStatus.createPrintingStatus(this ,
2287 job);
2288 printable = printingStatus
2289 .createNotificationPrintable(textPrintable);
2290 } else {
2291 printingStatus = null;
2292 printable = textPrintable;
2293 }
2294
2295 if (service != null) {
2296 job.setPrintService(service);
2297 }
2298
2299 job.setPrintable(printable);
2300
2301 final PrintRequestAttributeSet attr = (attributes == null) ? new HashPrintRequestAttributeSet()
2302 : attributes;
2303
2304 if (showPrintDialog && !isHeadless && !job.printDialog(attr)) {
2305 return false;
2306 }
2307
2308 /*
2309 * there are three cases for printing:
2310 * 1. print non interactively (! interactive || isHeadless)
2311 * 2. print interactively off EDT
2312 * 3. print interactively on EDT
2313 *
2314 * 1 and 2 prints on the current thread (3 prints on another thread)
2315 * 2 and 3 deal with PrintingStatusDialog
2316 */
2317 final Callable<Object> doPrint = 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 = new FutureTask<Object>(
2331 doPrint);
2332
2333 final Runnable runnablePrinting = new Runnable() {
2334 public void run() {
2335 //disable component
2336 boolean wasEnabled = false;
2337 if (isEventDispatchThread) {
2338 if (isEnabled()) {
2339 wasEnabled = true;
2340 setEnabled(false);
2341 }
2342 } else {
2343 try {
2344 wasEnabled = SwingUtilities2.submit(
2345 new Callable<Boolean>() {
2346 public Boolean call()
2347 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(new Runnable() {
2378 public void run() {
2379 setEnabled(true);
2380 }
2381 }, null).get();
2382 } catch (InterruptedException e) {
2383 throw new RuntimeException(e);
2384 } catch (ExecutionException e) {
2385 Throwable cause = e.getCause();
2386 if (cause instanceof Error) {
2387 throw (Error) cause;
2388 }
2389 if (cause instanceof RuntimeException) {
2390 throw (RuntimeException) cause;
2391 }
2392 throw new AssertionError(cause);
2393 }
2394 }
2395 }
2396 }
2397 };
2398
2399 if (!interactive || isHeadless) {
2400 runnablePrinting.run();
2401 } else {
2402 if (isEventDispatchThread) {
2403 (new Thread(runnablePrinting)).start();
2404 printingStatus.showModal(true);
2405 } else {
2406 printingStatus.showModal(false);
2407 runnablePrinting.run();
2408 }
2409 }
2410
2411 //the printing is done successfully or otherwise.
2412 //dialog is hidden if needed.
2413 try {
2414 futurePrinting.get();
2415 } catch (InterruptedException e) {
2416 throw new RuntimeException(e);
2417 } catch (ExecutionException e) {
2418 Throwable cause = e.getCause();
2419 if (cause instanceof PrinterAbortException) {
2420 if (printingStatus != null
2421 && printingStatus.isAborted()) {
2422 return false;
2423 } else {
2424 throw (PrinterAbortException) cause;
2425 }
2426 } else if (cause instanceof PrinterException) {
2427 throw (PrinterException) cause;
2428 } else if (cause instanceof RuntimeException) {
2429 throw (RuntimeException) cause;
2430 } else if (cause instanceof Error) {
2431 throw (Error) cause;
2432 } else {
2433 throw new AssertionError(cause);
2434 }
2435 }
2436 return true;
2437 }
2438
2439 /**
2440 * Returns a {@code Printable} to use for printing the content of this
2441 * {@code JTextComponent}. The returned {@code Printable} prints
2442 * the document as it looks on the screen except being reformatted
2443 * to fit the paper.
2444 * The returned {@code Printable} can be wrapped inside another
2445 * {@code Printable} in order to create complex reports and
2446 * documents.
2447 *
2448 *
2449 * <p>
2450 * The returned {@code Printable} shares the {@code document} with this
2451 * {@code JTextComponent}. It is the responsibility of the developer to
2452 * ensure that the {@code document} is not mutated while this {@code Printable}
2453 * is used. Printing behavior is undefined when the {@code document} is
2454 * mutated during printing.
2455 *
2456 * <p>
2457 * Page header and footer text can be added to the output by providing
2458 * {@code MessageFormat} arguments. The printing code requests
2459 * {@code Strings} from the formats, providing a single item which may be
2460 * included in the formatted string: an {@code Integer} representing the
2461 * current page number.
2462 *
2463 * <p>
2464 * The returned {@code Printable} when printed, formats the
2465 * document content appropriately for the page size. For correct
2466 * line wrapping the {@code imageable width} of all pages must be the
2467 * same. See {@link java.awt.print.PageFormat#getImageableWidth}.
2468 *
2469 * <p>
2470 * This method is thread-safe, although most Swing methods are not. Please
2471 * see <A
2472 * HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">
2473 * How to Use Threads</A> for more information.
2474 *
2475 * <p>
2476 * The returned {@code Printable} can be printed on any thread.
2477 *
2478 * <p>
2479 * This implementation returned {@code Printable} performs all painting on
2480 * the <i>Event Dispatch Thread</i>, regardless of what thread it is
2481 * used on.
2482 *
2483 * @param headerFormat the text, in {@code MessageFormat}, to be
2484 * used as the header, or {@code null} for no header
2485 * @param footerFormat the text, in {@code MessageFormat}, to be
2486 * used as the footer, or {@code null} for no footer
2487 * @return a {@code Printable} for use in printing content of this
2488 * {@code JTextComponent}
2489 *
2490 *
2491 * @see java.awt.print.Printable
2492 * @see java.awt.print.PageFormat
2493 * @see javax.swing.text.Document#render(java.lang.Runnable)
2494 *
2495 * @since 1.6
2496 */
2497 public Printable getPrintable(final MessageFormat headerFormat,
2498 final MessageFormat footerFormat) {
2499 return TextComponentPrintable.getPrintable(this , headerFormat,
2500 footerFormat);
2501 }
2502
2503 /////////////////
2504 // Accessibility support
2505 ////////////////
2506
2507 /**
2508 * Gets the <code>AccessibleContext</code> associated with this
2509 * <code>JTextComponent</code>. For text components,
2510 * the <code>AccessibleContext</code> takes the form of an
2511 * <code>AccessibleJTextComponent</code>.
2512 * A new <code>AccessibleJTextComponent</code> instance
2513 * is created if necessary.
2514 *
2515 * @return an <code>AccessibleJTextComponent</code> that serves as the
2516 * <code>AccessibleContext</code> of this
2517 * <code>JTextComponent</code>
2518 */
2519 public AccessibleContext getAccessibleContext() {
2520 if (accessibleContext == null) {
2521 accessibleContext = new AccessibleJTextComponent();
2522 }
2523 return accessibleContext;
2524 }
2525
2526 /**
2527 * This class implements accessibility support for the
2528 * <code>JTextComponent</code> class. It provides an implementation of
2529 * the Java Accessibility API appropriate to menu user-interface elements.
2530 * <p>
2531 * <strong>Warning:</strong>
2532 * Serialized objects of this class will not be compatible with
2533 * future Swing releases. The current serialization support is
2534 * appropriate for short term storage or RMI between applications running
2535 * the same version of Swing. As of 1.4, support for long term storage
2536 * of all JavaBeans<sup><font size="-2">TM</font></sup>
2537 * has been added to the <code>java.beans</code> package.
2538 * Please see {@link java.beans.XMLEncoder}.
2539 */
2540 public class AccessibleJTextComponent extends AccessibleJComponent
2541 implements AccessibleText, CaretListener, DocumentListener,
2542 AccessibleAction, AccessibleEditableText,
2543 AccessibleExtendedText {
2544
2545 int caretPos;
2546 Point oldLocationOnScreen;
2547
2548 /**
2549 * Constructs an AccessibleJTextComponent. Adds a listener to track
2550 * caret change.
2551 */
2552 public AccessibleJTextComponent() {
2553 Document doc = JTextComponent.this .getDocument();
2554 if (doc != null) {
2555 doc.addDocumentListener(this );
2556 }
2557 JTextComponent.this .addCaretListener(this );
2558 caretPos = getCaretPosition();
2559
2560 try {
2561 oldLocationOnScreen = getLocationOnScreen();
2562 } catch (IllegalComponentStateException iae) {
2563 }
2564
2565 // Fire a ACCESSIBLE_VISIBLE_DATA_PROPERTY PropertyChangeEvent
2566 // when the text component moves (e.g., when scrolling).
2567 // Using an anonymous class since making AccessibleJTextComponent
2568 // implement ComponentListener would be an API change.
2569 JTextComponent.this
2570 .addComponentListener(new ComponentAdapter() {
2571
2572 public void componentMoved(ComponentEvent e) {
2573 try {
2574 Point newLocationOnScreen = getLocationOnScreen();
2575 firePropertyChange(
2576 ACCESSIBLE_VISIBLE_DATA_PROPERTY,
2577 oldLocationOnScreen,
2578 newLocationOnScreen);
2579
2580 oldLocationOnScreen = newLocationOnScreen;
2581 } catch (IllegalComponentStateException iae) {
2582 }
2583 }
2584 });
2585 }
2586
2587 /**
2588 * Handles caret updates (fire appropriate property change event,
2589 * which are AccessibleContext.ACCESSIBLE_CARET_PROPERTY and
2590 * AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY).
2591 * This keeps track of the dot position internally. When the caret
2592 * moves, the internal position is updated after firing the event.
2593 *
2594 * @param e the CaretEvent
2595 */
2596 public void caretUpdate(CaretEvent e) {
2597 int dot = e.getDot();
2598 int mark = e.getMark();
2599 if (caretPos != dot) {
2600 // the caret moved
2601 firePropertyChange(ACCESSIBLE_CARET_PROPERTY,
2602 new Integer(caretPos), new Integer(dot));
2603 caretPos = dot;
2604
2605 try {
2606 oldLocationOnScreen = getLocationOnScreen();
2607 } catch (IllegalComponentStateException iae) {
2608 }
2609 }
2610 if (mark != dot) {
2611 // there is a selection
2612 firePropertyChange(ACCESSIBLE_SELECTION_PROPERTY, null,
2613 getSelectedText());
2614 }
2615 }
2616
2617 // DocumentListener methods
2618
2619 /**
2620 * Handles document insert (fire appropriate property change event
2621 * which is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY).
2622 * This tracks the changed offset via the event.
2623 *
2624 * @param e the DocumentEvent
2625 */
2626 public void insertUpdate(DocumentEvent e) {
2627 final Integer pos = new Integer(e.getOffset());
2628 if (SwingUtilities.isEventDispatchThread()) {
2629 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, pos);
2630 } else {
2631 Runnable doFire = new Runnable() {
2632 public void run() {
2633 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
2634 null, pos);
2635 }
2636 };
2637 SwingUtilities.invokeLater(doFire);
2638 }
2639 }
2640
2641 /**
2642 * Handles document remove (fire appropriate property change event,
2643 * which is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY).
2644 * This tracks the changed offset via the event.
2645 *
2646 * @param e the DocumentEvent
2647 */
2648 public void removeUpdate(DocumentEvent e) {
2649 final Integer pos = new Integer(e.getOffset());
2650 if (SwingUtilities.isEventDispatchThread()) {
2651 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, pos);
2652 } else {
2653 Runnable doFire = new Runnable() {
2654 public void run() {
2655 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
2656 null, pos);
2657 }
2658 };
2659 SwingUtilities.invokeLater(doFire);
2660 }
2661 }
2662
2663 /**
2664 * Handles document remove (fire appropriate property change event,
2665 * which is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY).
2666 * This tracks the changed offset via the event.
2667 *
2668 * @param e the DocumentEvent
2669 */
2670 public void changedUpdate(DocumentEvent e) {
2671 final Integer pos = new Integer(e.getOffset());
2672 if (SwingUtilities.isEventDispatchThread()) {
2673 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, pos);
2674 } else {
2675 Runnable doFire = new Runnable() {
2676 public void run() {
2677 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
2678 null, pos);
2679 }
2680 };
2681 SwingUtilities.invokeLater(doFire);
2682 }
2683 }
2684
2685 /**
2686 * Gets the state set of the JTextComponent.
2687 * The AccessibleStateSet of an object is composed of a set of
2688 * unique AccessibleState's. A change in the AccessibleStateSet
2689 * of an object will cause a PropertyChangeEvent to be fired
2690 * for the AccessibleContext.ACCESSIBLE_STATE_PROPERTY property.
2691 *
2692 * @return an instance of AccessibleStateSet containing the
2693 * current state set of the object
2694 * @see AccessibleStateSet
2695 * @see AccessibleState
2696 * @see #addPropertyChangeListener
2697 */
2698 public AccessibleStateSet getAccessibleStateSet() {
2699 AccessibleStateSet states = super .getAccessibleStateSet();
2700 if (JTextComponent.this .isEditable()) {
2701 states.add(AccessibleState.EDITABLE);
2702 }
2703 return states;
2704 }
2705
2706 /**
2707 * Gets the role of this object.
2708 *
2709 * @return an instance of AccessibleRole describing the role of the
2710 * object (AccessibleRole.TEXT)
2711 * @see AccessibleRole
2712 */
2713 public AccessibleRole getAccessibleRole() {
2714 return AccessibleRole.TEXT;
2715 }
2716
2717 /**
2718 * Get the AccessibleText associated with this object. In the
2719 * implementation of the Java Accessibility API for this class,
2720 * return this object, which is responsible for implementing the
2721 * AccessibleText interface on behalf of itself.
2722 *
2723 * @return this object
2724 */
2725 public AccessibleText getAccessibleText() {
2726 return this ;
2727 }
2728
2729 // --- interface AccessibleText methods ------------------------
2730
2731 /**
2732 * Many of these methods are just convenience methods; they
2733 * just call the equivalent on the parent
2734 */
2735
2736 /**
2737 * Given a point in local coordinates, return the zero-based index
2738 * of the character under that Point. If the point is invalid,
2739 * this method returns -1.
2740 *
2741 * @param p the Point in local coordinates
2742 * @return the zero-based index of the character under Point p.
2743 */
2744 public int getIndexAtPoint(Point p) {
2745 if (p == null) {
2746 return -1;
2747 }
2748 return JTextComponent.this .viewToModel(p);
2749 }
2750
2751 /**
2752 * Gets the editor's drawing rectangle. Stolen
2753 * from the unfortunately named
2754 * BasicTextUI.getVisibleEditorRect()
2755 *
2756 * @return the bounding box for the root view
2757 */
2758 Rectangle getRootEditorRect() {
2759 Rectangle alloc = JTextComponent.this .getBounds();
2760 if ((alloc.width > 0) && (alloc.height > 0)) {
2761 alloc.x = alloc.y = 0;
2762 Insets insets = JTextComponent.this .getInsets();
2763 alloc.x += insets.left;
2764 alloc.y += insets.top;
2765 alloc.width -= insets.left + insets.right;
2766 alloc.height -= insets.top + insets.bottom;
2767 return alloc;
2768 }
2769 return null;
2770 }
2771
2772 /**
2773 * Determines the bounding box of the character at the given
2774 * index into the string. The bounds are returned in local
2775 * coordinates. If the index is invalid a null rectangle
2776 * is returned.
2777 *
2778 * The screen coordinates returned are "unscrolled coordinates"
2779 * if the JTextComponent is contained in a JScrollPane in which
2780 * case the resulting rectangle should be composed with the parent
2781 * coordinates. A good algorithm to use is:
2782 * <nf>
2783 * Accessible a:
2784 * AccessibleText at = a.getAccessibleText();
2785 * AccessibleComponent ac = a.getAccessibleComponent();
2786 * Rectangle r = at.getCharacterBounds();
2787 * Point p = ac.getLocation();
2788 * r.x += p.x;
2789 * r.y += p.y;
2790 * </nf>
2791 *
2792 * Note: the JTextComponent must have a valid size (e.g. have
2793 * been added to a parent container whose ancestor container
2794 * is a valid top-level window) for this method to be able
2795 * to return a meaningful (non-null) value.
2796 *
2797 * @param i the index into the String >= 0
2798 * @return the screen coordinates of the character's bounding box
2799 */
2800 public Rectangle getCharacterBounds(int i) {
2801 if (i < 0 || i > model.getLength() - 1) {
2802 return null;
2803 }
2804 TextUI ui = getUI();
2805 if (ui == null) {
2806 return null;
2807 }
2808 Rectangle rect = null;
2809 Rectangle alloc = getRootEditorRect();
2810 if (alloc == null) {
2811 return null;
2812 }
2813 if (model instanceof AbstractDocument) {
2814 ((AbstractDocument) model).readLock();
2815 }
2816 try {
2817 View rootView = ui.getRootView(JTextComponent.this );
2818 if (rootView != null) {
2819 rootView.setSize(alloc.width, alloc.height);
2820
2821 Shape bounds = rootView.modelToView(i,
2822 Position.Bias.Forward, i + 1,
2823 Position.Bias.Backward, alloc);
2824
2825 rect = (bounds instanceof Rectangle) ? (Rectangle) bounds
2826 : bounds.getBounds();
2827
2828 }
2829 } catch (BadLocationException e) {
2830 } finally {
2831 if (model instanceof AbstractDocument) {
2832 ((AbstractDocument) model).readUnlock();
2833 }
2834 }
2835 return rect;
2836 }
2837
2838 /**
2839 * Returns the number of characters (valid indices)
2840 *
2841 * @return the number of characters >= 0
2842 */
2843 public int getCharCount() {
2844 return model.getLength();
2845 }
2846
2847 /**
2848 * Returns the zero-based offset of the caret.
2849 *
2850 * Note: The character to the right of the caret will have the
2851 * same index value as the offset (the caret is between
2852 * two characters).
2853 *
2854 * @return the zero-based offset of the caret.
2855 */
2856 public int getCaretPosition() {
2857 return JTextComponent.this .getCaretPosition();
2858 }
2859
2860 /**
2861 * Returns the AttributeSet for a given character (at a given index).
2862 *
2863 * @param i the zero-based index into the text
2864 * @return the AttributeSet of the character
2865 */
2866 public AttributeSet getCharacterAttribute(int i) {
2867 Element e = null;
2868 if (model instanceof AbstractDocument) {
2869 ((AbstractDocument) model).readLock();
2870 }
2871 try {
2872 for (e = model.getDefaultRootElement(); !e.isLeaf();) {
2873 int index = e.getElementIndex(i);
2874 e = e.getElement(index);
2875 }
2876 } finally {
2877 if (model instanceof AbstractDocument) {
2878 ((AbstractDocument) model).readUnlock();
2879 }
2880 }
2881 return e.getAttributes();
2882 }
2883
2884 /**
2885 * Returns the start offset within the selected text.
2886 * If there is no selection, but there is
2887 * a caret, the start and end offsets will be the same.
2888 * Return 0 if the text is empty, or the caret position
2889 * if no selection.
2890 *
2891 * @return the index into the text of the start of the selection >= 0
2892 */
2893 public int getSelectionStart() {
2894 return JTextComponent.this .getSelectionStart();
2895 }
2896
2897 /**
2898 * Returns the end offset within the selected text.
2899 * If there is no selection, but there is
2900 * a caret, the start and end offsets will be the same.
2901 * Return 0 if the text is empty, or the caret position
2902 * if no selection.
2903 *
2904 * @return the index into teh text of the end of the selection >= 0
2905 */
2906 public int getSelectionEnd() {
2907 return JTextComponent.this .getSelectionEnd();
2908 }
2909
2910 /**
2911 * Returns the portion of the text that is selected.
2912 *
2913 * @return the text, null if no selection
2914 */
2915 public String getSelectedText() {
2916 return JTextComponent.this .getSelectedText();
2917 }
2918
2919 /**
2920 * IndexedSegment extends Segment adding the offset into the
2921 * the model the <code>Segment</code> was asked for.
2922 */
2923 private class IndexedSegment extends Segment {
2924 /**
2925 * Offset into the model that the position represents.
2926 */
2927 public int modelOffset;
2928 }
2929
2930 // TIGER - 4170173
2931 /**
2932 * Returns the String at a given index. Whitespace
2933 * between words is treated as a word.
2934 *
2935 * @param part the CHARACTER, WORD, or SENTENCE to retrieve
2936 * @param index an index within the text
2937 * @return the letter, word, or sentence.
2938 *
2939 */
2940 public String getAtIndex(int part, int index) {
2941 return getAtIndex(part, index, 0);
2942 }
2943
2944 /**
2945 * Returns the String after a given index. Whitespace
2946 * between words is treated as a word.
2947 *
2948 * @param part the CHARACTER, WORD, or SENTENCE to retrieve
2949 * @param index an index within the text
2950 * @return the letter, word, or sentence.
2951 */
2952 public String getAfterIndex(int part, int index) {
2953 return getAtIndex(part, index, 1);
2954 }
2955
2956 /**
2957 * Returns the String before a given index. Whitespace
2958 * between words is treated a word.
2959 *
2960 * @param part the CHARACTER, WORD, or SENTENCE to retrieve
2961 * @param index an index within the text
2962 * @return the letter, word, or sentence.
2963 */
2964 public String getBeforeIndex(int part, int index) {
2965 return getAtIndex(part, index, -1);
2966 }
2967
2968 /**
2969 * Gets the word, sentence, or character at <code>index</code>.
2970 * If <code>direction</code> is non-null this will find the
2971 * next/previous word/sentence/character.
2972 */
2973 private String getAtIndex(int part, int index, int direction) {
2974 if (model instanceof AbstractDocument) {
2975 ((AbstractDocument) model).readLock();
2976 }
2977 try {
2978 if (index < 0 || index >= model.getLength()) {
2979 return null;
2980 }
2981 switch (part) {
2982 case AccessibleText.CHARACTER:
2983 if (index + direction < model.getLength()
2984 && index + direction >= 0) {
2985 return model.getText(index + direction, 1);
2986 }
2987 break;
2988
2989 case AccessibleText.WORD:
2990 case AccessibleText.SENTENCE:
2991 IndexedSegment seg = getSegmentAt(part, index);
2992 if (seg != null) {
2993 if (direction != 0) {
2994 int next;
2995
2996 if (direction < 0) {
2997 next = seg.modelOffset - 1;
2998 } else {
2999 next = seg.modelOffset + direction
3000 * seg.count;
3001 }
3002 if (next >= 0 && next <= model.getLength()) {
3003 seg = getSegmentAt(part, next);
3004 } else {
3005 seg = null;
3006 }
3007 }
3008 if (seg != null) {
3009 return new String(seg.array, seg.offset,
3010 seg.count);
3011 }
3012 }
3013 break;
3014
3015 default:
3016 break;
3017 }
3018 } catch (BadLocationException e) {
3019 } finally {
3020 if (model instanceof AbstractDocument) {
3021 ((AbstractDocument) model).readUnlock();
3022 }
3023 }
3024 return null;
3025 }
3026
3027 /*
3028 * Returns the paragraph element for the specified index.
3029 */
3030 private Element getParagraphElement(int index) {
3031 if (model instanceof PlainDocument) {
3032 PlainDocument sdoc = (PlainDocument) model;
3033 return sdoc.getParagraphElement(index);
3034 } else if (model instanceof StyledDocument) {
3035 StyledDocument sdoc = (StyledDocument) model;
3036 return sdoc.getParagraphElement(index);
3037 } else {
3038 Element para = null;
3039 for (para = model.getDefaultRootElement(); !para
3040 .isLeaf();) {
3041 int pos = para.getElementIndex(index);
3042 para = para.getElement(pos);
3043 }
3044 if (para == null) {
3045 return null;
3046 }
3047 return para.getParentElement();
3048 }
3049 }
3050
3051 /*
3052 * Returns a <code>Segment</code> containing the paragraph text
3053 * at <code>index</code>, or null if <code>index</code> isn't
3054 * valid.
3055 */
3056 private IndexedSegment getParagraphElementText(int index)
3057 throws BadLocationException {
3058 Element para = getParagraphElement(index);
3059
3060 if (para != null) {
3061 IndexedSegment segment = new IndexedSegment();
3062 try {
3063 int length = para.getEndOffset()
3064 - para.getStartOffset();
3065 model.getText(para.getStartOffset(), length,
3066 segment);
3067 } catch (BadLocationException e) {
3068 return null;
3069 }
3070 segment.modelOffset = para.getStartOffset();
3071 return segment;
3072 }
3073 return null;
3074 }
3075
3076 /**
3077 * Returns the Segment at <code>index</code> representing either
3078 * the paragraph or sentence as identified by <code>part</code>, or
3079 * null if a valid paragraph/sentence can't be found. The offset
3080 * will point to the start of the word/sentence in the array, and
3081 * the modelOffset will point to the location of the word/sentence
3082 * in the model.
3083 */
3084 private IndexedSegment getSegmentAt(int part, int index)
3085 throws BadLocationException {
3086 IndexedSegment seg = getParagraphElementText(index);
3087 if (seg == null) {
3088 return null;
3089 }
3090 BreakIterator iterator;
3091 switch (part) {
3092 case AccessibleText.WORD:
3093 iterator = BreakIterator.getWordInstance(getLocale());
3094 break;
3095 case AccessibleText.SENTENCE:
3096 iterator = BreakIterator
3097 .getSentenceInstance(getLocale());
3098 break;
3099 default:
3100 return null;
3101 }
3102 seg.first();
3103 iterator.setText(seg);
3104 int end = iterator.following(index - seg.modelOffset
3105 + seg.offset);
3106 if (end == BreakIterator.DONE) {
3107 return null;
3108 }
3109 if (end > seg.offset + seg.count) {
3110 return null;
3111 }
3112 int begin = iterator.previous();
3113 if (begin == BreakIterator.DONE
3114 || begin >= seg.offset + seg.count) {
3115 return null;
3116 }
3117 seg.modelOffset = seg.modelOffset + begin - seg.offset;
3118 seg.offset = begin;
3119 seg.count = end - begin;
3120 return seg;
3121 }
3122
3123 // begin AccessibleEditableText methods -----
3124
3125 /**
3126 * Returns the AccessibleEditableText interface for
3127 * this text component.
3128 *
3129 * @return the AccessibleEditableText interface
3130 * @since 1.4
3131 */
3132 public AccessibleEditableText getAccessibleEditableText() {
3133 return this ;
3134 }
3135
3136 /**
3137 * Sets the text contents to the specified string.
3138 *
3139 * @param s the string to set the text contents
3140 * @since 1.4
3141 */
3142 public void setTextContents(String s) {
3143 JTextComponent.this .setText(s);
3144 }
3145
3146 /**
3147 * Inserts the specified string at the given index
3148 *
3149 * @param index the index in the text where the string will
3150 * be inserted
3151 * @param s the string to insert in the text
3152 * @since 1.4
3153 */
3154 public void insertTextAtIndex(int index, String s) {
3155 Document doc = JTextComponent.this .getDocument();
3156 if (doc != null) {
3157 try {
3158 if (s != null && s.length() > 0) {
3159 boolean composedTextSaved = saveComposedText(index);
3160 doc.insertString(index, s, null);
3161 if (composedTextSaved) {
3162 restoreComposedText();
3163 }
3164 }
3165 } catch (BadLocationException e) {
3166 UIManager.getLookAndFeel().provideErrorFeedback(
3167 JTextComponent.this );
3168 }
3169 }
3170 }
3171
3172 /**
3173 * Returns the text string between two indices.
3174 *
3175 * @param startIndex the starting index in the text
3176 * @param endIndex the ending index in the text
3177 * @return the text string between the indices
3178 * @since 1.4
3179 */
3180 public String getTextRange(int startIndex, int endIndex) {
3181 String txt = null;
3182 int p0 = Math.min(startIndex, endIndex);
3183 int p1 = Math.max(startIndex, endIndex);
3184 if (p0 != p1) {
3185 try {
3186 Document doc = JTextComponent.this .getDocument();
3187 txt = doc.getText(p0, p1 - p0);
3188 } catch (BadLocationException e) {
3189 throw new IllegalArgumentException(e.getMessage());
3190 }
3191 }
3192 return txt;
3193 }
3194
3195 /**
3196 * Deletes the text between two indices
3197 *
3198 * @param startIndex the starting index in the text
3199 * @param endIndex the ending index in the text
3200 * @since 1.4
3201 */
3202 public void delete(int startIndex, int endIndex) {
3203 if (isEditable() && isEnabled()) {
3204 try {
3205 int p0 = Math.min(startIndex, endIndex);
3206 int p1 = Math.max(startIndex, endIndex);
3207 if (p0 != p1) {
3208 Document doc = getDocument();
3209 doc.remove(p0, p1 - p0);
3210 }
3211 } catch (BadLocationException e) {
3212 }
3213 } else {
3214 UIManager.getLookAndFeel().provideErrorFeedback(
3215 JTextComponent.this );
3216 }
3217 }
3218
3219 /**
3220 * Cuts the text between two indices into the system clipboard.
3221 *
3222 * @param startIndex the starting index in the text
3223 * @param endIndex the ending index in the text
3224 * @since 1.4
3225 */
3226 public void cut(int startIndex, int endIndex) {
3227 selectText(startIndex, endIndex);
3228 JTextComponent.this .cut();
3229 }
3230
3231 /**
3232 * Pastes the text from the system clipboard into the text
3233 * starting at the specified index.
3234 *
3235 * @param startIndex the starting index in the text
3236 * @since 1.4
3237 */
3238 public void paste(int startIndex) {
3239 setCaretPosition(startIndex);
3240 JTextComponent.this .paste();
3241 }
3242
3243 /**
3244 * Replaces the text between two indices with the specified
3245 * string.
3246 *
3247 * @param startIndex the starting index in the text
3248 * @param endIndex the ending index in the text
3249 * @param s the string to replace the text between two indices
3250 * @since 1.4
3251 */
3252 public void replaceText(int startIndex, int endIndex, String s) {
3253 selectText(startIndex, endIndex);
3254 JTextComponent.this .replaceSelection(s);
3255 }
3256
3257 /**
3258 * Selects the text between two indices.
3259 *
3260 * @param startIndex the starting index in the text
3261 * @param endIndex the ending index in the text
3262 * @since 1.4
3263 */
3264 public void selectText(int startIndex, int endIndex) {
3265 JTextComponent.this .select(startIndex, endIndex);
3266 }
3267
3268 /**
3269 * Sets attributes for the text between two indices.
3270 *
3271 * @param startIndex the starting index in the text
3272 * @param endIndex the ending index in the text
3273 * @param as the attribute set
3274 * @see AttributeSet
3275 * @since 1.4
3276 */
3277 public void setAttributes(int startIndex, int endIndex,
3278 AttributeSet as) {
3279
3280 // Fixes bug 4487492
3281 Document doc = JTextComponent.this .getDocument();
3282 if (doc != null && doc instanceof StyledDocument) {
3283 StyledDocument sDoc = (StyledDocument) doc;
3284 int offset = startIndex;
3285 int length = endIndex - startIndex;
3286 sDoc.setCharacterAttributes(offset, length, as, true);
3287 }
3288 }
3289
3290 // ----- end AccessibleEditableText methods
3291
3292 // ----- begin AccessibleExtendedText methods
3293
3294 // Probably should replace the helper method getAtIndex() to return
3295 // instead an AccessibleTextSequence also for LINE & ATTRIBUTE_RUN
3296 // and then make the AccessibleText methods get[At|After|Before]Point
3297 // call this new method instead and return only the string portion
3298
3299 /**
3300 * Returns the AccessibleTextSequence at a given <code>index</code>.
3301 * If <code>direction</code> is non-null this will find the
3302 * next/previous word/sentence/character.
3303 *
3304 * @param part the <code>CHARACTER</code>, <code>WORD</code>,
3305 * <code>SENTENCE</code>, <code>LINE</code> or
3306 * <code>ATTRIBUTE_RUN</code> to retrieve
3307 * @param index an index within the text
3308 * @param direction is either -1, 0, or 1
3309 * @return an <code>AccessibleTextSequence</code> specifying the text
3310 * if <code>part</code> and <code>index</code> are valid. Otherwise,
3311 * <code>null</code> is returned.
3312 *
3313 * @see javax.accessibility.AccessibleText#CHARACTER
3314 * @see javax.accessibility.AccessibleText#WORD
3315 * @see javax.accessibility.AccessibleText#SENTENCE
3316 * @see javax.accessibility.AccessibleExtendedText#LINE
3317 * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN
3318 *
3319 * @since 1.6
3320 */
3321 private AccessibleTextSequence getSequenceAtIndex(int part,
3322 int index, int direction) {
3323 if (index < 0 || index >= model.getLength()) {
3324 return null;
3325 }
3326 if (direction < -1 || direction > 1) {
3327 return null; // direction must be 1, 0, or -1
3328 }
3329
3330 switch (part) {
3331 case AccessibleText.CHARACTER:
3332 if (model instanceof AbstractDocument) {
3333 ((AbstractDocument) model).readLock();
3334 }
3335 AccessibleTextSequence charSequence = null;
3336 try {
3337 if (index + direction < model.getLength()
3338 && index + direction >= 0) {
3339 charSequence = new AccessibleTextSequence(index
3340 + direction, index + direction + 1,
3341 model.getText(index + direction, 1));
3342 }
3343
3344 } catch (BadLocationException e) {
3345 // we are intentionally silent; our contract says we return
3346 // null if there is any failure in this method
3347 } finally {
3348 if (model instanceof AbstractDocument) {
3349 ((AbstractDocument) model).readUnlock();
3350 }
3351 }
3352 return charSequence;
3353
3354 case AccessibleText.WORD:
3355 case AccessibleText.SENTENCE:
3356 if (model instanceof AbstractDocument) {
3357 ((AbstractDocument) model).readLock();
3358 }
3359 AccessibleTextSequence rangeSequence = null;
3360 try {
3361 IndexedSegment seg = getSegmentAt(part, index);
3362 if (seg != null) {
3363 if (direction != 0) {
3364 int next;
3365
3366 if (direction < 0) {
3367 next = seg.modelOffset - 1;
3368 } else {
3369 next = seg.modelOffset + seg.count;
3370 }
3371 if (next >= 0 && next <= model.getLength()) {
3372 seg = getSegmentAt(part, next);
3373 } else {
3374 seg = null;
3375 }
3376 }
3377 if (seg != null
3378 && (seg.offset + seg.count) <= model
3379 .getLength()) {
3380 rangeSequence = new AccessibleTextSequence(
3381 seg.offset, seg.offset + seg.count,
3382 new String(seg.array, seg.offset,
3383 seg.count));
3384 } // else we leave rangeSequence set to null
3385 }
3386 } catch (BadLocationException e) {
3387 // we are intentionally silent; our contract says we return
3388 // null if there is any failure in this method
3389 } finally {
3390 if (model instanceof AbstractDocument) {
3391 ((AbstractDocument) model).readUnlock();
3392 }
3393 }
3394 return rangeSequence;
3395
3396 case AccessibleExtendedText.LINE:
3397 AccessibleTextSequence lineSequence = null;
3398 if (model instanceof AbstractDocument) {
3399 ((AbstractDocument) model).readLock();
3400 }
3401 try {
3402 int startIndex = Utilities.getRowStart(
3403 JTextComponent.this , index);
3404 int endIndex = Utilities.getRowEnd(
3405 JTextComponent.this , index);
3406 if (startIndex >= 0 && endIndex >= startIndex) {
3407 if (direction == 0) {
3408 lineSequence = new AccessibleTextSequence(
3409 startIndex, endIndex,
3410 model.getText(startIndex, endIndex
3411 - startIndex + 1));
3412 } else if (direction == -1 && startIndex > 0) {
3413 endIndex = Utilities
3414 .getRowEnd(JTextComponent.this ,
3415 startIndex - 1);
3416 startIndex = Utilities
3417 .getRowStart(JTextComponent.this ,
3418 startIndex - 1);
3419 if (startIndex >= 0
3420 && endIndex >= startIndex) {
3421 lineSequence = new AccessibleTextSequence(
3422 startIndex, endIndex,
3423 model.getText(startIndex,
3424 endIndex - startIndex
3425 + 1));
3426 }
3427 } else if (direction == 1
3428 && endIndex < model.getLength()) {
3429 startIndex = Utilities.getRowStart(
3430 JTextComponent.this , endIndex + 1);
3431 endIndex = Utilities.getRowEnd(
3432 JTextComponent.this , endIndex + 1);
3433 if (startIndex >= 0
3434 && endIndex >= startIndex) {
3435 lineSequence = new AccessibleTextSequence(
3436 startIndex, endIndex,
3437 model.getText(startIndex,
3438 endIndex - startIndex
3439 + 1));
3440 }
3441 }
3442 // already validated 'direction' above...
3443 }
3444 } catch (BadLocationException e) {
3445 // we are intentionally silent; our contract says we return
3446 // null if there is any failure in this method
3447 } finally {
3448 if (model instanceof AbstractDocument) {
3449 ((AbstractDocument) model).readUnlock();
3450 }
3451 }
3452 return lineSequence;
3453
3454 case AccessibleExtendedText.ATTRIBUTE_RUN:
3455 // assumptions: (1) that all characters in a single element
3456 // share the same attribute set; (2) that adjacent elements
3457 // *may* share the same attribute set
3458
3459 int attributeRunStartIndex,
3460 attributeRunEndIndex;
3461 String runText = null;
3462 if (model instanceof AbstractDocument) {
3463 ((AbstractDocument) model).readLock();
3464 }
3465
3466 try {
3467 attributeRunStartIndex = attributeRunEndIndex = Integer.MIN_VALUE;
3468 int tempIndex = index;
3469 switch (direction) {
3470 case -1:
3471 // going backwards, so find left edge of this run -
3472 // that'll be the end of the previous run
3473 // (off-by-one counting)
3474 attributeRunEndIndex = getRunEdge(index,
3475 direction);
3476 // now set ourselves up to find the left edge of the
3477 // prev. run
3478 tempIndex = attributeRunEndIndex - 1;
3479 break;
3480 case 1:
3481 // going forward, so find right edge of this run -
3482 // that'll be the start of the next run
3483 // (off-by-one counting)
3484 attributeRunStartIndex = getRunEdge(index,
3485 direction);
3486 // now set ourselves up to find the right edge of the
3487 // next run
3488 tempIndex = attributeRunStartIndex;
3489 break;
3490 case 0:
3491 // interested in the current run, so nothing special to
3492 // set up in advance...
3493 break;
3494 default:
3495 // only those three values of direction allowed...
3496 throw new AssertionError(direction);
3497 }
3498
3499 // set the unset edge; if neither set then we're getting
3500 // both edges of the current run around our 'index'
3501 attributeRunStartIndex = (attributeRunStartIndex != Integer.MIN_VALUE) ? attributeRunStartIndex
3502 : getRunEdge(tempIndex, -1);
3503 attributeRunEndIndex = (attributeRunEndIndex != Integer.MIN_VALUE) ? attributeRunEndIndex
3504 : getRunEdge(tempIndex, 1);
3505
3506 runText = model.getText(attributeRunStartIndex,
3507 attributeRunEndIndex
3508 - attributeRunStartIndex);
3509 } catch (BadLocationException e) {
3510 // we are intentionally silent; our contract says we return
3511 // null if there is any failure in this method
3512 return null;
3513 } finally {
3514 if (model instanceof AbstractDocument) {
3515 ((AbstractDocument) model).readUnlock();
3516 }
3517 }
3518 return new AccessibleTextSequence(
3519 attributeRunStartIndex, attributeRunEndIndex,
3520 runText);
3521
3522 default:
3523 break;
3524 }
3525 return null;
3526 }
3527
3528 /**
3529 * Starting at text position <code>index</code>, and going in
3530 * <code>direction</code>, return the edge of run that shares the
3531 * same <code>AttributeSet</code> and parent element as those at
3532 * <code>index</code>.
3533 *
3534 * Note: we assume the document is already locked...
3535 */
3536 private int getRunEdge(int index, int direction)
3537 throws BadLocationException {
3538 if (index < 0 || index >= model.getLength()) {
3539 throw new BadLocationException(
3540 "Location out of bounds", index);
3541 }
3542 // locate the Element at index
3543 Element indexElement = null;
3544 // locate the Element at our index/offset
3545 int elementIndex = -1; // test for initialization
3546 for (indexElement = model.getDefaultRootElement(); !indexElement
3547 .isLeaf();) {
3548 elementIndex = indexElement.getElementIndex(index);
3549 indexElement = indexElement.getElement(elementIndex);
3550 }
3551 if (elementIndex == -1) {
3552 throw new AssertionError(index);
3553 }
3554 // cache the AttributeSet and parentElement atindex
3555 AttributeSet indexAS = indexElement.getAttributes();
3556 Element parent = indexElement.getParentElement();
3557
3558 // find the first Element before/after ours w/the same AttributeSet
3559 // if we are already at edge of the first element in our parent
3560 // then return that edge
3561 Element edgeElement = indexElement;
3562 switch (direction) {
3563 case -1:
3564 case 1:
3565 int edgeElementIndex = elementIndex;
3566 int elementCount = parent.getElementCount();
3567 while ((edgeElementIndex + direction) > 0
3568 && ((edgeElementIndex + direction) < elementCount)
3569 && parent.getElement(
3570 edgeElementIndex + direction)
3571 .getAttributes().isEqual(indexAS)) {
3572 edgeElementIndex += direction;
3573 }
3574 edgeElement = parent.getElement(edgeElementIndex);
3575 break;
3576 default:
3577 throw new AssertionError(direction);
3578 }
3579 switch (direction) {
3580 case -1:
3581 return edgeElement.getStartOffset();
3582 case 1:
3583 return edgeElement.getEndOffset();
3584 default:
3585 // we already caught this case earlier; this is to satisfy
3586 // the compiler...
3587 return Integer.MIN_VALUE;
3588 }
3589 }
3590
3591 // getTextRange() not needed; defined in AccessibleEditableText
3592
3593 /**
3594 * Returns the <code>AccessibleTextSequence</code> at a given
3595 * <code>index</code>.
3596 *
3597 * @param part the <code>CHARACTER</code>, <code>WORD</code>,
3598 * <code>SENTENCE</code>, <code>LINE</code> or
3599 * <code>ATTRIBUTE_RUN</code> to retrieve
3600 * @param index an index within the text
3601 * @return an <code>AccessibleTextSequence</code> specifying the text if
3602 * <code>part</code> and <code>index</code> are valid. Otherwise,
3603 * <code>null</code> is returned
3604 *
3605 * @see javax.accessibility.AccessibleText#CHARACTER
3606 * @see javax.accessibility.AccessibleText#WORD
3607 * @see javax.accessibility.AccessibleText#SENTENCE
3608 * @see javax.accessibility.AccessibleExtendedText#LINE
3609 * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN
3610 *
3611 * @since 1.6
3612 */
3613 public AccessibleTextSequence getTextSequenceAt(int part,
3614 int index) {
3615 return getSequenceAtIndex(part, index, 0);
3616 }
3617
3618 /**
3619 * Returns the <code>AccessibleTextSequence</code> after a given
3620 * <code>index</code>.
3621 *
3622 * @param part the <code>CHARACTER</code>, <code>WORD</code>,
3623 * <code>SENTENCE</code>, <code>LINE</code> or
3624 * <code>ATTRIBUTE_RUN</code> to retrieve
3625 * @param index an index within the text
3626 * @return an <code>AccessibleTextSequence</code> specifying the text
3627 * if <code>part</code> and <code>index</code> are valid. Otherwise,
3628 * <code>null</code> is returned
3629 *
3630 * @see javax.accessibility.AccessibleText#CHARACTER
3631 * @see javax.accessibility.AccessibleText#WORD
3632 * @see javax.accessibility.AccessibleText#SENTENCE
3633 * @see javax.accessibility.AccessibleExtendedText#LINE
3634 * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN
3635 *
3636 * @since 1.6
3637 */
3638 public AccessibleTextSequence getTextSequenceAfter(int part,
3639 int index) {
3640 return getSequenceAtIndex(part, index, 1);
3641 }
3642
3643 /**
3644 * Returns the <code>AccessibleTextSequence</code> before a given
3645 * <code>index</code>.
3646 *
3647 * @param part the <code>CHARACTER</code>, <code>WORD</code>,
3648 * <code>SENTENCE</code>, <code>LINE</code> or
3649 * <code>ATTRIBUTE_RUN</code> to retrieve
3650 * @param index an index within the text
3651 * @return an <code>AccessibleTextSequence</code> specifying the text
3652 * if <code>part</code> and <code>index</code> are valid. Otherwise,
3653 * <code>null</code> is returned
3654 *
3655 * @see javax.accessibility.AccessibleText#CHARACTER
3656 * @see javax.accessibility.AccessibleText#WORD
3657 * @see javax.accessibility.AccessibleText#SENTENCE
3658 * @see javax.accessibility.AccessibleExtendedText#LINE
3659 * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN
3660 *
3661 * @since 1.6
3662 */
3663 public AccessibleTextSequence getTextSequenceBefore(int part,
3664 int index) {
3665 return getSequenceAtIndex(part, index, -1);
3666 }
3667
3668 /**
3669 * Returns the <code>Rectangle</code> enclosing the text between
3670 * two indicies.
3671 *
3672 * @param startIndex the start index in the text
3673 * @param endIndex the end index in the text
3674 * @return the bounding rectangle of the text if the indices are valid.
3675 * Otherwise, <code>null</code> is returned
3676 *
3677 * @since 1.6
3678 */
3679 public Rectangle getTextBounds(int startIndex, int endIndex) {
3680 if (startIndex < 0 || startIndex > model.getLength() - 1
3681 || endIndex < 0 || endIndex > model.getLength() - 1
3682 || startIndex > endIndex) {
3683 return null;
3684 }
3685 TextUI ui = getUI();
3686 if (ui == null) {
3687 return null;
3688 }
3689 Rectangle rect = null;
3690 Rectangle alloc = getRootEditorRect();
3691 if (alloc == null) {
3692 return null;
3693 }
3694 if (model instanceof AbstractDocument) {
3695 ((AbstractDocument) model).readLock();
3696 }
3697 try {
3698 View rootView = ui.getRootView(JTextComponent.this );
3699 if (rootView != null) {
3700 Shape bounds = rootView.modelToView(startIndex,
3701 Position.Bias.Forward, endIndex,
3702 Position.Bias.Backward, alloc);
3703
3704 rect = (bounds instanceof Rectangle) ? (Rectangle) bounds
3705 : bounds.getBounds();
3706
3707 }
3708 } catch (BadLocationException e) {
3709 } finally {
3710 if (model instanceof AbstractDocument) {
3711 ((AbstractDocument) model).readUnlock();
3712 }
3713 }
3714 return rect;
3715 }
3716
3717 // ----- end AccessibleExtendedText methods
3718
3719 // --- interface AccessibleAction methods ------------------------
3720
3721 public AccessibleAction getAccessibleAction() {
3722 return this ;
3723 }
3724
3725 /**
3726 * Returns the number of accessible actions available in this object
3727 * If there are more than one, the first one is considered the
3728 * "default" action of the object.
3729 *
3730 * @return the zero-based number of Actions in this object
3731 * @since 1.4
3732 */
3733 public int getAccessibleActionCount() {
3734 Action[] actions = JTextComponent.this .getActions();
3735 return actions.length;
3736 }
3737
3738 /**
3739 * Returns a description of the specified action of the object.
3740 *
3741 * @param i zero-based index of the actions
3742 * @return a String description of the action
3743 * @see #getAccessibleActionCount
3744 * @since 1.4
3745 */
3746 public String getAccessibleActionDescription(int i) {
3747 Action[] actions = JTextComponent.this .getActions();
3748 if (i < 0 || i >= actions.length) {
3749 return null;
3750 }
3751 return (String) actions[i].getValue(Action.NAME);
3752 }
3753
3754 /**
3755 * Performs the specified Action on the object
3756 *
3757 * @param i zero-based index of actions
3758 * @return true if the action was performed; otherwise false.
3759 * @see #getAccessibleActionCount
3760 * @since 1.4
3761 */
3762 public boolean doAccessibleAction(int i) {
3763 Action[] actions = JTextComponent.this .getActions();
3764 if (i < 0 || i >= actions.length) {
3765 return false;
3766 }
3767 ActionEvent ae = new ActionEvent(JTextComponent.this ,
3768 ActionEvent.ACTION_PERFORMED, null, EventQueue
3769 .getMostRecentEventTime(),
3770 getCurrentEventModifiers());
3771 actions[i].actionPerformed(ae);
3772 return true;
3773 }
3774
3775 // ----- end AccessibleAction methods
3776
3777 }
3778
3779 // --- serialization ---------------------------------------------
3780
3781 private void readObject(ObjectInputStream s) throws IOException,
3782 ClassNotFoundException {
3783 s.defaultReadObject();
3784 caretEvent = new MutableCaretEvent(this );
3785 addMouseListener(caretEvent);
3786 addFocusListener(caretEvent);
3787 }
3788
3789 // --- member variables ----------------------------------
3790
3791 /**
3792 * The document model.
3793 */
3794 private Document model;
3795
3796 /**
3797 * The caret used to display the insert position
3798 * and navigate throughout the document.
3799 *
3800 * PENDING(prinz)
3801 * This should be serializable, default installed
3802 * by UI.
3803 */
3804 private transient Caret caret;
3805
3806 /**
3807 * Object responsible for restricting the cursor navigation.
3808 */
3809 private NavigationFilter navigationFilter;
3810
3811 /**
3812 * The object responsible for managing highlights.
3813 *
3814 * PENDING(prinz)
3815 * This should be serializable, default installed
3816 * by UI.
3817 */
3818 private transient Highlighter highlighter;
3819
3820 /**
3821 * The current key bindings in effect.
3822 *
3823 * PENDING(prinz)
3824 * This should be serializable, default installed
3825 * by UI.
3826 */
3827 private transient Keymap keymap;
3828
3829 private transient MutableCaretEvent caretEvent;
3830 private Color caretColor;
3831 private Color selectionColor;
3832 private Color selectedTextColor;
3833 private Color disabledTextColor;
3834 private boolean editable;
3835 private Insets margin;
3836 private char focusAccelerator;
3837 private boolean dragEnabled;
3838
3839 /**
3840 * The drop mode for this component.
3841 */
3842 private DropMode dropMode = DropMode.USE_SELECTION;
3843
3844 /**
3845 * The drop location.
3846 */
3847 private transient DropLocation dropLocation;
3848
3849 /**
3850 * Represents a drop location for <code>JTextComponent</code>s.
3851 *
3852 * @see #getDropLocation
3853 * @since 1.6
3854 */
3855 public static final class DropLocation extends
3856 TransferHandler.DropLocation {
3857 private final int index;
3858 private final Position.Bias bias;
3859
3860 private DropLocation(Point p, int index, Position.Bias bias) {
3861 super (p);
3862 this .index = index;
3863 this .bias = bias;
3864 }
3865
3866 /**
3867 * Returns the index where dropped data should be inserted into the
3868 * associated component. This index represents a position between
3869 * characters, as would be interpreted by a caret.
3870 *
3871 * @return the drop index
3872 */
3873 public int getIndex() {
3874 return index;
3875 }
3876
3877 /**
3878 * Returns the bias for the drop index.
3879 *
3880 * @return the drop bias
3881 */
3882 public Position.Bias getBias() {
3883 return bias;
3884 }
3885
3886 /**
3887 * Returns a string representation of this drop location.
3888 * This method is intended to be used for debugging purposes,
3889 * and the content and format of the returned string may vary
3890 * between implementations.
3891 *
3892 * @return a string representation of this drop location
3893 */
3894 public String toString() {
3895 return getClass().getName() + "[dropPoint="
3896 + getDropPoint() + "," + "index=" + index + ","
3897 + "bias=" + bias + "]";
3898 }
3899 }
3900
3901 /**
3902 * TransferHandler used if one hasn't been supplied by the UI.
3903 */
3904 private static DefaultTransferHandler defaultTransferHandler;
3905
3906 /**
3907 * Maps from class name to Boolean indicating if
3908 * <code>processInputMethodEvent</code> has been overriden.
3909 */
3910 private static Map overrideMap;
3911
3912 /**
3913 * Returns a string representation of this <code>JTextComponent</code>.
3914 * This method is intended to be used only for debugging purposes, and the
3915 * content and format of the returned string may vary between
3916 * implementations. The returned string may be empty but may not
3917 * be <code>null</code>.
3918 * <P>
3919 * Overriding <code>paramString</code> to provide information about the
3920 * specific new aspects of the JFC components.
3921 *
3922 * @return a string representation of this <code>JTextComponent</code>
3923 */
3924 protected String paramString() {
3925 String editableString = (editable ? "true" : "false");
3926 String caretColorString = (caretColor != null ? caretColor
3927 .toString() : "");
3928 String selectionColorString = (selectionColor != null ? selectionColor
3929 .toString()
3930 : "");
3931 String selectedTextColorString = (selectedTextColor != null ? selectedTextColor
3932 .toString()
3933 : "");
3934 String disabledTextColorString = (disabledTextColor != null ? disabledTextColor
3935 .toString()
3936 : "");
3937 String marginString = (margin != null ? margin.toString() : "");
3938
3939 return super .paramString() + ",caretColor=" + caretColorString
3940 + ",disabledTextColor=" + disabledTextColorString
3941 + ",editable=" + editableString + ",margin="
3942 + marginString + ",selectedTextColor="
3943 + selectedTextColorString + ",selectionColor="
3944 + selectionColorString;
3945 }
3946
3947 /**
3948 * A Simple TransferHandler that exports the data as a String, and
3949 * imports the data from the String clipboard. This is only used
3950 * if the UI hasn't supplied one, which would only happen if someone
3951 * hasn't subclassed Basic.
3952 */
3953 static class DefaultTransferHandler extends TransferHandler
3954 implements UIResource {
3955 public void exportToClipboard(JComponent comp,
3956 Clipboard clipboard, int action)
3957 throws IllegalStateException {
3958 if (comp instanceof JTextComponent) {
3959 JTextComponent text = (JTextComponent) comp;
3960 int p0 = text.getSelectionStart();
3961 int p1 = text.getSelectionEnd();
3962 if (p0 != p1) {
3963 try {
3964 Document doc = text.getDocument();
3965 String srcData = doc.getText(p0, p1 - p0);
3966 StringSelection contents = new StringSelection(
3967 srcData);
3968
3969 // this may throw an IllegalStateException,
3970 // but it will be caught and handled in the
3971 // action that invoked this method
3972 clipboard.setContents(contents, null);
3973
3974 if (action == TransferHandler.MOVE) {
3975 doc.remove(p0, p1 - p0);
3976 }
3977 } catch (BadLocationException ble) {
3978 }
3979 }
3980 }
3981 }
3982
3983 public boolean importData(JComponent comp, Transferable t) {
3984 if (comp instanceof JTextComponent) {
3985 DataFlavor flavor = getFlavor(t
3986 .getTransferDataFlavors());
3987
3988 if (flavor != null) {
3989 InputContext ic = comp.getInputContext();
3990 if (ic != null) {
3991 ic.endComposition();
3992 }
3993 try {
3994 String data = (String) t
3995 .getTransferData(flavor);
3996
3997 ((JTextComponent) comp).replaceSelection(data);
3998 return true;
3999 } catch (UnsupportedFlavorException ufe) {
4000 } catch (IOException ioe) {
4001 }
4002 }
4003 }
4004 return false;
4005 }
4006
4007 public boolean canImport(JComponent comp,
4008 DataFlavor[] transferFlavors) {
4009 JTextComponent c = (JTextComponent) comp;
4010 if (!(c.isEditable() && c.isEnabled())) {
4011 return false;
4012 }
4013 return (getFlavor(transferFlavors) != null);
4014 }
4015
4016 public int getSourceActions(JComponent c) {
4017 return NONE;
4018 }
4019
4020 private DataFlavor getFlavor(DataFlavor[] flavors) {
4021 if (flavors != null) {
4022 for (int counter = 0; counter < flavors.length; counter++) {
4023 if (flavors[counter]
4024 .equals(DataFlavor.stringFlavor)) {
4025 return flavors[counter];
4026 }
4027 }
4028 }
4029 return null;
4030 }
4031 }
4032
4033 /**
4034 * Returns the JTextComponent that most recently had focus. The returned
4035 * value may currently have focus.
4036 */
4037 static final JTextComponent getFocusedComponent() {
4038 return (JTextComponent) AppContext.getAppContext().get(
4039 FOCUSED_COMPONENT);
4040 }
4041
4042 private int getCurrentEventModifiers() {
4043 int modifiers = 0;
4044 AWTEvent currentEvent = EventQueue.getCurrentEvent();
4045 if (currentEvent instanceof InputEvent) {
4046 modifiers = ((InputEvent) currentEvent).getModifiers();
4047 } else if (currentEvent instanceof ActionEvent) {
4048 modifiers = ((ActionEvent) currentEvent).getModifiers();
4049 }
4050 return modifiers;
4051 }
4052
4053 private static final Object KEYMAP_TABLE = new StringBuilder(
4054 "JTextComponent_KeymapTable");
4055 private JTextComponent editor;
4056 //
4057 // member variables used for on-the-spot input method
4058 // editing style support
4059 //
4060 private transient InputMethodRequests inputMethodRequestsHandler;
4061 private SimpleAttributeSet composedTextAttribute;
4062 private String composedTextContent;
4063 private Position composedTextStart;
4064 private Position composedTextEnd;
4065 private Position latestCommittedTextStart;
4066 private Position latestCommittedTextEnd;
4067 private ComposedTextCaret composedTextCaret;
4068 private transient Caret originalCaret;
4069 /**
4070 * Set to true after the check for the override of processInputMethodEvent
4071 * has been checked.
4072 */
4073 private boolean checkedInputOverride;
4074 private boolean needToSendKeyTypedEvent;
4075
4076 static class DefaultKeymap implements Keymap {
4077
4078 DefaultKeymap(String nm, Keymap parent) {
4079 this .nm = nm;
4080 this .parent = parent;
4081 bindings = new Hashtable();
4082 }
4083
4084 /**
4085 * Fetch the default action to fire if a
4086 * key is typed (ie a KEY_TYPED KeyEvent is received)
4087 * and there is no binding for it. Typically this
4088 * would be some action that inserts text so that
4089 * the keymap doesn't require an action for each
4090 * possible key.
4091 */
4092 public Action getDefaultAction() {
4093 if (defaultAction != null) {
4094 return defaultAction;
4095 }
4096 return (parent != null) ? parent.getDefaultAction() : null;
4097 }
4098
4099 /**
4100 * Set the default action to fire if a key is typed.
4101 */
4102 public void setDefaultAction(Action a) {
4103 defaultAction = a;
4104 }
4105
4106 public String getName() {
4107 return nm;
4108 }
4109
4110 public Action getAction(KeyStroke key) {
4111 Action a = (Action) bindings.get(key);
4112 if ((a == null) && (parent != null)) {
4113 a = parent.getAction(key);
4114 }
4115 return a;
4116 }
4117
4118 public KeyStroke[] getBoundKeyStrokes() {
4119 KeyStroke[] keys = new KeyStroke[bindings.size()];
4120 int i = 0;
4121 for (Enumeration e = bindings.keys(); e.hasMoreElements();) {
4122 keys[i++] = (KeyStroke) e.nextElement();
4123 }
4124 return keys;
4125 }
4126
4127 public Action[] getBoundActions() {
4128 Action[] actions = new Action[bindings.size()];
4129 int i = 0;
4130 for (Enumeration e = bindings.elements(); e
4131 .hasMoreElements();) {
4132 actions[i++] = (Action) e.nextElement();
4133 }
4134 return actions;
4135 }
4136
4137 public KeyStroke[] getKeyStrokesForAction(Action a) {
4138 if (a == null) {
4139 return null;
4140 }
4141 KeyStroke[] retValue = null;
4142 // Determine local bindings first.
4143 Vector keyStrokes = null;
4144 for (Enumeration enum_ = bindings.keys(); enum_
4145 .hasMoreElements();) {
4146 Object key = enum_.nextElement();
4147 if (bindings.get(key) == a) {
4148 if (keyStrokes == null) {
4149 keyStrokes = new Vector();
4150 }
4151 keyStrokes.addElement(key);
4152 }
4153 }
4154 // See if the parent has any.
4155 if (parent != null) {
4156 KeyStroke[] pStrokes = parent.getKeyStrokesForAction(a);
4157 if (pStrokes != null) {
4158 // Remove any bindings defined in the parent that
4159 // are locally defined.
4160 int rCount = 0;
4161 for (int counter = pStrokes.length - 1; counter >= 0; counter--) {
4162 if (isLocallyDefined(pStrokes[counter])) {
4163 pStrokes[counter] = null;
4164 rCount++;
4165 }
4166 }
4167 if (rCount > 0 && rCount < pStrokes.length) {
4168 if (keyStrokes == null) {
4169 keyStrokes = new Vector();
4170 }
4171 for (int counter = pStrokes.length - 1; counter >= 0; counter--) {
4172 if (pStrokes[counter] != null) {
4173 keyStrokes
4174 .addElement(pStrokes[counter]);
4175 }
4176 }
4177 } else if (rCount == 0) {
4178 if (keyStrokes == null) {
4179 retValue = pStrokes;
4180 } else {
4181 retValue = new KeyStroke[keyStrokes.size()
4182 + pStrokes.length];
4183 keyStrokes.copyInto(retValue);
4184 System.arraycopy(pStrokes, 0, retValue,
4185 keyStrokes.size(), pStrokes.length);
4186 keyStrokes = null;
4187 }
4188 }
4189 }
4190 }
4191 if (keyStrokes != null) {
4192 retValue = new KeyStroke[keyStrokes.size()];
4193 keyStrokes.copyInto(retValue);
4194 }
4195 return retValue;
4196 }
4197
4198 public boolean isLocallyDefined(KeyStroke key) {
4199 return bindings.containsKey(key);
4200 }
4201
4202 public void addActionForKeyStroke(KeyStroke key, Action a) {
4203 bindings.put(key, a);
4204 }
4205
4206 public void removeKeyStrokeBinding(KeyStroke key) {
4207 bindings.remove(key);
4208 }
4209
4210 public void removeBindings() {
4211 bindings.clear();
4212 }
4213
4214 public Keymap getResolveParent() {
4215 return parent;
4216 }
4217
4218 public void setResolveParent(Keymap parent) {
4219 this .parent = parent;
4220 }
4221
4222 /**
4223 * String representation of the keymap... potentially
4224 * a very long string.
4225 */
4226 public String toString() {
4227 return "Keymap[" + nm + "]" + bindings;
4228 }
4229
4230 String nm;
4231 Keymap parent;
4232 Hashtable bindings;
4233 Action defaultAction;
4234 }
4235
4236 /**
4237 * KeymapWrapper wraps a Keymap inside an InputMap. For KeymapWrapper
4238 * to be useful it must be used with a KeymapActionMap.
4239 * KeymapWrapper for the most part, is an InputMap with two parents.
4240 * The first parent visited is ALWAYS the Keymap, with the second
4241 * parent being the parent inherited from InputMap. If
4242 * <code>keymap.getAction</code> returns null, implying the Keymap
4243 * does not have a binding for the KeyStroke,
4244 * the parent is then visited. If the Keymap has a binding, the
4245 * Action is returned, if not and the KeyStroke represents a
4246 * KeyTyped event and the Keymap has a defaultAction,
4247 * <code>DefaultActionKey</code> is returned.
4248 * <p>KeymapActionMap is then able to transate the object passed in
4249 * to either message the Keymap, or message its default implementation.
4250 */
4251 static class KeymapWrapper extends InputMap {
4252 static final Object DefaultActionKey = new Object();
4253
4254 private Keymap keymap;
4255
4256 KeymapWrapper(Keymap keymap) {
4257 this .keymap = keymap;
4258 }
4259
4260 public KeyStroke[] keys() {
4261 KeyStroke[] sKeys = super .keys();
4262 KeyStroke[] keymapKeys = keymap.getBoundKeyStrokes();
4263 int sCount = (sKeys == null) ? 0 : sKeys.length;
4264 int keymapCount = (keymapKeys == null) ? 0
4265 : keymapKeys.length;
4266 if (sCount == 0) {
4267 return keymapKeys;
4268 }
4269 if (keymapCount == 0) {
4270 return sKeys;
4271 }
4272 KeyStroke[] retValue = new KeyStroke[sCount + keymapCount];
4273 // There may be some duplication here...
4274 System.arraycopy(sKeys, 0, retValue, 0, sCount);
4275 System.arraycopy(keymapKeys, 0, retValue, sCount,
4276 keymapCount);
4277 return retValue;
4278 }
4279
4280 public int size() {
4281 // There may be some duplication here...
4282 KeyStroke[] keymapStrokes = keymap.getBoundKeyStrokes();
4283 int keymapCount = (keymapStrokes == null) ? 0
4284 : keymapStrokes.length;
4285 return super .size() + keymapCount;
4286 }
4287
4288 public Object get(KeyStroke keyStroke) {
4289 Object retValue = keymap.getAction(keyStroke);
4290 if (retValue == null) {
4291 retValue = super .get(keyStroke);
4292 if (retValue == null
4293 && keyStroke.getKeyChar() != KeyEvent.CHAR_UNDEFINED
4294 && keymap.getDefaultAction() != null) {
4295 // Implies this is a KeyTyped event, use the default
4296 // action.
4297 retValue = DefaultActionKey;
4298 }
4299 }
4300 return retValue;
4301 }
4302 }
4303
4304 /**
4305 * Wraps a Keymap inside an ActionMap. This is used with
4306 * a KeymapWrapper. If <code>get</code> is passed in
4307 * <code>KeymapWrapper.DefaultActionKey</code>, the default action is
4308 * returned, otherwise if the key is an Action, it is returned.
4309 */
4310 static class KeymapActionMap extends ActionMap {
4311 private Keymap keymap;
4312
4313 KeymapActionMap(Keymap keymap) {
4314 this .keymap = keymap;
4315 }
4316
4317 public Object[] keys() {
4318 Object[] sKeys = super .keys();
4319 Object[] keymapKeys = keymap.getBoundActions();
4320 int sCount = (sKeys == null) ? 0 : sKeys.length;
4321 int keymapCount = (keymapKeys == null) ? 0
4322 : keymapKeys.length;
4323 boolean hasDefault = (keymap.getDefaultAction() != null);
4324 if (hasDefault) {
4325 keymapCount++;
4326 }
4327 if (sCount == 0) {
4328 if (hasDefault) {
4329 Object[] retValue = new Object[keymapCount];
4330 if (keymapCount > 1) {
4331 System.arraycopy(keymapKeys, 0, retValue, 0,
4332 keymapCount - 1);
4333 }
4334 retValue[keymapCount - 1] = KeymapWrapper.DefaultActionKey;
4335 return retValue;
4336 }
4337 return keymapKeys;
4338 }
4339 if (keymapCount == 0) {
4340 return sKeys;
4341 }
4342 Object[] retValue = new Object[sCount + keymapCount];
4343 // There may be some duplication here...
4344 System.arraycopy(sKeys, 0, retValue, 0, sCount);
4345 if (hasDefault) {
4346 if (keymapCount > 1) {
4347 System.arraycopy(keymapKeys, 0, retValue, sCount,
4348 keymapCount - 1);
4349 }
4350 retValue[sCount + keymapCount - 1] = KeymapWrapper.DefaultActionKey;
4351 } else {
4352 System.arraycopy(keymapKeys, 0, retValue, sCount,
4353 keymapCount);
4354 }
4355 return retValue;
4356 }
4357
4358 public int size() {
4359 // There may be some duplication here...
4360 Object[] actions = keymap.getBoundActions();
4361 int keymapCount = (actions == null) ? 0 : actions.length;
4362 if (keymap.getDefaultAction() != null) {
4363 keymapCount++;
4364 }
4365 return super .size() + keymapCount;
4366 }
4367
4368 public Action get(Object key) {
4369 Action retValue = super .get(key);
4370 if (retValue == null) {
4371 // Try the Keymap.
4372 if (key == KeymapWrapper.DefaultActionKey) {
4373 retValue = keymap.getDefaultAction();
4374 } else if (key instanceof Action) {
4375 // This is a little iffy, technically an Action is
4376 // a valid Key. We're assuming the Action came from
4377 // the InputMap though.
4378 retValue = (Action) key;
4379 }
4380 }
4381 return retValue;
4382 }
4383 }
4384
4385 private static final Object FOCUSED_COMPONENT = new StringBuilder(
4386 "JTextComponent_FocusedComponent");
4387
4388 /**
4389 * The default keymap that will be shared by all
4390 * <code>JTextComponent</code> instances unless they
4391 * have had a different keymap set.
4392 */
4393 public static final String DEFAULT_KEYMAP = "default";
4394
4395 /**
4396 * Event to use when firing a notification of change to caret
4397 * position. This is mutable so that the event can be reused
4398 * since caret events can be fairly high in bandwidth.
4399 */
4400 static class MutableCaretEvent extends CaretEvent implements
4401 ChangeListener, FocusListener, MouseListener {
4402
4403 MutableCaretEvent(JTextComponent c) {
4404 super (c);
4405 }
4406
4407 final void fire() {
4408 JTextComponent c = (JTextComponent) getSource();
4409 if (c != null) {
4410 Caret caret = c.getCaret();
4411 dot = caret.getDot();
4412 mark = caret.getMark();
4413 c.fireCaretUpdate(this );
4414 }
4415 }
4416
4417 public final String toString() {
4418 return "dot=" + dot + "," + "mark=" + mark;
4419 }
4420
4421 // --- CaretEvent methods -----------------------
4422
4423 public final int getDot() {
4424 return dot;
4425 }
4426
4427 public final int getMark() {
4428 return mark;
4429 }
4430
4431 // --- ChangeListener methods -------------------
4432
4433 public final void stateChanged(ChangeEvent e) {
4434 if (!dragActive) {
4435 fire();
4436 }
4437 }
4438
4439 // --- FocusListener methods -----------------------------------
4440 public void focusGained(FocusEvent fe) {
4441 AppContext.getAppContext().put(FOCUSED_COMPONENT,
4442 fe.getSource());
4443 }
4444
4445 public void focusLost(FocusEvent fe) {
4446 }
4447
4448 // --- MouseListener methods -----------------------------------
4449
4450 /**
4451 * Requests focus on the associated
4452 * text component, and try to set the cursor position.
4453 *
4454 * @param e the mouse event
4455 * @see MouseListener#mousePressed
4456 */
4457 public final void mousePressed(MouseEvent e) {
4458 dragActive = true;
4459 }
4460
4461 /**
4462 * Called when the mouse is released.
4463 *
4464 * @param e the mouse event
4465 * @see MouseListener#mouseReleased
4466 */
4467 public final void mouseReleased(MouseEvent e) {
4468 dragActive = false;
4469 fire();
4470 }
4471
4472 public final void mouseClicked(MouseEvent e) {
4473 }
4474
4475 public final void mouseEntered(MouseEvent e) {
4476 }
4477
4478 public final void mouseExited(MouseEvent e) {
4479 }
4480
4481 private boolean dragActive;
4482 private int dot;
4483 private int mark;
4484 }
4485
4486 //
4487 // Process any input method events that the component itself
4488 // recognizes. The default on-the-spot handling for input method
4489 // composed(uncommitted) text is done here after all input
4490 // method listeners get called for stealing the events.
4491 //
4492 protected void processInputMethodEvent(InputMethodEvent e) {
4493 // let listeners handle the events
4494 super .processInputMethodEvent(e);
4495
4496 if (!e.isConsumed()) {
4497 if (!isEditable()) {
4498 return;
4499 } else {
4500 switch (e.getID()) {
4501 case InputMethodEvent.INPUT_METHOD_TEXT_CHANGED:
4502 replaceInputMethodText(e);
4503
4504 // fall through
4505
4506 case InputMethodEvent.CARET_POSITION_CHANGED:
4507 setInputMethodCaretPosition(e);
4508 break;
4509 }
4510 }
4511
4512 e.consume();
4513 }
4514 }
4515
4516 //
4517 // Overrides this method to become an active input method client.
4518 //
4519 public InputMethodRequests getInputMethodRequests() {
4520 if (inputMethodRequestsHandler == null) {
4521 inputMethodRequestsHandler = (InputMethodRequests) new InputMethodRequestsHandler();
4522 Document doc = getDocument();
4523 if (doc != null) {
4524 doc
4525 .addDocumentListener((DocumentListener) inputMethodRequestsHandler);
4526 }
4527 }
4528
4529 return inputMethodRequestsHandler;
4530 }
4531
4532 //
4533 // Overrides this method to watch the listener installed.
4534 //
4535 public void addInputMethodListener(InputMethodListener l) {
4536 super .addInputMethodListener(l);
4537 if (l != null) {
4538 needToSendKeyTypedEvent = false;
4539 checkedInputOverride = true;
4540 }
4541 }
4542
4543 //
4544 // Default implementation of the InputMethodRequests interface.
4545 //
4546 class InputMethodRequestsHandler implements InputMethodRequests,
4547 DocumentListener {
4548
4549 // --- InputMethodRequests methods ---
4550
4551 public AttributedCharacterIterator cancelLatestCommittedText(
4552 Attribute[] attributes) {
4553 Document doc = getDocument();
4554 if ((doc != null)
4555 && (latestCommittedTextStart != null)
4556 && (!latestCommittedTextStart
4557 .equals(latestCommittedTextEnd))) {
4558 try {
4559 int startIndex = latestCommittedTextStart
4560 .getOffset();
4561 int endIndex = latestCommittedTextEnd.getOffset();
4562 String latestCommittedText = doc.getText(
4563 startIndex, endIndex - startIndex);
4564 doc.remove(startIndex, endIndex - startIndex);
4565 return new AttributedString(latestCommittedText)
4566 .getIterator();
4567 } catch (BadLocationException ble) {
4568 }
4569 }
4570 return null;
4571 }
4572
4573 public AttributedCharacterIterator getCommittedText(
4574 int beginIndex, int endIndex, Attribute[] attributes) {
4575 int composedStartIndex = 0;
4576 int composedEndIndex = 0;
4577 if (composedTextExists()) {
4578 composedStartIndex = composedTextStart.getOffset();
4579 composedEndIndex = composedTextEnd.getOffset();
4580 }
4581
4582 String committed;
4583 try {
4584 if (beginIndex < composedStartIndex) {
4585 if (endIndex <= composedStartIndex) {
4586 committed = getText(beginIndex, endIndex
4587 - beginIndex);
4588 } else {
4589 int firstPartLength = composedStartIndex
4590 - beginIndex;
4591 committed = getText(beginIndex, firstPartLength)
4592 + getText(composedEndIndex, endIndex
4593 - beginIndex - firstPartLength);
4594 }
4595 } else {
4596 committed = getText(beginIndex
4597 + (composedEndIndex - composedStartIndex),
4598 endIndex - beginIndex);
4599 }
4600 } catch (BadLocationException ble) {
4601 throw new IllegalArgumentException("Invalid range");
4602 }
4603 return new AttributedString(committed).getIterator();
4604 }
4605
4606 public int getCommittedTextLength() {
4607 Document doc = getDocument();
4608 int length = 0;
4609 if (doc != null) {
4610 length = doc.getLength();
4611 if (composedTextContent != null) {
4612 if (composedTextEnd == null
4613 || composedTextStart == null) {
4614 /*
4615 * fix for : 6355666
4616 * this is the case when this method is invoked
4617 * from DocumentListener. At this point
4618 * composedTextEnd and composedTextStart are
4619 * not defined yet.
4620 */
4621 length -= composedTextContent.length();
4622 } else {
4623 length -= composedTextEnd.getOffset()
4624 - composedTextStart.getOffset();
4625 }
4626 }
4627 }
4628 return length;
4629 }
4630
4631 public int getInsertPositionOffset() {
4632 int composedStartIndex = 0;
4633 int composedEndIndex = 0;
4634 if (composedTextExists()) {
4635 composedStartIndex = composedTextStart.getOffset();
4636 composedEndIndex = composedTextEnd.getOffset();
4637 }
4638 int caretIndex = getCaretPosition();
4639
4640 if (caretIndex < composedStartIndex) {
4641 return caretIndex;
4642 } else if (caretIndex < composedEndIndex) {
4643 return composedStartIndex;
4644 } else {
4645 return caretIndex
4646 - (composedEndIndex - composedStartIndex);
4647 }
4648 }
4649
4650 public TextHitInfo getLocationOffset(int x, int y) {
4651 if (composedTextAttribute == null) {
4652 return null;
4653 } else {
4654 Point p = getLocationOnScreen();
4655 p.x = x - p.x;
4656 p.y = y - p.y;
4657 int pos = viewToModel(p);
4658 if ((pos >= composedTextStart.getOffset())
4659 && (pos <= composedTextEnd.getOffset())) {
4660 return TextHitInfo.leading(pos
4661 - composedTextStart.getOffset());
4662 } else {
4663 return null;
4664 }
4665 }
4666 }
4667
4668 public Rectangle getTextLocation(TextHitInfo offset) {
4669 Rectangle r;
4670
4671 try {
4672 r = modelToView(getCaretPosition());
4673 if (r != null) {
4674 Point p = getLocationOnScreen();
4675 r.translate(p.x, p.y);
4676 }
4677 } catch (BadLocationException ble) {
4678 r = null;
4679 }
4680
4681 if (r == null)
4682 r = new Rectangle();
4683
4684 return r;
4685 }
4686
4687 public AttributedCharacterIterator getSelectedText(
4688 Attribute[] attributes) {
4689 String selection = JTextComponent.this .getSelectedText();
4690 if (selection != null) {
4691 return new AttributedString(selection).getIterator();
4692 } else {
4693 return null;
4694 }
4695 }
4696
4697 // --- DocumentListener methods ---
4698
4699 public void changedUpdate(DocumentEvent e) {
4700 latestCommittedTextStart = latestCommittedTextEnd = null;
4701 }
4702
4703 public void insertUpdate(DocumentEvent e) {
4704 latestCommittedTextStart = latestCommittedTextEnd = null;
4705 }
4706
4707 public void removeUpdate(DocumentEvent e) {
4708 latestCommittedTextStart = latestCommittedTextEnd = null;
4709 }
4710 }
4711
4712 //
4713 // Replaces the current input method (composed) text according to
4714 // the passed input method event. This method also inserts the
4715 // committed text into the document.
4716 //
4717 private void replaceInputMethodText(InputMethodEvent e) {
4718 int commitCount = e.getCommittedCharacterCount();
4719 AttributedCharacterIterator text = e.getText();
4720 int composedTextIndex;
4721
4722 // old composed text deletion
4723 Document doc = getDocument();
4724 if (composedTextExists()) {
4725 try {
4726 doc.remove(composedTextStart.getOffset(),
4727 composedTextEnd.getOffset()
4728 - composedTextStart.getOffset());
4729 } catch (BadLocationException ble) {
4730 }
4731 composedTextStart = composedTextEnd = null;
4732 composedTextAttribute = null;
4733 composedTextContent = null;
4734 }
4735
4736 if (text != null) {
4737 text.first();
4738 int committedTextStartIndex = 0;
4739 int committedTextEndIndex = 0;
4740
4741 // committed text insertion
4742 if (commitCount > 0) {
4743 // Remember latest committed text start index
4744 committedTextStartIndex = caret.getDot();
4745
4746 // Need to generate KeyTyped events for the committed text for components
4747 // that are not aware they are active input method clients.
4748 if (shouldSynthensizeKeyEvents()) {
4749 for (char c = text.current(); commitCount > 0; c = text
4750 .next(), commitCount--) {
4751 KeyEvent ke = new KeyEvent(this ,
4752 KeyEvent.KEY_TYPED, EventQueue
4753 .getMostRecentEventTime(), 0,
4754 KeyEvent.VK_UNDEFINED, c);
4755 processKeyEvent(ke);
4756 }
4757 } else {
4758 StringBuffer strBuf = new StringBuffer();
4759 for (char c = text.current(); commitCount > 0; c = text
4760 .next(), commitCount--) {
4761 strBuf.append(c);
4762 }
4763
4764 // map it to an ActionEvent
4765 mapCommittedTextToAction(new String(strBuf));
4766 }
4767
4768 // Remember latest committed text end index
4769 committedTextEndIndex = caret.getDot();
4770 }
4771
4772 // new composed text insertion
4773 composedTextIndex = text.getIndex();
4774 if (composedTextIndex < text.getEndIndex()) {
4775 createComposedTextAttribute(composedTextIndex, text);
4776 try {
4777 replaceSelection(null);
4778 doc.insertString(caret.getDot(),
4779 composedTextContent, composedTextAttribute);
4780 composedTextStart = doc.createPosition(caret
4781 .getDot()
4782 - composedTextContent.length());
4783 composedTextEnd = doc
4784 .createPosition(caret.getDot());
4785 } catch (BadLocationException ble) {
4786 composedTextStart = composedTextEnd = null;
4787 composedTextAttribute = null;
4788 composedTextContent = null;
4789 }
4790 }
4791
4792 // Save the latest committed text information
4793 if (committedTextStartIndex != committedTextEndIndex) {
4794 try {
4795 latestCommittedTextStart = doc
4796 .createPosition(committedTextStartIndex);
4797 latestCommittedTextEnd = doc
4798 .createPosition(committedTextEndIndex);
4799 } catch (BadLocationException ble) {
4800 latestCommittedTextStart = latestCommittedTextEnd = null;
4801 }
4802 } else {
4803 latestCommittedTextStart = latestCommittedTextEnd = null;
4804 }
4805 }
4806 }
4807
4808 private void createComposedTextAttribute(int composedIndex,
4809 AttributedCharacterIterator text) {
4810 Document doc = getDocument();
4811 StringBuffer strBuf = new StringBuffer();
4812
4813 // create attributed string with no attributes
4814 for (char c = text.setIndex(composedIndex); c != CharacterIterator.DONE; c = text
4815 .next()) {
4816 strBuf.append(c);
4817 }
4818
4819 composedTextContent = new String(strBuf);
4820 composedTextAttribute = new SimpleAttributeSet();
4821 composedTextAttribute.addAttribute(
4822 StyleConstants.ComposedTextAttribute,
4823 new AttributedString(text, composedIndex, text
4824 .getEndIndex()));
4825 }
4826
4827 private 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 }
4840 return false;
4841 }
4842
4843 private void restoreComposedText() {
4844 Document doc = getDocument();
4845 try {
4846 doc.insertString(caret.getDot(), composedTextContent,
4847 composedTextAttribute);
4848 composedTextStart = doc.createPosition(caret.getDot()
4849 - composedTextContent.length());
4850 composedTextEnd = doc.createPosition(caret.getDot());
4851 } catch (BadLocationException ble) {
4852 }
4853 }
4854
4855 //
4856 // Map committed text to an ActionEvent. If the committed text length is 1,
4857 // treat it as a KeyStroke, otherwise or there is no KeyStroke defined,
4858 // treat it just as a default action.
4859 //
4860 private void mapCommittedTextToAction(String committedText) {
4861 Keymap binding = getKeymap();
4862 if (binding != null) {
4863 Action a = null;
4864 if (committedText.length() == 1) {
4865 KeyStroke k = KeyStroke.getKeyStroke(committedText
4866 .charAt(0));
4867 a = binding.getAction(k);
4868 }
4869
4870 if (a == null) {
4871 a = binding.getDefaultAction();
4872 }
4873
4874 if (a != null) {
4875 ActionEvent ae = new ActionEvent(this ,
4876 ActionEvent.ACTION_PERFORMED, committedText,
4877 EventQueue.getMostRecentEventTime(),
4878 getCurrentEventModifiers());
4879 a.actionPerformed(ae);
4880 }
4881 }
4882 }
4883
4884 //
4885 // Sets the caret position according to the passed input method
4886 // event. Also, sets/resets composed text caret appropriately.
4887 //
4888 private void setInputMethodCaretPosition(InputMethodEvent e) {
4889 int dot;
4890
4891 if (composedTextExists()) {
4892 dot = composedTextStart.getOffset();
4893 if (!(caret instanceof ComposedTextCaret)) {
4894 if (composedTextCaret == null) {
4895 composedTextCaret = new ComposedTextCaret();
4896 }
4897 originalCaret = caret;
4898 // Sets composed text caret
4899 exchangeCaret(originalCaret, composedTextCaret);
4900 }
4901
4902 TextHitInfo caretPos = e.getCaret();
4903 if (caretPos != null) {
4904 int index = caretPos.getInsertionIndex();
4905 dot += index;
4906 if (index == 0) {
4907 // Scroll the component if needed so that the composed text
4908 // becomes visible.
4909 try {
4910 Rectangle d = modelToView(dot);
4911 Rectangle end = modelToView(composedTextEnd
4912 .getOffset());
4913 Rectangle b = getBounds();
4914 d.x += Math.min(end.x - d.x, b.width);
4915 scrollRectToVisible(d);
4916 } catch (BadLocationException ble) {
4917 }
4918 }
4919 }
4920 caret.setDot(dot);
4921 } else if (caret instanceof ComposedTextCaret) {
4922 dot = caret.getDot();
4923 // Restores original caret
4924 exchangeCaret(caret, originalCaret);
4925 caret.setDot(dot);
4926 }
4927 }
4928
4929 private void exchangeCaret(Caret oldCaret, Caret newCaret) {
4930 int blinkRate = oldCaret.getBlinkRate();
4931 setCaret(newCaret);
4932 caret.setBlinkRate(blinkRate);
4933 caret.setVisible(hasFocus());
4934 }
4935
4936 /**
4937 * Returns true if KeyEvents should be synthesized from an InputEvent.
4938 */
4939 private boolean shouldSynthensizeKeyEvents() {
4940 if (!checkedInputOverride) {
4941 checkedInputOverride = true;
4942 needToSendKeyTypedEvent = !isProcessInputMethodEventOverridden();
4943 }
4944 return needToSendKeyTypedEvent;
4945 }
4946
4947 //
4948 // Checks whether the client code overrides processInputMethodEvent. If it is overridden,
4949 // need not to generate KeyTyped events for committed text. If it's not, behave as an
4950 // passive input method client.
4951 //
4952 private boolean isProcessInputMethodEventOverridden() {
4953 if (overrideMap == null) {
4954 overrideMap = Collections.synchronizedMap(new HashMap());
4955 }
4956 Boolean retValue = (Boolean) overrideMap.get(getClass()
4957 .getName());
4958
4959 if (retValue != null) {
4960 return retValue.booleanValue();
4961 }
4962 Boolean ret = (Boolean) AccessController
4963 .doPrivileged(new PrivilegedAction() {
4964 public Object run() {
4965 return isProcessInputMethodEventOverridden(JTextComponent.this
4966 .getClass());
4967 }
4968 });
4969
4970 return ret.booleanValue();
4971 }
4972
4973 //
4974 // Checks whether a composed text in this text component
4975 //
4976 boolean composedTextExists() {
4977 return (composedTextStart != null);
4978 }
4979
4980 //
4981 // Caret implementation for editing the composed text.
4982 //
4983 class ComposedTextCaret extends DefaultCaret implements
4984 Serializable {
4985 Color bg;
4986
4987 //
4988 // Get the background color of the component
4989 //
4990 public void install(JTextComponent c) {
4991 super .install(c);
4992
4993 Document doc = c.getDocument();
4994 if (doc instanceof StyledDocument) {
4995 StyledDocument sDoc = (StyledDocument) doc;
4996 Element elem = sDoc
4997 .getCharacterElement(c.composedTextStart
4998 .getOffset());
4999 AttributeSet attr = elem.getAttributes();
5000 bg = sDoc.getBackground(attr);
5001 }
5002
5003 if (bg == null) {
5004 bg = c.getBackground();
5005 }
5006 }
5007
5008 //
5009 // Draw caret in XOR mode.
5010 //
5011 public void paint(Graphics g) {
5012 if (isVisible()) {
5013 try {
5014 Rectangle r = component.modelToView(getDot());
5015 g.setXORMode(bg);
5016 g.drawLine(r.x, r.y, r.x, r.y + r.height - 1);
5017 g.setPaintMode();
5018 } catch (BadLocationException e) {
5019 // can't render I guess
5020 //System.err.println("Can't render cursor");
5021 }
5022 }
5023 }
5024
5025 //
5026 // If some area other than the composed text is clicked by mouse,
5027 // issue endComposition() to force commit the composed text.
5028 //
5029 protected void positionCaret(MouseEvent me) {
5030 JTextComponent host = component;
5031 Point pt = new Point(me.getX(), me.getY());
5032 int offset = host.viewToModel(pt);
5033 int composedStartIndex = host.composedTextStart.getOffset();
5034 if ((offset < composedStartIndex)
5035 || (offset > composedTextEnd.getOffset())) {
5036 try {
5037 // Issue endComposition
5038 Position newPos = host.getDocument()
5039 .createPosition(offset);
5040 host.getInputContext().endComposition();
5041
5042 // Post a caret positioning runnable to assure that the positioning
5043 // occurs *after* committing the composed text.
5044 EventQueue.invokeLater(new DoSetCaretPosition(host,
5045 newPos));
5046 } catch (BadLocationException ble) {
5047 System.err.println(ble);
5048 }
5049 } else {
5050 // Normal processing
5051 super .positionCaret(me);
5052 }
5053 }
5054 }
5055
5056 //
5057 // Runnable class for invokeLater() to set caret position later.
5058 //
5059 private class DoSetCaretPosition implements Runnable {
5060 JTextComponent host;
5061 Position newPos;
5062
5063 DoSetCaretPosition(JTextComponent host, Position newPos) {
5064 this .host = host;
5065 this .newPos = newPos;
5066 }
5067
5068 public void run() {
5069 host.setCaretPosition(newPos.getOffset());
5070 }
5071 }
5072 }
|