blob: 125440242748ab9a7a557fb086cf4cd3c23c2817 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2002-2004 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26/*
27 * (C) Copyright IBM Corp. 2000 - All Rights Reserved
28 *
29 * The original version of this source code and documentation is
30 * copyrighted and owned by IBM. These materials are provided
31 * under terms of a License Agreement between IBM and Sun.
32 * This technology is protected by multiple US and International
33 * patents. This notice and attribution to IBM may not be removed.
34 *
35 */
36
37package com.sun.inputmethods.internal.indicim;
38
39import java.awt.im.spi.InputMethodContext;
40
41import java.awt.event.KeyEvent;
42import java.awt.event.InputMethodEvent;
43import java.awt.font.TextAttribute;
44import java.awt.font.TextHitInfo;
45
46import java.text.AttributedCharacterIterator;
47
48import java.util.Hashtable;
49import java.util.HashSet;
50import java.util.Map;
51import java.util.Set;
52
53class IndicInputMethodImpl {
54
55 protected char[] KBD_MAP;
56
57 private static final char SUBSTITUTION_BASE = '\uff00';
58
59 // Indexed by map value - SUBSTITUTION_BASE
60 protected char[][] SUBSTITUTION_TABLE;
61
62 // Invalid character.
63 private static final char INVALID_CHAR = '\uffff';
64
65 // Unmapped versions of some interesting characters.
66 private static final char KEY_SIGN_VIRAMA = '\u0064'; // or just 'd'??
67 private static final char KEY_SIGN_NUKTA = '\u005d'; // or just ']'??
68
69 // Two succeeding viramas are replaced by one virama and one ZWNJ.
70 // Viram followed by Nukta is replaced by one VIRAMA and one ZWJ
71 private static final char ZWJ = '\u200d';
72 private static final char ZWNJ = '\u200c';
73
74 // Backspace
75 private static final char BACKSPACE = '\u0008';
76
77 // Sorted list of characters which can be followed by Nukta
78 protected char[] JOIN_WITH_NUKTA;
79
80 // Nukta form of the above characters
81 protected char[] NUKTA_FORM;
82
83 private int log2;
84 private int power;
85 private int extra;
86
87 // cached TextHitInfo. Only one type of TextHitInfo is required.
88 private static final TextHitInfo ZERO_TRAILING_HIT_INFO = TextHitInfo.trailing(0);
89
90 /**
91 * Returns the index of the given character in the JOIN_WITH_NUKTA array.
92 * If character is not found, -1 is returned.
93 */
94 private int nuktaIndex(char ch) {
95
96 if (JOIN_WITH_NUKTA == null) {
97 return -1;
98 }
99
100 int probe = power;
101 int index = 0;
102
103 if (JOIN_WITH_NUKTA[extra] <= ch) {
104 index = extra;
105 }
106
107 while (probe > (1 << 0)) {
108 probe >>= 1;
109
110 if (JOIN_WITH_NUKTA[index + probe] <= ch) {
111 index += probe;
112 }
113 }
114
115 if (JOIN_WITH_NUKTA[index] != ch) {
116 index = -1;
117 }
118
119 return index;
120 }
121
122 /**
123 * Returns the equivalent character for hindi locale.
124 * @param originalChar The original character.
125 */
126 private char getMappedChar( char originalChar )
127 {
128 if (originalChar <= KBD_MAP.length) {
129 return KBD_MAP[originalChar];
130 }
131
132 return originalChar;
133 }//getMappedChar()
134
135 // Array used to hold the text to be sent.
136 // If the last character was not committed it is stored in text[0].
137 // The variable totalChars give an indication of whether the last
138 // character was committed or not. If at any time ( but not within a
139 // a call to dispatchEvent ) totalChars is not equal to 0 ( it can
140 // only be 1 otherwise ) the last character was not committed.
141 private char [] text = new char[4];
142
143 // this is always 0 before and after call to dispatchEvent. This character assumes
144 // significance only within a call to dispatchEvent.
145 private int committedChars = 0;// number of committed characters
146
147 // the total valid characters in variable text currently.
148 private int totalChars = 0;//number of total characters ( committed + composed )
149
150 private boolean lastCharWasVirama = false;
151
152 private InputMethodContext context;
153
154 //
155 // Finds the high bit by binary searching
156 // through the bits in n.
157 //
158 private static byte highBit(int n)
159 {
160 if (n <= 0) {
161 return -32;
162 }
163
164 byte bit = 0;
165
166 if (n >= 1 << 16) {
167 n >>= 16;
168 bit += 16;
169 }
170
171 if (n >= 1 << 8) {
172 n >>= 8;
173 bit += 8;
174 }
175
176 if (n >= 1 << 4) {
177 n >>= 4;
178 bit += 4;
179 }
180
181 if (n >= 1 << 2) {
182 n >>= 2;
183 bit += 2;
184 }
185
186 if (n >= 1 << 1) {
187 n >>= 1;
188 bit += 1;
189 }
190
191 return bit;
192 }
193
194 IndicInputMethodImpl(char[] keyboardMap, char[] joinWithNukta, char[] nuktaForm,
195 char[][] substitutionTable) {
196 KBD_MAP = keyboardMap;
197 JOIN_WITH_NUKTA = joinWithNukta;
198 NUKTA_FORM = nuktaForm;
199 SUBSTITUTION_TABLE = substitutionTable;
200
201 if (JOIN_WITH_NUKTA != null) {
202 int log2 = highBit(JOIN_WITH_NUKTA.length);
203
204 power = 1 << log2;
205 extra = JOIN_WITH_NUKTA.length - power;
206 } else {
207 power = extra = 0;
208 }
209
210 }
211
212 void setInputMethodContext(InputMethodContext context) {
213
214 this.context = context;
215 }
216
217 void handleKeyTyped(KeyEvent kevent) {
218
219 char keyChar = kevent.getKeyChar();
220 char currentChar = getMappedChar(keyChar);
221
222 // The Explicit and Soft Halanta case.
223 if ( lastCharWasVirama ) {
224 switch (keyChar) {
225 case KEY_SIGN_NUKTA:
226 currentChar = ZWJ;
227 break;
228 case KEY_SIGN_VIRAMA:
229 currentChar = ZWNJ;
230 break;
231 default:
232 }//endSwitch
233 }//endif
234
235 if (currentChar == INVALID_CHAR) {
236 kevent.consume();
237 return;
238 }
239
240 if (currentChar == BACKSPACE) {
241 lastCharWasVirama = false;
242
243 if (totalChars > 0) {
244 totalChars = committedChars = 0;
245 } else {
246 return;
247 }
248 }
249 else if (keyChar == KEY_SIGN_NUKTA) {
250 int nuktaIndex = nuktaIndex(text[0]);
251
252 if (nuktaIndex != -1) {
253 text[0] = NUKTA_FORM[nuktaIndex];
254 } else {
255 // the last character was committed, commit just Nukta.
256 // Note : the lastChar must have been committed if it is not one of
257 // the characters which combine with nukta.
258 // the state must be totalChars = committedChars = 0;
259 text[totalChars++] = currentChar;
260 }
261
262 committedChars += 1;
263 }
264 else {
265 int nuktaIndex = nuktaIndex(currentChar);
266
267 if (nuktaIndex != -1) {
268 // Commit everything but currentChar
269 text[totalChars++] = currentChar;
270 committedChars = totalChars-1;
271 } else {
272 if (currentChar >= SUBSTITUTION_BASE) {
273 char[] sub = SUBSTITUTION_TABLE[currentChar - SUBSTITUTION_BASE];
274
275 System.arraycopy(sub, 0, text, totalChars, sub.length);
276 totalChars += sub.length;
277 } else {
278 text[totalChars++] = currentChar;
279 }
280
281 committedChars = totalChars;
282 }
283 }
284
285 ACIText aText = new ACIText( text, 0, totalChars, committedChars );
286 int composedCharLength = totalChars - committedChars;
287 TextHitInfo caret=null,visiblePosition=null;
288 switch( composedCharLength ) {
289 case 0:
290 break;
291 case 1:
292 visiblePosition = caret = ZERO_TRAILING_HIT_INFO;
293 break;
294 default:
295 assert false : "The code should not reach here. There is no case where there can be more than one character pending.";
296 }
297
298 context.dispatchInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
299 aText,
300 committedChars,
301 caret,
302 visiblePosition);
303
304 if (totalChars == 0) {
305 text[0] = INVALID_CHAR;
306 } else {
307 text[0] = text[totalChars - 1];// make text[0] hold the last character
308 }
309
310 lastCharWasVirama = keyChar == KEY_SIGN_VIRAMA && !lastCharWasVirama;
311
312 totalChars -= committedChars;
313 committedChars = 0;
314 // state now text[0] = last character
315 // totalChars = ( last character committed )? 0 : 1;
316 // committedChars = 0;
317
318 kevent.consume();// prevent client from getting this event.
319 }//dispatchEvent()
320
321 void endComposition() {
322 if( totalChars != 0 ) {// if some character is not committed.
323 ACIText aText = new ACIText( text, 0, totalChars, totalChars );
324 context.dispatchInputMethodEvent( InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
325 aText, totalChars, null, null );
326 totalChars = committedChars = 0;
327 text[0] = INVALID_CHAR;
328 lastCharWasVirama = false;
329 }//end if
330 }//endComposition()
331
332 // custom AttributedCharacterIterator -- much lightweight since currently there is no
333 // attribute defined on the text being generated by the input method.
334 private class ACIText implements AttributedCharacterIterator {
335 private char [] text = null;
336 private int committed = 0;
337 private int index = 0;
338
339 ACIText( char [] chArray, int offset, int length, int committed ) {
340 this.text = new char[length];
341 this.committed = committed;
342 System.arraycopy( chArray, offset, text, 0, length );
343 }//c'tor
344
345 // CharacterIterator methods.
346 public char first() {
347 return _setIndex( 0 );
348 }
349
350 public char last() {
351 if( text.length == 0 ) {
352 return _setIndex( text.length );
353 }
354 return _setIndex( text.length - 1 );
355 }
356
357 public char current() {
358 if( index == text.length )
359 return DONE;
360 return text[index];
361 }
362
363 public char next() {
364 if( index == text.length ) {
365 return DONE;
366 }
367 return _setIndex( index + 1 );
368 }
369
370 public char previous() {
371 if( index == 0 )
372 return DONE;
373 return _setIndex( index - 1 );
374 }
375
376 public char setIndex(int position) {
377 if( position < 0 || position > text.length ) {
378 throw new IllegalArgumentException();
379 }
380 return _setIndex( position );
381 }
382
383 public int getBeginIndex() {
384 return 0;
385 }
386
387 public int getEndIndex() {
388 return text.length;
389 }
390
391 public int getIndex() {
392 return index;
393 }
394
395 public Object clone() {
396 try {
397 ACIText clone = (ACIText) super.clone();
398 return clone;
399 } catch (CloneNotSupportedException e) {
400 throw new InternalError();
401 }
402 }
403
404
405 // AttributedCharacterIterator methods.
406 public int getRunStart() {
407 return index >= committed ? committed : 0;
408 }
409
410 public int getRunStart(AttributedCharacterIterator.Attribute attribute) {
411 return (index >= committed &&
412 attribute == TextAttribute.INPUT_METHOD_UNDERLINE) ? committed : 0;
413 }
414
415 public int getRunStart(Set<? extends Attribute> attributes) {
416 return (index >= committed &&
417 attributes.contains(TextAttribute.INPUT_METHOD_UNDERLINE)) ? committed : 0;
418 }
419
420 public int getRunLimit() {
421 return index < committed ? committed : text.length;
422 }
423
424 public int getRunLimit(AttributedCharacterIterator.Attribute attribute) {
425 return (index < committed &&
426 attribute == TextAttribute.INPUT_METHOD_UNDERLINE) ? committed : text.length;
427 }
428
429 public int getRunLimit(Set<? extends Attribute> attributes) {
430 return (index < committed &&
431 attributes.contains(TextAttribute.INPUT_METHOD_UNDERLINE)) ? committed : text.length;
432 }
433
434 public Map getAttributes() {
435 Hashtable result = new Hashtable();
436 if (index >= committed && committed < text.length) {
437 result.put(TextAttribute.INPUT_METHOD_UNDERLINE,
438 TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
439 }
440 return result;
441 }
442
443 public Object getAttribute(AttributedCharacterIterator.Attribute attribute) {
444 if (index >= committed &&
445 committed < text.length &&
446 attribute == TextAttribute.INPUT_METHOD_UNDERLINE) {
447
448 return TextAttribute.UNDERLINE_LOW_ONE_PIXEL;
449 }
450 return null;
451 }
452
453 public Set getAllAttributeKeys() {
454 HashSet result = new HashSet();
455 if (committed < text.length) {
456 result.add(TextAttribute.INPUT_METHOD_UNDERLINE);
457 }
458 return result;
459 }
460
461 // private methods
462
463 /**
464 * This is always called with valid i ( 0 < i <= text.length )
465 */
466 private char _setIndex( int i ) {
467 index = i;
468 if( i == text.length ) {
469 return DONE;
470 }
471 return text[i];
472 }//_setIndex()
473
474 }//end of inner class
475}