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.EventQueue;
  30 import java.awt.event.InputMethodEvent;
  31 import java.awt.font.TextAttribute;
  32 import java.awt.font.TextHitInfo;
  33 import java.awt.peer.ComponentPeer;
  34 import java.text.AttributedString;
  35 
  36 import sun.util.logging.PlatformLogger;
  37 
  38 /**
  39  * Input Method Adapter for XIM for AIX
  40  *
  41  * @author JavaSoft International
  42  */
  43 public abstract class X11InputMethod extends X11InputMethodBase {
  44 
  45     // to keep the instance of activating if IM resumed
  46     static protected X11InputMethod activatedInstance = null;
  47 
  48     /**
  49      * Constructs an X11InputMethod instance. It initializes the XIM
  50      * environment if it's not done yet.
  51      *
  52      * @exception AWTException if XOpenIM() failed.
  53      */
  54     public X11InputMethod() throws AWTException {
  55         super();
  56     }
  57 
  58     /**
  59      * Reset the composition state to the current composition state.
  60      */
  61     protected void resetCompositionState() {
  62         if (compositionEnableSupported && haveActiveClient()) {
  63             try {
  64                 /* Restore the composition mode to the last saved composition
  65                    mode. */
  66                 setCompositionEnabled(savedCompositionState);
  67             } catch (UnsupportedOperationException e) {
  68                 compositionEnableSupported = false;
  69             }
  70         }
  71     }
  72 
  73     /**
  74      * Activate input method.
  75      */
  76     public synchronized void activate() {
  77         activatedInstance = this;
  78         clientComponentWindow = getClientComponentWindow();
  79         if (clientComponentWindow == null)
  80             return;
  81 
  82         if (lastXICFocussedComponent != null) {
  83             if (log.isLoggable(PlatformLogger.Level.FINE)) {
  84                 log.fine("XICFocused {0}, AWTFocused {1}",
  85                          lastXICFocussedComponent, awtFocussedComponent);
  86             }
  87             if (lastXICFocussedComponent != awtFocussedComponent) {
  88                 ComponentPeer lastXICFocussedComponentPeer = getPeer(lastXICFocussedComponent);
  89                 if (lastXICFocussedComponentPeer != null) {
  90                     setXICFocus(lastXICFocussedComponentPeer, false, isLastXICActive);
  91                 }
  92             }
  93             lastXICFocussedComponent = null;
  94         }
  95 
  96         if (pData == 0) {
  97             if (!createXIC()) {
  98                 return;
  99             }
 100             disposed = false;
 101         }
 102 
 103         /*  reset input context if necessary and set the XIC focus
 104         */
 105         resetXICifneeded();
 106         ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent);
 107         setStatusAreaVisible(true, pData);
 108 
 109         if (awtFocussedComponentPeer != null) {
 110             setXICFocus(awtFocussedComponentPeer, true, haveActiveClient());
 111         }
 112         lastXICFocussedComponent = awtFocussedComponent;
 113         isLastXICActive = haveActiveClient();
 114         isActive = true;
 115         if (savedCompositionState) {
 116             resetCompositionState();
 117         }
 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         activatedInstance = null;
 144         savedCompositionState = getCompositionState();
 145 
 146         if (isTemporary) {
 147             //turn the status window off...
 148             turnoffStatusWindow();
 149             /* Delay resetting the XIC focus until activate is called and the newly
 150              * Focused component has a different peer as the last focused component.
 151              */
 152             lastXICFocussedComponent = awtFocussedComponent;
 153         } else {
 154             if (awtFocussedComponent != null ) {
 155                 ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent);
 156                 if (awtFocussedComponentPeer != null) {
 157                     setXICFocus(awtFocussedComponentPeer, false, isAc);
 158                 }
 159             }
 160             lastXICFocussedComponent = null;
 161         }
 162 
 163         isLastXICActive = isAc;
 164         isLastTemporary = isTemporary;
 165         isActive = false;
 166         setStatusAreaVisible(false, pData);
 167     }
 168 
 169     // implements java.awt.im.spi.InputMethod.hideWindows
 170     public void hideWindows() {
 171         if (pData != 0) {
 172             setStatusAreaVisible(false, pData);
 173             turnoffStatusWindow();
 174         }
 175     }
 176 
 177     /**
 178      * Updates composed text with XIM preedit information and
 179      * posts composed text to the awt event queue. The args of
 180      * this method correspond to the XIM preedit callback
 181      * information. The XIM highlight attributes are translated via
 182      * fixed mapping (i.e., independent from any underlying input
 183      * method engine). This method is invoked in the AWT Toolkit
 184      * (X event loop) thread context and thus inside the AWT Lock.
 185      */
 186     // NOTE: This method may be called by privileged threads.
 187     //       This functionality is implemented in a package-private method
 188     //       to insure that it cannot be overridden by client subclasses.
 189     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
 190     void dispatchComposedText(String chgText,
 191                                            int chgStyles[],
 192                                            int chgOffset,
 193                                            int chgLength,
 194                                            int caretPosition,
 195                                            long when) {
 196         if (disposed) {
 197             return;
 198         }
 199 
 200         // Workaround for deadlock bug on solaris2.6_zh bug#4170760
 201         if (chgText == null
 202             && chgStyles == null
 203             && chgOffset == 0
 204             && chgLength == 0
 205             && caretPosition == 0
 206             && composedText == null
 207             && committedText == null)
 208             return;
 209 
 210         // Recalculate chgOffset and chgLength for supplementary char
 211         if (composedText != null) {
 212             int tmpChgOffset=chgOffset;
 213             int tmpChgLength=chgLength;
 214             int index = 0;
 215             for (int i=0;i < tmpChgOffset; i++,index++){
 216                 if (index < composedText.length()
 217                     && Character.charCount(composedText.codePointAt(index))==2){
 218                     index++;
 219                     chgOffset++;
 220                 }
 221             }
 222             // The index keeps value
 223             for (int i=0;i < tmpChgLength; i++,index++){
 224                 if (index < composedText.length()
 225                     && Character.charCount(composedText.codePointAt(index))==2){
 226                     index++;
 227                     chgLength++;
 228                 }
 229             }
 230         }
 231 
 232         // Replace control character with a square box
 233         if (chgText != null) {
 234             StringBuffer newChgText = new StringBuffer();
 235             for (int i=0; i < chgText.length(); i++){
 236                 char c = chgText.charAt(i);
 237                 if (Character.isISOControl(c)){
 238                     c = '\u25A1';
 239                 }
 240                 newChgText.append(c);
 241             }
 242             chgText = new String(newChgText);
 243         }
 244 
 245         if (composedText == null) {
 246             // TODO: avoid reallocation of those buffers
 247             composedText = new StringBuffer(INITIAL_SIZE);
 248             rawFeedbacks = new IntBuffer(INITIAL_SIZE);
 249         }
 250         if (chgLength > 0) {
 251             if (chgText == null && chgStyles != null) {
 252                 rawFeedbacks.replace(chgOffset, chgStyles);
 253             } else {
 254                 if (chgLength == composedText.length()) {
 255                     // optimization for the special case to replace the
 256                     // entire previous text
 257                     composedText = new StringBuffer(INITIAL_SIZE);
 258                     rawFeedbacks = new IntBuffer(INITIAL_SIZE);
 259                 } else {
 260                     if (composedText.length() > 0) {
 261                         if (chgOffset+chgLength < composedText.length()) {
 262                             String text;
 263                             text = composedText.toString().substring(chgOffset+chgLength,
 264                                                                      composedText.length());
 265                             composedText.setLength(chgOffset);
 266                             composedText.append(text);
 267                         } else {
 268                             // in case to remove substring from chgOffset
 269                             // to the end
 270                             composedText.setLength(chgOffset);
 271                         }
 272                         rawFeedbacks.remove(chgOffset, chgLength);
 273                     }
 274                 }
 275             }
 276         }
 277         if (chgText != null) {
 278             composedText.insert(chgOffset, chgText);
 279             if (chgStyles != null) {
 280                 // Recalculate chgStyles for supplementary char
 281                 if (chgText.length() > chgStyles.length){
 282                     int index=0;
 283                     int[] newStyles = new int[chgText.length()];
 284                     for (int i=0; i < chgStyles.length; i++, index++){
 285                         newStyles[index]=chgStyles[i];
 286                         if (index < chgText.length()
 287                             && Character.charCount(chgText.codePointAt(index))==2){
 288                             newStyles[++index]=chgStyles[i];
 289                         }
 290                     }
 291                     chgStyles=newStyles;
 292                 }
 293                 rawFeedbacks.insert(chgOffset, chgStyles);
 294             }
 295 
 296         }
 297 
 298         else if (chgStyles != null) {
 299             // Recalculate chgStyles to support supplementary char
 300             int count=0;
 301             for (int i=0; i < chgStyles.length; i++){
 302                 if (composedText.length() > chgOffset+i+count
 303                     && Character.charCount(composedText.codePointAt(chgOffset+i+count))==2){
 304                     count++;
 305                 }
 306             }
 307             if (count>0){
 308                 int index=0;
 309                 int[] newStyles = new int[chgStyles.length+count];
 310                 for (int i=0; i < chgStyles.length; i++, index++){
 311                     newStyles[index]=chgStyles[i];
 312                     if (composedText.length() > chgOffset+index
 313                         && Character.charCount(composedText.codePointAt(chgOffset+index))==2){
 314                         newStyles[++index]=chgStyles[i];
 315                     }
 316                 }
 317                 chgStyles=newStyles;
 318             }
 319             rawFeedbacks.replace(chgOffset, chgStyles);
 320         }
 321 
 322         if (composedText.length() == 0) {
 323             composedText = null;
 324             rawFeedbacks = null;
 325 
 326             // if there is any outstanding committed text stored by
 327             // dispatchCommittedText(), it has to be sent to the
 328             // client component.
 329             if (committedText != null) {
 330                 dispatchCommittedText(committedText, when);
 331                 committedText = null;
 332                 return;
 333             }
 334 
 335             // otherwise, send null text to delete client's composed
 336             // text.
 337             postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
 338                                  null,
 339                                  0,
 340                                  null,
 341                                  null,
 342                                  when);
 343 
 344             return;
 345         }
 346 
 347         // Adjust caretPosition for supplementary char
 348         for (int i=0; i< caretPosition; i++){
 349             if (i < composedText.length()
 350                 && Character.charCount(composedText.codePointAt(i))==2){
 351                 caretPosition++;
 352                 i++;
 353             }
 354         }
 355 
 356         // Now sending the composed text to the client
 357         int composedOffset;
 358         AttributedString inputText;
 359 
 360         // if there is any partially committed text, concatenate it to
 361         // the composed text.
 362         if (committedText != null) {
 363             composedOffset = committedText.length();
 364             inputText = new AttributedString(committedText + composedText);
 365             committedText = null;
 366         } else {
 367             composedOffset = 0;
 368             inputText = new AttributedString(composedText.toString());
 369         }
 370 
 371         int currentFeedback;
 372         int nextFeedback;
 373         int startOffset = 0;
 374         int currentOffset;
 375         int visiblePosition = 0;
 376         TextHitInfo visiblePositionInfo = null;
 377 
 378         rawFeedbacks.rewind();
 379         currentFeedback = rawFeedbacks.getNext();
 380         rawFeedbacks.unget();
 381         while ((nextFeedback = rawFeedbacks.getNext()) != -1) {
 382             if (visiblePosition == 0) {
 383                 visiblePosition = nextFeedback & XIMVisibleMask;
 384                 if (visiblePosition != 0) {
 385                     int index = rawFeedbacks.getOffset() - 1;
 386 
 387                     if (visiblePosition == XIMVisibleToBackward)
 388                         visiblePositionInfo = TextHitInfo.leading(index);
 389                     else
 390                         visiblePositionInfo = TextHitInfo.trailing(index);
 391                 }
 392             }
 393             nextFeedback &= ~XIMVisibleMask;
 394             if (currentFeedback != nextFeedback) {
 395                 rawFeedbacks.unget();
 396                 currentOffset = rawFeedbacks.getOffset();
 397                 inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
 398                                        convertVisualFeedbackToHighlight(currentFeedback),
 399                                        composedOffset + startOffset,
 400                                        composedOffset + currentOffset);
 401                 startOffset = currentOffset;
 402                 currentFeedback = nextFeedback;
 403             }
 404         }
 405         currentOffset = rawFeedbacks.getOffset();
 406         if (currentOffset >= 0) {
 407             inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
 408                                    convertVisualFeedbackToHighlight(currentFeedback),
 409                                    composedOffset + startOffset,
 410                                    composedOffset + currentOffset);
 411         }
 412 
 413         postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
 414                              inputText.getIterator(),
 415                              composedOffset,
 416                              TextHitInfo.leading(caretPosition),
 417                              visiblePositionInfo,
 418                              when);
 419     }
 420 
 421     /* Some IMs need forced Text clear */
 422     void clearComposedText(long when) {
 423         composedText = null;
 424         postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
 425                              null, 0, null, null,
 426                              when);
 427         if (committedText != null && committedText.length() > 0) {
 428             dispatchCommittedText(committedText, when);
 429         }
 430         committedText = null;
 431         rawFeedbacks = null;
 432     }
 433 
 434     void clearComposedText() {
 435         if (EventQueue.isDispatchThread()) {
 436             clearComposedText(EventQueue.getMostRecentEventTime());
 437         }
 438     }
 439 
 440     /*
 441      * Subclasses should override disposeImpl() instead of dispose(). Client
 442      * code should always invoke dispose(), never disposeImpl().
 443      */
 444     protected synchronized void disposeImpl() {
 445         disposeXIC();
 446         awtLock();
 447         try {
 448             clearComposedText();
 449         } finally {
 450             // Put awtUnlock into finally block in case an exception is thrown in clearComposedText.
 451             awtUnlock();
 452         }
 453         awtFocussedComponent = null;
 454         lastXICFocussedComponent = null;
 455         needResetXIC = false;
 456         savedCompositionState = false;
 457         compositionEnableSupported = true;
 458     }
 459 
 460     /**
 461      * @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean)
 462      */
 463     public void setCompositionEnabled(boolean enable) {
 464         /* If the composition state is successfully changed, set
 465            the savedCompositionState to 'enable'. Otherwise, simply
 466            return.
 467            setCompositionEnabledNative may throw UnsupportedOperationException.
 468            Don't try to catch it since the method may be called by clients.
 469            Use package private mthod 'resetCompositionState' if you want the
 470            exception to be caught.
 471         */
 472         boolean pre, post;
 473         pre=getCompositionState();
 474 
 475         if (setCompositionEnabledNative(enable)) {
 476             savedCompositionState = enable;
 477         }
 478 
 479         post=getCompositionState();
 480         if (pre != post && post == enable){
 481             if (enable == false) flushText();
 482             if (awtFocussedComponent != null && isActive){
 483                 setXICFocus(getPeer(awtFocussedComponent),
 484                             true, haveActiveClient());
 485             }
 486         }
 487     }
 488 
 489     private native void setStatusAreaVisible(boolean value, long data);
 490 }