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