1 /* 2 * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 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 protected void finalize() throws Throwable 136 { 137 // Release the resources used by the native input context. 138 if (context!=0) { 139 destroyNativeContext(context); 140 context=0; 141 } 142 super.finalize(); 143 } 144 145 @Override 146 public synchronized void setInputMethodContext(InputMethodContext context) { 147 inputContext = context; 148 } 149 150 @Override 151 public void dispose() { 152 // Due to a memory management problem in Windows 98, we should retain 153 // the native input context until this object is finalized. So do 154 // nothing here. 155 } 156 157 /** 158 * Returns null. 159 * 160 * @see java.awt.im.spi.InputMethod#getControlObject 161 */ 162 @Override 163 public Object getControlObject() { 164 return null; 165 } 166 167 @Override 168 public boolean setLocale(Locale lang) { 169 return setLocale(lang, false); 170 } 171 172 private boolean setLocale(Locale lang, boolean onActivate) { 173 Locale[] available = WInputMethodDescriptor.getAvailableLocalesInternal(); 174 for (int i = 0; i < available.length; i++) { 175 Locale locale = available[i]; 176 if (lang.equals(locale) || 177 // special compatibility rule for Japanese and Korean 178 locale.equals(Locale.JAPAN) && lang.equals(Locale.JAPANESE) || 179 locale.equals(Locale.KOREA) && lang.equals(Locale.KOREAN)) { 180 if (isActive) { 181 setNativeLocale(locale.toLanguageTag(), onActivate); 182 } 183 currentLocale = locale; 184 return true; 185 } 186 } 187 return false; 188 } 189 190 @Override 191 public Locale getLocale() { 192 if (isActive) { 193 currentLocale = getNativeLocale(); 194 if (currentLocale == null) { 195 currentLocale = Locale.getDefault(); 196 } 197 } 198 return currentLocale; 199 } 200 201 /** 202 * Implements InputMethod.setCharacterSubsets for Windows. 203 * 204 * @see java.awt.im.spi.InputMethod#setCharacterSubsets 205 */ 206 @Override 207 public void setCharacterSubsets(Subset[] subsets) { 208 if (subsets == null){ 209 setConversionStatus(context, cmode); 210 setOpenStatus(context, open); 211 return; 212 } 213 214 // Use first subset only. Other subsets in array is ignored. 215 // This is restriction of Win32 implementation. 216 Subset subset1 = subsets[0]; 217 218 Locale locale = getNativeLocale(); 219 int newmode; 220 221 if (locale == null) { 222 return; 223 } 224 225 if (locale.getLanguage().equals(Locale.JAPANESE.getLanguage())) { 226 if (subset1 == UnicodeBlock.BASIC_LATIN || subset1 == InputSubset.LATIN_DIGITS) { 227 setOpenStatus(context, false); 228 } else { 229 if (subset1 == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS 230 || subset1 == InputSubset.KANJI 231 || subset1 == UnicodeBlock.HIRAGANA) 232 newmode = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE; 233 else if (subset1 == UnicodeBlock.KATAKANA) 234 newmode = IME_CMODE_NATIVE | IME_CMODE_KATAKANA| IME_CMODE_FULLSHAPE; 235 else if (subset1 == InputSubset.HALFWIDTH_KATAKANA) 236 newmode = IME_CMODE_NATIVE | IME_CMODE_KATAKANA; 237 else if (subset1 == InputSubset.FULLWIDTH_LATIN) 238 newmode = IME_CMODE_FULLSHAPE; 239 else 240 return; 241 setOpenStatus(context, true); 242 newmode |= (getConversionStatus(context)&IME_CMODE_ROMAN); // reserve ROMAN input mode 243 setConversionStatus(context, newmode); 244 } 245 } else if (locale.getLanguage().equals(Locale.KOREAN.getLanguage())) { 246 if (subset1 == UnicodeBlock.BASIC_LATIN || subset1 == InputSubset.LATIN_DIGITS) { 247 setOpenStatus(context, false); 248 } else { 249 if (subset1 == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS 250 || subset1 == InputSubset.HANJA 251 || subset1 == UnicodeBlock.HANGUL_SYLLABLES 252 || subset1 == UnicodeBlock.HANGUL_JAMO 253 || subset1 == UnicodeBlock.HANGUL_COMPATIBILITY_JAMO) 254 newmode = IME_CMODE_NATIVE; 255 else if (subset1 == InputSubset.FULLWIDTH_LATIN) 256 newmode = IME_CMODE_FULLSHAPE; 257 else 258 return; 259 setOpenStatus(context, true); 260 setConversionStatus(context, newmode); 261 } 262 } else if (locale.getLanguage().equals(Locale.CHINESE.getLanguage())) { 263 if (subset1 == UnicodeBlock.BASIC_LATIN || subset1 == InputSubset.LATIN_DIGITS) { 264 setOpenStatus(context, false); 265 } else { 266 if (subset1 == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS 267 || subset1 == InputSubset.TRADITIONAL_HANZI 268 || subset1 == InputSubset.SIMPLIFIED_HANZI) 269 newmode = IME_CMODE_NATIVE; 270 else if (subset1 == InputSubset.FULLWIDTH_LATIN) 271 newmode = IME_CMODE_FULLSHAPE; 272 else 273 return; 274 setOpenStatus(context, true); 275 setConversionStatus(context, newmode); 276 } 277 } 278 } 279 280 @Override 281 public void dispatchEvent(AWTEvent e) { 282 if (e instanceof ComponentEvent) { 283 Component comp = ((ComponentEvent) e).getComponent(); 284 if (comp == awtFocussedComponent) { 285 if (awtFocussedComponentPeer == null || 286 awtFocussedComponentPeer.isDisposed()) { 287 awtFocussedComponentPeer = getNearestNativePeer(comp); 288 } 289 if (awtFocussedComponentPeer != null) { 290 handleNativeIMEEvent(awtFocussedComponentPeer, e); 291 } 292 } 293 } 294 } 295 296 @Override 297 public void activate() { 298 boolean isAc = haveActiveClient(); 299 300 // When the last focussed component peer is different from the 301 // current focussed component or if they are different client 302 // (active or passive), disable native IME for the old focussed 303 // component and enable for the new one. 304 if (lastFocussedComponentPeer != awtFocussedComponentPeer || 305 isLastFocussedActiveClient != isAc) { 306 if (lastFocussedComponentPeer != null) { 307 disableNativeIME(lastFocussedComponentPeer); 308 } 309 if (awtFocussedComponentPeer != null) { 310 enableNativeIME(awtFocussedComponentPeer, context, !isAc); 311 } 312 lastFocussedComponentPeer = awtFocussedComponentPeer; 313 isLastFocussedActiveClient = isAc; 314 } 315 isActive = true; 316 if (currentLocale != null) { 317 setLocale(currentLocale, true); 318 } 319 320 /* If the status window or Windows language bar is turned off due to 321 native input method was switched to java input method, we 322 have to turn it on otherwise it is gone for good until next time 323 the user turns it on through Windows Control Panel. See details 324 from bug 6252674. 325 */ 326 if (statusWindowHidden) { 327 setStatusWindowVisible(awtFocussedComponentPeer, true); 328 statusWindowHidden = false; 329 } 330 331 } 332 333 @Override 334 public void deactivate(boolean isTemporary) 335 { 336 // Sync currentLocale with the Windows keyboard layout which might be changed 337 // by hot key 338 getLocale(); 339 340 // Delay calling disableNativeIME until activate is called and the newly 341 // focussed component has a different peer as the last focussed component. 342 if (awtFocussedComponentPeer != null) { 343 lastFocussedComponentPeer = awtFocussedComponentPeer; 344 isLastFocussedActiveClient = haveActiveClient(); 345 } 346 isActive = false; 347 } 348 349 /** 350 * Explicitly disable the native IME. Native IME is not disabled when 351 * deactivate is called. 352 */ 353 @Override 354 public void disableInputMethod() { 355 if (lastFocussedComponentPeer != null) { 356 disableNativeIME(lastFocussedComponentPeer); 357 lastFocussedComponentPeer = null; 358 isLastFocussedActiveClient = false; 359 } 360 } 361 362 /** 363 * Returns a string with information about the windows input method, 364 * or null. 365 */ 366 @Override 367 public String getNativeInputMethodInfo() { 368 return getNativeIMMDescription(); 369 } 370 371 /** 372 * @see sun.awt.im.InputMethodAdapter#stopListening 373 * This method is called when the input method is swapped out. 374 * Calling stopListening to give other input method the keybaord input 375 * focus. 376 */ 377 @Override 378 protected void stopListening() { 379 // Since the native input method is not disabled when deactivate is 380 // called, we need to call disableInputMethod to explicitly turn off the 381 // native IME. 382 disableInputMethod(); 383 } 384 385 // implements sun.awt.im.InputMethodAdapter.setAWTFocussedComponent 386 @Override 387 protected void setAWTFocussedComponent(Component component) { 388 if (component == null) { 389 return; 390 } 391 WComponentPeer peer = getNearestNativePeer(component); 392 if (isActive) { 393 // deactivate/activate are being suppressed during a focus change - 394 // this may happen when an input method window is made visible 395 if (awtFocussedComponentPeer != null) { 396 disableNativeIME(awtFocussedComponentPeer); 397 } 398 if (peer != null) { 399 enableNativeIME(peer, context, !haveActiveClient()); 400 } 401 } 402 awtFocussedComponent = component; 403 awtFocussedComponentPeer = peer; 404 } 405 406 // implements java.awt.im.spi.InputMethod.hideWindows 407 @Override 408 public void hideWindows() { 409 if (awtFocussedComponentPeer != null) { 410 /* Hide the native status window including the Windows language 411 bar if it is on. One typical senario this method 412 gets called is when the native input method is 413 switched to java input method, for example. 414 */ 415 setStatusWindowVisible(awtFocussedComponentPeer, false); 416 statusWindowHidden = true; 417 } 418 } 419 420 /** 421 * @see java.awt.im.spi.InputMethod#removeNotify 422 */ 423 @Override 424 public void removeNotify() { 425 endCompositionNative(context, DISCARD_INPUT); 426 awtFocussedComponent = null; 427 awtFocussedComponentPeer = null; 428 } 429 430 /** 431 * @see java.awt.Toolkit#mapInputMethodHighlight 432 */ 433 static Map<TextAttribute,?> mapInputMethodHighlight(InputMethodHighlight highlight) { 434 int index; 435 int state = highlight.getState(); 436 if (state == InputMethodHighlight.RAW_TEXT) { 437 index = 0; 438 } else if (state == InputMethodHighlight.CONVERTED_TEXT) { 439 index = 2; 440 } else { 441 return null; 442 } 443 if (highlight.isSelected()) { 444 index += 1; 445 } 446 return highlightStyles[index]; 447 } 448 449 // see sun.awt.im.InputMethodAdapter.supportsBelowTheSpot 450 @Override 451 protected boolean supportsBelowTheSpot() { 452 return true; 453 } 454 455 @Override 456 public void endComposition() 457 { 458 //right now the native endCompositionNative() just cancel 459 //the composition string, maybe a commtting is desired 460 endCompositionNative(context, 461 (haveActiveClient() ? COMMIT_INPUT : DISCARD_INPUT)); 462 } 463 464 /** 465 * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean) 466 */ 467 @Override 468 public void setCompositionEnabled(boolean enable) { 469 setOpenStatus(context, enable); 470 } 471 472 /** 473 * @see java.awt.im.spi.InputMethod#isCompositionEnabled 474 */ 475 @Override 476 public boolean isCompositionEnabled() { 477 return getOpenStatus(context); 478 } 479 480 public void sendInputMethodEvent(int id, long when, String text, 481 int[] clauseBoundary, String[] clauseReading, 482 int[] attributeBoundary, byte[] attributeValue, 483 int commitedTextLength, int caretPos, int visiblePos) 484 { 485 486 AttributedCharacterIterator iterator = null; 487 488 if (text!=null) { 489 490 // construct AttributedString 491 AttributedString attrStr = new AttributedString(text); 492 493 // set Language Information 494 attrStr.addAttribute(Attribute.LANGUAGE, 495 Locale.getDefault(), 0, text.length()); 496 497 // set Clause and Reading Information 498 if (clauseBoundary!=null && clauseReading!=null && 499 clauseReading.length!=0 && clauseBoundary.length==clauseReading.length+1 && 500 clauseBoundary[0]==0 && clauseBoundary[clauseReading.length]==text.length() ) 501 { 502 for (int i=0; i<clauseBoundary.length-1; i++) { 503 attrStr.addAttribute(Attribute.INPUT_METHOD_SEGMENT, 504 new Annotation(null), clauseBoundary[i], clauseBoundary[i+1]); 505 attrStr.addAttribute(Attribute.READING, 506 new Annotation(clauseReading[i]), clauseBoundary[i], clauseBoundary[i+1]); 507 } 508 } else { 509 // if (clauseBoundary != null) 510 // System.out.println("Invalid clause information!"); 511 512 attrStr.addAttribute(Attribute.INPUT_METHOD_SEGMENT, 513 new Annotation(null), 0, text.length()); 514 attrStr.addAttribute(Attribute.READING, 515 new Annotation(""), 0, text.length()); 516 } 517 518 // set Hilight Information 519 if (attributeBoundary!=null && attributeValue!=null && 520 attributeValue.length!=0 && attributeBoundary.length==attributeValue.length+1 && 521 attributeBoundary[0]==0 && attributeBoundary[attributeValue.length]==text.length() ) 522 { 523 for (int i=0; i<attributeBoundary.length-1; i++) { 524 InputMethodHighlight highlight; 525 switch (attributeValue[i]) { 526 case ATTR_TARGET_CONVERTED: 527 highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT; 528 break; 529 case ATTR_CONVERTED: 530 highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT; 531 break; 532 case ATTR_TARGET_NOTCONVERTED: 533 highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT; 534 break; 535 case ATTR_INPUT: 536 case ATTR_INPUT_ERROR: 537 default: 538 highlight = InputMethodHighlight.UNSELECTED_RAW_TEXT_HIGHLIGHT; 539 break; 540 } 541 attrStr.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, 542 highlight, 543 attributeBoundary[i], attributeBoundary[i+1]); 544 } 545 } else { 546 // if (attributeBoundary != null) 547 // System.out.println("Invalid attribute information!"); 548 549 attrStr.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, 550 InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT, 551 0, text.length()); 552 } 553 554 // get iterator 555 iterator = attrStr.getIterator(); 556 557 } 558 559 Component source = getClientComponent(); 560 if (source == null) 561 return; 562 563 InputMethodEvent event = new InputMethodEvent(source, 564 id, 565 when, 566 iterator, 567 commitedTextLength, 568 TextHitInfo.leading(caretPos), 569 TextHitInfo.leading(visiblePos)); 570 WToolkit.postEvent(WToolkit.targetToAppContext(source), event); 571 } 572 573 public void inquireCandidatePosition() 574 { 575 Component source = getClientComponent(); 576 if (source == null) { 577 return; 578 } 579 // This call should return immediately just to cause 580 // InputMethodRequests.getTextLocation be called within 581 // AWT Event thread. Otherwise, a potential deadlock 582 // could happen. 583 Runnable r = new Runnable() { 584 @Override 585 public void run() { 586 int x = 0; 587 int y = 0; 588 Component client = getClientComponent(); 589 590 if (client != null) { 591 if (haveActiveClient()) { 592 Rectangle rc = inputContext.getTextLocation(TextHitInfo.leading(0)); 593 x = rc.x; 594 y = rc.y + rc.height; 595 } else { 596 Point pt = client.getLocationOnScreen(); 597 Dimension size = client.getSize(); 598 x = pt.x; 599 y = pt.y + size.height; 600 } 601 } 602 603 openCandidateWindow(awtFocussedComponentPeer, x, y); 604 } 605 }; 606 WToolkit.postEvent(WToolkit.targetToAppContext(source), 607 new InvocationEvent(source, r)); 608 } 609 610 // java.awt.Toolkit#getNativeContainer() is not available 611 // from this package 612 private WComponentPeer getNearestNativePeer(Component comp) 613 { 614 if (comp==null) return null; 615 final ComponentAccessor acc = AWTAccessor.getComponentAccessor(); 616 ComponentPeer peer = acc.getPeer(comp); 617 if (peer==null) return null; 618 619 while (peer instanceof java.awt.peer.LightweightPeer) { 620 comp = comp.getParent(); 621 if (comp==null) return null; 622 peer = acc.getPeer(comp); 623 if (peer==null) return null; 624 } 625 626 if (peer instanceof WComponentPeer) 627 return (WComponentPeer)peer; 628 else 629 return null; 630 631 } 632 633 private native int createNativeContext(); 634 private native void destroyNativeContext(int context); 635 private native void enableNativeIME(WComponentPeer peer, int context, boolean useNativeCompWindow); 636 private native void disableNativeIME(WComponentPeer peer); 637 private native void handleNativeIMEEvent(WComponentPeer peer, AWTEvent e); 638 private native void endCompositionNative(int context, boolean flag); 639 private native void setConversionStatus(int context, int cmode); 640 private native int getConversionStatus(int context); 641 private native void setOpenStatus(int context, boolean flag); 642 private native boolean getOpenStatus(int context); 643 private native void setStatusWindowVisible(WComponentPeer peer, boolean visible); 644 private native String getNativeIMMDescription(); 645 static native Locale getNativeLocale(); 646 static native boolean setNativeLocale(String localeName, boolean onActivate); 647 private native void openCandidateWindow(WComponentPeer peer, int x, int y); 648 }