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