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 }