You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
443 lines
16 KiB
443 lines
16 KiB
/*
|
|
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
package java.awt.event;
|
|
|
|
import sun.awt.AWTAccessor;
|
|
import sun.awt.AppContext;
|
|
import sun.awt.SunToolkit;
|
|
|
|
import java.awt.AWTEvent;
|
|
import java.awt.Component;
|
|
import java.awt.EventQueue;
|
|
import java.awt.font.TextHitInfo;
|
|
import java.io.IOException;
|
|
import java.io.ObjectInputStream;
|
|
import java.text.AttributedCharacterIterator;
|
|
import java.text.CharacterIterator;
|
|
import java.lang.annotation.Native;
|
|
|
|
/**
|
|
* Input method events contain information about text that is being
|
|
* composed using an input method. Whenever the text changes, the
|
|
* input method sends an event. If the text component that's currently
|
|
* using the input method is an active client, the event is dispatched
|
|
* to that component. Otherwise, it is dispatched to a separate
|
|
* composition window.
|
|
*
|
|
* <p>
|
|
* The text included with the input method event consists of two parts:
|
|
* committed text and composed text. Either part may be empty. The two
|
|
* parts together replace any uncommitted composed text sent in previous events,
|
|
* or the currently selected committed text.
|
|
* Committed text should be integrated into the text component's persistent
|
|
* data, it will not be sent again. Composed text may be sent repeatedly,
|
|
* with changes to reflect the user's editing operations. Committed text
|
|
* always precedes composed text.
|
|
*
|
|
* @author JavaSoft Asia/Pacific
|
|
* @since 1.2
|
|
*/
|
|
public class InputMethodEvent extends AWTEvent {
|
|
|
|
/**
|
|
* Serial Version ID.
|
|
*/
|
|
private static final long serialVersionUID = 4727190874778922661L;
|
|
|
|
/**
|
|
* Marks the first integer id for the range of input method event ids.
|
|
*/
|
|
@Native public static final int INPUT_METHOD_FIRST = 1100;
|
|
|
|
/**
|
|
* The event type indicating changed input method text. This event is
|
|
* generated by input methods while processing input.
|
|
*/
|
|
@Native public static final int INPUT_METHOD_TEXT_CHANGED = INPUT_METHOD_FIRST;
|
|
|
|
/**
|
|
* The event type indicating a changed insertion point in input method text.
|
|
* This event is
|
|
* generated by input methods while processing input if only the caret changed.
|
|
*/
|
|
@Native public static final int CARET_POSITION_CHANGED = INPUT_METHOD_FIRST + 1;
|
|
|
|
/**
|
|
* Marks the last integer id for the range of input method event ids.
|
|
*/
|
|
@Native public static final int INPUT_METHOD_LAST = INPUT_METHOD_FIRST + 1;
|
|
|
|
/**
|
|
* The time stamp that indicates when the event was created.
|
|
*
|
|
* @serial
|
|
* @see #getWhen
|
|
* @since 1.4
|
|
*/
|
|
long when;
|
|
|
|
// Text object
|
|
private transient AttributedCharacterIterator text;
|
|
private transient int committedCharacterCount;
|
|
private transient TextHitInfo caret;
|
|
private transient TextHitInfo visiblePosition;
|
|
|
|
/**
|
|
* Constructs an <code>InputMethodEvent</code> with the specified
|
|
* source component, type, time, text, caret, and visiblePosition.
|
|
* <p>
|
|
* The offsets of caret and visiblePosition are relative to the current
|
|
* composed text; that is, the composed text within <code>text</code>
|
|
* if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
|
|
* the composed text within the <code>text</code> of the
|
|
* preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
|
|
* <p>Note that passing in an invalid <code>id</code> results in
|
|
* unspecified behavior. This method throws an
|
|
* <code>IllegalArgumentException</code> if <code>source</code>
|
|
* is <code>null</code>.
|
|
*
|
|
* @param source the object where the event originated
|
|
* @param id the event type
|
|
* @param when a long integer that specifies the time the event occurred
|
|
* @param text the combined committed and composed text,
|
|
* committed text first; must be <code>null</code>
|
|
* when the event type is <code>CARET_POSITION_CHANGED</code>;
|
|
* may be <code>null</code> for
|
|
* <code>INPUT_METHOD_TEXT_CHANGED</code> if there's no
|
|
* committed or composed text
|
|
* @param committedCharacterCount the number of committed
|
|
* characters in the text
|
|
* @param caret the caret (a.k.a. insertion point);
|
|
* <code>null</code> if there's no caret within current
|
|
* composed text
|
|
* @param visiblePosition the position that's most important
|
|
* to be visible; <code>null</code> if there's no
|
|
* recommendation for a visible position within current
|
|
* composed text
|
|
* @throws IllegalArgumentException if <code>id</code> is not
|
|
* in the range
|
|
* <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>;
|
|
* or if id is <code>CARET_POSITION_CHANGED</code> and
|
|
* <code>text</code> is not <code>null</code>;
|
|
* or if <code>committedCharacterCount</code> is not in the range
|
|
* <code>0</code>..<code>(text.getEndIndex() - text.getBeginIndex())</code>
|
|
* @throws IllegalArgumentException if <code>source</code> is null
|
|
*
|
|
* @since 1.4
|
|
*/
|
|
public InputMethodEvent(Component source, int id, long when,
|
|
AttributedCharacterIterator text, int committedCharacterCount,
|
|
TextHitInfo caret, TextHitInfo visiblePosition) {
|
|
super(source, id);
|
|
if (id < INPUT_METHOD_FIRST || id > INPUT_METHOD_LAST) {
|
|
throw new IllegalArgumentException("id outside of valid range");
|
|
}
|
|
|
|
if (id == CARET_POSITION_CHANGED && text != null) {
|
|
throw new IllegalArgumentException("text must be null for CARET_POSITION_CHANGED");
|
|
}
|
|
|
|
this.when = when;
|
|
this.text = text;
|
|
int textLength = 0;
|
|
if (text != null) {
|
|
textLength = text.getEndIndex() - text.getBeginIndex();
|
|
}
|
|
|
|
if (committedCharacterCount < 0 || committedCharacterCount > textLength) {
|
|
throw new IllegalArgumentException("committedCharacterCount outside of valid range");
|
|
}
|
|
this.committedCharacterCount = committedCharacterCount;
|
|
|
|
this.caret = caret;
|
|
this.visiblePosition = visiblePosition;
|
|
}
|
|
|
|
/**
|
|
* Constructs an <code>InputMethodEvent</code> with the specified
|
|
* source component, type, text, caret, and visiblePosition.
|
|
* <p>
|
|
* The offsets of caret and visiblePosition are relative to the current
|
|
* composed text; that is, the composed text within <code>text</code>
|
|
* if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
|
|
* the composed text within the <code>text</code> of the
|
|
* preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
|
|
* The time stamp for this event is initialized by invoking
|
|
* {@link java.awt.EventQueue#getMostRecentEventTime()}.
|
|
* <p>Note that passing in an invalid <code>id</code> results in
|
|
* unspecified behavior. This method throws an
|
|
* <code>IllegalArgumentException</code> if <code>source</code>
|
|
* is <code>null</code>.
|
|
*
|
|
* @param source the object where the event originated
|
|
* @param id the event type
|
|
* @param text the combined committed and composed text,
|
|
* committed text first; must be <code>null</code>
|
|
* when the event type is <code>CARET_POSITION_CHANGED</code>;
|
|
* may be <code>null</code> for
|
|
* <code>INPUT_METHOD_TEXT_CHANGED</code> if there's no
|
|
* committed or composed text
|
|
* @param committedCharacterCount the number of committed
|
|
* characters in the text
|
|
* @param caret the caret (a.k.a. insertion point);
|
|
* <code>null</code> if there's no caret within current
|
|
* composed text
|
|
* @param visiblePosition the position that's most important
|
|
* to be visible; <code>null</code> if there's no
|
|
* recommendation for a visible position within current
|
|
* composed text
|
|
* @throws IllegalArgumentException if <code>id</code> is not
|
|
* in the range
|
|
* <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>;
|
|
* or if id is <code>CARET_POSITION_CHANGED</code> and
|
|
* <code>text</code> is not <code>null</code>;
|
|
* or if <code>committedCharacterCount</code> is not in the range
|
|
* <code>0</code>..<code>(text.getEndIndex() - text.getBeginIndex())</code>
|
|
* @throws IllegalArgumentException if <code>source</code> is null
|
|
*/
|
|
public InputMethodEvent(Component source, int id,
|
|
AttributedCharacterIterator text, int committedCharacterCount,
|
|
TextHitInfo caret, TextHitInfo visiblePosition) {
|
|
this(source, id,
|
|
getMostRecentEventTimeForSource(source),
|
|
text, committedCharacterCount,
|
|
caret, visiblePosition);
|
|
}
|
|
|
|
/**
|
|
* Constructs an <code>InputMethodEvent</code> with the
|
|
* specified source component, type, caret, and visiblePosition.
|
|
* The text is set to <code>null</code>,
|
|
* <code>committedCharacterCount</code> to 0.
|
|
* <p>
|
|
* The offsets of <code>caret</code> and <code>visiblePosition</code>
|
|
* are relative to the current composed text; that is,
|
|
* the composed text within the <code>text</code> of the
|
|
* preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event if the
|
|
* event being constructed as a <code>CARET_POSITION_CHANGED</code> event.
|
|
* For an <code>INPUT_METHOD_TEXT_CHANGED</code> event without text,
|
|
* <code>caret</code> and <code>visiblePosition</code> must be
|
|
* <code>null</code>.
|
|
* The time stamp for this event is initialized by invoking
|
|
* {@link java.awt.EventQueue#getMostRecentEventTime()}.
|
|
* <p>Note that passing in an invalid <code>id</code> results in
|
|
* unspecified behavior. This method throws an
|
|
* <code>IllegalArgumentException</code> if <code>source</code>
|
|
* is <code>null</code>.
|
|
*
|
|
* @param source the object where the event originated
|
|
* @param id the event type
|
|
* @param caret the caret (a.k.a. insertion point);
|
|
* <code>null</code> if there's no caret within current
|
|
* composed text
|
|
* @param visiblePosition the position that's most important
|
|
* to be visible; <code>null</code> if there's no
|
|
* recommendation for a visible position within current
|
|
* composed text
|
|
* @throws IllegalArgumentException if <code>id</code> is not
|
|
* in the range
|
|
* <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>
|
|
* @throws IllegalArgumentException if <code>source</code> is null
|
|
*/
|
|
public InputMethodEvent(Component source, int id, TextHitInfo caret,
|
|
TextHitInfo visiblePosition) {
|
|
this(source, id,
|
|
getMostRecentEventTimeForSource(source),
|
|
null, 0, caret, visiblePosition);
|
|
}
|
|
|
|
/**
|
|
* Gets the combined committed and composed text.
|
|
* Characters from index 0 to index <code>getCommittedCharacterCount() - 1</code> are committed
|
|
* text, the remaining characters are composed text.
|
|
*
|
|
* @return the text.
|
|
* Always null for CARET_POSITION_CHANGED;
|
|
* may be null for INPUT_METHOD_TEXT_CHANGED if there's no composed or committed text.
|
|
*/
|
|
public AttributedCharacterIterator getText() {
|
|
return text;
|
|
}
|
|
|
|
/**
|
|
* Gets the number of committed characters in the text.
|
|
*/
|
|
public int getCommittedCharacterCount() {
|
|
return committedCharacterCount;
|
|
}
|
|
|
|
/**
|
|
* Gets the caret.
|
|
* <p>
|
|
* The offset of the caret is relative to the current
|
|
* composed text; that is, the composed text within getText()
|
|
* if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
|
|
* the composed text within getText() of the
|
|
* preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
|
|
*
|
|
* @return the caret (a.k.a. insertion point).
|
|
* Null if there's no caret within current composed text.
|
|
*/
|
|
public TextHitInfo getCaret() {
|
|
return caret;
|
|
}
|
|
|
|
/**
|
|
* Gets the position that's most important to be visible.
|
|
* <p>
|
|
* The offset of the visible position is relative to the current
|
|
* composed text; that is, the composed text within getText()
|
|
* if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
|
|
* the composed text within getText() of the
|
|
* preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
|
|
*
|
|
* @return the position that's most important to be visible.
|
|
* Null if there's no recommendation for a visible position within current composed text.
|
|
*/
|
|
public TextHitInfo getVisiblePosition() {
|
|
return visiblePosition;
|
|
}
|
|
|
|
/**
|
|
* Consumes this event so that it will not be processed
|
|
* in the default manner by the source which originated it.
|
|
*/
|
|
public void consume() {
|
|
consumed = true;
|
|
}
|
|
|
|
/**
|
|
* Returns whether or not this event has been consumed.
|
|
* @see #consume
|
|
*/
|
|
public boolean isConsumed() {
|
|
return consumed;
|
|
}
|
|
|
|
/**
|
|
* Returns the time stamp of when this event occurred.
|
|
*
|
|
* @return this event's timestamp
|
|
* @since 1.4
|
|
*/
|
|
public long getWhen() {
|
|
return when;
|
|
}
|
|
|
|
/**
|
|
* Returns a parameter string identifying this event.
|
|
* This method is useful for event-logging and for debugging.
|
|
* It contains the event ID in text form, the characters of the
|
|
* committed and composed text
|
|
* separated by "+", the number of committed characters,
|
|
* the caret, and the visible position.
|
|
*
|
|
* @return a string identifying the event and its attributes
|
|
*/
|
|
public String paramString() {
|
|
String typeStr;
|
|
switch(id) {
|
|
case INPUT_METHOD_TEXT_CHANGED:
|
|
typeStr = "INPUT_METHOD_TEXT_CHANGED";
|
|
break;
|
|
case CARET_POSITION_CHANGED:
|
|
typeStr = "CARET_POSITION_CHANGED";
|
|
break;
|
|
default:
|
|
typeStr = "unknown type";
|
|
}
|
|
|
|
String textString;
|
|
if (text == null) {
|
|
textString = "no text";
|
|
} else {
|
|
StringBuilder textBuffer = new StringBuilder("\"");
|
|
int committedCharacterCount = this.committedCharacterCount;
|
|
char c = text.first();
|
|
while (committedCharacterCount-- > 0) {
|
|
textBuffer.append(c);
|
|
c = text.next();
|
|
}
|
|
textBuffer.append("\" + \"");
|
|
while (c != CharacterIterator.DONE) {
|
|
textBuffer.append(c);
|
|
c = text.next();
|
|
}
|
|
textBuffer.append("\"");
|
|
textString = textBuffer.toString();
|
|
}
|
|
|
|
String countString = committedCharacterCount + " characters committed";
|
|
|
|
String caretString;
|
|
if (caret == null) {
|
|
caretString = "no caret";
|
|
} else {
|
|
caretString = "caret: " + caret.toString();
|
|
}
|
|
|
|
String visiblePositionString;
|
|
if (visiblePosition == null) {
|
|
visiblePositionString = "no visible position";
|
|
} else {
|
|
visiblePositionString = "visible position: " + visiblePosition.toString();
|
|
}
|
|
|
|
return typeStr + ", " + textString + ", " + countString + ", " + caretString + ", " + visiblePositionString;
|
|
}
|
|
|
|
/**
|
|
* Initializes the <code>when</code> field if it is not present in the
|
|
* object input stream. In that case, the field will be initialized by
|
|
* invoking {@link java.awt.EventQueue#getMostRecentEventTime()}.
|
|
*/
|
|
private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
|
|
s.defaultReadObject();
|
|
if (when == 0) {
|
|
// Can't use getMostRecentEventTimeForSource because source is always null during deserialization
|
|
when = EventQueue.getMostRecentEventTime();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the most recent event time in the {@code EventQueue} which the {@code source}
|
|
* belongs to.
|
|
*
|
|
* @param source the source of the event
|
|
* @exception IllegalArgumentException if source is null.
|
|
* @return most recent event time in the {@code EventQueue}
|
|
*/
|
|
private static long getMostRecentEventTimeForSource(Object source) {
|
|
if (source == null) {
|
|
// throw the IllegalArgumentException to conform to EventObject spec
|
|
throw new IllegalArgumentException("null source");
|
|
}
|
|
AppContext appContext = SunToolkit.targetToAppContext(source);
|
|
EventQueue eventQueue = SunToolkit.getSystemEventQueueImplPP(appContext);
|
|
return AWTAccessor.getEventQueueAccessor().getMostRecentEventTime(eventQueue);
|
|
}
|
|
}
|