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