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