/*
* Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing.text;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.beans.Transient;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Vector;
import java.util.Map;
import java.util.concurrent.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.print.*;
import java.awt.datatransfer.*;
import java.awt.im.InputContext;
import java.awt.im.InputMethodRequests;
import java.awt.font.TextHitInfo;
import java.awt.font.TextAttribute;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import javax.print.PrintService;
import javax.print.attribute.PrintRequestAttributeSet;
import java.text.*;
import java.text.AttributedCharacterIterator.Attribute;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.accessibility.*;
import javax.print.attribute.*;
import sun.awt.AppContext;
import sun.swing.PrintingStatus;
import sun.swing.SwingUtilities2;
import sun.swing.text.TextComponentPrintable;
import sun.swing.SwingAccessor;
/**
* JTextComponent
is the base class for swing text
* components. It tries to be compatible with the
* java.awt.TextComponent
class
* where it can reasonably do so. Also provided are other services
* for additional flexibility (beyond the pluggable UI and bean
* support).
* You can find information on how to use the functionality
* this class provides in
* General Rules for Using Text Components,
* a section in The Java Tutorial.
*
*
*
CaretListener
* interface that have been registered with the text component.
* The UI will install a default caret unless a customized caret
* has been set. DefaultCaret
* tries to make itself visible which may lead to scrolling
* of a text component within JScrollPane
. The default caret
* behavior can be changed by the {@link DefaultCaret#setUpdatePolicy} method.
* *
Action
interface,
* using the TextAction
implementation.
* The set of commands supported by the text component can be
* found with the {@link #getActions} method. These actions
* can be bound to key events, fired from buttons, etc.
*
* *
* A {@link javax.swing.text.Keymap} lets an application bind key
* strokes to actions.
* In order to allow keymaps to be shared across multiple text components, they
* can use actions that extend TextAction
.
* TextAction
can determine which JTextComponent
* most recently has or had focus and therefore is the subject of
* the action (In the case that the ActionEvent
* sent to the action doesn't contain the target text component as its source).
*
* The input method framework
* lets text components interact with input methods, separate software
* components that preprocess events to let users enter thousands of
* different characters using keyboards with far fewer keys.
* JTextComponent
is an active client of
* the framework, so it implements the preferred user interface for interacting
* with input methods. As a consequence, some key events do not reach the text
* component because they are handled by an input method, and some text input
* reaches the text component as committed text within an {@link
* java.awt.event.InputMethodEvent} instead of as a key event.
* The complete text input is the combination of the characters in
* keyTyped
key events and committed text in input method events.
*
* The AWT listener model lets applications attach event listeners to * components in order to bind events to actions. Swing encourages the * use of keymaps instead of listeners, but maintains compatibility * with listeners by giving the listeners a chance to steal an event * by consuming it. *
* Keyboard event and input method events are handled in the following stages, * with each stage capable of consuming the event: * *
Stage |
* KeyEvent |
* InputMethodEvent |
---|---|---|
1. | *input methods | *(generated here) |
2. | *focus manager | ** |
3. | *registered key listeners | *registered input method listeners |
4. | ** | input method handling in JTextComponent |
5. | keymap handling using the current keymap | |
6. | keyboard handling in JComponent (e.g. accelerators, component navigation, etc.) | *
* To maintain compatibility with applications that listen to key * events but are not aware of input method events, the input * method handling in stage 4 provides a compatibility mode for * components that do not process input method events. For these * components, the committed text is converted to keyTyped key events * and processed in the key event pipeline starting at stage 3 * instead of in the input method event pipeline. *
* By default the component will create a keymap (named DEFAULT_KEYMAP) * that is shared by all JTextComponent instances as the default keymap. * Typically a look-and-feel implementation will install a different keymap * that resolves to the default keymap for those bindings not found in the * different keymap. The minimal bindings include: *
*
* The model is defined by the {@link Document} interface. * This is intended to provide a flexible text storage mechanism * that tracks change during edits and can be extended to more sophisticated * models. The model interfaces are meant to capture the capabilities of * expression given by SGML, a system used to express a wide variety of * content. * Each modification to the document causes notification of the * details of the change to be sent to all observers in the form of a * {@link DocumentEvent} which allows the views to stay up to date with the model. * This event is sent to observers that have implemented the * {@link DocumentListener} * interface and registered interest with the model being observed. * *
*
*
UndoableEdit
records that can be used in conjunction
* with a history buffer to provide the undo/redo support.
* The support is provided by the Document model, which allows
* one to attach UndoableEditListener implementations.
*
* *
AbstractDocument
* describes the assumptions of the protection provided.
* The methods that are safe to call asynchronously are marked
* with comments.
*
* *
*
* Warning:
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeansTM
* has been added to the java.beans
package.
* Please see {@link java.beans.XMLEncoder}.
*
* @beaninfo
* attribute: isContainer false
*
* @author Timothy Prinzing
* @author Igor Kushnirskiy (printing support)
* @see Document
* @see DocumentEvent
* @see DocumentListener
* @see Caret
* @see CaretEvent
* @see CaretListener
* @see TextUI
* @see View
* @see ViewFactory
*/
public abstract class JTextComponent extends JComponent implements Scrollable, Accessible
{
/**
* Creates a new JTextComponent
.
* Listeners for caret events are established, and the pluggable
* UI installed. The component is marked as editable. No layout manager
* is used, because layout is managed by the view subsystem of text.
* The document model is set to null
.
*/
public JTextComponent() {
super();
// enable InputMethodEvent for on-the-spot pre-editing
enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.INPUT_METHOD_EVENT_MASK);
caretEvent = new MutableCaretEvent(this);
addMouseListener(caretEvent);
addFocusListener(caretEvent);
setEditable(true);
setDragEnabled(false);
setLayout(null); // layout is managed by View hierarchy
updateUI();
}
/**
* Fetches the user-interface factory for this text-oriented editor.
*
* @return the factory
*/
public TextUI getUI() { return (TextUI)ui; }
/**
* Sets the user-interface factory for this text-oriented editor.
*
* @param ui the factory
*/
public void setUI(TextUI ui) {
super.setUI(ui);
}
/**
* Reloads the pluggable UI. The key used to fetch the
* new interface is getUIClassID()
. The type of
* the UI is TextUI
. invalidate
* is called after setting the UI.
*/
public void updateUI() {
setUI((TextUI)UIManager.getUI(this));
invalidate();
}
/**
* Adds a caret listener for notification of any changes
* to the caret.
*
* @param listener the listener to be added
* @see javax.swing.event.CaretEvent
*/
public void addCaretListener(CaretListener listener) {
listenerList.add(CaretListener.class, listener);
}
/**
* Removes a caret listener.
*
* @param listener the listener to be removed
* @see javax.swing.event.CaretEvent
*/
public void removeCaretListener(CaretListener listener) {
listenerList.remove(CaretListener.class, listener);
}
/**
* Returns an array of all the caret listeners
* registered on this text component.
*
* @return all of this component's CaretListener
s
* or an empty
* array if no caret listeners are currently registered
*
* @see #addCaretListener
* @see #removeCaretListener
*
* @since 1.4
*/
public CaretListener[] getCaretListeners() {
return listenerList.getListeners(CaretListener.class);
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method. The listener list is processed in a
* last-to-first manner.
*
* @param e the event
* @see EventListenerList
*/
protected void fireCaretUpdate(CaretEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==CaretListener.class) {
((CaretListener)listeners[i+1]).caretUpdate(e);
}
}
}
/**
* Associates the editor with a text document.
* The currently registered factory is used to build a view for
* the document, which gets displayed by the editor after revalidation.
* A PropertyChange event ("document") is propagated to each listener.
*
* @param doc the document to display/edit
* @see #getDocument
* @beaninfo
* description: the text document model
* bound: true
* expert: true
*/
public void setDocument(Document doc) {
Document old = model;
/*
* aquire a read lock on the old model to prevent notification of
* mutations while we disconnecting the old model.
*/
try {
if (old instanceof AbstractDocument) {
((AbstractDocument)old).readLock();
}
if (accessibleContext != null) {
model.removeDocumentListener(
((AccessibleJTextComponent)accessibleContext));
}
if (inputMethodRequestsHandler != null) {
model.removeDocumentListener((DocumentListener)inputMethodRequestsHandler);
}
model = doc;
// Set the document's run direction property to match the
// component's ComponentOrientation property.
Boolean runDir = getComponentOrientation().isLeftToRight()
? TextAttribute.RUN_DIRECTION_LTR
: TextAttribute.RUN_DIRECTION_RTL;
if (runDir != doc.getProperty(TextAttribute.RUN_DIRECTION)) {
doc.putProperty(TextAttribute.RUN_DIRECTION, runDir );
}
firePropertyChange("document", old, doc);
} finally {
if (old instanceof AbstractDocument) {
((AbstractDocument)old).readUnlock();
}
}
revalidate();
repaint();
if (accessibleContext != null) {
model.addDocumentListener(
((AccessibleJTextComponent)accessibleContext));
}
if (inputMethodRequestsHandler != null) {
model.addDocumentListener((DocumentListener)inputMethodRequestsHandler);
}
}
/**
* Fetches the model associated with the editor. This is
* primarily for the UI to get at the minimal amount of
* state required to be a text editor. Subclasses will
* return the actual type of the model which will typically
* be something that extends Document.
*
* @return the model
*/
public Document getDocument() {
return model;
}
// Override of Component.setComponentOrientation
public void setComponentOrientation( ComponentOrientation o ) {
// Set the document's run direction property to match the
// ComponentOrientation property.
Document doc = getDocument();
if( doc != null ) {
Boolean runDir = o.isLeftToRight()
? TextAttribute.RUN_DIRECTION_LTR
: TextAttribute.RUN_DIRECTION_RTL;
doc.putProperty( TextAttribute.RUN_DIRECTION, runDir );
}
super.setComponentOrientation( o );
}
/**
* Fetches the command list for the editor. This is
* the list of commands supported by the plugged-in UI
* augmented by the collection of commands that the
* editor itself supports. These are useful for binding
* to events, such as in a keymap.
*
* @return the command list
*/
public Action[] getActions() {
return getUI().getEditorKit(this).getActions();
}
/**
* Sets margin space between the text component's border
* and its text. The text component's default Border
* object will use this value to create the proper margin.
* However, if a non-default border is set on the text component,
* it is that Border
object's responsibility to create the
* appropriate margin space (else this property will effectively
* be ignored). This causes a redraw of the component.
* A PropertyChange event ("margin") is sent to all listeners.
*
* @param m the space between the border and the text
* @beaninfo
* description: desired space between the border and text area
* bound: true
*/
public void setMargin(Insets m) {
Insets old = margin;
margin = m;
firePropertyChange("margin", old, m);
invalidate();
}
/**
* Returns the margin between the text component's border and
* its text.
*
* @return the margin
*/
public Insets getMargin() {
return margin;
}
/**
* Sets the NavigationFilter
. NavigationFilter
* is used by DefaultCaret
and the default cursor movement
* actions as a way to restrict the cursor movement.
*
* @since 1.4
*/
public void setNavigationFilter(NavigationFilter filter) {
navigationFilter = filter;
}
/**
* Returns the NavigationFilter
. NavigationFilter
* is used by DefaultCaret
and the default cursor movement
* actions as a way to restrict the cursor movement. A null return value
* implies the cursor movement and selection should not be restricted.
*
* @since 1.4
* @return the NavigationFilter
*/
public NavigationFilter getNavigationFilter() {
return navigationFilter;
}
/**
* Fetches the caret that allows text-oriented navigation over
* the view.
*
* @return the caret
*/
@Transient
public Caret getCaret() {
return caret;
}
/**
* Sets the caret to be used. By default this will be set
* by the UI that gets installed. This can be changed to
* a custom caret if desired. Setting the caret results in a
* PropertyChange event ("caret") being fired.
*
* @param c the caret
* @see #getCaret
* @beaninfo
* description: the caret used to select/navigate
* bound: true
* expert: true
*/
public void setCaret(Caret c) {
if (caret != null) {
caret.removeChangeListener(caretEvent);
caret.deinstall(this);
}
Caret old = caret;
caret = c;
if (caret != null) {
caret.install(this);
caret.addChangeListener(caretEvent);
}
firePropertyChange("caret", old, caret);
}
/**
* Fetches the object responsible for making highlights.
*
* @return the highlighter
*/
public Highlighter getHighlighter() {
return highlighter;
}
/**
* Sets the highlighter to be used. By default this will be set
* by the UI that gets installed. This can be changed to
* a custom highlighter if desired. The highlighter can be set to
* null
to disable it.
* A PropertyChange event ("highlighter") is fired
* when a new highlighter is installed.
*
* @param h the highlighter
* @see #getHighlighter
* @beaninfo
* description: object responsible for background highlights
* bound: true
* expert: true
*/
public void setHighlighter(Highlighter h) {
if (highlighter != null) {
highlighter.deinstall(this);
}
Highlighter old = highlighter;
highlighter = h;
if (highlighter != null) {
highlighter.install(this);
}
firePropertyChange("highlighter", old, h);
}
/**
* Sets the keymap to use for binding events to
* actions. Setting to null
effectively disables
* keyboard input.
* A PropertyChange event ("keymap") is fired when a new keymap
* is installed.
*
* @param map the keymap
* @see #getKeymap
* @beaninfo
* description: set of key event to action bindings to use
* bound: true
*/
public void setKeymap(Keymap map) {
Keymap old = keymap;
keymap = map;
firePropertyChange("keymap", old, keymap);
updateInputMap(old, map);
}
/**
* Turns on or off automatic drag handling. In order to enable automatic
* drag handling, this property should be set to {@code true}, and the
* component's {@code TransferHandler} needs to be {@code non-null}.
* The default value of the {@code dragEnabled} property is {@code false}.
*
* The job of honoring this property, and recognizing a user drag gesture, * lies with the look and feel implementation, and in particular, the component's * {@code TextUI}. When automatic drag handling is enabled, most look and * feels (including those that subclass {@code BasicLookAndFeel}) begin a * drag and drop operation whenever the user presses the mouse button over * a selection and then moves the mouse a few pixels. Setting this property to * {@code true} can therefore have a subtle effect on how selections behave. *
* If a look and feel is used that ignores this property, you can still
* begin a drag and drop operation by calling {@code exportAsDrag} on the
* component's {@code TransferHandler}.
*
* @param b whether or not to enable automatic drag handling
* @exception HeadlessException if
* b
is true
and
* GraphicsEnvironment.isHeadless()
* returns true
* @see java.awt.GraphicsEnvironment#isHeadless
* @see #getDragEnabled
* @see #setTransferHandler
* @see TransferHandler
* @since 1.4
*
* @beaninfo
* description: determines whether automatic drag handling is enabled
* bound: false
*/
public void setDragEnabled(boolean b) {
if (b && GraphicsEnvironment.isHeadless()) {
throw new HeadlessException();
}
dragEnabled = b;
}
/**
* Returns whether or not automatic drag handling is enabled.
*
* @return the value of the {@code dragEnabled} property
* @see #setDragEnabled
* @since 1.4
*/
public boolean getDragEnabled() {
return dragEnabled;
}
/**
* Sets the drop mode for this component. For backward compatibility,
* the default for this property is DropMode.USE_SELECTION
.
* Usage of DropMode.INSERT
is recommended, however,
* for an improved user experience. It offers similar behavior of dropping
* between text locations, but does so without affecting the actual text
* selection and caret location.
*
* JTextComponents
support the following drop modes:
*
DropMode.USE_SELECTION
DropMode.INSERT
* The drop mode is only meaningful if this component has a
* TransferHandler
that accepts drops.
*
* @param dropMode the drop mode to use
* @throws IllegalArgumentException if the drop mode is unsupported
* or null
* @see #getDropMode
* @see #getDropLocation
* @see #setTransferHandler
* @see javax.swing.TransferHandler
* @since 1.6
*/
public final void setDropMode(DropMode dropMode) {
if (dropMode != null) {
switch (dropMode) {
case USE_SELECTION:
case INSERT:
this.dropMode = dropMode;
return;
}
}
throw new IllegalArgumentException(dropMode + ": Unsupported drop mode for text");
}
/**
* Returns the drop mode for this component.
*
* @return the drop mode for this component
* @see #setDropMode
* @since 1.6
*/
public final DropMode getDropMode() {
return dropMode;
}
static {
SwingAccessor.setJTextComponentAccessor(
new SwingAccessor.JTextComponentAccessor() {
public TransferHandler.DropLocation dropLocationForPoint(JTextComponent textComp,
Point p)
{
return textComp.dropLocationForPoint(p);
}
public Object setDropLocation(JTextComponent textComp,
TransferHandler.DropLocation location,
Object state, boolean forDrop)
{
return textComp.setDropLocation(location, state, forDrop);
}
});
}
/**
* Calculates a drop location in this component, representing where a
* drop at the given point should insert data.
*
* Note: This method is meant to override
* JComponent.dropLocationForPoint()
, which is package-private
* in javax.swing. TransferHandler
will detect text components
* and call this method instead via reflection. It's name should therefore
* not be changed.
*
* @param p the point to calculate a drop location for
* @return the drop location, or null
*/
DropLocation dropLocationForPoint(Point p) {
Position.Bias[] bias = new Position.Bias[1];
int index = getUI().viewToModel(this, p, bias);
// viewToModel currently returns null for some HTML content
// when the point is within the component's top inset
if (bias[0] == null) {
bias[0] = Position.Bias.Forward;
}
return new DropLocation(p, index, bias[0]);
}
/**
* Called to set or clear the drop location during a DnD operation.
* In some cases, the component may need to use it's internal selection
* temporarily to indicate the drop location. To help facilitate this,
* this method returns and accepts as a parameter a state object.
* This state object can be used to store, and later restore, the selection
* state. Whatever this method returns will be passed back to it in
* future calls, as the state parameter. If it wants the DnD system to
* continue storing the same state, it must pass it back every time.
* Here's how this is used:
*
* Let's say that on the first call to this method the component decides
* to save some state (because it is about to use the selection to show
* a drop index). It can return a state object to the caller encapsulating
* any saved selection state. On a second call, let's say the drop location
* is being changed to something else. The component doesn't need to
* restore anything yet, so it simply passes back the same state object
* to have the DnD system continue storing it. Finally, let's say this
* method is messaged with null
. This means DnD
* is finished with this component for now, meaning it should restore
* state. At this point, it can use the state parameter to restore
* said state, and of course return null
since there's
* no longer anything to store.
*
* Note: This method is meant to override
* JComponent.setDropLocation()
, which is package-private
* in javax.swing. TransferHandler
will detect text components
* and call this method instead via reflection. It's name should therefore
* not be changed.
*
* @param location the drop location (as calculated by
* dropLocationForPoint
) or null
* if there's no longer a valid drop location
* @param state the state object saved earlier for this component,
* or null
* @param forDrop whether or not the method is being called because an
* actual drop occurred
* @return any saved state for this component, or null
if none
*/
Object setDropLocation(TransferHandler.DropLocation location,
Object state,
boolean forDrop) {
Object retVal = null;
DropLocation textLocation = (DropLocation)location;
if (dropMode == DropMode.USE_SELECTION) {
if (textLocation == null) {
if (state != null) {
/*
* This object represents the state saved earlier.
* If the caret is a DefaultCaret it will be
* an Object array containing, in order:
* - the saved caret mark (Integer)
* - the saved caret dot (Integer)
* - the saved caret visibility (Boolean)
* - the saved mark bias (Position.Bias)
* - the saved dot bias (Position.Bias)
* If the caret is not a DefaultCaret it will
* be similar, but will not contain the dot
* or mark bias.
*/
Object[] vals = (Object[])state;
if (!forDrop) {
if (caret instanceof DefaultCaret) {
((DefaultCaret)caret).setDot(((Integer)vals[0]).intValue(),
(Position.Bias)vals[3]);
((DefaultCaret)caret).moveDot(((Integer)vals[1]).intValue(),
(Position.Bias)vals[4]);
} else {
caret.setDot(((Integer)vals[0]).intValue());
caret.moveDot(((Integer)vals[1]).intValue());
}
}
caret.setVisible(((Boolean)vals[2]).booleanValue());
}
} else {
if (dropLocation == null) {
boolean visible;
if (caret instanceof DefaultCaret) {
DefaultCaret dc = (DefaultCaret)caret;
visible = dc.isActive();
retVal = new Object[] {Integer.valueOf(dc.getMark()),
Integer.valueOf(dc.getDot()),
Boolean.valueOf(visible),
dc.getMarkBias(),
dc.getDotBias()};
} else {
visible = caret.isVisible();
retVal = new Object[] {Integer.valueOf(caret.getMark()),
Integer.valueOf(caret.getDot()),
Boolean.valueOf(visible)};
}
caret.setVisible(true);
} else {
retVal = state;
}
if (caret instanceof DefaultCaret) {
((DefaultCaret)caret).setDot(textLocation.getIndex(), textLocation.getBias());
} else {
caret.setDot(textLocation.getIndex());
}
}
} else {
if (textLocation == null) {
if (state != null) {
caret.setVisible(((Boolean)state).booleanValue());
}
} else {
if (dropLocation == null) {
boolean visible = caret instanceof DefaultCaret
? ((DefaultCaret)caret).isActive()
: caret.isVisible();
retVal = Boolean.valueOf(visible);
caret.setVisible(false);
} else {
retVal = state;
}
}
}
DropLocation old = dropLocation;
dropLocation = textLocation;
firePropertyChange("dropLocation", old, dropLocation);
return retVal;
}
/**
* Returns the location that this component should visually indicate
* as the drop location during a DnD operation over the component,
* or {@code null} if no location is to currently be shown.
*
* This method is not meant for querying the drop location
* from a {@code TransferHandler}, as the drop location is only
* set after the {@code TransferHandler}'s canImport
* has returned and has allowed for the location to be shown.
*
* When this property changes, a property change event with
* name "dropLocation" is fired by the component.
*
* @return the drop location
* @see #setDropMode
* @see TransferHandler#canImport(TransferHandler.TransferSupport)
* @since 1.6
*/
public final DropLocation getDropLocation() {
return dropLocation;
}
/**
* Updates the
* Warning:
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeansTM
* has been added to the
* Loads a keymap with a bunch of
* bindings. This can be used to take a static table of
* definitions and load them into some keymap. The following
* example illustrates an example of binding some keys to
* the cut, copy, and paste actions associated with a
* JTextComponent. A code fragment to accomplish
* this might look as follows:
*
* This is the method that is used by the default implementation
* of the action for inserting content that gets bound to the
* keymap actions.
*
* @param content the content to replace the selection with
*/
public void replaceSelection(String content) {
Document doc = getDocument();
if (doc != null) {
try {
boolean composedTextSaved = saveComposedText(caret.getDot());
int p0 = Math.min(caret.getDot(), caret.getMark());
int p1 = Math.max(caret.getDot(), caret.getMark());
if (doc instanceof AbstractDocument) {
((AbstractDocument)doc).replace(p0, p1 - p0, content,null);
}
else {
if (p0 != p1) {
doc.remove(p0, p1 - p0);
}
if (content != null && content.length() > 0) {
doc.insertString(p0, content, null);
}
}
if (composedTextSaved) {
restoreComposedText();
}
} catch (BadLocationException e) {
UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this);
}
}
}
/**
* Fetches a portion of the text represented by the
* component. Returns an empty string if length is 0.
*
* @param offs the offset >= 0
* @param len the length >= 0
* @return the text
* @exception BadLocationException if the offset or length are invalid
*/
public String getText(int offs, int len) throws BadLocationException {
return getDocument().getText(offs, len);
}
/**
* Converts the given location in the model to a place in
* the view coordinate system.
* The component must have a positive size for
* this translation to be computed (i.e. layout cannot
* be computed until the component has been sized). The
* component does not have to be visible or painted.
*
* @param pos the position >= 0
* @return the coordinates as a rectangle, with (r.x, r.y) as the location
* in the coordinate system, or null if the component does
* not yet have a positive size.
* @exception BadLocationException if the given position does not
* represent a valid location in the associated document
* @see TextUI#modelToView
*/
public Rectangle modelToView(int pos) throws BadLocationException {
return getUI().modelToView(this, pos);
}
/**
* Converts the given place in the view coordinate system
* to the nearest representative location in the model.
* The component must have a positive size for
* this translation to be computed (i.e. layout cannot
* be computed until the component has been sized). The
* component does not have to be visible or painted.
*
* @param pt the location in the view to translate
* @return the offset >= 0 from the start of the document,
* or -1 if the component does not yet have a positive
* size.
* @see TextUI#viewToModel
*/
public int viewToModel(Point pt) {
return getUI().viewToModel(this, pt);
}
/**
* Transfers the currently selected range in the associated
* text model to the system clipboard, removing the contents
* from the model. The current selection is reset. Does nothing
* for
* Note that text is not a bound property, so no
* This is available for backward compatibility to code
* that called this method on
* This is available for backward compatibility to code
* that called this method on
* This method sets the start and end positions of the
* selected text, enforcing the restriction that the start position
* must be greater than or equal to zero. The end position must be
* greater than or equal to the start position, and less than or
* equal to the length of the text component's text.
*
* If the caller supplies values that are inconsistent or out of
* bounds, the method enforces these constraints silently, and
* without failure. Specifically, if the start position or end
* position is greater than the length of the text, it is reset to
* equal the text length. If the start position is less than zero,
* it is reset to zero, and if the end position is less than the
* start position, it is reset to the start position.
*
* This call is provided for backward compatibility.
* It is routed to a call to
* The default implementation of this is to simply return 10% of
* the visible area. Subclasses are likely to be able to provide
* a much more reasonable value.
*
* @param visibleRect the view area visible within the viewport
* @param orientation either
* The default implementation of this is to simply return the visible
* area. Subclasses will likely be able to provide a much more
* reasonable value.
*
* @param visibleRect the view area visible within the viewport
* @param orientation either
* Scrolling containers, like
* Scrolling containers, like
* Note: In headless mode, no dialogs will be shown.
*
* This method calls the full featured
* {@link #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean)
* print} method to perform printing.
* @return {@code true}, unless printing is canceled by the user
* @throws PrinterException if an error in the print system causes the job
* to be aborted
* @throws SecurityException if this thread is not allowed to
* initiate a print job request
*
* @see #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean)
*
* @since 1.6
*/
public boolean print() throws PrinterException {
return print(null, null, true, null, null, true);
}
/**
* A convenience print method that displays a print dialog, and then
* prints this {@code JTextComponent} in interactive mode with
* the specified header and footer text. Note: this method
* blocks until printing is done.
*
* Note: In headless mode, no dialogs will be shown.
*
* This method calls the full featured
* {@link #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean)
* print} method to perform printing.
* @param headerFormat the text, in {@code MessageFormat}, to be
* used as the header, or {@code null} for no header
* @param footerFormat the text, in {@code MessageFormat}, to be
* used as the footer, or {@code null} for no footer
* @return {@code true}, unless printing is canceled by the user
* @throws PrinterException if an error in the print system causes the job
* to be aborted
* @throws SecurityException if this thread is not allowed to
* initiate a print job request
*
* @see #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean)
* @see java.text.MessageFormat
* @since 1.6
*/
public boolean print(final MessageFormat headerFormat,
final MessageFormat footerFormat) throws PrinterException {
return print(headerFormat, footerFormat, true, null, null, true);
}
/**
* Prints the content of this {@code JTextComponent}. Note: this method
* blocks until printing is done.
*
*
* Page header and footer text can be added to the output by providing
* {@code MessageFormat} arguments. The printing code requests
* {@code Strings} from the formats, providing a single item which may be
* included in the formatted string: an {@code Integer} representing the
* current page number.
*
*
* {@code showPrintDialog boolean} parameter allows you to specify whether
* a print dialog is displayed to the user. When it is, the user
* may use the dialog to change printing attributes or even cancel the
* print.
*
*
* {@code service} allows you to provide the initial
* {@code PrintService} for the print dialog, or to specify
* {@code PrintService} to print to when the dialog is not shown.
*
*
* {@code attributes} can be used to provide the
* initial values for the print dialog, or to supply any needed
* attributes when the dialog is not shown. {@code attributes} can
* be used to control how the job will print, for example
* duplex or single-sided.
*
*
* {@code interactive boolean} parameter allows you to specify
* whether to perform printing in interactive
* mode. If {@code true}, a progress dialog, with an abort option,
* is displayed for the duration of printing. This dialog is
* modal when {@code print} is invoked on the Event Dispatch
* Thread and non-modal otherwise. Warning:
* calling this method on the Event Dispatch Thread with {@code
* interactive false} blocks all events, including repaints, from
* being processed until printing is complete. It is only
* recommended when printing from an application with no
* visible GUI.
*
*
* Note: In headless mode, {@code showPrintDialog} and
* {@code interactive} parameters are ignored and no dialogs are
* shown.
*
*
* This method ensures the {@code document} is not mutated during printing.
* To indicate it visually, {@code setEnabled(false)} is set for the
* duration of printing.
*
*
* This method uses {@link #getPrintable} to render document content.
*
*
* This method is thread-safe, although most Swing methods are not. Please
* see
* How to Use Threads for more information.
*
*
* Sample Usage. This code snippet shows a cross-platform print
* dialog and then prints the {@code JTextComponent} in interactive mode
* unless the user cancels the dialog:
*
*
* Executing this code off the Event Dispatch Thread
* performs printing on the background.
* The following pattern might be used for background
* printing:
* InputMap
s in response to a
* Keymap
change.
* @param oldKm the old Keymap
* @param newKm the new Keymap
*/
void updateInputMap(Keymap oldKm, Keymap newKm) {
// Locate the current KeymapWrapper.
InputMap km = getInputMap(JComponent.WHEN_FOCUSED);
InputMap last = km;
while (km != null && !(km instanceof KeymapWrapper)) {
last = km;
km = km.getParent();
}
if (km != null) {
// Found it, tweak the InputMap that points to it, as well
// as anything it points to.
if (newKm == null) {
if (last != km) {
last.setParent(km.getParent());
}
else {
last.setParent(null);
}
}
else {
InputMap newKM = new KeymapWrapper(newKm);
last.setParent(newKM);
if (last != km) {
newKM.setParent(km.getParent());
}
}
}
else if (newKm != null) {
km = getInputMap(JComponent.WHEN_FOCUSED);
if (km != null) {
// Couldn't find it.
// Set the parent of WHEN_FOCUSED InputMap to be the new one.
InputMap newKM = new KeymapWrapper(newKm);
newKM.setParent(km.getParent());
km.setParent(newKM);
}
}
// Do the same thing with the ActionMap
ActionMap am = getActionMap();
ActionMap lastAM = am;
while (am != null && !(am instanceof KeymapActionMap)) {
lastAM = am;
am = am.getParent();
}
if (am != null) {
// Found it, tweak the Actionap that points to it, as well
// as anything it points to.
if (newKm == null) {
if (lastAM != am) {
lastAM.setParent(am.getParent());
}
else {
lastAM.setParent(null);
}
}
else {
ActionMap newAM = new KeymapActionMap(newKm);
lastAM.setParent(newAM);
if (lastAM != am) {
newAM.setParent(am.getParent());
}
}
}
else if (newKm != null) {
am = getActionMap();
if (am != null) {
// Couldn't find it.
// Set the parent of ActionMap to be the new one.
ActionMap newAM = new KeymapActionMap(newKm);
newAM.setParent(am.getParent());
am.setParent(newAM);
}
}
}
/**
* Fetches the keymap currently active in this text
* component.
*
* @return the keymap
*/
public Keymap getKeymap() {
return keymap;
}
/**
* Adds a new keymap into the keymap hierarchy. Keymap bindings
* resolve from bottom up so an attribute specified in a child
* will override an attribute specified in the parent.
*
* @param nm the name of the keymap (must be unique within the
* collection of named keymaps in the document); the name may
* be null
if the keymap is unnamed,
* but the caller is responsible for managing the reference
* returned as an unnamed keymap can't
* be fetched by name
* @param parent the parent keymap; this may be null
if
* unspecified bindings need not be resolved in some other keymap
* @return the keymap
*/
public static Keymap addKeymap(String nm, Keymap parent) {
Keymap map = new DefaultKeymap(nm, parent);
if (nm != null) {
// add a named keymap, a class of bindings
getKeymapTable().put(nm, map);
}
return map;
}
/**
* Removes a named keymap previously added to the document. Keymaps
* with null
names may not be removed in this way.
*
* @param nm the name of the keymap to remove
* @return the keymap that was removed
*/
public static Keymap removeKeymap(String nm) {
return getKeymapTable().remove(nm);
}
/**
* Fetches a named keymap previously added to the document.
* This does not work with null
-named keymaps.
*
* @param nm the name of the keymap
* @return the keymap
*/
public static Keymap getKeymap(String nm) {
return getKeymapTable().get(nm);
}
private static HashMapjava.beans
package.
* Please see {@link java.beans.XMLEncoder}.
*/
public static class KeyBinding {
/**
* The key.
*/
public KeyStroke key;
/**
* The name of the action for the key.
*/
public String actionName;
/**
* Creates a new key binding.
*
* @param key the key
* @param actionName the name of the action for the key
*/
public KeyBinding(KeyStroke key, String actionName) {
this.key = key;
this.actionName = actionName;
}
}
/**
*
* The sets of bindings and actions may be empty but must be
* non-
*
* static final JTextComponent.KeyBinding[] defaultBindings = {
* new JTextComponent.KeyBinding(
* KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK),
* DefaultEditorKit.copyAction),
* new JTextComponent.KeyBinding(
* KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK),
* DefaultEditorKit.pasteAction),
* new JTextComponent.KeyBinding(
* KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK),
* DefaultEditorKit.cutAction),
* };
*
* JTextComponent c = new JTextPane();
* Keymap k = c.getKeymap();
* JTextComponent.loadKeymap(k, defaultBindings, c.getActions());
*
*
null
.
*
* @param map the keymap
* @param bindings the bindings
* @param actions the set of actions
*/
public static void loadKeymap(Keymap map, KeyBinding[] bindings, Action[] actions) {
Hashtableklass
is NOT a JTextComponent and it or
* one of its superclasses (stoping at JTextComponent) overrides
* processInputMethodEvent
. It is assumed this will be
* invoked from within a doPrivileged
, and it is also
* assumed klass
extends JTextComponent
.
*/
private static Boolean isProcessInputMethodEventOverridden(Class> klass) {
if (klass == JTextComponent.class) {
return Boolean.FALSE;
}
Boolean retValue = overrideMap.get(klass.getName());
if (retValue != null) {
return retValue;
}
Boolean sOverriden = isProcessInputMethodEventOverridden(
klass.getSuperclass());
if (sOverriden.booleanValue()) {
// If our superclass has overriden it, then by definition klass
// overrides it.
overrideMap.put(klass.getName(), sOverriden);
return sOverriden;
}
// klass's superclass didn't override it, check for an override in
// klass.
try {
Class[] classes = new Class[1];
classes[0] = InputMethodEvent.class;
Method m = klass.getDeclaredMethod("processInputMethodEvent",
classes);
retValue = Boolean.TRUE;
} catch (NoSuchMethodException nsme) {
retValue = Boolean.FALSE;
}
overrideMap.put(klass.getName(), retValue);
return retValue;
}
/**
* Fetches the current color used to render the
* caret.
*
* @return the color
*/
public Color getCaretColor() {
return caretColor;
}
/**
* Sets the current color used to render the caret.
* Setting to null
effectively restores the default color.
* Setting the color results in a PropertyChange event ("caretColor")
* being fired.
*
* @param c the color
* @see #getCaretColor
* @beaninfo
* description: the color used to render the caret
* bound: true
* preferred: true
*/
public void setCaretColor(Color c) {
Color old = caretColor;
caretColor = c;
firePropertyChange("caretColor", old, caretColor);
}
/**
* Fetches the current color used to render the
* selection.
*
* @return the color
*/
public Color getSelectionColor() {
return selectionColor;
}
/**
* Sets the current color used to render the selection.
* Setting the color to null
is the same as setting
* Color.white
. Setting the color results in a
* PropertyChange event ("selectionColor").
*
* @param c the color
* @see #getSelectionColor
* @beaninfo
* description: color used to render selection background
* bound: true
* preferred: true
*/
public void setSelectionColor(Color c) {
Color old = selectionColor;
selectionColor = c;
firePropertyChange("selectionColor", old, selectionColor);
}
/**
* Fetches the current color used to render the
* selected text.
*
* @return the color
*/
public Color getSelectedTextColor() {
return selectedTextColor;
}
/**
* Sets the current color used to render the selected text.
* Setting the color to null
is the same as
* Color.black
. Setting the color results in a
* PropertyChange event ("selectedTextColor") being fired.
*
* @param c the color
* @see #getSelectedTextColor
* @beaninfo
* description: color used to render selected text
* bound: true
* preferred: true
*/
public void setSelectedTextColor(Color c) {
Color old = selectedTextColor;
selectedTextColor = c;
firePropertyChange("selectedTextColor", old, selectedTextColor);
}
/**
* Fetches the current color used to render the
* disabled text.
*
* @return the color
*/
public Color getDisabledTextColor() {
return disabledTextColor;
}
/**
* Sets the current color used to render the
* disabled text. Setting the color fires off a
* PropertyChange event ("disabledTextColor").
*
* @param c the color
* @see #getDisabledTextColor
* @beaninfo
* description: color used to render disabled text
* bound: true
* preferred: true
*/
public void setDisabledTextColor(Color c) {
Color old = disabledTextColor;
disabledTextColor = c;
firePropertyChange("disabledTextColor", old, disabledTextColor);
}
/**
* Replaces the currently selected content with new content
* represented by the given string. If there is no selection
* this amounts to an insert of the given text. If there
* is no replacement text this amounts to a removal of the
* current selection.
* null
selections.
*
* @see java.awt.Toolkit#getSystemClipboard
* @see java.awt.datatransfer.Clipboard
*/
public void cut() {
if (isEditable() && isEnabled()) {
invokeAction("cut", TransferHandler.getCutAction());
}
}
/**
* Transfers the currently selected range in the associated
* text model to the system clipboard, leaving the contents
* in the text model. The current selection remains intact.
* Does nothing for null
selections.
*
* @see java.awt.Toolkit#getSystemClipboard
* @see java.awt.datatransfer.Clipboard
*/
public void copy() {
invokeAction("copy", TransferHandler.getCopyAction());
}
/**
* Transfers the contents of the system clipboard into the
* associated text model. If there is a selection in the
* associated view, it is replaced with the contents of the
* clipboard. If there is no selection, the clipboard contents
* are inserted in front of the current insert position in
* the associated view. If the clipboard is empty, does nothing.
*
* @see #replaceSelection
* @see java.awt.Toolkit#getSystemClipboard
* @see java.awt.datatransfer.Clipboard
*/
public void paste() {
if (isEditable() && isEnabled()) {
invokeAction("paste", TransferHandler.getPasteAction());
}
}
/**
* This is a conveniance method that is only useful for
* cut
, copy
and paste
. If
* an Action
with the name name
does not
* exist in the ActionMap
, this will attemp to install a
* TransferHandler
and then use altAction
.
*/
private void invokeAction(String name, Action altAction) {
ActionMap map = getActionMap();
Action action = null;
if (map != null) {
action = map.get(name);
}
if (action == null) {
installDefaultTransferHandlerIfNecessary();
action = altAction;
}
action.actionPerformed(new ActionEvent(this,
ActionEvent.ACTION_PERFORMED, (String)action.
getValue(Action.NAME),
EventQueue.getMostRecentEventTime(),
getCurrentEventModifiers()));
}
/**
* If the current TransferHandler
is null, this will
* install a new one.
*/
private void installDefaultTransferHandlerIfNecessary() {
if (getTransferHandler() == null) {
if (defaultTransferHandler == null) {
defaultTransferHandler = new DefaultTransferHandler();
}
setTransferHandler(defaultTransferHandler);
}
}
/**
* Moves the caret to a new position, leaving behind a mark
* defined by the last time setCaretPosition
was
* called. This forms a selection.
* If the document is null
, does nothing. The position
* must be between 0 and the length of the component's text or else
* an exception is thrown.
*
* @param pos the position
* @exception IllegalArgumentException if the value supplied
* for position
is less than zero or greater
* than the component's text length
* @see #setCaretPosition
*/
public void moveCaretPosition(int pos) {
Document doc = getDocument();
if (doc != null) {
if (pos > doc.getLength() || pos < 0) {
throw new IllegalArgumentException("bad position: " + pos);
}
caret.moveDot(pos);
}
}
/**
* The bound property name for the focus accelerator.
*/
public static final String FOCUS_ACCELERATOR_KEY = "focusAcceleratorKey";
/**
* Sets the key accelerator that will cause the receiving text
* component to get the focus. The accelerator will be the
* key combination of the alt key and the character
* given (converted to upper case). By default, there is no focus
* accelerator key. Any previous key accelerator setting will be
* superseded. A '\0' key setting will be registered, and has the
* effect of turning off the focus accelerator. When the new key
* is set, a PropertyChange event (FOCUS_ACCELERATOR_KEY) will be fired.
*
* @param aKey the key
* @see #getFocusAccelerator
* @beaninfo
* description: accelerator character used to grab focus
* bound: true
*/
public void setFocusAccelerator(char aKey) {
aKey = Character.toUpperCase(aKey);
char old = focusAccelerator;
focusAccelerator = aKey;
// Fix for 4341002: value of FOCUS_ACCELERATOR_KEY is wrong.
// So we fire both FOCUS_ACCELERATOR_KEY, for compatibility,
// and the correct event here.
firePropertyChange(FOCUS_ACCELERATOR_KEY, old, focusAccelerator);
firePropertyChange("focusAccelerator", old, focusAccelerator);
}
/**
* Returns the key accelerator that will cause the receiving
* text component to get the focus. Return '\0' if no focus
* accelerator has been set.
*
* @return the key
*/
public char getFocusAccelerator() {
return focusAccelerator;
}
/**
* Initializes from a stream. This creates a
* model of the type appropriate for the component
* and initializes the model from the stream.
* By default this will load the model as plain
* text. Previous contents of the model are discarded.
*
* @param in the stream to read from
* @param desc an object describing the stream; this
* might be a string, a File, a URL, etc. Some kinds
* of documents (such as html for example) might be
* able to make use of this information; if non-null
,
* it is added as a property of the document
* @exception IOException as thrown by the stream being
* used to initialize
* @see EditorKit#createDefaultDocument
* @see #setDocument
* @see PlainDocument
*/
public void read(Reader in, Object desc) throws IOException {
EditorKit kit = getUI().getEditorKit(this);
Document doc = kit.createDefaultDocument();
if (desc != null) {
doc.putProperty(Document.StreamDescriptionProperty, desc);
}
try {
kit.read(in, doc, 0);
setDocument(doc);
} catch (BadLocationException e) {
throw new IOException(e.getMessage());
}
}
/**
* Stores the contents of the model into the given
* stream. By default this will store the model as plain
* text.
*
* @param out the output stream
* @exception IOException on any I/O error
*/
public void write(Writer out) throws IOException {
Document doc = getDocument();
try {
getUI().getEditorKit(this).write(out, doc, 0, doc.getLength());
} catch (BadLocationException e) {
throw new IOException(e.getMessage());
}
}
public void removeNotify() {
super.removeNotify();
if (getFocusedComponent() == this) {
AppContext.getAppContext().remove(FOCUSED_COMPONENT);
}
}
// --- java.awt.TextComponent methods ------------------------
/**
* Sets the position of the text insertion caret for the
* TextComponent
. Note that the caret tracks change,
* so this may move if the underlying text of the component is changed.
* If the document is null
, does nothing. The position
* must be between 0 and the length of the component's text or else
* an exception is thrown.
*
* @param position the position
* @exception IllegalArgumentException if the value supplied
* for position
is less than zero or greater
* than the component's text length
* @beaninfo
* description: the caret position
*/
public void setCaretPosition(int position) {
Document doc = getDocument();
if (doc != null) {
if (position > doc.getLength() || position < 0) {
throw new IllegalArgumentException("bad position: " + position);
}
caret.setDot(position);
}
}
/**
* Returns the position of the text insertion caret for the
* text component.
*
* @return the position of the text insertion caret for the
* text component >= 0
*/
@Transient
public int getCaretPosition() {
return caret.getDot();
}
/**
* Sets the text of this TextComponent
* to the specified text. If the text is null
* or empty, has the effect of simply deleting the old text.
* When text has been inserted, the resulting caret location
* is determined by the implementation of the caret class.
*
* PropertyChangeEvent
*
is fired when it changes. To listen for changes to the text,
* use DocumentListener
.
*
* @param t the new text to be set
* @see #getText
* @see DefaultCaret
* @beaninfo
* description: the text of this component
*/
public void setText(String t) {
try {
Document doc = getDocument();
if (doc instanceof AbstractDocument) {
((AbstractDocument)doc).replace(0, doc.getLength(), t,null);
}
else {
doc.remove(0, doc.getLength());
doc.insertString(0, t, null);
}
} catch (BadLocationException e) {
UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this);
}
}
/**
* Returns the text contained in this TextComponent
.
* If the underlying document is null
,
* will give a NullPointerException
.
*
* Note that text is not a bound property, so no PropertyChangeEvent
*
is fired when it changes. To listen for changes to the text,
* use DocumentListener
.
*
* @return the text
* @exception NullPointerException if the document is null
* @see #setText
*/
public String getText() {
Document doc = getDocument();
String txt;
try {
txt = doc.getText(0, doc.getLength());
} catch (BadLocationException e) {
txt = null;
}
return txt;
}
/**
* Returns the selected text contained in this
* TextComponent
. If the selection is
* null
or the document empty, returns null
.
*
* @return the text
* @exception IllegalArgumentException if the selection doesn't
* have a valid mapping into the document for some reason
* @see #setText
*/
public String getSelectedText() {
String txt = null;
int p0 = Math.min(caret.getDot(), caret.getMark());
int p1 = Math.max(caret.getDot(), caret.getMark());
if (p0 != p1) {
try {
Document doc = getDocument();
txt = doc.getText(p0, p1 - p0);
} catch (BadLocationException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
return txt;
}
/**
* Returns the boolean indicating whether this
* TextComponent
is editable or not.
*
* @return the boolean value
* @see #setEditable
*/
public boolean isEditable() {
return editable;
}
/**
* Sets the specified boolean to indicate whether or not this
* TextComponent
should be editable.
* A PropertyChange event ("editable") is fired when the
* state is changed.
*
* @param b the boolean to be set
* @see #isEditable
* @beaninfo
* description: specifies if the text can be edited
* bound: true
*/
public void setEditable(boolean b) {
if (b != editable) {
boolean oldVal = editable;
editable = b;
enableInputMethods(editable);
firePropertyChange("editable", Boolean.valueOf(oldVal), Boolean.valueOf(editable));
repaint();
}
}
/**
* Returns the selected text's start position. Return 0 for an
* empty document, or the value of dot if no selection.
*
* @return the start position >= 0
*/
@Transient
public int getSelectionStart() {
int start = Math.min(caret.getDot(), caret.getMark());
return start;
}
/**
* Sets the selection start to the specified position. The new
* starting point is constrained to be before or at the current
* selection end.
* java.awt.TextComponent
.
* This is implemented to forward to the Caret
* implementation which is where the actual selection is maintained.
*
* @param selectionStart the start position of the text >= 0
* @beaninfo
* description: starting location of the selection.
*/
public void setSelectionStart(int selectionStart) {
/* Route through select method to enforce consistent policy
* between selectionStart and selectionEnd.
*/
select(selectionStart, getSelectionEnd());
}
/**
* Returns the selected text's end position. Return 0 if the document
* is empty, or the value of dot if there is no selection.
*
* @return the end position >= 0
*/
@Transient
public int getSelectionEnd() {
int end = Math.max(caret.getDot(), caret.getMark());
return end;
}
/**
* Sets the selection end to the specified position. The new
* end point is constrained to be at or after the current
* selection start.
* java.awt.TextComponent
.
* This is implemented to forward to the Caret
* implementation which is where the actual selection is maintained.
*
* @param selectionEnd the end position of the text >= 0
* @beaninfo
* description: ending location of the selection.
*/
public void setSelectionEnd(int selectionEnd) {
/* Route through select method to enforce consistent policy
* between selectionStart and selectionEnd.
*/
select(getSelectionStart(), selectionEnd);
}
/**
* Selects the text between the specified start and end positions.
* setCaretPosition
* followed by a call to moveCaretPosition
.
* The preferred way to manage selection is by calling
* those methods directly.
*
* @param selectionStart the start position of the text
* @param selectionEnd the end position of the text
* @see #setCaretPosition
* @see #moveCaretPosition
*/
public void select(int selectionStart, int selectionEnd) {
// argument adjustment done by java.awt.TextComponent
int docLength = getDocument().getLength();
if (selectionStart < 0) {
selectionStart = 0;
}
if (selectionStart > docLength) {
selectionStart = docLength;
}
if (selectionEnd > docLength) {
selectionEnd = docLength;
}
if (selectionEnd < selectionStart) {
selectionEnd = selectionStart;
}
setCaretPosition(selectionStart);
moveCaretPosition(selectionEnd);
}
/**
* Selects all the text in the TextComponent
.
* Does nothing on a null
or empty document.
*/
public void selectAll() {
Document doc = getDocument();
if (doc != null) {
setCaretPosition(0);
moveCaretPosition(doc.getLength());
}
}
// --- Tooltip Methods ---------------------------------------------
/**
* Returns the string to be used as the tooltip for event
.
* This will return one of:
*
*
* By default setToolTipText
has been invoked with a
* non-null
* value, it will be returned, otherwise
* getToolTipText
on
* the UI will be returned.
* JTextComponent
does not register
* itself with the ToolTipManager
.
* This means that tooltips will NOT be shown from the
* TextUI
unless registerComponent
has
* been invoked on the ToolTipManager
.
*
* @param event the event in question
* @return the string to be used as the tooltip for event
* @see javax.swing.JComponent#setToolTipText
* @see javax.swing.plaf.TextUI#getToolTipText
* @see javax.swing.ToolTipManager#registerComponent
*/
public String getToolTipText(MouseEvent event) {
String retValue = super.getToolTipText(event);
if (retValue == null) {
TextUI ui = getUI();
if (ui != null) {
retValue = ui.getToolTipText(this, new Point(event.getX(),
event.getY()));
}
}
return retValue;
}
// --- Scrollable methods ---------------------------------------------
/**
* Returns the preferred size of the viewport for a view component.
* This is implemented to do the default behavior of returning
* the preferred size of the component.
*
* @return the preferredSize
of a JViewport
* whose view is this Scrollable
*/
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
/**
* Components that display logical rows or columns should compute
* the scroll increment that will completely expose one new row
* or column, depending on the value of orientation. Ideally,
* components should handle a partially exposed row or column by
* returning the distance required to completely expose the item.
* SwingConstants.VERTICAL
or
* SwingConstants.HORIZONTAL
* @param direction less than zero to scroll up/left, greater than
* zero for down/right
* @return the "unit" increment for scrolling in the specified direction
* @exception IllegalArgumentException for an invalid orientation
* @see JScrollBar#setUnitIncrement
*/
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
switch(orientation) {
case SwingConstants.VERTICAL:
return visibleRect.height / 10;
case SwingConstants.HORIZONTAL:
return visibleRect.width / 10;
default:
throw new IllegalArgumentException("Invalid orientation: " + orientation);
}
}
/**
* Components that display logical rows or columns should compute
* the scroll increment that will completely expose one block
* of rows or columns, depending on the value of orientation.
* SwingConstants.VERTICAL
or
* SwingConstants.HORIZONTAL
* @param direction less than zero to scroll up/left, greater than zero
* for down/right
* @return the "block" increment for scrolling in the specified direction
* @exception IllegalArgumentException for an invalid orientation
* @see JScrollBar#setBlockIncrement
*/
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
switch(orientation) {
case SwingConstants.VERTICAL:
return visibleRect.height;
case SwingConstants.HORIZONTAL:
return visibleRect.width;
default:
throw new IllegalArgumentException("Invalid orientation: " + orientation);
}
}
/**
* Returns true if a viewport should always force the width of this
* Scrollable
to match the width of the viewport.
* For example a normal text view that supported line wrapping
* would return true here, since it would be undesirable for
* wrapped lines to disappear beyond the right
* edge of the viewport. Note that returning true for a
* Scrollable
whose ancestor is a JScrollPane
* effectively disables horizontal scrolling.
* JViewport
,
* will use this method each time they are validated.
*
* @return true if a viewport should force the Scrollable
s
* width to match its own
*/
public boolean getScrollableTracksViewportWidth() {
Container parent = SwingUtilities.getUnwrappedParent(this);
if (parent instanceof JViewport) {
return parent.getWidth() > getPreferredSize().width;
}
return false;
}
/**
* Returns true if a viewport should always force the height of this
* Scrollable
to match the height of the viewport.
* For example a columnar text view that flowed text in left to
* right columns could effectively disable vertical scrolling by
* returning true here.
* JViewport
,
* will use this method each time they are validated.
*
* @return true if a viewport should force the Scrollables height
* to match its own
*/
public boolean getScrollableTracksViewportHeight() {
Container parent = SwingUtilities.getUnwrappedParent(this);
if (parent instanceof JViewport) {
return parent.getHeight() > getPreferredSize().height;
}
return false;
}
//////////////////
// Printing Support
//////////////////
/**
* A convenience print method that displays a print dialog, and then
* prints this {@code JTextComponent} in interactive mode with no
* header or footer text. Note: this method
* blocks until printing is done.
*
* textComponent.print(new MessageFormat("My text component header"),
* new MessageFormat("Footer. Page - {0}"), true, null, null, true);
*
*
* FutureTask<Boolean> future =
* new FutureTask<Boolean>(
* new Callable<Boolean>() {
* public Boolean call() {
* return textComponent.print(.....);
* }
* });
* executor.execute(future);
*
*
* @param headerFormat the text, in {@code MessageFormat}, to be
* used as the header, or {@code null} for no header
* @param footerFormat the text, in {@code MessageFormat}, to be
* used as the footer, or {@code null} for no footer
* @param showPrintDialog {@code true} to display a print dialog,
* {@code false} otherwise
* @param service initial {@code PrintService}, or {@code null} for the
* default
* @param attributes the job attributes to be applied to the print job, or
* {@code null} for none
* @param interactive whether to print in an interactive mode
* @return {@code true}, unless printing is canceled by the user
* @throws PrinterException if an error in the print system causes the job
* to be aborted
* @throws SecurityException if this thread is not allowed to
* initiate a print job request
*
* @see #getPrintable
* @see java.text.MessageFormat
* @see java.awt.GraphicsEnvironment#isHeadless
* @see java.util.concurrent.FutureTask
*
* @since 1.6
*/
public boolean print(final MessageFormat headerFormat,
final MessageFormat footerFormat,
final boolean showPrintDialog,
final PrintService service,
final PrintRequestAttributeSet attributes,
final boolean interactive)
throws PrinterException {
final PrinterJob job = PrinterJob.getPrinterJob();
final Printable printable;
final PrintingStatus printingStatus;
final boolean isHeadless = GraphicsEnvironment.isHeadless();
final boolean isEventDispatchThread =
SwingUtilities.isEventDispatchThread();
final Printable textPrintable = getPrintable(headerFormat, footerFormat);
if (interactive && ! isHeadless) {
printingStatus =
PrintingStatus.createPrintingStatus(this, job);
printable =
printingStatus.createNotificationPrintable(textPrintable);
} else {
printingStatus = null;
printable = textPrintable;
}
if (service != null) {
job.setPrintService(service);
}
job.setPrintable(printable);
final PrintRequestAttributeSet attr = (attributes == null)
? new HashPrintRequestAttributeSet()
: attributes;
if (showPrintDialog && ! isHeadless && ! job.printDialog(attr)) {
return false;
}
/*
* there are three cases for printing:
* 1. print non interactively (! interactive || isHeadless)
* 2. print interactively off EDT
* 3. print interactively on EDT
*
* 1 and 2 prints on the current thread (3 prints on another thread)
* 2 and 3 deal with PrintingStatusDialog
*/
final Callable