001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Dmitry A. Durnev
019: * @version $Revision$
020: */package org.apache.harmony.awt.im;
021:
022: import java.awt.AWTEvent;
023: import java.awt.Component;
024: import java.awt.KeyboardFocusManager;
025: import java.awt.Rectangle;
026: import java.awt.Window;
027: import java.awt.event.FocusEvent;
028: import java.awt.event.InputMethodEvent;
029: import java.awt.event.KeyEvent;
030: import java.awt.font.TextHitInfo;
031: import java.awt.im.InputContext;
032: import java.awt.im.InputMethodRequests;
033: import java.awt.im.spi.InputMethod;
034: import java.awt.im.spi.InputMethodDescriptor;
035: import java.lang.Character.Subset;
036: import java.text.AttributedCharacterIterator;
037: import java.text.AttributedCharacterIterator.Attribute;
038: import java.util.HashMap;
039: import java.util.HashSet;
040: import java.util.Iterator;
041: import java.util.Locale;
042: import java.util.Map;
043: import java.util.Set;
044:
045: import javax.swing.JFrame;
046:
047: import org.apache.harmony.awt.wtk.NativeIM;
048:
049: /**
050: * Implementation of InputMethodContext
051: * interface, also provides all useful
052: * functionality of InputContext
053: *
054: */
055: public class InputMethodContext extends InputContext implements
056: java.awt.im.spi.InputMethodContext {
057:
058: private InputMethod inputMethod; // current IM
059: private Component client; // current "active" client component
060: private CompositionWindow composeWindow; // composition Window
061: private final Map<InputMethodDescriptor, InputMethod> imInstances; // Map<InputMethodDescriptor, InputMethod>
062: private final Map<Locale, InputMethod> localeIM; // Map<Locale, InputMethod> last user-selected IM for locale
063: private final Set<InputMethod> notifyIM; // set of IMs to notify of client window bounds changes
064: /**
065: * a flag indicating that IM should be notified of client window
066: * position/visibility changes as soon as it is activated(new client
067: * appears)
068: */
069: private boolean pendingClientNotify;
070: private Component nextComp; // component to gain focus after endComposition()
071: private final Set<Window> imWindows; // set of all IM windows created by this instance
072: private final NativeIM nativeIM;
073:
074: public InputMethodContext() {
075: notifyIM = new HashSet<InputMethod>();
076: imWindows = new HashSet<Window>();
077: imInstances = new HashMap<InputMethodDescriptor, InputMethod>();
078: localeIM = new HashMap<Locale, InputMethod>();
079: selectInputMethod(Locale.US); // not default?
080: nativeIM = (NativeIM) inputMethod;
081: }
082:
083: @Override
084: public void dispatchEvent(AWTEvent event) {
085: int id = event.getID();
086: if ((id >= FocusEvent.FOCUS_FIRST)
087: && (id <= FocusEvent.FOCUS_LAST)) {
088: dispatchFocusEvent((FocusEvent) event);
089: } else {
090: // handle special KEY_PRESSED
091: // event to show IM selection menu
092: if (id == KeyEvent.KEY_PRESSED) {
093: KeyEvent ke = (KeyEvent) event;
094: IMManager.selectIM(ke, this , IMManager.getWindow(ke
095: .getComponent()));
096: }
097: // dispatch all input events to the current IM:
098: if (inputMethod != null) {
099: inputMethod.dispatchEvent(event);
100: }
101: }
102: }
103:
104: private void dispatchFocusEvent(FocusEvent fe) {
105: switch (fe.getID()) {
106: case FocusEvent.FOCUS_LOST:
107: if (inputMethod != null) {
108: inputMethod.deactivate(fe.isTemporary());
109: }
110: break;
111: case FocusEvent.FOCUS_GAINED:
112:
113: Component comp = fe.getComponent();
114: if (imWindows.contains(comp)) {
115: // prevent activating when IM windows
116: // attached to this context gain focus
117: return;
118: }
119: InputMethodContext lastActive = IMManager
120: .getLastActiveIMC();
121: if ((lastActive != this ) && (lastActive != null)) {
122: lastActive.hideWindows();
123: }
124: if (inputMethod != null) {
125: activateIM(inputMethod);
126: if (!getCompositionWindow().isEmpty()) {
127: IMManager.showCompositionWindow(composeWindow);
128: }
129: if (client == comp) {
130: if (nextComp != null) {
131: // temporarily got focus to
132: // end composition
133: endComposition();
134:
135: // transfer focus to new client
136: client = nextComp;
137: nextComp = null;
138: client.requestFocusInWindow();
139: }
140: } else if ((client != null)
141: && getCompositionWindow().isVisible()) {
142: // temporarily return focus back
143: // to previous client to be able
144: // to end composition
145: nextComp = comp;
146: client.requestFocusInWindow();
147: } else {
148: client = comp;
149: }
150: }
151: if (pendingClientNotify) {
152: notifyClientWindowChange(IMManager.getWindow(comp)
153: .getBounds());
154: }
155: break;
156: }
157:
158: }
159:
160: private void activateIM(InputMethod im) {
161: Component c;
162:
163: im.activate();
164: if ((nativeIM != null) && (im != nativeIM)) {
165: // when Java IM is active
166: // native input method editor must be
167: // explicitly disabled
168: nativeIM.disableIME();
169: }
170: IMManager.setLastActiveIMC(this );
171:
172: c = getClient();
173: if (c != null) {
174: c.getInputMethodRequests();
175: }
176: }
177:
178: @SuppressWarnings("deprecation")
179: private void hideWindows() {
180: if (inputMethod != null) {
181: inputMethod.hideWindows();
182: }
183: if (composeWindow != null) {
184: composeWindow.hide();
185: }
186: }
187:
188: private void createCompositionWindow() {
189: composeWindow = new CompositionWindow(client);
190: }
191:
192: private CompositionWindow getCompositionWindow() {
193: if (composeWindow == null) {
194: createCompositionWindow();
195: }
196: composeWindow.setClient(client);
197: return composeWindow;
198: }
199:
200: /**
201: * Gets input method requests for the current client
202: * irrespective of input style.
203: * @return input method requests of composition window if
204: * client is passive,
205: * otherwise input method requests of client
206: */
207: private InputMethodRequests getIMRequests() {
208: InputMethodRequests imRequests = null;
209:
210: if (client != null) {
211: imRequests = client.getInputMethodRequests();
212: if (imRequests == null) {
213: imRequests = getCompositionWindow()
214: .getInputMethodRequests();
215: }
216: }
217:
218: return imRequests;
219: }
220:
221: /**
222: * Gets input method requests for the current client & input style.
223: * @return input method requests of composition window if
224: * input style is "below-the-spot"(or client is passive),
225: * otherwise client input method requests
226: */
227: private InputMethodRequests getStyleIMRequests() {
228: if (IMManager.belowTheSpot()) {
229: return getCompositionWindow().getInputMethodRequests();
230: }
231: return getIMRequests();
232: }
233:
234: @Override
235: public void dispose() {
236: if (inputMethod != null) {
237: closeIM(inputMethod);
238: inputMethod.dispose();
239: }
240: notifyIM.clear();
241: super .dispose();
242: }
243:
244: @Override
245: public void endComposition() {
246: if (inputMethod != null) {
247: inputMethod.endComposition();
248: }
249: super .endComposition();
250: }
251:
252: @Override
253: public Object getInputMethodControlObject() {
254: if (inputMethod != null) {
255: return inputMethod.getControlObject();
256: }
257: return super .getInputMethodControlObject();
258: }
259:
260: @Override
261: public Locale getLocale() {
262: if (inputMethod != null) {
263: return inputMethod.getLocale();
264: }
265: return super .getLocale();
266: }
267:
268: @Override
269: public boolean isCompositionEnabled() {
270: if (inputMethod != null) {
271: return inputMethod.isCompositionEnabled();
272: }
273: return super .isCompositionEnabled();
274: }
275:
276: @Override
277: public void reconvert() {
278: if (inputMethod != null) {
279: inputMethod.reconvert();
280: }
281: super .reconvert();
282: }
283:
284: @Override
285: public void removeNotify(Component client) {
286: if ((inputMethod != null) && (client == this .client)) {
287: inputMethod.removeNotify();
288: client = null;
289: // set flag indicating that IM should be notified
290: // as soon as it is activated(new client appears)
291: pendingClientNotify = true;
292: }
293:
294: super .removeNotify(client);
295: }
296:
297: @Override
298: public boolean selectInputMethod(Locale locale) {
299:
300: if ((inputMethod != null) && inputMethod.setLocale(locale)) {
301: return true;
302: }
303: // first
304: // take last user-selected IM for locale
305: InputMethod newIM = localeIM.get(locale);
306:
307: // if not found search through IM descriptors
308: // and take already created instance if exists
309: // or create, store new IM instance in descriptor->instance map
310: if (newIM == null) {
311: try {
312: newIM = getIMInstance(IMManager.getIMDescriptors()
313: .iterator(), locale);
314: } catch (Exception e) {
315: // ignore exceptions - just return false
316: }
317: }
318:
319: return switchToIM(locale, newIM);
320: }
321:
322: private boolean switchToIM(Locale locale, InputMethod newIM) {
323: if (newIM != null) {
324: closeIM(inputMethod);
325: client = KeyboardFocusManager
326: .getCurrentKeyboardFocusManager().getFocusOwner();
327: if (client != null) {
328: client.getInputMethodRequests();
329: }
330: initIM(newIM, locale);
331: inputMethod = newIM;
332:
333: return true;
334: }
335: return false;
336: }
337:
338: /**
339: * Is called when IM is selected from UI
340: */
341: void selectIM(InputMethodDescriptor imd, Locale locale) {
342: try {
343: switchToIM(locale, getIMInstance(imd));
344: } catch (Exception e) {
345: e.printStackTrace();
346: }
347: }
348:
349: /**
350: * Gets input method instance for the given
351: * locale from the given list of descriptors
352: * @param descriptors iterator of the list of IM descriptors
353: * @param locale the locale to be supported by the IM
354: * @return input method instance
355: * @throws Exception
356: */
357: private InputMethod getIMInstance(
358: Iterator<InputMethodDescriptor> descriptors, Locale locale)
359: throws Exception {
360: while (descriptors.hasNext()) {
361: InputMethodDescriptor desc = descriptors.next();
362: Locale[] locs = desc.getAvailableLocales();
363: for (Locale element : locs) {
364: if (locale.equals(element)) {
365: return getIMInstance(desc);
366: }
367: }
368: }
369: return null;
370: }
371:
372: private InputMethod getIMInstance(InputMethodDescriptor imd)
373: throws Exception {
374: InputMethod im = imInstances.get(imd);
375: if (im == null) {
376: im = imd.createInputMethod();
377: im.setInputMethodContext(this );
378: imInstances.put(imd, im);
379: }
380: return im;
381: }
382:
383: private void initIM(InputMethod im, Locale locale) {
384: if (im == null) {
385: return;
386: }
387: im.setLocale(locale);
388: im.setCharacterSubsets(null);
389: activateIM(im);
390: try {
391: im.setCompositionEnabled(inputMethod != null ? inputMethod
392: .isCompositionEnabled() : true);
393: } catch (UnsupportedOperationException uoe) {
394:
395: }
396:
397: }
398:
399: private void closeIM(InputMethod im) {
400: if (im == null) {
401: return;
402: }
403: if (im.isCompositionEnabled()) {
404: im.endComposition();
405: }
406:
407: im.deactivate(true);
408: im.hideWindows();
409:
410: }
411:
412: @Override
413: public void setCharacterSubsets(Subset[] subsets) {
414: if (inputMethod != null) {
415: inputMethod.setCharacterSubsets(subsets);
416: }
417: super .setCharacterSubsets(subsets);
418: }
419:
420: @Override
421: public void setCompositionEnabled(boolean enable) {
422: if (inputMethod != null) {
423: inputMethod.setCompositionEnabled(enable);
424: }
425: super .setCompositionEnabled(enable);
426: }
427:
428: public JFrame createInputMethodJFrame(String title,
429: boolean attachToInputContext) {
430: JFrame jf = new IMJFrame(title, attachToInputContext ? this
431: : null);
432: imWindows.add(jf);
433: return jf;
434: }
435:
436: public Window createInputMethodWindow(String title,
437: boolean attachToInputContext) {
438: Window w = new IMWindow(title, attachToInputContext ? this
439: : null);
440: imWindows.add(w);
441: return w;
442: }
443:
444: @SuppressWarnings("deprecation")
445: public void dispatchInputMethodEvent(int id,
446: AttributedCharacterIterator text,
447: int committedCharacterCount, TextHitInfo caret,
448: TextHitInfo visiblePosition) {
449: if (client == null) {
450: return;
451: }
452: InputMethodEvent ime = new InputMethodEvent(client, id, text,
453: committedCharacterCount, caret, visiblePosition);
454:
455: if ((client.getInputMethodRequests() != null)
456: && !IMManager.belowTheSpot()) {
457:
458: client.dispatchEvent(ime);
459: } else {
460:
461: // show/hide composition window if necessary
462: if (committedCharacterCount < text.getEndIndex()) {
463: IMManager.showCompositionWindow(getCompositionWindow());
464: } else {
465: getCompositionWindow().hide();
466: }
467: composeWindow.getActiveClient().dispatchEvent(ime);
468: }
469:
470: }
471:
472: public void enableClientWindowNotification(InputMethod inputMethod,
473: boolean enable) {
474: if (enable) {
475: notifyIM.add(inputMethod);
476: if (client != null) {
477: notifyClientWindowChange(IMManager.getWindow(client)
478: .getBounds());
479: } else {
480: pendingClientNotify = true;
481: }
482: } else {
483: notifyIM.remove(inputMethod);
484: }
485:
486: }
487:
488: public AttributedCharacterIterator cancelLatestCommittedText(
489: Attribute[] attributes) {
490: return getIMRequests().cancelLatestCommittedText(attributes);
491: }
492:
493: public AttributedCharacterIterator getCommittedText(int beginIndex,
494: int endIndex, Attribute[] attributes) {
495: return getIMRequests().getCommittedText(beginIndex, endIndex,
496: attributes);
497: }
498:
499: public int getCommittedTextLength() {
500: return getIMRequests().getCommittedTextLength();
501: }
502:
503: public int getInsertPositionOffset() {
504: return getIMRequests().getInsertPositionOffset();
505: }
506:
507: public TextHitInfo getLocationOffset(int x, int y) {
508: InputMethodRequests imr = getStyleIMRequests();
509: if (imr != null) {
510: return imr.getLocationOffset(x, y);
511: }
512: return null;
513: }
514:
515: public AttributedCharacterIterator getSelectedText(
516: Attribute[] attributes) {
517: return getIMRequests().getSelectedText(attributes);
518: }
519:
520: public Rectangle getTextLocation(TextHitInfo offset) {
521: return getStyleIMRequests().getTextLocation(offset);
522: }
523:
524: /**
525: * To be called by AWT when client Window's bounds/visibility/state
526: * change
527: */
528: public void notifyClientWindowChange(Rectangle bounds) {
529: if (notifyIM.contains(inputMethod)) {
530: inputMethod.notifyClientWindowChange(bounds);
531: }
532: pendingClientNotify = false;
533: }
534:
535: public final InputMethod getInputMethod() {
536: return inputMethod;
537: }
538:
539: public final Component getClient() {
540: return client;
541: }
542:
543: public final NativeIM getNativeIM() {
544: return nativeIM;
545: }
546: }
|