1 /******************************************************************************* 2 * Copyright (c) 2007, 2008 IBM Corporation and others. 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Eclipse Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/epl-v10.html 7 * 8 * Contributors: 9 * IBM Corporation - initial API and implementation 10 *******************************************************************************/ 11 module org.eclipse.swt.widgets.IME; 12 13 import java.lang.all; 14 15 import org.eclipse.swt.SWT; 16 import org.eclipse.swt.SWTException; 17 import org.eclipse.swt.graphics.Color; 18 import org.eclipse.swt.graphics.TextStyle; 19 import org.eclipse.swt.internal.Converter; 20 import org.eclipse.swt.internal.gtk.OS; 21 22 import org.eclipse.swt.widgets.Widget; 23 import org.eclipse.swt.widgets.Canvas; 24 import org.eclipse.swt.widgets.Event; 25 26 /** 27 * Instances of this class represent input method editors. 28 * These are typically in-line pre-edit text areas that allow 29 * the user to compose characters from Far Eastern languages 30 * such as Japanese, Chinese or Korean. 31 * 32 * <dl> 33 * <dt><b>Styles:</b></dt> 34 * <dd>(none)</dd> 35 * <dt><b>Events:</b></dt> 36 * <dd>ImeComposition</dd> 37 * </dl> 38 * <p> 39 * IMPORTANT: This class is <em>not</em> intended to be subclassed. 40 * </p> 41 * 42 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> 43 * 44 * @since 3.4 45 */ 46 public class IME : Widget { 47 Canvas parent; 48 int caretOffset; 49 int startOffset; 50 int commitCount; 51 String text; 52 int [] ranges; 53 TextStyle [] styles; 54 bool inComposition; 55 56 /** 57 * Prevents uninitialized instances from being created outside the package. 58 */ 59 this () { 60 } 61 62 /** 63 * Constructs a new instance of this class given its parent 64 * and a style value describing its behavior and appearance. 65 * <p> 66 * The style value is either one of the style constants defined in 67 * class <code>SWT</code> which is applicable to instances of this 68 * class, or must be built by <em>bitwise OR</em>'ing together 69 * (that is, using the <code>int</code> "|" operator) two or more 70 * of those <code>SWT</code> style constants. The class description 71 * lists the style constants that are applicable to the class. 72 * Style bits are also inherited from superclasses. 73 * </p> 74 * 75 * @param parent a canvas control which will be the parent of the new instance (cannot be null) 76 * @param style the style of control to construct 77 * 78 * @exception IllegalArgumentException <ul> 79 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> 80 * </ul> 81 * @exception SWTException <ul> 82 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> 83 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li> 84 * </ul> 85 * 86 * @see Widget#checkSubclass 87 * @see Widget#getStyle 88 */ 89 public this (Canvas parent, int style) { 90 super (parent, style); 91 this.parent = parent; 92 createWidget (); 93 } 94 95 void createWidget () { 96 text = ""; 97 startOffset = -1; 98 if (parent.getIME () is null) { 99 parent.setIME (this); 100 } 101 } 102 103 /** 104 * Returns the offset of the caret from the start of the document. 105 * The caret is within the current composition. 106 * 107 * @return the caret offset 108 * 109 * @exception SWTException <ul> 110 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 111 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 112 * </ul> 113 */ 114 public int getCaretOffset () { 115 checkWidget (); 116 return startOffset + caretOffset; 117 } 118 119 /** 120 * Returns the commit count of the composition. This is the 121 * number of characters that have been composed. When the 122 * commit count is equal to the length of the composition 123 * text, then the in-line edit operation is complete. 124 * 125 * @return the commit count 126 * 127 * @exception SWTException <ul> 128 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 129 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 130 * </ul> 131 * 132 * @see IME#getText 133 */ 134 public int getCommitCount () { 135 checkWidget (); 136 return commitCount; 137 } 138 139 /** 140 * Returns the offset of the composition from the start of the document. 141 * This is the start offset of the composition within the document and 142 * in not changed by the input method editor itself during the in-line edit 143 * session. 144 * 145 * @return the offset of the composition 146 * 147 * @exception SWTException <ul> 148 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 149 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 150 * </ul> 151 */ 152 public int getCompositionOffset () { 153 checkWidget (); 154 return startOffset; 155 } 156 157 /** 158 * Returns the ranges for the style that should be applied during the 159 * in-line edit session. 160 * <p> 161 * The ranges array contains start and end pairs. Each pair refers to 162 * the corresponding style in the styles array. For example, the pair 163 * that starts at ranges[n] and ends at ranges[n+1] uses the style 164 * at styles[n/2] returned by <code>getStyles()</code>. 165 * </p> 166 * @return the ranges for the styles 167 * 168 * @exception SWTException <ul> 169 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 170 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 171 * </ul> 172 * 173 * @see IME#getStyles 174 */ 175 public int [] getRanges () { 176 checkWidget (); 177 if (ranges is null) return new int [0]; 178 int [] result = new int [ranges.length]; 179 for (int i = 0; i < result.length; i++) { 180 result [i] = ranges [i] + startOffset; 181 } 182 return result; 183 } 184 185 /** 186 * Returns the styles for the ranges. 187 * <p> 188 * The ranges array contains start and end pairs. Each pair refers to 189 * the corresponding style in the styles array. For example, the pair 190 * that starts at ranges[n] and ends at ranges[n+1] uses the style 191 * at styles[n/2]. 192 * </p> 193 * 194 * @return the ranges for the styles 195 * 196 * @exception SWTException <ul> 197 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 198 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 199 * </ul> 200 * 201 * @see IME#getRanges 202 */ 203 public TextStyle [] getStyles () { 204 checkWidget (); 205 if (styles is null) return new TextStyle [0]; 206 TextStyle [] result = new TextStyle [styles.length]; 207 System.arraycopy (styles, 0, result, 0, styles.length); 208 return result; 209 } 210 211 /** 212 * Returns the composition text. 213 * <p> 214 * The text for an IME is the characters in the widget that 215 * are in the current composition. When the commit count is 216 * equal to the length of the composition text, then the 217 * in-line edit operation is complete. 218 * </p> 219 * 220 * @return the widget text 221 * 222 * @exception SWTException <ul> 223 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 224 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 225 * </ul> 226 */ 227 public String getText () { 228 checkWidget (); 229 return text; 230 } 231 232 /** 233 * Returns <code>true</code> if the caret should be wide, and 234 * <code>false</code> otherwise. In some languages, for example 235 * Korean, the caret is typically widened to the width of the 236 * current character in the in-line edit session. 237 * 238 * @return the wide caret state 239 * 240 * @exception SWTException <ul> 241 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 242 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 243 * </ul> 244 */ 245 public bool getWideCaret () { 246 checkWidget (); 247 return false; 248 } 249 250 override int gtk_button_press_event (GtkWidget* widget, GdkEventButton* event) { 251 if (!isInlineEnabled ()) return 0; 252 auto imHandle_ = imHandle (); 253 if (imHandle_ !is null) OS.gtk_im_context_reset (imHandle_); 254 return 0; 255 } 256 257 override int gtk_commit (GtkIMContext* imcontext, char* textPtr) { 258 if (!isInlineEnabled ()) return 0; 259 bool doit = true; 260 ranges = null; 261 styles = null; 262 caretOffset = commitCount = 0; 263 if (textPtr !is null && inComposition) { 264 int length = OS.strlen (textPtr); 265 if (length !is 0) { 266 String chars = fromStringz(textPtr)._idup(); 267 Event event = new Event(); 268 event.detail = SWT.COMPOSITION_CHANGED; 269 event.start = startOffset; 270 event.end = cast(int)/*64bit*/(startOffset + text.length); 271 event.text = text = chars !is null ? chars : ""; 272 commitCount = cast(int)/*64bit*/text.length; 273 sendEvent (SWT.ImeComposition, event); 274 doit = event.doit; 275 text = ""; 276 startOffset = -1; 277 commitCount = 0; 278 } 279 } 280 inComposition = false; 281 return doit ? 0 : 1; 282 } 283 284 override int gtk_preedit_changed (GtkIMContext* imcontext) { 285 if (!isInlineEnabled ()) return 0; 286 ranges = null; 287 styles = null; 288 commitCount = 0; 289 auto imHandle_ = imHandle (); 290 char* preeditString; 291 void* pangoAttrs; 292 int cursorPos; 293 OS.gtk_im_context_get_preedit_string (imHandle_, &preeditString, &pangoAttrs, &cursorPos); 294 caretOffset = cursorPos ; 295 String chars = null; 296 if (preeditString !is null) { 297 int length = OS.strlen (preeditString); 298 chars = fromStringz(preeditString)._idup(); 299 if (pangoAttrs !is null) { 300 int count = 0; 301 auto iterator = OS.pango_attr_list_get_iterator (pangoAttrs ); 302 while (OS.pango_attr_iterator_next (iterator)) count++; 303 OS.pango_attr_iterator_destroy (iterator); 304 ranges = new int [count * 2]; 305 styles = new TextStyle [count]; 306 iterator = OS.pango_attr_list_get_iterator (pangoAttrs ); 307 PangoAttrColor* attrColor; 308 PangoAttrInt* attrInt; 309 int start; 310 int end; 311 for (int i = 0; i < count; i++) { 312 OS.pango_attr_iterator_range (iterator, &start, &end); 313 ranges [i * 2] = cast(int)/*64bit*/OS.g_utf8_pointer_to_offset (preeditString, preeditString + start); 314 ranges [i * 2 + 1] = cast(int)/*64bit*/OS.g_utf8_pointer_to_offset (preeditString, preeditString + end) - 1; 315 styles [i] = new TextStyle (null, null, null); 316 auto attr = OS.pango_attr_iterator_get (iterator, OS.PANGO_ATTR_FOREGROUND); 317 if (attr !is null) { 318 attrColor = cast(PangoAttrColor*) attr; 319 GdkColor* color = new GdkColor (); 320 color.red = attrColor.color.red; 321 color.green = attrColor.color.green; 322 color.blue = attrColor.color.blue; 323 styles [i].foreground = Color.gtk_new (display, color); 324 } 325 attr = OS.pango_attr_iterator_get (iterator, OS.PANGO_ATTR_BACKGROUND); 326 if (attr !is null) { 327 attrColor = cast(PangoAttrColor*) attr; 328 GdkColor* color = new GdkColor (); 329 color.red = attrColor.color.red; 330 color.green = attrColor.color.green; 331 color.blue = attrColor.color.blue; 332 styles [i].background = Color.gtk_new (display, color); 333 } 334 attr = OS.pango_attr_iterator_get (iterator, OS.PANGO_ATTR_UNDERLINE); 335 if (attr !is null) { 336 attrInt = cast(PangoAttrInt*) attr; 337 styles [i].underline = attrInt.value !is OS.PANGO_UNDERLINE_NONE; 338 styles [i].underlineStyle = SWT.UNDERLINE_SINGLE; 339 switch (attrInt.value) { 340 case OS.PANGO_UNDERLINE_DOUBLE: 341 styles [i].underlineStyle = SWT.UNDERLINE_DOUBLE; 342 break; 343 case OS.PANGO_UNDERLINE_ERROR: 344 styles [i].underlineStyle = SWT.UNDERLINE_ERROR; 345 break; 346 default: break; 347 } 348 if (styles [i].underline) { 349 attr = OS.pango_attr_iterator_get(iterator, OS.PANGO_ATTR_UNDERLINE_COLOR); 350 if (attr !is null) { 351 attrColor = cast(PangoAttrColor*) attr; 352 GdkColor* color = new GdkColor; 353 color.red = attrColor.color.red; 354 color.green = attrColor.color.green; 355 color.blue = attrColor.color.blue; 356 styles [i].underlineColor = Color.gtk_new (display, color); 357 } 358 } 359 } 360 OS.pango_attr_iterator_next (iterator); 361 } 362 OS.pango_attr_iterator_destroy (iterator); 363 OS.pango_attr_list_unref (pangoAttrs); 364 } 365 OS.g_free (preeditString); 366 } 367 if (chars !is null) { 368 if (text.length is 0) startOffset = -1; 369 ptrdiff_t end = startOffset + text.length; 370 if (startOffset is -1) { 371 Event event = new Event (); 372 event.detail = SWT.COMPOSITION_SELECTION; 373 sendEvent (SWT.ImeComposition, event); 374 startOffset = event.start; 375 end = event.end; 376 } 377 inComposition = true; 378 Event event = new Event (); 379 event.detail = SWT.COMPOSITION_CHANGED; 380 event.start = startOffset; 381 event.end = cast(int)/*64bit*/end; 382 event.text = text = chars !is null ? chars : ""; 383 sendEvent (SWT.ImeComposition, event); 384 } 385 return 1; 386 } 387 388 GtkIMContext* imHandle () { 389 return parent.imHandle (); 390 } 391 392 bool isInlineEnabled () { 393 return hooks (SWT.ImeComposition); 394 } 395 396 override 397 void releaseParent () { 398 super.releaseParent (); 399 if (this is parent.getIME ()) parent.setIME (null); 400 } 401 402 override 403 void releaseWidget () { 404 super.releaseWidget (); 405 parent = null; 406 text = null; 407 styles = null; 408 ranges = null; 409 } 410 411 /** 412 * Sets the offset of the composition from the start of the document. 413 * This is the start offset of the composition within the document and 414 * in not changed by the input method editor itself during the in-line edit 415 * session but may need to be changed by clients of the IME. For example, 416 * if during an in-line edit operation, a text editor inserts characters 417 * above the IME, then the IME must be informed that the composition 418 * offset has changed. 419 * 420 * @return the offset of the composition 421 * 422 * @exception SWTException <ul> 423 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 424 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 425 * </ul> 426 */ 427 public void setCompositionOffset (int offset) { 428 checkWidget (); 429 if (offset < 0) return; 430 if (startOffset !is -1) { 431 startOffset = offset; 432 } 433 } 434 435 }