2 *******************************************************************************
\r
3 * Copyright (C) 2000-2007, 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.im.spi.InputMethodContext;
\r
12 import java.awt.event.KeyEvent;
\r
13 import java.awt.event.InputMethodEvent;
\r
14 import java.awt.font.TextAttribute;
\r
15 import java.awt.font.TextHitInfo;
\r
16 import java.text.AttributedCharacterIterator;
\r
17 import java.util.Hashtable;
\r
18 import java.util.HashSet;
\r
19 import java.util.Map;
\r
20 import java.util.Set;
\r
22 class IndicInputMethodImpl {
\r
24 protected char[] KBD_MAP;
\r
26 private static final char SUBSTITUTION_BASE = '\uff00';
\r
28 // Indexed by map value - SUBSTITUTION_BASE
\r
29 protected char[][] SUBSTITUTION_TABLE;
\r
31 // Invalid character.
\r
32 private static final char INVALID_CHAR = '\uffff';
\r
34 // Unmapped versions of some interesting characters.
\r
35 private static final char KEY_SIGN_VIRAMA = '\u0064'; // or just 'd'??
\r
36 private static final char KEY_SIGN_NUKTA = '\u005d'; // or just ']'??
\r
38 // Two succeeding viramas are replaced by one virama and one ZWNJ.
\r
39 // Viram followed by Nukta is replaced by one VIRAMA and one ZWJ
\r
40 private static final char ZWJ = '\u200d';
\r
41 private static final char ZWNJ = '\u200c';
\r
44 private static final char BACKSPACE = '\u0008';
\r
46 // Sorted list of characters which can be followed by Nukta
\r
47 protected char[] JOIN_WITH_NUKTA;
\r
49 // Nukta form of the above characters
\r
50 protected char[] NUKTA_FORM;
\r
56 // cached TextHitInfo. Only one type of TextHitInfo is required.
\r
57 private static final TextHitInfo ZERO_TRAILING_HIT_INFO = TextHitInfo.trailing(0);
\r
60 * Returns the index of the given character in the JOIN_WITH_NUKTA array.
\r
61 * If character is not found, -1 is returned.
\r
63 private int nuktaIndex(char ch) {
\r
64 if (JOIN_WITH_NUKTA == null) {
\r
71 if (JOIN_WITH_NUKTA[extra] <= ch) {
\r
75 while (probe > (1 << 0)) {
\r
78 if (JOIN_WITH_NUKTA[index + probe] <= ch) {
\r
83 if (JOIN_WITH_NUKTA[index] != ch) {
\r
91 * Returns the equivalent character for hindi locale.
\r
92 * @param originalChar The original character.
\r
94 private char getMappedChar(char originalChar) {
\r
95 if (originalChar <= KBD_MAP.length) {
\r
96 return KBD_MAP[originalChar];
\r
99 return originalChar;
\r
102 // Array used to hold the text to be sent.
\r
103 // If the last character was not committed it is stored in text[0].
\r
104 // The variable totalChars give an indication of whether the last
\r
105 // character was committed or not. If at any time ( but not within a
\r
106 // a call to dispatchEvent ) totalChars is not equal to 0 ( it can
\r
107 // only be 1 otherwise ) the last character was not committed.
\r
108 private char [] text = new char[4];
\r
110 // this is always 0 before and after call to dispatchEvent. This character assumes
\r
111 // significance only within a call to dispatchEvent.
\r
112 private int committedChars = 0;// number of committed characters
\r
114 // the total valid characters in variable text currently.
\r
115 private int totalChars = 0;//number of total characters ( committed + composed )
\r
117 private boolean lastCharWasVirama = false;
\r
119 private InputMethodContext context;
\r
122 // Finds the high bit by binary searching
\r
123 // through the bits in n.
\r
125 private static byte highBit(int n) {
\r
132 if (n >= 1 << 16) {
\r
160 IndicInputMethodImpl(char[] keyboardMap, char[] joinWithNukta, char[] nuktaForm,
\r
161 char[][] substitutionTable) {
\r
162 KBD_MAP = keyboardMap;
\r
163 JOIN_WITH_NUKTA = joinWithNukta;
\r
164 NUKTA_FORM = nuktaForm;
\r
165 SUBSTITUTION_TABLE = substitutionTable;
\r
167 if (JOIN_WITH_NUKTA != null) {
\r
168 int log2 = highBit(JOIN_WITH_NUKTA.length);
\r
171 extra = JOIN_WITH_NUKTA.length - power;
\r
178 void setInputMethodContext(InputMethodContext context) {
\r
179 this.context = context;
\r
182 void handleKeyTyped(KeyEvent kevent) {
\r
183 char keyChar = kevent.getKeyChar();
\r
184 char currentChar = getMappedChar(keyChar);
\r
186 // The Explicit and Soft Halanta case.
\r
187 if ( lastCharWasVirama ) {
\r
189 case KEY_SIGN_NUKTA:
\r
192 case KEY_SIGN_VIRAMA:
\r
193 currentChar = ZWNJ;
\r
199 if (currentChar == INVALID_CHAR) {
\r
204 if (currentChar == BACKSPACE) {
\r
205 lastCharWasVirama = false;
\r
207 if (totalChars > 0) {
\r
208 totalChars = committedChars = 0;
\r
213 else if (keyChar == KEY_SIGN_NUKTA) {
\r
214 int nuktaIndex = nuktaIndex(text[0]);
\r
216 if (nuktaIndex != -1) {
\r
217 text[0] = NUKTA_FORM[nuktaIndex];
\r
219 // the last character was committed, commit just Nukta.
\r
220 // Note : the lastChar must have been committed if it is not one of
\r
221 // the characters which combine with nukta.
\r
222 // the state must be totalChars = committedChars = 0;
\r
223 text[totalChars++] = currentChar;
\r
226 committedChars += 1;
\r
229 int nuktaIndex = nuktaIndex(currentChar);
\r
231 if (nuktaIndex != -1) {
\r
232 // Commit everything but currentChar
\r
233 text[totalChars++] = currentChar;
\r
234 committedChars = totalChars-1;
\r
236 if (currentChar >= SUBSTITUTION_BASE) {
\r
237 char[] sub = SUBSTITUTION_TABLE[currentChar - SUBSTITUTION_BASE];
\r
239 System.arraycopy(sub, 0, text, totalChars, sub.length);
\r
240 totalChars += sub.length;
\r
242 text[totalChars++] = currentChar;
\r
245 committedChars = totalChars;
\r
249 ACIText aText = new ACIText( text, 0, totalChars, committedChars );
\r
250 int composedCharLength = totalChars - committedChars;
\r
251 TextHitInfo caret=null,visiblePosition=null;
\r
252 switch( composedCharLength ) {
\r
256 visiblePosition = caret = ZERO_TRAILING_HIT_INFO;
\r
259 // The code should not reach here. There is no case where there can be
\r
260 // more than one character pending.
\r
263 context.dispatchInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
\r
269 if (totalChars == 0) {
\r
270 text[0] = INVALID_CHAR;
\r
272 text[0] = text[totalChars - 1];// make text[0] hold the last character
\r
275 lastCharWasVirama = keyChar == KEY_SIGN_VIRAMA && !lastCharWasVirama;
\r
277 totalChars -= committedChars;
\r
278 committedChars = 0;
\r
279 // state now text[0] = last character
\r
280 // totalChars = ( last character committed )? 0 : 1;
\r
281 // committedChars = 0;
\r
283 kevent.consume();// prevent client from getting this event.
\r
286 void endComposition() {
\r
287 if( totalChars != 0 ) {// if some character is not committed.
\r
288 ACIText aText = new ACIText( text, 0, totalChars, totalChars );
\r
289 context.dispatchInputMethodEvent( InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
\r
290 aText, totalChars, null, null );
\r
291 totalChars = committedChars = 0;
\r
292 text[0] = INVALID_CHAR;
\r
293 lastCharWasVirama = false;
\r
297 // custom AttributedCharacterIterator -- much lightweight since currently there is no
\r
298 // attribute defined on the text being generated by the input method.
\r
299 private class ACIText implements AttributedCharacterIterator {
\r
300 private char [] text = null;
\r
301 private int committed = 0;
\r
302 private int index = 0;
\r
304 ACIText( char [] chArray, int offset, int length, int committed ) {
\r
305 this.text = new char[length];
\r
306 this.committed = committed;
\r
307 System.arraycopy( chArray, offset, text, 0, length );
\r
310 // CharacterIterator methods.
\r
311 public char first() {
\r
312 return _setIndex( 0 );
\r
315 public char last() {
\r
316 if( text.length == 0 ) {
\r
317 return _setIndex( text.length );
\r
319 return _setIndex( text.length - 1 );
\r
322 public char current() {
\r
323 if( index == text.length )
\r
325 return text[index];
\r
328 public char next() {
\r
329 if( index == text.length ) {
\r
332 return _setIndex( index + 1 );
\r
335 public char previous() {
\r
338 return _setIndex( index - 1 );
\r
341 public char setIndex(int position) {
\r
342 if( position < 0 || position > text.length ) {
\r
343 throw new IllegalArgumentException();
\r
345 return _setIndex( position );
\r
348 public int getBeginIndex() {
\r
352 public int getEndIndex() {
\r
353 return text.length;
\r
356 public int getIndex() {
\r
360 public Object clone() {
\r
362 ACIText clone = (ACIText) super.clone();
\r
364 } catch (CloneNotSupportedException e) {
\r
365 throw new IllegalStateException();
\r
369 // AttributedCharacterIterator methods.
\r
370 public int getRunStart() {
\r
371 return index >= committed ? committed : 0;
\r
374 public int getRunStart(AttributedCharacterIterator.Attribute attribute) {
\r
375 return (index >= committed &&
\r
376 attribute == TextAttribute.INPUT_METHOD_UNDERLINE) ? committed : 0;
\r
379 public int getRunStart(Set attributes) {
\r
380 return (index >= committed &&
\r
381 attributes.contains(TextAttribute.INPUT_METHOD_UNDERLINE)) ? committed : 0;
\r
384 public int getRunLimit() {
\r
385 return index < committed ? committed : text.length;
\r
388 public int getRunLimit(AttributedCharacterIterator.Attribute attribute) {
\r
389 return (index < committed &&
\r
390 attribute == TextAttribute.INPUT_METHOD_UNDERLINE) ? committed : text.length;
\r
393 public int getRunLimit(Set attributes) {
\r
394 return (index < committed &&
\r
395 attributes.contains(TextAttribute.INPUT_METHOD_UNDERLINE)) ? committed : text.length;
\r
398 public Map getAttributes() {
\r
399 Hashtable result = new Hashtable();
\r
400 if (index >= committed && committed < text.length) {
\r
401 result.put(TextAttribute.INPUT_METHOD_UNDERLINE,
\r
402 TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
\r
407 public Object getAttribute(AttributedCharacterIterator.Attribute attribute) {
\r
408 if (index >= committed &&
\r
409 committed < text.length &&
\r
410 attribute == TextAttribute.INPUT_METHOD_UNDERLINE) {
\r
412 return TextAttribute.UNDERLINE_LOW_ONE_PIXEL;
\r
417 public Set getAllAttributeKeys() {
\r
418 HashSet result = new HashSet();
\r
419 if (committed < text.length) {
\r
420 result.add(TextAttribute.INPUT_METHOD_UNDERLINE);
\r
428 * This is always called with valid i ( 0 < i <= text.length )
\r
430 private char _setIndex( int i ) {
\r
432 if( i == text.length ) {
\r