blob: ff7a5769de7f5abc1a54017d671909f3f3e7ce54 [file] [log] [blame]
/*
* Copyright (c) 2002, 2004, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
*/
package com.sun.inputmethods.internal.codepointim;
import java.text.AttributedCharacterIterator;
import java.util.Map;
import java.awt.AWTEvent;
import java.awt.Toolkit;
import java.awt.Rectangle;
import java.awt.event.InputMethodEvent;
import java.awt.event.KeyEvent;
import java.awt.font.TextAttribute;
import java.awt.font.TextHitInfo;
import java.awt.im.InputMethodHighlight;
import java.awt.im.spi.InputMethod;
import java.awt.im.spi.InputMethodContext;
import java.io.IOException;
import java.text.AttributedString;
import java.util.Locale;
/**
* The Code Point Input Method is a simple input method that allows Unicode
* characters to be entered using their code point or code unit values. See the
* accompanying file README.txt for more information.
*
* @author Brian Beck
*/
public class CodePointInputMethod implements InputMethod {
private static final int UNSET = 0;
private static final int ESCAPE = 1; // \u0000 - \uFFFF
private static final int SPECIAL_ESCAPE = 2; // \U000000 - \U10FFFF
private static final int SURROGATE_PAIR = 3; // \uD800\uDC00 - \uDBFF\uDFFF
private InputMethodContext context;
private Locale locale;
private StringBuffer buffer;
private int insertionPoint;
private int format = UNSET;
public CodePointInputMethod() throws IOException {
}
/**
* This is the input method's main routine. The composed text is stored
* in buffer.
*/
public void dispatchEvent(AWTEvent event) {
// This input method handles KeyEvent only.
if (!(event instanceof KeyEvent)) {
return;
}
KeyEvent e = (KeyEvent) event;
int eventID = event.getID();
boolean notInCompositionMode = buffer.length() == 0;
if (eventID == KeyEvent.KEY_PRESSED) {
// If we are not in composition mode, pass through
if (notInCompositionMode) {
return;
}
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
moveCaretLeft();
break;
case KeyEvent.VK_RIGHT:
moveCaretRight();
break;
}
} else if (eventID == KeyEvent.KEY_TYPED) {
char c = e.getKeyChar();
// If we are not in composition mode, wait a back slash
if (notInCompositionMode) {
// If the type character is not a back slash, pass through
if (c != '\\') {
return;
}
startComposition(); // Enter to composition mode
} else {
switch (c) {
case ' ': // Exit from composition mode
finishComposition();
break;
case '\u007f': // Delete
deleteCharacter();
break;
case '\b': // BackSpace
deletePreviousCharacter();
break;
case '\u001b': // Escape
cancelComposition();
break;
case '\n': // Return
case '\t': // Tab
sendCommittedText();
break;
default:
composeUnicodeEscape(c);
break;
}
}
} else { // KeyEvent.KEY_RELEASED
// If we are not in composition mode, pass through
if (notInCompositionMode) {
return;
}
}
e.consume();
}
private void composeUnicodeEscape(char c) {
switch (buffer.length()) {
case 1: // \\
waitEscapeCharacter(c);
break;
case 2: // \\u or \\U
case 3: // \\ux or \\Ux
case 4: // \\uxx or \\Uxx
waitDigit(c);
break;
case 5: // \\uxxx or \\Uxxx
if (format == SPECIAL_ESCAPE) {
waitDigit(c);
} else {
waitDigit2(c);
}
break;
case 6: // \\uxxxx or \\Uxxxx
if (format == SPECIAL_ESCAPE) {
waitDigit(c);
} else if (format == SURROGATE_PAIR) {
waitBackSlashOrLowSurrogate(c);
} else {
beep();
}
break;
case 7: // \\Uxxxxx
// Only SPECIAL_ESCAPE format uses this state.
// Since the second "\\u" of SURROGATE_PAIR format is inserted
// automatically, users don't have to type these keys.
waitDigit(c);
break;
case 8: // \\uxxxx\\u
case 9: // \\uxxxx\\ux
case 10: // \\uxxxx\\uxx
case 11: // \\uxxxx\\uxxx
if (format == SURROGATE_PAIR) {
waitDigit(c);
} else {
beep();
}
break;
default:
beep();
break;
}
}
private void waitEscapeCharacter(char c) {
if (c == 'u' || c == 'U') {
buffer.append(c);
insertionPoint++;
sendComposedText();
format = (c == 'u') ? ESCAPE : SPECIAL_ESCAPE;
} else {
if (c != '\\') {
buffer.append(c);
insertionPoint++;
}
sendCommittedText();
}
}
private void waitDigit(char c) {
if (Character.digit(c, 16) != -1) {
buffer.insert(insertionPoint++, c);
sendComposedText();
} else {
beep();
}
}
private void waitDigit2(char c) {
if (Character.digit(c, 16) != -1) {
buffer.insert(insertionPoint++, c);
char codePoint = (char)getCodePoint(buffer, 2, 5);
if (Character.isHighSurrogate(codePoint)) {
format = SURROGATE_PAIR;
buffer.append("\\u");
insertionPoint = 8;
} else {
format = ESCAPE;
}
sendComposedText();
} else {
beep();
}
}
private void waitBackSlashOrLowSurrogate(char c) {
if (insertionPoint == 6) {
if (c == '\\') {
buffer.append(c);
buffer.append('u');
insertionPoint = 8;
sendComposedText();
} else if (Character.digit(c, 16) != -1) {
buffer.append("\\u");
buffer.append(c);
insertionPoint = 9;
sendComposedText();
} else {
beep();
}
} else {
beep();
}
}
/**
* Send the composed text to the client.
*/
private void sendComposedText() {
AttributedString as = new AttributedString(buffer.toString());
as.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT);
context.dispatchInputMethodEvent(
InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
as.getIterator(), 0,
TextHitInfo.leading(insertionPoint), null);
}
/**
* Send the committed text to the client.
*/
private void sendCommittedText() {
AttributedString as = new AttributedString(buffer.toString());
context.dispatchInputMethodEvent(
InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
as.getIterator(), buffer.length(),
TextHitInfo.leading(insertionPoint), null);
buffer.setLength(0);
insertionPoint = 0;
format = UNSET;
}
/**
* Move the insertion point one position to the left in the composed text.
* Do not let the caret move to the left of the "\\u" or "\\U".
*/
private void moveCaretLeft() {
int len = buffer.length();
if (--insertionPoint < 2) {
insertionPoint++;
beep();
} else if (format == SURROGATE_PAIR && insertionPoint == 7) {
insertionPoint = 8;
beep();
}
context.dispatchInputMethodEvent(
InputMethodEvent.CARET_POSITION_CHANGED,
null, 0,
TextHitInfo.leading(insertionPoint), null);
}
/**
* Move the insertion point one position to the right in the composed text.
*/
private void moveCaretRight() {
int len = buffer.length();
if (++insertionPoint > len) {
insertionPoint = len;
beep();
}
context.dispatchInputMethodEvent(
InputMethodEvent.CARET_POSITION_CHANGED,
null, 0,
TextHitInfo.leading(insertionPoint), null);
}
/**
* Delete the character preceding the insertion point in the composed text.
* If the insertion point is not at the end of the composed text and the
* preceding text is "\\u" or "\\U", ring the bell.
*/
private void deletePreviousCharacter() {
if (insertionPoint == 2) {
if (buffer.length() == 2) {
cancelComposition();
} else {
// Do not allow deletion of the leading "\\u" or "\\U" if there
// are other digits in the composed text.
beep();
}
} else if (insertionPoint == 8) {
if (buffer.length() == 8) {
if (format == SURROGATE_PAIR) {
buffer.deleteCharAt(--insertionPoint);
}
buffer.deleteCharAt(--insertionPoint);
sendComposedText();
} else {
// Do not allow deletion of the second "\\u" if there are other
// digits in the composed text.
beep();
}
} else {
buffer.deleteCharAt(--insertionPoint);
if (buffer.length() == 0) {
sendCommittedText();
} else {
sendComposedText();
}
}
}
/**
* Delete the character following the insertion point in the composed text.
* If the insertion point is at the end of the composed text, ring the bell.
*/
private void deleteCharacter() {
if (insertionPoint < buffer.length()) {
buffer.deleteCharAt(insertionPoint);
sendComposedText();
} else {
beep();
}
}
private void startComposition() {
buffer.append('\\');
insertionPoint = 1;
sendComposedText();
}
private void cancelComposition() {
buffer.setLength(0);
insertionPoint = 0;
sendCommittedText();
}
private void finishComposition() {
int len = buffer.length();
if (len == 6 && format != SPECIAL_ESCAPE) {
char codePoint = (char)getCodePoint(buffer, 2, 5);
if (Character.isValidCodePoint(codePoint) && codePoint != 0xFFFF) {
buffer.setLength(0);
buffer.append(codePoint);
sendCommittedText();
return;
}
} else if (len == 8 && format == SPECIAL_ESCAPE) {
int codePoint = getCodePoint(buffer, 2, 7);
if (Character.isValidCodePoint(codePoint) && codePoint != 0xFFFF) {
buffer.setLength(0);
buffer.appendCodePoint(codePoint);
sendCommittedText();
return;
}
} else if (len == 12 && format == SURROGATE_PAIR) {
char[] codePoint = {
(char)getCodePoint(buffer, 2, 5),
(char)getCodePoint(buffer, 8, 11)
};
if (Character.isHighSurrogate(codePoint[0]) &&
Character.isLowSurrogate(codePoint[1])) {
buffer.setLength(0);
buffer.append(codePoint);
sendCommittedText();
return;
}
}
beep();
}
private int getCodePoint(StringBuffer sb, int from, int to) {
int value = 0;
for (int i = from; i <= to; i++) {
value = (value<<4) + Character.digit(sb.charAt(i), 16);
}
return value;
}
private static void beep() {
Toolkit.getDefaultToolkit().beep();
}
public void activate() {
if (buffer == null) {
buffer = new StringBuffer(12);
insertionPoint = 0;
}
}
public void deactivate(boolean isTemporary) {
if (!isTemporary) {
buffer = null;
}
}
public void dispose() {
}
public Object getControlObject() {
return null;
}
public void endComposition() {
sendCommittedText();
}
public Locale getLocale() {
return locale;
}
public void hideWindows() {
}
public boolean isCompositionEnabled() {
// always enabled
return true;
}
public void notifyClientWindowChange(Rectangle location) {
}
public void reconvert() {
// not supported yet
throw new UnsupportedOperationException();
}
public void removeNotify() {
}
public void setCharacterSubsets(Character.Subset[] subsets) {
}
public void setCompositionEnabled(boolean enable) {
// not supported yet
throw new UnsupportedOperationException();
}
public void setInputMethodContext(InputMethodContext context) {
this.context = context;
}
/*
* The Code Point Input Method supports all locales.
*/
public boolean setLocale(Locale locale) {
this.locale = locale;
return true;
}
}