/* * @(#)CodePointInputMethod.java 1.8 10/03/23 * * Copyright (c) 2006, 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: * * -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 Oracle 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 MICROSYSTEMS, 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.8 10/03/23 */ 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; } }