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 }