1 /*
   2  * Copyright (c) 1997, 2018, Oracle and/or its affiliates. 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt;
  27 
  28 import java.awt.AWTException;
  29 import java.awt.event.InputMethodEvent;
  30 import java.awt.font.TextAttribute;
  31 import java.awt.font.TextHitInfo;
  32 import java.awt.peer.ComponentPeer;
  33 import java.text.AttributedString;
  34 
  35 import sun.util.logging.PlatformLogger;
  36 
  37 /**
  38  * Input Method Adapter for XIM
  39  *
  40  * @author JavaSoft International
  41  */
  42 public abstract class X11InputMethod extends X11InputMethodBase {
  43 
  44     /**
  45      * Constructs an X11InputMethod instance. It initializes the XIM
  46      * environment if it's not done yet.
  47      *
  48      * @exception AWTException if XOpenIM() failed.
  49      */
  50     public X11InputMethod() throws AWTException {
  51         super();
  52     }
  53 
  54     /**
  55      * Reset the composition state to the current composition state.
  56      */
  57     protected void resetCompositionState() {
  58         if (compositionEnableSupported) {
  59             try {
  60                 /* Restore the composition mode to the last saved composition
  61                    mode. */
  62                 setCompositionEnabled(savedCompositionState);
  63             } catch (UnsupportedOperationException e) {
  64                 compositionEnableSupported = false;
  65             }
  66         }
  67     }
  68 
  69     /**
  70      * Activate input method.
  71      */
  72     public synchronized void activate() {
  73         activatedInstance = this;
  74         clientComponentWindow = getClientComponentWindow();
  75         if (clientComponentWindow == null)
  76             return;
  77 
  78         if (lastXICFocussedComponent != null) {
  79             if (log.isLoggable(PlatformLogger.Level.FINE)) {
  80                 log.fine("XICFocused {0}, AWTFocused {1}",
  81                          lastXICFocussedComponent, awtFocussedComponent);
  82             }
  83         }
  84 
  85         if (pData == 0) {
  86             if (!createXIC()) {
  87                 return;
  88             }
  89             disposed = false;
  90         }
  91 
  92         /*  reset input context if necessary and set the XIC focus
  93         */
  94         resetXICifneeded();
  95         ComponentPeer lastXICFocussedComponentPeer = null;
  96         ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent);
  97 
  98         if (lastXICFocussedComponent != null) {
  99            lastXICFocussedComponentPeer = getPeer(lastXICFocussedComponent);
 100         }
 101 
 102         /* If the last XIC focussed component has a different peer as the
 103            current focussed component, change the XIC focus to the newly
 104            focussed component.
 105         */
 106         if (isLastTemporary || lastXICFocussedComponentPeer != awtFocussedComponentPeer ||
 107             isLastXICActive != haveActiveClient()) {
 108             if (lastXICFocussedComponentPeer != null) {
 109                 setXICFocus(lastXICFocussedComponentPeer, false, isLastXICActive);
 110             }
 111             if (awtFocussedComponentPeer != null) {
 112                 setXICFocus(awtFocussedComponentPeer, true, haveActiveClient());
 113             }
 114             lastXICFocussedComponent = awtFocussedComponent;
 115             isLastXICActive = haveActiveClient();
 116         }
 117         resetCompositionState();
 118         isActive = true;
 119     }
 120 
 121     /**
 122      * Deactivate input method.
 123      */
 124     public synchronized void deactivate(boolean isTemporary) {
 125         boolean   isAc =  haveActiveClient();
 126         /* Usually as the client component, let's call it component A,
 127            loses the focus, this method is called. Then when another client
 128            component, let's call it component B,  gets the focus, activate is first called on
 129            the previous focused compoent which is A, then endComposition is called on A,
 130            deactivate is called on A again. And finally activate is called on the newly
 131            focused component B. Here is the call sequence.
 132 
 133            A loses focus               B gains focus
 134            -------------> deactivate A -------------> activate A -> endComposition A ->
 135            deactivate A -> activate B ----....
 136 
 137            So in order to carry the composition mode across the components sharing the same
 138            input context, we save it when deactivate is called so that when activate is
 139            called, it can be restored correctly till activate is called on the newly focused
 140            component. (See also sun/awt/im/InputContext and bug 6184471).
 141            Last note, getCompositionState should be called before setXICFocus since
 142            setXICFocus here sets the XIC to 0.
 143         */
 144         activatedInstance = null;
 145         savedCompositionState = getCompositionState();
 146 
 147         if (isTemporary) {
 148             //turn the status window off...
 149             turnoffStatusWindow();
 150         }
 151 
 152         /* Delay resetting the XIC focus until activate is called and the newly
 153          * Focused component has a different peer as the last focused component.
 154          */
 155         lastXICFocussedComponent = awtFocussedComponent;
 156         isLastXICActive = isAc;
 157         isLastTemporary = isTemporary;
 158         isActive = false;
 159     }
 160 
 161     // implements java.awt.im.spi.InputMethod.hideWindows
 162     public void hideWindows() {
 163         // ??? need real implementation
 164     }
 165 
 166     /**
 167      * Updates composed text with XIM preedit information and
 168      * posts composed text to the awt event queue. The args of
 169      * this method correspond to the XIM preedit callback
 170      * information. The XIM highlight attributes are translated via
 171      * fixed mapping (i.e., independent from any underlying input
 172      * method engine). This method is invoked in the AWT Toolkit
 173      * (X event loop) thread context and thus inside the AWT Lock.
 174      */
 175     // NOTE: This method may be called by privileged threads.
 176     //       This functionality is implemented in a package-private method
 177     //       to insure that it cannot be overridden by client subclasses.
 178     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
 179     void dispatchComposedText(String chgText,
 180                                            int chgStyles[],
 181                                            int chgOffset,
 182                                            int chgLength,
 183                                            int caretPosition,
 184                                            long when) {
 185         if (disposed) {
 186             return;
 187         }
 188 
 189         // Workaround for deadlock bug on solaris2.6_zh bug#4170760
 190         if (chgText == null
 191             && chgStyles == null
 192             && chgOffset == 0
 193             && chgLength == 0
 194             && caretPosition == 0
 195             && composedText == null
 196             && committedText == null)
 197             return;
 198 
 199         if (composedText == null) {
 200             // TODO: avoid reallocation of those buffers
 201             composedText = new StringBuffer(INITIAL_SIZE);
 202             rawFeedbacks = new IntBuffer(INITIAL_SIZE);
 203         }
 204         if (chgLength > 0) {
 205             if (chgText == null && chgStyles != null) {
 206                 rawFeedbacks.replace(chgOffset, chgStyles);
 207             } else {
 208                 if (chgLength == composedText.length()) {
 209                     // optimization for the special case to replace the
 210                     // entire previous text
 211                     composedText = new StringBuffer(INITIAL_SIZE);
 212                     rawFeedbacks = new IntBuffer(INITIAL_SIZE);
 213                 } else {
 214                     if (composedText.length() > 0) {
 215                         if (chgOffset+chgLength < composedText.length()) {
 216                             String text;
 217                             text = composedText.toString().substring(chgOffset+chgLength,
 218                                                                      composedText.length());
 219                             composedText.setLength(chgOffset);
 220                             composedText.append(text);
 221                         } else {
 222                             // in case to remove substring from chgOffset
 223                             // to the end
 224                             composedText.setLength(chgOffset);
 225                         }
 226                         rawFeedbacks.remove(chgOffset, chgLength);
 227                     }
 228                 }
 229             }
 230         }
 231         if (chgText != null) {
 232             composedText.insert(chgOffset, chgText);
 233             if (chgStyles != null)
 234                 rawFeedbacks.insert(chgOffset, chgStyles);
 235         }
 236 
 237         if (composedText.length() == 0) {
 238             composedText = null;
 239             rawFeedbacks = null;
 240 
 241             // if there is any outstanding committed text stored by
 242             // dispatchCommittedText(), it has to be sent to the
 243             // client component.
 244             if (committedText != null) {
 245                 dispatchCommittedText(committedText, when);
 246                 committedText = null;
 247                 return;
 248             }
 249 
 250             // otherwise, send null text to delete client's composed
 251             // text.
 252             postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
 253                                  null,
 254                                  0,
 255                                  null,
 256                                  null,
 257                                  when);
 258 
 259             return;
 260         }
 261 
 262         // Now sending the composed text to the client
 263         int composedOffset;
 264         AttributedString inputText;
 265 
 266         // if there is any partially committed text, concatenate it to
 267         // the composed text.
 268         if (committedText != null) {
 269             composedOffset = committedText.length();
 270             inputText = new AttributedString(committedText + composedText);
 271             committedText = null;
 272         } else {
 273             composedOffset = 0;
 274             inputText = new AttributedString(composedText.toString());
 275         }
 276 
 277         int currentFeedback;
 278         int nextFeedback;
 279         int startOffset = 0;
 280         int currentOffset;
 281         int visiblePosition = 0;
 282         TextHitInfo visiblePositionInfo = null;
 283 
 284         rawFeedbacks.rewind();
 285         currentFeedback = rawFeedbacks.getNext();
 286         rawFeedbacks.unget();
 287         while ((nextFeedback = rawFeedbacks.getNext()) != -1) {
 288             if (visiblePosition == 0) {
 289                 visiblePosition = nextFeedback & XIMVisibleMask;
 290                 if (visiblePosition != 0) {
 291                     int index = rawFeedbacks.getOffset() - 1;
 292 
 293                     if (visiblePosition == XIMVisibleToBackward)
 294                         visiblePositionInfo = TextHitInfo.leading(index);
 295                     else
 296                         visiblePositionInfo = TextHitInfo.trailing(index);
 297                 }
 298             }
 299             nextFeedback &= ~XIMVisibleMask;
 300             if (currentFeedback != nextFeedback) {
 301                 rawFeedbacks.unget();
 302                 currentOffset = rawFeedbacks.getOffset();
 303                 inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
 304                                        convertVisualFeedbackToHighlight(currentFeedback),
 305                                        composedOffset + startOffset,
 306                                        composedOffset + currentOffset);
 307                 startOffset = currentOffset;
 308                 currentFeedback = nextFeedback;
 309             }
 310         }
 311         currentOffset = rawFeedbacks.getOffset();
 312         if (currentOffset >= 0) {
 313             inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
 314                                    convertVisualFeedbackToHighlight(currentFeedback),
 315                                    composedOffset + startOffset,
 316                                    composedOffset + currentOffset);
 317         }
 318 
 319         postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
 320                              inputText.getIterator(),
 321                              composedOffset,
 322                              TextHitInfo.leading(caretPosition),
 323                              visiblePositionInfo,
 324                              when);
 325     }
 326 
 327     /*
 328      * Subclasses should override disposeImpl() instead of dispose(). Client
 329      * code should always invoke dispose(), never disposeImpl().
 330      */
 331     protected synchronized void disposeImpl() {
 332         disposeXIC();
 333         awtLock();
 334         composedText = null;
 335         committedText = null;
 336         rawFeedbacks = null;
 337         awtUnlock();
 338         awtFocussedComponent = null;
 339         lastXICFocussedComponent = null;
 340     }
 341 
 342     /**
 343      * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean)
 344      */
 345     public void setCompositionEnabled(boolean enable) {
 346         /* If the composition state is successfully changed, set
 347            the savedCompositionState to 'enable'. Otherwise, simply
 348            return.
 349            setCompositionEnabledNative may throw UnsupportedOperationException.
 350            Don't try to catch it since the method may be called by clients.
 351            Use package private mthod 'resetCompositionState' if you want the
 352            exception to be caught.
 353         */
 354         if (setCompositionEnabledNative(enable)) {
 355             savedCompositionState = enable;
 356         }
 357     }
 358 }