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         // We need to record the client component as the source so
 110         // that we have correct information if we later have to break up this
 111         // event into key events.
 112         Component source;
 113 
 114         source = getClientComponent();
 115         if (source != null) {
 116             InputMethodEvent event = new InputMethodEvent(source,
 117                     id, text, committedCharacterCount, caret, visiblePosition);
 118 
 119             if (haveActiveClient() && !useBelowTheSpotInput()) {
 120                 source.dispatchEvent(event);
 121             } else {
 122                 getCompositionAreaHandler(true).processInputMethodEvent(event);
 123             }
 124         }
 125     }
 126 
 127     /**
 128      * Dispatches committed text to a client component.
 129      * Called by composition window.
 130      *
 131      * @param client The component that the text should get dispatched to.
 132      * @param text The iterator providing access to the committed
 133      *        (and possible composed) text.
 134      * @param committedCharacterCount The number of committed characters in the text.
 135      */
 136     synchronized void dispatchCommittedText(Component client,
 137                  AttributedCharacterIterator text,
 138                  int committedCharacterCount) {
 139         // note that the client is not always the current client component -
 140         // some host input method adapters may dispatch input method events
 141         // through the Java event queue, and we may have switched clients while
 142         // the event was in the queue.
 143         if (committedCharacterCount == 0
 144                 || text.getEndIndex() <= text.getBeginIndex()) {
 145             return;
 146         }
 147         long time = System.currentTimeMillis();
 148         dispatchingCommittedText = true;
 149         try {
 150             InputMethodRequests req = client.getInputMethodRequests();
 151             if (req != null) {
 152                 // active client -> send text as InputMethodEvent
 153                 int beginIndex = text.getBeginIndex();
 154                 AttributedCharacterIterator toBeCommitted =
 155                     (new AttributedString(text, beginIndex, beginIndex + committedCharacterCount)).getIterator();
 156 
 157                 InputMethodEvent inputEvent = new InputMethodEvent(
 158                         client,
 159                         InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
 160                         toBeCommitted,
 161                         committedCharacterCount,
 162                         null, null);
 163 
 164                 client.dispatchEvent(inputEvent);
 165             } else {
 166                 // passive client -> send text as KeyEvents
 167                 char keyChar = text.first();
 168                 while (committedCharacterCount-- > 0 && keyChar != CharacterIterator.DONE) {
 169                     KeyEvent keyEvent = new KeyEvent(client, KeyEvent.KEY_TYPED,
 170                                                  time, 0, KeyEvent.VK_UNDEFINED, keyChar);
 171                     client.dispatchEvent(keyEvent);
 172                     keyChar = text.next();
 173                 }
 174             }
 175         } finally {
 176             dispatchingCommittedText = false;
 177         }
 178     }
 179 
 180     public void dispatchEvent(AWTEvent event) {
 181         // some host input method adapters may dispatch input method events
 182         // through the Java event queue. If the component that the event is
 183         // intended for isn't an active client, or if we're using below-the-spot
 184         // input, we need to dispatch this event
 185         // to the input window. Note that that component is not necessarily the
 186         // current client component, since we may have switched clients while
 187         // the event was in the queue.
 188         if (event instanceof InputMethodEvent) {
 189             if (((Component) event.getSource()).getInputMethodRequests() == null
 190                     || (useBelowTheSpotInput() && !dispatchingCommittedText)) {
 191                 getCompositionAreaHandler(true).processInputMethodEvent((InputMethodEvent) event);
 192             }
 193         } else {
 194             // make sure we don't dispatch our own key events back to the input method
 195             if (!dispatchingCommittedText) {
 196                 super.dispatchEvent(event);
 197             }
 198         }
 199     }
 200 
 201     /**
 202      * Gets this context's composition area handler, creating it if necessary.
 203      * If requested, it grabs the composition area for use by this context.
 204      * The composition area's text is not updated.
 205      */
 206     private CompositionAreaHandler getCompositionAreaHandler(boolean grab) {
 207         synchronized(compositionAreaHandlerLock) {
 208             if (compositionAreaHandler == null) {
 209                 compositionAreaHandler = new CompositionAreaHandler(this);
 210             }
 211             compositionAreaHandler.setClientComponent(getClientComponent());
 212             if (grab) {
 213                 compositionAreaHandler.grabCompositionArea(false);
 214             }
 215 
 216             return compositionAreaHandler;
 217         }
 218     }
 219 
 220     /**
 221      * Grabs the composition area for use by this context.
 222      * If doUpdate is true, updates the composition area with previously sent
 223      * composed text.
 224      */
 225     void grabCompositionArea(boolean doUpdate) {
 226         synchronized(compositionAreaHandlerLock) {
 227             if (compositionAreaHandler != null) {
 228                 compositionAreaHandler.grabCompositionArea(doUpdate);
 229             } else {
 230                 // if this context hasn't seen a need for a composition area yet,
 231                 // just close it without creating the machinery
 232                 CompositionAreaHandler.closeCompositionArea();
 233             }
 234         }
 235     }
 236 
 237     /**
 238      * Releases and closes the composition area if it is currently owned by
 239      * this context's composition area handler.
 240      */
 241     void releaseCompositionArea() {
 242         synchronized(compositionAreaHandlerLock) {
 243             if (compositionAreaHandler != null) {
 244                 compositionAreaHandler.releaseCompositionArea();
 245             }
 246         }
 247     }
 248 
 249     /**
 250      * Calls CompositionAreaHandler.isCompositionAreaVisible() to see
 251      * whether the composition area is visible or not.
 252      * Notice that this method is always called on the AWT event dispatch
 253      * thread.
 254      */
 255     boolean isCompositionAreaVisible() {
 256         if (compositionAreaHandler != null) {
 257             return compositionAreaHandler.isCompositionAreaVisible();
 258         }
 259 
 260         return false;
 261     }
 262     /**
 263      * Calls CompositionAreaHandler.setCompositionAreaVisible to
 264      * show or hide the composition area.
 265      * As isCompositionAreaVisible method, it is always called
 266      * on AWT event dispatch thread.
 267      */
 268     void setCompositionAreaVisible(boolean visible) {
 269         if (compositionAreaHandler != null) {
 270             compositionAreaHandler.setCompositionAreaVisible(visible);
 271         }
 272     }
 273 
 274     /**
 275      * Calls the current client component's implementation of getTextLocation.
 276      */
 277     public Rectangle getTextLocation(TextHitInfo offset) {
 278         return getReq().getTextLocation(offset);
 279     }
 280 
 281     /**
 282      * Calls the current client component's implementation of getLocationOffset.
 283      */
 284     public TextHitInfo getLocationOffset(int x, int y) {
 285         return getReq().getLocationOffset(x, y);
 286     }
 287 
 288     /**
 289      * Calls the current client component's implementation of getInsertPositionOffset.
 290      */
 291     public int getInsertPositionOffset() {
 292         return getReq().getInsertPositionOffset();
 293     }
 294 
 295     /**
 296      * Calls the current client component's implementation of getCommittedText.
 297      */
 298     public AttributedCharacterIterator getCommittedText(int beginIndex,
 299                                                        int endIndex,
 300                                                        Attribute[] attributes) {
 301         return getReq().getCommittedText(beginIndex, endIndex, attributes);
 302     }
 303 
 304     /**
 305      * Calls the current client component's implementation of getCommittedTextLength.
 306      */
 307     public int getCommittedTextLength() {
 308         return getReq().getCommittedTextLength();
 309     }
 310 
 311 
 312     /**
 313      * Calls the current client component's implementation of cancelLatestCommittedText.
 314      */
 315     public AttributedCharacterIterator cancelLatestCommittedText(Attribute[] attributes) {
 316         return getReq().cancelLatestCommittedText(attributes);
 317     }
 318 
 319     /**
 320      * Calls the current client component's implementation of getSelectedText.
 321      */
 322     public AttributedCharacterIterator getSelectedText(Attribute[] attributes) {
 323         return getReq().getSelectedText(attributes);
 324     }
 325 
 326     private InputMethodRequests getReq() {
 327         if (haveActiveClient() && !useBelowTheSpotInput()) {
 328             return getClientComponent().getInputMethodRequests();
 329         } else {
 330             return getCompositionAreaHandler(false);
 331         }
 332     }
 333 
 334     // implements java.awt.im.spi.InputMethodContext.createInputMethodWindow
 335     public Window createInputMethodWindow(String title, boolean attachToInputContext) {
 336         InputContext context = attachToInputContext ? this : null;
 337         return createInputMethodWindow(title, context, false);
 338     }
 339 
 340     // implements java.awt.im.spi.InputMethodContext.createInputMethodJFrame
 341     public JFrame createInputMethodJFrame(String title, boolean attachToInputContext) {
 342         InputContext context = attachToInputContext ? this : null;
 343         return (JFrame)createInputMethodWindow(title, context, true);
 344     }
 345 
 346     static Window createInputMethodWindow(String title, InputContext context, boolean isSwing) {
 347         if (GraphicsEnvironment.isHeadless()) {
 348             throw new HeadlessException();
 349         }
 350         if (isSwing) {
 351             return new InputMethodJFrame(title, context);
 352         } else {
 353             Toolkit toolkit = Toolkit.getDefaultToolkit();
 354             if (toolkit instanceof InputMethodSupport) {
 355                 return ((InputMethodSupport)toolkit).createInputMethodWindow(
 356                     title, context);
 357             }
 358         }
 359         throw new InternalError("Input methods must be supported");
 360     }
 361 
 362     /**
 363      * @see java.awt.im.spi.InputMethodContext#enableClientWindowNotification
 364      */
 365     public void enableClientWindowNotification(InputMethod inputMethod, boolean enable) {
 366         super.enableClientWindowNotification(inputMethod, enable);
 367     }
 368 
 369   /**
 370    * Disables or enables decorations for the composition window.
 371    */
 372    void setCompositionAreaUndecorated(boolean undecorated) {
 373         if (compositionAreaHandler != null) {
 374             compositionAreaHandler.setCompositionAreaUndecorated(undecorated);
 375         }
 376    }
 377 }