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