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