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