jdk/src/share/demo/jfc/CodePointIM/CodePointInputMethod.java
changeset 2 90ce3da70b43
child 5506 202f599c92aa
--- /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;
+    }
+}