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