/*
* Copyright (c) 1997, 2019, 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.windows;
import java.awt.*;
import java.awt.peer.*;
import java.awt.event.*;
import java.awt.im.*;
import java.awt.im.spi.InputMethodContext;
import java.awt.font.*;
import java.text.*;
import java.text.AttributedCharacterIterator.Attribute;
import java.lang.Character.Subset;
import java.lang.Character.UnicodeBlock;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import sun.awt.AWTAccessor;
import sun.awt.AWTAccessor.ComponentAccessor;
import sun.awt.im.InputMethodAdapter;
final class WInputMethod extends InputMethodAdapter
{
/**
* The input method context, which is used to dispatch input method
* events to the client component and to request information from
* the client component.
*/
private InputMethodContext inputContext;
private Component awtFocussedComponent;
private WComponentPeer awtFocussedComponentPeer = null;
private WComponentPeer lastFocussedComponentPeer = null;
private boolean isLastFocussedActiveClient = false;
private boolean isActive;
private int context;
private boolean open; //default open status;
private int cmode; //default conversion mode;
private Locale currentLocale;
// indicate whether status window is hidden or not.
private boolean statusWindowHidden = false;
private boolean hasCompositionString = false;
// attribute definition in Win32 (in IMM.H)
public static final byte ATTR_INPUT = 0x00;
public static final byte ATTR_TARGET_CONVERTED = 0x01;
public static final byte ATTR_CONVERTED = 0x02;
public static final byte ATTR_TARGET_NOTCONVERTED = 0x03;
public static final byte ATTR_INPUT_ERROR = 0x04;
// cmode definition in Win32 (in IMM.H)
public static final int IME_CMODE_ALPHANUMERIC = 0x0000;
public static final int IME_CMODE_NATIVE = 0x0001;
public static final int IME_CMODE_KATAKANA = 0x0002;
public static final int IME_CMODE_LANGUAGE = 0x0003;
public static final int IME_CMODE_FULLSHAPE = 0x0008;
public static final int IME_CMODE_HANJACONVERT = 0x0040;
public static final int IME_CMODE_ROMAN = 0x0010;
// flag values for endCompositionNative() behavior
private static final boolean COMMIT_INPUT = true;
private static final boolean DISCARD_INPUT = false;
private static Map<TextAttribute,Object> [] highlightStyles;
// Initialize highlight mapping table
static {
@SuppressWarnings({"rawtypes", "unchecked"})
Map<TextAttribute,Object>[] styles = new Map[4];
HashMap<TextAttribute,Object> map;
// UNSELECTED_RAW_TEXT_HIGHLIGHT
map = new HashMap<>(1);
map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_DOTTED);
styles[0] = Collections.unmodifiableMap(map);
// SELECTED_RAW_TEXT_HIGHLIGHT
map = new HashMap<>(1);
map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_GRAY);
styles[1] = Collections.unmodifiableMap(map);
// UNSELECTED_CONVERTED_TEXT_HIGHLIGHT
map = new HashMap<>(1);
map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_DOTTED);
styles[2] = Collections.unmodifiableMap(map);
// SELECTED_CONVERTED_TEXT_HIGHLIGHT
map = new HashMap<>(4);
Color navyBlue = new Color(0, 0, 128);
map.put(TextAttribute.FOREGROUND, navyBlue);
map.put(TextAttribute.BACKGROUND, Color.white);
map.put(TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON);
map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
styles[3] = Collections.unmodifiableMap(map);
highlightStyles = styles;
}
public WInputMethod()
{
context = createNativeContext();
cmode = getConversionStatus(context);
open = getOpenStatus(context);
currentLocale = getNativeLocale();
if (currentLocale == null) {
currentLocale = Locale.getDefault();
}
}
@Override
@SuppressWarnings("deprecation")
protected void finalize() throws Throwable
{
// Release the resources used by the native input context.
if (context!=0) {
destroyNativeContext(context);
context=0;
}
super.finalize();
}
@Override
public synchronized void setInputMethodContext(InputMethodContext context) {
inputContext = context;
}
@Override
public void dispose() {
// Due to a memory management problem in Windows 98, we should retain
// the native input context until this object is finalized. So do
// nothing here.
}
/**
* Returns null.
*
* @see java.awt.im.spi.InputMethod#getControlObject
*/
@Override
public Object getControlObject() {
return null;
}
@Override
public boolean setLocale(Locale lang) {
return setLocale(lang, false);
}
private boolean setLocale(Locale lang, boolean onActivate) {
Locale[] available = WInputMethodDescriptor.getAvailableLocalesInternal();
for (int i = 0; i < available.length; i++) {
Locale locale = available[i];
if (lang.equals(locale) ||
// special compatibility rule for Japanese and Korean
locale.equals(Locale.JAPAN) && lang.equals(Locale.JAPANESE) ||
locale.equals(Locale.KOREA) && lang.equals(Locale.KOREAN)) {
if (isActive) {
setNativeLocale(locale.toLanguageTag(), onActivate);
}
currentLocale = locale;
return true;
}
}
return false;
}
@Override
public Locale getLocale() {
if (isActive) {
currentLocale = getNativeLocale();
if (currentLocale == null) {
currentLocale = Locale.getDefault();
}
}
return currentLocale;
}
/**
* Implements InputMethod.setCharacterSubsets for Windows.
*
* @see java.awt.im.spi.InputMethod#setCharacterSubsets
*/
@Override
public void setCharacterSubsets(Subset[] subsets) {
if (subsets == null){
setConversionStatus(context, cmode);
setOpenStatus(context, open);
return;
}
// Use first subset only. Other subsets in array is ignored.
// This is restriction of Win32 implementation.
Subset subset1 = subsets[0];
Locale locale = getNativeLocale();
int newmode;
if (locale == null) {
return;
}
if (locale.getLanguage().equals(Locale.JAPANESE.getLanguage())) {
if (subset1 == UnicodeBlock.BASIC_LATIN || subset1 == InputSubset.LATIN_DIGITS) {
setOpenStatus(context, false);
} else {
if (subset1 == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|| subset1 == InputSubset.KANJI
|| subset1 == UnicodeBlock.HIRAGANA)
newmode = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE;
else if (subset1 == UnicodeBlock.KATAKANA)
newmode = IME_CMODE_NATIVE | IME_CMODE_KATAKANA| IME_CMODE_FULLSHAPE;
else if (subset1 == InputSubset.HALFWIDTH_KATAKANA)
newmode = IME_CMODE_NATIVE | IME_CMODE_KATAKANA;
else if (subset1 == InputSubset.FULLWIDTH_LATIN)
newmode = IME_CMODE_FULLSHAPE;
else
return;
setOpenStatus(context, true);
newmode |= (getConversionStatus(context)&IME_CMODE_ROMAN); // reserve ROMAN input mode
setConversionStatus(context, newmode);
}
} else if (locale.getLanguage().equals(Locale.KOREAN.getLanguage())) {
if (subset1 == UnicodeBlock.BASIC_LATIN || subset1 == InputSubset.LATIN_DIGITS) {
setOpenStatus(context, false);
setConversionStatus(context, IME_CMODE_ALPHANUMERIC);
} else {
if (subset1 == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|| subset1 == InputSubset.HANJA
|| subset1 == UnicodeBlock.HANGUL_SYLLABLES
|| subset1 == UnicodeBlock.HANGUL_JAMO
|| subset1 == UnicodeBlock.HANGUL_COMPATIBILITY_JAMO)
newmode = IME_CMODE_NATIVE;
else if (subset1 == InputSubset.FULLWIDTH_LATIN)
newmode = IME_CMODE_FULLSHAPE;
else
return;
setOpenStatus(context, true);
setConversionStatus(context, newmode);
}
} else if (locale.getLanguage().equals(Locale.CHINESE.getLanguage())) {
if (subset1 == UnicodeBlock.BASIC_LATIN || subset1 == InputSubset.LATIN_DIGITS) {
setOpenStatus(context, false);
newmode = getConversionStatus(context);
newmode &= ~IME_CMODE_FULLSHAPE;
setConversionStatus(context, newmode);
} else {
if (subset1 == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|| subset1 == InputSubset.TRADITIONAL_HANZI
|| subset1 == InputSubset.SIMPLIFIED_HANZI)
newmode = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE;
else if (subset1 == InputSubset.FULLWIDTH_LATIN)
newmode = IME_CMODE_FULLSHAPE;
else
return;
setOpenStatus(context, true);
setConversionStatus(context, newmode);
}
}
}
@Override
public void dispatchEvent(AWTEvent e) {
if (e instanceof ComponentEvent) {
Component comp = ((ComponentEvent) e).getComponent();
if (comp == awtFocussedComponent) {
if (awtFocussedComponentPeer == null ||
awtFocussedComponentPeer.isDisposed()) {
awtFocussedComponentPeer = getNearestNativePeer(comp);
}
if (awtFocussedComponentPeer != null) {
handleNativeIMEEvent(awtFocussedComponentPeer, e);
}
}
}
}
@Override
public void activate() {
boolean isAc = haveActiveClient();
// When the last focussed component peer is different from the
// current focussed component or if they are different client
// (active or passive), disable native IME for the old focussed
// component and enable for the new one.
if (lastFocussedComponentPeer != awtFocussedComponentPeer ||
isLastFocussedActiveClient != isAc) {
if (lastFocussedComponentPeer != null) {
disableNativeIME(lastFocussedComponentPeer);
}
if (awtFocussedComponentPeer != null) {
enableNativeIME(awtFocussedComponentPeer, context, !isAc);
}
lastFocussedComponentPeer = awtFocussedComponentPeer;
isLastFocussedActiveClient = isAc;
}
isActive = true;
if (currentLocale != null) {
setLocale(currentLocale, true);
}
// Compare IM's composition string with Java's composition string
if (hasCompositionString && !isCompositionStringAvailable(context)) {
endCompositionNative(context, DISCARD_INPUT);
sendInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
EventQueue.getMostRecentEventTime(),
null, null, null, null, null, 0, 0, 0);
hasCompositionString = false;
}
/* If the status window or Windows language bar is turned off due to
native input method was switched to java input method, we
have to turn it on otherwise it is gone for good until next time
the user turns it on through Windows Control Panel. See details
from bug 6252674.
*/
if (statusWindowHidden) {
setStatusWindowVisible(awtFocussedComponentPeer, true);
statusWindowHidden = false;
}
}
@Override
public void deactivate(boolean isTemporary)
{
// Sync currentLocale with the Windows keyboard layout which might be changed
// by hot key
getLocale();
// Delay calling disableNativeIME until activate is called and the newly
// focussed component has a different peer as the last focussed component.
if (awtFocussedComponentPeer != null) {
lastFocussedComponentPeer = awtFocussedComponentPeer;
isLastFocussedActiveClient = haveActiveClient();
}
isActive = false;
hasCompositionString = isCompositionStringAvailable(context);
}
/**
* Explicitly disable the native IME. Native IME is not disabled when
* deactivate is called.
*/
@Override
public void disableInputMethod() {
if (lastFocussedComponentPeer != null) {
disableNativeIME(lastFocussedComponentPeer);
lastFocussedComponentPeer = null;
isLastFocussedActiveClient = false;
}
}
/**
* Returns a string with information about the windows input method,
* or null.
*/
@Override
public String getNativeInputMethodInfo() {
return getNativeIMMDescription();
}
/**
* @see sun.awt.im.InputMethodAdapter#stopListening
* This method is called when the input method is swapped out.
* Calling stopListening to give other input method the keybaord input
* focus.
*/
@Override
protected void stopListening() {
// Since the native input method is not disabled when deactivate is
// called, we need to call disableInputMethod to explicitly turn off the
// native IME.
disableInputMethod();
}
// implements sun.awt.im.InputMethodAdapter.setAWTFocussedComponent
@Override
protected void setAWTFocussedComponent(Component component) {
if (component == null) {
return;
}
WComponentPeer peer = getNearestNativePeer(component);
if (isActive) {
// deactivate/activate are being suppressed during a focus change -
// this may happen when an input method window is made visible
if (awtFocussedComponentPeer != null) {
disableNativeIME(awtFocussedComponentPeer);
}
if (peer != null) {
enableNativeIME(peer, context, !haveActiveClient());
}
}
awtFocussedComponent = component;
awtFocussedComponentPeer = peer;
}
// implements java.awt.im.spi.InputMethod.hideWindows
@Override
public void hideWindows() {
if (awtFocussedComponentPeer != null) {
/* Hide the native status window including the Windows language
bar if it is on. One typical senario this method
gets called is when the native input method is
switched to java input method, for example.
*/
setStatusWindowVisible(awtFocussedComponentPeer, false);
statusWindowHidden = true;
}
}
/**
* @see java.awt.im.spi.InputMethod#removeNotify
*/
@Override
public void removeNotify() {
endCompositionNative(context, DISCARD_INPUT);
awtFocussedComponent = null;
awtFocussedComponentPeer = null;
}
/**
* @see java.awt.Toolkit#mapInputMethodHighlight
*/
static Map<TextAttribute,?> 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.supportsBelowTheSpot
@Override
protected boolean supportsBelowTheSpot() {
return true;
}
@Override
public void endComposition()
{
//right now the native endCompositionNative() just cancel
//the composition string, maybe a commtting is desired
endCompositionNative(context,
(haveActiveClient() ? COMMIT_INPUT : DISCARD_INPUT));
}
/**
* @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean)
*/
@Override
public void setCompositionEnabled(boolean enable) {
setOpenStatus(context, enable);
}
/**
* @see java.awt.im.spi.InputMethod#isCompositionEnabled
*/
@Override
public boolean isCompositionEnabled() {
return getOpenStatus(context);
}
public void sendInputMethodEvent(int id, long when, String text,
int[] clauseBoundary, String[] clauseReading,
int[] attributeBoundary, byte[] attributeValue,
int commitedTextLength, int caretPos, int visiblePos)
{
AttributedCharacterIterator iterator = null;
if (text!=null) {
// construct AttributedString
AttributedString attrStr = new AttributedString(text);
// set Language Information
attrStr.addAttribute(Attribute.LANGUAGE,
Locale.getDefault(), 0, text.length());
// set Clause and Reading Information
if (clauseBoundary!=null && clauseReading!=null &&
clauseReading.length!=0 && clauseBoundary.length==clauseReading.length+1 &&
clauseBoundary[0]==0 && clauseBoundary[clauseReading.length]<=text.length() )
{
for (int i=0; i<clauseBoundary.length-1; i++) {
attrStr.addAttribute(Attribute.INPUT_METHOD_SEGMENT,
new Annotation(null), clauseBoundary[i], clauseBoundary[i+1]);
attrStr.addAttribute(Attribute.READING,
new Annotation(clauseReading[i]), clauseBoundary[i], clauseBoundary[i+1]);
}
} else {
// if (clauseBoundary != null)
// System.out.println("Invalid clause information!");
attrStr.addAttribute(Attribute.INPUT_METHOD_SEGMENT,
new Annotation(null), 0, text.length());
attrStr.addAttribute(Attribute.READING,
new Annotation(""), 0, text.length());
}
// set Hilight Information
if (attributeBoundary!=null && attributeValue!=null &&
attributeValue.length!=0 && attributeBoundary.length==attributeValue.length+1 &&
attributeBoundary[0]==0 && attributeBoundary[attributeValue.length]==text.length() )
{
for (int i=0; i<attributeBoundary.length-1; i++) {
InputMethodHighlight highlight;
switch (attributeValue[i]) {
case ATTR_TARGET_CONVERTED:
highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
break;
case ATTR_CONVERTED:
highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;
break;
case ATTR_TARGET_NOTCONVERTED:
highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
break;
case ATTR_INPUT:
case ATTR_INPUT_ERROR:
default:
highlight = InputMethodHighlight.UNSELECTED_RAW_TEXT_HIGHLIGHT;
break;
}
attrStr.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
highlight,
attributeBoundary[i], attributeBoundary[i+1]);
}
} else {
// if (attributeBoundary != null)
// System.out.println("Invalid attribute information!");
attrStr.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT,
0, text.length());
}
// get iterator
iterator = attrStr.getIterator();
}
Component source = getClientComponent();
if (source == null)
return;
InputMethodEvent event = new InputMethodEvent(source,
id,
when,
iterator,
commitedTextLength,
TextHitInfo.leading(caretPos),
TextHitInfo.leading(visiblePos));
WToolkit.postEvent(WToolkit.targetToAppContext(source), event);
}
public void inquireCandidatePosition()
{
Component source = getClientComponent();
if (source == null) {
return;
}
// This call should return immediately just to cause
// InputMethodRequests.getTextLocation be called within
// AWT Event thread. Otherwise, a potential deadlock
// could happen.
Runnable r = new Runnable() {
@Override
public void run() {
int x = 0;
int y = 0;
Component client = getClientComponent();
if (client != null) {
/**代码未完, 请加载全部代码(NowJava.com).**/