1 /*
2  * Copyright (c) 1997, 2019, 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.awt.AWTException;
29 import java.awt.event.InputMethodEvent;
30 import java.awt.font.TextAttribute;
31 import java.awt.font.TextHitInfo;
32 import java.awt.peer.ComponentPeer;
33 import java.text.AttributedString;
34 
35 import sun.util.logging.PlatformLogger;
36 
37 /**
38  * Input Method Adapter for XIM
39  *
40  * @author JavaSoft International
41  */
42 public abstract class X11InputMethod extends X11InputMethodBase {
43 
44     /**
45      * Constructs an X11InputMethod instance. It initializes the XIM
46      * environment if it's not done yet.
47      *
48      * @exception AWTException if XOpenIM() failed.
49      */
X11InputMethod()50     public X11InputMethod() throws AWTException {
51         super();
52     }
53 
54     /**
55      * Reset the composition state to the current composition state.
56      */
resetCompositionState()57     protected void resetCompositionState() {
58         if (compositionEnableSupported && haveActiveClient()) {
59             try {
60                 /* Restore the composition mode to the last saved composition
61                    mode. */
62                 setCompositionEnabled(savedCompositionState);
63             } catch (UnsupportedOperationException e) {
64                 compositionEnableSupported = false;
65             }
66         }
67     }
68 
69     /**
70      * Activate input method.
71      */
activate()72     public synchronized void activate() {
73         clientComponentWindow = getClientComponentWindow();
74         if (clientComponentWindow == null)
75             return;
76 
77         if (lastXICFocussedComponent != null) {
78             if (log.isLoggable(PlatformLogger.Level.FINE)) {
79                 log.fine("XICFocused {0}, AWTFocused {1}",
80                          lastXICFocussedComponent, awtFocussedComponent);
81             }
82         }
83 
84         if (pData == 0) {
85             if (!createXIC()) {
86                 return;
87             }
88             disposed = false;
89         }
90 
91         /*  reset input context if necessary and set the XIC focus
92         */
93         resetXICifneeded();
94         ComponentPeer lastXICFocussedComponentPeer = null;
95         ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent);
96 
97         if (lastXICFocussedComponent != null) {
98            lastXICFocussedComponentPeer = getPeer(lastXICFocussedComponent);
99         }
100 
101         /* If the last XIC focussed component has a different peer as the
102            current focussed component, change the XIC focus to the newly
103            focussed component.
104         */
105         if (isLastTemporary || lastXICFocussedComponentPeer != awtFocussedComponentPeer ||
106             isLastXICActive != haveActiveClient()) {
107             if (lastXICFocussedComponentPeer != null) {
108                 setXICFocus(lastXICFocussedComponentPeer, false, isLastXICActive);
109             }
110             if (awtFocussedComponentPeer != null) {
111                 setXICFocus(awtFocussedComponentPeer, true, haveActiveClient());
112             }
113             lastXICFocussedComponent = awtFocussedComponent;
114             isLastXICActive = haveActiveClient();
115         }
116         resetCompositionState();
117         isActive = true;
118     }
119 
120     /**
121      * Deactivate input method.
122      */
deactivate(boolean isTemporary)123     public synchronized void deactivate(boolean isTemporary) {
124         boolean   isAc =  haveActiveClient();
125         /* Usually as the client component, let's call it component A,
126            loses the focus, this method is called. Then when another client
127            component, let's call it component B,  gets the focus, activate is first called on
128            the previous focused compoent which is A, then endComposition is called on A,
129            deactivate is called on A again. And finally activate is called on the newly
130            focused component B. Here is the call sequence.
131 
132            A loses focus               B gains focus
133            -------------> deactivate A -------------> activate A -> endComposition A ->
134            deactivate A -> activate B ----....
135 
136            So in order to carry the composition mode across the components sharing the same
137            input context, we save it when deactivate is called so that when activate is
138            called, it can be restored correctly till activate is called on the newly focused
139            component. (See also sun/awt/im/InputContext and bug 6184471).
140            Last note, getCompositionState should be called before setXICFocus since
141            setXICFocus here sets the XIC to 0.
142         */
143         savedCompositionState = getCompositionState();
144 
145         if (isTemporary) {
146             //turn the status window off...
147             turnoffStatusWindow();
148         }
149 
150         /* Delay resetting the XIC focus until activate is called and the newly
151          * Focused component has a different peer as the last focused component.
152          */
153         lastXICFocussedComponent = awtFocussedComponent;
154         isLastXICActive = isAc;
155         isLastTemporary = isTemporary;
156         isActive = false;
157     }
158 
159     // implements java.awt.im.spi.InputMethod.hideWindows
hideWindows()160     public void hideWindows() {
161         // ??? need real implementation
162     }
163 
164     /**
165      * Updates composed text with XIM preedit information and
166      * posts composed text to the awt event queue. The args of
167      * this method correspond to the XIM preedit callback
168      * information. The XIM highlight attributes are translated via
169      * fixed mapping (i.e., independent from any underlying input
170      * method engine). This method is invoked in the AWT Toolkit
171      * (X event loop) thread context and thus inside the AWT Lock.
172      */
173     // NOTE: This method may be called by privileged threads.
174     //       This functionality is implemented in a package-private method
175     //       to insure that it cannot be overridden by client subclasses.
176     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
dispatchComposedText(String chgText, int[] chgStyles, int chgOffset, int chgLength, int caretPosition, long when)177     void dispatchComposedText(String chgText,
178                                            int[] chgStyles,
179                                            int chgOffset,
180                                            int chgLength,
181                                            int caretPosition,
182                                            long when) {
183         if (disposed) {
184             return;
185         }
186 
187         // Workaround for deadlock bug on solaris2.6_zh bug#4170760
188         if (chgText == null
189             && chgStyles == null
190             && chgOffset == 0
191             && chgLength == 0
192             && caretPosition == 0
193             && composedText == null
194             && committedText == null)
195             return;
196 
197         if (composedText == null) {
198             // TODO: avoid reallocation of those buffers
199             composedText = new StringBuffer(INITIAL_SIZE);
200             rawFeedbacks = new IntBuffer(INITIAL_SIZE);
201         }
202         if (chgLength > 0) {
203             if (chgText == null && chgStyles != null) {
204                 rawFeedbacks.replace(chgOffset, chgStyles);
205             } else {
206                 if (chgLength == composedText.length()) {
207                     // optimization for the special case to replace the
208                     // entire previous text
209                     composedText = new StringBuffer(INITIAL_SIZE);
210                     rawFeedbacks = new IntBuffer(INITIAL_SIZE);
211                 } else {
212                     if (composedText.length() > 0) {
213                         if (chgOffset+chgLength < composedText.length()) {
214                             String text;
215                             text = composedText.toString().substring(chgOffset+chgLength,
216                                                                      composedText.length());
217                             composedText.setLength(chgOffset);
218                             composedText.append(text);
219                         } else {
220                             // in case to remove substring from chgOffset
221                             // to the end
222                             composedText.setLength(chgOffset);
223                         }
224                         rawFeedbacks.remove(chgOffset, chgLength);
225                     }
226                 }
227             }
228         }
229         if (chgText != null) {
230             composedText.insert(chgOffset, chgText);
231             if (chgStyles != null)
232                 rawFeedbacks.insert(chgOffset, chgStyles);
233         }
234 
235         if (composedText.length() == 0) {
236             composedText = null;
237             rawFeedbacks = null;
238 
239             // if there is any outstanding committed text stored by
240             // dispatchCommittedText(), it has to be sent to the
241             // client component.
242             if (committedText != null) {
243                 dispatchCommittedText(committedText, when);
244                 committedText = null;
245                 return;
246             }
247 
248             // otherwise, send null text to delete client's composed
249             // text.
250             postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
251                                  null,
252                                  0,
253                                  null,
254                                  null,
255                                  when);
256 
257             return;
258         }
259 
260         // Now sending the composed text to the client
261         int composedOffset;
262         AttributedString inputText;
263 
264         // if there is any partially committed text, concatenate it to
265         // the composed text.
266         if (committedText != null) {
267             composedOffset = committedText.length();
268             inputText = new AttributedString(committedText + composedText);
269             committedText = null;
270         } else {
271             composedOffset = 0;
272             inputText = new AttributedString(composedText.toString());
273         }
274 
275         int currentFeedback;
276         int nextFeedback;
277         int startOffset = 0;
278         int currentOffset;
279         int visiblePosition = 0;
280         TextHitInfo visiblePositionInfo = null;
281 
282         rawFeedbacks.rewind();
283         currentFeedback = rawFeedbacks.getNext();
284         rawFeedbacks.unget();
285         while ((nextFeedback = rawFeedbacks.getNext()) != -1) {
286             if (visiblePosition == 0) {
287                 visiblePosition = nextFeedback & XIMVisibleMask;
288                 if (visiblePosition != 0) {
289                     int index = rawFeedbacks.getOffset() - 1;
290 
291                     if (visiblePosition == XIMVisibleToBackward)
292                         visiblePositionInfo = TextHitInfo.leading(index);
293                     else
294                         visiblePositionInfo = TextHitInfo.trailing(index);
295                 }
296             }
297             nextFeedback &= ~XIMVisibleMask;
298             if (currentFeedback != nextFeedback) {
299                 rawFeedbacks.unget();
300                 currentOffset = rawFeedbacks.getOffset();
301                 inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
302                                        convertVisualFeedbackToHighlight(currentFeedback),
303                                        composedOffset + startOffset,
304                                        composedOffset + currentOffset);
305                 startOffset = currentOffset;
306                 currentFeedback = nextFeedback;
307             }
308         }
309         currentOffset = rawFeedbacks.getOffset();
310         if (currentOffset >= 0) {
311             inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
312                                    convertVisualFeedbackToHighlight(currentFeedback),
313                                    composedOffset + startOffset,
314                                    composedOffset + currentOffset);
315         }
316 
317         postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
318                              inputText.getIterator(),
319                              composedOffset,
320                              TextHitInfo.leading(caretPosition),
321                              visiblePositionInfo,
322                              when);
323     }
324 
325     /*
326      * Subclasses should override disposeImpl() instead of dispose(). Client
327      * code should always invoke dispose(), never disposeImpl().
328      */
disposeImpl()329     protected synchronized void disposeImpl() {
330         disposeXIC();
331         awtLock();
332         composedText = null;
333         committedText = null;
334         rawFeedbacks = null;
335         awtUnlock();
336         awtFocussedComponent = null;
337         lastXICFocussedComponent = null;
338     }
339 
340     /**
341      * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean)
342      */
setCompositionEnabled(boolean enable)343     public void setCompositionEnabled(boolean enable) {
344         /* If the composition state is successfully changed, set
345            the savedCompositionState to 'enable'. Otherwise, simply
346            return.
347            setCompositionEnabledNative may throw UnsupportedOperationException.
348            Don't try to catch it since the method may be called by clients.
349            Use package private mthod 'resetCompositionState' if you want the
350            exception to be caught.
351         */
352         if (setCompositionEnabledNative(enable)) {
353             savedCompositionState = enable;
354         }
355     }
356 }
357