root/dwt/widgets/IME.d

Revision 298:8fa53b71485d, 22.3 kB (checked in by Frank Benoit <benoit@tionex.de>, 3 months ago)

Fix: IME unicode processing.

Line 
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 dwt.widgets.IME;
12
13
14 import dwt.DWT;
15 import dwt.DWTException;
16 import dwt.graphics.Color;
17 import dwt.graphics.TextStyle;
18 import dwt.internal.win32.OS;
19 import dwt.internal.win32.WINTYPES;
20
21 import dwt.widgets.Widget;
22 import dwt.widgets.Canvas;
23 import dwt.widgets.Event;
24 import dwt.widgets.Display;
25
26 import dwt.dwthelper.utils;
27
28 /**
29  * Instances of this class represent input method editors.
30  * These are typically in-line pre-edit text areas that allow
31  * the user to compose characters from Far Eastern languages
32  * such as Japanese, Chinese or Korean.
33  *
34  * <dl>
35  * <dt><b>Styles:</b></dt>
36  * <dd>(none)</dd>
37  * <dt><b>Events:</b></dt>
38  * <dd>ImeComposition</dd>
39  * </dl>
40  * <p>
41  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
42  * </p>
43  *
44  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
45  *
46  * @since 3.4
47  */
48 public class IME : Widget {
49     Canvas parent;
50     int caretOffset;
51     int startOffset;
52     int commitCount;
53     String text;
54     int [] ranges;
55     TextStyle [] styles;
56
57     static const int WM_MSIME_MOUSE;
58
59     static byte [16] IID_ITfInputProcessorProfiles;
60     static byte [16] IID_ITfDisplayAttributeProvider;
61     static byte [16] CLSID_TF_InputProcessorProfiles;
62     static byte [16] GUID_TFCAT_TIP_KEYBOARD;
63     static this() {
64         WM_MSIME_MOUSE = OS.RegisterWindowMessage (StrToTCHARz ("MSIMEMouseOperation"));
65
66         OS.IIDFromString ("{1F02B6C5-7842-4EE6-8A0B-9A24183A95CA}\0"w.ptr, IID_ITfInputProcessorProfiles.ptr);
67         OS.IIDFromString ("{fee47777-163c-4769-996a-6e9c50ad8f54}\0"w.ptr, IID_ITfDisplayAttributeProvider.ptr);
68         OS.IIDFromString ("{33C53A50-F456-4884-B049-85FD643ECFED}\0"w.ptr, CLSID_TF_InputProcessorProfiles.ptr);
69         OS.IIDFromString ("{34745C63-B2F0-4784-8B67-5E12C8701A31}\0"w.ptr, GUID_TFCAT_TIP_KEYBOARD.ptr);
70     }
71
72     /* TextLayout has a copy of these constants */
73     static const int UNDERLINE_IME_DOT = 1 << 16;
74     static const int UNDERLINE_IME_DASH = 2 << 16;
75     static const int UNDERLINE_IME_THICK = 3 << 16;
76
77 /**
78  * Prevents uninitialized instances from being created outside the package.
79  */
80 this () {
81 }
82
83 /**
84  * Constructs a new instance of this class given its parent
85  * and a style value describing its behavior and appearance.
86  * <p>
87  * The style value is either one of the style constants defined in
88  * class <code>DWT</code> which is applicable to instances of this
89  * class, or must be built by <em>bitwise OR</em>'ing together
90  * (that is, using the <code>int</code> "|" operator) two or more
91  * of those <code>DWT</code> style constants. The class description
92  * lists the style constants that are applicable to the class.
93  * Style bits are also inherited from superclasses.
94  * </p>
95  *
96  * @param parent a canvas control which will be the parent of the new instance (cannot be null)
97  * @param style the style of control to construct
98  *
99  * @exception IllegalArgumentException <ul>
100  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
101  * </ul>
102  * @exception DWTException <ul>
103  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
104  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
105  * </ul>
106  *
107  * @see Widget#checkSubclass
108  * @see Widget#getStyle
109  */
110 public this (Canvas parent, int style) {
111     super (parent, style);
112     this.parent = parent;
113     createWidget ();
114 }
115
116 void createWidget () {
117     text = ""; //$NON-NLS-1$
118     startOffset = -1;
119     if (parent.getIME () is null) {
120         parent.setIME (this);
121     }
122 }
123
124 /**
125  * Returns the offset of the caret from the start of the document.
126  * The caret is within the current composition.
127  *
128  * @return the caret offset
129  *
130  * @exception DWTException <ul>
131  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
132  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
133  * </ul>
134  */
135 public int getCaretOffset () {
136     checkWidget ();
137     return startOffset + caretOffset;
138 }
139
140 /**
141  * Returns the commit count of the composition.  This is the
142  * number of characters that have been composed.  When the
143  * commit count is equal to the length of the composition
144  * text, then the in-line edit operation is complete.
145  *
146  * @return the commit count
147  *
148  * @exception DWTException <ul>
149  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
150  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
151  * </ul>
152  *
153  * @see IME#getText
154  */
155 public int getCommitCount () {
156     checkWidget ();
157     return commitCount;
158 }
159
160 /**
161  * Returns the offset of the composition from the start of the document.
162  * This is the start offset of the composition within the document and
163  * in not changed by the input method editor itself during the in-line edit
164  * session.
165  *
166  * @return the offset of the composition
167  *
168  * @exception DWTException <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 public int getCompositionOffset () {
174     checkWidget ();
175     return startOffset;
176 }
177
178 TF_DISPLAYATTRIBUTE* getDisplayAttribute (short langid, int attInfo) {
179     void* pProfiles;
180     int hr = OS.CoCreateInstance (CLSID_TF_InputProcessorProfiles.ptr, null, OS.CLSCTX_INPROC_SERVER, IID_ITfInputProcessorProfiles.ptr, &pProfiles);
181     TF_DISPLAYATTRIBUTE* pda;
182     if (hr is OS.S_OK) {
183         byte [16] pclsid;
184         byte [16] pguidProfile;
185         /* pProfiles.GetDefaultLanguageProfile () */
186         hr = OS.VtblCall (8, pProfiles, cast(int)langid, cast(int)GUID_TFCAT_TIP_KEYBOARD.ptr, cast(int)pclsid.ptr, cast(int)pguidProfile.ptr);
187         if (hr is OS.S_OK) {
188             void* pProvider;
189             hr = OS.CoCreateInstance (pclsid.ptr, null, OS.CLSCTX_INPROC_SERVER, IID_ITfDisplayAttributeProvider.ptr, &pProvider);
190             if (hr is OS.S_OK) {
191                 void* pEnum;
192                 /* pProvider.EnumDisplayAttributeInfo () */
193                 hr = OS.VtblCall (3, pProvider, cast(int)&pEnum);
194                 if (hr is OS.S_OK) {
195                     void* pDispInfo;
196                     TF_DISPLAYATTRIBUTE* tempPda = new TF_DISPLAYATTRIBUTE();
197                     /* pEnum.Next () */
198                     while ((hr = OS.VtblCall (4, pEnum, 1, cast(int) &pDispInfo, 0 )) is OS.S_OK) {
199                         /* pDispInfo.GetAttributeInfo(); */
200                         OS.VtblCall (5, pDispInfo, cast(int)cast(void*)tempPda);
201                         /* pDispInfo.Release () */
202                         OS.VtblCall (2, pDispInfo);
203                         if (tempPda.bAttr is attInfo) {
204                             pda = tempPda;
205                             break;
206                         }
207                     }
208                     /* pEnum.Release () */
209                     hr = OS.VtblCall (2, pEnum);
210                 }
211                 /* pProvider.Release () */
212                 hr = OS.VtblCall (2, pProvider);
213             }
214         }
215         /* pProfiles.Release () */
216         hr = OS.VtblCall (2, pProfiles);
217     }
218     if (pda is null) {
219         pda = new TF_DISPLAYATTRIBUTE ();
220         switch (attInfo) {
221             case OS.TF_ATTR_INPUT:
222                 pda.lsStyle = OS.TF_LS_SQUIGGLE;
223                 break;
224             case OS.TF_ATTR_CONVERTED:
225             case OS.TF_ATTR_TARGET_CONVERTED:
226                 pda.lsStyle = OS.TF_LS_SOLID;
227                 pda.fBoldLine = attInfo is OS.TF_ATTR_TARGET_CONVERTED;
228                 break;
229             default:
230         }
231     }
232     return pda;
233 }
234
235 /**
236  * Returns the ranges for the style that should be applied during the
237  * in-line edit session.
238  * <p>
239  * The ranges array contains start and end pairs.  Each pair refers to
240  * the corresponding style in the styles array.  For example, the pair
241  * that starts at ranges[n] and ends at ranges[n+1] uses the style
242  * at styles[n/2] returned by <code>getStyles()</code>.
243  * </p>
244  * @return the ranges for the styles
245  *
246  * @exception DWTException <ul>
247  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
248  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
249  * </ul>
250  *
251  * @see IME#getStyles
252  */
253 public int [] getRanges () {
254     checkWidget ();
255     if (ranges is null) return new int [0];
256     int [] result = new int [ranges.length];
257     for (int i = 0; i < result.length; i++) {
258         result [i] = ranges [i] + startOffset;
259     }
260     return result;
261 }
262
263 /**
264  * Returns the styles for the ranges.
265  * <p>
266  * The ranges array contains start and end pairs.  Each pair refers to
267  * the corresponding style in the styles array.  For example, the pair
268  * that starts at ranges[n] and ends at ranges[n+1] uses the style
269  * at styles[n/2].
270  * </p>
271  *
272  * @return the ranges for the styles
273  *
274  * @exception DWTException <ul>
275  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
276  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
277  * </ul>
278  *
279  * @see IME#getRanges
280  */
281 public TextStyle [] getStyles () {
282     checkWidget ();
283     if (styles is null) return new TextStyle [0];
284     TextStyle [] result = new TextStyle [styles.length];
285     System.arraycopy (styles, 0, result, 0, styles.length);
286     return result;
287 }
288
289 /**
290  * Returns the composition text.
291  * <p>
292  * The text for an IME is the characters in the widget that
293  * are in the current composition. When the commit count is
294  * equal to the length of the composition text, then the
295  * in-line edit operation is complete.
296  * </p>
297  *
298  * @return the widget text
299  *
300  * @exception DWTException <ul>
301  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
302  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
303  * </ul>
304  */
305 public String getText () {
306     checkWidget ();
307     return text;
308 }
309
310 /**
311  * Returns <code>true</code> if the caret should be wide, and
312  * <code>false</code> otherwise.  In some languages, for example
313  * Korean, the caret is typically widened to the width of the
314  * current character in the in-line edit session.
315  *
316  * @return the wide caret state
317  *
318  * @exception DWTException <ul>
319  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
320  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
321  * </ul>
322  */
323 public bool getWideCaret() {
324     checkWidget ();
325     auto layout = OS.GetKeyboardLayout (0);
326     short langID = cast(short)OS.LOWORD ( cast(int) layout);
327     return OS.PRIMARYLANGID (langID) is OS.LANG_KOREAN;
328 }
329
330 bool isInlineEnabled () {
331     if (OS.IsWinCE || OS.WIN32_VERSION < OS.VERSION (5, 1)) return false;
332     return OS.IsDBLocale && hooks (DWT.ImeComposition);
333 }
334
335 void releaseParent () {
336     super.releaseParent ();
337     if (this is parent.getIME ()) parent.setIME (null);
338 }
339
340 void releaseWidget () {
341     super.releaseWidget ();
342     parent = null;
343     text = null;
344     styles = null;
345     ranges = null;
346 }
347
348 /**
349  * Sets the offset of the composition from the start of the document.
350  * This is the start offset of the composition within the document and
351  * in not changed by the input method editor itself during the in-line edit
352  * session but may need to be changed by clients of the IME.  For example,
353  * if during an in-line edit operation, a text editor inserts characters
354  * above the IME, then the IME must be informed that the composition
355  * offset has changed.
356  *
357  * @return the offset of the composition
358  *
359  * @exception DWTException <ul>
360  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
361  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
362  * </ul>
363  */
364 public void setCompositionOffset (int offset) {
365     checkWidget ();
366     if (offset < 0) return;
367     if (startOffset !is -1) {
368         startOffset = offset;
369     }
370 }
371
372 LRESULT WM_IME_COMPOSITION (int /*long*/ wParam, int /*long*/ lParam) {
373     if (!isInlineEnabled ()) return null;
374     ranges = null;
375     styles = null;
376     caretOffset = commitCount = 0;
377     auto hwnd = parent.handle;
378     auto hIMC = OS.ImmGetContext (hwnd);
379     int codePage = parent.getCodePage ();
380     if (hIMC !is null) {
381         TCHAR[] buffer = null;
382         if ((lParam & OS.GCS_RESULTSTR) !is 0) {
383             int length_ = OS.ImmGetCompositionString (hIMC, OS.GCS_RESULTSTR, null, 0);
384             if (length_ > 0) {
385                 buffer = NewTCHARs (codePage, length_ / TCHAR.sizeof);
386                 OS.ImmGetCompositionString (hIMC, OS.GCS_RESULTSTR, buffer.ptr, length_);
387                 if (startOffset is -1) {
388                     Event event = new Event ();
389                     event.detail = DWT.COMPOSITION_SELECTION;
390                     sendEvent (DWT.ImeComposition, event);
391                     startOffset = event.start;
392                 }
393                 Event event = new Event ();
394                 event.detail = DWT.COMPOSITION_CHANGED;
395                 event.start = startOffset;
396                 event.end = startOffset + text.length;
397                 event.text = text = buffer !is null ? TCHARsToStr(buffer) : "";
398                 commitCount = text.length ;
399                 sendEvent (DWT.ImeComposition, event);
400                 String chars = text;
401                 text = ""; //$NON-NLS-1$
402                 startOffset = -1;
403                 commitCount = 0;
404                 if (event.doit) {
405                     Display display = this.display;
406                     display.lastKey = 0;
407                     display.lastVirtual = display.lastNull = display.lastDead = false;
408                     length_ = chars.length;
409                     for (int i = 0; i < length_; i+=chars.getRelativeCodePointOffset(i,1)) {
410                         dchar c = chars[ i .. $ ].firstCodePoint();
411                         display.lastAscii = c;
412                         event = new Event ();
413                         event.character = c;
414                         parent.sendEvent (DWT.KeyDown, event);
415                     }
416                 }
417             }
418             if ((lParam & OS.GCS_COMPSTR) is 0) return LRESULT.ONE;
419         }
420         buffer = null;
421         if ((lParam & OS.GCS_COMPSTR) !is 0) {
422             int length_ = OS.ImmGetCompositionString (hIMC, OS.GCS_COMPSTR, null, 0);
423             if (length_ > 0) {
424                 buffer = NewTCHARs (codePage, length_ / TCHAR.sizeof);
425                 OS.ImmGetCompositionString (hIMC, OS.GCS_COMPSTR, buffer.ptr, length_);
426                 if ((lParam & OS.GCS_CURSORPOS) !is 0) {
427                     caretOffset = OS.ImmGetCompositionString (hIMC, OS.GCS_CURSORPOS, null, 0);
428                 }
429                 int [] clauses = null;
430                 if ((lParam & OS.GCS_COMPCLAUSE) !is 0) {
431                     length_ = OS.ImmGetCompositionStringW (hIMC, OS.GCS_COMPCLAUSE, /+cast(int [])+/null, 0);
432                     if (length_ > 0) {
433                         clauses = new int [length_ / 4];
434                         OS.ImmGetCompositionStringW (hIMC, OS.GCS_COMPCLAUSE, clauses.ptr, length_);
435                     }
436                 }
437                 if ((lParam & OS.GCS_COMPATTR) !is 0 && clauses !is null) {
438                     length_ = OS.ImmGetCompositionStringA (hIMC, OS.GCS_COMPATTR, /+cast(byte [])+/null, 0);
439                     if (length_ > 0) {
440                         byte [] attrs = new byte [length_];
441                         OS.ImmGetCompositionStringA (hIMC, OS.GCS_COMPATTR, attrs.ptr, length_);
442                         length_ = clauses.length - 1;
443                         ranges = new int [length_ * 2];
444                         styles = new TextStyle [length_];
445                         auto layout = OS.GetKeyboardLayout (0);
446                         short langID = cast(short)OS.LOWORD ( cast(int) layout);
447                         TF_DISPLAYATTRIBUTE* attr = null;
448                         TextStyle style = null;
449                         for (int i = 0; i < length_; i++) {
450                             ranges [i * 2] = clauses [i];
451                             ranges [i * 2 + 1] = clauses [i + 1] - 1;
452                             styles [i] = style = new TextStyle ();
453                             attr = getDisplayAttribute (langID, attrs [clauses [i]]);
454                             if (attr !is null) {
455                                 switch (attr.crText.type) {
456                                     case OS.TF_CT_COLORREF:
457                                         style.foreground = Color.win32_new (display, attr.crText.cr);
458                                         break;
459                                     case OS.TF_CT_SYSCOLOR:
460                                         int colorRef = OS.GetSysColor (attr.crText.cr);
461                                         style.foreground = Color.win32_new (display, colorRef);
462                                         break;
463                                     default:
464                                 }
465                                 switch (attr.crBk.type) {
466                                     case OS.TF_CT_COLORREF:
467                                         style.background = Color.win32_new (display, attr.crBk.cr);
468                                         break;
469                                     case OS.TF_CT_SYSCOLOR:
470                                         int colorRef = OS.GetSysColor (attr.crBk.cr);
471                                         style.background = Color.win32_new (display, colorRef);
472                                         break;
473                                     default:
474                                 }
475                                 switch (attr.crLine.type) {
476                                     case OS.TF_CT_COLORREF:
477                                         style.underlineColor = Color.win32_new (display, attr.crLine.cr);
478                                         break;
479                                     case OS.TF_CT_SYSCOLOR:
480                                         int colorRef = OS.GetSysColor (attr.crLine.cr);
481                                         style.underlineColor = Color.win32_new (display, colorRef);
482                                         break;
483                                     default:
484                                 }
485                                 style.underline = attr.lsStyle !is OS.TF_LS_NONE;
486                                 switch (attr.lsStyle) {
487                                     case OS.TF_LS_SQUIGGLE:
488                                         style.underlineStyle = DWT.UNDERLINE_SQUIGGLE;
489                                         break;
490                                     case OS.TF_LS_DASH:
491                                         style.underlineStyle = UNDERLINE_IME_DASH;
492                                         break;
493                                     case OS.TF_LS_DOT:
494                                         style.underlineStyle = UNDERLINE_IME_DOT;
495                                         break;
496                                     case OS.TF_LS_SOLID:
497                                         style.underlineStyle = attr.fBoldLine ? UNDERLINE_IME_THICK : DWT.UNDERLINE_SINGLE;
498                                         break;
499                                     default:
500                                 }
501                             }
502                             delete attr;
503                         }
504                     }
505                 }
506             }
507             OS.ImmReleaseContext (hwnd, hIMC);
508         }
509         int end = startOffset + text.length;
510         if (startOffset is -1) {
511             Event event = new Event ();
512             event.detail = DWT.COMPOSITION_SELECTION;
513             sendEvent (DWT.ImeComposition, event);
514             startOffset = event.start;
515             end = event.end;
516         }
517         Event event = new Event ();
518         event.detail = DWT.COMPOSITION_CHANGED;
519         event.start = startOffset;
520         event.end = end;
521         event.text = text = buffer !is null ? TCHARsToStr(buffer) : "";
522         sendEvent (DWT.ImeComposition, event);
523         if (text.length is 0) {
524             startOffset = -1;
525             ranges = null;
526             styles = null;
527         }
528     }
529     return LRESULT.ONE;
530 }
531
532 LRESULT WM_IME_COMPOSITION_START (int /*long*/ wParam, int /*long*/ lParam) {
533     return isInlineEnabled () ? LRESULT.ONE : null;
534 }
535
536 LRESULT WM_IME_ENDCOMPOSITION (int /*long*/ wParam, int /*long*/ lParam) {
537     return isInlineEnabled () ? LRESULT.ONE : null;
538 }
539
540 LRESULT WM_KILLFOCUS (int /*long*/ wParam, int /*long*/ lParam) {
541     if (!isInlineEnabled ()) return null;
542     auto hwnd = parent.handle;
543     auto hIMC = OS.ImmGetContext (hwnd);
544     if (hIMC !is null) {
545         if (OS.ImmGetOpenStatus (hIMC)) {
546             OS.ImmNotifyIME (hIMC, OS.NI_COMPOSITIONSTR, OS.CPS_COMPLETE, 0);
547         }
548         OS.ImmReleaseContext (hwnd, hIMC);
549     }