|   
 /*
 * @(#)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;
 }
 }
 
 
 
 
 |