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