1 /*
2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26
27 package sun.awt.windows;
28
29 import java.awt.*;
30 import java.awt.peer.*;
31 import java.awt.event.*;
32 import java.awt.im.*;
33 import java.awt.im.spi.InputMethodContext;
34 import java.awt.font.*;
35 import java.text.*;
36 import java.text.AttributedCharacterIterator.Attribute;
37 import java.lang.Character.Subset;
38 import java.lang.Character.UnicodeBlock;
39 import java.util.Collections;
40 import java.util.HashMap;
41 import java.util.Locale;
42 import java.util.Map;
43 import sun.awt.im.InputMethodAdapter;
44
45 final class WInputMethod extends InputMethodAdapter
46 {
47 /**
48 * The input method context, which is used to dispatch input method
49 * events to the client component and to request information from
50 * the client component.
51 */
52 private InputMethodContext inputContext;
53
54 private Component awtFocussedComponent;
55 private WComponentPeer awtFocussedComponentPeer = null;
56 private WComponentPeer lastFocussedComponentPeer = null;
57 private boolean isLastFocussedActiveClient = false;
58 private boolean isActive;
59 private int context;
60 private boolean open; //default open status;
61 private int cmode; //default conversion mode;
62 private Locale currentLocale;
63 // indicate whether status window is hidden or not.
64 private boolean statusWindowHidden = false;
65
66 // attribute definition in Win32 (in IMM.H)
67 public final static byte ATTR_INPUT = 0x00;
68 public final static byte ATTR_TARGET_CONVERTED = 0x01;
69 public final static byte ATTR_CONVERTED = 0x02;
70 public final static byte ATTR_TARGET_NOTCONVERTED = 0x03;
71 public final static byte ATTR_INPUT_ERROR = 0x04;
72 // cmode definition in Win32 (in IMM.H)
73 public final static int IME_CMODE_ALPHANUMERIC = 0x0000;
74 public final static int IME_CMODE_NATIVE = 0x0001;
75 public final static int IME_CMODE_KATAKANA = 0x0002;
76 public final static int IME_CMODE_LANGUAGE = 0x0003;
77 public final static int IME_CMODE_FULLSHAPE = 0x0008;
78 public final static int IME_CMODE_HANJACONVERT = 0x0040;
79 public final static int IME_CMODE_ROMAN = 0x0010;
80
81 // flag values for endCompositionNative() behavior
82 private final static boolean COMMIT_INPUT = true;
83 private final static boolean DISCARD_INPUT = false;
84
85 private static Map<TextAttribute,Object> [] highlightStyles;
86
87 // Initialize highlight mapping table
88 static {
89 Map<TextAttribute,Object> styles[] = new Map[4];
90 HashMap<TextAttribute,Object> map;
91
92 // UNSELECTED_RAW_TEXT_HIGHLIGHT
93 map = new HashMap(1);
94 map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_DOTTED);
95 styles[0] = Collections.unmodifiableMap(map);
96
97 // SELECTED_RAW_TEXT_HIGHLIGHT
98 map = new HashMap(1);
99 map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_GRAY);
100 styles[1] = Collections.unmodifiableMap(map);
101
102 // UNSELECTED_CONVERTED_TEXT_HIGHLIGHT
103 map = new HashMap(1);
104 map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_DOTTED);
105 styles[2] = Collections.unmodifiableMap(map);
106
107 // SELECTED_CONVERTED_TEXT_HIGHLIGHT
108 map = new HashMap(4);
109 Color navyBlue = new Color(0, 0, 128);
110 map.put(TextAttribute.FOREGROUND, navyBlue);
111 map.put(TextAttribute.BACKGROUND, Color.white);
112 map.put(TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON);
113 map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
114 styles[3] = Collections.unmodifiableMap(map);
115
116 highlightStyles = styles;
117 }
118
119 public WInputMethod()
120 {
121 context = createNativeContext();
122 cmode = getConversionStatus(context);
123 open = getOpenStatus(context);
124 currentLocale = getNativeLocale();
125 if (currentLocale == null) {
126 currentLocale = Locale.getDefault();
127 }
128 }
129
130 @Override
131 protected void finalize() throws Throwable
132 {
133 // Release the resources used by the native input context.
134 if (context!=0) {
135 destroyNativeContext(context);
136 context=0;
137 }
138 super.finalize();
139 }
140
141 @Override
142 public synchronized void setInputMethodContext(InputMethodContext context) {
143 inputContext = context;
144 }
145
146 @Override
147 public final void dispose() {
148 // Due to a memory management problem in Windows 98, we should retain
149 // the native input context until this object is finalized. So do
150 // nothing here.
151 }
152
153 /**
154 * Returns null.
155 *
156 * @see java.awt.im.spi.InputMethod#getControlObject
157 */
158 @Override
159 public Object getControlObject() {
160 return null;
161 }
162
163 @Override
164 public boolean setLocale(Locale lang) {
165 return setLocale(lang, false);
166 }
167
168 private boolean setLocale(Locale lang, boolean onActivate) {
169 Locale[] available = WInputMethodDescriptor.getAvailableLocalesInternal();
170 for (int i = 0; i < available.length; i++) {
171 Locale locale = available[i];
172 if (lang.equals(locale) ||
173 // special compatibility rule for Japanese and Korean
174 locale.equals(Locale.JAPAN) && lang.equals(Locale.JAPANESE) ||
175 locale.equals(Locale.KOREA) && lang.equals(Locale.KOREAN)) {
176 if (isActive) {
177 setNativeLocale(locale.toLanguageTag(), onActivate);
178 }
179 currentLocale = locale;
180 return true;
181 }
182 }
183 return false;
184 }
185
186 @Override
187 public Locale getLocale() {
188 if (isActive) {
189 currentLocale = getNativeLocale();
190 if (currentLocale == null) {
191 currentLocale = Locale.getDefault();
192 }
193 }
194 return currentLocale;
195 }
196
197 /**
198 * Implements InputMethod.setCharacterSubsets for Windows.
199 *
200 * @see java.awt.im.spi.InputMethod#setCharacterSubsets
201 */
202 @Override
203 public void setCharacterSubsets(Subset[] subsets) {
204 if (subsets == null){
205 setConversionStatus(context, cmode);
206 setOpenStatus(context, open);
207 return;
208 }
209
210 // Use first subset only. Other subsets in array is ignored.
211 // This is restriction of Win32 implementation.
212 Subset subset1 = subsets[0];
213
214 Locale locale = getNativeLocale();
215 int newmode;
216
217 if (locale == null) {
218 return;
219 }
220
221 if (locale.getLanguage().equals(Locale.JAPANESE.getLanguage())) {
222 if (subset1 == UnicodeBlock.BASIC_LATIN || subset1 == InputSubset.LATIN_DIGITS) {
223 setOpenStatus(context, false);
224 } else {
225 if (subset1 == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
226 || subset1 == InputSubset.KANJI
227 || subset1 == UnicodeBlock.HIRAGANA)
228 newmode = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE;
229 else if (subset1 == UnicodeBlock.KATAKANA)
230 newmode = IME_CMODE_NATIVE | IME_CMODE_KATAKANA| IME_CMODE_FULLSHAPE;
231 else if (subset1 == InputSubset.HALFWIDTH_KATAKANA)
232 newmode = IME_CMODE_NATIVE | IME_CMODE_KATAKANA;
233 else if (subset1 == InputSubset.FULLWIDTH_LATIN)
234 newmode = IME_CMODE_FULLSHAPE;
235 else
236 return;
237 setOpenStatus(context, true);
238 newmode |= (getConversionStatus(context)&IME_CMODE_ROMAN); // reserve ROMAN input mode
239 setConversionStatus(context, newmode);
240 }
241 } else if (locale.getLanguage().equals(Locale.KOREAN.getLanguage())) {
242 if (subset1 == UnicodeBlock.BASIC_LATIN || subset1 == InputSubset.LATIN_DIGITS) {
243 setOpenStatus(context, false);
244 } else {
245 if (subset1 == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
246 || subset1 == InputSubset.HANJA
247 || subset1 == UnicodeBlock.HANGUL_SYLLABLES
248 || subset1 == UnicodeBlock.HANGUL_JAMO
249 || subset1 == UnicodeBlock.HANGUL_COMPATIBILITY_JAMO)
250 newmode = IME_CMODE_NATIVE;
251 else if (subset1 == InputSubset.FULLWIDTH_LATIN)
252 newmode = IME_CMODE_FULLSHAPE;
253 else
254 return;
255 setOpenStatus(context, true);
256 setConversionStatus(context, newmode);
257 }
258 } else if (locale.getLanguage().equals(Locale.CHINESE.getLanguage())) {
259 if (subset1 == UnicodeBlock.BASIC_LATIN || subset1 == InputSubset.LATIN_DIGITS) {
260 setOpenStatus(context, false);
261 } else {
262 if (subset1 == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
263 || subset1 == InputSubset.TRADITIONAL_HANZI
264 || subset1 == InputSubset.SIMPLIFIED_HANZI)
265 newmode = IME_CMODE_NATIVE;
266 else if (subset1 == InputSubset.FULLWIDTH_LATIN)
267 newmode = IME_CMODE_FULLSHAPE;
268 else
269 return;
270 setOpenStatus(context, true);
271 setConversionStatus(context, newmode);
272 }
273 }
274 }
275
276 @Override
277 public void dispatchEvent(AWTEvent e) {
278 if (e instanceof ComponentEvent) {
279 Component comp = ((ComponentEvent) e).getComponent();
280 if (comp == awtFocussedComponent) {
281 if (awtFocussedComponentPeer == null ||
282 awtFocussedComponentPeer.isDisposed()) {
283 awtFocussedComponentPeer = getNearestNativePeer(comp);
284 }
285 if (awtFocussedComponentPeer != null) {
286 handleNativeIMEEvent(awtFocussedComponentPeer, e);
287 }
288 }
289 }
290 }
291
292 @Override
293 public void activate() {
294 boolean isAc = haveActiveClient();
295
296 // When the last focussed component peer is different from the
297 // current focussed component or if they are different client
298 // (active or passive), disable native IME for the old focussed
299 // component and enable for the new one.
300 if (lastFocussedComponentPeer != awtFocussedComponentPeer ||
301 isLastFocussedActiveClient != isAc) {
302 if (lastFocussedComponentPeer != null) {
303 disableNativeIME(lastFocussedComponentPeer);
304 }
305 if (awtFocussedComponentPeer != null) {
306 enableNativeIME(awtFocussedComponentPeer, context, !isAc);
307 }
308 lastFocussedComponentPeer = awtFocussedComponentPeer;
309 isLastFocussedActiveClient = isAc;
310 }
311 isActive = true;
312 if (currentLocale != null) {
313 setLocale(currentLocale, true);
314 }
315
316 /* If the status window or Windows language bar is turned off due to
317 native input method was switched to java input method, we
318 have to turn it on otherwise it is gone for good until next time
319 the user turns it on through Windows Control Panel. See details
320 from bug 6252674.
321 */
322 if (statusWindowHidden) {
323 setStatusWindowVisible(awtFocussedComponentPeer, true);
324 statusWindowHidden = false;
325 }
326
327 }
328
329 @Override
330 public void deactivate(boolean isTemporary)
331 {
332 // Sync currentLocale with the Windows keyboard layout which might be changed
333 // by hot key
334 getLocale();
335
336 // Delay calling disableNativeIME until activate is called and the newly
337 // focussed component has a different peer as the last focussed component.
338 if (awtFocussedComponentPeer != null) {
339 lastFocussedComponentPeer = awtFocussedComponentPeer;
340 isLastFocussedActiveClient = haveActiveClient();
341 }
342 isActive = false;
343 }
344
345 /**
346 * Explicitly disable the native IME. Native IME is not disabled when
347 * deactivate is called.
348 */
349 @Override
350 public void disableInputMethod() {
351 if (lastFocussedComponentPeer != null) {
352 disableNativeIME(lastFocussedComponentPeer);
353 lastFocussedComponentPeer = null;
354 isLastFocussedActiveClient = false;
355 }
356 }
357
358 /**
359 * Returns a string with information about the windows input method,
360 * or null.
361 */
362 @Override
363 public String getNativeInputMethodInfo() {
364 return getNativeIMMDescription();
365 }
366
367 /**
368 * @see sun.awt.im.InputMethodAdapter#stopListening
369 * This method is called when the input method is swapped out.
370 * Calling stopListening to give other input method the keybaord input
371 * focus.
372 */
373 @Override
374 protected void stopListening() {
375 // Since the native input method is not disabled when deactivate is
376 // called, we need to call disableInputMethod to explicitly turn off the
377 // native IME.
378 disableInputMethod();
379 }
380
381 // implements sun.awt.im.InputMethodAdapter.setAWTFocussedComponent
382 @Override
383 protected void setAWTFocussedComponent(Component component) {
384 if (component == null) {
385 return;
386 }
387 WComponentPeer peer = getNearestNativePeer(component);
388 if (isActive) {
389 // deactivate/activate are being suppressed during a focus change -
390 // this may happen when an input method window is made visible
391 if (awtFocussedComponentPeer != null) {
392 disableNativeIME(awtFocussedComponentPeer);
393 }
394 if (peer != null) {
395 enableNativeIME(peer, context, !haveActiveClient());
396 }
397 }
398 awtFocussedComponent = component;
399 awtFocussedComponentPeer = peer;
400 }
401
402 // implements java.awt.im.spi.InputMethod.hideWindows
403 @Override
404 public void hideWindows() {
405 if (awtFocussedComponentPeer != null) {
406 /* Hide the native status window including the Windows language
407 bar if it is on. One typical senario this method
408 gets called is when the native input method is
409 switched to java input method, for example.
410 */
411 setStatusWindowVisible(awtFocussedComponentPeer, false);
412 statusWindowHidden = true;
413 }
414 }
415
416 /**
417 * @see java.awt.im.spi.InputMethod#removeNotify
418 */
419 @Override
420 public void removeNotify() {
421 endCompositionNative(context, DISCARD_INPUT);
422 awtFocussedComponent = null;
423 awtFocussedComponentPeer = null;
424 }
425
426 /**
427 * @see java.awt.Toolkit#mapInputMethodHighlight
428 */
429 static Map<TextAttribute,?> mapInputMethodHighlight(InputMethodHighlight highlight) {
430 int index;
431 int state = highlight.getState();
432 if (state == InputMethodHighlight.RAW_TEXT) {
433 index = 0;
434 } else if (state == InputMethodHighlight.CONVERTED_TEXT) {
435 index = 2;
436 } else {
437 return null;
438 }
439 if (highlight.isSelected()) {
440 index += 1;
441 }
442 return highlightStyles[index];
443 }
444
445 // see sun.awt.im.InputMethodAdapter.supportsBelowTheSpot
446 @Override
447 protected boolean supportsBelowTheSpot() {
448 return true;
449 }
450
451 @Override
452 public void endComposition()
453 {
454 //right now the native endCompositionNative() just cancel
455 //the composition string, maybe a commtting is desired
456 endCompositionNative(context,
457 (haveActiveClient() ? COMMIT_INPUT : DISCARD_INPUT));
458 }
459
460 /**
461 * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean)
462 */
463 @Override
464 public void setCompositionEnabled(boolean enable) {
465 setOpenStatus(context, enable);
466 }
467
468 /**
469 * @see java.awt.im.spi.InputMethod#isCompositionEnabled
470 */
471 @Override
472 public boolean isCompositionEnabled() {
473 return getOpenStatus(context);
474 }
475
476 public void sendInputMethodEvent(int id, long when, String text,
477 int[] clauseBoundary, String[] clauseReading,
478 int[] attributeBoundary, byte[] attributeValue,
479 int commitedTextLength, int caretPos, int visiblePos)
480 {
481
482 AttributedCharacterIterator iterator = null;
483
484 if (text!=null) {
485
486 // construct AttributedString
487 AttributedString attrStr = new AttributedString(text);
488
489 // set Language Information
490 attrStr.addAttribute(Attribute.LANGUAGE,
491 Locale.getDefault(), 0, text.length());
492
493 // set Clause and Reading Information
494 if (clauseBoundary!=null && clauseReading!=null &&
495 clauseReading.length!=0 && clauseBoundary.length==clauseReading.length+1 &&
496 clauseBoundary[0]==0 && clauseBoundary[clauseReading.length]<=text.length() )
497 {
498 for (int i=0; i<clauseBoundary.length-1; i++) {
499 attrStr.addAttribute(Attribute.INPUT_METHOD_SEGMENT,
500 new Annotation(null), clauseBoundary[i], clauseBoundary[i+1]);
501 attrStr.addAttribute(Attribute.READING,
502 new Annotation(clauseReading[i]), clauseBoundary[i], clauseBoundary[i+1]);
503 }
504 } else {
505 // if (clauseBoundary != null)
506 // System.out.println("Invalid clause information!");
507
508 attrStr.addAttribute(Attribute.INPUT_METHOD_SEGMENT,
509 new Annotation(null), 0, text.length());
510 attrStr.addAttribute(Attribute.READING,
511 new Annotation(""), 0, text.length());
512 }
513
514 // set Hilight Information
515 if (attributeBoundary!=null && attributeValue!=null &&
516 attributeValue.length!=0 && attributeBoundary.length==attributeValue.length+1 &&
517 attributeBoundary[0]==0 && attributeBoundary[attributeValue.length]==text.length() )
518 {
519 for (int i=0; i<attributeBoundary.length-1; i++) {
520 InputMethodHighlight highlight;
521 switch (attributeValue[i]) {
522 case ATTR_TARGET_CONVERTED:
523 highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
524 break;
525 case ATTR_CONVERTED:
526 highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;
527 break;
528 case ATTR_TARGET_NOTCONVERTED:
529 highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
530 break;
531 case ATTR_INPUT:
532 case ATTR_INPUT_ERROR:
533 default:
534 highlight = InputMethodHighlight.UNSELECTED_RAW_TEXT_HIGHLIGHT;
535 break;
536 }
537 attrStr.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
538 highlight,
539 attributeBoundary[i], attributeBoundary[i+1]);
540 }
541 } else {
542 // if (attributeBoundary != null)
543 // System.out.println("Invalid attribute information!");
544
545 attrStr.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
546 InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT,
547 0, text.length());
548 }
549
550 // get iterator
551 iterator = attrStr.getIterator();
552
553 }
554
555 Component source = getClientComponent();
556 if (source == null)
557 return;
558
559 InputMethodEvent event = new InputMethodEvent(source,
560 id,
561 when,
562 iterator,
563 commitedTextLength,
564 TextHitInfo.leading(caretPos),
565 TextHitInfo.leading(visiblePos));
566 WToolkit.postEvent(WToolkit.targetToAppContext(source), event);
567 }
568
569 public void inquireCandidatePosition()
570 {
571 Component source = getClientComponent();
572 if (source == null) {
573 return;
574 }
575 // This call should return immediately just to cause
576 // InputMethodRequests.getTextLocation be called within
577 // AWT Event thread. Otherwise, a potential deadlock
578 // could happen.
579 Runnable r = new Runnable() {
580 @Override
581 public void run() {
582 int x = 0;
583 int y = 0;
584 Component client = getClientComponent();
585
586 if (client != null) {
587 if (haveActiveClient()) {
588 Rectangle rc = inputContext.getTextLocation(TextHitInfo.leading(0));
589 x = rc.x;
590 y = rc.y + rc.height;
591 } else {
592 Point pt = client.getLocationOnScreen();
593 Dimension size = client.getSize();
594 x = pt.x;
595 y = pt.y + size.height;
596 }
597 }
598
599 openCandidateWindow(awtFocussedComponentPeer, x, y);
600 }
601 };
602 WToolkit.postEvent(WToolkit.targetToAppContext(source),
603 new InvocationEvent(source, r));
604 }
605
606 // java.awt.Toolkit#getNativeContainer() is not available
607 // from this package
608 private WComponentPeer getNearestNativePeer(Component comp)
609 {
610 if (comp==null) return null;
611
612 ComponentPeer peer = comp.getPeer();
613 if (peer==null) return null;
614
615 while (peer instanceof java.awt.peer.LightweightPeer) {
616 comp = comp.getParent();
617 if (comp==null) return null;
618 peer = comp.getPeer();
619 if (peer==null) return null;
620 }
621
622 if (peer instanceof WComponentPeer)
623 return (WComponentPeer)peer;
624 else
625 return null;
626
627 }
628
629 private native int createNativeContext();
630 private native void destroyNativeContext(int context);
631 private native void enableNativeIME(WComponentPeer peer, int context, boolean useNativeCompWindow);
632 private native void disableNativeIME(WComponentPeer peer);
633 private native void handleNativeIMEEvent(WComponentPeer peer, AWTEvent e);
634 private native void endCompositionNative(int context, boolean flag);
635 private native void setConversionStatus(int context, int cmode);
636 private native int getConversionStatus(int context);
637 private native void setOpenStatus(int context, boolean flag);
638 private native boolean getOpenStatus(int context);
639 private native void setStatusWindowVisible(WComponentPeer peer, boolean visible);
640 private native String getNativeIMMDescription();
641 static native Locale getNativeLocale();
642 static native boolean setNativeLocale(String localeName, boolean onActivate);
643 private native void openCandidateWindow(WComponentPeer peer, int x, int y);
644 }
--- EOF ---