1 /* 2 * Copyright (c) 1997, 2020, 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.lang.annotation.Native; 35 import java.text.AttributedCharacterIterator; 36 import java.text.CharacterIterator; 37 38 import sun.awt.AWTAccessor; 39 import sun.awt.AppContext; 40 import sun.awt.SunToolkit; 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 * 418 * @param s the {@code ObjectInputStream} to read 419 * @throws ClassNotFoundException if the class of a serialized object could 420 * not be found 421 * @throws IOException if an I/O error occurs 422 */ readObject(ObjectInputStream s)423 private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { 424 s.defaultReadObject(); 425 if (when == 0) { 426 // Can't use getMostRecentEventTimeForSource because source is always null during deserialization 427 when = EventQueue.getMostRecentEventTime(); 428 } 429 } 430 431 /** 432 * Get the most recent event time in the {@code EventQueue} which the {@code source} 433 * belongs to. 434 * 435 * @param source the source of the event 436 * @exception IllegalArgumentException if source is null. 437 * @return most recent event time in the {@code EventQueue} 438 */ getMostRecentEventTimeForSource(Object source)439 private static long getMostRecentEventTimeForSource(Object source) { 440 if (source == null) { 441 // throw the IllegalArgumentException to conform to EventObject spec 442 throw new IllegalArgumentException("null source"); 443 } 444 AppContext appContext = SunToolkit.targetToAppContext(source); 445 EventQueue eventQueue = SunToolkit.getSystemEventQueueImplPP(appContext); 446 return AWTAccessor.getEventQueueAccessor().getMostRecentEventTime(eventQueue); 447 } 448 } 449