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 }