0001: /*
0002: * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package sun.awt;
0027:
0028: import java.util.Collections;
0029: import java.util.Locale;
0030: import java.util.Map;
0031: import java.util.HashMap;
0032: import java.awt.AWTEvent;
0033: import java.awt.AWTException;
0034: import java.awt.Component;
0035: import java.awt.Container;
0036: import java.awt.EventQueue;
0037: import java.awt.Window;
0038: import java.awt.im.InputContext;
0039: import java.awt.im.InputMethodHighlight;
0040: import java.awt.im.spi.InputMethodContext;
0041: import sun.awt.im.InputMethodAdapter;
0042: import java.awt.event.InputEvent;
0043: import java.awt.event.KeyEvent;
0044: import java.awt.event.MouseEvent;
0045: import java.awt.event.FocusEvent;
0046: import java.awt.event.ComponentEvent;
0047: import java.awt.event.WindowEvent;
0048: import java.awt.event.InputMethodEvent;
0049: import java.awt.font.TextAttribute;
0050: import java.awt.font.TextHitInfo;
0051: import java.awt.peer.ComponentPeer;
0052: import java.lang.Character.Subset;
0053: import java.text.AttributedString;
0054: import java.text.AttributedCharacterIterator;
0055:
0056: import java.io.File;
0057: import java.io.FileReader;
0058: import java.io.BufferedReader;
0059: import java.io.IOException;
0060: import java.util.logging.*;
0061: import java.util.StringTokenizer;
0062: import java.util.regex.Pattern;
0063:
0064: /**
0065: * Input Method Adapter for XIM
0066: *
0067: * @author JavaSoft International
0068: */
0069: public abstract class X11InputMethod extends InputMethodAdapter {
0070: private static final Logger log = Logger
0071: .getLogger("sun.awt.X11InputMethod");
0072: /*
0073: * The following XIM* values must be the same as those defined in
0074: * Xlib.h
0075: */
0076: private static final int XIMReverse = (1 << 0);
0077: private static final int XIMUnderline = (1 << 1);
0078: private static final int XIMHighlight = (1 << 2);
0079: private static final int XIMPrimary = (1 << 5);
0080: private static final int XIMSecondary = (1 << 6);
0081: private static final int XIMTertiary = (1 << 7);
0082:
0083: /*
0084: * visible position values
0085: */
0086: private static final int XIMVisibleToForward = (1 << 8);
0087: private static final int XIMVisibleToBackward = (1 << 9);
0088: private static final int XIMVisibleCenter = (1 << 10);
0089: private static final int XIMVisibleMask = (XIMVisibleToForward
0090: | XIMVisibleToBackward | XIMVisibleCenter);
0091:
0092: private Locale locale;
0093: private static boolean isXIMOpened = false;
0094: protected Container clientComponentWindow = null;
0095: private Component awtFocussedComponent = null;
0096: private Component lastXICFocussedComponent = null;
0097: private boolean isLastXICActive = false;
0098: private boolean isActive = false;
0099: private boolean isActiveClient = false;
0100: private static Map[] highlightStyles;
0101: private boolean disposed = false;
0102:
0103: //reset the XIC if necessary
0104: private boolean needResetXIC = false;
0105: private Component needResetXICClient = null;
0106:
0107: // The use of compositionEnableSupported is to reduce unnecessary
0108: // native calls if set/isCompositionEnabled
0109: // throws UnsupportedOperationException.
0110: // It is set to false if that exception is thrown first time
0111: // either of the two methods are called.
0112: private boolean compositionEnableSupported = true;
0113: // The savedCompositionState indicates the composition mode when
0114: // endComposition or setCompositionEnabled is called. It doesn't always
0115: // reflect the actual composition state because it doesn't get updated
0116: // when the user changes the composition state through direct interaction
0117: // with the input method. It is used to save the composition mode when
0118: // focus is traversed across different client components sharing the
0119: // same java input context. Also if set/isCompositionEnabled are not
0120: // supported, it remains false.
0121: private boolean savedCompositionState = false;
0122:
0123: // variables to keep track of preedit context.
0124: // these variables need to be accessed within AWT_LOCK/UNLOCK
0125: private String committedText = null;
0126: private StringBuffer composedText = null;
0127: private IntBuffer rawFeedbacks;
0128:
0129: // private data (X11InputMethodData structure defined in
0130: // awt_InputMethod.c) for native methods
0131: // this structure needs to be accessed within AWT_LOCK/UNLOCK
0132: transient private long pData = 0; // accessed by native
0133:
0134: // Initialize highlight mapping table
0135: static {
0136: Map styles[] = new Map[4];
0137: HashMap map;
0138:
0139: // UNSELECTED_RAW_TEXT_HIGHLIGHT
0140: map = new HashMap(1);
0141: map.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
0142: styles[0] = Collections.unmodifiableMap(map);
0143:
0144: // SELECTED_RAW_TEXT_HIGHLIGHT
0145: map = new HashMap(1);
0146: map
0147: .put(TextAttribute.SWAP_COLORS,
0148: TextAttribute.SWAP_COLORS_ON);
0149: styles[1] = Collections.unmodifiableMap(map);
0150:
0151: // UNSELECTED_CONVERTED_TEXT_HIGHLIGHT
0152: map = new HashMap(1);
0153: map.put(TextAttribute.INPUT_METHOD_UNDERLINE,
0154: TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
0155: styles[2] = Collections.unmodifiableMap(map);
0156:
0157: // SELECTED_CONVERTED_TEXT_HIGHLIGHT
0158: map = new HashMap(1);
0159: map
0160: .put(TextAttribute.SWAP_COLORS,
0161: TextAttribute.SWAP_COLORS_ON);
0162: styles[3] = Collections.unmodifiableMap(map);
0163:
0164: highlightStyles = styles;
0165: }
0166:
0167: static {
0168: initIDs();
0169: }
0170:
0171: /**
0172: * Initialize JNI field and method IDs for fields that may be
0173: accessed from C.
0174: */
0175: private static native void initIDs();
0176:
0177: /**
0178: * Constructs an X11InputMethod instance. It initializes the XIM
0179: * environment if it's not done yet.
0180: *
0181: * @exception AWTException if XOpenIM() failed.
0182: */
0183: public X11InputMethod() throws AWTException {
0184: // supports only the locale in which the VM is started
0185: locale = X11InputMethodDescriptor.getSupportedLocale();
0186: if (initXIM() == false) {
0187: throw new AWTException("Cannot open X Input Method");
0188: }
0189: }
0190:
0191: protected void finalize() throws Throwable {
0192: dispose();
0193: super .finalize();
0194: }
0195:
0196: /**
0197: * Invokes openIM() that invokes XOpenIM() if it's not opened yet.
0198: * @return true if openXIM() is successful or it's already been opened.
0199: */
0200: private synchronized boolean initXIM() {
0201: if (isXIMOpened == false)
0202: isXIMOpened = openXIM();
0203: return isXIMOpened;
0204: }
0205:
0206: protected abstract boolean openXIM();
0207:
0208: protected boolean isDisposed() {
0209: return disposed;
0210: }
0211:
0212: protected abstract void setXICFocus(ComponentPeer peer,
0213: boolean value, boolean active);
0214:
0215: /**
0216: * Does nothing - this adapter doesn't use the input method context.
0217: *
0218: * @see java.awt.im.spi.InputMethod#setInputMethodContext
0219: */
0220: public void setInputMethodContext(InputMethodContext context) {
0221: }
0222:
0223: /**
0224: * Set locale to input. If input method doesn't support specified locale,
0225: * false will be returned and its behavior is not changed.
0226: *
0227: * @param lang locale to input
0228: * @return the true is returned when specified locale is supported.
0229: */
0230: public boolean setLocale(Locale lang) {
0231: if (lang.equals(locale)) {
0232: return true;
0233: }
0234: // special compatibility rule for Japanese and Korean
0235: if (locale.equals(Locale.JAPAN) && lang.equals(Locale.JAPANESE)
0236: || locale.equals(Locale.KOREA)
0237: && lang.equals(Locale.KOREAN)) {
0238: return true;
0239: }
0240: return false;
0241: }
0242:
0243: /**
0244: * Returns current input locale.
0245: */
0246: public Locale getLocale() {
0247: return locale;
0248: }
0249:
0250: /**
0251: * Does nothing - XIM doesn't let you specify which characters you expect.
0252: *
0253: * @see java.awt.im.spi.InputMethod#setCharacterSubsets
0254: */
0255: public void setCharacterSubsets(Subset[] subsets) {
0256: }
0257:
0258: /**
0259: * Dispatch event to input method. InputContext dispatch event with this
0260: * method. Input method set consume flag if event is consumed in
0261: * input method.
0262: *
0263: * @param e event
0264: */
0265: public void dispatchEvent(AWTEvent e) {
0266: }
0267:
0268: protected final void resetXICifneeded() {
0269: /* needResetXIC is used to indicate whether to call
0270: resetXIC on the active client. resetXIC will always be
0271: called on the passive client when endComposition is called.
0272: */
0273: if (needResetXIC && haveActiveClient()
0274: && getClientComponent() != needResetXICClient) {
0275: resetXIC();
0276:
0277: // needs to reset the last xic focussed component.
0278: lastXICFocussedComponent = null;
0279: isLastXICActive = false;
0280:
0281: needResetXICClient = null;
0282: needResetXIC = false;
0283: }
0284: }
0285:
0286: /**
0287: * Reset the composition state to the current composition state.
0288: */
0289: private void resetCompositionState() {
0290: if (compositionEnableSupported) {
0291: try {
0292: /* Restore the composition mode to the last saved composition
0293: mode. */
0294: setCompositionEnabled(savedCompositionState);
0295: } catch (UnsupportedOperationException e) {
0296: compositionEnableSupported = false;
0297: }
0298: }
0299: }
0300:
0301: /**
0302: * Query and then return the current composition state.
0303: * @returns the composition state if isCompositionEnabled call
0304: * is successful. Otherwise, it returns false.
0305: */
0306: private boolean getCompositionState() {
0307: boolean compositionState = false;
0308: if (compositionEnableSupported) {
0309: try {
0310: compositionState = isCompositionEnabled();
0311: } catch (UnsupportedOperationException e) {
0312: compositionEnableSupported = false;
0313: }
0314: }
0315: return compositionState;
0316: }
0317:
0318: /**
0319: * Activate input method.
0320: */
0321: public synchronized void activate() {
0322: clientComponentWindow = getClientComponentWindow();
0323: if (clientComponentWindow == null)
0324: return;
0325:
0326: if (lastXICFocussedComponent != null) {
0327: if (log.isLoggable(Level.FINE))
0328: log.log(Level.FINE, "XICFocused {0}, AWTFocused {1}",
0329: new Object[] { lastXICFocussedComponent,
0330: awtFocussedComponent });
0331: }
0332:
0333: if (pData == 0) {
0334: if (!createXIC()) {
0335: return;
0336: }
0337: disposed = false;
0338: }
0339:
0340: /* reset input context if necessary and set the XIC focus
0341: */
0342: resetXICifneeded();
0343: ComponentPeer lastXICFocussedComponentPeer = null;
0344: ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent);
0345:
0346: if (lastXICFocussedComponent != null) {
0347: lastXICFocussedComponentPeer = getPeer(lastXICFocussedComponent);
0348: }
0349:
0350: /* If the last XIC focussed component has a different peer as the
0351: current focussed component, change the XIC focus to the newly
0352: focussed component.
0353: */
0354: if (lastXICFocussedComponentPeer != awtFocussedComponentPeer
0355: || isLastXICActive != haveActiveClient()) {
0356: if (lastXICFocussedComponentPeer != null) {
0357: setXICFocus(lastXICFocussedComponentPeer, false,
0358: isLastXICActive);
0359: }
0360: if (awtFocussedComponentPeer != null) {
0361: setXICFocus(awtFocussedComponentPeer, true,
0362: haveActiveClient());
0363: }
0364: lastXICFocussedComponent = awtFocussedComponent;
0365: isLastXICActive = haveActiveClient();
0366: }
0367: resetCompositionState();
0368: isActive = true;
0369: }
0370:
0371: protected abstract boolean createXIC();
0372:
0373: /**
0374: * Deactivate input method.
0375: */
0376: public synchronized void deactivate(boolean isTemporary) {
0377: boolean isAc = haveActiveClient();
0378: /* Usually as the client component, let's call it component A,
0379: loses the focus, this method is called. Then when another client
0380: component, let's call it component B, gets the focus, activate is first called on
0381: the previous focused compoent which is A, then endComposition is called on A,
0382: deactivate is called on A again. And finally activate is called on the newly
0383: focused component B. Here is the call sequence.
0384:
0385: A loses focus B gains focus
0386: -------------> deactivate A -------------> activate A -> endComposition A ->
0387: deactivate A -> activate B ----....
0388:
0389: So in order to carry the composition mode across the components sharing the same
0390: input context, we save it when deactivate is called so that when activate is
0391: called, it can be restored correctly till activate is called on the newly focused
0392: component. (See also sun/awt/im/InputContext and bug 6184471).
0393: Last note, getCompositionState should be called before setXICFocus since
0394: setXICFocus here sets the XIC to 0.
0395: */
0396: savedCompositionState = getCompositionState();
0397:
0398: if (isTemporary) {
0399: //turn the status window off...
0400: turnoffStatusWindow();
0401: }
0402:
0403: /* Delay resetting the XIC focus until activate is called and the newly
0404: focussed component has a different peer as the last focussed component.
0405: */
0406: lastXICFocussedComponent = awtFocussedComponent;
0407: isLastXICActive = isAc;
0408: isActive = false;
0409: }
0410:
0411: /**
0412: * Explicitly disable the native IME. Native IME is not disabled when
0413: * deactivate is called.
0414: */
0415: public void disableInputMethod() {
0416: if (lastXICFocussedComponent != null) {
0417: setXICFocus(getPeer(lastXICFocussedComponent), false,
0418: isLastXICActive);
0419: lastXICFocussedComponent = null;
0420: isLastXICActive = false;
0421: }
0422: }
0423:
0424: // implements java.awt.im.spi.InputMethod.hideWindows
0425: public void hideWindows() {
0426: // ??? need real implementation
0427: }
0428:
0429: /**
0430: * @see java.awt.Toolkit#mapInputMethodHighlight
0431: */
0432: public static Map mapInputMethodHighlight(
0433: InputMethodHighlight highlight) {
0434: int index;
0435: int state = highlight.getState();
0436: if (state == InputMethodHighlight.RAW_TEXT) {
0437: index = 0;
0438: } else if (state == InputMethodHighlight.CONVERTED_TEXT) {
0439: index = 2;
0440: } else {
0441: return null;
0442: }
0443: if (highlight.isSelected()) {
0444: index += 1;
0445: }
0446: return highlightStyles[index];
0447: }
0448:
0449: /**
0450: * @see sun.awt.im.InputMethodAdapter#setAWTFocussedComponent
0451: */
0452: protected void setAWTFocussedComponent(Component component) {
0453: if (component == null) {
0454: return;
0455: }
0456: if (isActive) {
0457: // deactivate/activate are being suppressed during a focus change -
0458: // this may happen when an input method window is made visible
0459: boolean ac = haveActiveClient();
0460: setXICFocus(getPeer(awtFocussedComponent), false, ac);
0461: setXICFocus(getPeer(component), true, ac);
0462: }
0463: awtFocussedComponent = component;
0464: }
0465:
0466: /**
0467: * @see sun.awt.im.InputMethodAdapter#stopListening
0468: */
0469: protected void stopListening() {
0470: // It is desirable to disable XIM by calling XSetICValues with
0471: // XNPreeditState == XIMPreeditDisable. But Solaris 2.6 and
0472: // Solaris 7 do not implement this correctly without a patch,
0473: // so just call resetXIC here. Prior endComposition call commits
0474: // the existing composed text.
0475: endComposition();
0476: // disable the native input method so that the other input
0477: // method could get the input focus.
0478: disableInputMethod();
0479: if (needResetXIC) {
0480: resetXIC();
0481: needResetXICClient = null;
0482: needResetXIC = false;
0483: }
0484: }
0485:
0486: /**
0487: * Returns the Window instance in which the client component is
0488: * contained. If not found, null is returned. (IS THIS POSSIBLE?)
0489: */
0490: // NOTE: This method may be called by privileged threads.
0491: // DO NOT INVOKE CLIENT CODE ON THIS THREAD!
0492: private Window getClientComponentWindow() {
0493: Component client = getClientComponent();
0494: Container container;
0495:
0496: if (client instanceof Container) {
0497: container = (Container) client;
0498: } else {
0499: container = getParent(client);
0500: }
0501:
0502: while (container != null
0503: && !(container instanceof java.awt.Window)) {
0504: container = getParent(container);
0505: }
0506: return (Window) container;
0507: }
0508:
0509: protected abstract Container getParent(Component client);
0510:
0511: /**
0512: * Returns peer of the given client component. If the given client component
0513: * doesn't have peer, peer of the native container of the client is returned.
0514: */
0515: protected abstract ComponentPeer getPeer(Component client);
0516:
0517: /**
0518: * Used to protect preedit data
0519: */
0520: protected abstract void awtLock();
0521:
0522: protected abstract void awtUnlock();
0523:
0524: /**
0525: * Creates an input method event from the arguments given
0526: * and posts it on the AWT event queue. For arguments,
0527: * see InputMethodEvent. Called by input method.
0528: *
0529: * @see java.awt.event.InputMethodEvent#InputMethodEvent
0530: */
0531: private void postInputMethodEvent(int id,
0532: AttributedCharacterIterator text,
0533: int committedCharacterCount, TextHitInfo caret,
0534: TextHitInfo visiblePosition, long when) {
0535: Component source = getClientComponent();
0536: if (source != null) {
0537: InputMethodEvent event = new InputMethodEvent(source, id,
0538: when, text, committedCharacterCount, caret,
0539: visiblePosition);
0540: SunToolkit.postEvent(SunToolkit.targetToAppContext(source),
0541: (AWTEvent) event);
0542: }
0543: }
0544:
0545: private void postInputMethodEvent(int id,
0546: AttributedCharacterIterator text,
0547: int committedCharacterCount, TextHitInfo caret,
0548: TextHitInfo visiblePosition) {
0549: postInputMethodEvent(id, text, committedCharacterCount, caret,
0550: visiblePosition, EventQueue.getMostRecentEventTime());
0551: }
0552:
0553: /**
0554: * Dispatches committed text from XIM to the awt event queue. This
0555: * method is invoked from the event handler in canvas.c in the
0556: * AWT Toolkit thread context and thus inside the AWT Lock.
0557: * @param str committed text
0558: * @param long when
0559: */
0560: // NOTE: This method may be called by privileged threads.
0561: // This functionality is implemented in a package-private method
0562: // to insure that it cannot be overridden by client subclasses.
0563: // DO NOT INVOKE CLIENT CODE ON THIS THREAD!
0564: void dispatchCommittedText(String str, long when) {
0565: if (str == null)
0566: return;
0567:
0568: if (composedText == null) {
0569: AttributedString attrstr = new AttributedString(str);
0570: postInputMethodEvent(
0571: InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, attrstr
0572: .getIterator(), str.length(), null, null,
0573: when);
0574: } else {
0575: // if there is composed text, wait until the preedit
0576: // callback is invoked.
0577: committedText = str;
0578: }
0579: }
0580:
0581: private void dispatchCommittedText(String str) {
0582: dispatchCommittedText(str, EventQueue.getMostRecentEventTime());
0583: }
0584:
0585: /**
0586: * Updates composed text with XIM preedit information and
0587: * posts composed text to the awt event queue. The args of
0588: * this method correspond to the XIM preedit callback
0589: * information. The XIM highlight attributes are translated via
0590: * fixed mapping (i.e., independent from any underlying input
0591: * method engine). This method is invoked in the AWT Toolkit
0592: * (X event loop) thread context and thus inside the AWT Lock.
0593: */
0594: // NOTE: This method may be called by privileged threads.
0595: // This functionality is implemented in a package-private method
0596: // to insure that it cannot be overridden by client subclasses.
0597: // DO NOT INVOKE CLIENT CODE ON THIS THREAD!
0598: void dispatchComposedText(String chgText, int chgStyles[],
0599: int chgOffset, int chgLength, int caretPosition, long when) {
0600: if (disposed) {
0601: return;
0602: }
0603:
0604: //Workaround for deadlock bug on solaris2.6_zh bug#4170760
0605: if (chgText == null && chgStyles == null && chgOffset == 0
0606: && chgLength == 0 && caretPosition == 0
0607: && composedText == null && committedText == null)
0608: return;
0609:
0610: if (composedText == null) {
0611: // TODO: avoid reallocation of those buffers
0612: composedText = new StringBuffer(INITIAL_SIZE);
0613: rawFeedbacks = new IntBuffer(INITIAL_SIZE);
0614: }
0615: if (chgLength > 0) {
0616: if (chgText == null && chgStyles != null) {
0617: rawFeedbacks.replace(chgOffset, chgStyles);
0618: } else {
0619: if (chgLength == composedText.length()) {
0620: // optimization for the special case to replace the
0621: // entire previous text
0622: composedText = new StringBuffer(INITIAL_SIZE);
0623: rawFeedbacks = new IntBuffer(INITIAL_SIZE);
0624: } else {
0625: if (composedText.length() > 0) {
0626: if (chgOffset + chgLength < composedText
0627: .length()) {
0628: String text;
0629: text = composedText.toString().substring(
0630: chgOffset + chgLength,
0631: composedText.length());
0632: composedText.setLength(chgOffset);
0633: composedText.append(text);
0634: } else {
0635: // in case to remove substring from chgOffset
0636: // to the end
0637: composedText.setLength(chgOffset);
0638: }
0639: rawFeedbacks.remove(chgOffset, chgLength);
0640: }
0641: }
0642: }
0643: }
0644: if (chgText != null) {
0645: composedText.insert(chgOffset, chgText);
0646: if (chgStyles != null)
0647: rawFeedbacks.insert(chgOffset, chgStyles);
0648: }
0649:
0650: if (composedText.length() == 0) {
0651: composedText = null;
0652: rawFeedbacks = null;
0653:
0654: // if there is any outstanding committed text stored by
0655: // dispatchCommittedText(), it has to be sent to the
0656: // client component.
0657: if (committedText != null) {
0658: dispatchCommittedText(committedText, when);
0659: committedText = null;
0660: return;
0661: }
0662:
0663: // otherwise, send null text to delete client's composed
0664: // text.
0665: postInputMethodEvent(
0666: InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, null,
0667: 0, null, null, when);
0668:
0669: return;
0670: }
0671:
0672: // Now sending the composed text to the client
0673: int composedOffset;
0674: AttributedString inputText;
0675:
0676: // if there is any partially committed text, concatenate it to
0677: // the composed text.
0678: if (committedText != null) {
0679: composedOffset = committedText.length();
0680: inputText = new AttributedString(committedText
0681: + composedText);
0682: committedText = null;
0683: } else {
0684: composedOffset = 0;
0685: inputText = new AttributedString(composedText.toString());
0686: }
0687:
0688: int currentFeedback;
0689: int nextFeedback;
0690: int startOffset = 0;
0691: int currentOffset;
0692: int visiblePosition = 0;
0693: TextHitInfo visiblePositionInfo = null;
0694:
0695: rawFeedbacks.rewind();
0696: currentFeedback = rawFeedbacks.getNext();
0697: rawFeedbacks.unget();
0698: while ((nextFeedback = rawFeedbacks.getNext()) != -1) {
0699: if (visiblePosition == 0) {
0700: visiblePosition = nextFeedback & XIMVisibleMask;
0701: if (visiblePosition != 0) {
0702: int index = rawFeedbacks.getOffset() - 1;
0703:
0704: if (visiblePosition == XIMVisibleToBackward)
0705: visiblePositionInfo = TextHitInfo
0706: .leading(index);
0707: else
0708: visiblePositionInfo = TextHitInfo
0709: .trailing(index);
0710: }
0711: }
0712: nextFeedback &= ~XIMVisibleMask;
0713: if (currentFeedback != nextFeedback) {
0714: rawFeedbacks.unget();
0715: currentOffset = rawFeedbacks.getOffset();
0716: inputText
0717: .addAttribute(
0718: TextAttribute.INPUT_METHOD_HIGHLIGHT,
0719: convertVisualFeedbackToHighlight(currentFeedback),
0720: composedOffset + startOffset,
0721: composedOffset + currentOffset);
0722: startOffset = currentOffset;
0723: currentFeedback = nextFeedback;
0724: }
0725: }
0726: currentOffset = rawFeedbacks.getOffset();
0727: if (currentOffset >= 0) {
0728: inputText.addAttribute(
0729: TextAttribute.INPUT_METHOD_HIGHLIGHT,
0730: convertVisualFeedbackToHighlight(currentFeedback),
0731: composedOffset + startOffset, composedOffset
0732: + currentOffset);
0733: }
0734:
0735: postInputMethodEvent(
0736: InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, inputText
0737: .getIterator(), composedOffset, TextHitInfo
0738: .leading(caretPosition), visiblePositionInfo,
0739: when);
0740: }
0741:
0742: /**
0743: * Flushes composed and committed text held in this context.
0744: * This method is invoked in the AWT Toolkit (X event loop) thread context
0745: * and thus inside the AWT Lock.
0746: */
0747: // NOTE: This method may be called by privileged threads.
0748: // This functionality is implemented in a package-private method
0749: // to insure that it cannot be overridden by client subclasses.
0750: // DO NOT INVOKE CLIENT CODE ON THIS THREAD!
0751: void flushText() {
0752: String flush = (committedText != null ? committedText : "");
0753: if (composedText != null) {
0754: flush += composedText.toString();
0755: }
0756:
0757: if (!flush.equals("")) {
0758: AttributedString attrstr = new AttributedString(flush);
0759: postInputMethodEvent(
0760: InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, attrstr
0761: .getIterator(), flush.length(), null, null,
0762: EventQueue.getMostRecentEventTime());
0763: composedText = null;
0764: committedText = null;
0765: }
0766: }
0767:
0768: /*
0769: * Subclasses should override disposeImpl() instead of dispose(). Client
0770: * code should always invoke dispose(), never disposeImpl().
0771: */
0772: protected synchronized void disposeImpl() {
0773: disposeXIC();
0774: awtLock();
0775: composedText = null;
0776: committedText = null;
0777: rawFeedbacks = null;
0778: awtUnlock();
0779: awtFocussedComponent = null;
0780: lastXICFocussedComponent = null;
0781: }
0782:
0783: /**
0784: * Frees all X Window resources associated with this object.
0785: *
0786: * @see java.awt.im.spi.InputMethod#dispose
0787: */
0788: public final void dispose() {
0789: boolean call_disposeImpl = false;
0790:
0791: if (!disposed) {
0792: synchronized (this ) {
0793: if (!disposed) {
0794: disposed = call_disposeImpl = true;
0795: }
0796: }
0797: }
0798:
0799: if (call_disposeImpl) {
0800: disposeImpl();
0801: }
0802: }
0803:
0804: /**
0805: * Returns null.
0806: *
0807: * @see java.awt.im.spi.InputMethod#getControlObject
0808: */
0809: public Object getControlObject() {
0810: return null;
0811: }
0812:
0813: /**
0814: * @see java.awt.im.spi.InputMethod#removeNotify
0815: */
0816: public synchronized void removeNotify() {
0817: dispose();
0818: }
0819:
0820: /**
0821: * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean)
0822: */
0823: public void setCompositionEnabled(boolean enable) {
0824: /* If the composition state is successfully changed, set
0825: the savedCompositionState to 'enable'. Otherwise, simply
0826: return.
0827: setCompositionEnabledNative may throw UnsupportedOperationException.
0828: Don't try to catch it since the method may be called by clients.
0829: Use package private mthod 'resetCompositionState' if you want the
0830: exception to be caught.
0831: */
0832: if (setCompositionEnabledNative(enable)) {
0833: savedCompositionState = enable;
0834: }
0835: }
0836:
0837: /**
0838: * @see java.awt.im.spi.InputMethod#isCompositionEnabled
0839: */
0840: public boolean isCompositionEnabled() {
0841: /* isCompositionEnabledNative may throw UnsupportedOperationException.
0842: Don't try to catch it since this method may be called by clients.
0843: Use package private method 'getCompositionState' if you want the
0844: exception to be caught.
0845: */
0846: return isCompositionEnabledNative();
0847: }
0848:
0849: /**
0850: * Ends any input composition that may currently be going on in this
0851: * context. Depending on the platform and possibly user preferences,
0852: * this may commit or delete uncommitted text. Any changes to the text
0853: * are communicated to the active component using an input method event.
0854: *
0855: * <p>
0856: * A text editing component may call this in a variety of situations,
0857: * for example, when the user moves the insertion point within the text
0858: * (but outside the composed text), or when the component's text is
0859: * saved to a file or copied to the clipboard.
0860: *
0861: */
0862: public void endComposition() {
0863: if (disposed) {
0864: return;
0865: }
0866:
0867: /* Before calling resetXIC, record the current composition mode
0868: so that it can be restored later. */
0869: savedCompositionState = getCompositionState();
0870: boolean active = haveActiveClient();
0871: if (active && composedText == null && committedText == null) {
0872: needResetXIC = true;
0873: needResetXICClient = getClientComponent();
0874: return;
0875: }
0876:
0877: String text = resetXIC();
0878: /* needResetXIC is only set to true for active client. So passive
0879: client should not reset the flag to false. */
0880: if (active) {
0881: needResetXIC = false;
0882: }
0883:
0884: // Remove any existing composed text by posting an InputMethodEvent
0885: // with null composed text. It would be desirable to wait for a
0886: // dispatchComposedText call from X input method engine, but some
0887: // input method does not conform to the XIM specification and does
0888: // not call the preedit callback to erase preedit text on calling
0889: // XmbResetIC. To work around this problem, do it here by ourselves.
0890: awtLock();
0891: composedText = null;
0892: postInputMethodEvent(
0893: InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, null, 0,
0894: null, null);
0895:
0896: if (text != null && text.length() > 0) {
0897: dispatchCommittedText(text);
0898: }
0899: awtUnlock();
0900:
0901: // Restore the preedit state if it was enabled
0902: if (savedCompositionState) {
0903: resetCompositionState();
0904: }
0905: }
0906:
0907: /**
0908: * Returns a string with information about the current input method server, or null.
0909: * On both Linux & SunOS, the value of environment variable XMODIFIERS is
0910: * returned if set. Otherwise, on SunOS, $HOME/.dtprofile will be parsed
0911: * to find out the language service engine (atok or wnn) since there is
0912: * no API in Xlib which returns the information of native
0913: * IM server or language service and we want to try our best to return as much
0914: * information as possible.
0915: *
0916: * Note: This method could return null on Linux if XMODIFIERS is not set properly or
0917: * if any IOException is thrown.
0918: * See man page of XSetLocaleModifiers(3X11) for the usgae of XMODIFIERS,
0919: * atok12setup(1) and wnn6setup(1) for the information written to
0920: * $HOME/.dtprofile when you run these two commands.
0921: *
0922: */
0923: public String getNativeInputMethodInfo() {
0924: String xmodifiers = System.getenv("XMODIFIERS");
0925: String imInfo = null;
0926:
0927: // If XMODIFIERS is set, return the value
0928: if (xmodifiers != null) {
0929: int imIndex = xmodifiers.indexOf("@im=");
0930: if (imIndex != -1) {
0931: imInfo = xmodifiers.substring(imIndex + 4);
0932: }
0933: } else if (System.getProperty("os.name").startsWith("SunOS")) {
0934: File dtprofile = new File(System.getProperty("user.home")
0935: + "/.dtprofile");
0936: String languageEngineInfo = null;
0937: try {
0938: BufferedReader br = new BufferedReader(new FileReader(
0939: dtprofile));
0940: String line = null;
0941:
0942: while (languageEngineInfo == null
0943: && (line = br.readLine()) != null) {
0944: if (line.contains("atok") || line.contains("wnn")) {
0945: StringTokenizer tokens = new StringTokenizer(
0946: line);
0947: while (tokens.hasMoreTokens()) {
0948: String token = tokens.nextToken();
0949: if (Pattern.matches("atok.*setup", token)
0950: || Pattern.matches("wnn.*setup",
0951: token)) {
0952: languageEngineInfo = token.substring(0,
0953: token.indexOf("setup"));
0954: break;
0955: }
0956: }
0957: }
0958: }
0959:
0960: br.close();
0961: } catch (IOException ioex) {
0962: // Since this method is provided for internal testing only,
0963: // we dump the stack trace for the ease of debugging.
0964: ioex.printStackTrace();
0965: }
0966:
0967: imInfo = "htt " + languageEngineInfo;
0968: }
0969:
0970: return imInfo;
0971: }
0972:
0973: /**
0974: * Performs mapping from an XIM visible feedback value to Java IM highlight.
0975: * @return Java input method highlight
0976: */
0977: private InputMethodHighlight convertVisualFeedbackToHighlight(
0978: int feedback) {
0979: InputMethodHighlight highlight;
0980:
0981: switch (feedback) {
0982: case XIMUnderline:
0983: highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;
0984: break;
0985: case XIMReverse:
0986: highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
0987: break;
0988: case XIMHighlight:
0989: highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
0990: break;
0991: case XIMPrimary:
0992: highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;
0993: break;
0994: case XIMSecondary:
0995: highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
0996: break;
0997: case XIMTertiary:
0998: highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
0999: break;
1000: default:
1001: highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
1002: break;
1003: }
1004: return highlight;
1005: }
1006:
1007: // initial capacity size for string buffer, etc.
1008: private static final int INITIAL_SIZE = 64;
1009:
1010: /**
1011: * IntBuffer is an inner class that manipulates an int array and
1012: * provides UNIX file io stream-like programming interfaces to
1013: * access it. (An alternative would be to use ArrayList which may
1014: * be too expensive for the work.)
1015: */
1016: private final class IntBuffer {
1017: private int[] intArray;
1018: private int size;
1019: private int index;
1020:
1021: IntBuffer(int initialCapacity) {
1022: intArray = new int[initialCapacity];
1023: size = 0;
1024: index = 0;
1025: }
1026:
1027: void insert(int offset, int[] values) {
1028: int newSize = size + values.length;
1029: if (intArray.length < newSize) {
1030: int[] newIntArray = new int[newSize * 2];
1031: System.arraycopy(intArray, 0, newIntArray, 0, size);
1032: intArray = newIntArray;
1033: }
1034: System.arraycopy(intArray, offset, intArray, offset
1035: + values.length, size - offset);
1036: System
1037: .arraycopy(values, 0, intArray, offset,
1038: values.length);
1039: size += values.length;
1040: if (index > offset)
1041: index = offset;
1042: }
1043:
1044: void remove(int offset, int length) {
1045: if (offset + length != size)
1046: System.arraycopy(intArray, offset + length, intArray,
1047: offset, size - offset - length);
1048: size -= length;
1049: if (index > offset)
1050: index = offset;
1051: }
1052:
1053: void replace(int offset, int[] values) {
1054: System
1055: .arraycopy(values, 0, intArray, offset,
1056: values.length);
1057: }
1058:
1059: void removeAll() {
1060: size = 0;
1061: index = 0;
1062: }
1063:
1064: void rewind() {
1065: index = 0;
1066: }
1067:
1068: int getNext() {
1069: if (index == size)
1070: return -1;
1071: return intArray[index++];
1072: }
1073:
1074: void unget() {
1075: if (index != 0)
1076: index--;
1077: }
1078:
1079: int getOffset() {
1080: return index;
1081: }
1082:
1083: public String toString() {
1084: StringBuffer s = new StringBuffer();
1085: for (int i = 0; i < size;) {
1086: s.append(intArray[i++]);
1087: if (i < size)
1088: s.append(",");
1089: }
1090: return s.toString();
1091: }
1092: }
1093:
1094: /*
1095: * Native methods
1096: */
1097: protected native String resetXIC();
1098:
1099: private native void disposeXIC();
1100:
1101: private native boolean setCompositionEnabledNative(boolean enable);
1102:
1103: private native boolean isCompositionEnabledNative();
1104:
1105: private native void turnoffStatusWindow();
1106: }
|