2 *******************************************************************************
\r
3 * Copyright (C) 2000-2010, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
8 package com.ibm.icu.dev.tool.ime.indic;
\r
10 import java.awt.event.InputMethodEvent;
\r
11 import java.awt.event.KeyEvent;
\r
12 import java.awt.font.TextAttribute;
\r
13 import java.awt.font.TextHitInfo;
\r
14 import java.awt.im.spi.InputMethodContext;
\r
15 import java.text.AttributedCharacterIterator;
\r
16 import java.util.HashSet;
\r
17 import java.util.Hashtable;
\r
18 import java.util.Map;
\r
19 import java.util.Set;
\r
21 class IndicInputMethodImpl {
\r
23 protected char[] KBD_MAP;
\r
25 private static final char SUBSTITUTION_BASE = '\uff00';
\r
27 // Indexed by map value - SUBSTITUTION_BASE
\r
28 protected char[][] SUBSTITUTION_TABLE;
\r
30 // Invalid character.
\r
31 private static final char INVALID_CHAR = '\uffff';
\r
33 // Unmapped versions of some interesting characters.
\r
34 private static final char KEY_SIGN_VIRAMA = '\u0064'; // or just 'd'??
\r
35 private static final char KEY_SIGN_NUKTA = '\u005d'; // or just ']'??
\r
37 // Two succeeding viramas are replaced by one virama and one ZWNJ.
\r
38 // Viram followed by Nukta is replaced by one VIRAMA and one ZWJ
\r
39 private static final char ZWJ = '\u200d';
\r
40 private static final char ZWNJ = '\u200c';
\r
43 private static final char BACKSPACE = '\u0008';
\r
45 // Sorted list of characters which can be followed by Nukta
\r
46 protected char[] JOIN_WITH_NUKTA;
\r
48 // Nukta form of the above characters
\r
49 protected char[] NUKTA_FORM;
\r
55 // cached TextHitInfo. Only one type of TextHitInfo is required.
\r
56 private static final TextHitInfo ZERO_TRAILING_HIT_INFO = TextHitInfo.trailing(0);
\r
59 * Returns the index of the given character in the JOIN_WITH_NUKTA array.
\r
60 * If character is not found, -1 is returned.
\r
62 private int nuktaIndex(char ch) {
\r
63 if (JOIN_WITH_NUKTA == null) {
\r
70 if (JOIN_WITH_NUKTA[extra] <= ch) {
\r
74 while (probe > (1 << 0)) {
\r
77 if (JOIN_WITH_NUKTA[index + probe] <= ch) {
\r
82 if (JOIN_WITH_NUKTA[index] != ch) {
\r
90 * Returns the equivalent character for hindi locale.
\r
91 * @param originalChar The original character.
\r
93 private char getMappedChar(char originalChar) {
\r
94 if (originalChar <= KBD_MAP.length) {
\r
95 return KBD_MAP[originalChar];
\r
98 return originalChar;
\r
101 // Array used to hold the text to be sent.
\r
102 // If the last character was not committed it is stored in text[0].
\r
103 // The variable totalChars give an indication of whether the last
\r
104 // character was committed or not. If at any time ( but not within a
\r
105 // a call to dispatchEvent ) totalChars is not equal to 0 ( it can
\r
106 // only be 1 otherwise ) the last character was not committed.
\r
107 private char [] text = new char[4];
\r
109 // this is always 0 before and after call to dispatchEvent. This character assumes
\r
110 // significance only within a call to dispatchEvent.
\r
111 private int committedChars = 0;// number of committed characters
\r
113 // the total valid characters in variable text currently.
\r
114 private int totalChars = 0;//number of total characters ( committed + composed )
\r
116 private boolean lastCharWasVirama = false;
\r
118 private InputMethodContext context;
\r
121 // Finds the high bit by binary searching
\r
122 // through the bits in n.
\r
124 private static byte highBit(int n) {
\r
131 if (n >= 1 << 16) {
\r
159 IndicInputMethodImpl(char[] keyboardMap, char[] joinWithNukta, char[] nuktaForm,
\r
160 char[][] substitutionTable) {
\r
161 KBD_MAP = keyboardMap;
\r
162 JOIN_WITH_NUKTA = joinWithNukta;
\r
163 NUKTA_FORM = nuktaForm;
\r
164 SUBSTITUTION_TABLE = substitutionTable;
\r
166 if (JOIN_WITH_NUKTA != null) {
\r
167 int log2 = highBit(JOIN_WITH_NUKTA.length);
\r
170 extra = JOIN_WITH_NUKTA.length - power;
\r
177 void setInputMethodContext(InputMethodContext context) {
\r
178 this.context = context;
\r
181 void handleKeyTyped(KeyEvent kevent) {
\r
182 char keyChar = kevent.getKeyChar();
\r
183 char currentChar = getMappedChar(keyChar);
\r
185 // The Explicit and Soft Halanta case.
\r
186 if ( lastCharWasVirama ) {
\r
188 case KEY_SIGN_NUKTA:
\r
191 case KEY_SIGN_VIRAMA:
\r
192 currentChar = ZWNJ;
\r
198 if (currentChar == INVALID_CHAR) {
\r
203 if (currentChar == BACKSPACE) {
\r
204 lastCharWasVirama = false;
\r
206 if (totalChars > 0) {
\r
207 totalChars = committedChars = 0;
\r
212 else if (keyChar == KEY_SIGN_NUKTA) {
\r
213 int nuktaIndex = nuktaIndex(text[0]);
\r
215 if (nuktaIndex != -1) {
\r
216 text[0] = NUKTA_FORM[nuktaIndex];
\r
218 // the last character was committed, commit just Nukta.
\r
219 // Note : the lastChar must have been committed if it is not one of
\r
220 // the characters which combine with nukta.
\r
221 // the state must be totalChars = committedChars = 0;
\r
222 text[totalChars++] = currentChar;
\r
225 committedChars += 1;
\r
228 int nuktaIndex = nuktaIndex(currentChar);
\r
230 if (nuktaIndex != -1) {
\r
231 // Commit everything but currentChar
\r
232 text[totalChars++] = currentChar;
\r
233 committedChars = totalChars-1;
\r
235 if (currentChar >= SUBSTITUTION_BASE) {
\r
236 char[] sub = SUBSTITUTION_TABLE[currentChar - SUBSTITUTION_BASE];
\r
238 System.arraycopy(sub, 0, text, totalChars, sub.length);
\r
239 totalChars += sub.length;
\r
241 text[totalChars++] = currentChar;
\r
244 committedChars = totalChars;
\r
248 ACIText aText = new ACIText( text, 0, totalChars, committedChars );
\r
249 int composedCharLength = totalChars - committedChars;
\r
250 TextHitInfo caret=null,visiblePosition=null;
\r
251 switch( composedCharLength ) {
\r
255 visiblePosition = caret = ZERO_TRAILING_HIT_INFO;
\r
258 // The code should not reach here. There is no case where there can be
\r
259 // more than one character pending.
\r
262 context.dispatchInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
\r
268 if (totalChars == 0) {
\r
269 text[0] = INVALID_CHAR;
\r
271 text[0] = text[totalChars - 1];// make text[0] hold the last character
\r
274 lastCharWasVirama = keyChar == KEY_SIGN_VIRAMA && !lastCharWasVirama;
\r
276 totalChars -= committedChars;
\r
277 committedChars = 0;
\r
278 // state now text[0] = last character
\r
279 // totalChars = ( last character committed )? 0 : 1;
\r
280 // committedChars = 0;
\r
282 kevent.consume();// prevent client from getting this event.
\r
285 void endComposition() {
\r
286 if( totalChars != 0 ) {// if some character is not committed.
\r
287 ACIText aText = new ACIText( text, 0, totalChars, totalChars );
\r
288 context.dispatchInputMethodEvent( InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
\r
289 aText, totalChars, null, null );
\r
290 totalChars = committedChars = 0;
\r
291 text[0] = INVALID_CHAR;
\r
292 lastCharWasVirama = false;
\r
296 // custom AttributedCharacterIterator -- much lightweight since currently there is no
\r
297 // attribute defined on the text being generated by the input method.
\r
298 private class ACIText implements AttributedCharacterIterator {
\r
299 private char [] text = null;
\r
300 private int committed = 0;
\r
301 private int index = 0;
\r
303 ACIText( char [] chArray, int offset, int length, int committed ) {
\r
304 this.text = new char[length];
\r
305 this.committed = committed;
\r
306 System.arraycopy( chArray, offset, text, 0, length );
\r
309 // CharacterIterator methods.
\r
310 public char first() {
\r
311 return _setIndex( 0 );
\r
314 public char last() {
\r
315 if( text.length == 0 ) {
\r
316 return _setIndex( text.length );
\r
318 return _setIndex( text.length - 1 );
\r
321 public char current() {
\r
322 if( index == text.length )
\r
324 return text[index];
\r
327 public char next() {
\r
328 if( index == text.length ) {
\r
331 return _setIndex( index + 1 );
\r
334 public char previous() {
\r
337 return _setIndex( index - 1 );
\r
340 public char setIndex(int position) {
\r
341 if( position < 0 || position > text.length ) {
\r
342 throw new IllegalArgumentException();
\r
344 return _setIndex( position );
\r
347 public int getBeginIndex() {
\r
351 public int getEndIndex() {
\r
352 return text.length;
\r
355 public int getIndex() {
\r
359 public Object clone() {
\r
361 ACIText clone = (ACIText) super.clone();
\r
363 } catch (CloneNotSupportedException e) {
\r
364 throw new IllegalStateException();
\r
368 // AttributedCharacterIterator methods.
\r
369 public int getRunStart() {
\r
370 return index >= committed ? committed : 0;
\r
373 public int getRunStart(AttributedCharacterIterator.Attribute attribute) {
\r
374 return (index >= committed &&
\r
375 attribute == TextAttribute.INPUT_METHOD_UNDERLINE) ? committed : 0;
\r
378 public int getRunStart(Set attributes) {
\r
379 return (index >= committed &&
\r
380 attributes.contains(TextAttribute.INPUT_METHOD_UNDERLINE)) ? committed : 0;
\r
383 public int getRunLimit() {
\r
384 return index < committed ? committed : text.length;
\r
387 public int getRunLimit(AttributedCharacterIterator.Attribute attribute) {
\r
388 return (index < committed &&
\r
389 attribute == TextAttribute.INPUT_METHOD_UNDERLINE) ? committed : text.length;
\r
392 public int getRunLimit(Set attributes) {
\r
393 return (index < committed &&
\r
394 attributes.contains(TextAttribute.INPUT_METHOD_UNDERLINE)) ? committed : text.length;
\r
397 public Map getAttributes() {
\r
398 Hashtable result = new Hashtable();
\r
399 if (index >= committed && committed < text.length) {
\r
400 result.put(TextAttribute.INPUT_METHOD_UNDERLINE,
\r
401 TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
\r
406 public Object getAttribute(AttributedCharacterIterator.Attribute attribute) {
\r
407 if (index >= committed &&
\r
408 committed < text.length &&
\r
409 attribute == TextAttribute.INPUT_METHOD_UNDERLINE) {
\r
411 return TextAttribute.UNDERLINE_LOW_ONE_PIXEL;
\r
416 public Set getAllAttributeKeys() {
\r
417 HashSet result = new HashSet();
\r
418 if (committed < text.length) {
\r
419 result.add(TextAttribute.INPUT_METHOD_UNDERLINE);
\r
427 * This is always called with valid i ( 0 < i <= text.length )
\r
429 private char _setIndex( int i ) {
\r
431 if( i == text.length ) {
\r