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