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