--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/demo/jfc/CodePointIM/CodePointInputMethod.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,497 @@
+/*
+ * Copyright 2002-2004 Sun Microsystems, Inc. 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 Sun Microsystems 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;
+ }
+}