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 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 resetXIC(); 420 needResetXICClient = null; 421 needResetXIC = false; 422 } 423 } 424 425 // implements java.awt.im.spi.InputMethod.hideWindows 426 public void hideWindows() { 427 // ??? need real implementation 428 } 429 430 /** 431 * @see java.awt.Toolkit#mapInputMethodHighlight 432 */ 433 public static Map mapInputMethodHighlight(InputMethodHighlight highlight) { 434 int index; 435 int state = highlight.getState(); 436 if (state == InputMethodHighlight.RAW_TEXT) { 437 index = 0; 438 } else if (state == InputMethodHighlight.CONVERTED_TEXT) { 439 index = 2; 440 } else { 441 return null; 442 } 443 if (highlight.isSelected()) { 444 index += 1; 445 } 446 return highlightStyles[index]; 447 } 448 449 /** 450 * @see sun.awt.im.InputMethodAdapter#setAWTFocussedComponent 451 */ 452 protected void setAWTFocussedComponent(Component component) { 453 if (component == null) { 454 return; 455 } 456 if (isActive) { 457 // deactivate/activate are being suppressed during a focus change - 458 // this may happen when an input method window is made visible 459 boolean ac = haveActiveClient(); 460 setXICFocus(getPeer(awtFocussedComponent), false, ac); 461 setXICFocus(getPeer(component), true, ac); 462 } 463 awtFocussedComponent = component; 464 } 465 466 /** 467 * @see sun.awt.im.InputMethodAdapter#stopListening 468 */ 469 protected void stopListening() { 470 // It is desirable to disable XIM by calling XSetICValues with 471 // XNPreeditState == XIMPreeditDisable. But Solaris 2.6 and 472 // Solaris 7 do not implement this correctly without a patch, 473 // so just call resetXIC here. Prior endComposition call commits 474 // the existing composed text. 475 endComposition(); 476 // disable the native input method so that the other input 477 // method could get the input focus. 478 disableInputMethod(); 479 if (needResetXIC) { 480 resetXIC(); 481 needResetXICClient = null; 482 needResetXIC = false; 483 } 484 } 485 486 /** 487 * Returns the Window instance in which the client component is 488 * contained. If not found, null is returned. (IS THIS POSSIBLE?) 489 */ 490 // NOTE: This method may be called by privileged threads. 491 // DO NOT INVOKE CLIENT CODE ON THIS THREAD! 492 private Window getClientComponentWindow() { 493 Component client = getClientComponent(); 494 Container container; 495 496 if (client instanceof Container) { 497 container = (Container) client; 498 } else { 499 container = getParent(client); 500 } 501 502 while (container != null && !(container instanceof java.awt.Window)) { 503 container = getParent(container); 504 } 505 return (Window) container; 506 } 507 508 protected abstract Container getParent(Component client); 509 510 /** 511 * Returns peer of the given client component. If the given client component 512 * doesn't have peer, peer of the native container of the client is returned. 513 */ 514 protected abstract ComponentPeer getPeer(Component client); 515 516 /** 517 * Used to protect preedit data 518 */ 519 protected abstract void awtLock(); 520 protected abstract void awtUnlock(); 521 522 /** 523 * Creates an input method event from the arguments given 524 * and posts it on the AWT event queue. For arguments, 525 * see InputMethodEvent. Called by input method. 526 * 527 * @see java.awt.event.InputMethodEvent#InputMethodEvent 528 */ 529 private void postInputMethodEvent(int id, 530 AttributedCharacterIterator text, 531 int committedCharacterCount, 532 TextHitInfo caret, 533 TextHitInfo visiblePosition, 534 long when) { 535 Component source = getClientComponent(); 536 if (source != null) { 537 InputMethodEvent event = new InputMethodEvent(source, 538 id, when, text, committedCharacterCount, caret, visiblePosition); 539 SunToolkit.postEvent(SunToolkit.targetToAppContext(source), (AWTEvent)event); 540 } 541 } 542 543 private void postInputMethodEvent(int id, 544 AttributedCharacterIterator text, 545 int committedCharacterCount, 546 TextHitInfo caret, 547 TextHitInfo visiblePosition) { 548 postInputMethodEvent(id, text, committedCharacterCount, 549 caret, visiblePosition, EventQueue.getMostRecentEventTime()); 550 } 551 552 /** 553 * Dispatches committed text from XIM to the awt event queue. This 554 * method is invoked from the event handler in canvas.c in the 555 * AWT Toolkit thread context and thus inside the AWT Lock. 556 * @param str committed text 557 * @param long when 558 */ 559 // NOTE: This method may be called by privileged threads. 560 // This functionality is implemented in a package-private method 561 // to insure that it cannot be overridden by client subclasses. 562 // DO NOT INVOKE CLIENT CODE ON THIS THREAD! 563 void dispatchCommittedText(String str, long when) { 564 if (str == null) 565 return; 566 567 if (composedText == null) { 568 AttributedString attrstr = new AttributedString(str); 569 postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 570 attrstr.getIterator(), 571 str.length(), 572 null, 573 null, 574 when); 575 } else { 576 // if there is composed text, wait until the preedit 577 // callback is invoked. 578 committedText = str; 579 } 580 } 581 582 private void dispatchCommittedText(String str) { 583 dispatchCommittedText(str, EventQueue.getMostRecentEventTime()); 584 } 585 586 /** 587 * Updates composed text with XIM preedit information and 588 * posts composed text to the awt event queue. The args of 589 * this method correspond to the XIM preedit callback 590 * information. The XIM highlight attributes are translated via 591 * fixed mapping (i.e., independent from any underlying input 592 * method engine). This method is invoked in the AWT Toolkit 593 * (X event loop) thread context and thus inside the AWT Lock. 594 */ 595 // NOTE: This method may be called by privileged threads. 596 // This functionality is implemented in a package-private method 597 // to insure that it cannot be overridden by client subclasses. 598 // DO NOT INVOKE CLIENT CODE ON THIS THREAD! 599 void dispatchComposedText(String chgText, 600 int chgStyles[], 601 int chgOffset, 602 int chgLength, 603 int caretPosition, 604 long when) { 605 if (disposed) { 606 return; 607 } 608 609 //Workaround for deadlock bug on solaris2.6_zh bug#4170760 610 if (chgText == null 611 && chgStyles == null 612 && chgOffset == 0 613 && chgLength == 0 614 && caretPosition == 0 615 && composedText == null 616 && committedText == null) 617 return; 618 619 if (composedText == null) { 620 // TODO: avoid reallocation of those buffers 621 composedText = new StringBuffer(INITIAL_SIZE); 622 rawFeedbacks = new IntBuffer(INITIAL_SIZE); 623 } 624 if (chgLength > 0) { 625 if (chgText == null && chgStyles != null) { 626 rawFeedbacks.replace(chgOffset, chgStyles); 627 } else { 628 if (chgLength == composedText.length()) { 629 // optimization for the special case to replace the 630 // entire previous text 631 composedText = new StringBuffer(INITIAL_SIZE); 632 rawFeedbacks = new IntBuffer(INITIAL_SIZE); 633 } else { 634 if (composedText.length() > 0) { 635 if (chgOffset+chgLength < composedText.length()) { 636 String text; 637 text = composedText.toString().substring(chgOffset+chgLength, 638 composedText.length()); 639 composedText.setLength(chgOffset); 640 composedText.append(text); 641 } else { 642 // in case to remove substring from chgOffset 643 // to the end 644 composedText.setLength(chgOffset); 645 } 646 rawFeedbacks.remove(chgOffset, chgLength); 647 } 648 } 649 } 650 } 651 if (chgText != null) { 652 composedText.insert(chgOffset, chgText); 653 if (chgStyles != null) 654 rawFeedbacks.insert(chgOffset, chgStyles); 655 } 656 657 if (composedText.length() == 0) { 658 composedText = null; 659 rawFeedbacks = null; 660 661 // if there is any outstanding committed text stored by 662 // dispatchCommittedText(), it has to be sent to the 663 // client component. 664 if (committedText != null) { 665 dispatchCommittedText(committedText, when); 666 committedText = null; 667 return; 668 } 669 670 // otherwise, send null text to delete client's composed 671 // text. 672 postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 673 null, 674 0, 675 null, 676 null, 677 when); 678 679 return; 680 } 681 682 // Now sending the composed text to the client 683 int composedOffset; 684 AttributedString inputText; 685 686 // if there is any partially committed text, concatenate it to 687 // the composed text. 688 if (committedText != null) { 689 composedOffset = committedText.length(); 690 inputText = new AttributedString(committedText + composedText); 691 committedText = null; 692 } else { 693 composedOffset = 0; 694 inputText = new AttributedString(composedText.toString()); 695 } 696 697 int currentFeedback; 698 int nextFeedback; 699 int startOffset = 0; 700 int currentOffset; 701 int visiblePosition = 0; 702 TextHitInfo visiblePositionInfo = null; 703 704 rawFeedbacks.rewind(); 705 currentFeedback = rawFeedbacks.getNext(); 706 rawFeedbacks.unget(); 707 while ((nextFeedback = rawFeedbacks.getNext()) != -1) { 708 if (visiblePosition == 0) { 709 visiblePosition = nextFeedback & XIMVisibleMask; 710 if (visiblePosition != 0) { 711 int index = rawFeedbacks.getOffset() - 1; 712 713 if (visiblePosition == XIMVisibleToBackward) 714 visiblePositionInfo = TextHitInfo.leading(index); 715 else 716 visiblePositionInfo = TextHitInfo.trailing(index); 717 } 718 } 719 nextFeedback &= ~XIMVisibleMask; 720 if (currentFeedback != nextFeedback) { 721 rawFeedbacks.unget(); 722 currentOffset = rawFeedbacks.getOffset(); 723 inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, 724 convertVisualFeedbackToHighlight(currentFeedback), 725 composedOffset + startOffset, 726 composedOffset + currentOffset); 727 startOffset = currentOffset; 728 currentFeedback = nextFeedback; 729 } 730 } 731 currentOffset = rawFeedbacks.getOffset(); 732 if (currentOffset >= 0) { 733 inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, 734 convertVisualFeedbackToHighlight(currentFeedback), 735 composedOffset + startOffset, 736 composedOffset + currentOffset); 737 } 738 739 postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 740 inputText.getIterator(), 741 composedOffset, 742 TextHitInfo.leading(caretPosition), 743 visiblePositionInfo, 744 when); 745 } 746 747 /** 748 * Flushes composed and committed text held in this context. 749 * This method is invoked in the AWT Toolkit (X event loop) thread context 750 * and thus inside the AWT Lock. 751 */ 752 // NOTE: This method may be called by privileged threads. 753 // This functionality is implemented in a package-private method 754 // to insure that it cannot be overridden by client subclasses. 755 // DO NOT INVOKE CLIENT CODE ON THIS THREAD! 756 void flushText() { 757 String flush = (committedText != null ? committedText : ""); 758 if (composedText != null) { 759 flush += composedText.toString(); 760 } 761 762 if (!flush.equals("")) { 763 AttributedString attrstr = new AttributedString(flush); 764 postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 765 attrstr.getIterator(), 766 flush.length(), 767 null, 768 null, 769 EventQueue.getMostRecentEventTime()); 770 composedText = null; 771 committedText = null; 772 } 773 } 774 775 /* 776 * Subclasses should override disposeImpl() instead of dispose(). Client 777 * code should always invoke dispose(), never disposeImpl(). 778 */ 779 protected synchronized void disposeImpl() { 780 disposeXIC(); 781 awtLock(); 782 composedText = null; 783 committedText = null; 784 rawFeedbacks = null; 785 awtUnlock(); 786 awtFocussedComponent = null; 787 lastXICFocussedComponent = null; 788 } 789 790 /** 791 * Frees all X Window resources associated with this object. 792 * 793 * @see java.awt.im.spi.InputMethod#dispose 794 */ 795 public final void dispose() { 796 boolean call_disposeImpl = false; 797 798 if (!disposed) { 799 synchronized (this) { 800 if (!disposed) { 801 disposed = call_disposeImpl = true; 802 } 803 } 804 } 805 806 if (call_disposeImpl) { 807 disposeImpl(); 808 } 809 } 810 811 /** 812 * Returns null. 813 * 814 * @see java.awt.im.spi.InputMethod#getControlObject 815 */ 816 public Object getControlObject() { 817 return null; 818 } 819 820 /** 821 * @see java.awt.im.spi.InputMethod#removeNotify 822 */ 823 public synchronized void removeNotify() { 824 dispose(); 825 } 826 827 /** 828 * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean) 829 */ 830 public void setCompositionEnabled(boolean enable) { 831 /* If the composition state is successfully changed, set 832 the savedCompositionState to 'enable'. Otherwise, simply 833 return. 834 setCompositionEnabledNative may throw UnsupportedOperationException. 835 Don't try to catch it since the method may be called by clients. 836 Use package private mthod 'resetCompositionState' if you want the 837 exception to be caught. 838 */ 839 if (setCompositionEnabledNative(enable)) { 840 savedCompositionState = enable; 841 } 842 } 843 844 /** 845 * @see java.awt.im.spi.InputMethod#isCompositionEnabled 846 */ 847 public boolean isCompositionEnabled() { 848 /* isCompositionEnabledNative may throw UnsupportedOperationException. 849 Don't try to catch it since this method may be called by clients. 850 Use package private method 'getCompositionState' if you want the 851 exception to be caught. 852 */ 853 return isCompositionEnabledNative(); 854 } 855 856 /** 857 * Ends any input composition that may currently be going on in this 858 * context. Depending on the platform and possibly user preferences, 859 * this may commit or delete uncommitted text. Any changes to the text 860 * are communicated to the active component using an input method event. 861 * 862 * <p> 863 * A text editing component may call this in a variety of situations, 864 * for example, when the user moves the insertion point within the text 865 * (but outside the composed text), or when the component's text is 866 * saved to a file or copied to the clipboard. 867 * 868 */ 869 public void endComposition() { 870 if (disposed) { 871 return; 872 } 873 874 /* Before calling resetXIC, record the current composition mode 875 so that it can be restored later. */ 876 savedCompositionState = getCompositionState(); 877 boolean active = haveActiveClient(); 878 if (active && composedText == null && committedText == null){ 879 needResetXIC = true; 880 needResetXICClient = getClientComponent(); 881 return; 882 } 883 884 String text = resetXIC(); 885 /* needResetXIC is only set to true for active client. So passive 886 client should not reset the flag to false. */ 887 if (active) { 888 needResetXIC = false; 889 } 890 891 // Remove any existing composed text by posting an InputMethodEvent 892 // with null composed text. It would be desirable to wait for a 893 // dispatchComposedText call from X input method engine, but some 894 // input method does not conform to the XIM specification and does 895 // not call the preedit callback to erase preedit text on calling 896 // XmbResetIC. To work around this problem, do it here by ourselves. 897 awtLock(); 898 composedText = null; 899 postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 900 null, 901 0, 902 null, 903 null); 904 905 if (text != null && text.length() > 0) { 906 dispatchCommittedText(text); 907 } 908 awtUnlock(); 909 910 // Restore the preedit state if it was enabled 911 if (savedCompositionState) { 912 resetCompositionState(); 913 } 914 } 915 916 /** 917 * Returns a string with information about the current input method server, or null. 918 * On both Linux & SunOS, the value of environment variable XMODIFIERS is 919 * returned if set. Otherwise, on SunOS, $HOME/.dtprofile will be parsed 920 * to find out the language service engine (atok or wnn) since there is 921 * no API in Xlib which returns the information of native 922 * IM server or language service and we want to try our best to return as much 923 * information as possible. 924 * 925 * Note: This method could return null on Linux if XMODIFIERS is not set properly or 926 * if any IOException is thrown. 927 * See man page of XSetLocaleModifiers(3X11) for the usgae of XMODIFIERS, 928 * atok12setup(1) and wnn6setup(1) for the information written to 929 * $HOME/.dtprofile when you run these two commands. 930 * 931 */ 932 public String getNativeInputMethodInfo() { 933 String xmodifiers = System.getenv("XMODIFIERS"); 934 String imInfo = null; 935 936 // If XMODIFIERS is set, return the value 937 if (xmodifiers != null) { 938 int imIndex = xmodifiers.indexOf("@im="); 939 if (imIndex != -1) { 940 imInfo = xmodifiers.substring(imIndex + 4); 941 } 942 } else if (System.getProperty("os.name").startsWith("SunOS")) { 943 File dtprofile = new File(System.getProperty("user.home") + 944 "/.dtprofile"); 945 String languageEngineInfo = null; 946 try { 947 BufferedReader br = new BufferedReader(new FileReader(dtprofile)); 948 String line = null; 949 950 while ( languageEngineInfo == null && (line = br.readLine()) != null) { 951 if (line.contains("atok") || line.contains("wnn")) { 952 StringTokenizer tokens = new StringTokenizer(line); 953 while (tokens.hasMoreTokens()) { 954 String token = tokens.nextToken(); 955 if (Pattern.matches("atok.*setup", token) || 956 Pattern.matches("wnn.*setup", token)){ 957 languageEngineInfo = token.substring(0, token.indexOf("setup")); 958 break; 959 } 960 } 961 } 962 } 963 964 br.close(); 965 } catch(IOException ioex) { 966 // Since this method is provided for internal testing only, 967 // we dump the stack trace for the ease of debugging. 968 ioex.printStackTrace(); 969 } 970 971 imInfo = "htt " + languageEngineInfo; 972 } 973 974 return imInfo; 975 } 976 977 978 /** 979 * Performs mapping from an XIM visible feedback value to Java IM highlight. 980 * @return Java input method highlight 981 */ 982 private InputMethodHighlight convertVisualFeedbackToHighlight(int feedback) { 983 InputMethodHighlight highlight; 984 985 switch (feedback) { 986 case XIMUnderline: 987 highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT; 988 break; 989 case XIMReverse: 990 highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT; 991 break; 992 case XIMHighlight: 993 highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT; 994 break; 995 case XIMPrimary: 996 highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT; 997 break; 998 case XIMSecondary: 999 highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT; 1000 break; 1001 case XIMTertiary: 1002 highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT; 1003 break; 1004 default: 1005 highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT; 1006 break; 1007 } 1008 return highlight; 1009 } 1010 1011 // initial capacity size for string buffer, etc. 1012 private static final int INITIAL_SIZE = 64; 1013 1014 /** 1015 * IntBuffer is an inner class that manipulates an int array and 1016 * provides UNIX file io stream-like programming interfaces to 1017 * access it. (An alternative would be to use ArrayList which may 1018 * be too expensive for the work.) 1019 */ 1020 private final class IntBuffer { 1021 private int[] intArray; 1022 private int size; 1023 private int index; 1024 1025 IntBuffer(int initialCapacity) { 1026 intArray = new int[initialCapacity]; 1027 size = 0; 1028 index = 0; 1029 } 1030 1031 void insert(int offset, int[] values) { 1032 int newSize = size + values.length; 1033 if (intArray.length < newSize) { 1034 int[] newIntArray = new int[newSize * 2]; 1035 System.arraycopy(intArray, 0, newIntArray, 0, size); 1036 intArray = newIntArray; 1037 } 1038 System.arraycopy(intArray, offset, intArray, offset+values.length, 1039 size - offset); 1040 System.arraycopy(values, 0, intArray, offset, values.length); 1041 size += values.length; 1042 if (index > offset) 1043 index = offset; 1044 } 1045 1046 void remove(int offset, int length) { 1047 if (offset + length != size) 1048 System.arraycopy(intArray, offset+length, intArray, offset, 1049 size - offset - length); 1050 size -= length; 1051 if (index > offset) 1052 index = offset; 1053 } 1054 1055 void replace(int offset, int[] values) { 1056 System.arraycopy(values, 0, intArray, offset, values.length); 1057 } 1058 1059 void removeAll() { 1060 size = 0; 1061 index = 0; 1062 } 1063 1064 void rewind() { 1065 index = 0; 1066 } 1067 1068 int getNext() { 1069 if (index == size) 1070 return -1; 1071 return intArray[index++]; 1072 } 1073 1074 void unget() { 1075 if (index != 0) 1076 index--; 1077 } 1078 1079 int getOffset() { 1080 return index; 1081 } 1082 1083 public String toString() { 1084 StringBuffer s = new StringBuffer(); 1085 for (int i = 0; i < size;) { 1086 s.append(intArray[i++]); 1087 if (i < size) 1088 s.append(","); 1089 } 1090 return s.toString(); 1091 } 1092 } 1093 1094 /* 1095 * Native methods 1096 */ 1097 protected native String resetXIC(); 1098 private native void disposeXIC(); 1099 private native boolean setCompositionEnabledNative(boolean enable); 1100 private native boolean isCompositionEnabledNative(); 1101 private native void turnoffStatusWindow(); 1102 }