1 /*
2 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package java.awt.event;
27
28 import java.awt.AWTEvent;
29 import java.awt.Component;
30 import java.awt.EventQueue;
31 import java.awt.font.TextHitInfo;
32 import java.io.IOException;
33 import java.io.ObjectInputStream;
34 import java.text.AttributedCharacterIterator;
35 import java.text.CharacterIterator;
36
37 /**
38 * Input method events contain information about text that is being
39 * composed using an input method. Whenever the text changes, the
40 * input method sends an event. If the text component that's currently
41 * using the input method is an active client, the event is dispatched
42 * to that component. Otherwise, it is dispatched to a separate
43 * composition window.
44 *
45 * <p>
46 * The text included with the input method event consists of two parts:
47 * committed text and composed text. Either part may be empty. The two
48 * parts together replace any uncommitted composed text sent in previous events,
49 * or the currently selected committed text.
50 * Committed text should be integrated into the text component's persistent
51 * data, it will not be sent again. Composed text may be sent repeatedly,
52 * with changes to reflect the user's editing operations. Committed text
53 * always precedes composed text.
54 *
55 * @author JavaSoft Asia/Pacific
56 * @since 1.2
57 */
58
59 public class InputMethodEvent extends AWTEvent {
60
61 /**
62 * Serial Version ID.
63 */
64 private static final long serialVersionUID = 4727190874778922661L;
65
66 /**
67 * Marks the first integer id for the range of input method event ids.
68 */
69 public static final int INPUT_METHOD_FIRST = 1100;
70
71 /**
72 * The event type indicating changed input method text. This event is
73 * generated by input methods while processing input.
74 */
75 public static final int INPUT_METHOD_TEXT_CHANGED = INPUT_METHOD_FIRST;
76
77 /**
78 * The event type indicating a changed insertion point in input method text.
79 * This event is
80 * generated by input methods while processing input if only the caret changed.
81 */
82 public static final int CARET_POSITION_CHANGED = INPUT_METHOD_FIRST + 1;
83
84 /**
85 * Marks the last integer id for the range of input method event ids.
86 */
87 public static final int INPUT_METHOD_LAST = INPUT_METHOD_FIRST + 1;
88
89 /**
90 * The time stamp that indicates when the event was created.
91 *
92 * @serial
93 * @see #getWhen
94 * @since 1.4
95 */
96 long when;
97
98 // Text object
99 private transient AttributedCharacterIterator text;
100 private transient int committedCharacterCount;
101 private transient TextHitInfo caret;
102 private transient TextHitInfo visiblePosition;
103
104 /**
105 * Constructs an <code>InputMethodEvent</code> with the specified
106 * source component, type, time, text, caret, and visiblePosition.
107 * <p>
108 * The offsets of caret and visiblePosition are relative to the current
109 * composed text; that is, the composed text within <code>text</code>
110 * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
111 * the composed text within the <code>text</code> of the
112 * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
113 * <p>Note that passing in an invalid <code>id</code> results in
114 * unspecified behavior. This method throws an
115 * <code>IllegalArgumentException</code> if <code>source</code>
116 * is <code>null</code>.
117 *
118 * @param source the object where the event originated
119 * @param id the event type
120 * @param when a long integer that specifies the time the event occurred
121 * @param text the combined committed and composed text,
122 * committed text first; must be <code>null</code>
123 * when the event type is <code>CARET_POSITION_CHANGED</code>;
124 * may be <code>null</code> for
125 * <code>INPUT_METHOD_TEXT_CHANGED</code> if there's no
126 * committed or composed text
127 * @param committedCharacterCount the number of committed
128 * characters in the text
129 * @param caret the caret (a.k.a. insertion point);
130 * <code>null</code> if there's no caret within current
131 * composed text
132 * @param visiblePosition the position that's most important
133 * to be visible; <code>null</code> if there's no
134 * recommendation for a visible position within current
135 * composed text
136 * @throws IllegalArgumentException if <code>id</code> is not
137 * in the range
138 * <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>;
139 * or if id is <code>CARET_POSITION_CHANGED</code> and
140 * <code>text</code> is not <code>null</code>;
141 * or if <code>committedCharacterCount</code> is not in the range
142 * <code>0</code>..<code>(text.getEndIndex() - text.getBeginIndex())</code>
143 * @throws IllegalArgumentException if <code>source</code> is null
144 *
145 * @since 1.4
146 */
147 public InputMethodEvent(Component source, int id, long when,
148 AttributedCharacterIterator text, int committedCharacterCount,
149 TextHitInfo caret, TextHitInfo visiblePosition) {
150 super(source, id);
151 if (id < INPUT_METHOD_FIRST || id > INPUT_METHOD_LAST) {
152 throw new IllegalArgumentException("id outside of valid range");
153 }
154
155 if (id == CARET_POSITION_CHANGED && text != null) {
156 throw new IllegalArgumentException("text must be null for CARET_POSITION_CHANGED");
157 }
158
159 this.when = when;
160 this.text = text;
161 int textLength = 0;
162 if (text != null) {
163 textLength = text.getEndIndex() - text.getBeginIndex();
164 }
165
166 if (committedCharacterCount < 0 || committedCharacterCount > textLength) {
167 throw new IllegalArgumentException("committedCharacterCount outside of valid range");
168 }
169 this.committedCharacterCount = committedCharacterCount;
170
171 this.caret = caret;
172 this.visiblePosition = visiblePosition;
173 }
174
175 /**
176 * Constructs an <code>InputMethodEvent</code> with the specified
177 * source component, type, text, caret, and visiblePosition.
178 * <p>
179 * The offsets of caret and visiblePosition are relative to the current
180 * composed text; that is, the composed text within <code>text</code>
181 * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
182 * the composed text within the <code>text</code> of the
183 * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
184 * The time stamp for this event is initialized by invoking
185 * {@link java.awt.EventQueue#getMostRecentEventTime()}.
186 * <p>Note that passing in an invalid <code>id</code> results in
187 * unspecified behavior. This method throws an
188 * <code>IllegalArgumentException</code> if <code>source</code>
189 * is <code>null</code>.
190 *
191 * @param source the object where the event originated
192 * @param id the event type
193 * @param text the combined committed and composed text,
194 * committed text first; must be <code>null</code>
195 * when the event type is <code>CARET_POSITION_CHANGED</code>;
196 * may be <code>null</code> for
197 * <code>INPUT_METHOD_TEXT_CHANGED</code> if there's no
198 * committed or composed text
199 * @param committedCharacterCount the number of committed
200 * characters in the text
201 * @param caret the caret (a.k.a. insertion point);
202 * <code>null</code> if there's no caret within current
203 * composed text
204 * @param visiblePosition the position that's most important
205 * to be visible; <code>null</code> if there's no
206 * recommendation for a visible position within current
207 * composed text
208 * @throws IllegalArgumentException if <code>id</code> is not
209 * in the range
210 * <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>;
211 * or if id is <code>CARET_POSITION_CHANGED</code> and
212 * <code>text</code> is not <code>null</code>;
213 * or if <code>committedCharacterCount</code> is not in the range
214 * <code>0</code>..<code>(text.getEndIndex() - text.getBeginIndex())</code>
215 * @throws IllegalArgumentException if <code>source</code> is null
216 */
217 public InputMethodEvent(Component source, int id,
218 AttributedCharacterIterator text, int committedCharacterCount,
219 TextHitInfo caret, TextHitInfo visiblePosition) {
220 this(source, id, EventQueue.getMostRecentEventTime(), text,
221 committedCharacterCount, caret, visiblePosition);
222 }
223
224 /**
225 * Constructs an <code>InputMethodEvent</code> with the
226 * specified source component, type, caret, and visiblePosition.
227 * The text is set to <code>null</code>,
228 * <code>committedCharacterCount</code> to 0.
229 * <p>
230 * The offsets of <code>caret</code> and <code>visiblePosition</code>
231 * are relative to the current composed text; that is,
232 * the composed text within the <code>text</code> of the
233 * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event if the
234 * event being constructed as a <code>CARET_POSITION_CHANGED</code> event.
235 * For an <code>INPUT_METHOD_TEXT_CHANGED</code> event without text,
236 * <code>caret</code> and <code>visiblePosition</code> must be
237 * <code>null</code>.
238 * The time stamp for this event is initialized by invoking
239 * {@link java.awt.EventQueue#getMostRecentEventTime()}.
240 * <p>Note that passing in an invalid <code>id</code> results in
241 * unspecified behavior. This method throws an
242 * <code>IllegalArgumentException</code> if <code>source</code>
243 * is <code>null</code>.
244 *
245 * @param source the object where the event originated
246 * @param id the event type
247 * @param caret the caret (a.k.a. insertion point);
248 * <code>null</code> if there's no caret within current
249 * composed text
250 * @param visiblePosition the position that's most important
251 * to be visible; <code>null</code> if there's no
252 * recommendation for a visible position within current
253 * composed text
254 * @throws IllegalArgumentException if <code>id</code> is not
255 * in the range
256 * <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>
257 * @throws IllegalArgumentException if <code>source</code> is null
258 */
259 public InputMethodEvent(Component source, int id, TextHitInfo caret,
260 TextHitInfo visiblePosition) {
261 this(source, id, EventQueue.getMostRecentEventTime(), null,
262 0, caret, visiblePosition);
263 }
264
265 /**
266 * Gets the combined committed and composed text.
267 * Characters from index 0 to index <code>getCommittedCharacterCount() - 1</code> are committed
268 * text, the remaining characters are composed text.
269 *
270 * @return the text.
271 * Always null for CARET_POSITION_CHANGED;
272 * may be null for INPUT_METHOD_TEXT_CHANGED if there's no composed or committed text.
273 */
274 public AttributedCharacterIterator getText() {
275 return text;
276 }
277
278 /**
279 * Gets the number of committed characters in the text.
280 */
281 public int getCommittedCharacterCount() {
282 return committedCharacterCount;
283 }
284
285 /**
286 * Gets the caret.
287 * <p>
288 * The offset of the caret is relative to the current
289 * composed text; that is, the composed text within getText()
290 * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
291 * the composed text within getText() of the
292 * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
293 *
294 * @return the caret (a.k.a. insertion point).
295 * Null if there's no caret within current composed text.
296 */
297 public TextHitInfo getCaret() {
298 return caret;
299 }
300
301 /**
302 * Gets the position that's most important to be visible.
303 * <p>
304 * The offset of the visible position is relative to the current
305 * composed text; that is, the composed text within getText()
306 * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
307 * the composed text within getText() of the
308 * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
309 *
310 * @return the position that's most important to be visible.
311 * Null if there's no recommendation for a visible position within current composed text.
312 */
313 public TextHitInfo getVisiblePosition() {
314 return visiblePosition;
315 }
316
317 /**
318 * Consumes this event so that it will not be processed
319 * in the default manner by the source which originated it.
320 */
321 public void consume() {
322 consumed = true;
323 }
324
325 /**
326 * Returns whether or not this event has been consumed.
327 * @see #consume
328 */
329 public boolean isConsumed() {
330 return consumed;
331 }
332
333 /**
334 * Returns the time stamp of when this event occurred.
335 *
336 * @return this event's timestamp
337 * @since 1.4
338 */
339 public long getWhen() {
340 return when;
341 }
342
343 /**
344 * Returns a parameter string identifying this event.
345 * This method is useful for event-logging and for debugging.
346 * It contains the event ID in text form, the characters of the
347 * committed and composed text
348 * separated by "+", the number of committed characters,
349 * the caret, and the visible position.
350 *
351 * @return a string identifying the event and its attributes
352 */
353 public String paramString() {
354 String typeStr;
355 switch(id) {
356 case INPUT_METHOD_TEXT_CHANGED:
357 typeStr = "INPUT_METHOD_TEXT_CHANGED";
358 break;
359 case CARET_POSITION_CHANGED:
360 typeStr = "CARET_POSITION_CHANGED";
361 break;
362 default:
363 typeStr = "unknown type";
364 }
365
366 String textString;
367 if (text == null) {
368 textString = "no text";
369 } else {
370 StringBuilder textBuffer = new StringBuilder("\"");
371 int committedCharacterCount = this.committedCharacterCount;
372 char c = text.first();
373 while (committedCharacterCount-- > 0) {
374 textBuffer.append(c);
375 c = text.next();
376 }
377 textBuffer.append("\" + \"");
378 while (c != CharacterIterator.DONE) {
379 textBuffer.append(c);
380 c = text.next();
381 }
382 textBuffer.append("\"");
383 textString = textBuffer.toString();
384 }
385
386 String countString = committedCharacterCount + " characters committed";
387
388 String caretString;
389 if (caret == null) {
390 caretString = "no caret";
391 } else {
392 caretString = "caret: " + caret.toString();
393 }
394
395 String visiblePositionString;
396 if (visiblePosition == null) {
397 visiblePositionString = "no visible position";
398 } else {
399 visiblePositionString = "visible position: " + visiblePosition.toString();
400 }
401
402 return typeStr + ", " + textString + ", " + countString + ", " + caretString + ", " + visiblePositionString;
403 }
404
405 /**
406 * Initializes the <code>when</code> field if it is not present in the
407 * object input stream. In that case, the field will be initialized by
408 * invoking {@link java.awt.EventQueue#getMostRecentEventTime()}.
409 */
410 private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
411 s.defaultReadObject();
412 if (when == 0) {
413 when = EventQueue.getMostRecentEventTime();
414 }
415 }
416 }