| GateIM.java |
1 /*
2 * GateIM.java
3 *
4 * Copyright (c) 1998-2004, The University of Sheffield.
5 *
6 * This file is part of GATE (see http://gate.ac.uk/), and is free
7 * software, licenced under the GNU Library General Public License,
8 * Version 2, June1991.
9 *
10 * A copy of this licence is included in the distribution in the file
11 * licence.html, and is also available at http://gate.ac.uk/gate/licence.html.
12 *
13 * Valentin Tablan, October 2000
14 *
15 * $Id: GateIM.java,v 1.8 2004/07/21 17:10:11 akshay Exp $
16 */
17 package guk.im;
18
19 import java.awt.*;
20 import java.awt.event.InputMethodEvent;
21 import java.awt.event.KeyEvent;
22 import java.awt.im.spi.InputMethod;
23 import java.awt.im.spi.InputMethodContext;
24 import java.io.IOException;
25 import java.lang.Character.Subset;
26 import java.text.AttributedString;
27 import java.util.*;
28
29 /**
30 * The Gate input method
31 *
32 */
33 public class GateIM implements InputMethod {
34
35 /**
36 * Constructs a new Gate input method
37 *
38 * @param supportedLocales
39 */
40 public GateIM(Map supportedLocales) {
41 this.supportedLocales = supportedLocales;
42 loadedLocales = new HashMap();
43 }// GateIM(Map supportedLocales)
44
45 /**
46 * Provides the input method with a context. This method is called by the
47 * system after the input method is loaded and linked to a text component.
48 *
49 * @param context
50 */
51 public void setInputMethodContext(InputMethodContext context) {
52 myContext = context;
53 //we don't care about the client window state and position
54 myContext.enableClientWindowNotification(this, false);
55 }// setInputMethodContext(InputMethodContext context)
56
57 /**
58 * Selects the active locale
59 *
60 * @param locale
61 */
62 public boolean setLocale(Locale locale) {
63 endComposition();
64 try {
65 if(supportedLocales.containsKey(locale)){
66 currentLocale = locale;
67 loadLocale(locale);
68 if(keyboardMap != null) keyboardMap.update(currentHandler,
69 currentState);
70 return true;
71 }
72 } catch(IllegalArgumentException iae){
73 iae.printStackTrace();
74 return false;
75 }
76 return false;
77 }// boolean setLocale(Locale locale)
78
79 /**
80 * Gets the active locale
81 *
82 */
83 public Locale getLocale() {
84 return currentLocale;
85 }
86
87 /**
88 * gets the descriptor class for this input method
89 *
90 */
91 public GateIMDescriptor getDescriptor(){
92 return new GateIMDescriptor();
93 }
94
95 /**
96 * Restricts the character ranges valid for this input method output. This is
97 * currently ignored by the input method.
98 *
99 * @param subsets
100 */
101 public void setCharacterSubsets(Subset[] subsets) {
102 }
103
104 /**
105 * Enables this input method for composition
106 *
107 * @param enable
108 */
109 public void setCompositionEnabled(boolean enable) {
110 enabled = enable;
111 }
112
113 /**
114 * Is this input method enabled?
115 *
116 */
117 public boolean isCompositionEnabled() {
118 return enabled;
119 }
120
121 /**
122 * Throws a UnsupportedOperationException as this input method does not
123 * support recnversion.
124 *
125 */
126 public void reconvert() {
127 /**@todo: Implement this java.awt.im.spi.InputMethod method*/
128 throw new java.lang.UnsupportedOperationException(
129 "Reconversion not supported!");
130 }
131
132 /**
133 * Called by the system when an input event occures in a component that uses
134 * this input method.
135 * The input method then analyses the input event and sends an input method
136 * event to the interested components
137 * using the input context provided by the system.
138 *
139 * @param event
140 */
141 public void dispatchEvent(AWTEvent event) {
142 if(event instanceof KeyEvent){
143 KeyEvent kEvent = (KeyEvent) event;
144 char ch = kEvent.getKeyChar();
145 int keyCode = kEvent.getKeyCode();
146 int modifiers = kEvent.getModifiers();
147 int id = kEvent.getID();
148 //process the CTRL+? events that do not generate key-typed events.
149 if((id == KeyEvent.KEY_PRESSED || id == KeyEvent.KEY_RELEASED) &&
150 (modifiers & KeyEvent.CTRL_MASK) > 0 &&
151 keyCode != KeyEvent.VK_CONTROL){
152 boolean shift = (modifiers & KeyEvent.SHIFT_MASK) > 0;
153 if(ch == KeyEvent.CHAR_UNDEFINED ||
154 Character.isISOControl(ch)){
155 if((int)'0' <= keyCode && keyCode <= (int)'9'){
156 if(!shift){
157 ch = (char)keyCode;
158 }else{
159 //shifted versions for the digit keys
160 switch((char)keyCode){
161 case '0':{
162 ch = ')';
163 break;
164 }
165 case '1':{
166 ch = '!';
167 break;
168 }
169 case '2':{
170 ch = '\"';
171 break;
172 }
173 case '3':{
174 ch = '\u00a3'; //pound symbol
175 break;
176 }
177 case '4':{
178 ch = '$';
179 break;
180 }
181 case '5':{
182 ch = '%';
183 break;
184 }
185 case '6':{
186 ch = '^';
187 break;
188 }
189 case '7':{
190 ch = '&';
191 break;
192 }
193 case '8':{
194 ch = '*';
195 break;
196 }
197 case '9':{
198 ch = '(';
199 break;
200 }
201 }//switch((char)keyCode)
202 }
203 } else if((int)'A' <= keyCode && keyCode <= (int)'Z'){
204 ch = (char)keyCode;
205 if(!shift){
206 ch = Character.toLowerCase(ch);
207 }
208 } else {
209 switch(keyCode){
210 case KeyEvent.VK_MINUS:{
211 ch = shift?'_':'-';
212 break;
213 }
214 case KeyEvent.VK_EQUALS:{
215 ch = shift?'+':'=';
216 break;
217 }
218 case KeyEvent.VK_OPEN_BRACKET:{
219 ch = shift?'{':'[';
220 break;
221 }
222 case KeyEvent.VK_CLOSE_BRACKET:{
223 ch = shift?'}':']';
224 break;
225 }
226 case KeyEvent.VK_SEMICOLON:{
227 ch = shift?':':';';
228 break;
229 }
230 case KeyEvent.VK_BACK_QUOTE:{
231 ch = shift?'@':'\'';
232 break;
233 }
234 case KeyEvent.VK_QUOTE:{
235 ch = shift?'~':'#';
236 break;
237 }
238 case KeyEvent.VK_BACK_SLASH:{
239 ch = shift?'|':'\\';
240 break;
241 }
242 case KeyEvent.VK_COMMA:{
243 ch = shift?'<':',';
244 break;
245 }
246 case KeyEvent.VK_STOP:{
247 ch = shift?'>':'.';
248 break;
249 }
250 case KeyEvent.VK_SLASH:{
251 ch = shift?'?':'/';
252 break;
253 }
254 }
255 }
256 }//if(ch = KeyEvent.CHAR_UNDEFINED)
257 //modify the event
258 if(id == KeyEvent.KEY_PRESSED) id = KeyEvent.KEY_TYPED;
259 }
260
261 //now send it to the virtual keyboard
262 ((KeyEvent)event).setKeyChar(ch);
263 if(keyboardMap != null) keyboardMap.addJob(event);
264
265 //now process it for input
266 if( id == KeyEvent.KEY_TYPED &&
267 ( ch == ' ' ||
268 !Character.isISOControl(ch)
269 )
270 )
271 {
272 //it's a key typed event
273 Key key = new Key(ch, modifiers);
274 Action action = currentState.getNext(key);
275 if(action == null){
276 //we can't go further, commit if in final state cancel otherwise
277 if(currentState.isFinal()){
278 myContext.dispatchInputMethodEvent(
279 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
280 (new AttributedString(composedText)).getIterator(),
281 composedText.length(), null, null);
282 }
283 //move to the initial state
284 composedText = "";
285 currentState = currentHandler.getInitialState();
286 action = currentState.getNext(key);
287 }
288 if(action ==null){
289 //undefined state, remain in initial state, cancel composed text
290 //send the key char
291 composedText = "";
292 } else {
293 //advance and compose new text
294 currentState = action.getNext();
295 composedText = action.getComposedText();
296 }
297 ((KeyEvent)event).consume();
298 //fire the event to the client
299 boolean commit = !currentState.hasNext();
300 myContext.dispatchInputMethodEvent(
301 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
302 (new AttributedString(composedText)).getIterator(),
303 commit?composedText.length():0, null,null);
304 if(commit) composedText = "";
305 if(keyboardMap != null) keyboardMap.update(currentHandler, currentState);
306 }
307 }// if
308 }// dispatchEvent(AWTEvent event)
309
310 /**
311 * Called by the system when the client window has changed size or position.
312 * This event is ignored by the input method.
313 *
314 * @param bounds
315 */
316 public void notifyClientWindowChange(Rectangle bounds) {
317 //we don't care about that as we don't display any composition windows
318 //do nothing
319 }
320
321 /**
322 * Activates this input method
323 *
324 */
325 public void activate() {
326 enabled = true;
327 if(currentLocale == null) setLocale(defaultLocale);
328 if(mapVisible){
329 if(keyboardMap == null) keyboardMap = new KeyboardMap(this,
330 currentHandler,
331 currentState);
332 keyboardMap.addJob("SHOW");
333 }
334 }// activate()
335
336 /**
337 * Deactivates this input method
338 *
339 * @param isTemporary
340 */
341 public void deactivate(boolean isTemporary) {
342 endComposition();
343 enabled = false;
344 // if(mapVisible) keyboardMap.addJob("HIDE");
345 }// deactivate(boolean isTemporary)
346
347 /**
348 * Hides all the windows displayed by the input method. Currently this only
349 * includes the virtual keyboard map.
350 *
351 */
352 public void hideWindows() {
353 if(mapVisible) keyboardMap.addJob("HIDE");
354 }
355
356 /**
357 * Called by the system when a client unregisters to this input method. This
358 * event is currently ignored by the input method.
359 *
360 */
361 public void removeNotify() {
362 //so what! :)
363 }
364
365 /**
366 * Ends the curent composition.
367 *
368 */
369 public void endComposition() {
370 //System.out.println("GateIM endComposition()!");
371 if(composedText.length() > 0 && currentState.isFinal()){
372 myContext.dispatchInputMethodEvent(
373 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
374 (new AttributedString(composedText)).getIterator(),
375 composedText.length(), null, null);
376 }
377 composedText = "";
378 }
379
380 /**
381 * Disposes this input method releasing all the memory.
382 *
383 */
384 public void dispose() {
385 endComposition();
386 if(keyboardMap != null){
387 keyboardMap.addJob("DIE");
388 keyboardMap = null;
389 }
390 currentLocale = null;
391 currentHandler = null;
392 currentState = null;
393 myContext = null;
394 supportedLocales.clear();
395 supportedLocales = null;
396 loadedLocales.clear();
397 loadedLocales = null;
398 }
399
400 /**
401 * Gives the clients a chance to control the bevaviour of this input method
402 * by returning a handle to itself.
403 *
404 * @return a reference to this input method
405 */
406 public Object getControlObject() {
407 return this;
408 }
409
410 /**
411 * Should the virtual keyboard map be visible?
412 *
413 * @param mapvis
414 */
415 public void setMapVisible(boolean mapvis) {
416 if(mapvis){
417 mapVisible = true;
418 if(keyboardMap == null) keyboardMap = new KeyboardMap(this,
419 currentHandler,
420 currentState);
421 keyboardMap.addJob("SHOW");
422 }else{
423 mapVisible = false;
424 if(keyboardMap != null) keyboardMap.addJob("HIDE");
425 }
426 }// setMapVisible(boolean mapvis)
427
428 /**
429 * Loads a new locale if it's not already loaded.
430 *
431 * @param locale
432 */
433 protected void loadLocale(Locale locale){
434 String fileName = (String)supportedLocales.get(locale);
435 if(fileName == null) throw new IllegalArgumentException(
436 "Unknown locale: " + locale);
437 currentHandler = (LocaleHandler)loadedLocales.get(locale);
438 if(currentHandler == null){
439 try {
440 currentHandler = new LocaleHandler(locale, fileName);
441 loadedLocales.put(locale, currentHandler);
442 currentState = currentHandler.getInitialState();
443 } catch(IOException ioe) {
444 throw new IllegalArgumentException("Cannot load locale: " + locale);
445 }
446 }// if
447 }// loadLocale(Locale locale)
448
449 /**
450 * Returns theinput context for this input method.
451 *
452 */
453 public InputMethodContext getContext(){
454 return myContext;
455 }
456
457 //--------- variables
458 /**
459 * The active locale
460 *
461 */
462 Locale currentLocale;
463 /**
464 * The default locale to be used when this method is loaded and no locale is
465 * specified.
466 *
467 */
468 Locale defaultLocale = new Locale("en", "", "ASCII");
469 /**
470 * The current locale handler.
471 *
472 */
473 LocaleHandler currentHandler;
474 /**
475 * The input context
476 *
477 */
478 InputMethodContext myContext;
479 //maps from Loacle to String (the file name)
480 /**
481 * The available locales (the locales for which a definition file exists).
482 *
483 */
484 Map supportedLocales;
485 //maps from Locale to LocaleHandler
486 /**
487 * The locales that have been loaded already. Maps from Loacle to
488 * {@link LocaleHandler}.
489 *
490 */
491 Map loadedLocales;
492 /**
493 * Is this inpuit method enabled?
494 *
495 */
496 boolean enabled;
497 /**
498 * The composed text;
499 *
500 */
501 String composedText = "";
502 /**
503 * The current state of the current LocaleHandler.
504 *
505 */
506 State currentState;
507 /**
508 * Not used
509 *
510 */
511 Map additionalKeymaps;
512 /**
513 * The current virtual keyboard map.
514 *
515 */
516 static KeyboardMap keyboardMap;
517 /**
518 * Should the keyboard map be visible?
519 *
520 */
521 boolean mapVisible = true;
522
523 /** The resource path to the input methods director
524 */
525 static private String imBase = "/guk/im/data/";
526
527 /** Sets the default path to be used when looking for input methods.
528 * This should be a resource path (a path inside the class path).
529 * By default the path is "guk/im/data/"
530 *
531 * @param path
532 */
533 static public void setIMBase(String path){
534 imBase = path;
535 }
536
537 /** Gets the path inside the classpath where the input methods should be found
538 */
539 public static String getIMBase(){return imBase;}
540
541
542 static private Font keyboardFont = new Font("Arial Unicode MS", Font.PLAIN, 12);
543 public static Font getKeyboardFont(){
544 return keyboardFont;
545 }
546 }// class GateIM implements InputMethod
547