| /* |
| * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.lwawt.macosx; |
| |
| import java.awt.im.spi.*; |
| import java.util.*; |
| import java.awt.*; |
| import java.awt.peer.*; |
| import java.awt.event.*; |
| import java.awt.im.*; |
| import java.awt.font.*; |
| import java.lang.Character.Subset; |
| import java.lang.reflect.InvocationTargetException; |
| import java.text.AttributedCharacterIterator.Attribute; |
| import java.text.*; |
| import javax.swing.text.JTextComponent; |
| |
| import sun.awt.im.InputMethodAdapter; |
| import sun.lwawt.*; |
| |
| public class CInputMethod extends InputMethodAdapter { |
| private InputMethodContext fIMContext; |
| private Component fAwtFocussedComponent; |
| private LWComponentPeer fAwtFocussedComponentPeer; |
| private boolean isActive; |
| |
| private static Map<TextAttribute, Integer>[] sHighlightStyles; |
| |
| // Intitalize highlight mapping table and its mapper. |
| static { |
| Map<TextAttribute, Integer> styles[] = new Map[4]; |
| HashMap<TextAttribute, Integer> map; |
| |
| // UNSELECTED_RAW_TEXT_HIGHLIGHT |
| map = new HashMap<TextAttribute, Integer>(1); |
| map.put(TextAttribute.INPUT_METHOD_UNDERLINE, |
| TextAttribute.UNDERLINE_LOW_GRAY); |
| styles[0] = Collections.unmodifiableMap(map); |
| |
| // SELECTED_RAW_TEXT_HIGHLIGHT |
| map = new HashMap<TextAttribute, Integer>(1); |
| map.put(TextAttribute.INPUT_METHOD_UNDERLINE, |
| TextAttribute.UNDERLINE_LOW_GRAY); |
| styles[1] = Collections.unmodifiableMap(map); |
| |
| // UNSELECTED_CONVERTED_TEXT_HIGHLIGHT |
| map = new HashMap<TextAttribute, Integer>(1); |
| map.put(TextAttribute.INPUT_METHOD_UNDERLINE, |
| TextAttribute.UNDERLINE_LOW_ONE_PIXEL); |
| styles[2] = Collections.unmodifiableMap(map); |
| |
| // SELECTED_CONVERTED_TEXT_HIGHLIGHT |
| map = new HashMap<TextAttribute, Integer>(1); |
| map.put(TextAttribute.INPUT_METHOD_UNDERLINE, |
| TextAttribute.UNDERLINE_LOW_TWO_PIXEL); |
| styles[3] = Collections.unmodifiableMap(map); |
| |
| sHighlightStyles = styles; |
| |
| nativeInit(); |
| |
| } |
| |
| public CInputMethod() { |
| } |
| |
| |
| /** |
| * Sets the input method context, which is used to dispatch input method |
| * events to the client component and to request information from |
| * the client component. |
| * <p> |
| * This method is called once immediately after instantiating this input |
| * method. |
| * |
| * @param context the input method context for this input method |
| * @exception NullPointerException if <code>context</code> is null |
| */ |
| public void setInputMethodContext(InputMethodContext context) { |
| fIMContext = context; |
| } |
| |
| /** |
| * Attempts to set the input locale. If the input method supports the |
| * desired locale, it changes its behavior to support input for the locale |
| * and returns true. |
| * Otherwise, it returns false and does not change its behavior. |
| * <p> |
| * This method is called |
| * <ul> |
| * <li>by {@link java.awt.im.InputContext#selectInputMethod InputContext.selectInputMethod}, |
| * <li>when switching to this input method through the user interface if the user |
| * specified a locale or if the previously selected input method's |
| * {@link java.awt.im.spi.InputMethod#getLocale getLocale} method |
| * returns a non-null value. |
| * </ul> |
| * |
| * @param lang locale to input |
| * @return whether the specified locale is supported |
| * @exception NullPointerException if <code>locale</code> is null |
| */ |
| public boolean setLocale(Locale lang) { |
| return setLocale(lang, false); |
| } |
| |
| private boolean setLocale(Locale lang, boolean onActivate) { |
| Object[] available = CInputMethodDescriptor.getAvailableLocalesInternal(); |
| for (int i = 0; i < available.length; i++) { |
| Locale locale = (Locale)available[i]; |
| if (lang.equals(locale) || |
| // special compatibility rule for Japanese and Korean |
| locale.equals(Locale.JAPAN) && lang.equals(Locale.JAPANESE) || |
| locale.equals(Locale.KOREA) && lang.equals(Locale.KOREAN)) { |
| if (isActive) { |
| setNativeLocale(locale.toString(), onActivate); |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the current input locale. Might return null in exceptional cases. |
| * <p> |
| * This method is called |
| * <ul> |
| * <li>by {@link java.awt.im.InputContext#getLocale InputContext.getLocale} and |
| * <li>when switching from this input method to a different one through the |
| * user interface. |
| * </ul> |
| * |
| * @return the current input locale, or null |
| */ |
| public Locale getLocale() { |
| // On Mac OS X we'll ask the currently active input method what its locale is. |
| Locale returnValue = getNativeLocale(); |
| if (returnValue == null) { |
| returnValue = Locale.getDefault(); |
| } |
| |
| return returnValue; |
| } |
| |
| /** |
| * Sets the subsets of the Unicode character set that this input method |
| * is allowed to input. Null may be passed in to indicate that all |
| * characters are allowed. |
| * <p> |
| * This method is called |
| * <ul> |
| * <li>immediately after instantiating this input method, |
| * <li>when switching to this input method from a different one, and |
| * <li>by {@link java.awt.im.InputContext#setCharacterSubsets InputContext.setCharacterSubsets}. |
| * </ul> |
| * |
| * @param subsets the subsets of the Unicode character set from which |
| * characters may be input |
| */ |
| public void setCharacterSubsets(Subset[] subsets) { |
| // -- SAK: Does mac OS X support this? |
| } |
| |
| /** |
| * Composition cannot be set on Mac OS X -- the input method remembers this |
| */ |
| public void setCompositionEnabled(boolean enable) { |
| throw new UnsupportedOperationException("Can't adjust composition mode on Mac OS X."); |
| } |
| |
| public boolean isCompositionEnabled() { |
| throw new UnsupportedOperationException("Can't adjust composition mode on Mac OS X."); |
| } |
| |
| /** |
| * Dispatches the event to the input method. If input method support is |
| * enabled for the focussed component, incoming events of certain types |
| * are dispatched to the current input method for this component before |
| * they are dispatched to the component's methods or event listeners. |
| * The input method decides whether it needs to handle the event. If it |
| * does, it also calls the event's <code>consume</code> method; this |
| * causes the event to not get dispatched to the component's event |
| * processing methods or event listeners. |
| * <p> |
| * Events are dispatched if they are instances of InputEvent or its |
| * subclasses. |
| * This includes instances of the AWT classes KeyEvent and MouseEvent. |
| * <p> |
| * This method is called by {@link java.awt.im.InputContext#dispatchEvent InputContext.dispatchEvent}. |
| * |
| * @param event the event being dispatched to the input method |
| * @exception NullPointerException if <code>event</code> is null |
| */ |
| public void dispatchEvent(final AWTEvent event) { |
| // No-op for Mac OS X. |
| } |
| |
| |
| /** |
| * Activate and deactivate are no-ops on Mac OS X. |
| * A non-US keyboard layout is an 'input method' in that it generates events the same way as |
| * a CJK input method. A component that doesn't want input method events still wants the dead-key |
| * events. |
| * |
| * |
| */ |
| public void activate() { |
| isActive = true; |
| } |
| |
| public void deactivate(boolean isTemporary) { |
| isActive = false; |
| } |
| |
| /** |
| * Closes or hides all windows opened by this input method instance or |
| * its class. Deactivate hides windows for us on Mac OS X. |
| */ |
| public void hideWindows() { |
| } |
| |
| long getNativeViewPtr(LWComponentPeer peer) { |
| if (peer.getPlatformWindow() instanceof CPlatformWindow) { |
| CPlatformWindow platformWindow = (CPlatformWindow) peer.getPlatformWindow(); |
| CPlatformView platformView = platformWindow.getContentView(); |
| return platformView.getAWTView(); |
| } else { |
| return 0; |
| } |
| } |
| |
| /** |
| * Notifies the input method that a client component has been |
| * removed from its containment hierarchy, or that input method |
| * support has been disabled for the component. |
| */ |
| public void removeNotify() { |
| if (fAwtFocussedComponentPeer != null) { |
| nativeEndComposition(getNativeViewPtr(fAwtFocussedComponentPeer)); |
| } |
| |
| fAwtFocussedComponentPeer = null; |
| } |
| |
| /** |
| * Informs the input method adapter about the component that has the AWT |
| * focus if it's using the input context owning this adapter instance. |
| * We also take the opportunity to tell the native side that we are the input method |
| * to talk to when responding to key events. |
| */ |
| protected void setAWTFocussedComponent(Component component) { |
| LWComponentPeer peer = null; |
| long modelPtr = 0; |
| CInputMethod imInstance = this; |
| |
| // component will be null when we are told there's no focused component. |
| // When that happens we need to notify the native architecture to stop generating IMEs |
| if (component == null) { |
| peer = fAwtFocussedComponentPeer; |
| imInstance = null; |
| } else { |
| peer = getNearestNativePeer(component); |
| |
| // If we have a passive client, don't pass input method events to it. |
| if (component.getInputMethodRequests() == null) { |
| imInstance = null; |
| } |
| |
| LWWindowPeer windowPeer = peer.getPlatformWindow().getPeer(); |
| if (windowPeer.isSimpleWindow()) { |
| // A simple window gains focus. Cocoa won't dispatch IME events into the simple window, but into its owner. |
| // This IM represents the focused component in the simple window. We will use the owner as IME proxy. |
| // For that, this IM is set for the owner and is dropped for the simple window. |
| Window owner = windowPeer.getTarget().getOwner(); |
| assert owner != null && owner.isActive(); |
| long ownerPtr = getNativeViewPtr((LWComponentPeer)owner.getPeer()); |
| nativeNotifyPeer(ownerPtr, this); |
| imInstance = null; |
| } |
| } |
| |
| if (peer != null) { |
| modelPtr = getNativeViewPtr(peer); |
| |
| // modelPtr refers to the ControlModel that either got or lost focus. |
| nativeNotifyPeer(modelPtr, imInstance); |
| } |
| |
| // Track the focused component and its nearest peer. |
| fAwtFocussedComponent = component; |
| fAwtFocussedComponentPeer = getNearestNativePeer(component); |
| } |
| |
| /** |
| * @see java.awt.Toolkit#mapInputMethodHighlight |
| */ |
| public static Map mapInputMethodHighlight(InputMethodHighlight highlight) { |
| int index; |
| int state = highlight.getState(); |
| if (state == InputMethodHighlight.RAW_TEXT) { |
| index = 0; |
| } else if (state == InputMethodHighlight.CONVERTED_TEXT) { |
| index = 2; |
| } else { |
| return null; |
| } |
| if (highlight.isSelected()) { |
| index += 1; |
| } |
| return sHighlightStyles[index]; |
| } |
| |
| /** |
| * Ends any input composition that may currently be going on in this |
| * context. Depending on the platform and possibly user preferences, |
| * this may commit or delete uncommitted text. Any changes to the text |
| * are communicated to the active component using an input method event. |
| * |
| * <p> |
| * A text editing component may call this in a variety of situations, |
| * for example, when the user moves the insertion point within the text |
| * (but outside the composed text), or when the component's text is |
| * saved to a file or copied to the clipboard. |
| * <p> |
| * This method is called |
| * <ul> |
| * <li>by {@link java.awt.im.InputContext#endComposition InputContext.endComposition}, |
| * <li>by {@link java.awt.im.InputContext#dispatchEvent InputContext.dispatchEvent} |
| * when switching to a different client component |
| * <li>when switching from this input method to a different one using the |
| * user interface or |
| * {@link java.awt.im.InputContext#selectInputMethod InputContext.selectInputMethod}. |
| * </ul> |
| */ |
| public void endComposition() { |
| if (fAwtFocussedComponentPeer != null) |
| nativeEndComposition(getNativeViewPtr(fAwtFocussedComponentPeer)); |
| } |
| |
| /** |
| * Disposes of the input method and releases the resources used by it. |
| * In particular, the input method should dispose windows and close files that are no |
| * longer needed. |
| * <p> |
| * This method is called by {@link java.awt.im.InputContext#dispose InputContext.dispose}. |
| * <p> |
| * The method is only called when the input method is inactive. |
| * No method of this interface is called on this instance after dispose. |
| */ |
| public void dispose() { |
| fIMContext = null; |
| fAwtFocussedComponent = null; |
| fAwtFocussedComponentPeer = null; |
| } |
| |
| /** |
| * Returns a control object from this input method, or null. A |
| * control object provides methods that control the behavior of the |
| * input method or obtain information from the input method. The type |
| * of the object is an input method specific class. Clients have to |
| * compare the result against known input method control object |
| * classes and cast to the appropriate class to invoke the methods |
| * provided. |
| * <p> |
| * This method is called by |
| * {@link java.awt.im.InputContext#getInputMethodControlObject InputContext.getInputMethodControlObject}. |
| * |
| * @return a control object from this input method, or null |
| */ |
| public Object getControlObject() { |
| return null; |
| } |
| |
| // java.awt.Toolkit#getNativeContainer() is not available |
| // from this package |
| private LWComponentPeer getNearestNativePeer(Component comp) { |
| if (comp==null) |
| return null; |
| |
| ComponentPeer peer = comp.getPeer(); |
| if (peer==null) |
| return null; |
| |
| while (peer instanceof java.awt.peer.LightweightPeer) { |
| comp = comp.getParent(); |
| if (comp==null) |
| return null; |
| peer = comp.getPeer(); |
| if (peer==null) |
| return null; |
| } |
| |
| if (peer instanceof LWComponentPeer) |
| return (LWComponentPeer)peer; |
| |
| return null; |
| } |
| |
| // =========================== NSTextInput callbacks =========================== |
| // The 'marked text' that we get from Cocoa. We need to track this separately, since |
| // Java doesn't let us ask the IM context for it. |
| private AttributedString fCurrentText = null; |
| private String fCurrentTextAsString = null; |
| private int fCurrentTextLength = 0; |
| |
| /** |
| * Tell the component to commit all of the characters in the string to the current |
| * text view. This effectively wipes out any text in progress. |
| */ |
| synchronized private void insertText(String aString) { |
| AttributedString attribString = new AttributedString(aString); |
| |
| // Set locale information on the new string. |
| attribString.addAttribute(Attribute.LANGUAGE, getLocale(), 0, aString.length()); |
| |
| TextHitInfo theCaret = TextHitInfo.afterOffset(aString.length() - 1); |
| InputMethodEvent event = new InputMethodEvent(fAwtFocussedComponent, |
| InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, |
| attribString.getIterator(), |
| aString.length(), |
| theCaret, |
| theCaret); |
| LWCToolkit.postEvent(LWCToolkit.targetToAppContext(fAwtFocussedComponent), event); |
| fCurrentText = null; |
| fCurrentTextAsString = null; |
| fCurrentTextLength = 0; |
| } |
| |
| private void startIMUpdate (String rawText) { |
| fCurrentTextAsString = new String(rawText); |
| fCurrentText = new AttributedString(fCurrentTextAsString); |
| fCurrentTextLength = rawText.length(); |
| } |
| |
| static private final int kCaretPosition = 0; |
| static private final int kRawText = 1; |
| static private final int kSelectedRawText = 2; |
| static private final int kConvertedText = 3; |
| static private final int kSelectedConvertedText = 4; |
| |
| /** |
| * Convert Cocoa text highlight attributes into Java input method highlighting. |
| */ |
| private void addAttribute (boolean isThickUnderline, boolean isGray, int start, int length) { |
| int begin = start; |
| int end = start + length; |
| int markupType = kRawText; |
| |
| if (isThickUnderline && isGray) { |
| markupType = kRawText; |
| } else if (!isThickUnderline && isGray) { |
| markupType = kRawText; |
| } else if (isThickUnderline && !isGray) { |
| markupType = kSelectedConvertedText; |
| } else if (!isThickUnderline && !isGray) { |
| markupType = kConvertedText; |
| } |
| |
| InputMethodHighlight theHighlight; |
| |
| switch (markupType) { |
| case kSelectedRawText: |
| theHighlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT; |
| break; |
| case kConvertedText: |
| theHighlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT; |
| break; |
| case kSelectedConvertedText: |
| theHighlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT; |
| break; |
| case kRawText: |
| default: |
| theHighlight = InputMethodHighlight.UNSELECTED_RAW_TEXT_HIGHLIGHT; |
| break; |
| } |
| |
| fCurrentText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, theHighlight, begin, end); |
| } |
| |
| /* Called from JNI to select the previously typed glyph during press and hold */ |
| private void selectPreviousGlyph() { |
| if (fIMContext == null) return; // ??? |
| try { |
| LWCToolkit.invokeLater(new Runnable() { |
| public void run() { |
| final int offset = fIMContext.getInsertPositionOffset(); |
| if (offset < 1) return; // ??? |
| |
| if (fAwtFocussedComponent instanceof JTextComponent) { |
| ((JTextComponent) fAwtFocussedComponent).select(offset - 1, offset); |
| return; |
| } |
| |
| if (fAwtFocussedComponent instanceof TextComponent) { |
| ((TextComponent) fAwtFocussedComponent).select(offset - 1, offset); |
| return; |
| } |
| // TODO: Ideally we want to disable press-and-hold in this case |
| } |
| }, fAwtFocussedComponent); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| private void selectNextGlyph() { |
| if (fIMContext == null || !(fAwtFocussedComponent instanceof JTextComponent)) return; |
| try { |
| LWCToolkit.invokeLater(new Runnable() { |
| public void run() { |
| final int offset = fIMContext.getInsertPositionOffset(); |
| if (offset < 0) return; |
| ((JTextComponent) fAwtFocussedComponent).select(offset, offset + 1); |
| return; |
| } |
| }, fAwtFocussedComponent); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| private void dispatchText(int selectStart, int selectLength, boolean pressAndHold) { |
| // Nothing to do if we have no text. |
| if (fCurrentText == null) |
| return; |
| |
| TextHitInfo theCaret = (selectLength == 0 ? TextHitInfo.beforeOffset(selectStart) : null); |
| TextHitInfo visiblePosition = TextHitInfo.beforeOffset(0); |
| |
| InputMethodEvent event = new InputMethodEvent(fAwtFocussedComponent, |
| InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, |
| fCurrentText.getIterator(), |
| 0, |
| theCaret, |
| visiblePosition); |
| LWCToolkit.postEvent(LWCToolkit.targetToAppContext(fAwtFocussedComponent), event); |
| |
| if (pressAndHold) selectNextGlyph(); |
| } |
| |
| /** |
| * Frequent callbacks from NSTextInput. I think we're supposed to commit it here? |
| */ |
| synchronized private void unmarkText() { |
| if (fCurrentText == null) |
| return; |
| |
| TextHitInfo theCaret = TextHitInfo.afterOffset(fCurrentTextLength); |
| TextHitInfo visiblePosition = theCaret; |
| InputMethodEvent event = new InputMethodEvent(fAwtFocussedComponent, |
| InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, |
| fCurrentText.getIterator(), |
| fCurrentTextLength, |
| theCaret, |
| visiblePosition); |
| LWCToolkit.postEvent(LWCToolkit.targetToAppContext(fAwtFocussedComponent), event); |
| fCurrentText = null; |
| fCurrentTextAsString = null; |
| fCurrentTextLength = 0; |
| } |
| |
| synchronized private boolean hasMarkedText() { |
| return fCurrentText != null; |
| } |
| |
| /** |
| * Cocoa assumes the marked text and committed text is all stored in the same storage, but |
| * Java does not. So, we have to see where the request is and based on that return the right |
| * substring. |
| */ |
| synchronized private String attributedSubstringFromRange(final int locationIn, final int lengthIn) { |
| final String[] retString = new String[] {""}; |
| |
| try { |
| if (fIMContext != null) |
| LWCToolkit.invokeAndWait(new Runnable() { |
| public void run() { synchronized(retString) { |
| int location = locationIn; |
| int length = lengthIn; |
| |
| if ((location + length) > (fIMContext.getCommittedTextLength() + fCurrentTextLength)) { |
| length = fIMContext.getCommittedTextLength() - location; |
| } |
| |
| AttributedCharacterIterator theIterator = null; |
| |
| if (fCurrentText == null) { |
| theIterator = fIMContext.getCommittedText(location, location + length, null); |
| } else { |
| int insertSpot = fIMContext.getInsertPositionOffset(); |
| |
| if (location < insertSpot) { |
| theIterator = fIMContext.getCommittedText(location, location + length, null); |
| } else if (location >= insertSpot && location < insertSpot + fCurrentTextLength) { |
| theIterator = fCurrentText.getIterator(null, location - insertSpot, location - insertSpot +length); |
| } else { |
| theIterator = fIMContext.getCommittedText(location - fCurrentTextLength, location - fCurrentTextLength + length, null); |
| } |
| } |
| |
| // Get the characters from the iterator |
| char selectedText[] = new char[theIterator.getEndIndex() - theIterator.getBeginIndex()]; |
| char current = theIterator.first(); |
| int index = 0; |
| while (current != CharacterIterator.DONE) { |
| selectedText[index++] = current; |
| current = theIterator.next(); |
| } |
| |
| retString[0] = new String(selectedText); |
| }} |
| }, fAwtFocussedComponent); |
| } catch (InvocationTargetException ite) { ite.printStackTrace(); } |
| |
| synchronized(retString) { return retString[0]; } |
| } |
| |
| /** |
| * Cocoa wants the range of characters that are currently selected. We have to synthesize this |
| * by getting the insert location and the length of the selected text. NB: This does NOT allow |
| * for the fact that the insert point in Swing can come AFTER the selected text, making this |
| * potentially incorrect. |
| */ |
| synchronized private int[] selectedRange() { |
| final int[] returnValue = new int[2]; |
| |
| try { |
| if (fIMContext != null) |
| LWCToolkit.invokeAndWait(new Runnable() { |
| public void run() { synchronized(returnValue) { |
| AttributedCharacterIterator theIterator = fIMContext.getSelectedText(null); |
| if (theIterator == null) { |
| returnValue[0] = fIMContext.getInsertPositionOffset(); |
| returnValue[1] = 0; |
| return; |
| } |
| |
| int startLocation; |
| |
| if (fAwtFocussedComponent instanceof JTextComponent) { |
| JTextComponent theComponent = (JTextComponent)fAwtFocussedComponent; |
| startLocation = theComponent.getSelectionStart(); |
| } else if (fAwtFocussedComponent instanceof TextComponent) { |
| TextComponent theComponent = (TextComponent)fAwtFocussedComponent; |
| startLocation = theComponent.getSelectionStart(); |
| } else { |
| // If we don't have a Swing or AWT component, we have to guess whether the selection is before or after the input spot. |
| startLocation = fIMContext.getInsertPositionOffset() - (theIterator.getEndIndex() - theIterator.getBeginIndex()); |
| |
| // If the calculated spot is negative the insert spot must be at the beginning of |
| // the selection. |
| if (startLocation < 0) { |
| startLocation = fIMContext.getInsertPositionOffset() + (theIterator.getEndIndex() - theIterator.getBeginIndex()); |
| } |
| } |
| |
| returnValue[0] = startLocation; |
| returnValue[1] = theIterator.getEndIndex() - theIterator.getBeginIndex(); |
| |
| }} |
| }, fAwtFocussedComponent); |
| } catch (InvocationTargetException ite) { ite.printStackTrace(); } |
| |
| synchronized(returnValue) { return returnValue; } |
| } |
| |
| /** |
| * Cocoa wants the range of characters that are currently marked. Since Java doesn't store committed and |
| * text in progress (composed text) together, we have to synthesize it. We know where the text will be |
| * inserted, so we can return that position, and the length of the text in progress. If there is no marked text |
| * return null. |
| */ |
| synchronized private int[] markedRange() { |
| if (fCurrentText == null) |
| return null; |
| |
| final int[] returnValue = new int[2]; |
| |
| try { |
| LWCToolkit.invokeAndWait(new Runnable() { |
| public void run() { synchronized(returnValue) { |
| // The insert position is always after the composed text, so the range start is the |
| // insert spot less the length of the composed text. |
| returnValue[0] = fIMContext.getInsertPositionOffset(); |
| }} |
| }, fAwtFocussedComponent); |
| } catch (InvocationTargetException ite) { ite.printStackTrace(); } |
| |
| returnValue[1] = fCurrentTextLength; |
| synchronized(returnValue) { return returnValue; } |
| } |
| |
| /** |
| * Cocoa wants a rectangle that describes where a particular range is on screen, but only cares about the |
| * location of that rectangle. We are given the index of the character for which we want the location on |
| * screen, which will be a character in the in-progress text. By subtracting the current insert position, |
| * which is always in front of the in-progress text, we get the offset into the composed text, and we get |
| * that location from the input method context. |
| */ |
| synchronized private int[] firstRectForCharacterRange(final int absoluteTextOffset) { |
| final int[] rect = new int[4]; |
| |
| try { |
| if (fIMContext != null) |
| LWCToolkit.invokeAndWait(new Runnable() { |
| public void run() { synchronized(rect) { |
| int insertOffset = fIMContext.getInsertPositionOffset(); |
| int composedTextOffset = absoluteTextOffset - insertOffset; |
| if (composedTextOffset < 0) composedTextOffset = 0; |
| Rectangle r = fIMContext.getTextLocation(TextHitInfo.beforeOffset(composedTextOffset)); |
| rect[0] = r.x; |
| rect[1] = r.y; |
| rect[2] = r.width; |
| rect[3] = r.height; |
| |
| // This next if-block is a hack to work around a bug in JTextComponent. getTextLocation ignores |
| // the TextHitInfo passed to it and always returns the location of the insertion point, which is |
| // at the start of the composed text. We'll do some calculation so the candidate window for Kotoeri |
| // follows the requested offset into the composed text. |
| if (composedTextOffset > 0 && (fAwtFocussedComponent instanceof JTextComponent)) { |
| Rectangle r2 = fIMContext.getTextLocation(TextHitInfo.beforeOffset(0)); |
| |
| if (r.equals(r2) && fCurrentTextAsString != null) { |
| // FIXME: (SAK) If the candidate text wraps over two lines, this calculation pushes the candidate |
| // window off the right edge of the component. |
| String inProgressSubstring = fCurrentTextAsString.substring(0, composedTextOffset); |
| Graphics g = fAwtFocussedComponent.getGraphics(); |
| int xOffset = g.getFontMetrics().stringWidth(inProgressSubstring); |
| rect[0] += xOffset; |
| g.dispose(); |
| } |
| } |
| }} |
| }, fAwtFocussedComponent); |
| } catch (InvocationTargetException ite) { ite.printStackTrace(); } |
| |
| synchronized(rect) { return rect; } |
| } |
| |
| /* This method returns the index for the character that is nearest to the point described by screenX and screenY. |
| * The coordinates are in Java screen coordinates. If no character in the composed text was hit, we return -1, indicating |
| * not found. |
| */ |
| synchronized private int characterIndexForPoint(final int screenX, final int screenY) { |
| final TextHitInfo[] offsetInfo = new TextHitInfo[1]; |
| final int[] insertPositionOffset = new int[1]; |
| |
| try { |
| if (fIMContext != null) |
| LWCToolkit.invokeAndWait(new Runnable() { |
| public void run() { synchronized(offsetInfo) { |
| offsetInfo[0] = fIMContext.getLocationOffset(screenX, screenY); |
| insertPositionOffset[0] = fIMContext.getInsertPositionOffset(); |
| }} |
| }, fAwtFocussedComponent); |
| } catch (InvocationTargetException ite) { ite.printStackTrace(); } |
| |
| // This bit of gymnastics ensures that the returned location is within the composed text. |
| // If it falls outside that region, the input method will commit the text, which is inconsistent with native |
| // Cocoa apps (see TextEdit, for example.) Clicking to the left of or above the selected text moves the |
| // cursor to the start of the composed text, and to the right or below moves it to one character before the end. |
| if (offsetInfo[0] == null) { |
| return insertPositionOffset[0]; |
| } |
| |
| int returnValue = offsetInfo[0].getCharIndex() + insertPositionOffset[0]; |
| |
| if (offsetInfo[0].getCharIndex() == fCurrentTextLength) |
| returnValue --; |
| |
| return returnValue; |
| } |
| |
| // On Mac OS X we effectively disabled the input method when focus was lost, so |
| // this call can be ignored. |
| public void disableInputMethod() |
| { |
| // Deliberately ignored. See setAWTFocussedComponent above. |
| } |
| |
| public String getNativeInputMethodInfo() |
| { |
| return nativeGetCurrentInputMethodInfo(); |
| } |
| |
| |
| // =========================== Native methods =========================== |
| // Note that if nativePeer isn't something that normally accepts keystrokes (i.e., a CPanel) |
| // these calls will be ignored. |
| private native void nativeNotifyPeer(long nativePeer, CInputMethod imInstance); |
| private native void nativeEndComposition(long nativePeer); |
| private native void nativeHandleEvent(LWComponentPeer peer, AWTEvent event); |
| |
| // Returns the locale of the active input method. |
| static native Locale getNativeLocale(); |
| |
| // Switches to the input method with language indicated in localeName |
| static native boolean setNativeLocale(String localeName, boolean onActivate); |
| |
| // Returns information about the currently selected input method. |
| static native String nativeGetCurrentInputMethodInfo(); |
| |
| // Initialize toolbox routines |
| static native void nativeInit(); |
| } |