CodePointInputMethod.java Source code

Java tutorial

Introduction

Here is the source code for CodePointInputMethod.java

Source

/*
 * @(#)CodePointInputMethod.java  1.6 05/11/17
 * 
 * Copyright (c) 2006 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:
 * 
 * -Redistribution of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 * 
 * -Redistribution 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, Inc. or the names of contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL 
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST 
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, 
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY 
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, 
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * You acknowledge that this software is not designed, licensed or intended
 * for use in the design, construction, operation or maintenance of any
 * nuclear facility.
 */

/*
 * @(#)CodePointInputMethod.java  1.6 05/11/17
 */

//package com.sun.inputmethods.internal.codepointim;
import java.awt.AWTEvent;
import java.awt.Rectangle;
import java.awt.Toolkit;
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;
    }
}