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