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</code> 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</code>
114      * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
115      * the composed text within the <code>text</code> of the
116      * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
117      * <p>Note that passing in an invalid <code>id</code> results in
118      * unspecified behavior. This method throws an
119      * <code>IllegalArgumentException</code> if <code>source</code>
120      * is <code>null</code>.
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</code>
127      *      when the event type is <code>CARET_POSITION_CHANGED</code>;
128      *      may be <code>null</code> for
129      *      <code>INPUT_METHOD_TEXT_CHANGED</code> 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</code> 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</code> if there's no
138      *      recommendation for a visible position within current
139      *      composed text
140      * @throws IllegalArgumentException if <code>id</code> is not
141      *      in the range
142      *      <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>;
143      *      or if id is <code>CARET_POSITION_CHANGED</code> and
144      *      <code>text</code> is not <code>null</code>;
145      *      or if <code>committedCharacterCount</code> is not in the range
146      *      <code>0</code>..<code>(text.getEndIndex() - text.getBeginIndex())</code>
147      * @throws IllegalArgumentException if <code>source</code> 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</code> 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</code>
185      * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
186      * the composed text within the <code>text</code> of the
187      * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> 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</code> results in
191      * unspecified behavior. This method throws an
192      * <code>IllegalArgumentException</code> if <code>source</code>
193      * is <code>null</code>.
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</code>
199      *      when the event type is <code>CARET_POSITION_CHANGED</code>;
200      *      may be <code>null</code> for
201      *      <code>INPUT_METHOD_TEXT_CHANGED</code> 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</code> 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</code> if there's no
210      *      recommendation for a visible position within current
211      *      composed text
212      * @throws IllegalArgumentException if <code>id</code> is not
213      *      in the range
214      *      <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>;
215      *      or if id is <code>CARET_POSITION_CHANGED</code> and
216      *      <code>text</code> is not <code>null</code>;
217      *      or if <code>committedCharacterCount</code> is not in the range
218      *      <code>0</code>..<code>(text.getEndIndex() - text.getBeginIndex())</code>
219      * @throws IllegalArgumentException if <code>source</code> 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</code> with the
232      * specified source component, type, caret, and visiblePosition.
233      * The text is set to <code>null</code>,
234      * <code>committedCharacterCount</code> to 0.
235      * <p>
236      * The offsets of <code>caret</code> and <code>visiblePosition</code>
237      * are relative to the current composed text; that is,
238      * the composed text within the <code>text</code> of the
239      * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event if the
240      * event being constructed as a <code>CARET_POSITION_CHANGED</code> event.
241      * For an <code>INPUT_METHOD_TEXT_CHANGED</code> event without text,
242      * <code>caret</code> and <code>visiblePosition</code> must be
243      * <code>null</code>.
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</code> results in
247      * unspecified behavior. This method throws an
248      * <code>IllegalArgumentException</code> if <code>source</code>
249      * is <code>null</code>.
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</code> 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</code> if there's no
258      *      recommendation for a visible position within current
259      *      composed text
260      * @throws IllegalArgumentException if <code>id</code> is not
261      *      in the range
262      *      <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>
263      * @throws IllegalArgumentException if <code>source</code> 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</code> 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      */
getCommittedCharacterCount()288     public int getCommittedCharacterCount() {
289         return committedCharacterCount;
290     }
291 
292     /**
293      * Gets the caret.
294      * <p>
295      * The offset of the caret is relative to the current
296      * composed text; that is, the composed text within getText()
297      * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
298      * the composed text within getText() of the
299      * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
300      *
301      * @return the caret (a.k.a. insertion point).
302      * Null if there's no caret within current composed text.
303      */
getCaret()304     public TextHitInfo getCaret() {
305         return caret;
306     }
307 
308     /**
309      * Gets the position that's most important to be visible.
310      * <p>
311      * The offset of the visible position is relative to the current
312      * composed text; that is, the composed text within getText()
313      * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
314      * the composed text within getText() of the
315      * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
316      *
317      * @return the position that's most important to be visible.
318      * Null if there's no recommendation for a visible position within current composed text.
319      */
getVisiblePosition()320     public TextHitInfo getVisiblePosition() {
321         return visiblePosition;
322     }
323 
324     /**
325      * Consumes this event so that it will not be processed
326      * in the default manner by the source which originated it.
327      */
consume()328     public void consume() {
329         consumed = true;
330     }
331 
332     /**
333      * Returns whether or not this event has been consumed.
334      * @see #consume
335      */
isConsumed()336     public boolean isConsumed() {
337         return consumed;
338     }
339 
340     /**
341      * Returns the time stamp of when this event occurred.
342      *
343      * @return this event's timestamp
344      * @since 1.4
345      */
getWhen()346     public long getWhen() {
347       return when;
348     }
349 
350     /**
351      * Returns a parameter string identifying this event.
352      * This method is useful for event-logging and for debugging.
353      * It contains the event ID in text form, the characters of the
354      * committed and composed text
355      * separated by "+", the number of committed characters,
356      * the caret, and the visible position.
357      *
358      * @return a string identifying the event and its attributes
359      */
paramString()360     public String paramString() {
361         String typeStr;
362         switch(id) {
363           case INPUT_METHOD_TEXT_CHANGED:
364               typeStr = "INPUT_METHOD_TEXT_CHANGED";
365               break;
366           case CARET_POSITION_CHANGED:
367               typeStr = "CARET_POSITION_CHANGED";
368               break;
369           default:
370               typeStr = "unknown type";
371         }
372 
373         String textString;
374         if (text == null) {
375             textString = "no text";
376         } else {
377             StringBuilder textBuffer = new StringBuilder("\"");
378             int committedCharacterCount = this.committedCharacterCount;
379             char c = text.first();
380             while (committedCharacterCount-- > 0) {
381                 textBuffer.append(c);
382                 c = text.next();
383             }
384             textBuffer.append("\" + \"");
385             while (c != CharacterIterator.DONE) {
386                 textBuffer.append(c);
387                 c = text.next();
388             }
389             textBuffer.append("\"");
390             textString = textBuffer.toString();
391         }
392 
393         String countString = committedCharacterCount + " characters committed";
394 
395         String caretString;
396         if (caret == null) {
397             caretString = "no caret";
398         } else {
399             caretString = "caret: " + caret.toString();
400         }
401 
402         String visiblePositionString;
403         if (visiblePosition == null) {
404             visiblePositionString = "no visible position";
405         } else {
406             visiblePositionString = "visible position: " + visiblePosition.toString();
407         }
408 
409         return typeStr + ", " + textString + ", " + countString + ", " + caretString + ", " + visiblePositionString;
410     }
411 
412     /**
413      * Initializes the <code>when</code> field if it is not present in the
414      * object input stream. In that case, the field will be initialized by
415      * invoking {@link java.awt.EventQueue#getMostRecentEventTime()}.
416      */
readObject(ObjectInputStream s)417     private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
418         s.defaultReadObject();
419         if (when == 0) {
420             // Can't use getMostRecentEventTimeForSource because source is always null during deserialization
421             when = EventQueue.getMostRecentEventTime();
422         }
423     }
424 
425     /**
426      * Get the most recent event time in the {@code EventQueue} which the {@code source}
427      * belongs to.
428      *
429      * @param source the source of the event
430      * @exception  IllegalArgumentException  if source is null.
431      * @return most recent event time in the {@code EventQueue}
432      */
getMostRecentEventTimeForSource(Object source)433     private static long getMostRecentEventTimeForSource(Object source) {
434         if (source == null) {
435             // throw the IllegalArgumentException to conform to EventObject spec
436             throw new IllegalArgumentException("null source");
437         }
438         AppContext appContext = SunToolkit.targetToAppContext(source);
439         EventQueue eventQueue = SunToolkit.getSystemEventQueueImplPP(appContext);
440         return AWTAccessor.getEventQueueAccessor().getMostRecentEventTime(eventQueue);
441     }
442 }
443