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