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