001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Dmitry A. Durnev
019: * @version $Revision$
020: */package org.apache.harmony.awt.wtk.windows;
021:
022: import java.awt.AWTException;
023: import java.awt.im.spi.InputMethod;
024: import java.util.HashMap;
025: import java.util.Locale;
026:
027: import org.apache.harmony.awt.ContextStorage;
028: import org.apache.harmony.awt.im.IMManager;
029: import org.apache.harmony.awt.im.InputMethodContext;
030: import org.apache.harmony.awt.nativebridge.Int16Pointer;
031: import org.apache.harmony.awt.nativebridge.Int8Pointer;
032: import org.apache.harmony.awt.nativebridge.NativeBridge;
033: import org.apache.harmony.awt.nativebridge.PointerPointer;
034: import org.apache.harmony.awt.nativebridge.windows.Win32;
035: import org.apache.harmony.awt.wtk.NativeIM;
036:
037: import org.apache.harmony.awt.nativebridge.windows.WindowsDefs;
038:
039: /**
040: * Windows-specific native input method
041: * functionality
042: */
043: public class WinIM extends NativeIM {
044:
045: private static final NativeBridge nb = NativeBridge.getInstance();
046: private static final Win32 win32 = Win32.getInstance();
047: private static final WinWindowFactory wwf = (WinWindowFactory) ContextStorage
048: .getWindowFactory();
049: private static final HashMap<Long, Locale> hkl2Locale = new HashMap<Long, Locale>();;
050: private static final HashMap<Locale, Long> locale2HKL = new HashMap<Locale, Long>();
051: private Locale curLocale; // last locale set by user
052: private long hIMC; // private native input context handle
053: private long defaultIMC;
054:
055: WinIM() {
056: WinEventQueue.Task task = new WinEventQueue.Task() {
057: @Override
058: public void perform() {
059: hIMC = win32.ImmCreateContext();
060: }
061: };
062: wwf.eventQueue.performTask(task);
063: }
064:
065: @Override
066: public Locale[] getAvailableLocales() throws AWTException {
067: int nBuff = win32.GetKeyboardLayoutList(0, null);
068: PointerPointer buffPtr = nb.createPointerPointer(nBuff, false);
069: nBuff = win32.GetKeyboardLayoutList(nBuff, buffPtr);
070: for (int i = 0; i < nBuff; i++) {
071: long hkl = buffPtr.getElementPointer(i).getAddress(0);
072: hkl2Locale(hkl);
073: }
074:
075: return locale2HKL.keySet().toArray(new Locale[0]);
076: }
077:
078: @Override
079: public boolean setLocale(final Locale locale) {
080: if (getLocale().equals(locale)) {
081: curLocale = locale;
082: return true;
083: }
084: WinEventQueue.Task task = new WinEventQueue.Task() {
085: @Override
086: public void perform() {
087: long hkl = locale2HKL(locale);
088: int flags = 0;
089: boolean res = (win32.ActivateKeyboardLayout(hkl, flags) != 0);
090: returnValue = Boolean.valueOf(res);
091: }
092: };
093: wwf.eventQueue.performTask(task);
094: boolean result = ((Boolean) task.returnValue).booleanValue();
095: if (result) {
096: curLocale = locale;
097: }
098: return result;
099: }
100:
101: @Override
102: public Locale getLocale() {
103:
104: WinEventQueue.Task task = new WinEventQueue.Task() {
105: @Override
106: public void perform() {
107: returnValue = new Long(win32.GetKeyboardLayout(0));
108: }
109: };
110: wwf.eventQueue.performTask(task);
111: Long hkl = (Long) task.returnValue;
112: return hkl2Locale(hkl.longValue());
113: }
114:
115: private static int makeLCID(short langid, short sortid) {
116: return ((sortid << 16) | langid);
117:
118: }
119:
120: private static String getLocaleInfo(int lcid, int lcType) {
121: int size = 6;
122: Int16Pointer lpLCData = nb.createInt16Pointer(size, false);
123: size = win32.GetLocaleInfoW(lcid, lcType, lpLCData, size);
124: return lpLCData.getString();
125: }
126:
127: /**
128: * convert LANGID(16 low-order bits of HKL) into Locale instance
129: */
130: private static Locale hkl2Locale(long hkl) {
131: Long key = new Long(hkl);
132: if (hkl2Locale.containsKey(key)) {
133: return hkl2Locale.get(key);
134: }
135: short langid = (short) hkl;
136: short sortid = WindowsDefs.SORT_DEFAULT;
137: int lcid = makeLCID(langid, sortid);
138: String country = getLocaleInfo(lcid,
139: WindowsDefs.LOCALE_SISO3166CTRYNAME);
140: String language = getLocaleInfo(lcid,
141: WindowsDefs.LOCALE_SISO639LANGNAME);
142: Locale locale = new Locale(language, country);
143: hkl2Locale.put(key, locale);
144: locale2HKL.put(locale, key);
145: return locale;
146: }
147:
148: /**
149: * convert Locale instance to HKL
150: *
151: * @param locale Locale to get HKL for
152: * @return HKL identifier of the given locale
153: */
154: private static long locale2HKL(Locale locale) {
155: // there's no native functionality to get
156: // lcid from locale name
157: // maybe have to call getAvailableLocales()
158: // before to update map(?)
159: Long hkl = locale2HKL.get(locale);
160: if (hkl != null) {
161: return hkl.longValue();
162: }
163: return 0l;
164: }
165:
166: /**
167: * Must create new instance of this IM for
168: * every instance of input context
169: */
170: @Override
171: public InputMethod createInputMethod() throws Exception {
172: return new WinIM();
173: }
174:
175: @Override
176: public void activate() {
177: // reassociate focused window with
178: // default native input context
179: // if IME was previously disabled
180: WinEventQueue.Task task = new WinEventQueue.Task() {
181: @Override
182: public void perform() {
183: final long hwnd = win32.GetFocus();
184: if (hwnd == 0l) {
185: return;
186: }
187: long curIMC = win32.ImmGetContext(hwnd);
188: if ((curIMC != 0) && isActiveClient()) {
189: // close composition window
190: // opened by passive client
191: win32.ImmSetOpenStatus(curIMC, 0);
192: }
193: if (curIMC != hIMC) {
194: long res = win32.ImmAssociateContext(hwnd, hIMC);
195:
196: if (res != 0l) {
197: defaultIMC = res;
198: }
199: returnValue = new Long(res);
200:
201: } else {
202: // have to change input context on every
203: // activation to be able to process IME
204: // messages without showing default composition
205: // window for active clients
206:
207: win32.ImmAssociateContext(hwnd, defaultIMC);
208: }
209:
210: win32.ImmReleaseContext(hwnd, curIMC);
211: }
212:
213: };
214: wwf.eventQueue.performTask(task);
215:
216: if (curLocale != null) {
217: setLocale(curLocale);
218: }
219: }
220:
221: /**
222: * Is called before the IME generates the composition string
223: * as a result of a keystroke.
224: * @param hwnd owner of the composition window
225: * @return false if the default composition window
226: * should be opened, true otherwise
227: */
228: static boolean onStartComposition(long hwnd) {
229: long hIMC = win32.ImmGetContext(hwnd);
230: boolean active = isActiveClient();
231:
232: if ((hIMC != 0l) && !active) {
233: setDefaultCompositionWindow(hIMC);
234: }
235: win32.ImmReleaseContext(hwnd, hIMC);
236: return active;
237: }
238:
239: /**
240: * Is called when IME composition string changes
241: * @param hwnd window where composition occurs
242: * @param idx specifies how the composition string changed
243: * @return true if message was processed by IM and
244: * no default processing is required
245: */
246: static boolean onComposition(long hwnd, long idx) {
247: // TODO: convert composition string change event
248: // to Java InputMethodEvent and dispatch it
249: // to active client(or post it to the EventQueue?)
250: long hIMC = win32.ImmGetContext(hwnd);
251:
252: if (hIMC != 0l) {
253: if ((idx & WindowsDefs.GCS_COMPATTR) != 0) {
254: int size = 0;
255: size = win32.ImmGetCompositionStringW(hIMC,
256: WindowsDefs.GCS_COMPATTR, 0l, size);
257: size += 2; // 0-terminator
258:
259: Int8Pointer lpBuf = nb.createInt8Pointer(size, false);
260: win32.ImmGetCompositionStringW(hIMC,
261: WindowsDefs.GCS_COMPATTR, lpBuf, size);
262: processAttributes(size - 2, lpBuf);
263: }
264: if ((idx & WindowsDefs.GCS_COMPCLAUSE) != 0) {
265: }
266: if ((idx & WindowsDefs.GCS_COMPREADATTR) != 0) {
267: }
268: if ((idx & WindowsDefs.GCS_COMPREADCLAUSE) != 0) {
269: }
270: if ((idx & WindowsDefs.GCS_COMPREADSTR) != 0) {
271: }
272: if ((idx & WindowsDefs.GCS_COMPSTR) != 0) {
273: }
274: if ((idx & WindowsDefs.GCS_CURSORPOS) != 0) {
275: }
276: if ((idx & WindowsDefs.GCS_DELTASTART) != 0) {
277: }
278: if ((idx & WindowsDefs.GCS_RESULTCLAUSE) != 0) {
279: }
280: if ((idx & WindowsDefs.GCS_RESULTREADCLAUSE) != 0) {
281: }
282: if ((idx & WindowsDefs.GCS_RESULTREADSTR) != 0) {
283: }
284: if ((idx & WindowsDefs.GCS_RESULTSTR) != 0) {
285: }
286: }
287: win32.ImmReleaseContext(hwnd, hIMC);
288: return isActiveClient();
289: }
290:
291: private static void processAttributes(int size, Int8Pointer lpBuf) {
292: // TODO: convert windows IM attributes to
293: // AttributedCharacterIterator attributes
294: for (int i = 0; i < size; i++) {
295: byte attr = lpBuf.get(i);
296: String strAttr = ""; //$NON-NLS-1$
297: switch (attr) {
298: case WindowsDefs.ATTR_INPUT:
299: strAttr = "INP"; //$NON-NLS-1$
300: break;
301: case WindowsDefs.ATTR_INPUT_ERROR:
302: strAttr = "IE"; //$NON-NLS-1$
303: break;
304: case WindowsDefs.ATTR_TARGET_CONVERTED:
305: strAttr = "T_CONV"; //$NON-NLS-1$
306: break;
307: case WindowsDefs.ATTR_CONVERTED:
308: strAttr = "CONV"; //$NON-NLS-1$
309: break;
310: case WindowsDefs.ATTR_TARGET_NOTCONVERTED:
311: strAttr = "T_NCONV"; //$NON-NLS-1$
312: break;
313: case WindowsDefs.ATTR_FIXEDCONVERTED:
314: strAttr = "FIX_CONV"; //$NON-NLS-1$
315: break;
316: }
317: }
318: }
319:
320: /**
321: * sets IME composition window position/style
322: * for passive clients
323: */
324: private static int setDefaultCompositionWindow(long hIMC) {
325: Win32.COMPOSITIONFORM form = win32.createCOMPOSITIONFORM(false);
326: form.set_dwStyle(WindowsDefs.CFS_DEFAULT);
327: return win32.ImmSetCompositionWindow(hIMC, form);
328: }
329:
330: @Override
331: public void removeNotify() {
332: disableIME();
333: }
334:
335: @Override
336: public void dispose() {
337: WinEventQueue.Task task = new WinEventQueue.Task() {
338: @Override
339: public void perform() {
340: win32.ImmDestroyContext(hIMC);
341: }
342: };
343: wwf.eventQueue.performTask(task);
344: }
345:
346: /**
347: * Disables native input method support for the
348: * focused window
349: */
350: @Override
351: public void disableIME() {
352: WinEventQueue.Task task = new WinEventQueue.Task() {
353: @Override
354: public void perform() {
355: final long hwnd = win32.GetFocus();
356: long curIMC = win32.ImmGetContext(hwnd);
357: if (curIMC != 0l) {
358: win32.ImmAssociateContext(hwnd, 0l);
359: }
360: win32.ImmReleaseContext(hwnd, curIMC);
361: returnValue = new Long(hwnd);
362: }
363:
364: };
365: wwf.eventQueue.performTask(task);
366: }
367:
368: /**
369: * Is called when user chooses the new input language via
370: * native system interface, i. e. with the hotkey or
371: * from the indicator on the system taskbar.
372: */
373: static void onInputLangChange(long lcid) {
374: // remember the new locale as selected in the
375: // input context of the focused component
376: InputMethodContext imc = IMManager.getLastActiveIMC();
377: if (imc == null) {
378: return;
379: }
380: InputMethod im = imc.getInputMethod();
381: if (im instanceof NativeIM) {
382: im.setLocale(hkl2Locale(lcid));
383: }
384:
385: }
386:
387: private static boolean isActiveClient() {
388: InputMethodContext imc = IMManager.getLastActiveIMC();
389: if ((imc == null) || (imc.getClient() == null)) {
390: return false;
391: }
392: return (imc.getClient().getInputMethodRequests() != null);
393: }
394:
395: /**
396: * Is called when input context is activated.
397: * @return false if IME default composition window
398: * should be activated for input context, true
399: * otherwise
400: */
401: static boolean onActivateContext(long hwnd) {
402: boolean result = isActiveClient();
403: return result;
404: }
405: }
|