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