1 /*
   2  * Copyright (c) 1997, 2011, 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 sun.awt.im;
  27 
  28 import java.awt.AWTEvent;
  29 import java.awt.Component;
  30 import java.awt.GraphicsEnvironment;
  31 import java.awt.HeadlessException;
  32 import java.awt.Rectangle;
  33 import java.awt.Toolkit;
  34 import java.awt.Window;
  35 import java.awt.event.KeyEvent;
  36 import java.awt.event.InputMethodEvent;
  37 import java.awt.font.TextHitInfo;
  38 import java.awt.im.InputMethodRequests;
  39 import java.awt.im.spi.InputMethod;
  40 import java.security.AccessController;
  41 import java.text.AttributedCharacterIterator;
  42 import java.text.AttributedCharacterIterator.Attribute;
  43 import java.text.AttributedString;
  44 import java.text.CharacterIterator;
  45 import javax.swing.JFrame;
  46 import sun.awt.InputMethodSupport;
  47 import sun.security.action.GetPropertyAction;
  48 
  49 /**
  50  * The InputMethodContext class provides methods that input methods
  51  * can use to communicate with their client components.
  52  * It is a subclass of InputContext, which provides methods for use by
  53  * components.
  54  *
  55  * @author JavaSoft International
  56  */
  57 
  58 public class InputMethodContext
  59        extends sun.awt.im.InputContext
  60        implements java.awt.im.spi.InputMethodContext {
  61 
  62     private boolean dispatchingCommittedText;
  63 
  64     // Creation of the context's composition area handler is
  65     // delayed until we really need a composition area.
  66     private CompositionAreaHandler compositionAreaHandler;
  67     private Object compositionAreaHandlerLock = new Object();
  68 
  69     static private boolean belowTheSpotInputRequested;
  70     private boolean inputMethodSupportsBelowTheSpot;
  71 
  72     static {
  73         // check whether we should use below-the-spot input
  74         // get property from command line
  75         String inputStyle = AccessController.doPrivileged
  76                 (new GetPropertyAction("java.awt.im.style", null));
  77         // get property from awt.properties file
  78         if (inputStyle == null) {
  79             inputStyle = Toolkit.getProperty("java.awt.im.style", null);
  80         }
  81         belowTheSpotInputRequested = "below-the-spot".equals(inputStyle);
  82     }
  83 
  84     /**
  85      * Constructs an InputMethodContext.
  86      */
  87     public InputMethodContext() {
  88         super();
  89     }
  90 
  91     void setInputMethodSupportsBelowTheSpot(boolean supported) {
  92         inputMethodSupportsBelowTheSpot = supported;
  93     }
  94 
  95    boolean useBelowTheSpotInput() {
  96         return belowTheSpotInputRequested && inputMethodSupportsBelowTheSpot;
  97     }
  98 
  99     private boolean haveActiveClient() {
 100         Component client = getClientComponent();
 101         return client != null
 102                && client.getInputMethodRequests() != null;
 103     }
 104 
 105     // implements java.awt.im.spi.InputMethodContext.dispatchInputMethodEvent
 106     public void dispatchInputMethodEvent(int id,
 107                 AttributedCharacterIterator text, int committedCharacterCount,
 108                 TextHitInfo caret, TextHitInfo visiblePosition) {
 109         dispatchInputMethodEvent(id, text, committedCharacterCount, caret,
 110                 visiblePosition, -1, -1);
 111     }
 112 
 113     // implements java.awt.im.spi.InputMethodContext.dispatchInputMethodEvent
 114     public void dispatchInputMethodEvent(int id,
 115          AttributedCharacterIterator text, int committedCharacterCount,
 116          TextHitInfo caret, TextHitInfo visiblePosition, int start, int end) {
 117         // We need to record the client component as the source so
 118         // that we have correct information if we later have to break up this
 119         // event into key events.
 120         Component source;
 121 
 122         source = getClientComponent();
 123         if (source != null) {
 124             InputMethodEvent event = new InputMethodEvent(source,
 125                     id, text, committedCharacterCount, caret, visiblePosition,
 126                     start, end);
 127 
 128             if (haveActiveClient() && !useBelowTheSpotInput()) {
 129                 source.dispatchEvent(event);
 130             } else {
 131                 getCompositionAreaHandler(true).processInputMethodEvent(event);
 132             }
 133         }
 134     }
 135 
 136     /**
 137      * Dispatches committed text to a client component.
 138      * Called by composition window.
 139      *
 140      * @param client The component that the text should get dispatched to.
 141      * @param text The iterator providing access to the committed
 142      *        (and possible composed) text.
 143      * @param committedCharacterCount The number of committed characters in the text.
 144      */
 145     synchronized void dispatchCommittedText(Component client,
 146                  AttributedCharacterIterator text,
 147                  int committedCharacterCount) {
 148         // note that the client is not always the current client component -
 149         // some host input method adapters may dispatch input method events
 150         // through the Java event queue, and we may have switched clients while
 151         // the event was in the queue.
 152         if (committedCharacterCount == 0
 153                 || text.getEndIndex() <= text.getBeginIndex()) {
 154             return;
 155         }
 156         long time = System.currentTimeMillis();
 157         dispatchingCommittedText = true;
 158         try {
 159             InputMethodRequests req = client.getInputMethodRequests();
 160             if (req != null) {
 161                 // active client -> send text as InputMethodEvent
 162                 int beginIndex = text.getBeginIndex();
 163                 AttributedCharacterIterator toBeCommitted =
 164                     (new AttributedString(text, beginIndex, beginIndex + committedCharacterCount)).getIterator();
 165 
 166                 InputMethodEvent inputEvent = new InputMethodEvent(
 167                         client,
 168                         InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
 169                         toBeCommitted,
 170                         committedCharacterCount,
 171                         null, null);
 172 
 173                 client.dispatchEvent(inputEvent);
 174             } else {
 175                 // passive client -> send text as KeyEvents
 176                 char keyChar = text.first();
 177                 while (committedCharacterCount-- > 0 && keyChar != CharacterIterator.DONE) {
 178                     KeyEvent keyEvent = new KeyEvent(client, KeyEvent.KEY_TYPED,
 179                                                  time, 0, KeyEvent.VK_UNDEFINED, keyChar);
 180                     client.dispatchEvent(keyEvent);
 181                     keyChar = text.next();
 182                 }
 183             }
 184         } finally {
 185             dispatchingCommittedText = false;
 186         }
 187     }
 188 
 189     public void dispatchEvent(AWTEvent event) {
 190         // some host input method adapters may dispatch input method events
 191         // through the Java event queue. If the component that the event is
 192         // intended for isn't an active client, or if we're using below-the-spot
 193         // input, we need to dispatch this event
 194         // to the input window. Note that that component is not necessarily the
 195         // current client component, since we may have switched clients while
 196         // the event was in the queue.
 197         if (event instanceof InputMethodEvent) {
 198             if (((Component) event.getSource()).getInputMethodRequests() == null
 199                     || (useBelowTheSpotInput() && !dispatchingCommittedText)) {
 200                 getCompositionAreaHandler(true).processInputMethodEvent((InputMethodEvent) event);
 201             }
 202         } else {
 203             // make sure we don't dispatch our own key events back to the input method
 204             if (!dispatchingCommittedText) {
 205                 super.dispatchEvent(event);
 206             }
 207         }
 208     }
 209 
 210     /**
 211      * Gets this context's composition area handler, creating it if necessary.
 212      * If requested, it grabs the composition area for use by this context.
 213      * The composition area's text is not updated.
 214      */
 215     private CompositionAreaHandler getCompositionAreaHandler(boolean grab) {
 216         synchronized(compositionAreaHandlerLock) {
 217             if (compositionAreaHandler == null) {
 218                 compositionAreaHandler = new CompositionAreaHandler(this);
 219             }
 220             compositionAreaHandler.setClientComponent(getClientComponent());
 221             if (grab) {
 222                 compositionAreaHandler.grabCompositionArea(false);
 223             }
 224 
 225             return compositionAreaHandler;
 226         }
 227     }
 228 
 229     /**
 230      * Grabs the composition area for use by this context.
 231      * If doUpdate is true, updates the composition area with previously sent
 232      * composed text.
 233      */
 234     void grabCompositionArea(boolean doUpdate) {
 235         synchronized(compositionAreaHandlerLock) {
 236             if (compositionAreaHandler != null) {
 237                 compositionAreaHandler.grabCompositionArea(doUpdate);
 238             } else {
 239                 // if this context hasn't seen a need for a composition area yet,
 240                 // just close it without creating the machinery
 241                 CompositionAreaHandler.closeCompositionArea();
 242             }
 243         }
 244     }
 245 
 246     /**
 247      * Releases and closes the composition area if it is currently owned by
 248      * this context's composition area handler.
 249      */
 250     void releaseCompositionArea() {
 251         synchronized(compositionAreaHandlerLock) {
 252             if (compositionAreaHandler != null) {
 253                 compositionAreaHandler.releaseCompositionArea();
 254             }
 255         }
 256     }
 257 
 258     /**
 259      * Calls CompositionAreaHandler.isCompositionAreaVisible() to see
 260      * whether the composition area is visible or not.
 261      * Notice that this method is always called on the AWT event dispatch
 262      * thread.
 263      */
 264     boolean isCompositionAreaVisible() {
 265         if (compositionAreaHandler != null) {
 266             return compositionAreaHandler.isCompositionAreaVisible();
 267         }
 268 
 269         return false;
 270     }
 271     /**
 272      * Calls CompositionAreaHandler.setCompositionAreaVisible to
 273      * show or hide the composition area.
 274      * As isCompositionAreaVisible method, it is always called
 275      * on AWT event dispatch thread.
 276      */
 277     void setCompositionAreaVisible(boolean visible) {
 278         if (compositionAreaHandler != null) {
 279             compositionAreaHandler.setCompositionAreaVisible(visible);
 280         }
 281     }
 282 
 283     /**
 284      * Calls the current client component's implementation of getTextLocation.
 285      */
 286     public Rectangle getTextLocation(TextHitInfo offset) {
 287         return getReq().getTextLocation(offset);
 288     }
 289 
 290     /**
 291      * Calls the current client component's implementation of getLocationOffset.
 292      */
 293     public TextHitInfo getLocationOffset(int x, int y) {
 294         return getReq().getLocationOffset(x, y);
 295     }
 296 
 297     /**
 298      * Calls the current client component's implementation of getInsertPositionOffset.
 299      */
 300     public int getInsertPositionOffset() {
 301         return getReq().getInsertPositionOffset();
 302     }
 303 
 304     /**
 305      * Calls the current client component's implementation of getCommittedText.
 306      */
 307     public AttributedCharacterIterator getCommittedText(int beginIndex,
 308                                                        int endIndex,
 309                                                        Attribute[] attributes) {
 310         return getReq().getCommittedText(beginIndex, endIndex, attributes);
 311     }
 312 
 313     /**
 314      * Calls the current client component's implementation of getCommittedTextLength.
 315      */
 316     public int getCommittedTextLength() {
 317         return getReq().getCommittedTextLength();
 318     }
 319 
 320 
 321     /**
 322      * Calls the current client component's implementation of cancelLatestCommittedText.
 323      */
 324     public AttributedCharacterIterator cancelLatestCommittedText(Attribute[] attributes) {
 325         return getReq().cancelLatestCommittedText(attributes);
 326     }
 327 
 328     /**
 329      * Calls the current client component's implementation of getSelectedText.
 330      */
 331     public AttributedCharacterIterator getSelectedText(Attribute[] attributes) {
 332         return getReq().getSelectedText(attributes);
 333     }
 334 
 335     private InputMethodRequests getReq() {
 336         if (haveActiveClient() && !useBelowTheSpotInput()) {
 337             return getClientComponent().getInputMethodRequests();
 338         } else {
 339             return getCompositionAreaHandler(false);
 340         }
 341     }
 342 
 343     // implements java.awt.im.spi.InputMethodContext.createInputMethodWindow
 344     public Window createInputMethodWindow(String title, boolean attachToInputContext) {
 345         InputContext context = attachToInputContext ? this : null;
 346         return createInputMethodWindow(title, context, false);
 347     }
 348 
 349     // implements java.awt.im.spi.InputMethodContext.createInputMethodJFrame
 350     public JFrame createInputMethodJFrame(String title, boolean attachToInputContext) {
 351         InputContext context = attachToInputContext ? this : null;
 352         return (JFrame)createInputMethodWindow(title, context, true);
 353     }
 354 
 355     static Window createInputMethodWindow(String title, InputContext context, boolean isSwing) {
 356         if (GraphicsEnvironment.isHeadless()) {
 357             throw new HeadlessException();
 358         }
 359         if (isSwing) {
 360             return new InputMethodJFrame(title, context);
 361         } else {
 362             Toolkit toolkit = Toolkit.getDefaultToolkit();
 363             if (toolkit instanceof InputMethodSupport) {
 364                 return ((InputMethodSupport)toolkit).createInputMethodWindow(
 365                     title, context);
 366             }
 367         }
 368         throw new InternalError("Input methods must be supported");
 369     }
 370 
 371     /**
 372      * @see java.awt.im.spi.InputMethodContext#enableClientWindowNotification
 373      */
 374     public void enableClientWindowNotification(InputMethod inputMethod, boolean enable) {
 375         super.enableClientWindowNotification(inputMethod, enable);
 376     }
 377 
 378   /**
 379    * Disables or enables decorations for the composition window.
 380    */
 381    void setCompositionAreaUndecorated(boolean undecorated) {
 382         if (compositionAreaHandler != null) {
 383             compositionAreaHandler.setCompositionAreaUndecorated(undecorated);
 384         }
 385    }
 386 }