1 /* 2 * Copyright (c) 1997, 2019, 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.AWTKeyStroke; 30 import java.awt.Component; 31 import java.awt.EventQueue; 32 import java.awt.Frame; 33 import java.awt.Rectangle; 34 import java.awt.Toolkit; 35 import java.awt.Window; 36 import java.awt.event.ComponentEvent; 37 import java.awt.event.ComponentListener; 38 import java.awt.event.FocusEvent; 39 import java.awt.event.InputEvent; 40 import java.awt.event.InputMethodEvent; 41 import java.awt.event.KeyEvent; 42 import java.awt.event.WindowEvent; 43 import java.awt.event.WindowListener; 44 import java.awt.im.InputMethodRequests; 45 import java.awt.im.spi.InputMethod; 46 import java.lang.Character.Subset; 47 import java.security.AccessController; 48 import java.security.PrivilegedAction; 49 import java.text.MessageFormat; 50 import java.util.HashMap; 51 import java.util.Iterator; 52 import java.util.Locale; 53 import java.util.prefs.BackingStoreException; 54 import java.util.prefs.Preferences; 55 import sun.util.logging.PlatformLogger; 56 import sun.awt.SunToolkit; 57 58 /** 59 * This InputContext class contains parts of the implementation of 60 * java.text.im.InputContext. These parts have been moved 61 * here to avoid exposing protected members that are needed by the 62 * subclass InputMethodContext. 63 * 64 * @see java.awt.im.InputContext 65 * @author JavaSoft Asia/Pacific 66 */ 67 68 public class InputContext extends java.awt.im.InputContext 69 implements ComponentListener, WindowListener { 70 private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.im.InputContext"); 71 // The current input method is represented by two objects: 72 // a locator is used to keep information about the selected 73 // input method and locale until we actually need a real input 74 // method; only then the input method itself is created. 75 // Once there is an input method, the input method's locale 76 // takes precedence over locale information in the locator. 77 private InputMethodLocator inputMethodLocator; 78 private InputMethod inputMethod; 79 private boolean inputMethodCreationFailed; 80 81 // holding bin for previously used input method instances, but not the current one 82 private HashMap<InputMethodLocator, InputMethod> usedInputMethods; 83 84 // the current client component is kept until the user focusses on a different 85 // client component served by the same input context. When that happens, we call 86 // endComposition so that text doesn't jump from one component to another. 87 private Component currentClientComponent; 88 private Component awtFocussedComponent; 89 private boolean isInputMethodActive; 90 private Subset[] characterSubsets = null; 91 92 // true if composition area has been set to invisible when focus was lost 93 private boolean compositionAreaHidden = false; 94 95 // The input context for whose input method we may have to call hideWindows 96 private static InputContext inputMethodWindowContext; 97 98 // Previously active input method to decide whether we need to call 99 // InputMethodAdapter.stopListening() on activateInputMethod() 100 private static InputMethod previousInputMethod = null; 101 102 // true if the current input method requires client window change notification 103 private boolean clientWindowNotificationEnabled = false; 104 // client window to which this input context is listening 105 private Window clientWindowListened; 106 // cache location notification 107 private Rectangle clientWindowLocation = null; 108 // holding the state of clientWindowNotificationEnabled of only non-current input methods 109 private HashMap<InputMethod, Boolean> perInputMethodState; 110 111 // Input Method selection hot key stuff 112 private static AWTKeyStroke inputMethodSelectionKey; 113 private static boolean inputMethodSelectionKeyInitialized = false; 114 private static final String inputMethodSelectionKeyPath = "/java/awt/im/selectionKey"; 115 private static final String inputMethodSelectionKeyCodeName = "keyCode"; 116 private static final String inputMethodSelectionKeyModifiersName = "modifiers"; 117 118 /** 119 * Constructs an InputContext. 120 */ 121 protected InputContext() { 122 InputMethodManager imm = InputMethodManager.getInstance(); 123 synchronized (InputContext.class) { 124 if (!inputMethodSelectionKeyInitialized) { 125 inputMethodSelectionKeyInitialized = true; 126 if (imm.hasMultipleInputMethods()) { 127 initializeInputMethodSelectionKey(); 128 } 129 } 130 } 131 selectInputMethod(imm.getDefaultKeyboardLocale()); 132 } 133 134 /** 135 * @see java.awt.im.InputContext#selectInputMethod 136 * @exception NullPointerException when the locale is null. 137 */ 138 public synchronized boolean selectInputMethod(Locale locale) { 139 if (locale == null) { 140 throw new NullPointerException(); 141 } 142 143 // see whether the current input method supports the locale 144 if (inputMethod != null) { 145 if (inputMethod.setLocale(locale)) { 146 return true; 147 } 148 } else if (inputMethodLocator != null) { 149 // This is not 100% correct, since the input method 150 // may support the locale without advertising it. 151 // But before we try instantiations and setLocale, 152 // we look for an input method that's more confident. 153 if (inputMethodLocator.isLocaleAvailable(locale)) { 154 inputMethodLocator = inputMethodLocator.deriveLocator(locale); 155 return true; 156 } 157 } 158 159 // see whether there's some other input method that supports the locale 160 InputMethodLocator newLocator = InputMethodManager.getInstance().findInputMethod(locale); 161 if (newLocator != null) { 162 changeInputMethod(newLocator); 163 return true; 164 } 165 166 // make one last desperate effort with the current input method 167 // ??? is this good? This is pretty high cost for something that's likely to fail. 168 if (inputMethod == null && inputMethodLocator != null) { 169 inputMethod = getInputMethod(); 170 if (inputMethod != null) { 171 return inputMethod.setLocale(locale); 172 } 173 } 174 return false; 175 } 176 177 /** 178 * @see java.awt.im.InputContext#getLocale 179 */ 180 public Locale getLocale() { 181 if (inputMethod != null) { 182 return inputMethod.getLocale(); 183 } else if (inputMethodLocator != null) { 184 return inputMethodLocator.getLocale(); 185 } else { 186 return null; 187 } 188 } 189 190 /** 191 * @see java.awt.im.InputContext#setCharacterSubsets 192 */ 193 public void setCharacterSubsets(Subset[] subsets) { 194 if (subsets == null) { 195 characterSubsets = null; 196 } else { 197 characterSubsets = new Subset[subsets.length]; 198 System.arraycopy(subsets, 0, 199 characterSubsets, 0, characterSubsets.length); 200 } 201 if (inputMethod != null) { 202 inputMethod.setCharacterSubsets(subsets); 203 } 204 } 205 206 /** 207 * @see java.awt.im.InputContext#reconvert 208 * @since 1.3 209 * @exception UnsupportedOperationException when input method is null 210 */ 211 public synchronized void reconvert() { 212 InputMethod inputMethod = getInputMethod(); 213 if (inputMethod == null) { 214 throw new UnsupportedOperationException(); 215 } 216 inputMethod.reconvert(); 217 } 218 219 /** 220 * @see java.awt.im.InputContext#dispatchEvent 221 */ 222 @SuppressWarnings("fallthrough") 223 public void dispatchEvent(AWTEvent event) { 224 225 if (event instanceof InputMethodEvent) { 226 return; 227 } 228 229 // Ignore focus events that relate to the InputMethodWindow of this context. 230 // This is a workaround. Should be removed after 4452384 is fixed. 231 if (event instanceof FocusEvent) { 232 Component opposite = ((FocusEvent)event).getOppositeComponent(); 233 if ((opposite != null) && 234 (getComponentWindow(opposite) instanceof InputMethodWindow) && 235 (opposite.getInputContext() == this)) { 236 return; 237 } 238 } 239 240 InputMethod inputMethod = getInputMethod(); 241 int id = event.getID(); 242 243 switch (id) { 244 case FocusEvent.FOCUS_GAINED: 245 focusGained((Component) event.getSource()); 246 break; 247 248 case FocusEvent.FOCUS_LOST: 249 focusLost((Component) event.getSource(), ((FocusEvent) event).isTemporary()); 250 break; 251 252 case KeyEvent.KEY_PRESSED: 253 if (checkInputMethodSelectionKey((KeyEvent)event)) { 254 // pop up the input method selection menu 255 InputMethodManager.getInstance().notifyChangeRequestByHotKey((Component)event.getSource()); 256 break; 257 } 258 259 // fall through 260 261 default: 262 if ((inputMethod != null) && (event instanceof InputEvent)) { 263 inputMethod.dispatchEvent(event); 264 } 265 } 266 } 267 268 /** 269 * Handles focus gained events for any component that's using 270 * this input context. 271 * These events are generated by AWT when the keyboard focus 272 * moves to a component. 273 * Besides actual client components, the source components 274 * may also be the composition area or any component in an 275 * input method window. 276 * <p> 277 * When handling the focus event for a client component, this 278 * method checks whether the input context was previously 279 * active for a different client component, and if so, calls 280 * endComposition for the previous client component. 281 * 282 * @param source the component gaining the focus 283 */ 284 private void focusGained(Component source) { 285 286 /* 287 * NOTE: When a Container is removing its Component which 288 * invokes this.removeNotify(), the Container has the global 289 * Component lock. It is possible to happen that an 290 * application thread is calling this.removeNotify() while an 291 * AWT event queue thread is dispatching a focus event via 292 * this.dispatchEvent(). If an input method uses AWT 293 * components (e.g., IIIMP status window), it causes deadlock, 294 * for example, Component.show()/hide() in this situation 295 * because hide/show tried to obtain the lock. Therefore, 296 * it's necessary to obtain the global Component lock before 297 * activating or deactivating an input method. 298 */ 299 synchronized (source.getTreeLock()) { 300 synchronized (this) { 301 if ("sun.awt.im.CompositionArea".equals(source.getClass().getName())) { 302 // no special handling for this one 303 } else if (getComponentWindow(source) instanceof InputMethodWindow) { 304 // no special handling for this one either 305 } else { 306 if (!source.isDisplayable()) { 307 // Component is being disposed 308 return; 309 } 310 311 // Focus went to a real client component. 312 // Check whether we're switching between client components 313 // that share an input context. We can't do that earlier 314 // than here because we don't want to end composition 315 // until we really know we're switching to a different component 316 if (inputMethod != null) { 317 if (currentClientComponent != null && currentClientComponent != source) { 318 if (!isInputMethodActive) { 319 activateInputMethod(false); 320 } 321 endComposition(); 322 deactivateInputMethod(false); 323 } 324 } 325 326 currentClientComponent = source; 327 } 328 329 awtFocussedComponent = source; 330 if (inputMethod instanceof InputMethodAdapter) { 331 ((InputMethodAdapter) inputMethod).setAWTFocussedComponent(source); 332 } 333 334 // it's possible that the input method is still active because 335 // we suppressed a deactivate cause by an input method window 336 // coming up 337 if (!isInputMethodActive) { 338 activateInputMethod(true); 339 } 340 341 342 // If the client component is an active client with the below-the-spot 343 // input style, then make the composition window undecorated without a title bar. 344 InputMethodContext inputContext = ((InputMethodContext)this); 345 if (!inputContext.isCompositionAreaVisible()) { 346 InputMethodRequests req = source.getInputMethodRequests(); 347 if (req != null && inputContext.useBelowTheSpotInput()) { 348 inputContext.setCompositionAreaUndecorated(true); 349 } else { 350 inputContext.setCompositionAreaUndecorated(false); 351 } 352 } 353 // restores the composition area if it was set to invisible 354 // when focus got lost 355 if (compositionAreaHidden == true) { 356 ((InputMethodContext)this).setCompositionAreaVisible(true); 357 compositionAreaHidden = false; 358 } 359 } 360 } 361 } 362 363 /** 364 * Activates the current input method of this input context, and grabs 365 * the composition area for use by this input context. 366 * If updateCompositionArea is true, the text in the composition area 367 * is updated (set to false if the text is going to change immediately 368 * to avoid screen flicker). 369 */ 370 private void activateInputMethod(boolean updateCompositionArea) { 371 // call hideWindows() if this input context uses a different 372 // input method than the previously activated one 373 if (inputMethodWindowContext != null && inputMethodWindowContext != this && 374 inputMethodWindowContext.inputMethodLocator != null && 375 !inputMethodWindowContext.inputMethodLocator.sameInputMethod(inputMethodLocator) && 376 inputMethodWindowContext.inputMethod != null) { 377 inputMethodWindowContext.inputMethod.hideWindows(); 378 } 379 inputMethodWindowContext = this; 380 381 if (inputMethod != null) { 382 if (previousInputMethod != inputMethod && 383 previousInputMethod instanceof InputMethodAdapter) { 384 // let the host adapter pass through the input events for the 385 // new input method 386 ((InputMethodAdapter) previousInputMethod).stopListening(); 387 } 388 previousInputMethod = null; 389 390 if (log.isLoggable(PlatformLogger.Level.FINE)) { 391 log.fine("Current client component " + currentClientComponent); 392 } 393 if (inputMethod instanceof InputMethodAdapter) { 394 ((InputMethodAdapter) inputMethod).setClientComponent(currentClientComponent); 395 } 396 inputMethod.activate(); 397 isInputMethodActive = true; 398 399 if (perInputMethodState != null) { 400 Boolean state = perInputMethodState.remove(inputMethod); 401 if (state != null) { 402 clientWindowNotificationEnabled = state.booleanValue(); 403 } 404 } 405 if (clientWindowNotificationEnabled) { 406 if (!addedClientWindowListeners()) { 407 addClientWindowListeners(); 408 } 409 synchronized(this) { 410 if (clientWindowListened != null) { 411 notifyClientWindowChange(clientWindowListened); 412 } 413 } 414 } else { 415 if (addedClientWindowListeners()) { 416 removeClientWindowListeners(); 417 } 418 } 419 } 420 InputMethodManager.getInstance().setInputContext(this); 421 422 ((InputMethodContext) this).grabCompositionArea(updateCompositionArea); 423 } 424 425 static Window getComponentWindow(Component component) { 426 while (true) { 427 if (component == null) { 428 return null; 429 } else if (component instanceof Window) { 430 return (Window) component; 431 } else { 432 component = component.getParent(); 433 } 434 } 435 } 436 437 /** 438 * Handles focus lost events for any component that's using 439 * this input context. 440 * These events are generated by AWT when the keyboard focus 441 * moves away from a component. 442 * Besides actual client components, the source components 443 * may also be the composition area or any component in an 444 * input method window. 445 * 446 * @param source the component losing the focus 447 * @isTemporary whether the focus change is temporary 448 */ 449 private void focusLost(Component source, boolean isTemporary) { 450 451 // see the note on synchronization in focusGained 452 synchronized (source.getTreeLock()) { 453 synchronized (this) { 454 455 // We need to suppress deactivation if removeNotify has been called earlier. 456 // This is indicated by isInputMethodActive == false. 457 if (isInputMethodActive) { 458 deactivateInputMethod(isTemporary); 459 } 460 461 awtFocussedComponent = null; 462 if (inputMethod instanceof InputMethodAdapter) { 463 ((InputMethodAdapter) inputMethod).setAWTFocussedComponent(null); 464 } 465 466 // hides the composition area if currently it is visible 467 InputMethodContext inputContext = ((InputMethodContext)this); 468 if (inputContext.isCompositionAreaVisible()) { 469 inputContext.setCompositionAreaVisible(false); 470 compositionAreaHidden = true; 471 } 472 } 473 } 474 } 475 476 /** 477 * Checks the key event is the input method selection key or not. 478 */ 479 private boolean checkInputMethodSelectionKey(KeyEvent event) { 480 if (inputMethodSelectionKey != null) { 481 AWTKeyStroke aKeyStroke = AWTKeyStroke.getAWTKeyStrokeForEvent(event); 482 return inputMethodSelectionKey.equals(aKeyStroke); 483 } else { 484 return false; 485 } 486 } 487 488 private void deactivateInputMethod(boolean isTemporary) { 489 InputMethodManager.getInstance().setInputContext(null); 490 if (inputMethod != null) { 491 isInputMethodActive = false; 492 inputMethod.deactivate(isTemporary); 493 previousInputMethod = inputMethod; 494 } 495 } 496 497 /** 498 * Switches from the current input method to the one described by newLocator. 499 * The current input method, if any, is asked to end composition, deactivated, 500 * and saved for future use. The newLocator is made the current locator. If 501 * the input context is active, an input method instance for the new locator 502 * is obtained; otherwise this is deferred until required. 503 */ 504 synchronized void changeInputMethod(InputMethodLocator newLocator) { 505 // If we don't have a locator yet, this must be a new input context. 506 // If we created a new input method here, we might get into an 507 // infinite loop: create input method -> create some input method window -> 508 // create new input context -> add input context to input method manager's context list -> 509 // call changeInputMethod on it. 510 // So, just record the locator. dispatchEvent will create the input method when needed. 511 if (inputMethodLocator == null) { 512 inputMethodLocator = newLocator; 513 inputMethodCreationFailed = false; 514 return; 515 } 516 517 // If the same input method is specified, just keep it. 518 // Adjust the locale if necessary. 519 if (inputMethodLocator.sameInputMethod(newLocator)) { 520 Locale newLocale = newLocator.getLocale(); 521 if (newLocale != null && inputMethodLocator.getLocale() != newLocale) { 522 if (inputMethod != null) { 523 inputMethod.setLocale(newLocale); 524 } 525 inputMethodLocator = newLocator; 526 } 527 return; 528 } 529 530 // Switch out the old input method 531 Locale savedLocale = inputMethodLocator.getLocale(); 532 boolean wasInputMethodActive = isInputMethodActive; 533 boolean wasCompositionEnabledSupported = false; 534 boolean wasCompositionEnabled = false; 535 if (inputMethod != null) { 536 try { 537 wasCompositionEnabled = inputMethod.isCompositionEnabled(); 538 wasCompositionEnabledSupported = true; 539 } catch (UnsupportedOperationException e) { } 540 541 if (currentClientComponent != null) { 542 if (!isInputMethodActive) { 543 activateInputMethod(false); 544 } 545 endComposition(); 546 deactivateInputMethod(false); 547 if (inputMethod instanceof InputMethodAdapter) { 548 ((InputMethodAdapter) inputMethod).setClientComponent(null); 549 } 550 if (null == currentClientComponent.getInputMethodRequests()) 551 wasCompositionEnabledSupported = false; 552 } 553 savedLocale = inputMethod.getLocale(); 554 555 // keep the input method instance around for future use 556 if (usedInputMethods == null) { 557 usedInputMethods = new HashMap<>(5); 558 } 559 if (perInputMethodState == null) { 560 perInputMethodState = new HashMap<>(5); 561 } 562 usedInputMethods.put(inputMethodLocator.deriveLocator(null), inputMethod); 563 perInputMethodState.put(inputMethod, 564 Boolean.valueOf(clientWindowNotificationEnabled)); 565 enableClientWindowNotification(inputMethod, false); 566 if (this == inputMethodWindowContext) { 567 inputMethod.hideWindows(); 568 inputMethod.removeNotify(); 569 inputMethodWindowContext = null; 570 } 571 inputMethodLocator = null; 572 inputMethod = null; 573 inputMethodCreationFailed = false; 574 } 575 576 // Switch in the new input method 577 if (newLocator.getLocale() == null && savedLocale != null && 578 newLocator.isLocaleAvailable(savedLocale)) { 579 newLocator = newLocator.deriveLocator(savedLocale); 580 } 581 inputMethodLocator = newLocator; 582 inputMethodCreationFailed = false; 583 584 // activate the new input method if the old one was active 585 if (wasInputMethodActive) { 586 inputMethod = getInputMethodInstance(); 587 if (inputMethod instanceof InputMethodAdapter) { 588 ((InputMethodAdapter) inputMethod).setAWTFocussedComponent(awtFocussedComponent); 589 } 590 activateInputMethod(true); 591 } 592 593 // enable/disable composition if the old one supports querying enable/disable 594 if (wasCompositionEnabledSupported) { 595 inputMethod = getInputMethod(); 596 if (inputMethod != null) { 597 try { 598 inputMethod.setCompositionEnabled(wasCompositionEnabled); 599 } catch (UnsupportedOperationException e) { } 600 } 601 } 602 } 603 604 /** 605 * Returns the client component. 606 */ 607 Component getClientComponent() { 608 return currentClientComponent; 609 } 610 611 /** 612 * @see java.awt.im.InputContext#removeNotify 613 * @exception NullPointerException when the component is null. 614 */ 615 public synchronized void removeNotify(Component component) { 616 if (component == null) { 617 throw new NullPointerException(); 618 } 619 620 if (inputMethod == null) { 621 if (component == currentClientComponent) { 622 currentClientComponent = null; 623 } 624 return; 625 } 626 627 // We may or may not get a FOCUS_LOST event for this component, 628 // so do the deactivation stuff here too. 629 if (component == awtFocussedComponent) { 630 focusLost(component, false); 631 } 632 633 if (component == currentClientComponent) { 634 if (isInputMethodActive) { 635 // component wasn't the one that had the focus 636 deactivateInputMethod(false); 637 } 638 inputMethod.removeNotify(); 639 if (clientWindowNotificationEnabled && addedClientWindowListeners()) { 640 removeClientWindowListeners(); 641 } 642 currentClientComponent = null; 643 if (inputMethod instanceof InputMethodAdapter) { 644 ((InputMethodAdapter) inputMethod).setClientComponent(null); 645 } 646 647 // removeNotify() can be issued from a thread other than the event dispatch 648 // thread. In that case, avoid possible deadlock between Component.AWTTreeLock 649 // and InputMethodContext.compositionAreaHandlerLock by releasing the composition 650 // area on the event dispatch thread. 651 if (EventQueue.isDispatchThread()) { 652 ((InputMethodContext)this).releaseCompositionArea(); 653 } else { 654 EventQueue.invokeLater(new Runnable() { 655 public void run() { 656 ((InputMethodContext)InputContext.this).releaseCompositionArea(); 657 } 658 }); 659 } 660 } 661 } 662 663 /** 664 * @see java.awt.im.InputContext#dispose 665 * @exception IllegalStateException when the currentClientComponent is not null 666 */ 667 public synchronized void dispose() { 668 if (currentClientComponent != null) { 669 throw new IllegalStateException("Can't dispose InputContext while it's active"); 670 } 671 if (inputMethod != null) { 672 if (this == inputMethodWindowContext) { 673 inputMethod.hideWindows(); 674 inputMethodWindowContext = null; 675 } 676 if (inputMethod == previousInputMethod) { 677 previousInputMethod = null; 678 } 679 if (clientWindowNotificationEnabled) { 680 if (addedClientWindowListeners()) { 681 removeClientWindowListeners(); 682 } 683 clientWindowNotificationEnabled = false; 684 } 685 inputMethod.dispose(); 686 687 // in case the input method enabled the client window 688 // notification in dispose(), which shouldn't happen, it 689 // needs to be cleaned up again. 690 if (clientWindowNotificationEnabled) { 691 enableClientWindowNotification(inputMethod, false); 692 } 693 694 inputMethod = null; 695 } 696 inputMethodLocator = null; 697 if (usedInputMethods != null && !usedInputMethods.isEmpty()) { 698 Iterator<InputMethod> iterator = usedInputMethods.values().iterator(); 699 usedInputMethods = null; 700 while (iterator.hasNext()) { 701 iterator.next().dispose(); 702 } 703 } 704 705 // cleanup client window notification variables 706 clientWindowNotificationEnabled = false; 707 clientWindowListened = null; 708 perInputMethodState = null; 709 } 710 711 /** 712 * @see java.awt.im.InputContext#getInputMethodControlObject 713 */ 714 public synchronized Object getInputMethodControlObject() { 715 InputMethod inputMethod = getInputMethod(); 716 717 if (inputMethod != null) { 718 return inputMethod.getControlObject(); 719 } else { 720 return null; 721 } 722 } 723 724 /** 725 * @see java.awt.im.InputContext#setCompositionEnabled(boolean) 726 * @exception UnsupportedOperationException when input method is null 727 */ 728 public void setCompositionEnabled(boolean enable) { 729 InputMethod inputMethod = getInputMethod(); 730 731 if (inputMethod == null) { 732 throw new UnsupportedOperationException(); 733 } 734 inputMethod.setCompositionEnabled(enable); 735 } 736 737 /** 738 * @see java.awt.im.InputContext#isCompositionEnabled 739 * @exception UnsupportedOperationException when input method is null 740 */ 741 public boolean isCompositionEnabled() { 742 InputMethod inputMethod = getInputMethod(); 743 744 if (inputMethod == null) { 745 throw new UnsupportedOperationException(); 746 } 747 return inputMethod.isCompositionEnabled(); 748 } 749 750 /** 751 * @return a string with information about the current input method. 752 * @exception UnsupportedOperationException when input method is null 753 */ 754 public String getInputMethodInfo() { 755 InputMethod inputMethod = getInputMethod(); 756 757 if (inputMethod == null) { 758 throw new UnsupportedOperationException("Null input method"); 759 } 760 761 String inputMethodInfo = null; 762 if (inputMethod instanceof InputMethodAdapter) { 763 // returns the information about the host native input method. 764 inputMethodInfo = ((InputMethodAdapter)inputMethod). 765 getNativeInputMethodInfo(); 766 } 767 768 // extracts the information from the InputMethodDescriptor 769 // associated with the current java input method. 770 if (inputMethodInfo == null && inputMethodLocator != null) { 771 inputMethodInfo = inputMethodLocator.getDescriptor(). 772 getInputMethodDisplayName(getLocale(), SunToolkit. 773 getStartupLocale()); 774 } 775 776 if (inputMethodInfo != null && !inputMethodInfo.equals("")) { 777 return inputMethodInfo; 778 } 779 780 // do our best to return something useful. 781 return inputMethod.toString() + "-" + inputMethod.getLocale().toString(); 782 } 783 784 /** 785 * Turns off the native IM. The native IM is diabled when 786 * the deactive method of InputMethod is called. It is 787 * delayed until the active method is called on a different 788 * peer component. This method is provided to explicitly disable 789 * the native IM. 790 */ 791 public void disableNativeIM() { 792 InputMethod inputMethod = getInputMethod(); 793 if (inputMethod != null && inputMethod instanceof InputMethodAdapter) { 794 ((InputMethodAdapter)inputMethod).stopListening(); 795 } 796 } 797 798 799 private synchronized InputMethod getInputMethod() { 800 if (inputMethod != null) { 801 return inputMethod; 802 } 803 804 if (inputMethodCreationFailed) { 805 return null; 806 } 807 808 inputMethod = getInputMethodInstance(); 809 return inputMethod; 810 } 811 812 /** 813 * Returns an instance of the input method described by 814 * the current input method locator. This may be an input 815 * method that was previously used and switched out of, 816 * or a new instance. The locale, character subsets, and 817 * input method context of the input method are set. 818 * 819 * The inputMethodCreationFailed field is set to true if the 820 * instantiation failed. 821 * 822 * @return an InputMethod instance 823 * @see java.awt.im.spi.InputMethod#setInputMethodContext 824 * @see java.awt.im.spi.InputMethod#setLocale 825 * @see java.awt.im.spi.InputMethod#setCharacterSubsets 826 */ 827 private InputMethod getInputMethodInstance() { 828 InputMethodLocator locator = inputMethodLocator; 829 if (locator == null) { 830 inputMethodCreationFailed = true; 831 return null; 832 } 833 834 Locale locale = locator.getLocale(); 835 InputMethod inputMethodInstance = null; 836 837 // see whether we have a previously used input method 838 if (usedInputMethods != null) { 839 inputMethodInstance = usedInputMethods.remove(locator.deriveLocator(null)); 840 if (inputMethodInstance != null) { 841 if (locale != null) { 842 inputMethodInstance.setLocale(locale); 843 } 844 inputMethodInstance.setCharacterSubsets(characterSubsets); 845 Boolean state = perInputMethodState.remove(inputMethodInstance); 846 if (state != null) { 847 enableClientWindowNotification(inputMethodInstance, state.booleanValue()); 848 } 849 ((InputMethodContext) this).setInputMethodSupportsBelowTheSpot( 850 (!(inputMethodInstance instanceof InputMethodAdapter)) || 851 ((InputMethodAdapter) inputMethodInstance).supportsBelowTheSpot()); 852 return inputMethodInstance; 853 } 854 } 855 856 // need to create new instance 857 try { 858 inputMethodInstance = locator.getDescriptor().createInputMethod(); 859 860 if (locale != null) { 861 inputMethodInstance.setLocale(locale); 862 } 863 inputMethodInstance.setInputMethodContext((InputMethodContext) this); 864 inputMethodInstance.setCharacterSubsets(characterSubsets); 865 866 } catch (Exception e) { 867 logCreationFailed(e); 868 869 // there are a number of bad things that can happen while creating 870 // the input method. In any case, we just continue without an 871 // input method. 872 inputMethodCreationFailed = true; 873 874 // if the instance has been created, then it means either 875 // setLocale() or setInputMethodContext() failed. 876 if (inputMethodInstance != null) { 877 inputMethodInstance = null; 878 } 879 } catch (LinkageError e) { 880 logCreationFailed(e); 881 882 // same as above 883 inputMethodCreationFailed = true; 884 } 885 ((InputMethodContext) this).setInputMethodSupportsBelowTheSpot( 886 (!(inputMethodInstance instanceof InputMethodAdapter)) || 887 ((InputMethodAdapter) inputMethodInstance).supportsBelowTheSpot()); 888 return inputMethodInstance; 889 } 890 891 private void logCreationFailed(Throwable throwable) { 892 PlatformLogger logger = PlatformLogger.getLogger("sun.awt.im"); 893 if (logger.isLoggable(PlatformLogger.Level.CONFIG)) { 894 String errorTextFormat = Toolkit.getProperty("AWT.InputMethodCreationFailed", 895 "Could not create {0}. Reason: {1}"); 896 Object[] args = 897 {inputMethodLocator.getDescriptor().getInputMethodDisplayName(null, Locale.getDefault()), 898 throwable.getLocalizedMessage()}; 899 MessageFormat mf = new MessageFormat(errorTextFormat); 900 logger.config(mf.format(args)); 901 } 902 } 903 904 InputMethodLocator getInputMethodLocator() { 905 if (inputMethod != null) { 906 return inputMethodLocator.deriveLocator(inputMethod.getLocale()); 907 } 908 return inputMethodLocator; 909 } 910 911 /** 912 * @see java.awt.im.InputContext#endComposition 913 */ 914 public synchronized void endComposition() { 915 if (inputMethod != null) { 916 inputMethod.endComposition(); 917 } 918 } 919 920 /** 921 * @see java.awt.im.spi.InputMethodContext#enableClientWindowNotification 922 */ 923 synchronized void enableClientWindowNotification(InputMethod requester, 924 boolean enable) { 925 // in case this request is not from the current input method, 926 // store the request and handle it when this requesting input 927 // method becomes the current one. 928 if (requester != inputMethod) { 929 if (perInputMethodState == null) { 930 perInputMethodState = new HashMap<>(5); 931 } 932 perInputMethodState.put(requester, Boolean.valueOf(enable)); 933 return; 934 } 935 936 if (clientWindowNotificationEnabled != enable) { 937 clientWindowLocation = null; 938 clientWindowNotificationEnabled = enable; 939 } 940 if (clientWindowNotificationEnabled) { 941 if (!addedClientWindowListeners()) { 942 addClientWindowListeners(); 943 } 944 if (clientWindowListened != null) { 945 clientWindowLocation = null; 946 notifyClientWindowChange(clientWindowListened); 947 } 948 } else { 949 if (addedClientWindowListeners()) { 950 removeClientWindowListeners(); 951 } 952 } 953 } 954 955 private synchronized void notifyClientWindowChange(Window window) { 956 if (inputMethod == null) { 957 return; 958 } 959 960 // if the window is invisible or iconified, send null to the input method. 961 if (!window.isVisible() || 962 ((window instanceof Frame) && ((Frame)window).getState() == Frame.ICONIFIED)) { 963 clientWindowLocation = null; 964 inputMethod.notifyClientWindowChange(null); 965 return; 966 } 967 Rectangle location = window.getBounds(); 968 if (clientWindowLocation == null || !clientWindowLocation.equals(location)) { 969 clientWindowLocation = location; 970 inputMethod.notifyClientWindowChange(clientWindowLocation); 971 } 972 } 973 974 private synchronized void addClientWindowListeners() { 975 Component client = getClientComponent(); 976 if (client == null) { 977 return; 978 } 979 Window window = getComponentWindow(client); 980 if (window == null) { 981 return; 982 } 983 window.addComponentListener(this); 984 window.addWindowListener(this); 985 clientWindowListened = window; 986 } 987 988 private synchronized void removeClientWindowListeners() { 989 clientWindowListened.removeComponentListener(this); 990 clientWindowListened.removeWindowListener(this); 991 clientWindowListened = null; 992 } 993 994 /** 995 * Returns true if listeners have been set up for client window 996 * change notification. 997 */ 998 private boolean addedClientWindowListeners() { 999 return clientWindowListened != null; 1000 } 1001 1002 /* 1003 * ComponentListener and WindowListener implementation 1004 */ 1005 public void componentResized(ComponentEvent e) { 1006 notifyClientWindowChange((Window)e.getComponent()); 1007 } 1008 1009 public void componentMoved(ComponentEvent e) { 1010 notifyClientWindowChange((Window)e.getComponent()); 1011 } 1012 1013 public void componentShown(ComponentEvent e) { 1014 notifyClientWindowChange((Window)e.getComponent()); 1015 } 1016 1017 public void componentHidden(ComponentEvent e) { 1018 notifyClientWindowChange((Window)e.getComponent()); 1019 } 1020 1021 public void windowOpened(WindowEvent e) {} 1022 public void windowClosing(WindowEvent e) {} 1023 public void windowClosed(WindowEvent e) {} 1024 1025 public void windowIconified(WindowEvent e) { 1026 notifyClientWindowChange(e.getWindow()); 1027 } 1028 1029 public void windowDeiconified(WindowEvent e) { 1030 notifyClientWindowChange(e.getWindow()); 1031 } 1032 1033 public void windowActivated(WindowEvent e) {} 1034 public void windowDeactivated(WindowEvent e) {} 1035 1036 /** 1037 * Initializes the input method selection key definition in preference trees 1038 */ 1039 private void initializeInputMethodSelectionKey() { 1040 AccessController.doPrivileged(new PrivilegedAction<Object>() { 1041 public Object run() { 1042 // Look in user's tree 1043 Preferences root = Preferences.userRoot(); 1044 inputMethodSelectionKey = getInputMethodSelectionKeyStroke(root); 1045 1046 if (inputMethodSelectionKey == null) { 1047 // Look in system's tree 1048 root = Preferences.systemRoot(); 1049 inputMethodSelectionKey = getInputMethodSelectionKeyStroke(root); 1050 } 1051 return null; 1052 } 1053 }); 1054 } 1055 1056 private AWTKeyStroke getInputMethodSelectionKeyStroke(Preferences root) { 1057 try { 1058 if (root.nodeExists(inputMethodSelectionKeyPath)) { 1059 Preferences node = root.node(inputMethodSelectionKeyPath); 1060 int keyCode = node.getInt(inputMethodSelectionKeyCodeName, KeyEvent.VK_UNDEFINED); 1061 if (keyCode != KeyEvent.VK_UNDEFINED) { 1062 int modifiers = node.getInt(inputMethodSelectionKeyModifiersName, 0); 1063 return AWTKeyStroke.getAWTKeyStroke(keyCode, modifiers); 1064 } 1065 } 1066 } catch (BackingStoreException bse) { 1067 } 1068 1069 return null; 1070 } 1071 }