1 /*
2 * Copyright (c) 1997, 2012, 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);
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);
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);
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);
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 {
170 initIDs();
171 }
172
173 /**
174 * Initialize JNI field and method IDs for fields that may be
175 accessed from C.
176 */
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 */
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
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 */
202 private synchronized boolean initXIM() {
203 if (isXIMOpened == false)
204 isXIMOpened = openXIM();
205 return isXIMOpened;
206 }
207
208 protected abstract boolean openXIM();
209
210 protected boolean isDisposed() {
211 return disposed;
212 }
213
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 */
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 */
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 */
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 */
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 */
266 public void dispatchEvent(AWTEvent e) {
267 }
268
269
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 */
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 */
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 */
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
371 protected abstract boolean createXIC();
372
373 /**
374 * Deactivate input method.
375 */
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 */
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
429 public void hideWindows() {
430 // ??? need real implementation
431 }
432
433 /**
434 * @see java.awt.Toolkit#mapInputMethodHighlight
435 */
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 */
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 */
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!
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
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 */
517 protected abstract ComponentPeer getPeer(Component client);
518
519 /**
520 * Used to protect preedit data
521 */
522 protected abstract void awtLock();
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 */
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
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!
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
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!
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!
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 */
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 */
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 */
819 public Object getControlObject() {
820 return null;
821 }
822
823 /**
824 * @see java.awt.im.spi.InputMethod#removeNotify
825 */
826 public synchronized void removeNotify() {
827 dispose();
828 }
829
830 /**
831 * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean)
832 */
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 */
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 */
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 */
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 */
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
1028 IntBuffer(int initialCapacity) {
1029 intArray = new int[initialCapacity];
1030 size = 0;
1031 index = 0;
1032 }
1033
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
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
1058 void replace(int offset, int[] values) {
1059 System.arraycopy(values, 0, intArray, offset, values.length);
1060 }
1061
1062 void removeAll() {
1063 size = 0;
1064 index = 0;
1065 }
1066
1067 void rewind() {
1068 index = 0;
1069 }
1070
1071 int getNext() {
1072 if (index == size)
1073 return -1;
1074 return intArray[index++];
1075 }
1076
1077 void unget() {
1078 if (index != 0)
1079 index--;
1080 }
1081
1082 int getOffset() {
1083 return index;
1084 }
1085
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 */
1100 protected native String resetXIC();
1101 private native void disposeXIC();
1102 private native boolean setCompositionEnabledNative(boolean enable);
1103 private native boolean isCompositionEnabledNative();
1104 private native void turnoffStatusWindow();
1105 }
--- EOF ---