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 }