1 /*
2  * Copyright (c) 1997, 2013, 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 package sun.awt;
27 
28 import java.util.Collections;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.HashMap;
32 import java.awt.AWTEvent;
33 import java.awt.AWTException;
34 import java.awt.Component;
35 import java.awt.Container;
36 import java.awt.EventQueue;
37 import java.awt.Window;
38 import java.awt.im.InputContext;
39 import java.awt.im.InputMethodHighlight;
40 import java.awt.im.spi.InputMethodContext;
41 import sun.awt.im.InputMethodAdapter;
42 import java.awt.event.InputEvent;
43 import java.awt.event.KeyEvent;
44 import java.awt.event.MouseEvent;
45 import java.awt.event.FocusEvent;
46 import java.awt.event.ComponentEvent;
47 import java.awt.event.WindowEvent;
48 import java.awt.event.InputMethodEvent;
49 import java.awt.font.TextAttribute;
50 import java.awt.font.TextHitInfo;
51 import java.awt.peer.ComponentPeer;
52 import java.lang.Character.Subset;
53 import java.text.AttributedString;
54 import java.text.AttributedCharacterIterator;
55 
56 import java.io.File;
57 import java.io.FileReader;
58 import java.io.BufferedReader;
59 import java.io.IOException;
60 import java.lang.ref.WeakReference;
61 import sun.util.logging.PlatformLogger;
62 import java.util.StringTokenizer;
63 import java.util.regex.Pattern;
64 
65 
66 /**
67  * Input Method Adapter for XIM
68  *
69  * @author JavaSoft International
70  */
71 public abstract class X11InputMethod extends InputMethodAdapter {
72     private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11InputMethod");
73     /*
74      * The following XIM* values must be the same as those defined in
75      * Xlib.h
76      */
77     private static final int XIMReverse = (1<<0);
78     private static final int XIMUnderline = (1<<1);
79     private static final int XIMHighlight = (1<<2);
80     private static final int XIMPrimary = (1<<5);
81     private static final int XIMSecondary = (1<<6);
82     private static final int XIMTertiary = (1<<7);
83 
84     /*
85      * visible position values
86      */
87     private static final int XIMVisibleToForward = (1<<8);
88     private static final int XIMVisibleToBackward = (1<<9);
89     private static final int XIMVisibleCenter = (1<<10);
90     private static final int XIMVisibleMask = (XIMVisibleToForward|
91                                                XIMVisibleToBackward|
92                                                XIMVisibleCenter);
93 
94     private Locale locale;
95     private static boolean isXIMOpened = false;
96     protected Container clientComponentWindow = null;
97     private Component awtFocussedComponent = null;
98     private Component lastXICFocussedComponent = null;
99     private boolean   isLastXICActive = false;
100     private boolean   isLastTemporary = false;
101     private boolean   isActive = false;
102     private boolean   isActiveClient = false;
103     private static Map[] highlightStyles;
104     private boolean disposed = false;
105 
106     //reset the XIC if necessary
107     private boolean   needResetXIC = false;
108     private WeakReference<Component> needResetXICClient = new WeakReference<>(null);
109 
110     // The use of compositionEnableSupported is to reduce unnecessary
111     // native calls if set/isCompositionEnabled
112     // throws UnsupportedOperationException.
113     // It is set to false if that exception is thrown first time
114     // either of the two methods are called.
115     private boolean compositionEnableSupported = true;
116     // The savedCompositionState indicates the composition mode when
117     // endComposition or setCompositionEnabled is called. It doesn't always
118     // reflect the actual composition state because it doesn't get updated
119     // when the user changes the composition state through direct interaction
120     // with the input method. It is used to save the composition mode when
121     // focus is traversed across different client components sharing the
122     // same java input context. Also if set/isCompositionEnabled are not
123     // supported, it remains false.
124     private boolean savedCompositionState = false;
125 
126     // variables to keep track of preedit context.
127     // these variables need to be accessed within AWT_LOCK/UNLOCK
128     private String committedText = null;
129     private StringBuffer composedText = null;
130     private IntBuffer rawFeedbacks;
131 
132     // private data (X11InputMethodData structure defined in
133     // awt_InputMethod.c) for native methods
134     // this structure needs to be accessed within AWT_LOCK/UNLOCK
135     transient private long pData = 0; // accessed by native
136 
137     // Initialize highlight mapping table
138     static {
139         Map styles[] = new Map[4];
140         HashMap map;
141 
142         // UNSELECTED_RAW_TEXT_HIGHLIGHT
143         map = new HashMap(1);
map.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD)144         map.put(TextAttribute.WEIGHT,
145                   TextAttribute.WEIGHT_BOLD);
146         styles[0] = Collections.unmodifiableMap(map);
147 
148         // SELECTED_RAW_TEXT_HIGHLIGHT
149         map = new HashMap(1);
map.put(TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON)150         map.put(TextAttribute.SWAP_COLORS,
151                   TextAttribute.SWAP_COLORS_ON);
152         styles[1] = Collections.unmodifiableMap(map);
153 
154         // UNSELECTED_CONVERTED_TEXT_HIGHLIGHT
155         map = new HashMap(1);
map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL)156         map.put(TextAttribute.INPUT_METHOD_UNDERLINE,
157                   TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
158         styles[2] = Collections.unmodifiableMap(map);
159 
160         // SELECTED_CONVERTED_TEXT_HIGHLIGHT
161         map = new HashMap(1);
map.put(TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON)162         map.put(TextAttribute.SWAP_COLORS,
163                   TextAttribute.SWAP_COLORS_ON);
164         styles[3] = Collections.unmodifiableMap(map);
165 
166         highlightStyles = styles;
167     }
168 
169     static {
initIDs()170         initIDs();
171     }
172 
173     /**
174      * Initialize JNI field and method IDs for fields that may be
175        accessed from C.
176      */
initIDs()177     private static native void initIDs();
178 
179     /**
180      * Constructs an X11InputMethod instance. It initializes the XIM
181      * environment if it's not done yet.
182      *
183      * @exception AWTException if XOpenIM() failed.
184      */
X11InputMethod()185     public X11InputMethod() throws AWTException {
186         // supports only the locale in which the VM is started
187         locale = X11InputMethodDescriptor.getSupportedLocale();
188         if (initXIM() == false) {
189             throw new AWTException("Cannot open X Input Method");
190         }
191     }
192 
finalize()193     protected void finalize() throws Throwable {
194         dispose();
195         super.finalize();
196     }
197 
198     /**
199      * Invokes openIM() that invokes XOpenIM() if it's not opened yet.
200      * @return  true if openXIM() is successful or it's already been opened.
201      */
initXIM()202     private synchronized boolean initXIM() {
203         if (isXIMOpened == false)
204             isXIMOpened = openXIM();
205         return isXIMOpened;
206     }
207 
openXIM()208     protected abstract boolean openXIM();
209 
isDisposed()210     protected boolean isDisposed() {
211         return disposed;
212     }
213 
setXICFocus(ComponentPeer peer, boolean value, boolean active)214     protected abstract void setXICFocus(ComponentPeer peer,
215                                     boolean value, boolean active);
216 
217     /**
218      * Does nothing - this adapter doesn't use the input method context.
219      *
220      * @see java.awt.im.spi.InputMethod#setInputMethodContext
221      */
setInputMethodContext(InputMethodContext context)222     public void setInputMethodContext(InputMethodContext context) {
223     }
224 
225     /**
226      * Set locale to input. If input method doesn't support specified locale,
227      * false will be returned and its behavior is not changed.
228      *
229      * @param lang locale to input
230      * @return the true is returned when specified locale is supported.
231      */
setLocale(Locale lang)232     public boolean setLocale(Locale lang) {
233         if (lang.equals(locale)) {
234             return true;
235         }
236         // special compatibility rule for Japanese and Korean
237         if (locale.equals(Locale.JAPAN) && lang.equals(Locale.JAPANESE) ||
238                 locale.equals(Locale.KOREA) && lang.equals(Locale.KOREAN)) {
239             return true;
240         }
241         return false;
242     }
243 
244     /**
245      * Returns current input locale.
246      */
getLocale()247     public Locale getLocale() {
248         return locale;
249     }
250 
251     /**
252      * Does nothing - XIM doesn't let you specify which characters you expect.
253      *
254      * @see java.awt.im.spi.InputMethod#setCharacterSubsets
255      */
setCharacterSubsets(Subset[] subsets)256     public void setCharacterSubsets(Subset[] subsets) {
257     }
258 
259     /**
260      * Dispatch event to input method. InputContext dispatch event with this
261      * method. Input method set consume flag if event is consumed in
262      * input method.
263      *
264      * @param e event
265      */
dispatchEvent(AWTEvent e)266     public void dispatchEvent(AWTEvent e) {
267     }
268 
269 
resetXICifneeded()270     protected final void resetXICifneeded(){
271         /* needResetXIC is used to indicate whether to call
272            resetXIC on the active client. resetXIC will always be
273            called on the passive client when endComposition is called.
274         */
275         if (needResetXIC && haveActiveClient() &&
276             getClientComponent() != needResetXICClient.get()){
277             resetXIC();
278 
279             // needs to reset the last xic focussed component.
280             lastXICFocussedComponent = null;
281             isLastXICActive = false;
282 
283             needResetXICClient.clear();
284             needResetXIC = false;
285         }
286     }
287 
288     /**
289      * Reset the composition state to the current composition state.
290      */
resetCompositionState()291     private void resetCompositionState() {
292         if (compositionEnableSupported) {
293             try {
294                 /* Restore the composition mode to the last saved composition
295                    mode. */
296                 setCompositionEnabled(savedCompositionState);
297             } catch (UnsupportedOperationException e) {
298                 compositionEnableSupported = false;
299             }
300         }
301     }
302 
303     /**
304      * Query and then return the current composition state.
305      * @returns the composition state if isCompositionEnabled call
306      * is successful. Otherwise, it returns false.
307      */
getCompositionState()308     private boolean getCompositionState() {
309         boolean compositionState = false;
310         if (compositionEnableSupported) {
311             try {
312                 compositionState = isCompositionEnabled();
313             } catch (UnsupportedOperationException e) {
314                 compositionEnableSupported = false;
315             }
316         }
317         return compositionState;
318     }
319 
320     /**
321      * Activate input method.
322      */
activate()323     public synchronized void activate() {
324         clientComponentWindow = getClientComponentWindow();
325         if (clientComponentWindow == null)
326             return;
327 
328         if (lastXICFocussedComponent != null){
329             if (log.isLoggable(PlatformLogger.Level.FINE)) {
330                 log.fine("XICFocused {0}, AWTFocused {1}",
331                          lastXICFocussedComponent, awtFocussedComponent);
332             }
333         }
334 
335         if (pData == 0) {
336             if (!createXIC()) {
337                 return;
338             }
339             disposed = false;
340         }
341 
342         /*  reset input context if necessary and set the XIC focus
343         */
344         resetXICifneeded();
345         ComponentPeer lastXICFocussedComponentPeer = null;
346         ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent);
347 
348         if (lastXICFocussedComponent != null) {
349            lastXICFocussedComponentPeer = getPeer(lastXICFocussedComponent);
350         }
351 
352         /* If the last XIC focussed component has a different peer as the
353            current focussed component, change the XIC focus to the newly
354            focussed component.
355         */
356         if (isLastTemporary || lastXICFocussedComponentPeer != awtFocussedComponentPeer ||
357             isLastXICActive != haveActiveClient()) {
358             if (lastXICFocussedComponentPeer != null) {
359                 setXICFocus(lastXICFocussedComponentPeer, false, isLastXICActive);
360             }
361             if (awtFocussedComponentPeer != null) {
362                 setXICFocus(awtFocussedComponentPeer, true, haveActiveClient());
363             }
364             lastXICFocussedComponent = awtFocussedComponent;
365             isLastXICActive = haveActiveClient();
366         }
367         resetCompositionState();
368         isActive = true;
369     }
370 
createXIC()371     protected abstract boolean createXIC();
372 
373     /**
374      * Deactivate input method.
375      */
deactivate(boolean isTemporary)376     public synchronized void deactivate(boolean isTemporary) {
377         boolean   isAc =  haveActiveClient();
378         /* Usually as the client component, let's call it component A,
379            loses the focus, this method is called. Then when another client
380            component, let's call it component B,  gets the focus, activate is first called on
381            the previous focused compoent which is A, then endComposition is called on A,
382            deactivate is called on A again. And finally activate is called on the newly
383            focused component B. Here is the call sequence.
384 
385            A loses focus               B gains focus
386            -------------> deactivate A -------------> activate A -> endComposition A ->
387            deactivate A -> activate B ----....
388 
389            So in order to carry the composition mode across the components sharing the same
390            input context, we save it when deactivate is called so that when activate is
391            called, it can be restored correctly till activate is called on the newly focused
392            component. (See also sun/awt/im/InputContext and bug 6184471).
393            Last note, getCompositionState should be called before setXICFocus since
394            setXICFocus here sets the XIC to 0.
395         */
396         savedCompositionState = getCompositionState();
397 
398         if (isTemporary){
399             //turn the status window off...
400             turnoffStatusWindow();
401         }
402 
403         /* Delay resetting the XIC focus until activate is called and the newly
404            focussed component has a different peer as the last focussed component.
405         */
406         lastXICFocussedComponent = awtFocussedComponent;
407         isLastXICActive = isAc;
408         isLastTemporary = isTemporary;
409         isActive = false;
410     }
411 
412     /**
413      * Explicitly disable the native IME. Native IME is not disabled when
414      * deactivate is called.
415      */
disableInputMethod()416     public void disableInputMethod() {
417         if (lastXICFocussedComponent != null) {
418             setXICFocus(getPeer(lastXICFocussedComponent), false, isLastXICActive);
419             lastXICFocussedComponent = null;
420             isLastXICActive = false;
421 
422             resetXIC();
423             needResetXICClient.clear();
424             needResetXIC = false;
425         }
426     }
427 
428     // implements java.awt.im.spi.InputMethod.hideWindows
hideWindows()429     public void hideWindows() {
430         // ??? need real implementation
431     }
432 
433     /**
434      * @see java.awt.Toolkit#mapInputMethodHighlight
435      */
mapInputMethodHighlight(InputMethodHighlight highlight)436     public static Map mapInputMethodHighlight(InputMethodHighlight highlight) {
437         int index;
438         int state = highlight.getState();
439         if (state == InputMethodHighlight.RAW_TEXT) {
440             index = 0;
441         } else if (state == InputMethodHighlight.CONVERTED_TEXT) {
442             index = 2;
443         } else {
444             return null;
445         }
446         if (highlight.isSelected()) {
447             index += 1;
448         }
449         return highlightStyles[index];
450     }
451 
452     /**
453      * @see sun.awt.im.InputMethodAdapter#setAWTFocussedComponent
454      */
setAWTFocussedComponent(Component component)455     protected void setAWTFocussedComponent(Component component) {
456         if (component == null) {
457             return;
458         }
459         if (isActive) {
460             // deactivate/activate are being suppressed during a focus change -
461             // this may happen when an input method window is made visible
462             boolean ac = haveActiveClient();
463             setXICFocus(getPeer(awtFocussedComponent), false, ac);
464             setXICFocus(getPeer(component), true, ac);
465         }
466         awtFocussedComponent = component;
467     }
468 
469     /**
470      * @see sun.awt.im.InputMethodAdapter#stopListening
471      */
stopListening()472     protected void stopListening() {
473         // It is desirable to disable XIM by calling XSetICValues with
474         // XNPreeditState == XIMPreeditDisable.  But Solaris 2.6 and
475         // Solaris 7 do not implement this correctly without a patch,
476         // so just call resetXIC here.  Prior endComposition call commits
477         // the existing composed text.
478         endComposition();
479         // disable the native input method so that the other input
480         // method could get the input focus.
481         disableInputMethod();
482         if (needResetXIC) {
483             resetXIC();
484             needResetXICClient.clear();
485             needResetXIC = false;
486         }
487     }
488 
489     /**
490      * Returns the Window instance in which the client component is
491      * contained. If not found, null is returned. (IS THIS POSSIBLE?)
492      */
493     // NOTE: This method may be called by privileged threads.
494     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
getClientComponentWindow()495     private Window getClientComponentWindow() {
496         Component client = getClientComponent();
497         Container container;
498 
499         if (client instanceof Container) {
500             container = (Container) client;
501         } else {
502             container = getParent(client);
503         }
504 
505         while (container != null && !(container instanceof java.awt.Window)) {
506             container = getParent(container);
507         }
508         return (Window) container;
509     }
510 
getParent(Component client)511     protected abstract Container getParent(Component client);
512 
513     /**
514      * Returns peer of the given client component. If the given client component
515      * doesn't have peer, peer of the native container of the client is returned.
516      */
getPeer(Component client)517     protected abstract ComponentPeer getPeer(Component client);
518 
519     /**
520      * Used to protect preedit data
521      */
awtLock()522     protected abstract void awtLock();
awtUnlock()523     protected abstract void awtUnlock();
524 
525     /**
526      * Creates an input method event from the arguments given
527      * and posts it on the AWT event queue. For arguments,
528      * see InputMethodEvent. Called by input method.
529      *
530      * @see java.awt.event.InputMethodEvent#InputMethodEvent
531      */
postInputMethodEvent(int id, AttributedCharacterIterator text, int committedCharacterCount, TextHitInfo caret, TextHitInfo visiblePosition, long when)532     private void postInputMethodEvent(int id,
533                                       AttributedCharacterIterator text,
534                                       int committedCharacterCount,
535                                       TextHitInfo caret,
536                                       TextHitInfo visiblePosition,
537                                       long when) {
538         Component source = getClientComponent();
539         if (source != null) {
540             InputMethodEvent event = new InputMethodEvent(source,
541                 id, when, text, committedCharacterCount, caret, visiblePosition);
542             SunToolkit.postEvent(SunToolkit.targetToAppContext(source), (AWTEvent)event);
543         }
544     }
545 
postInputMethodEvent(int id, AttributedCharacterIterator text, int committedCharacterCount, TextHitInfo caret, TextHitInfo visiblePosition)546     private void postInputMethodEvent(int id,
547                                       AttributedCharacterIterator text,
548                                       int committedCharacterCount,
549                                       TextHitInfo caret,
550                                       TextHitInfo visiblePosition) {
551         postInputMethodEvent(id, text, committedCharacterCount,
552                              caret, visiblePosition, EventQueue.getMostRecentEventTime());
553     }
554 
555     /**
556      * Dispatches committed text from XIM to the awt event queue. This
557      * method is invoked from the event handler in canvas.c in the
558      * AWT Toolkit thread context and thus inside the AWT Lock.
559      * @param   str     committed text
560      * @param   long    when
561      */
562     // NOTE: This method may be called by privileged threads.
563     //       This functionality is implemented in a package-private method
564     //       to insure that it cannot be overridden by client subclasses.
565     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
dispatchCommittedText(String str, long when)566     void dispatchCommittedText(String str, long when) {
567         if (str == null)
568             return;
569 
570         if (composedText == null) {
571             AttributedString attrstr = new AttributedString(str);
572             postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
573                                  attrstr.getIterator(),
574                                  str.length(),
575                                  null,
576                                  null,
577                                  when);
578         } else {
579             // if there is composed text, wait until the preedit
580             // callback is invoked.
581             committedText = str;
582         }
583     }
584 
dispatchCommittedText(String str)585     private void dispatchCommittedText(String str) {
586         dispatchCommittedText(str, EventQueue.getMostRecentEventTime());
587     }
588 
589     /**
590      * Updates composed text with XIM preedit information and
591      * posts composed text to the awt event queue. The args of
592      * this method correspond to the XIM preedit callback
593      * information. The XIM highlight attributes are translated via
594      * fixed mapping (i.e., independent from any underlying input
595      * method engine). This method is invoked in the AWT Toolkit
596      * (X event loop) thread context and thus inside the AWT Lock.
597      */
598     // NOTE: This method may be called by privileged threads.
599     //       This functionality is implemented in a package-private method
600     //       to insure that it cannot be overridden by client subclasses.
601     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
dispatchComposedText(String chgText, int chgStyles[], int chgOffset, int chgLength, int caretPosition, long when)602     void dispatchComposedText(String chgText,
603                                            int chgStyles[],
604                                            int chgOffset,
605                                            int chgLength,
606                                            int caretPosition,
607                                            long when) {
608         if (disposed) {
609             return;
610         }
611 
612         //Workaround for deadlock bug on solaris2.6_zh bug#4170760
613         if (chgText == null
614             && chgStyles == null
615             && chgOffset == 0
616             && chgLength == 0
617             && caretPosition == 0
618             && composedText == null
619             && committedText == null)
620             return;
621 
622         if (composedText == null) {
623             // TODO: avoid reallocation of those buffers
624             composedText = new StringBuffer(INITIAL_SIZE);
625             rawFeedbacks = new IntBuffer(INITIAL_SIZE);
626         }
627         if (chgLength > 0) {
628             if (chgText == null && chgStyles != null) {
629                 rawFeedbacks.replace(chgOffset, chgStyles);
630             } else {
631                 if (chgLength == composedText.length()) {
632                     // optimization for the special case to replace the
633                     // entire previous text
634                     composedText = new StringBuffer(INITIAL_SIZE);
635                     rawFeedbacks = new IntBuffer(INITIAL_SIZE);
636                 } else {
637                     if (composedText.length() > 0) {
638                         if (chgOffset+chgLength < composedText.length()) {
639                             String text;
640                             text = composedText.toString().substring(chgOffset+chgLength,
641                                                                      composedText.length());
642                             composedText.setLength(chgOffset);
643                             composedText.append(text);
644                         } else {
645                             // in case to remove substring from chgOffset
646                             // to the end
647                             composedText.setLength(chgOffset);
648                         }
649                         rawFeedbacks.remove(chgOffset, chgLength);
650                     }
651                 }
652             }
653         }
654         if (chgText != null) {
655             composedText.insert(chgOffset, chgText);
656             if (chgStyles != null)
657                 rawFeedbacks.insert(chgOffset, chgStyles);
658         }
659 
660         if (composedText.length() == 0) {
661             composedText = null;
662             rawFeedbacks = null;
663 
664             // if there is any outstanding committed text stored by
665             // dispatchCommittedText(), it has to be sent to the
666             // client component.
667             if (committedText != null) {
668                 dispatchCommittedText(committedText, when);
669                 committedText = null;
670                 return;
671             }
672 
673             // otherwise, send null text to delete client's composed
674             // text.
675             postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
676                                  null,
677                                  0,
678                                  null,
679                                  null,
680                                  when);
681 
682             return;
683         }
684 
685         // Now sending the composed text to the client
686         int composedOffset;
687         AttributedString inputText;
688 
689         // if there is any partially committed text, concatenate it to
690         // the composed text.
691         if (committedText != null) {
692             composedOffset = committedText.length();
693             inputText = new AttributedString(committedText + composedText);
694             committedText = null;
695         } else {
696             composedOffset = 0;
697             inputText = new AttributedString(composedText.toString());
698         }
699 
700         int currentFeedback;
701         int nextFeedback;
702         int startOffset = 0;
703         int currentOffset;
704         int visiblePosition = 0;
705         TextHitInfo visiblePositionInfo = null;
706 
707         rawFeedbacks.rewind();
708         currentFeedback = rawFeedbacks.getNext();
709         rawFeedbacks.unget();
710         while ((nextFeedback = rawFeedbacks.getNext()) != -1) {
711             if (visiblePosition == 0) {
712                 visiblePosition = nextFeedback & XIMVisibleMask;
713                 if (visiblePosition != 0) {
714                     int index = rawFeedbacks.getOffset() - 1;
715 
716                     if (visiblePosition == XIMVisibleToBackward)
717                         visiblePositionInfo = TextHitInfo.leading(index);
718                     else
719                         visiblePositionInfo = TextHitInfo.trailing(index);
720                 }
721             }
722             nextFeedback &= ~XIMVisibleMask;
723             if (currentFeedback != nextFeedback) {
724                 rawFeedbacks.unget();
725                 currentOffset = rawFeedbacks.getOffset();
726                 inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
727                                        convertVisualFeedbackToHighlight(currentFeedback),
728                                        composedOffset + startOffset,
729                                        composedOffset + currentOffset);
730                 startOffset = currentOffset;
731                 currentFeedback = nextFeedback;
732             }
733         }
734         currentOffset = rawFeedbacks.getOffset();
735         if (currentOffset >= 0) {
736             inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
737                                    convertVisualFeedbackToHighlight(currentFeedback),
738                                    composedOffset + startOffset,
739                                    composedOffset + currentOffset);
740         }
741 
742         postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
743                              inputText.getIterator(),
744                              composedOffset,
745                              TextHitInfo.leading(caretPosition),
746                              visiblePositionInfo,
747                              when);
748     }
749 
750     /**
751      * Flushes composed and committed text held in this context.
752      * This method is invoked in the AWT Toolkit (X event loop) thread context
753      * and thus inside the AWT Lock.
754      */
755     // NOTE: This method may be called by privileged threads.
756     //       This functionality is implemented in a package-private method
757     //       to insure that it cannot be overridden by client subclasses.
758     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
flushText()759     void flushText() {
760         String flush = (committedText != null ? committedText : "");
761         if (composedText != null) {
762             flush += composedText.toString();
763         }
764 
765         if (!flush.equals("")) {
766             AttributedString attrstr = new AttributedString(flush);
767             postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
768                                  attrstr.getIterator(),
769                                  flush.length(),
770                                  null,
771                                  null,
772                                  EventQueue.getMostRecentEventTime());
773             composedText = null;
774             committedText = null;
775         }
776     }
777 
778     /*
779      * Subclasses should override disposeImpl() instead of dispose(). Client
780      * code should always invoke dispose(), never disposeImpl().
781      */
disposeImpl()782     protected synchronized void disposeImpl() {
783         disposeXIC();
784         awtLock();
785         composedText = null;
786         committedText = null;
787         rawFeedbacks = null;
788         awtUnlock();
789         awtFocussedComponent = null;
790         lastXICFocussedComponent = null;
791     }
792 
793     /**
794      * Frees all X Window resources associated with this object.
795      *
796      * @see java.awt.im.spi.InputMethod#dispose
797      */
dispose()798     public final void dispose() {
799         boolean call_disposeImpl = false;
800 
801         if (!disposed) {
802             synchronized (this) {
803                 if (!disposed) {
804                     disposed = call_disposeImpl = true;
805                 }
806             }
807         }
808 
809         if (call_disposeImpl) {
810             disposeImpl();
811         }
812     }
813 
814     /**
815      * Returns null.
816      *
817      * @see java.awt.im.spi.InputMethod#getControlObject
818      */
getControlObject()819     public Object getControlObject() {
820         return null;
821     }
822 
823     /**
824      * @see java.awt.im.spi.InputMethod#removeNotify
825      */
removeNotify()826     public synchronized void removeNotify() {
827         dispose();
828     }
829 
830     /**
831      * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean)
832      */
setCompositionEnabled(boolean enable)833     public void setCompositionEnabled(boolean enable) {
834         /* If the composition state is successfully changed, set
835            the savedCompositionState to 'enable'. Otherwise, simply
836            return.
837            setCompositionEnabledNative may throw UnsupportedOperationException.
838            Don't try to catch it since the method may be called by clients.
839            Use package private mthod 'resetCompositionState' if you want the
840            exception to be caught.
841         */
842         if (setCompositionEnabledNative(enable)) {
843             savedCompositionState = enable;
844         }
845     }
846 
847     /**
848      * @see java.awt.im.spi.InputMethod#isCompositionEnabled
849      */
isCompositionEnabled()850     public boolean isCompositionEnabled() {
851         /* isCompositionEnabledNative may throw UnsupportedOperationException.
852            Don't try to catch it since this method may be called by clients.
853            Use package private method 'getCompositionState' if you want the
854            exception to be caught.
855         */
856         return isCompositionEnabledNative();
857     }
858 
859     /**
860      * Ends any input composition that may currently be going on in this
861      * context. Depending on the platform and possibly user preferences,
862      * this may commit or delete uncommitted text. Any changes to the text
863      * are communicated to the active component using an input method event.
864      *
865      * <p>
866      * A text editing component may call this in a variety of situations,
867      * for example, when the user moves the insertion point within the text
868      * (but outside the composed text), or when the component's text is
869      * saved to a file or copied to the clipboard.
870      *
871      */
endComposition()872     public void endComposition() {
873         if (disposed) {
874             return;
875         }
876 
877         /* Before calling resetXIC, record the current composition mode
878            so that it can be restored later. */
879         savedCompositionState = getCompositionState();
880         boolean active = haveActiveClient();
881         if (active && composedText == null && committedText == null){
882             needResetXIC = true;
883             needResetXICClient = new WeakReference<>(getClientComponent());
884             return;
885         }
886 
887         String text = resetXIC();
888         /* needResetXIC is only set to true for active client. So passive
889            client should not reset the flag to false. */
890         if (active) {
891             needResetXIC = false;
892         }
893 
894         // Remove any existing composed text by posting an InputMethodEvent
895         // with null composed text.  It would be desirable to wait for a
896         // dispatchComposedText call from X input method engine, but some
897         // input method does not conform to the XIM specification and does
898         // not call the preedit callback to erase preedit text on calling
899         // XmbResetIC.  To work around this problem, do it here by ourselves.
900         awtLock();
901         composedText = null;
902         postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
903                              null,
904                              0,
905                              null,
906                              null);
907 
908         if (text != null && text.length() > 0) {
909             dispatchCommittedText(text);
910         }
911         awtUnlock();
912 
913         // Restore the preedit state if it was enabled
914         if (savedCompositionState) {
915             resetCompositionState();
916         }
917     }
918 
919     /**
920      * Returns a string with information about the current input method server, or null.
921      * On both Linux & SunOS, the value of environment variable XMODIFIERS is
922      * returned if set. Otherwise, on SunOS, $HOME/.dtprofile will be parsed
923      * to find out the language service engine (atok or wnn) since there is
924      * no API in Xlib which returns the information of native
925      * IM server or language service and we want to try our best to return as much
926      * information as possible.
927      *
928      * Note: This method could return null on Linux if XMODIFIERS is not set properly or
929      * if any IOException is thrown.
930      * See man page of XSetLocaleModifiers(3X11) for the usgae of XMODIFIERS,
931      * atok12setup(1) and wnn6setup(1) for the information written to
932      * $HOME/.dtprofile when you run these two commands.
933      *
934      */
getNativeInputMethodInfo()935     public String getNativeInputMethodInfo() {
936         String xmodifiers = System.getenv("XMODIFIERS");
937         String imInfo = null;
938 
939         // If XMODIFIERS is set, return the value
940         if (xmodifiers != null) {
941             int imIndex = xmodifiers.indexOf("@im=");
942             if (imIndex != -1) {
943                 imInfo = xmodifiers.substring(imIndex + 4);
944             }
945         } else if (System.getProperty("os.name").startsWith("SunOS")) {
946             File dtprofile = new File(System.getProperty("user.home") +
947                                       "/.dtprofile");
948             String languageEngineInfo = null;
949             try {
950                 BufferedReader br = new BufferedReader(new FileReader(dtprofile));
951                 String line = null;
952 
953                 while ( languageEngineInfo == null && (line = br.readLine()) != null) {
954                     if (line.contains("atok") || line.contains("wnn")) {
955                         StringTokenizer tokens =  new StringTokenizer(line);
956                         while (tokens.hasMoreTokens()) {
957                             String token = tokens.nextToken();
958                             if (Pattern.matches("atok.*setup", token) ||
959                                 Pattern.matches("wnn.*setup", token)){
960                                 languageEngineInfo = token.substring(0, token.indexOf("setup"));
961                                 break;
962                             }
963                         }
964                     }
965                 }
966 
967                 br.close();
968             } catch(IOException ioex) {
969                 // Since this method is provided for internal testing only,
970                 // we dump the stack trace for the ease of debugging.
971                 ioex.printStackTrace();
972             }
973 
974             imInfo = "htt " + languageEngineInfo;
975         }
976 
977         return imInfo;
978     }
979 
980 
981     /**
982      * Performs mapping from an XIM visible feedback value to Java IM highlight.
983      * @return Java input method highlight
984      */
convertVisualFeedbackToHighlight(int feedback)985     private InputMethodHighlight convertVisualFeedbackToHighlight(int feedback) {
986         InputMethodHighlight highlight;
987 
988         switch (feedback) {
989         case XIMUnderline:
990             highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;
991             break;
992         case XIMReverse:
993             highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
994             break;
995         case XIMHighlight:
996             highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
997             break;
998         case XIMPrimary:
999             highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;
1000             break;
1001         case XIMSecondary:
1002             highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
1003             break;
1004         case XIMTertiary:
1005             highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
1006             break;
1007         default:
1008             highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
1009             break;
1010         }
1011         return highlight;
1012     }
1013 
1014     // initial capacity size for string buffer, etc.
1015     private static final int INITIAL_SIZE = 64;
1016 
1017     /**
1018      * IntBuffer is an inner class that manipulates an int array and
1019      * provides UNIX file io stream-like programming interfaces to
1020      * access it. (An alternative would be to use ArrayList which may
1021      * be too expensive for the work.)
1022      */
1023     private final class IntBuffer {
1024         private int[] intArray;
1025         private int size;
1026         private int index;
1027 
IntBuffer(int initialCapacity)1028         IntBuffer(int initialCapacity) {
1029             intArray = new int[initialCapacity];
1030             size = 0;
1031             index = 0;
1032         }
1033 
insert(int offset, int[] values)1034         void insert(int offset, int[] values) {
1035             int newSize = size + values.length;
1036             if (intArray.length < newSize) {
1037                 int[] newIntArray = new int[newSize * 2];
1038                 System.arraycopy(intArray, 0, newIntArray, 0, size);
1039                 intArray = newIntArray;
1040             }
1041             System.arraycopy(intArray, offset, intArray, offset+values.length,
1042                              size - offset);
1043             System.arraycopy(values, 0, intArray, offset, values.length);
1044             size += values.length;
1045             if (index > offset)
1046                 index = offset;
1047         }
1048 
remove(int offset, int length)1049         void remove(int offset, int length) {
1050             if (offset + length != size)
1051                 System.arraycopy(intArray, offset+length, intArray, offset,
1052                                  size - offset - length);
1053             size -= length;
1054             if (index > offset)
1055                 index = offset;
1056         }
1057 
replace(int offset, int[] values)1058         void replace(int offset, int[] values) {
1059             System.arraycopy(values, 0, intArray, offset, values.length);
1060         }
1061 
removeAll()1062         void removeAll() {
1063             size = 0;
1064             index = 0;
1065         }
1066 
rewind()1067         void rewind() {
1068             index = 0;
1069         }
1070 
getNext()1071         int getNext() {
1072             if (index == size)
1073                 return -1;
1074             return intArray[index++];
1075         }
1076 
unget()1077         void unget() {
1078             if (index != 0)
1079                 index--;
1080         }
1081 
getOffset()1082         int getOffset() {
1083             return index;
1084         }
1085 
toString()1086         public String toString() {
1087             StringBuffer s = new StringBuffer();
1088             for (int i = 0; i < size;) {
1089                 s.append(intArray[i++]);
1090                 if (i < size)
1091                     s.append(",");
1092             }
1093             return s.toString();
1094         }
1095     }
1096 
1097     /*
1098      * Native methods
1099      */
resetXIC()1100     protected native String resetXIC();
disposeXIC()1101     private native void disposeXIC();
setCompositionEnabledNative(boolean enable)1102     private native boolean setCompositionEnabledNative(boolean enable);
isCompositionEnabledNative()1103     private native boolean isCompositionEnabledNative();
turnoffStatusWindow()1104     private native void turnoffStatusWindow();
1105 }
1106