/*
* Copyright (c) 1997, 2006, 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.awt.im;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.InputMethodEvent;
import java.awt.font.TextHitInfo;
import java.awt.im.InputMethodRequests;
import java.awt.im.spi.InputMethod;
import java.security.AccessController;
import java.text.AttributedCharacterIterator;
import java.text.AttributedCharacterIterator.Attribute;
import java.text.AttributedString;
import java.text.CharacterIterator;
import javax.swing.JFrame;
import sun.awt.InputMethodSupport;
import sun.security.action.GetPropertyAction;
/**
* The InputMethodContext class provides methods that input methods
* can use to communicate with their client components.
* It is a subclass of InputContext, which provides methods for use by
* components.
*
* @author JavaSoft International
*/
public class InputMethodContext
extends sun.awt.im.InputContext
implements java.awt.im.spi.InputMethodContext {
private boolean dispatchingCommittedText;
// Creation of the context's composition area handler is
// delayed until we really need a composition area.
private CompositionAreaHandler compositionAreaHandler;
private Object compositionAreaHandlerLock = new Object();
static private boolean belowTheSpotInputRequested;
private boolean inputMethodSupportsBelowTheSpot;
static {
// check whether we should use below-the-spot input
// get property from command line
String inputStyle = (String) AccessController.doPrivileged
(new GetPropertyAction("java.awt.im.style", null));
// get property from awt.properties file
if (inputStyle == null) {
inputStyle = Toolkit.getDefaultToolkit().
getProperty("java.awt.im.style", null);
}
belowTheSpotInputRequested = "below-the-spot".equals(inputStyle);
}
/**
* Constructs an InputMethodContext.
*/
public InputMethodContext() {
super();
}
void setInputMethodSupportsBelowTheSpot(boolean supported) {
inputMethodSupportsBelowTheSpot = supported;
}
boolean useBelowTheSpotInput() {
return belowTheSpotInputRequested && inputMethodSupportsBelowTheSpot;
}
private boolean haveActiveClient() {
Component client = getClientComponent();
return client != null
&& client.getInputMethodRequests() != null;
}
// implements java.awt.im.spi.InputMethodContext.dispatchInputMethodEvent
public void dispatchInputMethodEvent(int id,
AttributedCharacterIterator text, int committedCharacterCount,
TextHitInfo caret, TextHitInfo visiblePosition) {
// We need to record the client component as the source so
// that we have correct information if we later have to break up this
// event into key events.
Component source;
source = getClientComponent();
if (source != null) {
InputMethodEvent event = new InputMethodEvent(source,
id, text, committedCharacterCount, caret, visiblePosition);
if (haveActiveClient() && !useBelowTheSpotInput()) {
source.dispatchEvent(event);
} else {
getCompositionAreaHandler(true).processInputMethodEvent(event);
}
}
}
/**
* Dispatches committed text to a client component.
* Called by composition window.
*
* @param client The component that the text should get dispatched to.
* @param text The iterator providing access to the committed
* (and possible composed) text.
* @param committedCharacterCount The number of committed characters in the text.
*/
synchronized void dispatchCommittedText(Component client,
AttributedCharacterIterator text,
int committedCharacterCount) {
// note that the client is not always the current client component -
// some host input method adapters may dispatch input method events
// through the Java event queue, and we may have switched clients while
// the event was in the queue.
if (committedCharacterCount == 0
|| text.getEndIndex() <= text.getBeginIndex()) {
return;
}
long time = System.currentTimeMillis();
dispatchingCommittedText = true;
try {
InputMethodRequests req = client.getInputMethodRequests();
if (req != null) {
// active client -> send text as InputMethodEvent
int beginIndex = text.getBeginIndex();
AttributedCharacterIterator toBeCommitted =
(new AttributedString(text, beginIndex, beginIndex + committedCharacterCount)).getIterator();
InputMethodEvent inputEvent = new InputMethodEvent(
client,
InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
toBeCommitted,
committedCharacterCount,
null, null);
client.dispatchEvent(inputEvent);
} else {
// passive client -> send text as KeyEvents
char keyChar = text.first();
while (committedCharacterCount-- > 0 && keyChar != CharacterIterator.DONE) {
KeyEvent keyEvent = new KeyEvent(client, KeyEvent.KEY_TYPED,
time, 0, KeyEvent.VK_UNDEFINED, keyChar);
client.dispatchEvent(keyEvent);
keyChar = text.next();
}
}
} finally {
dispatchingCommittedText = false;
}
}
public void dispatchEvent(AWTEvent event) {
// some host input method adapters may dispatch input method events
// through the Java event queue. If the component that the event is
// intended for isn't an active client, or if we're using below-the-spot
// input, we need to dispatch this event
// to the input window. Note that that component is not necessarily the
// current client component, since we may have switched clients while
// the event was in the queue.
if (event instanceof InputMethodEvent) {
if (((Component) event.getSource()).getInputMethodRequests() == null
|| (useBelowTheSpotInput() && !dispatchingCommittedText)) {
getCompositionAreaHandler(true).processInputMethodEvent((InputMethodEvent) event);
}
} else {
// make sure we don't dispatch our own key events back to the input method
if (!dispatchingCommittedText) {
super.dispatchEvent(event);
}
}
}
/**
* Gets this context's composition area handler, creating it if necessary.
* If requested, it grabs the composition area for use by this context.
* The composition area's text is not updated.
*/
private CompositionAreaHandler getCompositionAreaHandler(boolean grab) {
synchronized(compositionAreaHandlerLock) {
if (compositionAreaHandler == null) {
compositionAreaHandler = new CompositionAreaHandler(this);
}
compositionAreaHandler.setClientComponent(getClientComponent());
if (grab) {
compositionAreaHandler.grabCompositionArea(false);
}
return compositionAreaHandler;
}
}
/**
* Grabs the composition area for use by this context.
* If doUpdate is true, updates the composition area with previously sent
* composed text.
*/
void grabCompositionArea(boolean doUpdate) {
synchronized(compositionAreaHandlerLock) {
if (compositionAreaHandler != null) {
compositionAreaHandler.grabCompositionArea(doUpdate);
} else {
// if this context hasn't seen a need for a composition area yet,
// just close it without creating the machinery
CompositionAreaHandler.closeCompositionArea();
}
}
}
/**
* Releases and closes the composition area if it is currently owned by
* this context's composition area handler.
*/
void releaseCompositionArea() {
synchronized(compositionAreaHandlerLock) {
if (compositionAreaHandler != null) {
compositionAreaHandler.releaseCompositionArea();
}
}
}
/**
* Calls CompositionAreaHandler.isCompositionAreaVisible() to see
* whether the composition area is visible or not.
* Notice that this method is always called on the AWT event dispatch
* thread.
*/
boolean isCompositionAreaVisible() {
if (compositionAreaHandler != null) {
return compositionAreaHandler.isCompositionAreaVisible();
}
return false;
}
/**
* Calls CompositionAreaHandler.setCompositionAreaVisible to
* show or hide the composition area.
* As isCompositionAreaVisible method, it is always called
* on AWT event dispatch thread.
*/
void setCompositionAreaVisible(boolean visible) {
if (compositionAreaHandler != null) {
compositionAreaHandler.setCompositionAreaVisible(visible);
}
}
/**
* Calls the current client component's implementation of getTextLocation.
*/
public Rectangle getTextLocation(TextHitInfo offset) {
return getReq().getTextLocation(offset);
}
/**
* Calls the current client component's implementation of getLocationOffset.
*/
public TextHitInfo getLocationOffset(int x, int y) {
return getReq().getLocationOffset(x, y);
}
/**
* Calls the current client component's implementation of getInsertPositionOffset.
*/
public int getInsertPositionOffset() {
return getReq().getInsertPositionOffset();
}
/**
* Calls the current client component's implementation of getCommittedText.
*/
public AttributedCharacterIterator getCommittedText(int beginIndex,
int endIndex,
Attribute[] attributes) {
return getReq().getCommittedText(beginIndex, endIndex, attributes);
}
/**
* Calls the current client component's implementation of getCommittedTextLength.
*/
public int getCommittedTextLength() {
return getReq().getCommittedTextLength();
}
/**
* Calls the current client component's implementation of cancelLatestCommittedText.
*/
public AttributedCharacterIterator cancelLatestCommittedText(Attribute[] attributes) {
return getReq().cancelLatestCommittedText(attributes);
}
/**
* Calls the current client component's implementation of getSelectedText.
*/
public AttributedCharacterIterator getSelectedText(Attribute[] attributes) {
return getReq().getSelectedText(attributes);
}
private InputMethodRequests getReq() {
if (haveActiveClient() && !useBelowTheSpotInput()) {
return getClientComponent().getInputMethodRequests();
} else {
return getCompositionAreaHandler(false);
}
}
// implements java.awt.im.spi.InputMethodContext.createInputMethodWindow
public Window createInputMethodWindow(String title, boolean attachToInputContext) {
InputContext context = attachToInputContext ? this : null;
return createInputMethodWindow(title, context, false);
}
// implements java.awt.im.spi.InputMethodContext.createInputMethodJFrame
public JFrame createInputMethodJFrame(String title, boolean attachToInputContext) {
InputContext context = attachToInputContext ? this : null;
return (JFrame)createInputMethodWindow(title, context, true);
}
static Window createInputMethodWindow(String title, InputContext context, boolean isSwing) {
if (GraphicsEnvironment.isHeadless()) {
throw new HeadlessException();
}
if (isSwing) {
return new InputMethodJFrame(title, context);
} else {
Toolkit toolkit = Toolkit.getDefaultToolkit();
if (toolkit instanceof InputMethodSupport) {
return ((InputMethodSupport)toolkit).createInputMethodWindow(
title, context);
}
}
throw new InternalError("Input methods must be supported");
}
/**
* @see java.awt.im.spi.InputMethodContext#enableClientWindowNotification
*/
public void enableClientWindowNotification(InputMethod inputMethod, boolean enable) {
super.enableClientWindowNotification(inputMethod, enable);
}
/**
* Disables or enables decorations for the composition window.
*/
void setCompositionAreaUndecorated(boolean undecorated) {
if (compositionAreaHandler != null) {
compositionAreaHandler.setCompositionAreaUndecorated(undecorated);
}
}
}