]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/dev/tool/ime/indic/IndicInputMethodImpl.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / dev / tool / ime / indic / IndicInputMethodImpl.java
1 /*\r
2  *******************************************************************************\r
3  * Copyright (C) 2000-2007, International Business Machines Corporation and    *\r
4  * others. All Rights Reserved.                                                *\r
5  *******************************************************************************\r
6  */\r
7 \r
8 package com.ibm.icu.dev.tool.ime.indic;\r
9 \r
10 import java.awt.im.spi.InputMethodContext;\r
11 \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
21 \r
22 class IndicInputMethodImpl {\r
23     \r
24     protected char[] KBD_MAP;\r
25     \r
26     private static final char SUBSTITUTION_BASE = '\uff00';\r
27 \r
28     // Indexed by map value - SUBSTITUTION_BASE\r
29     protected char[][] SUBSTITUTION_TABLE;\r
30 \r
31     // Invalid character.\r
32     private static final char INVALID_CHAR              = '\uffff';\r
33 \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
37 \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
42     \r
43     // Backspace\r
44     private static final char BACKSPACE                 = '\u0008';\r
45 \r
46     // Sorted list of characters which can be followed by Nukta\r
47     protected char[] JOIN_WITH_NUKTA;\r
48     \r
49     // Nukta form of the above characters\r
50     protected char[] NUKTA_FORM;\r
51         \r
52     //private int log2;\r
53     private int power;\r
54     private int extra;\r
55 \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
58 \r
59     /**\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
62      */\r
63     private int nuktaIndex(char ch) {\r
64         if (JOIN_WITH_NUKTA == null) {\r
65             return -1;\r
66         }\r
67         \r
68         int probe = power;\r
69         int index = 0;\r
70 \r
71         if (JOIN_WITH_NUKTA[extra] <= ch) {\r
72             index = extra;\r
73         }\r
74          \r
75         while (probe > (1 << 0)) {\r
76             probe >>= 1;\r
77 \r
78             if (JOIN_WITH_NUKTA[index + probe] <= ch) {\r
79                 index += probe;\r
80             }\r
81         }\r
82 \r
83         if (JOIN_WITH_NUKTA[index] != ch) {\r
84             index = -1;\r
85         }\r
86 \r
87         return index;\r
88     }\r
89     \r
90     /**\r
91      * Returns the equivalent character for hindi locale.\r
92      * @param originalChar The original character.\r
93      */\r
94     private char getMappedChar(char originalChar) {\r
95         if (originalChar <= KBD_MAP.length) {\r
96             return KBD_MAP[originalChar];\r
97         }\r
98         \r
99         return originalChar;\r
100     }\r
101     \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
109 \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
113 \r
114     // the total valid characters in variable text currently.\r
115     private int totalChars = 0;//number of total characters ( committed + composed )\r
116 \r
117     private boolean lastCharWasVirama = false;\r
118     \r
119     private InputMethodContext context;\r
120 \r
121     //\r
122     // Finds the high bit by binary searching\r
123     // through the bits in n.\r
124     //\r
125     private static byte highBit(int n) {\r
126         if (n <= 0) {\r
127             return -32;\r
128         }\r
129 \r
130         byte bit = 0;\r
131 \r
132         if (n >= 1 << 16) {\r
133             n >>= 16;\r
134             bit += 16;\r
135         }\r
136 \r
137         if (n >= 1 << 8) {\r
138             n >>= 8;\r
139             bit += 8;\r
140         }\r
141 \r
142         if (n >= 1 << 4) {\r
143             n >>= 4;\r
144             bit += 4;\r
145         }\r
146 \r
147         if (n >= 1 << 2) {\r
148             n >>= 2;\r
149             bit += 2;\r
150         }\r
151 \r
152         if (n >= 1 << 1) {\r
153             n >>= 1;\r
154             bit += 1;\r
155         }\r
156 \r
157         return bit;\r
158     }\r
159 \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
166         \r
167         if (JOIN_WITH_NUKTA != null) {\r
168             int log2 = highBit(JOIN_WITH_NUKTA.length);\r
169             \r
170             power = 1 << log2;\r
171             extra = JOIN_WITH_NUKTA.length - power;\r
172         } else {\r
173             power = extra = 0;\r
174         }\r
175         \r
176     }\r
177     \r
178     void setInputMethodContext(InputMethodContext context) {\r
179         this.context = context;\r
180     }\r
181     \r
182     void handleKeyTyped(KeyEvent kevent) {\r
183         char keyChar = kevent.getKeyChar();\r
184         char currentChar = getMappedChar(keyChar);\r
185         \r
186         // The Explicit and Soft Halanta case.\r
187         if ( lastCharWasVirama ) {\r
188             switch (keyChar) {\r
189             case KEY_SIGN_NUKTA:\r
190                 currentChar = ZWJ;\r
191                 break;\r
192             case KEY_SIGN_VIRAMA:\r
193                 currentChar = ZWNJ;\r
194                 break;\r
195             default:\r
196             }//endSwitch\r
197         }//endif\r
198 \r
199         if (currentChar == INVALID_CHAR) {\r
200             kevent.consume();\r
201             return;\r
202         }\r
203 \r
204         if (currentChar == BACKSPACE) {\r
205             lastCharWasVirama = false;\r
206             \r
207             if (totalChars > 0) {\r
208                 totalChars = committedChars = 0;\r
209             } else {\r
210                 return;\r
211             }                            \r
212         }\r
213         else if (keyChar == KEY_SIGN_NUKTA) {\r
214             int nuktaIndex = nuktaIndex(text[0]);\r
215             \r
216             if (nuktaIndex != -1) {\r
217                 text[0] = NUKTA_FORM[nuktaIndex];\r
218             } else {\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
224             }\r
225             \r
226             committedChars += 1;\r
227         }\r
228         else {\r
229             int nuktaIndex = nuktaIndex(currentChar);\r
230             \r
231             if (nuktaIndex != -1) {\r
232                 // Commit everything but currentChar\r
233                 text[totalChars++] = currentChar;\r
234                 committedChars = totalChars-1;\r
235             } else {\r
236                 if (currentChar >= SUBSTITUTION_BASE) {\r
237                     char[] sub = SUBSTITUTION_TABLE[currentChar - SUBSTITUTION_BASE];\r
238                     \r
239                     System.arraycopy(sub, 0, text, totalChars, sub.length);\r
240                     totalChars += sub.length;\r
241                 } else {\r
242                     text[totalChars++] = currentChar;\r
243                 }\r
244                 \r
245                 committedChars = totalChars;\r
246             }\r
247         }\r
248         \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
253             case 0:\r
254                 break;\r
255             case 1:\r
256                 visiblePosition = caret = ZERO_TRAILING_HIT_INFO;\r
257                 break;\r
258             default:\r
259                 // The code should not reach here. There is no case where there can be\r
260                 // more than one character pending.\r
261         }\r
262                         \r
263         context.dispatchInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,\r
264                                          aText,\r
265                                          committedChars,\r
266                                          caret,\r
267                                          visiblePosition);\r
268                                         \r
269         if (totalChars == 0) {\r
270             text[0] = INVALID_CHAR;\r
271         } else {\r
272             text[0] = text[totalChars - 1];// make text[0] hold the last character\r
273         }\r
274         \r
275         lastCharWasVirama =  keyChar == KEY_SIGN_VIRAMA && !lastCharWasVirama;\r
276                     \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
282 \r
283         kevent.consume();// prevent client from getting this event.\r
284     }\r
285 \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
294         }\r
295     }\r
296     \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
303 \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
308         }\r
309 \r
310         // CharacterIterator methods.\r
311         public char first() {\r
312             return _setIndex( 0 );\r
313         }\r
314 \r
315         public char last() {\r
316             if( text.length == 0 ) {\r
317                 return _setIndex( text.length );\r
318             }\r
319             return _setIndex( text.length - 1 );\r
320         }\r
321 \r
322         public char current() {\r
323             if( index == text.length )\r
324                 return DONE;\r
325             return text[index];\r
326         }\r
327 \r
328         public char next() {\r
329             if( index == text.length ) {\r
330                 return DONE;\r
331             }\r
332             return _setIndex( index + 1 );\r
333         }\r
334 \r
335         public char previous() {\r
336             if( index == 0 )\r
337                 return DONE;\r
338             return _setIndex( index - 1 );\r
339         }\r
340 \r
341         public char setIndex(int position) {\r
342             if( position < 0 || position > text.length ) {\r
343                 throw new IllegalArgumentException();\r
344             }\r
345             return _setIndex( position );\r
346         }\r
347 \r
348         public int getBeginIndex() {\r
349             return 0;\r
350         }\r
351 \r
352         public int getEndIndex() {\r
353             return text.length;\r
354         }\r
355 \r
356         public int getIndex() {\r
357             return index;\r
358         }\r
359 \r
360         public Object clone() {\r
361             try {\r
362                 ACIText clone = (ACIText) super.clone();\r
363                 return clone;\r
364             } catch (CloneNotSupportedException e) {\r
365                 throw new IllegalStateException();\r
366             }\r
367         }\r
368 \r
369         // AttributedCharacterIterator methods.\r
370         public int getRunStart() {\r
371             return index >= committed ? committed : 0;\r
372         }\r
373 \r
374         public int getRunStart(AttributedCharacterIterator.Attribute attribute) {\r
375             return (index >= committed &&\r
376                 attribute == TextAttribute.INPUT_METHOD_UNDERLINE) ? committed : 0;\r
377         }\r
378 \r
379         public int getRunStart(Set attributes) {\r
380             return (index >= committed &&\r
381                     attributes.contains(TextAttribute.INPUT_METHOD_UNDERLINE)) ? committed : 0;\r
382         }\r
383 \r
384         public int getRunLimit() {\r
385             return index < committed ? committed : text.length;\r
386         }\r
387 \r
388         public int getRunLimit(AttributedCharacterIterator.Attribute attribute) {\r
389             return (index < committed && \r
390                     attribute == TextAttribute.INPUT_METHOD_UNDERLINE) ? committed : text.length;\r
391         }\r
392 \r
393         public int getRunLimit(Set attributes) {\r
394             return (index < committed &&\r
395                     attributes.contains(TextAttribute.INPUT_METHOD_UNDERLINE)) ? committed : text.length;\r
396         }\r
397 \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
403             }\r
404             return result;\r
405         }\r
406 \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
411 \r
412                 return TextAttribute.UNDERLINE_LOW_ONE_PIXEL;\r
413             }\r
414             return null;\r
415         }\r
416 \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
421             }\r
422             return result;\r
423         }\r
424 \r
425         // private methods\r
426 \r
427         /**\r
428          * This is always called with valid i ( 0 < i <= text.length )\r
429          */\r
430         private char _setIndex( int i ) {\r
431             index = i;\r
432             if( i == text.length ) {\r
433                 return DONE;\r
434             }\r
435             return text[i];\r
436         }\r
437 \r
438     }\r
439 }\r