1 /* 2 * Copyright (c) 2011, 2018, 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 package sun.lwawt.macosx; 27 28 import java.awt.im.spi.*; 29 import java.util.*; 30 import java.awt.*; 31 import java.awt.peer.*; 32 import java.awt.event.*; 33 import java.awt.im.*; 34 import java.awt.font.*; 35 import java.lang.Character.Subset; 36 import java.lang.reflect.InvocationTargetException; 37 import java.text.AttributedCharacterIterator.Attribute; 38 import java.text.*; 39 import javax.swing.text.JTextComponent; 40 41 import sun.awt.AWTAccessor; 42 import sun.awt.im.InputMethodAdapter; 43 import sun.lwawt.*; 44 45 import static sun.awt.AWTAccessor.ComponentAccessor; 46 47 public class CInputMethod extends InputMethodAdapter { 48 private InputMethodContext fIMContext; 49 private Component fAwtFocussedComponent; 50 private LWComponentPeer<?, ?> fAwtFocussedComponentPeer; 51 private boolean isActive; 52 53 private static Map<TextAttribute, Integer>[] sHighlightStyles; 54 55 // Intitalize highlight mapping table and its mapper. 56 static { 57 @SuppressWarnings({"rawtypes", "unchecked"}) 58 Map<TextAttribute, Integer>[] styles = new Map[4]; 59 HashMap<TextAttribute, Integer> map; 60 61 // UNSELECTED_RAW_TEXT_HIGHLIGHT 62 map = new HashMap<TextAttribute, Integer>(1); map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_GRAY)63 map.put(TextAttribute.INPUT_METHOD_UNDERLINE, 64 TextAttribute.UNDERLINE_LOW_GRAY); 65 styles[0] = Collections.unmodifiableMap(map); 66 67 // SELECTED_RAW_TEXT_HIGHLIGHT 68 map = new HashMap<TextAttribute, Integer>(1); map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_GRAY)69 map.put(TextAttribute.INPUT_METHOD_UNDERLINE, 70 TextAttribute.UNDERLINE_LOW_GRAY); 71 styles[1] = Collections.unmodifiableMap(map); 72 73 // UNSELECTED_CONVERTED_TEXT_HIGHLIGHT 74 map = new HashMap<TextAttribute, Integer>(1); map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL)75 map.put(TextAttribute.INPUT_METHOD_UNDERLINE, 76 TextAttribute.UNDERLINE_LOW_ONE_PIXEL); 77 styles[2] = Collections.unmodifiableMap(map); 78 79 // SELECTED_CONVERTED_TEXT_HIGHLIGHT 80 map = new HashMap<TextAttribute, Integer>(1); map.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_TWO_PIXEL)81 map.put(TextAttribute.INPUT_METHOD_UNDERLINE, 82 TextAttribute.UNDERLINE_LOW_TWO_PIXEL); 83 styles[3] = Collections.unmodifiableMap(map); 84 85 sHighlightStyles = styles; 86 nativeInit()87 nativeInit(); 88 89 } 90 CInputMethod()91 public CInputMethod() { 92 } 93 94 95 /** 96 * Sets the input method context, which is used to dispatch input method 97 * events to the client component and to request information from 98 * the client component. 99 * <p> 100 * This method is called once immediately after instantiating this input 101 * method. 102 * 103 * @param context the input method context for this input method 104 * @exception NullPointerException if {@code context} is null 105 */ setInputMethodContext(InputMethodContext context)106 public void setInputMethodContext(InputMethodContext context) { 107 fIMContext = context; 108 } 109 110 /** 111 * Attempts to set the input locale. If the input method supports the 112 * desired locale, it changes its behavior to support input for the locale 113 * and returns true. 114 * Otherwise, it returns false and does not change its behavior. 115 * <p> 116 * This method is called 117 * <ul> 118 * <li>by {@link java.awt.im.InputContext#selectInputMethod InputContext.selectInputMethod}, 119 * <li>when switching to this input method through the user interface if the user 120 * specified a locale or if the previously selected input method's 121 * {@link java.awt.im.spi.InputMethod#getLocale getLocale} method 122 * returns a non-null value. 123 * </ul> 124 * 125 * @param lang locale to input 126 * @return whether the specified locale is supported 127 * @exception NullPointerException if {@code locale} is null 128 */ setLocale(Locale lang)129 public boolean setLocale(Locale lang) { 130 return setLocale(lang, false); 131 } 132 setLocale(Locale lang, boolean onActivate)133 private boolean setLocale(Locale lang, boolean onActivate) { 134 Object[] available = CInputMethodDescriptor.getAvailableLocalesInternal(); 135 for (int i = 0; i < available.length; i++) { 136 Locale locale = (Locale)available[i]; 137 if (lang.equals(locale) || 138 // special compatibility rule for Japanese and Korean 139 locale.equals(Locale.JAPAN) && lang.equals(Locale.JAPANESE) || 140 locale.equals(Locale.KOREA) && lang.equals(Locale.KOREAN)) { 141 if (isActive) { 142 setNativeLocale(locale.toString(), onActivate); 143 } 144 return true; 145 } 146 } 147 return false; 148 } 149 150 /** 151 * Returns the current input locale. Might return null in exceptional cases. 152 * <p> 153 * This method is called 154 * <ul> 155 * <li>by {@link java.awt.im.InputContext#getLocale InputContext.getLocale} and 156 * <li>when switching from this input method to a different one through the 157 * user interface. 158 * </ul> 159 * 160 * @return the current input locale, or null 161 */ getLocale()162 public Locale getLocale() { 163 // On Mac OS X we'll ask the currently active input method what its locale is. 164 Locale returnValue = getNativeLocale(); 165 if (returnValue == null) { 166 returnValue = Locale.getDefault(); 167 } 168 169 return returnValue; 170 } 171 172 /** 173 * Sets the subsets of the Unicode character set that this input method 174 * is allowed to input. Null may be passed in to indicate that all 175 * characters are allowed. 176 * <p> 177 * This method is called 178 * <ul> 179 * <li>immediately after instantiating this input method, 180 * <li>when switching to this input method from a different one, and 181 * <li>by {@link java.awt.im.InputContext#setCharacterSubsets InputContext.setCharacterSubsets}. 182 * </ul> 183 * 184 * @param subsets the subsets of the Unicode character set from which 185 * characters may be input 186 */ setCharacterSubsets(Subset[] subsets)187 public void setCharacterSubsets(Subset[] subsets) { 188 // -- SAK: Does mac OS X support this? 189 } 190 191 /** 192 * Composition cannot be set on Mac OS X -- the input method remembers this 193 */ setCompositionEnabled(boolean enable)194 public void setCompositionEnabled(boolean enable) { 195 throw new UnsupportedOperationException("Can't adjust composition mode on Mac OS X."); 196 } 197 isCompositionEnabled()198 public boolean isCompositionEnabled() { 199 throw new UnsupportedOperationException("Can't adjust composition mode on Mac OS X."); 200 } 201 202 /** 203 * Dispatches the event to the input method. If input method support is 204 * enabled for the focussed component, incoming events of certain types 205 * are dispatched to the current input method for this component before 206 * they are dispatched to the component's methods or event listeners. 207 * The input method decides whether it needs to handle the event. If it 208 * does, it also calls the event's {@code consume} method; this 209 * causes the event to not get dispatched to the component's event 210 * processing methods or event listeners. 211 * <p> 212 * Events are dispatched if they are instances of InputEvent or its 213 * subclasses. 214 * This includes instances of the AWT classes KeyEvent and MouseEvent. 215 * <p> 216 * This method is called by {@link java.awt.im.InputContext#dispatchEvent InputContext.dispatchEvent}. 217 * 218 * @param event the event being dispatched to the input method 219 * @exception NullPointerException if {@code event} is null 220 */ dispatchEvent(final AWTEvent event)221 public void dispatchEvent(final AWTEvent event) { 222 // No-op for Mac OS X. 223 } 224 225 226 /** 227 * Activate and deactivate are no-ops on Mac OS X. 228 * A non-US keyboard layout is an 'input method' in that it generates events the same way as 229 * a CJK input method. A component that doesn't want input method events still wants the dead-key 230 * events. 231 * 232 * 233 */ activate()234 public void activate() { 235 isActive = true; 236 } 237 deactivate(boolean isTemporary)238 public void deactivate(boolean isTemporary) { 239 isActive = false; 240 } 241 242 /** 243 * Closes or hides all windows opened by this input method instance or 244 * its class. Deactivate hides windows for us on Mac OS X. 245 */ hideWindows()246 public void hideWindows() { 247 } 248 getNativeViewPtr(LWComponentPeer<?, ?> peer)249 long getNativeViewPtr(LWComponentPeer<?, ?> peer) { 250 if (peer.getPlatformWindow() instanceof CPlatformWindow) { 251 CPlatformWindow platformWindow = (CPlatformWindow) peer.getPlatformWindow(); 252 CPlatformView platformView = platformWindow.getContentView(); 253 return platformView.getAWTView(); 254 } else { 255 return 0; 256 } 257 } 258 259 /** 260 * Notifies the input method that a client component has been 261 * removed from its containment hierarchy, or that input method 262 * support has been disabled for the component. 263 */ removeNotify()264 public void removeNotify() { 265 if (fAwtFocussedComponentPeer != null) { 266 nativeEndComposition(getNativeViewPtr(fAwtFocussedComponentPeer)); 267 } 268 269 fAwtFocussedComponentPeer = null; 270 } 271 272 /** 273 * Informs the input method adapter about the component that has the AWT 274 * focus if it's using the input context owning this adapter instance. 275 * We also take the opportunity to tell the native side that we are the input method 276 * to talk to when responding to key events. 277 */ setAWTFocussedComponent(Component component)278 protected void setAWTFocussedComponent(Component component) { 279 LWComponentPeer<?, ?> peer = null; 280 long modelPtr = 0; 281 CInputMethod imInstance = this; 282 283 // component will be null when we are told there's no focused component. 284 // When that happens we need to notify the native architecture to stop generating IMEs 285 if (component == null) { 286 peer = fAwtFocussedComponentPeer; 287 imInstance = null; 288 } else { 289 peer = getNearestNativePeer(component); 290 291 // If we have a passive client, don't pass input method events to it. 292 if (component.getInputMethodRequests() == null) { 293 imInstance = null; 294 } 295 } 296 297 if (peer != null) { 298 modelPtr = getNativeViewPtr(peer); 299 300 // modelPtr refers to the ControlModel that either got or lost focus. 301 nativeNotifyPeer(modelPtr, imInstance); 302 } 303 304 // Track the focused component and its nearest peer. 305 fAwtFocussedComponent = component; 306 fAwtFocussedComponentPeer = getNearestNativePeer(component); 307 } 308 309 /** 310 * @see java.awt.Toolkit#mapInputMethodHighlight 311 */ mapInputMethodHighlight(InputMethodHighlight highlight)312 public static Map<TextAttribute, ?> mapInputMethodHighlight(InputMethodHighlight highlight) { 313 int index; 314 int state = highlight.getState(); 315 if (state == InputMethodHighlight.RAW_TEXT) { 316 index = 0; 317 } else if (state == InputMethodHighlight.CONVERTED_TEXT) { 318 index = 2; 319 } else { 320 return null; 321 } 322 if (highlight.isSelected()) { 323 index += 1; 324 } 325 return sHighlightStyles[index]; 326 } 327 328 /** 329 * Ends any input composition that may currently be going on in this 330 * context. Depending on the platform and possibly user preferences, 331 * this may commit or delete uncommitted text. Any changes to the text 332 * are communicated to the active component using an input method event. 333 * 334 * <p> 335 * A text editing component may call this in a variety of situations, 336 * for example, when the user moves the insertion point within the text 337 * (but outside the composed text), or when the component's text is 338 * saved to a file or copied to the clipboard. 339 * <p> 340 * This method is called 341 * <ul> 342 * <li>by {@link java.awt.im.InputContext#endComposition InputContext.endComposition}, 343 * <li>by {@link java.awt.im.InputContext#dispatchEvent InputContext.dispatchEvent} 344 * when switching to a different client component 345 * <li>when switching from this input method to a different one using the 346 * user interface or 347 * {@link java.awt.im.InputContext#selectInputMethod InputContext.selectInputMethod}. 348 * </ul> 349 */ endComposition()350 public void endComposition() { 351 if (fAwtFocussedComponentPeer != null) 352 nativeEndComposition(getNativeViewPtr(fAwtFocussedComponentPeer)); 353 } 354 355 /** 356 * Disposes of the input method and releases the resources used by it. 357 * In particular, the input method should dispose windows and close files that are no 358 * longer needed. 359 * <p> 360 * This method is called by {@link java.awt.im.InputContext#dispose InputContext.dispose}. 361 * <p> 362 * The method is only called when the input method is inactive. 363 * No method of this interface is called on this instance after dispose. 364 */ dispose()365 public void dispose() { 366 fIMContext = null; 367 fAwtFocussedComponent = null; 368 fAwtFocussedComponentPeer = null; 369 } 370 371 /** 372 * Returns a control object from this input method, or null. A 373 * control object provides methods that control the behavior of the 374 * input method or obtain information from the input method. The type 375 * of the object is an input method specific class. Clients have to 376 * compare the result against known input method control object 377 * classes and cast to the appropriate class to invoke the methods 378 * provided. 379 * <p> 380 * This method is called by 381 * {@link java.awt.im.InputContext#getInputMethodControlObject InputContext.getInputMethodControlObject}. 382 * 383 * @return a control object from this input method, or null 384 */ getControlObject()385 public Object getControlObject() { 386 return null; 387 } 388 389 // java.awt.Toolkit#getNativeContainer() is not available 390 // from this package getNearestNativePeer(Component comp)391 private LWComponentPeer<?, ?> getNearestNativePeer(Component comp) { 392 if (comp==null) 393 return null; 394 final ComponentAccessor acc = AWTAccessor.getComponentAccessor(); 395 ComponentPeer peer = acc.getPeer(comp); 396 if (peer==null) 397 return null; 398 399 while (peer instanceof java.awt.peer.LightweightPeer) { 400 comp = comp.getParent(); 401 if (comp==null) 402 return null; 403 peer = acc.getPeer(comp); 404 if (peer==null) 405 return null; 406 } 407 408 if (peer instanceof LWComponentPeer) 409 return (LWComponentPeer)peer; 410 411 return null; 412 } 413 414 // =========================== NSTextInput callbacks =========================== 415 // The 'marked text' that we get from Cocoa. We need to track this separately, since 416 // Java doesn't let us ask the IM context for it. 417 private AttributedString fCurrentText = null; 418 private String fCurrentTextAsString = null; 419 private int fCurrentTextLength = 0; 420 421 /** 422 * Tell the component to commit all of the characters in the string to the current 423 * text view. This effectively wipes out any text in progress. 424 */ insertText(String aString)425 private synchronized void insertText(String aString) { 426 AttributedString attribString = new AttributedString(aString); 427 428 // Set locale information on the new string. 429 attribString.addAttribute(Attribute.LANGUAGE, getLocale(), 0, aString.length()); 430 431 TextHitInfo theCaret = TextHitInfo.afterOffset(aString.length() - 1); 432 InputMethodEvent event = new InputMethodEvent(fAwtFocussedComponent, 433 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 434 attribString.getIterator(), 435 aString.length(), 436 theCaret, 437 theCaret); 438 LWCToolkit.postEvent(LWCToolkit.targetToAppContext(fAwtFocussedComponent), event); 439 fCurrentText = null; 440 fCurrentTextAsString = null; 441 fCurrentTextLength = 0; 442 } 443 startIMUpdate(String rawText)444 private void startIMUpdate (String rawText) { 445 fCurrentTextAsString = new String(rawText); 446 fCurrentText = new AttributedString(fCurrentTextAsString); 447 fCurrentTextLength = rawText.length(); 448 } 449 450 private static final int kCaretPosition = 0; 451 private static final int kRawText = 1; 452 private static final int kSelectedRawText = 2; 453 private static final int kConvertedText = 3; 454 private static final int kSelectedConvertedText = 4; 455 456 /** 457 * Convert Cocoa text highlight attributes into Java input method highlighting. 458 */ addAttribute(boolean isThickUnderline, boolean isGray, int start, int length)459 private void addAttribute (boolean isThickUnderline, boolean isGray, int start, int length) { 460 int begin = start; 461 int end = start + length; 462 int markupType = kRawText; 463 464 if (isThickUnderline && isGray) { 465 markupType = kRawText; 466 } else if (!isThickUnderline && isGray) { 467 markupType = kRawText; 468 } else if (isThickUnderline && !isGray) { 469 markupType = kSelectedConvertedText; 470 } else if (!isThickUnderline && !isGray) { 471 markupType = kConvertedText; 472 } 473 474 InputMethodHighlight theHighlight; 475 476 switch (markupType) { 477 case kSelectedRawText: 478 theHighlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT; 479 break; 480 case kConvertedText: 481 theHighlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT; 482 break; 483 case kSelectedConvertedText: 484 theHighlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT; 485 break; 486 case kRawText: 487 default: 488 theHighlight = InputMethodHighlight.UNSELECTED_RAW_TEXT_HIGHLIGHT; 489 break; 490 } 491 492 fCurrentText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, theHighlight, begin, end); 493 } 494 495 /* Called from JNI to select the previously typed glyph during press and hold */ selectPreviousGlyph()496 private void selectPreviousGlyph() { 497 if (fIMContext == null) return; // ??? 498 try { 499 LWCToolkit.invokeLater(new Runnable() { 500 public void run() { 501 final int offset = fIMContext.getInsertPositionOffset(); 502 if (offset < 1) return; // ??? 503 504 if (fAwtFocussedComponent instanceof JTextComponent) { 505 ((JTextComponent) fAwtFocussedComponent).select(offset - 1, offset); 506 return; 507 } 508 509 if (fAwtFocussedComponent instanceof TextComponent) { 510 ((TextComponent) fAwtFocussedComponent).select(offset - 1, offset); 511 return; 512 } 513 // TODO: Ideally we want to disable press-and-hold in this case 514 } 515 }, fAwtFocussedComponent); 516 } catch (Exception e) { 517 e.printStackTrace(); 518 } 519 } 520 selectNextGlyph()521 private void selectNextGlyph() { 522 if (fIMContext == null || !(fAwtFocussedComponent instanceof JTextComponent)) return; 523 try { 524 LWCToolkit.invokeLater(new Runnable() { 525 public void run() { 526 final int offset = fIMContext.getInsertPositionOffset(); 527 if (offset < 0) return; 528 ((JTextComponent) fAwtFocussedComponent).select(offset, offset + 1); 529 return; 530 } 531 }, fAwtFocussedComponent); 532 } catch (Exception e) { 533 e.printStackTrace(); 534 } 535 } 536 dispatchText(int selectStart, int selectLength, boolean pressAndHold)537 private void dispatchText(int selectStart, int selectLength, boolean pressAndHold) { 538 // Nothing to do if we have no text. 539 if (fCurrentText == null) 540 return; 541 542 TextHitInfo theCaret = (selectLength == 0 ? TextHitInfo.beforeOffset(selectStart) : null); 543 TextHitInfo visiblePosition = TextHitInfo.beforeOffset(0); 544 545 InputMethodEvent event = new InputMethodEvent(fAwtFocussedComponent, 546 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 547 fCurrentText.getIterator(), 548 0, 549 theCaret, 550 visiblePosition); 551 LWCToolkit.postEvent(LWCToolkit.targetToAppContext(fAwtFocussedComponent), event); 552 553 if (pressAndHold) selectNextGlyph(); 554 } 555 556 /** 557 * Frequent callbacks from NSTextInput. I think we're supposed to commit it here? 558 */ unmarkText()559 private synchronized void unmarkText() { 560 if (fCurrentText == null) 561 return; 562 563 TextHitInfo theCaret = TextHitInfo.afterOffset(fCurrentTextLength); 564 TextHitInfo visiblePosition = theCaret; 565 InputMethodEvent event = new InputMethodEvent(fAwtFocussedComponent, 566 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 567 fCurrentText.getIterator(), 568 fCurrentTextLength, 569 theCaret, 570 visiblePosition); 571 LWCToolkit.postEvent(LWCToolkit.targetToAppContext(fAwtFocussedComponent), event); 572 fCurrentText = null; 573 fCurrentTextAsString = null; 574 fCurrentTextLength = 0; 575 } 576 hasMarkedText()577 private synchronized boolean hasMarkedText() { 578 return fCurrentText != null; 579 } 580 581 /** 582 * Cocoa assumes the marked text and committed text is all stored in the same storage, but 583 * Java does not. So, we have to see where the request is and based on that return the right 584 * substring. 585 */ attributedSubstringFromRange(final int locationIn, final int lengthIn)586 private synchronized String attributedSubstringFromRange(final int locationIn, final int lengthIn) { 587 final String[] retString = new String[1]; 588 589 try { 590 LWCToolkit.invokeAndWait(new Runnable() { 591 public void run() { synchronized(retString) { 592 int location = locationIn; 593 int length = lengthIn; 594 595 if ((location + length) > (fIMContext.getCommittedTextLength() + fCurrentTextLength)) { 596 length = fIMContext.getCommittedTextLength() - location; 597 } 598 599 AttributedCharacterIterator theIterator = null; 600 601 if (fCurrentText == null) { 602 theIterator = fIMContext.getCommittedText(location, location + length, null); 603 } else { 604 int insertSpot = fIMContext.getInsertPositionOffset(); 605 606 if (location < insertSpot) { 607 theIterator = fIMContext.getCommittedText(location, location + length, null); 608 } else if (location >= insertSpot && location < insertSpot + fCurrentTextLength) { 609 theIterator = fCurrentText.getIterator(null, location - insertSpot, location - insertSpot +length); 610 } else { 611 theIterator = fIMContext.getCommittedText(location - fCurrentTextLength, location - fCurrentTextLength + length, null); 612 } 613 } 614 615 // Get the characters from the iterator 616 char[] selectedText = new char[theIterator.getEndIndex() - theIterator.getBeginIndex()]; 617 char current = theIterator.first(); 618 int index = 0; 619 while (current != CharacterIterator.DONE) { 620 selectedText[index++] = current; 621 current = theIterator.next(); 622 } 623 624 retString[0] = new String(selectedText); 625 }} 626 }, fAwtFocussedComponent); 627 } catch (InvocationTargetException ite) { ite.printStackTrace(); } 628 629 synchronized(retString) { return retString[0]; } 630 } 631 632 /** 633 * Cocoa wants the range of characters that are currently selected. We have to synthesize this 634 * by getting the insert location and the length of the selected text. NB: This does NOT allow 635 * for the fact that the insert point in Swing can come AFTER the selected text, making this 636 * potentially incorrect. 637 */ selectedRange()638 private synchronized int[] selectedRange() { 639 final int[] returnValue = new int[2]; 640 641 try { 642 LWCToolkit.invokeAndWait(new Runnable() { 643 public void run() { synchronized(returnValue) { 644 AttributedCharacterIterator theIterator = fIMContext.getSelectedText(null); 645 if (theIterator == null) { 646 returnValue[0] = fIMContext.getInsertPositionOffset(); 647 returnValue[1] = 0; 648 return; 649 } 650 651 int startLocation; 652 653 if (fAwtFocussedComponent instanceof JTextComponent) { 654 JTextComponent theComponent = (JTextComponent)fAwtFocussedComponent; 655 startLocation = theComponent.getSelectionStart(); 656 } else if (fAwtFocussedComponent instanceof TextComponent) { 657 TextComponent theComponent = (TextComponent)fAwtFocussedComponent; 658 startLocation = theComponent.getSelectionStart(); 659 } else { 660 // If we don't have a Swing or AWT component, we have to guess whether the selection is before or after the input spot. 661 startLocation = fIMContext.getInsertPositionOffset() - (theIterator.getEndIndex() - theIterator.getBeginIndex()); 662 663 // If the calculated spot is negative the insert spot must be at the beginning of 664 // the selection. 665 if (startLocation < 0) { 666 startLocation = fIMContext.getInsertPositionOffset() + (theIterator.getEndIndex() - theIterator.getBeginIndex()); 667 } 668 } 669 670 returnValue[0] = startLocation; 671 returnValue[1] = theIterator.getEndIndex() - theIterator.getBeginIndex(); 672 673 }} 674 }, fAwtFocussedComponent); 675 } catch (InvocationTargetException ite) { ite.printStackTrace(); } 676 677 synchronized(returnValue) { return returnValue; } 678 } 679 680 /** 681 * Cocoa wants the range of characters that are currently marked. Since Java doesn't store committed and 682 * text in progress (composed text) together, we have to synthesize it. We know where the text will be 683 * inserted, so we can return that position, and the length of the text in progress. If there is no marked text 684 * return null. 685 */ markedRange()686 private synchronized int[] markedRange() { 687 if (fCurrentText == null) 688 return null; 689 690 final int[] returnValue = new int[2]; 691 692 try { 693 LWCToolkit.invokeAndWait(new Runnable() { 694 public void run() { synchronized(returnValue) { 695 // The insert position is always after the composed text, so the range start is the 696 // insert spot less the length of the composed text. 697 returnValue[0] = fIMContext.getInsertPositionOffset(); 698 }} 699 }, fAwtFocussedComponent); 700 } catch (InvocationTargetException ite) { ite.printStackTrace(); } 701 702 returnValue[1] = fCurrentTextLength; 703 synchronized(returnValue) { return returnValue; } 704 } 705 706 /** 707 * Cocoa wants a rectangle that describes where a particular range is on screen, but only cares about the 708 * location of that rectangle. We are given the index of the character for which we want the location on 709 * screen, which will be a character in the in-progress text. By subtracting the current insert position, 710 * which is always in front of the in-progress text, we get the offset into the composed text, and we get 711 * that location from the input method context. 712 */ firstRectForCharacterRange(final int absoluteTextOffset)713 private synchronized int[] firstRectForCharacterRange(final int absoluteTextOffset) { 714 final int[] rect = new int[4]; 715 716 try { 717 LWCToolkit.invokeAndWait(new Runnable() { 718 public void run() { synchronized(rect) { 719 int insertOffset = fIMContext.getInsertPositionOffset(); 720 int composedTextOffset = absoluteTextOffset - insertOffset; 721 if (composedTextOffset < 0) composedTextOffset = 0; 722 Rectangle r = fIMContext.getTextLocation(TextHitInfo.beforeOffset(composedTextOffset)); 723 rect[0] = r.x; 724 rect[1] = r.y; 725 rect[2] = r.width; 726 rect[3] = r.height; 727 728 // This next if-block is a hack to work around a bug in JTextComponent. getTextLocation ignores 729 // the TextHitInfo passed to it and always returns the location of the insertion point, which is 730 // at the start of the composed text. We'll do some calculation so the candidate window for Kotoeri 731 // follows the requested offset into the composed text. 732 if (composedTextOffset > 0 && (fAwtFocussedComponent instanceof JTextComponent)) { 733 Rectangle r2 = fIMContext.getTextLocation(TextHitInfo.beforeOffset(0)); 734 735 if (r.equals(r2)) { 736 // FIXME: (SAK) If the candidate text wraps over two lines, this calculation pushes the candidate 737 // window off the right edge of the component. 738 String inProgressSubstring = fCurrentTextAsString.substring(0, composedTextOffset); 739 Graphics g = fAwtFocussedComponent.getGraphics(); 740 int xOffset = g.getFontMetrics().stringWidth(inProgressSubstring); 741 rect[0] += xOffset; 742 g.dispose(); 743 } 744 } 745 }} 746 }, fAwtFocussedComponent); 747 } catch (InvocationTargetException ite) { ite.printStackTrace(); } 748 749 synchronized(rect) { return rect; } 750 } 751 752 /* This method returns the index for the character that is nearest to the point described by screenX and screenY. 753 * The coordinates are in Java screen coordinates. If no character in the composed text was hit, we return -1, indicating 754 * not found. 755 */ characterIndexForPoint(final int screenX, final int screenY)756 private synchronized int characterIndexForPoint(final int screenX, final int screenY) { 757 final TextHitInfo[] offsetInfo = new TextHitInfo[1]; 758 final int[] insertPositionOffset = new int[1]; 759 760 try { 761 LWCToolkit.invokeAndWait(new Runnable() { 762 public void run() { synchronized(offsetInfo) { 763 offsetInfo[0] = fIMContext.getLocationOffset(screenX, screenY); 764 insertPositionOffset[0] = fIMContext.getInsertPositionOffset(); 765 }} 766 }, fAwtFocussedComponent); 767 } catch (InvocationTargetException ite) { ite.printStackTrace(); } 768 769 // This bit of gymnastics ensures that the returned location is within the composed text. 770 // If it falls outside that region, the input method will commit the text, which is inconsistent with native 771 // Cocoa apps (see TextEdit, for example.) Clicking to the left of or above the selected text moves the 772 // cursor to the start of the composed text, and to the right or below moves it to one character before the end. 773 if (offsetInfo[0] == null) { 774 return insertPositionOffset[0]; 775 } 776 777 int returnValue = offsetInfo[0].getCharIndex() + insertPositionOffset[0]; 778 779 if (offsetInfo[0].getCharIndex() == fCurrentTextLength) 780 returnValue --; 781 782 return returnValue; 783 } 784 785 // On Mac OS X we effectively disabled the input method when focus was lost, so 786 // this call can be ignored. disableInputMethod()787 public void disableInputMethod() 788 { 789 // Deliberately ignored. See setAWTFocussedComponent above. 790 } 791 getNativeInputMethodInfo()792 public String getNativeInputMethodInfo() 793 { 794 return nativeGetCurrentInputMethodInfo(); 795 } 796 797 798 // =========================== Native methods =========================== 799 // Note that if nativePeer isn't something that normally accepts keystrokes (i.e., a CPanel) 800 // these calls will be ignored. nativeNotifyPeer(long nativePeer, CInputMethod imInstance)801 private native void nativeNotifyPeer(long nativePeer, CInputMethod imInstance); nativeEndComposition(long nativePeer)802 private native void nativeEndComposition(long nativePeer); nativeHandleEvent(LWComponentPeer<?, ?> peer, AWTEvent event)803 private native void nativeHandleEvent(LWComponentPeer<?, ?> peer, AWTEvent event); 804 805 // Returns the locale of the active input method. getNativeLocale()806 static native Locale getNativeLocale(); 807 808 // Switches to the input method with language indicated in localeName setNativeLocale(String localeName, boolean onActivate)809 static native boolean setNativeLocale(String localeName, boolean onActivate); 810 811 // Returns information about the currently selected input method. nativeGetCurrentInputMethodInfo()812 static native String nativeGetCurrentInputMethodInfo(); 813 814 // Initialize toolbox routines nativeInit()815 static native void nativeInit(); 816 } 817