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 }