1 /* 2 * Copyright (c) 1999, 2006, 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.im; 27 28 import java.awt.Component; 29 import java.awt.Container; 30 import java.awt.Rectangle; 31 import java.awt.event.InputMethodEvent; 32 import java.awt.event.InputMethodListener; 33 import java.awt.font.TextAttribute; 34 import java.awt.font.TextHitInfo; 35 import java.awt.im.InputMethodRequests; 36 import java.lang.ref.WeakReference; 37 import java.text.AttributedCharacterIterator; 38 import java.text.AttributedCharacterIterator.Attribute; 39 import java.text.AttributedString; 40 41 /** 42 * A composition area handler handles events and input method requests for 43 * the composition area. Typically each input method context has its own 44 * composition area handler if it supports passive clients or below-the-spot 45 * input, but all handlers share a single composition area. 46 * 47 * @author JavaSoft International 48 */ 49 50 class CompositionAreaHandler implements InputMethodListener, 51 InputMethodRequests { 52 53 private static CompositionArea compositionArea; 54 private static Object compositionAreaLock = new Object(); 55 private static CompositionAreaHandler compositionAreaOwner; // synchronized through compositionArea 56 57 private AttributedCharacterIterator composedText; 58 private TextHitInfo caret = null; 59 private WeakReference<Component> clientComponent = new WeakReference<>(null); 60 private InputMethodContext inputMethodContext; 61 62 /** 63 * Constructs the composition area handler. 64 */ 65 CompositionAreaHandler(InputMethodContext context) { 66 inputMethodContext = context; 67 } 68 69 /** 70 * Creates the composition area. 71 */ 72 private void createCompositionArea() { 73 synchronized(compositionAreaLock) { 74 compositionArea = new CompositionArea(); 75 if (compositionAreaOwner != null) { 76 compositionArea.setHandlerInfo(compositionAreaOwner, inputMethodContext); 77 } 78 // If the client component is an active client using below-the-spot style, then 79 // make the composition window undecorated without a title bar. 80 Component client = clientComponent.get(); 81 if(client != null){ 82 InputMethodRequests req = client.getInputMethodRequests(); 83 if (req != null && inputMethodContext.useBelowTheSpotInput()) { 84 setCompositionAreaUndecorated(true); 85 } 86 } 87 } 88 } 89 90 void setClientComponent(Component clientComponent) { 91 this.clientComponent = new WeakReference<>(clientComponent); 92 } 93 94 /** 95 * Grabs the composition area, makes this handler its owner, and installs 96 * the handler and its input context into the composition area for event 97 * and input method request handling. 98 * If doUpdate is true, updates the composition area with previously sent 99 * composed text. 100 */ 101 102 void grabCompositionArea(boolean doUpdate) { 103 synchronized (compositionAreaLock) { 104 if (compositionAreaOwner != this) { 105 compositionAreaOwner = this; 106 if (compositionArea != null) { 107 compositionArea.setHandlerInfo(this, inputMethodContext); 108 } 109 if (doUpdate) { 110 // Create the composition area if necessary 111 if ((composedText != null) && (compositionArea == null)) { 112 createCompositionArea(); 113 } 114 if (compositionArea != null) { 115 compositionArea.setText(composedText, caret); 116 } 117 } 118 } 119 } 120 } 121 122 /** 123 * Releases and closes the composition area if it is currently owned by 124 * this composition area handler. 125 */ 126 void releaseCompositionArea() { 127 synchronized (compositionAreaLock) { 128 if (compositionAreaOwner == this) { 129 compositionAreaOwner = null; 130 if (compositionArea != null) { 131 compositionArea.setHandlerInfo(null, null); 132 compositionArea.setText(null, null); 133 } 134 } 135 } 136 } 137 138 /** 139 * Releases and closes the composition area if it has been created, 140 * independent of the current owner. 141 */ 142 static void closeCompositionArea() { 143 if (compositionArea != null) { 144 synchronized (compositionAreaLock) { 145 compositionAreaOwner = null; 146 compositionArea.setHandlerInfo(null, null); 147 compositionArea.setText(null, null); 148 } 149 } 150 } 151 152 /** 153 * Returns whether the composition area is currently visible 154 */ 155 boolean isCompositionAreaVisible() { 156 if (compositionArea != null) { 157 return compositionArea.isCompositionAreaVisible(); 158 } 159 160 return false; 161 } 162 163 164 /** 165 * Shows or hides the composition Area 166 */ 167 void setCompositionAreaVisible(boolean visible) { 168 if (compositionArea != null) { 169 compositionArea.setCompositionAreaVisible(visible); 170 } 171 } 172 173 void processInputMethodEvent(InputMethodEvent event) { 174 if (event.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) { 175 inputMethodTextChanged(event); 176 } else { 177 caretPositionChanged(event); 178 } 179 } 180 181 /** 182 * set the compositionArea frame decoration 183 */ 184 void setCompositionAreaUndecorated(boolean undecorated) { 185 if (compositionArea != null) { 186 compositionArea.setCompositionAreaUndecorated(undecorated); 187 } 188 } 189 190 // 191 // InputMethodListener methods 192 // 193 194 private static final Attribute[] IM_ATTRIBUTES = 195 { TextAttribute.INPUT_METHOD_HIGHLIGHT }; 196 197 public void inputMethodTextChanged(InputMethodEvent event) { 198 AttributedCharacterIterator text = event.getText(); 199 int committedCharacterCount = event.getCommittedCharacterCount(); 200 201 // extract composed text and prepare it for display 202 composedText = null; 203 caret = null; 204 if (text != null 205 && committedCharacterCount < text.getEndIndex() - text.getBeginIndex()) { 206 207 // Create the composition area if necessary 208 if (compositionArea == null) { 209 createCompositionArea(); 210 } 211 212 // copy the composed text 213 AttributedString composedTextString; 214 composedTextString = new AttributedString(text, 215 text.getBeginIndex() + committedCharacterCount, // skip over committed text 216 text.getEndIndex(), IM_ATTRIBUTES); 217 composedTextString.addAttribute(TextAttribute.FONT, compositionArea.getFont()); 218 composedText = composedTextString.getIterator(); 219 caret = event.getCaret(); 220 } 221 222 if (compositionArea != null) { 223 compositionArea.setText(composedText, caret); 224 } 225 226 // send any committed text to the text component 227 if (committedCharacterCount > 0) { 228 inputMethodContext.dispatchCommittedText(((Component) event.getSource()), 229 text, committedCharacterCount); 230 231 // this may have changed the text location, so reposition the window 232 if (isCompositionAreaVisible()) { 233 compositionArea.updateWindowLocation(); 234 } 235 } 236 237 // event has been handled, so consume it 238 event.consume(); 239 } 240 241 public void caretPositionChanged(InputMethodEvent event) { 242 if (compositionArea != null) { 243 compositionArea.setCaret(event.getCaret()); 244 } 245 246 // event has been handled, so consume it 247 event.consume(); 248 } 249 250 // 251 // InputMethodRequests methods 252 // 253 254 /** 255 * Returns the input method request handler of the client component. 256 * When using the composition window for an active client (below-the-spot 257 * input), input method requests that do not relate to the display of 258 * the composed text are forwarded to the client component. 259 */ 260 InputMethodRequests getClientInputMethodRequests() { 261 Component client = clientComponent.get(); 262 if (client != null) { 263 return client.getInputMethodRequests(); 264 } 265 266 return null; 267 } 268 269 public Rectangle getTextLocation(TextHitInfo offset) { 270 synchronized (compositionAreaLock) { 271 if (compositionAreaOwner == this && isCompositionAreaVisible()) { 272 return compositionArea.getTextLocation(offset); 273 } else if (composedText != null) { 274 // there's composed text, but it's not displayed, so fake a rectangle 275 return new Rectangle(0, 0, 0, 10); 276 } else { 277 InputMethodRequests requests = getClientInputMethodRequests(); 278 if (requests != null) { 279 return requests.getTextLocation(offset); 280 } else { 281 // passive client, no composed text, so fake a rectangle 282 return new Rectangle(0, 0, 0, 10); 283 } 284 } 285 } 286 } 287 288 public TextHitInfo getLocationOffset(int x, int y) { 289 synchronized (compositionAreaLock) { 290 if (compositionAreaOwner == this && isCompositionAreaVisible()) { 291 return compositionArea.getLocationOffset(x, y); 292 } else { 293 return null; 294 } 295 } 296 } 297 298 public int getInsertPositionOffset() { 299 InputMethodRequests req = getClientInputMethodRequests(); 300 if (req != null) { 301 return req.getInsertPositionOffset(); 302 } 303 304 // we don't have access to the client component's text. 305 return 0; 306 } 307 308 private static final AttributedCharacterIterator EMPTY_TEXT = 309 (new AttributedString("")).getIterator(); 310 311 public AttributedCharacterIterator getCommittedText(int beginIndex, 312 int endIndex, 313 Attribute[] attributes) { 314 InputMethodRequests req = getClientInputMethodRequests(); 315 if(req != null) { 316 return req.getCommittedText(beginIndex, endIndex, attributes); 317 } 318 319 // we don't have access to the client component's text. 320 return EMPTY_TEXT; 321 } 322 323 public int getCommittedTextLength() { 324 InputMethodRequests req = getClientInputMethodRequests(); 325 if(req != null) { 326 return req.getCommittedTextLength(); 327 } 328 329 // we don't have access to the client component's text. 330 return 0; 331 } 332 333 334 public AttributedCharacterIterator cancelLatestCommittedText(Attribute[] attributes) { 335 InputMethodRequests req = getClientInputMethodRequests(); 336 if(req != null) { 337 return req.cancelLatestCommittedText(attributes); 338 } 339 340 // we don't have access to the client component's text. 341 return null; 342 } 343 344 public AttributedCharacterIterator getSelectedText(Attribute[] attributes) { 345 InputMethodRequests req = getClientInputMethodRequests(); 346 if(req != null) { 347 return req.getSelectedText(attributes); 348 } 349 350 // we don't have access to the client component's text. 351 return EMPTY_TEXT; 352 } 353 354 }