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 }