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 @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 private LWComponentPeer<?, ?> getNearestNativePeer(Component comp) { 389 if (comp==null) 390 return null; 391 392 ComponentPeer peer = comp.getPeer(); 393 if (peer==null) 394 return null; 395 396 while (peer instanceof java.awt.peer.LightweightPeer) { 397 comp = comp.getParent(); 398 if (comp==null) 399 return null; 400 peer = comp.getPeer(); 401 if (peer==null) 402 return null; 403 } 404 405 if (peer instanceof LWComponentPeer) 406 return (LWComponentPeer)peer; 407 408 return null; 409 } 410 411 // =========================== NSTextInput callbacks =========================== 412 // The 'marked text' that we get from Cocoa. We need to track this separately, since 413 // Java doesn't let us ask the IM context for it. 414 private AttributedString fCurrentText = null; 415 private String fCurrentTextAsString = null; 416 private int fCurrentTextLength = 0; 417 418 /** 419 * Tell the component to commit all of the characters in the string to the current 420 * text view. This effectively wipes out any text in progress. 421 */ 422 synchronized private void insertText(String aString) { 423 AttributedString attribString = new AttributedString(aString); 424 425 // Set locale information on the new string. 426 attribString.addAttribute(Attribute.LANGUAGE, getLocale(), 0, aString.length()); 427 428 TextHitInfo theCaret = TextHitInfo.afterOffset(aString.length() - 1); 429 InputMethodEvent event = new InputMethodEvent(fAwtFocussedComponent, 430 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 431 attribString.getIterator(), 432 aString.length(), 433 theCaret, 434 theCaret); 435 LWCToolkit.postEvent(LWCToolkit.targetToAppContext(fAwtFocussedComponent), event); 436 fCurrentText = null; 437 fCurrentTextAsString = null; 438 fCurrentTextLength = 0; 439 } 440 441 private void startIMUpdate (String rawText) { 442 fCurrentTextAsString = new String(rawText); 443 fCurrentText = new AttributedString(fCurrentTextAsString); 444 fCurrentTextLength = rawText.length(); 445 } 446 447 static private final int kCaretPosition = 0; 448 static private final int kRawText = 1; 449 static private final int kSelectedRawText = 2; 450 static private final int kConvertedText = 3; 451 static private final int kSelectedConvertedText = 4; 452 453 /** 454 * Convert Cocoa text highlight attributes into Java input method highlighting. 455 */ 456 private void addAttribute (boolean isThickUnderline, boolean isGray, int start, int length) { 457 int begin = start; 458 int end = start + length; 459 int markupType = kRawText; 460 461 if (isThickUnderline && isGray) { 462 markupType = kRawText; 463 } else if (!isThickUnderline && isGray) { 464 markupType = kRawText; 465 } else if (isThickUnderline && !isGray) { 466 markupType = kSelectedConvertedText; 467 } else if (!isThickUnderline && !isGray) { 468 markupType = kConvertedText; 469 } 470 471 InputMethodHighlight theHighlight; 472 473 switch (markupType) { 474 case kSelectedRawText: 475 theHighlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT; 476 break; 477 case kConvertedText: 478 theHighlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT; 479 break; 480 case kSelectedConvertedText: 481 theHighlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT; 482 break; 483 case kRawText: 484 default: 485 theHighlight = InputMethodHighlight.UNSELECTED_RAW_TEXT_HIGHLIGHT; 486 break; 487 } 488 489 fCurrentText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, theHighlight, begin, end); 490 } 491 492 /* Called from JNI to select the previously typed glyph during press and hold */ 493 private void selectPreviousGlyph() { 494 if (fIMContext == null) return; // ??? 495 try { 496 LWCToolkit.invokeLater(new Runnable() { 497 public void run() { 498 final int offset = fIMContext.getInsertPositionOffset(); 499 if (offset < 1) return; // ??? 500 501 if (fAwtFocussedComponent instanceof JTextComponent) { 502 ((JTextComponent) fAwtFocussedComponent).select(offset - 1, offset); 503 return; 504 } 505 506 if (fAwtFocussedComponent instanceof TextComponent) { 507 ((TextComponent) fAwtFocussedComponent).select(offset - 1, offset); 508 return; 509 } 510 // TODO: Ideally we want to disable press-and-hold in this case 511 } 512 }, fAwtFocussedComponent); 513 } catch (Exception e) { 514 e.printStackTrace(); 515 } 516 } 517 518 private void selectNextGlyph() { 519 if (fIMContext == null || !(fAwtFocussedComponent instanceof JTextComponent)) return; 520 try { 521 LWCToolkit.invokeLater(new Runnable() { 522 public void run() { 523 final int offset = fIMContext.getInsertPositionOffset(); 524 if (offset < 0) return; 525 ((JTextComponent) fAwtFocussedComponent).select(offset, offset + 1); 526 return; 527 } 528 }, fAwtFocussedComponent); 529 } catch (Exception e) { 530 e.printStackTrace(); 531 } 532 } 533 534 private void dispatchText(int selectStart, int selectLength, boolean pressAndHold) { 535 // Nothing to do if we have no text. 536 if (fCurrentText == null) 537 return; 538 539 TextHitInfo theCaret = (selectLength == 0 ? TextHitInfo.beforeOffset(selectStart) : null); 540 TextHitInfo visiblePosition = TextHitInfo.beforeOffset(0); 541 542 InputMethodEvent event = new InputMethodEvent(fAwtFocussedComponent, 543 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 544 fCurrentText.getIterator(), 545 0, 546 theCaret, 547 visiblePosition); 548 LWCToolkit.postEvent(LWCToolkit.targetToAppContext(fAwtFocussedComponent), event); 549 550 if (pressAndHold) selectNextGlyph(); 551 } 552 553 /** 554 * Frequent callbacks from NSTextInput. I think we're supposed to commit it here? 555 */ 556 synchronized private void unmarkText() { 557 if (fCurrentText == null) 558 return; 559 560 TextHitInfo theCaret = TextHitInfo.afterOffset(fCurrentTextLength); 561 TextHitInfo visiblePosition = theCaret; 562 InputMethodEvent event = new InputMethodEvent(fAwtFocussedComponent, 563 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 564 fCurrentText.getIterator(), 565 fCurrentTextLength, 566 theCaret, 567 visiblePosition); 568 LWCToolkit.postEvent(LWCToolkit.targetToAppContext(fAwtFocussedComponent), event); 569 fCurrentText = null; 570 fCurrentTextAsString = null; 571 fCurrentTextLength = 0; 572 } 573 574 synchronized private boolean hasMarkedText() { 575 return fCurrentText != null; 576 } 577 578 /** 579 * Cocoa assumes the marked text and committed text is all stored in the same storage, but 580 * Java does not. So, we have to see where the request is and based on that return the right 581 * substring. 582 */ 583 synchronized private String attributedSubstringFromRange(final int locationIn, final int lengthIn) { 584 final String[] retString = new String[1]; 585 586 try { 587 LWCToolkit.invokeAndWait(new Runnable() { 588 public void run() { synchronized(retString) { 589 int location = locationIn; 590 int length = lengthIn; 591 592 if ((location + length) > (fIMContext.getCommittedTextLength() + fCurrentTextLength)) { 593 length = fIMContext.getCommittedTextLength() - location; 594 } 595 596 AttributedCharacterIterator theIterator = null; 597 598 if (fCurrentText == null) { 599 theIterator = fIMContext.getCommittedText(location, location + length, null); 600 } else { 601 int insertSpot = fIMContext.getInsertPositionOffset(); 602 603 if (location < insertSpot) { 604 theIterator = fIMContext.getCommittedText(location, location + length, null); 605 } else if (location >= insertSpot && location < insertSpot + fCurrentTextLength) { 606 theIterator = fCurrentText.getIterator(null, location - insertSpot, location - insertSpot +length); 607 } else { 608 theIterator = fIMContext.getCommittedText(location - fCurrentTextLength, location - fCurrentTextLength + length, null); 609 } 610 } 611 612 // Get the characters from the iterator 613 char selectedText[] = new char[theIterator.getEndIndex() - theIterator.getBeginIndex()]; 614 char current = theIterator.first(); 615 int index = 0; 616 while (current != CharacterIterator.DONE) { 617 selectedText[index++] = current; 618 current = theIterator.next(); 619 } 620 621 retString[0] = new String(selectedText); 622 }} 623 }, fAwtFocussedComponent); 624 } catch (InvocationTargetException ite) { ite.printStackTrace(); } 625 626 synchronized(retString) { return retString[0]; } 627 } 628 629 /** 630 * Cocoa wants the range of characters that are currently selected. We have to synthesize this 631 * by getting the insert location and the length of the selected text. NB: This does NOT allow 632 * for the fact that the insert point in Swing can come AFTER the selected text, making this 633 * potentially incorrect. 634 */ 635 synchronized private int[] selectedRange() { 636 final int[] returnValue = new int[2]; 637 638 try { 639 LWCToolkit.invokeAndWait(new Runnable() { 640 public void run() { synchronized(returnValue) { 641 AttributedCharacterIterator theIterator = fIMContext.getSelectedText(null); 642 if (theIterator == null) { 643 returnValue[0] = fIMContext.getInsertPositionOffset(); 644 returnValue[1] = 0; 645 return; 646 } 647 648 int startLocation; 649 650 if (fAwtFocussedComponent instanceof JTextComponent) { 651 JTextComponent theComponent = (JTextComponent)fAwtFocussedComponent; 652 startLocation = theComponent.getSelectionStart(); 653 } else if (fAwtFocussedComponent instanceof TextComponent) { 654 TextComponent theComponent = (TextComponent)fAwtFocussedComponent; 655 startLocation = theComponent.getSelectionStart(); 656 } else { 657 // If we don't have a Swing or AWT component, we have to guess whether the selection is before or after the input spot. 658 startLocation = fIMContext.getInsertPositionOffset() - (theIterator.getEndIndex() - theIterator.getBeginIndex()); 659 660 // If the calculated spot is negative the insert spot must be at the beginning of 661 // the selection. 662 if (startLocation < 0) { 663 startLocation = fIMContext.getInsertPositionOffset() + (theIterator.getEndIndex() - theIterator.getBeginIndex()); 664 } 665 } 666 667 returnValue[0] = startLocation; 668 returnValue[1] = theIterator.getEndIndex() - theIterator.getBeginIndex(); 669 670 }} 671 }, fAwtFocussedComponent); 672 } catch (InvocationTargetException ite) { ite.printStackTrace(); } 673 674 synchronized(returnValue) { return returnValue; } 675 } 676 677 /** 678 * Cocoa wants the range of characters that are currently marked. Since Java doesn't store committed and 679 * text in progress (composed text) together, we have to synthesize it. We know where the text will be 680 * inserted, so we can return that position, and the length of the text in progress. If there is no marked text 681 * return null. 682 */ 683 synchronized private int[] markedRange() { 684 if (fCurrentText == null) 685 return null; 686 687 final int[] returnValue = new int[2]; 688 689 try { 690 LWCToolkit.invokeAndWait(new Runnable() { 691 public void run() { synchronized(returnValue) { 692 // The insert position is always after the composed text, so the range start is the 693 // insert spot less the length of the composed text. 694 returnValue[0] = fIMContext.getInsertPositionOffset(); 695 }} 696 }, fAwtFocussedComponent); 697 } catch (InvocationTargetException ite) { ite.printStackTrace(); } 698 699 returnValue[1] = fCurrentTextLength; 700 synchronized(returnValue) { return returnValue; } 701 } 702 703 /** 704 * Cocoa wants a rectangle that describes where a particular range is on screen, but only cares about the 705 * location of that rectangle. We are given the index of the character for which we want the location on 706 * screen, which will be a character in the in-progress text. By subtracting the current insert position, 707 * which is always in front of the in-progress text, we get the offset into the composed text, and we get 708 * that location from the input method context. 709 */ 710 synchronized private int[] firstRectForCharacterRange(final int absoluteTextOffset) { 711 final int[] rect = new int[4]; 712 713 try { 714 LWCToolkit.invokeAndWait(new Runnable() { 715 public void run() { synchronized(rect) { 716 int insertOffset = fIMContext.getInsertPositionOffset(); 717 int composedTextOffset = absoluteTextOffset - insertOffset; 718 if (composedTextOffset < 0) composedTextOffset = 0; 719 Rectangle r = fIMContext.getTextLocation(TextHitInfo.beforeOffset(composedTextOffset)); 720 rect[0] = r.x; 721 rect[1] = r.y; 722 rect[2] = r.width; 723 rect[3] = r.height; 724 725 // This next if-block is a hack to work around a bug in JTextComponent. getTextLocation ignores 726 // the TextHitInfo passed to it and always returns the location of the insertion point, which is 727 // at the start of the composed text. We'll do some calculation so the candidate window for Kotoeri 728 // follows the requested offset into the composed text. 729 if (composedTextOffset > 0 && (fAwtFocussedComponent instanceof JTextComponent)) { 730 Rectangle r2 = fIMContext.getTextLocation(TextHitInfo.beforeOffset(0)); 731 732 if (r.equals(r2)) { 733 // FIXME: (SAK) If the candidate text wraps over two lines, this calculation pushes the candidate 734 // window off the right edge of the component. 735 String inProgressSubstring = fCurrentTextAsString.substring(0, composedTextOffset); 736 Graphics g = fAwtFocussedComponent.getGraphics(); 737 int xOffset = g.getFontMetrics().stringWidth(inProgressSubstring); 738 rect[0] += xOffset; 739 g.dispose(); 740 } 741 } 742 }} 743 }, fAwtFocussedComponent); 744 } catch (InvocationTargetException ite) { ite.printStackTrace(); } 745 746 synchronized(rect) { return rect; } 747 } 748 749 /* This method returns the index for the character that is nearest to the point described by screenX and screenY. 750 * The coordinates are in Java screen coordinates. If no character in the composed text was hit, we return -1, indicating 751 * not found. 752 */ 753 synchronized private int characterIndexForPoint(final int screenX, final int screenY) { 754 final TextHitInfo[] offsetInfo = new TextHitInfo[1]; 755 final int[] insertPositionOffset = new int[1]; 756 757 try { 758 LWCToolkit.invokeAndWait(new Runnable() { 759 public void run() { synchronized(offsetInfo) { 760 offsetInfo[0] = fIMContext.getLocationOffset(screenX, screenY); 761 insertPositionOffset[0] = fIMContext.getInsertPositionOffset(); 762 }} 763 }, fAwtFocussedComponent); 764 } catch (InvocationTargetException ite) { ite.printStackTrace(); } 765 766 // This bit of gymnastics ensures that the returned location is within the composed text. 767 // If it falls outside that region, the input method will commit the text, which is inconsistent with native 768 // Cocoa apps (see TextEdit, for example.) Clicking to the left of or above the selected text moves the 769 // cursor to the start of the composed text, and to the right or below moves it to one character before the end. 770 if (offsetInfo[0] == null) { 771 return insertPositionOffset[0]; 772 } 773 774 int returnValue = offsetInfo[0].getCharIndex() + insertPositionOffset[0]; 775 776 if (offsetInfo[0].getCharIndex() == fCurrentTextLength) 777 returnValue --; 778 779 return returnValue; 780 } 781 782 // On Mac OS X we effectively disabled the input method when focus was lost, so 783 // this call can be ignored. 784 public void disableInputMethod() 785 { 786 // Deliberately ignored. See setAWTFocussedComponent above. 787 } 788 789 public String getNativeInputMethodInfo() 790 { 791 return nativeGetCurrentInputMethodInfo(); 792 } 793 794 795 // =========================== Native methods =========================== 796 // Note that if nativePeer isn't something that normally accepts keystrokes (i.e., a CPanel) 797 // these calls will be ignored. 798 private native void nativeNotifyPeer(long nativePeer, CInputMethod imInstance); 799 private native void nativeEndComposition(long nativePeer); 800 private native void nativeHandleEvent(LWComponentPeer<?, ?> peer, AWTEvent event); 801 802 // Returns the locale of the active input method. 803 static native Locale getNativeLocale(); 804 805 // Switches to the input method with language indicated in localeName 806 static native boolean setNativeLocale(String localeName, boolean onActivate); 807 808 // Returns information about the currently selected input method. 809 static native String nativeGetCurrentInputMethodInfo(); 810 811 // Initialize toolbox routines 812 static native void nativeInit(); 813 }