1 /* 2 * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * - Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 11 * - Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * - Neither the name of Oracle nor the names of its 16 * contributors may be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * This source code is provided to illustrate the usage of a given feature 34 * or technique and has been deliberately simplified. Additional steps 35 * required for a production-quality application, such as security checks, 36 * input validation and proper error handling, might not be present in 37 * this sample code. 38 */ 39 40 package com.sun.inputmethods.internal.codepointim; 41 42 43 import java.awt.AWTEvent; 44 import java.awt.Toolkit; 45 import java.awt.Rectangle; 46 import java.awt.event.InputMethodEvent; 47 import java.awt.event.KeyEvent; 48 import java.awt.font.TextAttribute; 49 import java.awt.font.TextHitInfo; 50 import java.awt.im.InputMethodHighlight; 51 import java.awt.im.spi.InputMethod; 52 import java.awt.im.spi.InputMethodContext; 53 import java.io.IOException; 54 import java.text.AttributedString; 55 import java.util.Locale; 56 57 58 /** 59 * The Code Point Input Method is a simple input method that allows Unicode 60 * characters to be entered using their code point or code unit values. See the 61 * accompanying file README.txt for more information. 62 * 63 * @author Brian Beck 64 */ 65 public class CodePointInputMethod implements InputMethod { 66 67 private static final int UNSET = 0; 68 private static final int ESCAPE = 1; // \u0000 - \uFFFF 69 private static final int SPECIAL_ESCAPE = 2; // \U000000 - \U10FFFF 70 private static final int SURROGATE_PAIR = 3; // \uD800\uDC00 - \uDBFF\uDFFF 71 private InputMethodContext context; 72 private Locale locale; 73 private StringBuffer buffer; 74 private int insertionPoint; 75 private int format = UNSET; 76 CodePointInputMethod()77 public CodePointInputMethod() throws IOException { 78 } 79 80 /** 81 * This is the input method's main routine. The composed text is stored 82 * in buffer. 83 */ dispatchEvent(AWTEvent event)84 public void dispatchEvent(AWTEvent event) { 85 // This input method handles KeyEvent only. 86 if (!(event instanceof KeyEvent)) { 87 return; 88 } 89 90 KeyEvent e = (KeyEvent) event; 91 int eventID = event.getID(); 92 boolean notInCompositionMode = buffer.length() == 0; 93 94 if (eventID == KeyEvent.KEY_PRESSED) { 95 // If we are not in composition mode, pass through 96 if (notInCompositionMode) { 97 return; 98 } 99 100 switch (e.getKeyCode()) { 101 case KeyEvent.VK_LEFT: 102 moveCaretLeft(); 103 break; 104 case KeyEvent.VK_RIGHT: 105 moveCaretRight(); 106 break; 107 } 108 } else if (eventID == KeyEvent.KEY_TYPED) { 109 char c = e.getKeyChar(); 110 111 // If we are not in composition mode, wait a back slash 112 if (notInCompositionMode) { 113 // If the type character is not a back slash, pass through 114 if (c != '\\') { 115 return; 116 } 117 118 startComposition(); // Enter to composition mode 119 } else { 120 switch (c) { 121 case ' ': // Exit from composition mode 122 finishComposition(); 123 break; 124 case '\u007f': // Delete 125 deleteCharacter(); 126 break; 127 case '\b': // BackSpace 128 deletePreviousCharacter(); 129 break; 130 case '\u001b': // Escape 131 cancelComposition(); 132 break; 133 case '\n': // Return 134 case '\t': // Tab 135 sendCommittedText(); 136 break; 137 default: 138 composeUnicodeEscape(c); 139 break; 140 } 141 } 142 } else { // KeyEvent.KEY_RELEASED 143 // If we are not in composition mode, pass through 144 if (notInCompositionMode) { 145 return; 146 } 147 } 148 149 e.consume(); 150 } 151 composeUnicodeEscape(char c)152 private void composeUnicodeEscape(char c) { 153 switch (buffer.length()) { 154 case 1: // \\ 155 waitEscapeCharacter(c); 156 break; 157 case 2: // \\u or \\U 158 case 3: // \\ux or \\Ux 159 case 4: // \\uxx or \\Uxx 160 waitDigit(c); 161 break; 162 case 5: // \\uxxx or \\Uxxx 163 if (format == SPECIAL_ESCAPE) { 164 waitDigit(c); 165 } else { 166 waitDigit2(c); 167 } 168 break; 169 case 6: // \\uxxxx or \\Uxxxx 170 if (format == SPECIAL_ESCAPE) { 171 waitDigit(c); 172 } else if (format == SURROGATE_PAIR) { 173 waitBackSlashOrLowSurrogate(c); 174 } else { 175 beep(); 176 } 177 break; 178 case 7: // \\Uxxxxx 179 // Only SPECIAL_ESCAPE format uses this state. 180 // Since the second "\\u" of SURROGATE_PAIR format is inserted 181 // automatically, users don't have to type these keys. 182 waitDigit(c); 183 break; 184 case 8: // \\uxxxx\\u 185 case 9: // \\uxxxx\\ux 186 case 10: // \\uxxxx\\uxx 187 case 11: // \\uxxxx\\uxxx 188 if (format == SURROGATE_PAIR) { 189 waitDigit(c); 190 } else { 191 beep(); 192 } 193 break; 194 default: 195 beep(); 196 break; 197 } 198 } 199 waitEscapeCharacter(char c)200 private void waitEscapeCharacter(char c) { 201 if (c == 'u' || c == 'U') { 202 buffer.append(c); 203 insertionPoint++; 204 sendComposedText(); 205 format = (c == 'u') ? ESCAPE : SPECIAL_ESCAPE; 206 } else { 207 if (c != '\\') { 208 buffer.append(c); 209 insertionPoint++; 210 } 211 sendCommittedText(); 212 } 213 } 214 waitDigit(char c)215 private void waitDigit(char c) { 216 if (Character.digit(c, 16) != -1) { 217 buffer.insert(insertionPoint++, c); 218 sendComposedText(); 219 } else { 220 beep(); 221 } 222 } 223 waitDigit2(char c)224 private void waitDigit2(char c) { 225 if (Character.digit(c, 16) != -1) { 226 buffer.insert(insertionPoint++, c); 227 char codePoint = (char) getCodePoint(buffer, 2, 5); 228 if (Character.isHighSurrogate(codePoint)) { 229 format = SURROGATE_PAIR; 230 buffer.append("\\u"); 231 insertionPoint = 8; 232 } else { 233 format = ESCAPE; 234 } 235 sendComposedText(); 236 } else { 237 beep(); 238 } 239 } 240 waitBackSlashOrLowSurrogate(char c)241 private void waitBackSlashOrLowSurrogate(char c) { 242 if (insertionPoint == 6) { 243 if (c == '\\') { 244 buffer.append(c); 245 buffer.append('u'); 246 insertionPoint = 8; 247 sendComposedText(); 248 } else if (Character.digit(c, 16) != -1) { 249 buffer.append("\\u"); 250 buffer.append(c); 251 insertionPoint = 9; 252 sendComposedText(); 253 } else { 254 beep(); 255 } 256 } else { 257 beep(); 258 } 259 } 260 261 /** 262 * Send the composed text to the client. 263 */ sendComposedText()264 private void sendComposedText() { 265 AttributedString as = new AttributedString(buffer.toString()); 266 as.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, 267 InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT); 268 context.dispatchInputMethodEvent( 269 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 270 as.getIterator(), 0, 271 TextHitInfo.leading(insertionPoint), null); 272 } 273 274 /** 275 * Send the committed text to the client. 276 */ sendCommittedText()277 private void sendCommittedText() { 278 AttributedString as = new AttributedString(buffer.toString()); 279 context.dispatchInputMethodEvent( 280 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 281 as.getIterator(), buffer.length(), 282 TextHitInfo.leading(insertionPoint), null); 283 284 buffer.setLength(0); 285 insertionPoint = 0; 286 format = UNSET; 287 } 288 289 /** 290 * Move the insertion point one position to the left in the composed text. 291 * Do not let the caret move to the left of the "\\u" or "\\U". 292 */ moveCaretLeft()293 private void moveCaretLeft() { 294 int len = buffer.length(); 295 if (--insertionPoint < 2) { 296 insertionPoint++; 297 beep(); 298 } else if (format == SURROGATE_PAIR && insertionPoint == 7) { 299 insertionPoint = 8; 300 beep(); 301 } 302 303 context.dispatchInputMethodEvent( 304 InputMethodEvent.CARET_POSITION_CHANGED, 305 null, 0, 306 TextHitInfo.leading(insertionPoint), null); 307 } 308 309 /** 310 * Move the insertion point one position to the right in the composed text. 311 */ moveCaretRight()312 private void moveCaretRight() { 313 int len = buffer.length(); 314 if (++insertionPoint > len) { 315 insertionPoint = len; 316 beep(); 317 } 318 319 context.dispatchInputMethodEvent( 320 InputMethodEvent.CARET_POSITION_CHANGED, 321 null, 0, 322 TextHitInfo.leading(insertionPoint), null); 323 } 324 325 /** 326 * Delete the character preceding the insertion point in the composed text. 327 * If the insertion point is not at the end of the composed text and the 328 * preceding text is "\\u" or "\\U", ring the bell. 329 */ deletePreviousCharacter()330 private void deletePreviousCharacter() { 331 if (insertionPoint == 2) { 332 if (buffer.length() == 2) { 333 cancelComposition(); 334 } else { 335 // Do not allow deletion of the leading "\\u" or "\\U" if there 336 // are other digits in the composed text. 337 beep(); 338 } 339 } else if (insertionPoint == 8) { 340 if (buffer.length() == 8) { 341 if (format == SURROGATE_PAIR) { 342 buffer.deleteCharAt(--insertionPoint); 343 } 344 buffer.deleteCharAt(--insertionPoint); 345 sendComposedText(); 346 } else { 347 // Do not allow deletion of the second "\\u" if there are other 348 // digits in the composed text. 349 beep(); 350 } 351 } else { 352 buffer.deleteCharAt(--insertionPoint); 353 if (buffer.length() == 0) { 354 sendCommittedText(); 355 } else { 356 sendComposedText(); 357 } 358 } 359 } 360 361 /** 362 * Delete the character following the insertion point in the composed text. 363 * If the insertion point is at the end of the composed text, ring the bell. 364 */ deleteCharacter()365 private void deleteCharacter() { 366 if (insertionPoint < buffer.length()) { 367 buffer.deleteCharAt(insertionPoint); 368 sendComposedText(); 369 } else { 370 beep(); 371 } 372 } 373 startComposition()374 private void startComposition() { 375 buffer.append('\\'); 376 insertionPoint = 1; 377 sendComposedText(); 378 } 379 cancelComposition()380 private void cancelComposition() { 381 buffer.setLength(0); 382 insertionPoint = 0; 383 sendCommittedText(); 384 } 385 finishComposition()386 private void finishComposition() { 387 int len = buffer.length(); 388 if (len == 6 && format != SPECIAL_ESCAPE) { 389 char codePoint = (char) getCodePoint(buffer, 2, 5); 390 if (Character.isValidCodePoint(codePoint) && codePoint != 0xFFFF) { 391 buffer.setLength(0); 392 buffer.append(codePoint); 393 sendCommittedText(); 394 return; 395 } 396 } else if (len == 8 && format == SPECIAL_ESCAPE) { 397 int codePoint = getCodePoint(buffer, 2, 7); 398 if (Character.isValidCodePoint(codePoint) && codePoint != 0xFFFF) { 399 buffer.setLength(0); 400 buffer.appendCodePoint(codePoint); 401 sendCommittedText(); 402 return; 403 } 404 } else if (len == 12 && format == SURROGATE_PAIR) { 405 char[] codePoint = { 406 (char) getCodePoint(buffer, 2, 5), 407 (char) getCodePoint(buffer, 8, 11) 408 }; 409 if (Character.isHighSurrogate(codePoint[0]) && Character. 410 isLowSurrogate(codePoint[1])) { 411 buffer.setLength(0); 412 buffer.append(codePoint); 413 sendCommittedText(); 414 return; 415 } 416 } 417 418 beep(); 419 } 420 getCodePoint(StringBuffer sb, int from, int to)421 private int getCodePoint(StringBuffer sb, int from, int to) { 422 int value = 0; 423 for (int i = from; i <= to; i++) { 424 value = (value << 4) + Character.digit(sb.charAt(i), 16); 425 } 426 return value; 427 } 428 beep()429 private static void beep() { 430 Toolkit.getDefaultToolkit().beep(); 431 } 432 activate()433 public void activate() { 434 if (buffer == null) { 435 buffer = new StringBuffer(12); 436 insertionPoint = 0; 437 } 438 } 439 deactivate(boolean isTemporary)440 public void deactivate(boolean isTemporary) { 441 if (!isTemporary) { 442 buffer = null; 443 } 444 } 445 dispose()446 public void dispose() { 447 } 448 getControlObject()449 public Object getControlObject() { 450 return null; 451 } 452 endComposition()453 public void endComposition() { 454 sendCommittedText(); 455 } 456 getLocale()457 public Locale getLocale() { 458 return locale; 459 } 460 hideWindows()461 public void hideWindows() { 462 } 463 isCompositionEnabled()464 public boolean isCompositionEnabled() { 465 // always enabled 466 return true; 467 } 468 notifyClientWindowChange(Rectangle location)469 public void notifyClientWindowChange(Rectangle location) { 470 } 471 reconvert()472 public void reconvert() { 473 // not supported yet 474 throw new UnsupportedOperationException(); 475 } 476 removeNotify()477 public void removeNotify() { 478 } 479 setCharacterSubsets(Character.Subset[] subsets)480 public void setCharacterSubsets(Character.Subset[] subsets) { 481 } 482 setCompositionEnabled(boolean enable)483 public void setCompositionEnabled(boolean enable) { 484 // not supported yet 485 throw new UnsupportedOperationException(); 486 } 487 setInputMethodContext(InputMethodContext context)488 public void setInputMethodContext(InputMethodContext context) { 489 this.context = context; 490 } 491 492 /* 493 * The Code Point Input Method supports all locales. 494 */ setLocale(Locale locale)495 public boolean setLocale(Locale locale) { 496 this.locale = locale; 497 return true; 498 } 499 } 500