--- /dev/null 2018-02-05 10:17:31.364998584 +0900 +++ new/src/java.desktop/aix/classes/sun/awt/X11InputMethod.java 2018-04-13 18:47:00.000000000 +0900 @@ -0,0 +1,1239 @@ +/* + * Copyright (c) 1997, 2017, 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; + +import java.util.Collections; +import java.util.Locale; +import java.util.Map; +import java.util.HashMap; +import java.awt.AWTEvent; +import java.awt.AWTException; +import java.awt.Component; +import java.awt.Container; +import java.awt.EventQueue; +import java.awt.Window; +import java.awt.im.InputMethodHighlight; +import java.awt.im.spi.InputMethodContext; +import sun.awt.im.InputMethodAdapter; +import java.awt.event.InputMethodEvent; +import java.awt.font.TextAttribute; +import java.awt.font.TextHitInfo; +import java.awt.peer.ComponentPeer; +import java.lang.Character.Subset; +import java.text.AttributedString; +import java.text.AttributedCharacterIterator; + +import java.io.File; +import java.io.FileReader; +import java.io.BufferedReader; +import java.io.IOException; +import java.lang.ref.WeakReference; +import sun.util.logging.PlatformLogger; +import java.util.StringTokenizer; +import java.util.regex.Pattern; + + +/** + * Input Method Adapter for XIM + * + * @author JavaSoft International + */ +public abstract class X11InputMethod extends InputMethodAdapter { + private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11InputMethod"); + /* + * The following XIM* values must be the same as those defined in + * Xlib.h + */ + private static final int XIMReverse = (1<<0); + private static final int XIMUnderline = (1<<1); + private static final int XIMHighlight = (1<<2); + private static final int XIMPrimary = (1<<5); + private static final int XIMSecondary = (1<<6); + private static final int XIMTertiary = (1<<7); + + /* + * visible position values + */ + private static final int XIMVisibleToForward = (1<<8); + private static final int XIMVisibleToBackward = (1<<9); + private static final int XIMVisibleCenter = (1<<10); + private static final int XIMVisibleMask = (XIMVisibleToForward| + XIMVisibleToBackward| + XIMVisibleCenter); + + private Locale locale; + private static boolean isXIMOpened = false; + protected Container clientComponentWindow = null; + private Component awtFocussedComponent = null; + private Component lastXICFocussedComponent = null; + private boolean isLastXICActive = false; + private boolean isLastTemporary = false; + private boolean isActive = false; + private boolean isActiveClient = false; + private static Map[] highlightStyles; + private boolean disposed = false; + + //reset the XIC if necessary + private boolean needResetXIC = false; + private WeakReference needResetXICClient = new WeakReference<>(null); + + // The use of compositionEnableSupported is to reduce unnecessary + // native calls if set/isCompositionEnabled + // throws UnsupportedOperationException. + // It is set to false if that exception is thrown first time + // either of the two methods are called. + private boolean compositionEnableSupported = true; + // The savedCompositionState indicates the composition mode when + // endComposition or setCompositionEnabled is called. It doesn't always + // reflect the actual composition state because it doesn't get updated + // when the user changes the composition state through direct interaction + // with the input method. It is used to save the composition mode when + // focus is traversed across different client components sharing the + // same java input context. Also if set/isCompositionEnabled are not + // supported, it remains false. + private boolean savedCompositionState = false; + + // variables to keep track of preedit context. + // these variables need to be accessed within AWT_LOCK/UNLOCK + private String committedText = null; + private StringBuffer composedText = null; + private IntBuffer rawFeedbacks; + + // private data (X11InputMethodData structure defined in + // awt_InputMethod.c) for native methods + // this structure needs to be accessed within AWT_LOCK/UNLOCK + private transient long pData = 0; // accessed by native + + // to keep the instance of activating if IM resumed + static protected X11InputMethod activatedInstance = null; + + // Initialize highlight mapping table + static { + @SuppressWarnings({"unchecked", "rawtypes"}) + Map styles[] = new Map[4]; + HashMap map; + + // UNSELECTED_RAW_TEXT_HIGHLIGHT + map = new HashMap<>(1); + map.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD); + styles[0] = Collections.unmodifiableMap(map); + + // SELECTED_RAW_TEXT_HIGHLIGHT + map = new HashMap<>(1); + map.put(TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON); + styles[1] = Collections.unmodifiableMap(map); + + // UNSELECTED_CONVERTED_TEXT_HIGHLIGHT + map = new HashMap<>(1); + map.put(TextAttribute.INPUT_METHOD_UNDERLINE, + TextAttribute.UNDERLINE_LOW_ONE_PIXEL); + styles[2] = Collections.unmodifiableMap(map); + + // SELECTED_CONVERTED_TEXT_HIGHLIGHT + map = new HashMap<>(1); + map.put(TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON); + styles[3] = Collections.unmodifiableMap(map); + + highlightStyles = styles; + } + + static { + initIDs(); + } + + /** + * Initialize JNI field and method IDs for fields that may be + accessed from C. + */ + private static native void initIDs(); + + /** + * Constructs an X11InputMethod instance. It initializes the XIM + * environment if it's not done yet. + * + * @exception AWTException if XOpenIM() failed. + */ + public X11InputMethod() throws AWTException { + // supports only the locale in which the VM is started + locale = X11InputMethodDescriptor.getSupportedLocale(); + if (initXIM() == false) { + throw new AWTException("Cannot open X Input Method"); + } + } + + @SuppressWarnings("deprecation") + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } + + /** + * Invokes openIM() that invokes XOpenIM() if it's not opened yet. + * @return true if openXIM() is successful or it's already been opened. + */ + private synchronized boolean initXIM() { + if (isXIMOpened == false) + isXIMOpened = openXIM(); + return isXIMOpened; + } + + protected abstract boolean openXIM(); + + protected boolean isDisposed() { + return disposed; + } + + protected abstract void setXICFocus(ComponentPeer peer, + boolean value, boolean active); + + /** + * Does nothing - this adapter doesn't use the input method context. + * + * @see java.awt.im.spi.InputMethod#setInputMethodContext + */ + public void setInputMethodContext(InputMethodContext context) { + } + + /** + * Set locale to input. If input method doesn't support specified locale, + * false will be returned and its behavior is not changed. + * + * @param lang locale to input + * @return the true is returned when specified locale is supported. + */ + public boolean setLocale(Locale lang) { + if (lang.equals(locale)) { + return true; + } + // special compatibility rule for Japanese and Korean + if (locale.equals(Locale.JAPAN) && lang.equals(Locale.JAPANESE) || + locale.equals(Locale.KOREA) && lang.equals(Locale.KOREAN)) { + return true; + } + return false; + } + + /** + * Returns current input locale. + */ + public Locale getLocale() { + return locale; + } + + /** + * Does nothing - XIM doesn't let you specify which characters you expect. + * + * @see java.awt.im.spi.InputMethod#setCharacterSubsets + */ + public void setCharacterSubsets(Subset[] subsets) { + } + + /** + * Dispatch event to input method. InputContext dispatch event with this + * method. Input method set consume flag if event is consumed in + * input method. + * + * @param e event + */ + public void dispatchEvent(AWTEvent e) { + } + + + protected final void resetXICifneeded(){ + /* needResetXIC is used to indicate whether to call + resetXIC on the active client. resetXIC will always be + called on the passive client when endComposition is called. + */ + if (needResetXIC && haveActiveClient() && + getClientComponent() != needResetXICClient.get()){ + resetXIC(); + + // needs to reset the last xic focussed component. + lastXICFocussedComponent = null; + isLastXICActive = false; + + needResetXICClient.clear(); + needResetXIC = false; + } + } + + /** + * Reset the composition state to the current composition state. + */ + private void resetCompositionState() { + if (compositionEnableSupported && haveActiveClient()) { + try { + /* Restore the composition mode to the last saved composition + mode. */ + setCompositionEnabled(savedCompositionState); + } catch (UnsupportedOperationException e) { + compositionEnableSupported = false; + } + } + } + + /** + * Query and then return the current composition state. + * @return the composition state if isCompositionEnabled call + * is successful. Otherwise, it returns false. + */ + private boolean getCompositionState() { + boolean compositionState = false; + if (compositionEnableSupported) { + try { + compositionState = isCompositionEnabled(); + } catch (UnsupportedOperationException e) { + compositionEnableSupported = false; + } + } + return compositionState; + } + + /** + * Activate input method. + */ + public synchronized void activate() { + activatedInstance = this; + clientComponentWindow = getClientComponentWindow(); + if (clientComponentWindow == null) + return; + + if (lastXICFocussedComponent != null){ + if (log.isLoggable(PlatformLogger.Level.FINE)) { + log.fine("XICFocused {0}, AWTFocused {1}", + lastXICFocussedComponent, awtFocussedComponent); + } + if (lastXICFocussedComponent != awtFocussedComponent) { + ComponentPeer lastXICFocussedComponentPeer = getPeer(lastXICFocussedComponent); + if (lastXICFocussedComponentPeer != null){ + setXICFocus(lastXICFocussedComponentPeer, false, isLastXICActive); + } + } + lastXICFocussedComponent = null; + } + + if (pData == 0) { + if (!createXIC()) { + return; + } + disposed = false; + } + + /* reset input context if necessary and set the XIC focus + */ + resetXICifneeded(); + ComponentPeer lastXICFocussedComponentPeer = null; + ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent); + setStatusAreaVisible(true, pData); + + if (awtFocussedComponentPeer != null) { + setXICFocus(awtFocussedComponentPeer, true, haveActiveClient()); + } + lastXICFocussedComponent = awtFocussedComponent; + isLastXICActive = haveActiveClient(); + isActive = true; + if (savedCompositionState) { + resetCompositionState(); + } + } + + protected abstract boolean createXIC(); + + /** + * Deactivate input method. + */ + public synchronized void deactivate(boolean isTemporary) { + boolean isAc = haveActiveClient(); + /* Usually as the client component, let's call it component A, + loses the focus, this method is called. Then when another client + component, let's call it component B, gets the focus, activate is first called on + the previous focused compoent which is A, then endComposition is called on A, + deactivate is called on A again. And finally activate is called on the newly + focused component B. Here is the call sequence. + + A loses focus B gains focus + -------------> deactivate A -------------> activate A -> endComposition A -> + deactivate A -> activate B ----.... + + So in order to carry the composition mode across the components sharing the same + input context, we save it when deactivate is called so that when activate is + called, it can be restored correctly till activate is called on the newly focused + component. (See also sun/awt/im/InputContext and bug 6184471). + Last note, getCompositionState should be called before setXICFocus since + setXICFocus here sets the XIC to 0. + */ + activatedInstance = null; + savedCompositionState = getCompositionState(); + + if (isTemporary){ + //turn the status window off... + turnoffStatusWindow(); + } + + if (!isTemporary){ + if (awtFocussedComponent != null ){ + ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent); + if (awtFocussedComponentPeer != null){ + setXICFocus(awtFocussedComponentPeer, false, isAc); + } + } + lastXICFocussedComponent = null; + } else { + /* Delay resetting the XIC focus until activate is called and the newly + focussed component has a different peer as the last focussed component. + */ + lastXICFocussedComponent = awtFocussedComponent; + } + + isLastXICActive = isAc; + isLastTemporary = isTemporary; + isActive = false; + setStatusAreaVisible(false, pData); + } + + /** + * Explicitly disable the native IME. Native IME is not disabled when + * deactivate is called. + */ + public void disableInputMethod() { + if (lastXICFocussedComponent != null) { + setXICFocus(getPeer(lastXICFocussedComponent), false, isLastXICActive); + lastXICFocussedComponent = null; + isLastXICActive = false; + + resetXIC(); + needResetXICClient.clear(); + needResetXIC = false; + } + } + + // implements java.awt.im.spi.InputMethod.hideWindows + public void hideWindows() { + // ??? need real implementation + if (pData != 0) { + setStatusAreaVisible(false, pData); + turnoffStatusWindow(); + } + } + + /** + * @see java.awt.Toolkit#mapInputMethodHighlight + */ + public static Map mapInputMethodHighlight(InputMethodHighlight highlight) { + int index; + int state = highlight.getState(); + if (state == InputMethodHighlight.RAW_TEXT) { + index = 0; + } else if (state == InputMethodHighlight.CONVERTED_TEXT) { + index = 2; + } else { + return null; + } + if (highlight.isSelected()) { + index += 1; + } + return highlightStyles[index]; + } + + /** + * @see sun.awt.im.InputMethodAdapter#setAWTFocussedComponent + */ + protected void setAWTFocussedComponent(Component component) { + if (component == null) { + return; + } + if (isActive) { + // deactivate/activate are being suppressed during a focus change - + // this may happen when an input method window is made visible + boolean ac = haveActiveClient(); + setXICFocus(getPeer(awtFocussedComponent), false, ac); + setXICFocus(getPeer(component), true, ac); + } + awtFocussedComponent = component; + } + + /** + * @see sun.awt.im.InputMethodAdapter#stopListening + */ + protected void stopListening() { + // It is desirable to disable XIM by calling XSetICValues with + // XNPreeditState == XIMPreeditDisable. But Solaris 2.6 and + // Solaris 7 do not implement this correctly without a patch, + // so just call resetXIC here. Prior endComposition call commits + // the existing composed text. + endComposition(); + // disable the native input method so that the other input + // method could get the input focus. + disableInputMethod(); + if (needResetXIC) { + resetXIC(); + needResetXICClient.clear(); + needResetXIC = false; + } + } + + /** + * Returns the Window instance in which the client component is + * contained. If not found, null is returned. (IS THIS POSSIBLE?) + */ + // NOTE: This method may be called by privileged threads. + // DO NOT INVOKE CLIENT CODE ON THIS THREAD! + private Window getClientComponentWindow() { + Component client = getClientComponent(); + Container container; + + if (client instanceof Container) { + container = (Container) client; + } else { + container = getParent(client); + } + + while (container != null && !(container instanceof java.awt.Window)) { + container = getParent(container); + } + return (Window) container; + } + + protected abstract Container getParent(Component client); + + /** + * Returns peer of the given client component. If the given client component + * doesn't have peer, peer of the native container of the client is returned. + */ + protected abstract ComponentPeer getPeer(Component client); + + /** + * Used to protect preedit data + */ + protected abstract void awtLock(); + protected abstract void awtUnlock(); + + /** + * Creates an input method event from the arguments given + * and posts it on the AWT event queue. For arguments, + * see InputMethodEvent. Called by input method. + * + * @see java.awt.event.InputMethodEvent#InputMethodEvent + */ + private void postInputMethodEvent(int id, + AttributedCharacterIterator text, + int committedCharacterCount, + TextHitInfo caret, + TextHitInfo visiblePosition, + long when) { + Component source = getClientComponent(); + if (source != null) { + InputMethodEvent event = new InputMethodEvent(source, + id, when, text, committedCharacterCount, caret, visiblePosition); + SunToolkit.postEvent(SunToolkit.targetToAppContext(source), (AWTEvent)event); + } + } + + private void postInputMethodEvent(int id, + AttributedCharacterIterator text, + int committedCharacterCount, + TextHitInfo caret, + TextHitInfo visiblePosition) { + postInputMethodEvent(id, text, committedCharacterCount, + caret, visiblePosition, EventQueue.getMostRecentEventTime()); + } + + /** + * Dispatches committed text from XIM to the awt event queue. This + * method is invoked from the event handler in canvas.c in the + * AWT Toolkit thread context and thus inside the AWT Lock. + * @param str committed text + * @param when when + */ + // NOTE: This method may be called by privileged threads. + // This functionality is implemented in a package-private method + // to insure that it cannot be overridden by client subclasses. + // DO NOT INVOKE CLIENT CODE ON THIS THREAD! + void dispatchCommittedText(String str, long when) { + if (str == null) + return; + + if (composedText == null) { + AttributedString attrstr = new AttributedString(str); + postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, + attrstr.getIterator(), + str.length(), + null, + null, + when); + } else { + // if there is composed text, wait until the preedit + // callback is invoked. + committedText = str; + } + } + + private void dispatchCommittedText(String str) { + dispatchCommittedText(str, EventQueue.getMostRecentEventTime()); + } + + /** + * Updates composed text with XIM preedit information and + * posts composed text to the awt event queue. The args of + * this method correspond to the XIM preedit callback + * information. The XIM highlight attributes are translated via + * fixed mapping (i.e., independent from any underlying input + * method engine). This method is invoked in the AWT Toolkit + * (X event loop) thread context and thus inside the AWT Lock. + */ + // NOTE: This method may be called by privileged threads. + // This functionality is implemented in a package-private method + // to insure that it cannot be overridden by client subclasses. + // DO NOT INVOKE CLIENT CODE ON THIS THREAD! + void dispatchComposedText(String chgText, + int chgStyles[], + int chgOffset, + int chgLength, + int caretPosition, + long when) { + if (disposed) { + return; + } + + //Workaround for deadlock bug on solaris2.6_zh bug#4170760 + if (chgText == null + && chgStyles == null + && chgOffset == 0 + && chgLength == 0 + && caretPosition == 0 + && composedText == null + && committedText == null) + return; + + // Recalculate chgOffset and chgLength for supplementary char + if (composedText != null){ + int tmpChgOffset=chgOffset; + int tmpChgLength=chgLength; + int index = 0; + for (int i=0;i < tmpChgOffset; i++,index++){ + if (index < composedText.length() + && Character.charCount(composedText.codePointAt(index))==2){ + index++; + chgOffset++; + } + } + // The index keeps value + for (int i=0;i < tmpChgLength; i++,index++){ + if (index < composedText.length() + && Character.charCount(composedText.codePointAt(index))==2){ + index++; + chgLength++; + } + } + } + + // Replace control character with a square box + if (chgText != null){ + StringBuffer newChgText = new StringBuffer(); + for (int i=0; i < chgText.length(); i++){ + char c = chgText.charAt(i); + if (Character.isISOControl(c)){ + c = '\u25A1'; + } + newChgText.append(c); + } + chgText=new String(newChgText); + } + + if (composedText == null) { + // TODO: avoid reallocation of those buffers + composedText = new StringBuffer(INITIAL_SIZE); + rawFeedbacks = new IntBuffer(INITIAL_SIZE); + } + if (chgLength > 0) { + if (chgText == null && chgStyles != null) { + rawFeedbacks.replace(chgOffset, chgStyles); + } else { + if (chgLength == composedText.length()) { + // optimization for the special case to replace the + // entire previous text + composedText = new StringBuffer(INITIAL_SIZE); + rawFeedbacks = new IntBuffer(INITIAL_SIZE); + } else { + if (composedText.length() > 0) { + if (chgOffset+chgLength < composedText.length()) { + String text; + text = composedText.toString().substring(chgOffset+chgLength, + composedText.length()); + composedText.setLength(chgOffset); + composedText.append(text); + } else { + // in case to remove substring from chgOffset + // to the end + composedText.setLength(chgOffset); + } + rawFeedbacks.remove(chgOffset, chgLength); + } + } + } + } + if (chgText != null) { + composedText.insert(chgOffset, chgText); + if (chgStyles != null) { + // Recalculate chgStyles for supplementary char + if (chgText.length() > chgStyles.length){ + int index=0; + int[] newStyles = new int[chgText.length()]; + for (int i=0; i < chgStyles.length; i++, index++){ + newStyles[index]=chgStyles[i]; + if (index < chgText.length() + && Character.charCount(chgText.codePointAt(index))==2){ + newStyles[++index]=chgStyles[i]; + } + } + chgStyles=newStyles; + } + rawFeedbacks.insert(chgOffset, chgStyles); + } + + } + + else if (chgStyles != null) { + // Recalculate chgStyles to support supplementary char + int count=0; + for (int i=0; i < chgStyles.length; i++){ + if (composedText.length() > chgOffset+i+count + && Character.charCount(composedText.codePointAt(chgOffset+i+count))==2){ + count++; + } + } + if (count>0){ + int index=0; + int[] newStyles = new int[chgStyles.length+count]; + for (int i=0; i < chgStyles.length; i++, index++){ + newStyles[index]=chgStyles[i]; + if (composedText.length() > chgOffset+index + && Character.charCount(composedText.codePointAt(chgOffset+index))==2){ + newStyles[++index]=chgStyles[i]; + } + } + chgStyles=newStyles; + } + rawFeedbacks.replace(chgOffset, chgStyles); + } + + if (composedText.length() == 0) { + composedText = null; + rawFeedbacks = null; + + // if there is any outstanding committed text stored by + // dispatchCommittedText(), it has to be sent to the + // client component. + if (committedText != null) { + dispatchCommittedText(committedText, when); + committedText = null; + return; + } + + // otherwise, send null text to delete client's composed + // text. + postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, + null, + 0, + null, + null, + when); + + return; + } + + // Adjust caretPosition for supplementary char + for (int i=0; i< caretPosition; i++){ + if (i < composedText.length() + && Character.charCount(composedText.codePointAt(i))==2){ + caretPosition++; + i++; + } + } + + // Now sending the composed text to the client + int composedOffset; + AttributedString inputText; + + // if there is any partially committed text, concatenate it to + // the composed text. + if (committedText != null) { + composedOffset = committedText.length(); + inputText = new AttributedString(committedText + composedText); + committedText = null; + } else { + composedOffset = 0; + inputText = new AttributedString(composedText.toString()); + } + + int currentFeedback; + int nextFeedback; + int startOffset = 0; + int currentOffset; + int visiblePosition = 0; + TextHitInfo visiblePositionInfo = null; + + rawFeedbacks.rewind(); + currentFeedback = rawFeedbacks.getNext(); + rawFeedbacks.unget(); + while ((nextFeedback = rawFeedbacks.getNext()) != -1) { + if (visiblePosition == 0) { + visiblePosition = nextFeedback & XIMVisibleMask; + if (visiblePosition != 0) { + int index = rawFeedbacks.getOffset() - 1; + + if (visiblePosition == XIMVisibleToBackward) + visiblePositionInfo = TextHitInfo.leading(index); + else + visiblePositionInfo = TextHitInfo.trailing(index); + } + } + nextFeedback &= ~XIMVisibleMask; + if (currentFeedback != nextFeedback) { + rawFeedbacks.unget(); + currentOffset = rawFeedbacks.getOffset(); + inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, + convertVisualFeedbackToHighlight(currentFeedback), + composedOffset + startOffset, + composedOffset + currentOffset); + startOffset = currentOffset; + currentFeedback = nextFeedback; + } + } + currentOffset = rawFeedbacks.getOffset(); + if (currentOffset >= 0) { + inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, + convertVisualFeedbackToHighlight(currentFeedback), + composedOffset + startOffset, + composedOffset + currentOffset); + } + + postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, + inputText.getIterator(), + composedOffset, + TextHitInfo.leading(caretPosition), + visiblePositionInfo, + when); + } + + /** + * Flushes composed and committed text held in this context. + * This method is invoked in the AWT Toolkit (X event loop) thread context + * and thus inside the AWT Lock. + */ + // NOTE: This method may be called by privileged threads. + // This functionality is implemented in a package-private method + // to insure that it cannot be overridden by client subclasses. + // DO NOT INVOKE CLIENT CODE ON THIS THREAD! + void flushText() { + String flush = (committedText != null ? committedText : ""); + if (composedText != null) { + flush += composedText.toString(); + } + + if (!flush.equals("")) { + AttributedString attrstr = new AttributedString(flush); + postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, + attrstr.getIterator(), + flush.length(), + null, + null, + EventQueue.getMostRecentEventTime()); + composedText = null; + committedText = null; + } + } + + /* Some IMs need forced Text clear */ + void clearComposedText(long when) { + composedText = null; + postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, + null, 0, null, null, + when); + if (committedText != null && committedText.length() > 0) { + dispatchCommittedText(committedText, when); + } + committedText = null; + rawFeedbacks = null; + } + + void clearComposedText() { + if (EventQueue.isDispatchThread()) { + clearComposedText(EventQueue.getMostRecentEventTime()); + } + } + + /* + * Subclasses should override disposeImpl() instead of dispose(). Client + * code should always invoke dispose(), never disposeImpl(). + */ + protected synchronized void disposeImpl() { + disposeXIC(); + awtLock(); + try { + clearComposedText(); + } finally { + // Put awtUnlock into finally block in case an exception is thrown in clearComposedText. + awtUnlock(); + } + awtFocussedComponent = null; + lastXICFocussedComponent = null; + needResetXIC = false; + savedCompositionState = false; + compositionEnableSupported = true; + } + + /** + * Frees all X Window resources associated with this object. + * + * @see java.awt.im.spi.InputMethod#dispose + */ + public final void dispose() { + boolean call_disposeImpl = false; + + if (!disposed) { + synchronized (this) { + if (!disposed) { + disposed = call_disposeImpl = true; + } + } + } + + if (call_disposeImpl) { + disposeImpl(); + } + } + + /** + * Returns null. + * + * @see java.awt.im.spi.InputMethod#getControlObject + */ + public Object getControlObject() { + return null; + } + + /** + * @see java.awt.im.spi.InputMethod#removeNotify + */ + public synchronized void removeNotify() { + dispose(); + } + + /** + * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean) + */ + public void setCompositionEnabled(boolean enable) { + /* If the composition state is successfully changed, set + the savedCompositionState to 'enable'. Otherwise, simply + return. + setCompositionEnabledNative may throw UnsupportedOperationException. + Don't try to catch it since the method may be called by clients. + Use package private mthod 'resetCompositionState' if you want the + exception to be caught. + */ + boolean pre, post; + pre=getCompositionState(); + + if (setCompositionEnabledNative(enable)) { + savedCompositionState = enable; + } + + post=getCompositionState(); + if (pre != post && post == enable){ + if (enable == false) flushText(); + if (awtFocussedComponent != null && isActive){ + setXICFocus(getPeer(awtFocussedComponent), + true, haveActiveClient()); + } + } + } + + /** + * @see java.awt.im.spi.InputMethod#isCompositionEnabled + */ + public boolean isCompositionEnabled() { + /* isCompositionEnabledNative may throw UnsupportedOperationException. + Don't try to catch it since this method may be called by clients. + Use package private method 'getCompositionState' if you want the + exception to be caught. + */ + return isCompositionEnabledNative(); + } + + /** + * Ends any input composition that may currently be going on in this + * context. Depending on the platform and possibly user preferences, + * this may commit or delete uncommitted text. Any changes to the text + * are communicated to the active component using an input method event. + * + *

+ * A text editing component may call this in a variety of situations, + * for example, when the user moves the insertion point within the text + * (but outside the composed text), or when the component's text is + * saved to a file or copied to the clipboard. + * + */ + public void endComposition() { + if (disposed) { + return; + } + + /* Before calling resetXIC, record the current composition mode + so that it can be restored later. */ + savedCompositionState = getCompositionState(); + boolean active = haveActiveClient(); + if (active && composedText == null && committedText == null){ + needResetXIC = true; + needResetXICClient = new WeakReference<>(getClientComponent()); + return; + } + + String text = resetXIC(); + /* needResetXIC is only set to true for active client. So passive + client should not reset the flag to false. */ + if (active) { + needResetXIC = false; + } + + // Remove any existing composed text by posting an InputMethodEvent + // with null composed text. It would be desirable to wait for a + // dispatchComposedText call from X input method engine, but some + // input method does not conform to the XIM specification and does + // not call the preedit callback to erase preedit text on calling + // XmbResetIC. To work around this problem, do it here by ourselves. + awtLock(); + try { + composedText = null; + postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, + null, + 0, + null, + null); + + if (text != null && text.length() > 0) { + dispatchCommittedText(text); + } + } finally { + // Put awtUnlock into finally block in case an exception is thrown. + awtUnlock(); + } + + // Restore the preedit state if it was enabled + if (savedCompositionState) { + resetCompositionState(); + } + } + + /** + * Returns a string with information about the current input method server, or null. + * On both Linux & SunOS, the value of environment variable XMODIFIERS is + * returned if set. Otherwise, on SunOS, $HOME/.dtprofile will be parsed + * to find out the language service engine (atok or wnn) since there is + * no API in Xlib which returns the information of native + * IM server or language service and we want to try our best to return as much + * information as possible. + * + * Note: This method could return null on Linux if XMODIFIERS is not set properly or + * if any IOException is thrown. + * See man page of XSetLocaleModifiers(3X11) for the usgae of XMODIFIERS, + * atok12setup(1) and wnn6setup(1) for the information written to + * $HOME/.dtprofile when you run these two commands. + * + */ + public String getNativeInputMethodInfo() { + String xmodifiers = System.getenv("XMODIFIERS"); + String imInfo = null; + + // If XMODIFIERS is set, return the value + if (xmodifiers != null) { + int imIndex = xmodifiers.indexOf("@im="); + if (imIndex != -1) { + imInfo = xmodifiers.substring(imIndex + 4); + } + } else if (System.getProperty("os.name").startsWith("SunOS")) { + File dtprofile = new File(System.getProperty("user.home") + + "/.dtprofile"); + String languageEngineInfo = null; + try { + BufferedReader br = new BufferedReader(new FileReader(dtprofile)); + String line = null; + + while ( languageEngineInfo == null && (line = br.readLine()) != null) { + if (line.contains("atok") || line.contains("wnn")) { + StringTokenizer tokens = new StringTokenizer(line); + while (tokens.hasMoreTokens()) { + String token = tokens.nextToken(); + if (Pattern.matches("atok.*setup", token) || + Pattern.matches("wnn.*setup", token)){ + languageEngineInfo = token.substring(0, token.indexOf("setup")); + break; + } + } + } + } + + br.close(); + } catch(IOException ioex) { + // Since this method is provided for internal testing only, + // we dump the stack trace for the ease of debugging. + ioex.printStackTrace(); + } + + imInfo = "htt " + languageEngineInfo; + } + + return imInfo; + } + + + /** + * Performs mapping from an XIM visible feedback value to Java IM highlight. + * @return Java input method highlight + */ + private InputMethodHighlight convertVisualFeedbackToHighlight(int feedback) { + InputMethodHighlight highlight; + + switch (feedback) { + case XIMUnderline: + highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT; + break; + case XIMReverse: + highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT; + break; + case XIMHighlight: + highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT; + break; + case 0: //None of the value is set by Wnn + case XIMPrimary: + highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT; + break; + case XIMSecondary: + highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT; + break; + case XIMTertiary: + highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT; + break; + default: + highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT; + break; + } + return highlight; + } + + // initial capacity size for string buffer, etc. + private static final int INITIAL_SIZE = 64; + + /** + * IntBuffer is an inner class that manipulates an int array and + * provides UNIX file io stream-like programming interfaces to + * access it. (An alternative would be to use ArrayList which may + * be too expensive for the work.) + */ + private final class IntBuffer { + private int[] intArray; + private int size; + private int index; + + IntBuffer(int initialCapacity) { + intArray = new int[initialCapacity]; + size = 0; + index = 0; + } + + void insert(int offset, int[] values) { + int newSize = size + values.length; + if (intArray.length < newSize) { + int[] newIntArray = new int[newSize * 2]; + System.arraycopy(intArray, 0, newIntArray, 0, size); + intArray = newIntArray; + } + System.arraycopy(intArray, offset, intArray, offset+values.length, + size - offset); + System.arraycopy(values, 0, intArray, offset, values.length); + size += values.length; + if (index > offset) + index = offset; + } + + void remove(int offset, int length) { + if (offset + length != size) + System.arraycopy(intArray, offset+length, intArray, offset, + size - offset - length); + size -= length; + if (index > offset) + index = offset; + } + + void replace(int offset, int[] values) { + System.arraycopy(values, 0, intArray, offset, values.length); + } + + void removeAll() { + size = 0; + index = 0; + } + + void rewind() { + index = 0; + } + + int getNext() { + if (index == size) + return -1; + return intArray[index++]; + } + + void unget() { + if (index != 0) + index--; + } + + int getOffset() { + return index; + } + + public String toString() { + StringBuffer s = new StringBuffer(); + for (int i = 0; i < size;) { + s.append(intArray[i++]); + if (i < size) + s.append(","); + } + return s.toString(); + } + } + + /* + * Native methods + */ + private native String resetXIC(); + private native void disposeXIC(); + private native boolean setCompositionEnabledNative(boolean enable); + private native boolean isCompositionEnabledNative(); + private native void turnoffStatusWindow(); + private native void setStatusAreaVisible(boolean value, long data); +}