1 /* 2 * Copyright (c) 1997, 2019, 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 */ X11InputMethod()50 public X11InputMethod() throws AWTException { 51 super(); 52 } 53 54 /** 55 * Reset the composition state to the current composition state. 56 */ resetCompositionState()57 protected void resetCompositionState() { 58 if (compositionEnableSupported && haveActiveClient()) { 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 */ activate()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 */ deactivate(boolean isTemporary)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 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! dispatchComposedText(String chgText, int[] chgStyles, int chgOffset, int chgLength, int caretPosition, long when)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 */ disposeImpl()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 */ setCompositionEnabled(boolean enable)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 } 357