|
1 /* |
|
2 * Copyright (c) 1997, 2018, 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.EventQueue; |
|
30 import java.awt.event.InputMethodEvent; |
|
31 import java.awt.font.TextAttribute; |
|
32 import java.awt.font.TextHitInfo; |
|
33 import java.awt.peer.ComponentPeer; |
|
34 import java.text.AttributedString; |
|
35 |
|
36 import sun.util.logging.PlatformLogger; |
|
37 |
|
38 /** |
|
39 * Input Method Adapter for XIM for AIX |
|
40 * |
|
41 * @author JavaSoft International |
|
42 */ |
|
43 public abstract class X11InputMethod extends X11InputMethodBase { |
|
44 |
|
45 // to keep the instance of activating if IM resumed |
|
46 static protected X11InputMethod activatedInstance = null; |
|
47 |
|
48 /** |
|
49 * Constructs an X11InputMethod instance. It initializes the XIM |
|
50 * environment if it's not done yet. |
|
51 * |
|
52 * @exception AWTException if XOpenIM() failed. |
|
53 */ |
|
54 public X11InputMethod() throws AWTException { |
|
55 super(); |
|
56 } |
|
57 |
|
58 /** |
|
59 * Reset the composition state to the current composition state. |
|
60 */ |
|
61 protected void resetCompositionState() { |
|
62 if (compositionEnableSupported && haveActiveClient()) { |
|
63 try { |
|
64 /* Restore the composition mode to the last saved composition |
|
65 mode. */ |
|
66 setCompositionEnabled(savedCompositionState); |
|
67 } catch (UnsupportedOperationException e) { |
|
68 compositionEnableSupported = false; |
|
69 } |
|
70 } |
|
71 } |
|
72 |
|
73 /** |
|
74 * Activate input method. |
|
75 */ |
|
76 public synchronized void activate() { |
|
77 activatedInstance = this; |
|
78 clientComponentWindow = getClientComponentWindow(); |
|
79 if (clientComponentWindow == null) |
|
80 return; |
|
81 |
|
82 if (lastXICFocussedComponent != null) { |
|
83 if (log.isLoggable(PlatformLogger.Level.FINE)) { |
|
84 log.fine("XICFocused {0}, AWTFocused {1}", |
|
85 lastXICFocussedComponent, awtFocussedComponent); |
|
86 } |
|
87 if (lastXICFocussedComponent != awtFocussedComponent) { |
|
88 ComponentPeer lastXICFocussedComponentPeer = getPeer(lastXICFocussedComponent); |
|
89 if (lastXICFocussedComponentPeer != null) { |
|
90 setXICFocus(lastXICFocussedComponentPeer, false, isLastXICActive); |
|
91 } |
|
92 } |
|
93 lastXICFocussedComponent = null; |
|
94 } |
|
95 |
|
96 if (pData == 0) { |
|
97 if (!createXIC()) { |
|
98 return; |
|
99 } |
|
100 disposed = false; |
|
101 } |
|
102 |
|
103 /* reset input context if necessary and set the XIC focus |
|
104 */ |
|
105 resetXICifneeded(); |
|
106 ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent); |
|
107 setStatusAreaVisible(true, pData); |
|
108 |
|
109 if (awtFocussedComponentPeer != null) { |
|
110 setXICFocus(awtFocussedComponentPeer, true, haveActiveClient()); |
|
111 } |
|
112 lastXICFocussedComponent = awtFocussedComponent; |
|
113 isLastXICActive = haveActiveClient(); |
|
114 isActive = true; |
|
115 if (savedCompositionState) { |
|
116 resetCompositionState(); |
|
117 } |
|
118 } |
|
119 |
|
120 /** |
|
121 * Deactivate input method. |
|
122 */ |
|
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 activatedInstance = null; |
|
144 savedCompositionState = getCompositionState(); |
|
145 |
|
146 if (isTemporary) { |
|
147 //turn the status window off... |
|
148 turnoffStatusWindow(); |
|
149 /* Delay resetting the XIC focus until activate is called and the newly |
|
150 * Focused component has a different peer as the last focused component. |
|
151 */ |
|
152 lastXICFocussedComponent = awtFocussedComponent; |
|
153 } else { |
|
154 if (awtFocussedComponent != null ) { |
|
155 ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent); |
|
156 if (awtFocussedComponentPeer != null) { |
|
157 setXICFocus(awtFocussedComponentPeer, false, isAc); |
|
158 } |
|
159 } |
|
160 lastXICFocussedComponent = null; |
|
161 } |
|
162 |
|
163 isLastXICActive = isAc; |
|
164 isLastTemporary = isTemporary; |
|
165 isActive = false; |
|
166 setStatusAreaVisible(false, pData); |
|
167 } |
|
168 |
|
169 // implements java.awt.im.spi.InputMethod.hideWindows |
|
170 public void hideWindows() { |
|
171 if (pData != 0) { |
|
172 setStatusAreaVisible(false, pData); |
|
173 turnoffStatusWindow(); |
|
174 } |
|
175 } |
|
176 |
|
177 /** |
|
178 * Updates composed text with XIM preedit information and |
|
179 * posts composed text to the awt event queue. The args of |
|
180 * this method correspond to the XIM preedit callback |
|
181 * information. The XIM highlight attributes are translated via |
|
182 * fixed mapping (i.e., independent from any underlying input |
|
183 * method engine). This method is invoked in the AWT Toolkit |
|
184 * (X event loop) thread context and thus inside the AWT Lock. |
|
185 */ |
|
186 // NOTE: This method may be called by privileged threads. |
|
187 // This functionality is implemented in a package-private method |
|
188 // to insure that it cannot be overridden by client subclasses. |
|
189 // DO NOT INVOKE CLIENT CODE ON THIS THREAD! |
|
190 void dispatchComposedText(String chgText, |
|
191 int chgStyles[], |
|
192 int chgOffset, |
|
193 int chgLength, |
|
194 int caretPosition, |
|
195 long when) { |
|
196 if (disposed) { |
|
197 return; |
|
198 } |
|
199 |
|
200 // Workaround for deadlock bug on solaris2.6_zh bug#4170760 |
|
201 if (chgText == null |
|
202 && chgStyles == null |
|
203 && chgOffset == 0 |
|
204 && chgLength == 0 |
|
205 && caretPosition == 0 |
|
206 && composedText == null |
|
207 && committedText == null) |
|
208 return; |
|
209 |
|
210 // Recalculate chgOffset and chgLength for supplementary char |
|
211 if (composedText != null) { |
|
212 int tmpChgOffset=chgOffset; |
|
213 int tmpChgLength=chgLength; |
|
214 int index = 0; |
|
215 for (int i=0;i < tmpChgOffset; i++,index++){ |
|
216 if (index < composedText.length() |
|
217 && Character.charCount(composedText.codePointAt(index))==2){ |
|
218 index++; |
|
219 chgOffset++; |
|
220 } |
|
221 } |
|
222 // The index keeps value |
|
223 for (int i=0;i < tmpChgLength; i++,index++){ |
|
224 if (index < composedText.length() |
|
225 && Character.charCount(composedText.codePointAt(index))==2){ |
|
226 index++; |
|
227 chgLength++; |
|
228 } |
|
229 } |
|
230 } |
|
231 |
|
232 // Replace control character with a square box |
|
233 if (chgText != null) { |
|
234 StringBuffer newChgText = new StringBuffer(); |
|
235 for (int i=0; i < chgText.length(); i++){ |
|
236 char c = chgText.charAt(i); |
|
237 if (Character.isISOControl(c)){ |
|
238 c = '\u25A1'; |
|
239 } |
|
240 newChgText.append(c); |
|
241 } |
|
242 chgText = new String(newChgText); |
|
243 } |
|
244 |
|
245 if (composedText == null) { |
|
246 // TODO: avoid reallocation of those buffers |
|
247 composedText = new StringBuffer(INITIAL_SIZE); |
|
248 rawFeedbacks = new IntBuffer(INITIAL_SIZE); |
|
249 } |
|
250 if (chgLength > 0) { |
|
251 if (chgText == null && chgStyles != null) { |
|
252 rawFeedbacks.replace(chgOffset, chgStyles); |
|
253 } else { |
|
254 if (chgLength == composedText.length()) { |
|
255 // optimization for the special case to replace the |
|
256 // entire previous text |
|
257 composedText = new StringBuffer(INITIAL_SIZE); |
|
258 rawFeedbacks = new IntBuffer(INITIAL_SIZE); |
|
259 } else { |
|
260 if (composedText.length() > 0) { |
|
261 if (chgOffset+chgLength < composedText.length()) { |
|
262 String text; |
|
263 text = composedText.toString().substring(chgOffset+chgLength, |
|
264 composedText.length()); |
|
265 composedText.setLength(chgOffset); |
|
266 composedText.append(text); |
|
267 } else { |
|
268 // in case to remove substring from chgOffset |
|
269 // to the end |
|
270 composedText.setLength(chgOffset); |
|
271 } |
|
272 rawFeedbacks.remove(chgOffset, chgLength); |
|
273 } |
|
274 } |
|
275 } |
|
276 } |
|
277 if (chgText != null) { |
|
278 composedText.insert(chgOffset, chgText); |
|
279 if (chgStyles != null) { |
|
280 // Recalculate chgStyles for supplementary char |
|
281 if (chgText.length() > chgStyles.length){ |
|
282 int index=0; |
|
283 int[] newStyles = new int[chgText.length()]; |
|
284 for (int i=0; i < chgStyles.length; i++, index++){ |
|
285 newStyles[index]=chgStyles[i]; |
|
286 if (index < chgText.length() |
|
287 && Character.charCount(chgText.codePointAt(index))==2){ |
|
288 newStyles[++index]=chgStyles[i]; |
|
289 } |
|
290 } |
|
291 chgStyles=newStyles; |
|
292 } |
|
293 rawFeedbacks.insert(chgOffset, chgStyles); |
|
294 } |
|
295 |
|
296 } |
|
297 |
|
298 else if (chgStyles != null) { |
|
299 // Recalculate chgStyles to support supplementary char |
|
300 int count=0; |
|
301 for (int i=0; i < chgStyles.length; i++){ |
|
302 if (composedText.length() > chgOffset+i+count |
|
303 && Character.charCount(composedText.codePointAt(chgOffset+i+count))==2){ |
|
304 count++; |
|
305 } |
|
306 } |
|
307 if (count>0){ |
|
308 int index=0; |
|
309 int[] newStyles = new int[chgStyles.length+count]; |
|
310 for (int i=0; i < chgStyles.length; i++, index++){ |
|
311 newStyles[index]=chgStyles[i]; |
|
312 if (composedText.length() > chgOffset+index |
|
313 && Character.charCount(composedText.codePointAt(chgOffset+index))==2){ |
|
314 newStyles[++index]=chgStyles[i]; |
|
315 } |
|
316 } |
|
317 chgStyles=newStyles; |
|
318 } |
|
319 rawFeedbacks.replace(chgOffset, chgStyles); |
|
320 } |
|
321 |
|
322 if (composedText.length() == 0) { |
|
323 composedText = null; |
|
324 rawFeedbacks = null; |
|
325 |
|
326 // if there is any outstanding committed text stored by |
|
327 // dispatchCommittedText(), it has to be sent to the |
|
328 // client component. |
|
329 if (committedText != null) { |
|
330 dispatchCommittedText(committedText, when); |
|
331 committedText = null; |
|
332 return; |
|
333 } |
|
334 |
|
335 // otherwise, send null text to delete client's composed |
|
336 // text. |
|
337 postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, |
|
338 null, |
|
339 0, |
|
340 null, |
|
341 null, |
|
342 when); |
|
343 |
|
344 return; |
|
345 } |
|
346 |
|
347 // Adjust caretPosition for supplementary char |
|
348 for (int i=0; i< caretPosition; i++){ |
|
349 if (i < composedText.length() |
|
350 && Character.charCount(composedText.codePointAt(i))==2){ |
|
351 caretPosition++; |
|
352 i++; |
|
353 } |
|
354 } |
|
355 |
|
356 // Now sending the composed text to the client |
|
357 int composedOffset; |
|
358 AttributedString inputText; |
|
359 |
|
360 // if there is any partially committed text, concatenate it to |
|
361 // the composed text. |
|
362 if (committedText != null) { |
|
363 composedOffset = committedText.length(); |
|
364 inputText = new AttributedString(committedText + composedText); |
|
365 committedText = null; |
|
366 } else { |
|
367 composedOffset = 0; |
|
368 inputText = new AttributedString(composedText.toString()); |
|
369 } |
|
370 |
|
371 int currentFeedback; |
|
372 int nextFeedback; |
|
373 int startOffset = 0; |
|
374 int currentOffset; |
|
375 int visiblePosition = 0; |
|
376 TextHitInfo visiblePositionInfo = null; |
|
377 |
|
378 rawFeedbacks.rewind(); |
|
379 currentFeedback = rawFeedbacks.getNext(); |
|
380 rawFeedbacks.unget(); |
|
381 while ((nextFeedback = rawFeedbacks.getNext()) != -1) { |
|
382 if (visiblePosition == 0) { |
|
383 visiblePosition = nextFeedback & XIMVisibleMask; |
|
384 if (visiblePosition != 0) { |
|
385 int index = rawFeedbacks.getOffset() - 1; |
|
386 |
|
387 if (visiblePosition == XIMVisibleToBackward) |
|
388 visiblePositionInfo = TextHitInfo.leading(index); |
|
389 else |
|
390 visiblePositionInfo = TextHitInfo.trailing(index); |
|
391 } |
|
392 } |
|
393 nextFeedback &= ~XIMVisibleMask; |
|
394 if (currentFeedback != nextFeedback) { |
|
395 rawFeedbacks.unget(); |
|
396 currentOffset = rawFeedbacks.getOffset(); |
|
397 inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, |
|
398 convertVisualFeedbackToHighlight(currentFeedback), |
|
399 composedOffset + startOffset, |
|
400 composedOffset + currentOffset); |
|
401 startOffset = currentOffset; |
|
402 currentFeedback = nextFeedback; |
|
403 } |
|
404 } |
|
405 currentOffset = rawFeedbacks.getOffset(); |
|
406 if (currentOffset >= 0) { |
|
407 inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, |
|
408 convertVisualFeedbackToHighlight(currentFeedback), |
|
409 composedOffset + startOffset, |
|
410 composedOffset + currentOffset); |
|
411 } |
|
412 |
|
413 postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, |
|
414 inputText.getIterator(), |
|
415 composedOffset, |
|
416 TextHitInfo.leading(caretPosition), |
|
417 visiblePositionInfo, |
|
418 when); |
|
419 } |
|
420 |
|
421 /* Some IMs need forced Text clear */ |
|
422 void clearComposedText(long when) { |
|
423 composedText = null; |
|
424 postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, |
|
425 null, 0, null, null, |
|
426 when); |
|
427 if (committedText != null && committedText.length() > 0) { |
|
428 dispatchCommittedText(committedText, when); |
|
429 } |
|
430 committedText = null; |
|
431 rawFeedbacks = null; |
|
432 } |
|
433 |
|
434 void clearComposedText() { |
|
435 if (EventQueue.isDispatchThread()) { |
|
436 clearComposedText(EventQueue.getMostRecentEventTime()); |
|
437 } |
|
438 } |
|
439 |
|
440 /* |
|
441 * Subclasses should override disposeImpl() instead of dispose(). Client |
|
442 * code should always invoke dispose(), never disposeImpl(). |
|
443 */ |
|
444 protected synchronized void disposeImpl() { |
|
445 disposeXIC(); |
|
446 awtLock(); |
|
447 try { |
|
448 clearComposedText(); |
|
449 } finally { |
|
450 // Put awtUnlock into finally block in case an exception is thrown in clearComposedText. |
|
451 awtUnlock(); |
|
452 } |
|
453 awtFocussedComponent = null; |
|
454 lastXICFocussedComponent = null; |
|
455 needResetXIC = false; |
|
456 savedCompositionState = false; |
|
457 compositionEnableSupported = true; |
|
458 } |
|
459 |
|
460 /** |
|
461 * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean) |
|
462 */ |
|
463 public void setCompositionEnabled(boolean enable) { |
|
464 /* If the composition state is successfully changed, set |
|
465 the savedCompositionState to 'enable'. Otherwise, simply |
|
466 return. |
|
467 setCompositionEnabledNative may throw UnsupportedOperationException. |
|
468 Don't try to catch it since the method may be called by clients. |
|
469 Use package private mthod 'resetCompositionState' if you want the |
|
470 exception to be caught. |
|
471 */ |
|
472 boolean pre, post; |
|
473 pre=getCompositionState(); |
|
474 |
|
475 if (setCompositionEnabledNative(enable)) { |
|
476 savedCompositionState = enable; |
|
477 } |
|
478 |
|
479 post=getCompositionState(); |
|
480 if (pre != post && post == enable){ |
|
481 if (enable == false) flushText(); |
|
482 if (awtFocussedComponent != null && isActive){ |
|
483 setXICFocus(getPeer(awtFocussedComponent), |
|
484 true, haveActiveClient()); |
|
485 } |
|
486 } |
|
487 } |
|
488 |
|
489 private native void setStatusAreaVisible(boolean value, long data); |
|
490 } |