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 }