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