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 27 package sun.awt.windows; 28 29 import java.awt.*; 30 import java.awt.peer.*; 31 import java.awt.event.*; 32 import java.awt.im.*; 33 import java.awt.im.spi.InputMethodContext; 34 import java.awt.font.*; 35 import java.text.*; 36 import java.text.AttributedCharacterIterator.Attribute; 37 import java.lang.Character.Subset; 38 import java.lang.Character.UnicodeBlock; 39 import java.util.Collections; 40 import java.util.HashMap; 41 import java.util.Locale; 42 import java.util.Map; 43 44 import sun.awt.AWTAccessor; 45 import sun.awt.AWTAccessor.ComponentAccessor; 46 import sun.awt.im.InputMethodAdapter; 47 48 final class WInputMethod extends InputMethodAdapter 49 { 50 /** 51 * The input method context, which is used to dispatch input method 52 * events to the client component and to request information from 53 * the client component. 54 */ 55 private InputMethodContext inputContext; 56 57 private Component awtFocussedComponent; 58 private WComponentPeer awtFocussedComponentPeer = null; 59 private WComponentPeer lastFocussedComponentPeer = null; 60 private boolean isLastFocussedActiveClient = false; 61 private boolean isActive; 62 private int context; 63 private boolean open; //default open status; 64 private int cmode; //default conversion mode; 65 private Locale currentLocale; 66 // indicate whether status window is hidden or not. 67 private boolean statusWindowHidden = false; 68 69 // attribute definition in Win32 (in IMM.H) 70 public static final byte ATTR_INPUT = 0x00; 71 public static final byte ATTR_TARGET_CONVERTED = 0x01; 72 public static final byte ATTR_CONVERTED = 0x02; 73 public static final byte ATTR_TARGET_NOTCONVERTED = 0x03; 74 public static final byte ATTR_INPUT_ERROR = 0x04; 75 // cmode definition in Win32 (in IMM.H) 76 public static final int IME_CMODE_ALPHANUMERIC = 0x0000; 77 public static final int IME_CMODE_NATIVE = 0x0001; 78 public static final int IME_CMODE_KATAKANA = 0x0002; 79 public static final int IME_CMODE_LANGUAGE = 0x0003; 80 public static final int IME_CMODE_FULLSHAPE = 0x0008; 81 public static final int IME_CMODE_HANJACONVERT = 0x0040; 82 public static final int IME_CMODE_ROMAN = 0x0010; 83 84 // flag values for endCompositionNative() behavior 85 private static final boolean COMMIT_INPUT = true; 86 private static final boolean DISCARD_INPUT = false; 87 88 private static Map<TextAttribute,Object> [] highlightStyles; 89 90 // Initialize highlight mapping table 91 static { 92 @SuppressWarnings({"rawtypes", "unchecked"}) 93 Map<TextAttribute,Object> styles[] = new Map[4]; 94 HashMap<TextAttribute,Object> map; 95 96 // UNSELECTED_RAW_TEXT_HIGHLIGHT 97 map = new HashMap<>(1); 98 map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_DOTTED); 99 styles[0] = Collections.unmodifiableMap(map); 100 101 // SELECTED_RAW_TEXT_HIGHLIGHT 102 map = new HashMap<>(1); 103 map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_GRAY); 104 styles[1] = Collections.unmodifiableMap(map); 105 106 // UNSELECTED_CONVERTED_TEXT_HIGHLIGHT 107 map = new HashMap<>(1); 108 map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_DOTTED); 109 styles[2] = Collections.unmodifiableMap(map); 110 111 // SELECTED_CONVERTED_TEXT_HIGHLIGHT 112 map = new HashMap<>(4); 113 Color navyBlue = new Color(0, 0, 128); 114 map.put(TextAttribute.FOREGROUND, navyBlue); 115 map.put(TextAttribute.BACKGROUND, Color.white); 116 map.put(TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON); 117 map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL); 118 styles[3] = Collections.unmodifiableMap(map); 119 120 highlightStyles = styles; 121 } 122 123 public WInputMethod() 124 { 125 context = createNativeContext(); 126 cmode = getConversionStatus(context); 127 open = getOpenStatus(context); 128 currentLocale = getNativeLocale(); 129 if (currentLocale == null) { 130 currentLocale = Locale.getDefault(); 131 } 132 } 133 134 @Override 135 @SuppressWarnings("deprecation") 136 protected void finalize() throws Throwable 137 { 138 // Release the resources used by the native input context. 139 if (context!=0) { 140 destroyNativeContext(context); 141 context=0; 142 } 143 super.finalize(); 144 } 145 146 @Override 147 public synchronized void setInputMethodContext(InputMethodContext context) { 148 inputContext = context; 149 } 150 151 @Override 152 public void dispose() { 153 // Due to a memory management problem in Windows 98, we should retain 154 // the native input context until this object is finalized. So do 155 // nothing here. 156 } 157 158 /** 159 * Returns null. 160 * 161 * @see java.awt.im.spi.InputMethod#getControlObject 162 */ 163 @Override 164 public Object getControlObject() { 165 return null; 166 } 167 168 @Override 169 public boolean setLocale(Locale lang) { 170 return setLocale(lang, false); 171 } 172 173 private boolean setLocale(Locale lang, boolean onActivate) { 174 Locale[] available = WInputMethodDescriptor.getAvailableLocalesInternal(); 175 for (int i = 0; i < available.length; i++) { 176 Locale locale = available[i]; 177 if (lang.equals(locale) || 178 // special compatibility rule for Japanese and Korean 179 locale.equals(Locale.JAPAN) && lang.equals(Locale.JAPANESE) || 180 locale.equals(Locale.KOREA) && lang.equals(Locale.KOREAN)) { 181 if (isActive) { 182 setNativeLocale(locale.toLanguageTag(), onActivate); 183 } 184 currentLocale = locale; 185 return true; 186 } 187 } 188 return false; 189 } 190 191 @Override 192 public Locale getLocale() { 193 if (isActive) { 194 currentLocale = getNativeLocale(); 195 if (currentLocale == null) { 196 currentLocale = Locale.getDefault(); 197 } 198 } 199 return currentLocale; 200 } 201 202 /** 203 * Implements InputMethod.setCharacterSubsets for Windows. 204 * 205 * @see java.awt.im.spi.InputMethod#setCharacterSubsets 206 */ 207 @Override 208 public void setCharacterSubsets(Subset[] subsets) { 209 if (subsets == null){ 210 setConversionStatus(context, cmode); 211 setOpenStatus(context, open); 212 return; 213 } 214 215 // Use first subset only. Other subsets in array is ignored. 216 // This is restriction of Win32 implementation. 217 Subset subset1 = subsets[0]; 218 219 Locale locale = getNativeLocale(); 220 int newmode; 221 222 if (locale == null) { 223 return; 224 } 225 226 if (locale.getLanguage().equals(Locale.JAPANESE.getLanguage())) { 227 if (subset1 == UnicodeBlock.BASIC_LATIN || subset1 == InputSubset.LATIN_DIGITS) { 228 setOpenStatus(context, false); 229 } else { 230 if (subset1 == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS 231 || subset1 == InputSubset.KANJI 232 || subset1 == UnicodeBlock.HIRAGANA) 233 newmode = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE; 234 else if (subset1 == UnicodeBlock.KATAKANA) 235 newmode = IME_CMODE_NATIVE | IME_CMODE_KATAKANA| IME_CMODE_FULLSHAPE; 236 else if (subset1 == InputSubset.HALFWIDTH_KATAKANA) 237 newmode = IME_CMODE_NATIVE | IME_CMODE_KATAKANA; 238 else if (subset1 == InputSubset.FULLWIDTH_LATIN) 239 newmode = IME_CMODE_FULLSHAPE; 240 else 241 return; 242 setOpenStatus(context, true); 243 newmode |= (getConversionStatus(context)&IME_CMODE_ROMAN); // reserve ROMAN input mode 244 setConversionStatus(context, newmode); 245 } 246 } else if (locale.getLanguage().equals(Locale.KOREAN.getLanguage())) { 247 if (subset1 == UnicodeBlock.BASIC_LATIN || subset1 == InputSubset.LATIN_DIGITS) { 248 setOpenStatus(context, false); 249 } else { 250 if (subset1 == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS 251 || subset1 == InputSubset.HANJA 252 || subset1 == UnicodeBlock.HANGUL_SYLLABLES 253 || subset1 == UnicodeBlock.HANGUL_JAMO 254 || subset1 == UnicodeBlock.HANGUL_COMPATIBILITY_JAMO) 255 newmode = IME_CMODE_NATIVE; 256 else if (subset1 == InputSubset.FULLWIDTH_LATIN) 257 newmode = IME_CMODE_FULLSHAPE; 258 else 259 return; 260 setOpenStatus(context, true); 261 setConversionStatus(context, newmode); 262 } 263 } else if (locale.getLanguage().equals(Locale.CHINESE.getLanguage())) { 264 if (subset1 == UnicodeBlock.BASIC_LATIN || subset1 == InputSubset.LATIN_DIGITS) { 265 setOpenStatus(context, false); 266 } else { 267 if (subset1 == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS 268 || subset1 == InputSubset.TRADITIONAL_HANZI 269 || subset1 == InputSubset.SIMPLIFIED_HANZI) 270 newmode = IME_CMODE_NATIVE; 271 else if (subset1 == InputSubset.FULLWIDTH_LATIN) 272 newmode = IME_CMODE_FULLSHAPE; 273 else 274 return; 275 setOpenStatus(context, true); 276 setConversionStatus(context, newmode); 277 } 278 } 279 } 280 281 @Override 282 public void dispatchEvent(AWTEvent e) { 283 if (e instanceof ComponentEvent) { 284 Component comp = ((ComponentEvent) e).getComponent(); 285 if (comp == awtFocussedComponent) { 286 if (awtFocussedComponentPeer == null || 287 awtFocussedComponentPeer.isDisposed()) { 288 awtFocussedComponentPeer = getNearestNativePeer(comp); 289 } 290 if (awtFocussedComponentPeer != null) { 291 handleNativeIMEEvent(awtFocussedComponentPeer, e); 292 } 293 } 294 } 295 } 296 297 @Override 298 public void activate() { 299 boolean isAc = haveActiveClient(); 300 301 // When the last focussed component peer is different from the 302 // current focussed component or if they are different client 303 // (active or passive), disable native IME for the old focussed 304 // component and enable for the new one. 305 if (lastFocussedComponentPeer != awtFocussedComponentPeer || 306 isLastFocussedActiveClient != isAc) { 307 if (lastFocussedComponentPeer != null) { 308 disableNativeIME(lastFocussedComponentPeer); 309 } 310 if (awtFocussedComponentPeer != null) { 311 enableNativeIME(awtFocussedComponentPeer, context, !isAc); 312 } 313 lastFocussedComponentPeer = awtFocussedComponentPeer; 314 isLastFocussedActiveClient = isAc; 315 } 316 isActive = true; 317 if (currentLocale != null) { 318 setLocale(currentLocale, true); 319 } 320 321 /* If the status window or Windows language bar is turned off due to 322 native input method was switched to java input method, we 323 have to turn it on otherwise it is gone for good until next time 324 the user turns it on through Windows Control Panel. See details 325 from bug 6252674. 326 */ 327 if (statusWindowHidden) { 328 setStatusWindowVisible(awtFocussedComponentPeer, true); 329 statusWindowHidden = false; 330 } 331 332 } 333 334 @Override 335 public void deactivate(boolean isTemporary) 336 { 337 // Sync currentLocale with the Windows keyboard layout which might be changed 338 // by hot key 339 getLocale(); 340 341 // Delay calling disableNativeIME until activate is called and the newly 342 // focussed component has a different peer as the last focussed component. 343 if (awtFocussedComponentPeer != null) { 344 lastFocussedComponentPeer = awtFocussedComponentPeer; 345 isLastFocussedActiveClient = haveActiveClient(); 346 } 347 isActive = false; 348 } 349 350 /** 351 * Explicitly disable the native IME. Native IME is not disabled when 352 * deactivate is called. 353 */ 354 @Override 355 public void disableInputMethod() { 356 if (lastFocussedComponentPeer != null) { 357 disableNativeIME(lastFocussedComponentPeer); 358 lastFocussedComponentPeer = null; 359 isLastFocussedActiveClient = false; 360 } 361 } 362 363 /** 364 * Returns a string with information about the windows input method, 365 * or null. 366 */ 367 @Override 368 public String getNativeInputMethodInfo() { 369 return getNativeIMMDescription(); 370 } 371 372 /** 373 * @see sun.awt.im.InputMethodAdapter#stopListening 374 * This method is called when the input method is swapped out. 375 * Calling stopListening to give other input method the keybaord input 376 * focus. 377 */ 378 @Override 379 protected void stopListening() { 380 // Since the native input method is not disabled when deactivate is 381 // called, we need to call disableInputMethod to explicitly turn off the 382 // native IME. 383 disableInputMethod(); 384 } 385 386 // implements sun.awt.im.InputMethodAdapter.setAWTFocussedComponent 387 @Override 388 protected void setAWTFocussedComponent(Component component) { 389 if (component == null) { 390 return; 391 } 392 WComponentPeer peer = getNearestNativePeer(component); 393 if (isActive) { 394 // deactivate/activate are being suppressed during a focus change - 395 // this may happen when an input method window is made visible 396 if (awtFocussedComponentPeer != null) { 397 disableNativeIME(awtFocussedComponentPeer); 398 } 399 if (peer != null) { 400 enableNativeIME(peer, context, !haveActiveClient()); 401 } 402 } 403 awtFocussedComponent = component; 404 awtFocussedComponentPeer = peer; 405 } 406 407 // implements java.awt.im.spi.InputMethod.hideWindows 408 @Override 409 public void hideWindows() { 410 if (awtFocussedComponentPeer != null) { 411 /* Hide the native status window including the Windows language 412 bar if it is on. One typical senario this method 413 gets called is when the native input method is 414 switched to java input method, for example. 415 */ 416 setStatusWindowVisible(awtFocussedComponentPeer, false); 417 statusWindowHidden = true; 418 } 419 } 420 421 /** 422 * @see java.awt.im.spi.InputMethod#removeNotify 423 */ 424 @Override 425 public void removeNotify() { 426 endCompositionNative(context, DISCARD_INPUT); 427 awtFocussedComponent = null; 428 awtFocussedComponentPeer = null; 429 } 430 431 /** 432 * @see java.awt.Toolkit#mapInputMethodHighlight 433 */ 434 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 // see sun.awt.im.InputMethodAdapter.supportsBelowTheSpot 451 @Override 452 protected boolean supportsBelowTheSpot() { 453 return true; 454 } 455 456 @Override 457 public void endComposition() 458 { 459 //right now the native endCompositionNative() just cancel 460 //the composition string, maybe a commtting is desired 461 endCompositionNative(context, 462 (haveActiveClient() ? COMMIT_INPUT : DISCARD_INPUT)); 463 } 464 465 /** 466 * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean) 467 */ 468 @Override 469 public void setCompositionEnabled(boolean enable) { 470 setOpenStatus(context, enable); 471 } 472 473 /** 474 * @see java.awt.im.spi.InputMethod#isCompositionEnabled 475 */ 476 @Override 477 public boolean isCompositionEnabled() { 478 return getOpenStatus(context); 479 } 480 481 public void sendInputMethodEvent(int id, long when, String text, 482 int[] clauseBoundary, String[] clauseReading, 483 int[] attributeBoundary, byte[] attributeValue, 484 int commitedTextLength, int caretPos, int visiblePos) 485 { 486 487 AttributedCharacterIterator iterator = null; 488 489 if (text!=null) { 490 491 // construct AttributedString 492 AttributedString attrStr = new AttributedString(text); 493 494 // set Language Information 495 attrStr.addAttribute(Attribute.LANGUAGE, 496 Locale.getDefault(), 0, text.length()); 497 498 // set Clause and Reading Information 499 if (clauseBoundary!=null && clauseReading!=null && 500 clauseReading.length!=0 && clauseBoundary.length==clauseReading.length+1 && 501 clauseBoundary[0]==0 && clauseBoundary[clauseReading.length]<=text.length() ) 502 { 503 for (int i=0; i<clauseBoundary.length-1; i++) { 504 attrStr.addAttribute(Attribute.INPUT_METHOD_SEGMENT, 505 new Annotation(null), clauseBoundary[i], clauseBoundary[i+1]); 506 attrStr.addAttribute(Attribute.READING, 507 new Annotation(clauseReading[i]), clauseBoundary[i], clauseBoundary[i+1]); 508 } 509 } else { 510 // if (clauseBoundary != null) 511 // System.out.println("Invalid clause information!"); 512 513 attrStr.addAttribute(Attribute.INPUT_METHOD_SEGMENT, 514 new Annotation(null), 0, text.length()); 515 attrStr.addAttribute(Attribute.READING, 516 new Annotation(""), 0, text.length()); 517 } 518 519 // set Hilight Information 520 if (attributeBoundary!=null && attributeValue!=null && 521 attributeValue.length!=0 && attributeBoundary.length==attributeValue.length+1 && 522 attributeBoundary[0]==0 && attributeBoundary[attributeValue.length]==text.length() ) 523 { 524 for (int i=0; i<attributeBoundary.length-1; i++) { 525 InputMethodHighlight highlight; 526 switch (attributeValue[i]) { 527 case ATTR_TARGET_CONVERTED: 528 highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT; 529 break; 530 case ATTR_CONVERTED: 531 highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT; 532 break; 533 case ATTR_TARGET_NOTCONVERTED: 534 highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT; 535 break; 536 case ATTR_INPUT: 537 case ATTR_INPUT_ERROR: 538 default: 539 highlight = InputMethodHighlight.UNSELECTED_RAW_TEXT_HIGHLIGHT; 540 break; 541 } 542 attrStr.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, 543 highlight, 544 attributeBoundary[i], attributeBoundary[i+1]); 545 } 546 } else { 547 // if (attributeBoundary != null) 548 // System.out.println("Invalid attribute information!"); 549 550 attrStr.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, 551 InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT, 552 0, text.length()); 553 } 554 555 // get iterator 556 iterator = attrStr.getIterator(); 557 558 } 559 560 Component source = getClientComponent(); 561 if (source == null) 562 return; 563 564 InputMethodEvent event = new InputMethodEvent(source, 565 id, 566 when, 567 iterator, 568 commitedTextLength, 569 TextHitInfo.leading(caretPos), 570 TextHitInfo.leading(visiblePos)); 571 WToolkit.postEvent(WToolkit.targetToAppContext(source), event); 572 } 573 574 public void inquireCandidatePosition() 575 { 576 Component source = getClientComponent(); 577 if (source == null) { 578 return; 579 } 580 // This call should return immediately just to cause 581 // InputMethodRequests.getTextLocation be called within 582 // AWT Event thread. Otherwise, a potential deadlock 583 // could happen. 584 Runnable r = new Runnable() { 585 @Override 586 public void run() { 587 int x = 0; 588 int y = 0; 589 Component client = getClientComponent(); 590 591 if (client != null) { 592 if (!client.isShowing()) { 593 return; 594 } 595 if (haveActiveClient()) { 596 Rectangle rc = inputContext.getTextLocation(TextHitInfo.leading(0)); 597 x = rc.x; 598 y = rc.y + rc.height; 599 } else { 600 Point pt = client.getLocationOnScreen(); 601 Dimension size = client.getSize(); 602 x = pt.x; 603 y = pt.y + size.height; 604 } 605 } 606 607 openCandidateWindow(awtFocussedComponentPeer, x, y); 608 } 609 }; 610 WToolkit.postEvent(WToolkit.targetToAppContext(source), 611 new InvocationEvent(source, r)); 612 } 613 614 // java.awt.Toolkit#getNativeContainer() is not available 615 // from this package 616 private WComponentPeer getNearestNativePeer(Component comp) 617 { 618 if (comp==null) return null; 619 final ComponentAccessor acc = AWTAccessor.getComponentAccessor(); 620 ComponentPeer peer = acc.getPeer(comp); 621 if (peer==null) return null; 622 623 while (peer instanceof java.awt.peer.LightweightPeer) { 624 comp = comp.getParent(); 625 if (comp==null) return null; 626 peer = acc.getPeer(comp); 627 if (peer==null) return null; 628 } 629 630 if (peer instanceof WComponentPeer) 631 return (WComponentPeer)peer; 632 else 633 return null; 634 635 } 636 637 private native int createNativeContext(); 638 private native void destroyNativeContext(int context); 639 private native void enableNativeIME(WComponentPeer peer, int context, boolean useNativeCompWindow); 640 private native void disableNativeIME(WComponentPeer peer); 641 private native void handleNativeIMEEvent(WComponentPeer peer, AWTEvent e); 642 private native void endCompositionNative(int context, boolean flag); 643 private native void setConversionStatus(int context, int cmode); 644 private native int getConversionStatus(int context); 645 private native void setOpenStatus(int context, boolean flag); 646 private native boolean getOpenStatus(int context); 647 private native void setStatusWindowVisible(WComponentPeer peer, boolean visible); 648 private native String getNativeIMMDescription(); 649 static native Locale getNativeLocale(); 650 static native boolean setNativeLocale(String localeName, boolean onActivate); 651 private native void openCandidateWindow(WComponentPeer peer, int x, int y); 652 }