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