| /* | |
| * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package sun.awt.im; | |
| import java.awt.AWTEvent; | |
| import java.awt.AWTKeyStroke; | |
| import java.awt.Component; | |
| import java.awt.EventQueue; | |
| import java.awt.Frame; | |
| import java.awt.Rectangle; | |
| import java.awt.Toolkit; | |
| import java.awt.Window; | |
| import java.awt.event.ComponentEvent; | |
| import java.awt.event.ComponentListener; | |
| import java.awt.event.FocusEvent; | |
| import java.awt.event.InputEvent; | |
| import java.awt.event.InputMethodEvent; | |
| import java.awt.event.KeyEvent; | |
| import java.awt.event.WindowEvent; | |
| import java.awt.event.WindowListener; | |
| import java.awt.im.InputMethodRequests; | |
| import java.awt.im.spi.InputMethod; | |
| import java.lang.Character.Subset; | |
| import java.text.MessageFormat; | |
| import java.util.Collection; | |
| import java.util.HashMap; | |
| import java.util.Locale; | |
| import java.util.prefs.BackingStoreException; | |
| import java.util.prefs.Preferences; | |
| import sun.util.logging.PlatformLogger; | |
| import sun.awt.SunToolkit; | |
| /** | |
| * This InputContext class contains parts of the implementation of | |
| * java.text.im.InputContext. These parts have been moved | |
| * here to avoid exposing protected members that are needed by the | |
| * subclass InputMethodContext. | |
| * | |
| * @see java.awt.im.InputContext | |
| * @author JavaSoft Asia/Pacific | |
| */ | |
| public class InputContext extends java.awt.im.InputContext | |
| implements ComponentListener, WindowListener { | |
| private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.im.InputContext"); | |
| // The current input method is represented by two objects: | |
| // a locator is used to keep information about the selected | |
| // input method and locale until we actually need a real input | |
| // method; only then the input method itself is created. | |
| // Once there is an input method, the input method's locale | |
| // takes precedence over locale information in the locator. | |
| private InputMethodLocator inputMethodLocator; | |
| private InputMethod inputMethod; | |
| private boolean inputMethodCreationFailed; | |
| // holding bin for previously used input method instances, but not the current one | |
| private HashMap<InputMethodLocator, InputMethod> usedInputMethods; | |
| // the current client component is kept until the user focuses on a different | |
| // client component served by the same input context. When that happens, we call | |
| // endComposition so that text doesn't jump from one component to another. | |
| private Component currentClientComponent; | |
| private Component awtFocussedComponent; | |
| private boolean isInputMethodActive; | |
| private Subset[] characterSubsets = null; | |
| // true if composition area has been set to invisible when focus was lost | |
| private boolean compositionAreaHidden = false; | |
| // The input context for whose input method we may have to call hideWindows | |
| private static InputContext inputMethodWindowContext; | |
| // Previously active input method to decide whether we need to call | |
| // InputMethodAdapter.stopListening() on activateInputMethod() | |
| private static InputMethod previousInputMethod = null; | |
| // true if the current input method requires client window change notification | |
| private boolean clientWindowNotificationEnabled = false; | |
| // client window to which this input context is listening | |
| private Window clientWindowListened; | |
| // cache location notification | |
| private Rectangle clientWindowLocation = null; | |
| // holding the state of clientWindowNotificationEnabled of only non-current input methods | |
| private HashMap<InputMethod, Boolean> perInputMethodState; | |
| // Input Method selection hot key stuff | |
| private static AWTKeyStroke inputMethodSelectionKey; | |
| private static boolean inputMethodSelectionKeyInitialized = false; | |
| private static final String inputMethodSelectionKeyPath = "/java/awt/im/selectionKey"; | |
| private static final String inputMethodSelectionKeyCodeName = "keyCode"; | |
| private static final String inputMethodSelectionKeyModifiersName = "modifiers"; | |
| /** | |
| * Constructs an InputContext. | |
| */ | |
| protected InputContext() { | |
| InputMethodManager imm = InputMethodManager.getInstance(); | |
| synchronized (InputContext.class) { | |
| if (!inputMethodSelectionKeyInitialized) { | |
| inputMethodSelectionKeyInitialized = true; | |
| if (imm.hasMultipleInputMethods()) { | |
| initializeInputMethodSelectionKey(); | |
| } | |
| } | |
| } | |
| selectInputMethod(imm.getDefaultKeyboardLocale()); | |
| } | |
| /** | |
| * @see java.awt.im.InputContext#selectInputMethod | |
| * @throws NullPointerException when the locale is null. | |
| */ | |
| public synchronized boolean selectInputMethod(Locale locale) { | |
| if (locale == null) { | |
| throw new NullPointerException(); | |
| } | |
| // see whether the current input method supports the locale | |
| if (inputMethod != null) { | |
| if (inputMethod.setLocale(locale)) { | |
| return true; | |
| } | |
| } else if (inputMethodLocator != null) { | |
| // This is not 100% correct, since the input method | |
| // may support the locale without advertising it. | |
| // But before we try instantiations and setLocale, | |
| // we look for an input method that's more confident. | |
| if (inputMethodLocator.isLocaleAvailable(locale)) { | |
| inputMethodLocator = inputMethodLocator.deriveLocator(locale); | |
| return true; | |
| } | |
| } | |
| // see whether there's some other input method that supports the locale | |
| InputMethodLocator newLocator = InputMethodManager.getInstance().findInputMethod(locale); | |
| if (newLocator != null) { | |
| changeInputMethod(newLocator); | |
| return true; | |
| } | |
| // make one last desperate effort with the current input method | |
| // ??? is this good? This is pretty high cost for something that's likely to fail. | |
| if (inputMethod == null && inputMethodLocator != null) { | |
| inputMethod = getInputMethod(); | |
| if (inputMethod != null) { | |
| return inputMethod.setLocale(locale); | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * @see java.awt.im.InputContext#getLocale | |
| */ | |
| public Locale getLocale() { | |
| if (inputMethod != null) { | |
| return inputMethod.getLocale(); | |
| } else if (inputMethodLocator != null) { | |
| return inputMethodLocator.getLocale(); | |
| } else { | |
| return null; | |
| } | |
| } | |
| /** | |
| * @see java.awt.im.InputContext#setCharacterSubsets | |
| */ | |
| public void setCharacterSubsets(Subset[] subsets) { | |
| if (subsets == null) { | |
| characterSubsets = null; | |
| } else { | |
| characterSubsets = new Subset[subsets.length]; | |
| System.arraycopy(subsets, 0, | |
| characterSubsets, 0, characterSubsets.length); | |
| } | |
| if (inputMethod != null) { | |
| inputMethod.setCharacterSubsets(subsets); | |
| } | |
| } | |
| /** | |
| * @see java.awt.im.InputContext#reconvert | |
| * @since 1.3 | |
| * @throws UnsupportedOperationException when input method is null | |
| */ | |
| public synchronized void reconvert() { | |
| InputMethod inputMethod = getInputMethod(); | |
| if (inputMethod == null) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| inputMethod.reconvert(); | |
| } | |
| /** | |
| * @see java.awt.im.InputContext#dispatchEvent | |
| */ | |
| @SuppressWarnings("fallthrough") | |
| public void dispatchEvent(AWTEvent event) { | |
| if (event instanceof InputMethodEvent) { | |
| return; | |
| } | |
| // Ignore focus events that relate to the InputMethodWindow of this context. | |
| // This is a workaround. Should be removed after 4452384 is fixed. | |
| if (event instanceof FocusEvent) { | |
| Component opposite = ((FocusEvent)event).getOppositeComponent(); | |
| if ((opposite != null) && | |
| (getComponentWindow(opposite) instanceof InputMethodWindow) && | |
| (opposite.getInputContext() == this)) { | |
| return; | |
| } | |
| } | |
| InputMethod inputMethod = getInputMethod(); | |
| int id = event.getID(); | |
| switch (id) { | |
| case FocusEvent.FOCUS_GAINED: | |
| focusGained((Component) event.getSource()); | |
| break; | |
| case FocusEvent.FOCUS_LOST: | |
| focusLost((Component) event.getSource(), ((FocusEvent) event).isTemporary()); | |
| break; | |
| case KeyEvent.KEY_PRESSED: | |
| if (checkInputMethodSelectionKey((KeyEvent)event)) { | |
| // pop up the input method selection menu | |
| InputMethodManager.getInstance().notifyChangeRequestByHotKey((Component)event.getSource()); | |
| break; | |
| } | |
| // fall through | |
| default: | |
| if ((inputMethod != null) && (event instanceof InputEvent)) { | |
| inputMethod.dispatchEvent(event); | |
| } | |
| } | |
| } | |
| /** | |
| * Handles focus gained events for any component that's using | |
| * this input context. | |
| * These events are generated by AWT when the keyboard focus | |
| * moves to a component. | |
| * Besides actual client components, the source components | |
| * may also be the composition area or any component in an | |
| * input method window. | |
| * <p> | |
| * When handling the focus event for a client component, this | |
| * method checks whether the input context was previously | |
| * active for a different client component, and if so, calls | |
| * endComposition for the previous client component. | |
| * | |
| * @param source the component gaining the focus | |
| */ | |
| private void focusGained(Component source) { | |
| /* | |
| * NOTE: When a Container is removing its Component which | |
| * invokes this.removeNotify(), the Container has the global | |
| * Component lock. It is possible to happen that an | |
| * application thread is calling this.removeNotify() while an | |
| * AWT event queue thread is dispatching a focus event via | |
| * this.dispatchEvent(). If an input method uses AWT | |
| * components (e.g., IIIMP status window), it causes deadlock, | |
| * for example, Component.show()/hide() in this situation | |
| * because hide/show tried to obtain the lock. Therefore, | |
| * it's necessary to obtain the global Component lock before | |
| * activating or deactivating an input method. | |
| */ | |
| synchronized (source.getTreeLock()) { | |
| synchronized (this) { | |
| if ("sun.awt.im.CompositionArea".equals(source.getClass().getName())) { | |
| // no special handling for this one | |
| } else if (getComponentWindow(source) instanceof InputMethodWindow) { | |
| // no special handling for this one either | |
| } else { | |
| if (!source.isDisplayable()) { | |
| // Component is being disposed | |
| return; | |
| } | |
| // Focus went to a real client component. | |
| // Check whether we're switching between client components | |
| // that share an input context. We can't do that earlier | |
| // than here because we don't want to end composition | |
| // until we really know we're switching to a different component | |
| if (inputMethod != null) { | |
| if (currentClientComponent != null && currentClientComponent != source) { | |
| if (!isInputMethodActive) { | |
| activateInputMethod(false); | |
| } | |
| endComposition(); | |
| deactivateInputMethod(false); | |
| } | |
| } | |
| currentClientComponent = source; | |
| } | |
| awtFocussedComponent = source; | |
| if (inputMethod instanceof InputMethodAdapter) { | |
| ((InputMethodAdapter) inputMethod).setAWTFocussedComponent(source); | |
| } | |
| // it's possible that the input method is still active because | |
| // we suppressed a deactivate cause by an input method window | |
| // coming up | |
| if (!isInputMethodActive) { | |
| activateInputMethod(true); | |
| } | |
| // If the client component is an active client with the below-the-spot | |
| // input style, then make the composition window undecorated without a title bar. | |
| InputMethodContext inputContext = ((InputMethodContext)this); | |
| if (!inputContext.isCompositionAreaVisible()) { | |
| InputMethodRequests req = source.getInputMethodRequests(); | |
| if (req != null && inputContext.useBelowTheSpotInput()) { | |
| inputContext.setCompositionAreaUndecorated(true); | |
| } else { | |
| inputContext.setCompositionAreaUndecorated(false); | |
| } | |
| } | |
| // restores the composition area if it was set to invisible | |
| // when focus got lost | |
| if (compositionAreaHidden == true) { | |
| ((InputMethodContext)this).setCompositionAreaVisible(true); | |
| compositionAreaHidden = false; | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Activates the current input method of this input context, and grabs | |
| * the composition area for use by this input context. | |
| * If updateCompositionArea is true, the text in the composition area | |
| * is updated (set to false if the text is going to change immediately | |
| * to avoid screen flicker). | |
| */ | |
| private void activateInputMethod(boolean updateCompositionArea) { | |
| // call hideWindows() if this input context uses a different | |
| // input method than the previously activated one | |
| if (inputMethodWindowContext != null && inputMethodWindowContext != this && | |
| inputMethodWindowContext.inputMethodLocator != null && | |
| !inputMethodWindowContext.inputMethodLocator.sameInputMethod(inputMethodLocator) && | |
| inputMethodWindowContext.inputMethod != null) { | |
| inputMethodWindowContext.inputMethod.hideWindows(); | |
| } | |
| inputMethodWindowContext = this; | |
| if (inputMethod != null) { | |
| if (previousInputMethod != inputMethod && | |
| previousInputMethod instanceof InputMethodAdapter) { | |
| // let the host adapter pass through the input events for the | |
| // new input method | |
| ((InputMethodAdapter) previousInputMethod).stopListening(); | |
| } | |
| previousInputMethod = null; | |
| if (log.isLoggable(PlatformLogger.Level.FINE)) { | |
| log.fine("Current client component " + currentClientComponent); | |
| } | |
| if (inputMethod instanceof InputMethodAdapter) { | |
| ((InputMethodAdapter) inputMethod).setClientComponent(currentClientComponent); | |
| } | |
| inputMethod.activate(); | |
| isInputMethodActive = true; | |
| if (perInputMethodState != null) { | |
| Boolean state = perInputMethodState.remove(inputMethod); | |
| if (state != null) { | |
| clientWindowNotificationEnabled = state.booleanValue(); | |
| } | |
| } | |
| if (clientWindowNotificationEnabled) { | |
| if (!addedClientWindowListeners()) { | |
| addClientWindowListeners(); | |
| } | |
| synchronized(this) { | |
| if (clientWindowListened != null) { | |
| notifyClientWindowChange(clientWindowListened); | |
| } | |
| } | |
| } else { | |
| if (addedClientWindowListeners()) { | |
| removeClientWindowListeners(); | |
| } | |
| } | |
| } | |
| InputMethodManager.getInstance().setInputContext(this); | |
| ((InputMethodContext) this).grabCompositionArea(updateCompositionArea); | |
| } | |
| static Window getComponentWindow(Component component) { | |
| while (true) { | |
| if (component == null) { | |
| return null; | |
| } else if (component instanceof Window) { | |
| return (Window) component; | |
| } else { | |
| component = component.getParent(); | |
| } | |
| } | |
| } | |
| /** | |
| * Handles focus lost events for any component that's using | |
| * this input context. | |
| * These events are generated by AWT when the keyboard focus | |
| * moves away from a component. | |
| * Besides actual client components, the source components | |
| * may also be the composition area or any component in an | |
| * input method window. | |
| * | |
| * @param source the component losing the focus | |
| * @param isTemporary whether the focus change is temporary | |
| */ | |
| private void focusLost(Component source, boolean isTemporary) { | |
| // see the note on synchronization in focusGained | |
| synchronized (source.getTreeLock()) { | |
| synchronized (this) { | |
| // We need to suppress deactivation if removeNotify has been called earlier. | |
| // This is indicated by isInputMethodActive == false. | |
| if (isInputMethodActive) { | |
| deactivateInputMethod(isTemporary); | |
| } | |
| awtFocussedComponent = null; | |
| if (inputMethod instanceof InputMethodAdapter) { | |
| ((InputMethodAdapter) inputMethod).setAWTFocussedComponent(null); | |
| } | |
| // hides the composition area if currently it is visible | |
| InputMethodContext inputContext = ((InputMethodContext)this); | |
| if (inputContext.isCompositionAreaVisible()) { | |
| inputContext.setCompositionAreaVisible(false); | |
| compositionAreaHidden = true; | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Checks the key event is the input method selection key or not. | |
| */ | |
| private boolean checkInputMethodSelectionKey(KeyEvent event) { | |
| if (inputMethodSelectionKey != null) { | |
| AWTKeyStroke aKeyStroke = AWTKeyStroke.getAWTKeyStrokeForEvent(event); | |
| return inputMethodSelectionKey.equals(aKeyStroke); | |
| } else { | |
| return false; | |
| } | |
| } | |
| private void deactivateInputMethod(boolean isTemporary) { | |
| InputMethodManager.getInstance().setInputContext(null); | |
| if (inputMethod != null) { | |
| isInputMethodActive = false; | |
| inputMethod.deactivate(isTemporary); | |
| previousInputMethod = inputMethod; | |
| } | |
| } | |
| /** | |
| * Switches from the current input method to the one described by newLocator. | |
| * The current input method, if any, is asked to end composition, deactivated, | |
| * and saved for future use. The newLocator is made the current locator. If | |
| * the input context is active, an input method instance for the new locator | |
| * is obtained; otherwise this is deferred until required. | |
| */ | |
| synchronized void changeInputMethod(InputMethodLocator newLocator) { | |
| // If we don't have a locator yet, this must be a new input context. | |
| // If we created a new input method here, we might get into an | |
| // infinite loop: create input method -> create some input method window -> | |
| // create new input context -> add input context to input method manager's context list -> | |
| // call changeInputMethod on it. | |
| // So, just record the locator. dispatchEvent will create the input method when needed. | |
| if (inputMethodLocator == null) { | |
| inputMethodLocator = newLocator; | |
| inputMethodCreationFailed = false; | |
| return; | |
| } | |
| // If the same input method is specified, just keep it. | |
| // Adjust the locale if necessary. | |
| if (inputMethodLocator.sameInputMethod(newLocator)) { | |
| Locale newLocale = newLocator.getLocale(); | |
| if (newLocale != null && inputMethodLocator.getLocale() != newLocale) { | |
| if (inputMethod != null) { | |
| inputMethod.setLocale(newLocale); | |
| } | |
| inputMethodLocator = newLocator; | |
| } | |
| return; | |
| } | |
| // Switch out the old input method | |
| Locale savedLocale = inputMethodLocator.getLocale(); | |
| boolean wasInputMethodActive = isInputMethodActive; | |
| boolean wasCompositionEnabledSupported = false; | |
| boolean wasCompositionEnabled = false; | |
| if (inputMethod != null) { | |
| try { | |
| wasCompositionEnabled = inputMethod.isCompositionEnabled(); | |
| wasCompositionEnabledSupported = true; | |
| } catch (UnsupportedOperationException e) { } | |
| if (currentClientComponent != null) { | |
| if (!isInputMethodActive) { | |
| activateInputMethod(false); | |
| } | |
| endComposition(); | |
| deactivateInputMethod(false); | |
| if (inputMethod instanceof InputMethodAdapter) { | |
| ((InputMethodAdapter) inputMethod).setClientComponent(null); | |
| } | |
| if (null == currentClientComponent.getInputMethodRequests()) | |
| wasCompositionEnabledSupported = false; | |
| } | |
| savedLocale = inputMethod.getLocale(); | |
| // keep the input method instance around for future use | |
| if (usedInputMethods == null) { | |
| usedInputMethods = new HashMap<>(5); | |
| } | |
| if (perInputMethodState == null) { | |
| perInputMethodState = new HashMap<>(5); | |
| } | |
| usedInputMethods.put(inputMethodLocator.deriveLocator(null), inputMethod); | |
| perInputMethodState.put(inputMethod, | |
| Boolean.valueOf(clientWindowNotificationEnabled)); | |
| enableClientWindowNotification(inputMethod, false); | |
| if (this == inputMethodWindowContext) { | |
| inputMethod.hideWindows(); | |
| inputMethod.removeNotify(); | |
| inputMethodWindowContext = null; | |
| } | |
| inputMethodLocator = null; | |
| inputMethod = null; | |
| inputMethodCreationFailed = false; | |
| } | |
| // Switch in the new input method | |
| if (newLocator.getLocale() == null && savedLocale != null && | |
| newLocator.isLocaleAvailable(savedLocale)) { | |
| newLocator = newLocator.deriveLocator(savedLocale); | |
| } | |
| inputMethodLocator = newLocator; | |
| inputMethodCreationFailed = false; | |
| // activate the new input method if the old one was active | |
| if (wasInputMethodActive) { | |
| inputMethod = getInputMethodInstance(); | |
| if (inputMethod instanceof InputMethodAdapter) { | |
| ((InputMethodAdapter) inputMethod).setAWTFocussedComponent(awtFocussedComponent); | |
| } | |
| activateInputMethod(true); | |
| } | |
| // enable/disable composition if the old one supports querying enable/disable | |
| if (wasCompositionEnabledSupported) { | |
| inputMethod = getInputMethod(); | |
| if (inputMethod != null) { | |
| try { | |
| inputMethod.setCompositionEnabled(wasCompositionEnabled); | |
| } catch (UnsupportedOperationException e) { } | |
| } | |
| } | |
| } | |
| /** | |
| * Returns the client component. | |
| */ | |
| Component getClientComponent() { | |
| return currentClientComponent; | |
| } | |
| /** | |
| * @see java.awt.im.InputContext#removeNotify | |
| * @throws NullPointerException when the component is null. | |
| */ | |
| public synchronized void removeNotify(Component component) { | |
| if (component == null) { | |
| throw new NullPointerException(); | |
| } | |
| if (inputMethod == null) { | |
| if (component == currentClientComponent) { | |
| currentClientComponent = null; | |
| } | |
| return; | |
| } | |
| // We may or may not get a FOCUS_LOST event for this component, | |
| // so do the deactivation stuff here too. | |
| if (component == awtFocussedComponent) { | |
| focusLost(component, false); | |
| } | |
| if (component == currentClientComponent) { | |
| if (isInputMethodActive) { | |
| // component wasn't the one that had the focus | |
| deactivateInputMethod(false); | |
| } | |
| inputMethod.removeNotify(); | |
| if (clientWindowNotificationEnabled && addedClientWindowListeners()) { | |
| removeClientWindowListeners(); | |
| } | |
| currentClientComponent = null; | |
| if (inputMethod instanceof InputMethodAdapter) { | |
| ((InputMethodAdapter) inputMethod).setClientComponent(null); | |
| } | |
| // removeNotify() can be issued from a thread other than the event dispatch | |
| // thread. In that case, avoid possible deadlock between Component.AWTTreeLock | |
| // and InputMethodContext.compositionAreaHandlerLock by releasing the composition | |
| // area on the event dispatch thread. | |
| if (EventQueue.isDispatchThread()) { | |
| ((InputMethodContext)this).releaseCompositionArea(); | |
| } else { | |
| EventQueue.invokeLater(new Runnable() { | |
| public void run() { | |
| ((InputMethodContext)InputContext.this).releaseCompositionArea(); | |
| } | |
| }); | |
| } | |
| } | |
| } | |
| /** | |
| * @see java.awt.im.InputContext#dispose | |
| * @throws IllegalStateException when the currentClientComponent is not null | |
| */ | |
| public synchronized void dispose() { | |
| if (currentClientComponent != null) { | |
| throw new IllegalStateException("Can't dispose InputContext while it's active"); | |
| } | |
| if (inputMethod != null) { | |
| if (this == inputMethodWindowContext) { | |
| inputMethod.hideWindows(); | |
| inputMethodWindowContext = null; | |
| } | |
| if (inputMethod == previousInputMethod) { | |
| previousInputMethod = null; | |
| } | |
| if (clientWindowNotificationEnabled) { | |
| if (addedClientWindowListeners()) { | |
| removeClientWindowListeners(); | |
| } | |
| clientWindowNotificationEnabled = false; | |
| } | |
| inputMethod.dispose(); | |
| // in case the input method enabled the client window | |
| // notification in dispose(), which shouldn't happen, it | |
| // needs to be cleaned up again. | |
| if (clientWindowNotificationEnabled) { | |
| enableClientWindowNotification(inputMethod, false); | |
| } | |
| inputMethod = null; | |
| } | |
| inputMethodLocator = null; | |
| if (usedInputMethods != null && !usedInputMethods.isEmpty()) { | |
| Collection<InputMethod> methods = usedInputMethods.values(); | |
| usedInputMethods = null; | |
| for (InputMethod method : methods) { | |
| method.dispose(); | |
| } | |
| } | |
| // cleanup client window notification variables | |
| clientWindowNotificationEnabled = false; | |
| clientWindowListened = null; | |
| perInputMethodState = null; | |
| } | |
| /** | |
| * @see java.awt.im.InputContext#getInputMethodControlObject | |
| */ | |
| public synchronized Object getInputMethodControlObject() { | |
| InputMethod inputMethod = getInputMethod(); | |
| if (inputMethod != null) { | |
| return inputMethod.getControlObject(); | |
| } else { | |
| return null; | |
| } | |
| } | |
| /** | |
| * @see java.awt.im.InputContext#setCompositionEnabled(boolean) | |
| * @throws UnsupportedOperationException when input method is null | |
| */ | |
| public void setCompositionEnabled(boolean enable) { | |
| InputMethod inputMethod = getInputMethod(); | |
| if (inputMethod == null) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| inputMethod.setCompositionEnabled(enable); | |
| } | |
| /** | |
| * @see java.awt.im.InputContext#isCompositionEnabled | |
| * @throws UnsupportedOperationException when input method is null | |
| */ | |
| public boolean isCompositionEnabled() { | |
| InputMethod inputMethod = getInputMethod(); | |
| if (inputMethod == null) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| return inputMethod.isCompositionEnabled(); | |
| } | |
| /** | |
| * @return a string with information about the current input method. | |
| * @throws UnsupportedOperationException when input method is null | |
| */ | |
| public String getInputMethodInfo() { | |
| InputMethod inputMethod = getInputMethod(); | |
| if (inputMethod == null) { | |
| throw new UnsupportedOperationException("Null input method"); | |
| } | |
| String inputMethodInfo = null; | |
| if (inputMethod instanceof InputMethodAdapter) { | |
| // returns the information about the host native input method. | |
| inputMethodInfo = ((InputMethodAdapter)inputMethod). | |
| getNativeInputMethodInfo(); | |
| } | |
| // extracts the information from the InputMethodDescriptor | |
| // associated with the current java input method. | |
| if (inputMethodInfo == null && inputMethodLocator != null) { | |
| inputMethodInfo = inputMethodLocator.getDescriptor(). | |
| getInputMethodDisplayName(getLocale(), SunToolkit. | |
| getStartupLocale()); | |
| } | |
| if (inputMethodInfo != null && !inputMethodInfo.isEmpty()) { | |
| return inputMethodInfo; | |
| } | |
| // do our best to return something useful. | |
| return inputMethod.toString() + "-" + inputMethod.getLocale().toString(); | |
| } | |
| /** | |
| * Turns off the native IM. The native IM is disabled when | |
| * the deactivate method of InputMethod is called. It is | |
| * delayed until the active method is called on a different | |
| * peer component. This method is provided to explicitly disable | |
| * the native IM. | |
| */ | |
| public void disableNativeIM() { | |
| InputMethod inputMethod = getInputMethod(); | |
| if (inputMethod instanceof InputMethodAdapter adapter) { | |
| adapter.stopListening(); | |
| } | |
| } | |
| private synchronized InputMethod getInputMethod() { | |
| if (inputMethod != null) { | |
| return inputMethod; | |
| } | |
| if (inputMethodCreationFailed) { | |
| return null; | |
| } | |
| inputMethod = getInputMethodInstance(); | |
| return inputMethod; | |
| } | |
| /** | |
| * Returns an instance of the input method described by | |
| * the current input method locator. This may be an input | |
| * method that was previously used and switched out of, | |
| * or a new instance. The locale, character subsets, and | |
| * input method context of the input method are set. | |
| * | |
| * The inputMethodCreationFailed field is set to true if the | |
| * instantiation failed. | |
| * | |
| * @return an InputMethod instance | |
| * @see java.awt.im.spi.InputMethod#setInputMethodContext | |
| * @see java.awt.im.spi.InputMethod#setLocale | |
| * @see java.awt.im.spi.InputMethod#setCharacterSubsets | |
| */ | |
| private InputMethod getInputMethodInstance() { | |
| InputMethodLocator locator = inputMethodLocator; | |
| if (locator == null) { | |
| inputMethodCreationFailed = true; | |
| return null; | |
| } | |
| Locale locale = locator.getLocale(); | |
| InputMethod inputMethodInstance = null; | |
| // see whether we have a previously used input method | |
| if (usedInputMethods != null) { | |
| inputMethodInstance = usedInputMethods.remove(locator.deriveLocator(null)); | |
| if (inputMethodInstance != null) { | |
| if (locale != null) { | |
| inputMethodInstance.setLocale(locale); | |
| } | |
| inputMethodInstance.setCharacterSubsets(characterSubsets); | |
| Boolean state = perInputMethodState.remove(inputMethodInstance); | |
| if (state != null) { | |
| enableClientWindowNotification(inputMethodInstance, state.booleanValue()); | |
| } | |
| ((InputMethodContext) this).setInputMethodSupportsBelowTheSpot( | |
| (!(inputMethodInstance instanceof InputMethodAdapter)) || | |
| ((InputMethodAdapter) inputMethodInstance).supportsBelowTheSpot()); | |
| return inputMethodInstance; | |
| } | |
| } | |
| // need to create new instance | |
| try { | |
| inputMethodInstance = locator.getDescriptor().createInputMethod(); | |
| if (locale != null) { | |
| inputMethodInstance.setLocale(locale); | |
| } | |
| inputMethodInstance.setInputMethodContext((InputMethodContext) this); | |
| inputMethodInstance.setCharacterSubsets(characterSubsets); | |
| } catch (Exception e) { | |
| logCreationFailed(e); | |
| // there are a number of bad things that can happen while creating | |
| // the input method. In any case, we just continue without an | |
| // input method. | |
| inputMethodCreationFailed = true; | |
| // if the instance has been created, then it means either | |
| // setLocale() or setInputMethodContext() failed. | |
| if (inputMethodInstance != null) { | |
| inputMethodInstance = null; | |
| } | |
| } catch (LinkageError e) { | |
| logCreationFailed(e); | |
| // same as above | |
| inputMethodCreationFailed = true; | |
| } | |
| ((InputMethodContext) this).setInputMethodSupportsBelowTheSpot( | |
| (!(inputMethodInstance instanceof InputMethodAdapter)) || | |
| ((InputMethodAdapter) inputMethodInstance).supportsBelowTheSpot()); | |
| return inputMethodInstance; | |
| } | |
| private void logCreationFailed(Throwable throwable) { | |
| PlatformLogger logger = PlatformLogger.getLogger("sun.awt.im"); | |
| if (logger.isLoggable(PlatformLogger.Level.CONFIG)) { | |
| String errorTextFormat = Toolkit.getProperty("AWT.InputMethodCreationFailed", | |
| "Could not create {0}. Reason: {1}"); | |
| Object[] args = | |
| {inputMethodLocator.getDescriptor().getInputMethodDisplayName(null, Locale.getDefault()), | |
| throwable.getLocalizedMessage()}; | |
| MessageFormat mf = new MessageFormat(errorTextFormat); | |
| logger.config(mf.format(args)); | |
| } | |
| } | |
| InputMethodLocator getInputMethodLocator() { | |
| if (inputMethod != null) { | |
| return inputMethodLocator.deriveLocator(inputMethod.getLocale()); | |
| } | |
| return inputMethodLocator; | |
| } | |
| /** | |
| * @see java.awt.im.InputContext#endComposition | |
| */ | |
| public synchronized void endComposition() { | |
| if (inputMethod != null) { | |
| inputMethod.endComposition(); | |
| } | |
| } | |
| /** | |
| * @see java.awt.im.spi.InputMethodContext#enableClientWindowNotification | |
| */ | |
| synchronized void enableClientWindowNotification(InputMethod requester, | |
| boolean enable) { | |
| // in case this request is not from the current input method, | |
| // store the request and handle it when this requesting input | |
| // method becomes the current one. | |
| if (requester != inputMethod) { | |
| if (perInputMethodState == null) { | |
| perInputMethodState = new HashMap<>(5); | |
| } | |
| perInputMethodState.put(requester, Boolean.valueOf(enable)); | |
| return; | |
| } | |
| if (clientWindowNotificationEnabled != enable) { | |
| clientWindowLocation = null; | |
| clientWindowNotificationEnabled = enable; | |
| } | |
| if (clientWindowNotificationEnabled) { | |
| if (!addedClientWindowListeners()) { | |
| addClientWindowListeners(); | |
| } | |
| if (clientWindowListened != null) { | |
| clientWindowLocation = null; | |
| notifyClientWindowChange(clientWindowListened); | |
| } | |
| } else { | |
| if (addedClientWindowListeners()) { | |
| removeClientWindowListeners(); | |
| } | |
| } | |
| } | |
| private synchronized void notifyClientWindowChange(Window window) { | |
| if (inputMethod == null) { | |
| return; | |
| } | |
| // if the window is invisible or iconified, send null to the input method. | |
| if (!window.isVisible() || | |
| ((window instanceof Frame) && ((Frame)window).getState() == Frame.ICONIFIED)) { | |
| clientWindowLocation = null; | |
| inputMethod.notifyClientWindowChange(null); | |
| return; | |
| } | |
| Rectangle location = window.getBounds(); | |
| if (clientWindowLocation == null || !clientWindowLocation.equals(location)) { | |
| clientWindowLocation = location; | |
| inputMethod.notifyClientWindowChange(clientWindowLocation); | |
| } | |
| } | |
| private synchronized void addClientWindowListeners() { | |
| Component client = getClientComponent(); | |
| if (client == null) { | |
| return; | |
| } | |
| Window window = getComponentWindow(client); | |
| if (window == null) { | |
| return; | |
| } | |
| window.addComponentListener(this); | |
| window.addWindowListener(this); | |
| clientWindowListened = window; | |
| } | |
| private synchronized void removeClientWindowListeners() { | |
| clientWindowListened.removeComponentListener(this); | |
| clientWindowListened.removeWindowListener(this); | |
| clientWindowListened = null; | |
| } | |
| /** | |
| * Returns true if listeners have been set up for client window | |
| * change notification. | |
| */ | |
| private boolean addedClientWindowListeners() { | |
| return clientWindowListened != null; | |
| } | |
| /* | |
| * ComponentListener and WindowListener implementation | |
| */ | |
| public void componentResized(ComponentEvent e) { | |
| notifyClientWindowChange((Window)e.getComponent()); | |
| } | |
| public void componentMoved(ComponentEvent e) { | |
| notifyClientWindowChange((Window)e.getComponent()); | |
| } | |
| public void componentShown(ComponentEvent e) { | |
| notifyClientWindowChange((Window)e.getComponent()); | |
| } | |
| public void componentHidden(ComponentEvent e) { | |
| notifyClientWindowChange((Window)e.getComponent()); | |
| } | |
| public void windowOpened(WindowEvent e) {} | |
| public void windowClosing(WindowEvent e) {} | |
| public void windowClosed(WindowEvent e) {} | |
| public void windowIconified(WindowEvent e) { | |
| notifyClientWindowChange(e.getWindow()); | |
| } | |
| public void windowDeiconified(WindowEvent e) { | |
| notifyClientWindowChange(e.getWindow()); | |
| } | |
| public void windowActivated(WindowEvent e) {} | |
| public void windowDeactivated(WindowEvent e) {} | |
| /** | |
| * Initializes the input method selection key definition in preference trees | |
| */ | |
| private void initializeInputMethodSelectionKey() { | |
| // Look in user's tree | |
| Preferences root = Preferences.userRoot(); | |
| inputMethodSelectionKey = getInputMethodSelectionKeyStroke(root); | |
| if (inputMethodSelectionKey == null) { | |
| // Look in system's tree | |
| root = Preferences.systemRoot(); | |
| inputMethodSelectionKey = getInputMethodSelectionKeyStroke(root); | |
| } | |
| } | |
| private AWTKeyStroke getInputMethodSelectionKeyStroke(Preferences root) { | |
| try { | |
| if (root.nodeExists(inputMethodSelectionKeyPath)) { | |
| Preferences node = root.node(inputMethodSelectionKeyPath); | |
| int keyCode = node.getInt(inputMethodSelectionKeyCodeName, KeyEvent.VK_UNDEFINED); | |
| if (keyCode != KeyEvent.VK_UNDEFINED) { | |
| int modifiers = node.getInt(inputMethodSelectionKeyModifiersName, 0); | |
| return AWTKeyStroke.getAWTKeyStroke(keyCode, modifiers); | |
| } | |
| } | |
| } catch (BackingStoreException bse) { | |
| } | |
| return null; | |
| } | |
| } |