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