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 private transient int replaceStart; 108 private transient int replaceEnd; 109 110 111 /** 112 * Constructs an <code>InputMethodEvent</code> with the specified 113 * source component, type, time, text, caret, and visiblePosition. 114 * <p> 115 * The offsets of caret and visiblePosition are relative to the current 116 * composed text; that is, the composed text within <code>text</code> 117 * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event, 118 * the composed text within the <code>text</code> of the 119 * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise. 120 * <p>Note that passing in an invalid <code>id</code> results in 121 * unspecified behavior. This method throws an 122 * <code>IllegalArgumentException</code> if <code>source</code> 123 * is <code>null</code>. 124 * 125 * @param source the object where the event originated 126 * @param id the event type 127 * @param when a long integer that specifies the time the event occurred 128 * @param text the combined committed and composed text, 129 * committed text first; must be <code>null</code> 130 * when the event type is <code>CARET_POSITION_CHANGED</code>; 131 * may be <code>null</code> for 132 * <code>INPUT_METHOD_TEXT_CHANGED</code> if there's no 133 * committed or composed text 134 * @param committedCharacterCount the number of committed 135 * characters in the text 136 * @param caret the caret (a.k.a. insertion point); 137 * <code>null</code> if there's no caret within current 138 * composed text 139 * @param visiblePosition the position that's most important 140 * to be visible; <code>null</code> if there's no 141 * recommendation for a visible position within current 142 * composed text 143 * @throws IllegalArgumentException if <code>id</code> is not 144 * in the range 145 * <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>; 146 * or if id is <code>CARET_POSITION_CHANGED</code> and 147 * <code>text</code> is not <code>null</code>; 148 * or if <code>committedCharacterCount</code> is not in the range 149 * <code>0</code>..<code>(text.getEndIndex() - text.getBeginIndex())</code> 150 * @throws IllegalArgumentException if <code>source</code> is null 151 * 152 * @since 1.4 153 */ 154 public InputMethodEvent(Component source, int id, long when, 155 AttributedCharacterIterator text, int committedCharacterCount, 156 TextHitInfo caret, TextHitInfo visiblePosition) { 157 158 this(source, id, when, text, committedCharacterCount, 159 caret, visiblePosition, -1, -1); 160 } 161 162 private InputMethodEvent(Component source, int id, long when, 163 AttributedCharacterIterator text, int committedCharacterCount, 164 TextHitInfo caret, TextHitInfo visiblePosition, 165 int replaceStart, int replaceEnd) { 166 167 super(source, id); 168 if (id < INPUT_METHOD_FIRST || id > INPUT_METHOD_LAST) { 169 throw new IllegalArgumentException("id outside of valid range"); 170 } 171 172 if (id == CARET_POSITION_CHANGED && text != null) { 173 throw new IllegalArgumentException("text must be null for CARET_POSITION_CHANGED"); 174 } 175 176 this.when = when; 177 this.text = text; 178 int textLength = 0; 179 if (text != null) { 180 textLength = text.getEndIndex() - text.getBeginIndex(); 181 } 182 183 if (committedCharacterCount < 0 || committedCharacterCount > textLength) { 184 throw new IllegalArgumentException("committedCharacterCount outside of valid range"); 185 } 186 this.committedCharacterCount = committedCharacterCount; 187 188 this.caret = caret; 189 this.visiblePosition = visiblePosition; 190 this.replaceStart = replaceStart; 191 this.replaceEnd = replaceEnd; 192 } 193 194 /** 195 * Constructs an <code>InputMethodEvent</code> with the specified 196 * source component, type, text, caret, and visiblePosition. 197 * <p> 198 * The offsets of caret and visiblePosition are relative to the current 199 * composed text; that is, the composed text within <code>text</code> 200 * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event, 201 * the composed text within the <code>text</code> of the 202 * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise. 203 * The time stamp for this event is initialized by invoking 204 * {@link java.awt.EventQueue#getMostRecentEventTime()}. 205 * <p>Note that passing in an invalid <code>id</code> results in 206 * unspecified behavior. This method throws an 207 * <code>IllegalArgumentException</code> if <code>source</code> 208 * is <code>null</code>. 209 * 210 * @param source the object where the event originated 211 * @param id the event type 212 * @param text the combined committed and composed text, 213 * committed text first; must be <code>null</code> 214 * when the event type is <code>CARET_POSITION_CHANGED</code>; 215 * may be <code>null</code> for 216 * <code>INPUT_METHOD_TEXT_CHANGED</code> if there's no 217 * committed or composed text 218 * @param committedCharacterCount the number of committed 219 * characters in the text 220 * @param caret the caret (a.k.a. insertion point); 221 * <code>null</code> if there's no caret within current 222 * composed text 223 * @param visiblePosition the position that's most important 224 * to be visible; <code>null</code> if there's no 225 * recommendation for a visible position within current 226 * composed text 227 * @throws IllegalArgumentException if <code>id</code> is not 228 * in the range 229 * <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>; 230 * or if id is <code>CARET_POSITION_CHANGED</code> and 231 * <code>text</code> is not <code>null</code>; 232 * or if <code>committedCharacterCount</code> is not in the range 233 * <code>0</code>..<code>(text.getEndIndex() - text.getBeginIndex())</code> 234 * @throws IllegalArgumentException if <code>source</code> is null 235 */ 236 public InputMethodEvent(Component source, int id, 237 AttributedCharacterIterator text, int committedCharacterCount, 238 TextHitInfo caret, TextHitInfo visiblePosition) { 239 this(source, id, 240 getMostRecentEventTimeForSource(source), 241 text, committedCharacterCount, 242 caret, visiblePosition); 243 } 244 245 /** 246 * Constructs an <code>InputMethodEvent</code> with the 247 * specified source component, type, caret, and visiblePosition. 248 * The text is set to <code>null</code>, 249 * <code>committedCharacterCount</code> to 0. 250 * <p> 251 * The offsets of <code>caret</code> and <code>visiblePosition</code> 252 * are relative to the current composed text; that is, 253 * the composed text within the <code>text</code> of the 254 * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event if the 255 * event being constructed as a <code>CARET_POSITION_CHANGED</code> event. 256 * For an <code>INPUT_METHOD_TEXT_CHANGED</code> event without text, 257 * <code>caret</code> and <code>visiblePosition</code> must be 258 * <code>null</code>. 259 * The time stamp for this event is initialized by invoking 260 * {@link java.awt.EventQueue#getMostRecentEventTime()}. 261 * <p>Note that passing in an invalid <code>id</code> results in 262 * unspecified behavior. This method throws an 263 * <code>IllegalArgumentException</code> if <code>source</code> 264 * is <code>null</code>. 265 * 266 * @param source the object where the event originated 267 * @param id the event type 268 * @param caret the caret (a.k.a. insertion point); 269 * <code>null</code> if there's no caret within current 270 * composed text 271 * @param visiblePosition the position that's most important 272 * to be visible; <code>null</code> if there's no 273 * recommendation for a visible position within current 274 * composed text 275 * @throws IllegalArgumentException if <code>id</code> is not 276 * in the range 277 * <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code> 278 * @throws IllegalArgumentException if <code>source</code> is null 279 */ 280 public InputMethodEvent(Component source, int id, TextHitInfo caret, 281 TextHitInfo visiblePosition) { 282 this(source, id, 283 getMostRecentEventTimeForSource(source), 284 null, 0, caret, visiblePosition); 285 } 286 287 /** 288 * Constructs an <code>InputMethodEvent</code> with the 289 * specified source component, type, caret, and visiblePosition. 290 * The text is set to <code>null</code>, 291 * <code>committedCharacterCount</code> to 0. 292 * <p> 293 * The offsets of <code>caret</code> and <code>visiblePosition</code> 294 * are relative to the current composed text; that is, 295 * the composed text within the <code>text</code> of the 296 * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event if the 297 * event being constructed as a <code>CARET_POSITION_CHANGED</code> event. 298 * For an <code>INPUT_METHOD_TEXT_CHANGED</code> event without text, 299 * <code>caret</code> and <code>visiblePosition</code> must be 300 * <code>null</code>. 301 * The time stamp for this event is initialized by invoking 302 * {@link java.awt.EventQueue#getMostRecentEventTime()}. 303 * <p>Note that passing in an invalid <code>id</code> results in 304 * unspecified behavior. This method throws an 305 * <code>IllegalArgumentException</code> if <code>source</code> 306 * is <code>null</code>. 307 * 308 * @param source the object where the event originated 309 * @param id the event type 310 * @param caret the caret (a.k.a. insertion point); 311 * <code>null</code> if there's no caret within current 312 * composed text 313 * @param visiblePosition the position that's most important 314 * to be visible; <code>null</code> if there's no 315 * recommendation for a visible position within current 316 * composed text 317 * @param replaceStart start of the text range in the text document 318 * which this IM event content should replace. 319 * -1 if no content should be replaced 320 * @param replaceEnd end of the text range in the text document 321 * which this IM event content should replace. 322 * -1 if no content should be replaced 323 * @throws IllegalArgumentException if <code>id</code> is not 324 * in the range 325 * <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code> 326 * @throws IllegalArgumentException if <code>source</code> is null 327 */ 328 public InputMethodEvent(Component source, int id, AttributedCharacterIterator text, 329 int committedCharacterCount, TextHitInfo caret, TextHitInfo visiblePosition, 330 int replaceStart, int replaceEnd) { 331 332 this(source, id, getMostRecentEventTimeForSource(source), text, 333 committedCharacterCount, caret, visiblePosition, 334 replaceStart, replaceEnd); 335 } 336 337 338 /** 339 * Gets the combined committed and composed text. 340 * Characters from index 0 to index <code>getCommittedCharacterCount() - 1</code> are committed 341 * text, the remaining characters are composed text. 342 * 343 * @return the text. 344 * Always null for CARET_POSITION_CHANGED; 345 * may be null for INPUT_METHOD_TEXT_CHANGED if there's no composed or committed text. 346 */ 347 public AttributedCharacterIterator getText() { 348 return text; 349 } 350 351 /** 352 * Gets the number of committed characters in the text. 353 * @return the number of committed characters in the text 354 */ 355 public int getCommittedCharacterCount() { 356 return committedCharacterCount; 357 } 358 359 /** 360 * Gets the caret. 361 * <p> 362 * The offset of the caret is relative to the current 363 * composed text; that is, the composed text within getText() 364 * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event, 365 * the composed text within getText() of the 366 * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise. 367 * 368 * @return the caret (a.k.a. insertion point). 369 * Null if there's no caret within current composed text. 370 */ 371 public TextHitInfo getCaret() { 372 return caret; 373 } 374 375 /** 376 * Gets the position that's most important to be visible. 377 * <p> 378 * The offset of the visible position is relative to the current 379 * composed text; that is, the composed text within getText() 380 * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event, 381 * the composed text within getText() of the 382 * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise. 383 * 384 * @return the position that's most important to be visible. 385 * Null if there's no recommendation for a visible position within current composed text. 386 */ 387 public TextHitInfo getVisiblePosition() { 388 return visiblePosition; 389 } 390 391 /** 392 * Consumes this event so that it will not be processed 393 * in the default manner by the source which originated it. 394 */ 395 public void consume() { 396 consumed = true; 397 } 398 399 /** 400 * Returns whether or not this event has been consumed. 401 * @see #consume 402 */ 403 public boolean isConsumed() { 404 return consumed; 405 } 406 407 /** 408 * Returns the time stamp of when this event occurred. 409 * 410 * @return this event's timestamp 411 * @since 1.4 412 */ 413 public long getWhen() { 414 return when; 415 } 416 417 /** 418 * Returns start of the text range in the text document 419 * which this IM event content should replace. 420 * @return -1 if no content should be replaced 421 */ 422 public int getReplaceStart() { 423 return replaceStart; 424 } 425 426 /** 427 * Returns end of the text range in the text document 428 * which this IM event content should replace. 429 * @return -1 if no content should be replaced 430 */ 431 public int getReplaceEnd() { 432 return replaceEnd; 433 } 434 435 /** 436 * Returns a parameter string identifying this event. 437 * This method is useful for event-logging and for debugging. 438 * It contains the event ID in text form, the characters of the 439 * committed and composed text 440 * separated by "+", the number of committed characters, 441 * the caret, and the visible position. 442 * 443 * @return a string identifying the event and its attributes 444 */ 445 public String paramString() { 446 String typeStr; 447 switch(id) { 448 case INPUT_METHOD_TEXT_CHANGED: 449 typeStr = "INPUT_METHOD_TEXT_CHANGED"; 450 break; 451 case CARET_POSITION_CHANGED: 452 typeStr = "CARET_POSITION_CHANGED"; 453 break; 454 default: 455 typeStr = "unknown type"; 456 } 457 458 String textString; 459 if (text == null) { 460 textString = "no text"; 461 } else { 462 StringBuilder textBuffer = new StringBuilder("\""); 463 int committedCharacterCount = this.committedCharacterCount; 464 char c = text.first(); 465 while (committedCharacterCount-- > 0) { 466 textBuffer.append(c); 467 c = text.next(); 468 } 469 textBuffer.append("\" + \""); 470 while (c != CharacterIterator.DONE) { 471 textBuffer.append(c); 472 c = text.next(); 473 } 474 textBuffer.append("\""); 475 textString = textBuffer.toString(); 476 } 477 478 String countString = committedCharacterCount + " characters committed"; 479 480 String caretString; 481 if (caret == null) { 482 caretString = "no caret"; 483 } else { 484 caretString = "caret: " + caret.toString(); 485 } 486 487 String visiblePositionString; 488 if (visiblePosition == null) { 489 visiblePositionString = "no visible position"; 490 } else { 491 visiblePositionString = "visible position: " + visiblePosition.toString(); 492 } 493 494 String replacementRangeString = ""; 495 if (replaceStart >= 0) { 496 replacementRangeString = ", replacement range (" + replaceStart + 497 ", " + replaceEnd + ")"; 498 } 499 500 return typeStr + ", " + textString + ", " + countString + ", " 501 + caretString + ", " + visiblePositionString + replacementRangeString; 502 } 503 504 /** 505 * Initializes the <code>when</code> field if it is not present in the 506 * object input stream. In that case, the field will be initialized by 507 * invoking {@link java.awt.EventQueue#getMostRecentEventTime()}. 508 */ 509 private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { 510 s.defaultReadObject(); 511 if (when == 0) { 512 // Can't use getMostRecentEventTimeForSource because source is always null during deserialization 513 when = EventQueue.getMostRecentEventTime(); 514 } 515 } 516 517 /** 518 * Get the most recent event time in the {@code EventQueue} which the {@code source} 519 * belongs to. 520 * 521 * @param source the source of the event 522 * @exception IllegalArgumentException if source is null. 523 * @return most recent event time in the {@code EventQueue} 524 */ 525 private static long getMostRecentEventTimeForSource(Object source) { 526 if (source == null) { 527 // throw the IllegalArgumentException to conform to EventObject spec 528 throw new IllegalArgumentException("null source"); 529 } 530 AppContext appContext = SunToolkit.targetToAppContext(source); 531 EventQueue eventQueue = SunToolkit.getSystemEventQueueImplPP(appContext); 532 return AWTAccessor.getEventQueueAccessor().getMostRecentEventTime(eventQueue); 533 } 534 }