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