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