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