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 
  30 import java.awt.AWTEvent;
  31 import java.awt.Component;
  32 import java.awt.EventQueue;
  33 import java.awt.font.TextHitInfo;
  34 import java.io.IOException;
  35 import java.io.ObjectInputStream;
  36 import java.text.AttributedCharacterIterator;
  37 import java.text.CharacterIterator;
  38 import java.lang.annotation.Native;
  39 
  40 /**
  41  * Input method events contain information about text that is being
  42  * composed using an input method. Whenever the text changes, the
  43  * input method sends an event. If the text component that's currently
  44  * using the input method is an active client, the event is dispatched
  45  * to that component. Otherwise, it is dispatched to a separate
  46  * composition window.
  47  *
  48  * <p>
  49  * The text included with the input method event consists of two parts:
  50  * committed text and composed text. Either part may be empty. The two
  51  * parts together replace any uncommitted composed text sent in previous events,
  52  * or the currently selected committed text.
  53  * Committed text should be integrated into the text component's persistent
  54  * data, it will not be sent again. Composed text may be sent repeatedly,
  55  * with changes to reflect the user's editing operations. Committed text
  56  * always precedes composed text.
  57  *
  58  * @author JavaSoft Asia/Pacific
  59  * @since 1.2
  60  */
  61 public class InputMethodEvent extends AWTEvent {
  62 
  63     /**
  64      * Serial Version ID.
  65      */
  66     private static final long serialVersionUID = 4727190874778922661L;
  67 
  68     /**
  69      * Marks the first integer id for the range of input method event ids.
  70      */
  71     @Native public static final int INPUT_METHOD_FIRST = 1100;
  72 
  73     /**
  74      * The event type indicating changed input method text. This event is
  75      * generated by input methods while processing input.
  76      */
  77     @Native public static final int INPUT_METHOD_TEXT_CHANGED = INPUT_METHOD_FIRST;
  78 
  79     /**
  80      * The event type indicating a changed insertion point in input method text.
  81      * This event is
  82      * generated by input methods while processing input if only the caret changed.
  83      */
  84     @Native public static final int CARET_POSITION_CHANGED = INPUT_METHOD_FIRST + 1;
  85 
  86     /**
  87      * Marks the last integer id for the range of input method event ids.
  88      */
  89     @Native public static final int INPUT_METHOD_LAST = INPUT_METHOD_FIRST + 1;
  90 
  91     /**
  92      * The time stamp that indicates when the event was created.
  93      *
  94      * @serial
  95      * @see #getWhen
  96      * @since 1.4
  97      */
  98     long when;
  99 
 100     // Text object
 101     private transient AttributedCharacterIterator text;
 102     private transient int committedCharacterCount;
 103     private transient TextHitInfo caret;
 104     private transient TextHitInfo visiblePosition;
 105 
 106     /**
 107      * Constructs an <code>InputMethodEvent</code> with the specified
 108      * source component, type, time, text, caret, and visiblePosition.
 109      * <p>
 110      * The offsets of caret and visiblePosition are relative to the current
 111      * composed text; that is, the composed text within <code>text</code>
 112      * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
 113      * the composed text within the <code>text</code> of the
 114      * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
 115      * <p>Note that passing in an invalid <code>id</code> results in
 116      * unspecified behavior. This method throws an
 117      * <code>IllegalArgumentException</code> if <code>source</code>
 118      * is <code>null</code>.
 119      *
 120      * @param source the object where the event originated
 121      * @param id the event type
 122      * @param when a long integer that specifies the time the event occurred
 123      * @param text the combined committed and composed text,
 124      *      committed text first; must be <code>null</code>
 125      *      when the event type is <code>CARET_POSITION_CHANGED</code>;
 126      *      may be <code>null</code> for
 127      *      <code>INPUT_METHOD_TEXT_CHANGED</code> if there's no
 128      *      committed or composed text
 129      * @param committedCharacterCount the number of committed
 130      *      characters in the text
 131      * @param caret the caret (a.k.a. insertion point);
 132      *      <code>null</code> if there's no caret within current
 133      *      composed text
 134      * @param visiblePosition the position that's most important
 135      *      to be visible; <code>null</code> if there's no
 136      *      recommendation for a visible position within current
 137      *      composed text
 138      * @throws IllegalArgumentException if <code>id</code> is not
 139      *      in the range
 140      *      <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>;
 141      *      or if id is <code>CARET_POSITION_CHANGED</code> and
 142      *      <code>text</code> is not <code>null</code>;
 143      *      or if <code>committedCharacterCount</code> is not in the range
 144      *      <code>0</code>..<code>(text.getEndIndex() - text.getBeginIndex())</code>
 145      * @throws IllegalArgumentException if <code>source</code> is null
 146      *
 147      * @since 1.4
 148      */
 149     public InputMethodEvent(Component source, int id, long when,
 150             AttributedCharacterIterator text, int committedCharacterCount,
 151             TextHitInfo caret, TextHitInfo visiblePosition) {
 152         super(source, id);
 153         if (id < INPUT_METHOD_FIRST || id > INPUT_METHOD_LAST) {
 154             throw new IllegalArgumentException("id outside of valid range");
 155         }
 156 
 157         if (id == CARET_POSITION_CHANGED && text != null) {
 158             throw new IllegalArgumentException("text must be null for CARET_POSITION_CHANGED");
 159         }
 160 
 161         this.when = when;
 162         this.text = text;
 163         int textLength = 0;
 164         if (text != null) {
 165             textLength = text.getEndIndex() - text.getBeginIndex();
 166         }
 167 
 168         if (committedCharacterCount < 0 || committedCharacterCount > textLength) {
 169             throw new IllegalArgumentException("committedCharacterCount outside of valid range");
 170         }
 171         this.committedCharacterCount = committedCharacterCount;
 172 
 173         this.caret = caret;
 174         this.visiblePosition = visiblePosition;
 175    }
 176 
 177     /**
 178      * Constructs an <code>InputMethodEvent</code> with the specified
 179      * source component, type, text, caret, and visiblePosition.
 180      * <p>
 181      * The offsets of caret and visiblePosition are relative to the current
 182      * composed text; that is, the composed text within <code>text</code>
 183      * if this is an <code>INPUT_METHOD_TEXT_CHANGED</code> event,
 184      * the composed text within the <code>text</code> of the
 185      * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
 186      * The time stamp for this event is initialized by invoking
 187      * {@link java.awt.EventQueue#getMostRecentEventTime()}.
 188      * <p>Note that passing in an invalid <code>id</code> results in
 189      * unspecified behavior. This method throws an
 190      * <code>IllegalArgumentException</code> if <code>source</code>
 191      * is <code>null</code>.
 192      *
 193      * @param source the object where the event originated
 194      * @param id the event type
 195      * @param text the combined committed and composed text,
 196      *      committed text first; must be <code>null</code>
 197      *      when the event type is <code>CARET_POSITION_CHANGED</code>;
 198      *      may be <code>null</code> for
 199      *      <code>INPUT_METHOD_TEXT_CHANGED</code> if there's no
 200      *      committed or composed text
 201      * @param committedCharacterCount the number of committed
 202      *      characters in the text
 203      * @param caret the caret (a.k.a. insertion point);
 204      *      <code>null</code> if there's no caret within current
 205      *      composed text
 206      * @param visiblePosition the position that's most important
 207      *      to be visible; <code>null</code> if there's no
 208      *      recommendation for a visible position within current
 209      *      composed text
 210      * @throws IllegalArgumentException if <code>id</code> is not
 211      *      in the range
 212      *      <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>;
 213      *      or if id is <code>CARET_POSITION_CHANGED</code> and
 214      *      <code>text</code> is not <code>null</code>;
 215      *      or if <code>committedCharacterCount</code> is not in the range
 216      *      <code>0</code>..<code>(text.getEndIndex() - text.getBeginIndex())</code>
 217      * @throws IllegalArgumentException if <code>source</code> is null
 218      */
 219     public InputMethodEvent(Component source, int id,
 220             AttributedCharacterIterator text, int committedCharacterCount,
 221             TextHitInfo caret, TextHitInfo visiblePosition) {
 222         this(source, id,
 223                 AWTAccessor.getEventQueueAccessor()
 224                         .getMostRecentEventTimeForTarget(source),
 225                 text, committedCharacterCount,
 226                 caret, visiblePosition);
 227     }
 228 
 229     /**
 230      * Constructs an <code>InputMethodEvent</code> with the
 231      * specified source component, type, caret, and visiblePosition.
 232      * The text is set to <code>null</code>,
 233      * <code>committedCharacterCount</code> to 0.
 234      * <p>
 235      * The offsets of <code>caret</code> and <code>visiblePosition</code>
 236      * are relative to the current composed text; that is,
 237      * the composed text within the <code>text</code> of the
 238      * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event if the
 239      * event being constructed as a <code>CARET_POSITION_CHANGED</code> event.
 240      * For an <code>INPUT_METHOD_TEXT_CHANGED</code> event without text,
 241      * <code>caret</code> and <code>visiblePosition</code> must be
 242      * <code>null</code>.
 243      * The time stamp for this event is initialized by invoking
 244      * {@link java.awt.EventQueue#getMostRecentEventTime()}.
 245      * <p>Note that passing in an invalid <code>id</code> results in
 246      * unspecified behavior. This method throws an
 247      * <code>IllegalArgumentException</code> if <code>source</code>
 248      * is <code>null</code>.
 249      *
 250      * @param source the object where the event originated
 251      * @param id the event type
 252      * @param caret the caret (a.k.a. insertion point);
 253      *      <code>null</code> if there's no caret within current
 254      *      composed text
 255      * @param visiblePosition the position that's most important
 256      *      to be visible; <code>null</code> if there's no
 257      *      recommendation for a visible position within current
 258      *      composed text
 259      * @throws IllegalArgumentException if <code>id</code> is not
 260      *      in the range
 261      *      <code>INPUT_METHOD_FIRST</code>..<code>INPUT_METHOD_LAST</code>
 262      * @throws IllegalArgumentException if <code>source</code> is null
 263      */
 264     public InputMethodEvent(Component source, int id, TextHitInfo caret,
 265             TextHitInfo visiblePosition) {
 266         this(source, id,
 267                 AWTAccessor.getEventQueueAccessor()
 268                         .getMostRecentEventTimeForTarget(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</code> 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      */
 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      */
 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</code> event,
 299      * the composed text within getText() of the
 300      * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> event otherwise.
 301      *
 302      * @return the caret (a.k.a. insertion point).
 303      * Null if there's no caret within current composed text.
 304      */
 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</code> event,
 315      * the composed text within getText() of the
 316      * preceding <code>INPUT_METHOD_TEXT_CHANGED</code> 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      */
 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      */
 329     public void consume() {
 330         consumed = true;
 331     }
 332 
 333     /**
 334      * Returns whether or not this event has been consumed.
 335      * @see #consume
 336      */
 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      */
 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      */
 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</code> 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     private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
 419         s.defaultReadObject();
 420         if (when == 0) {
 421             when = AWTAccessor.getEventQueueAccessor()
 422                     .getMostRecentEventTimeForTarget(this.source);
 423         }
 424     }
 425 }