1 /*******************************************************************************
2  * Copyright (c) 2000, 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  * Port to the D programming language:
11  *     Frank Benoit <benoit@tionex.de>
12  *******************************************************************************/
13 module org.eclipse.swt.widgets.Spinner;
14 
15 
16 import org.eclipse.swt.widgets.Composite;
17 import org.eclipse.swt.widgets.TypedListener;
18 import org.eclipse.swt.widgets.Event;
19 import org.eclipse.swt.widgets.Widget;
20 import org.eclipse.swt.SWT;
21 import org.eclipse.swt.internal.gtk.OS;
22 import org.eclipse.swt.graphics.Point;
23 import org.eclipse.swt.graphics.Rectangle;
24 import org.eclipse.swt.events.SelectionListener;
25 import org.eclipse.swt.events.SelectionEvent;
26 import org.eclipse.swt.events.ModifyListener;
27 import org.eclipse.swt.events.VerifyListener;
28 
29 import java.lang.all;
30 version(Tango){
31     import tango.util.Convert;
32 } else { // Phobos
33     import std.conv;
34 }
35 
36 /**
37  * Instances of this class are selectable user interface
38  * objects that allow the user to enter and modify numeric
39  * values.
40  * <p>
41  * Note that although this class is a subclass of <code>Composite</code>,
42  * it does not make sense to add children to it, or set a layout on it.
43  * </p><p>
44  * <dl>
45  * <dt><b>Styles:</b></dt>
46  * <dd>READ_ONLY, WRAP</dd>
47  * <dt><b>Events:</b></dt>
48  * <dd>Selection, Modify, Verify</dd>
49  * </dl>
50  * </p><p>
51  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
52  * </p>
53  *
54  * @see <a href="http://www.eclipse.org/swt/snippets/#spinner">Spinner snippets</a>
55  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
56  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
57  * 
58  * @since 3.1
59  */
60 public class Spinner : Composite {
61 
62     alias Composite.computeSize computeSize;
63     alias Composite.createHandle createHandle;
64     alias Composite.setBackgroundColor setBackgroundColor;
65     alias Composite.setCursor setCursor;
66     alias Composite.translateTraversal translateTraversal;
67 
68     static const int INNER_BORDER = 2;
69     static const int MIN_ARROW_WIDTH = 6;
70     int lastEventTime = 0;
71     GdkEventKey* gdkEventKey;
72     int fixStart = -1, fixEnd = -1;
73 
74     /**
75      * the operating system limit for the number of characters
76      * that the text field in an instance of this class can hold
77      * 
78      * @since 3.4
79      */
80     public const static int LIMIT = 0x7FFFFFFF;
81 
82 /**
83  * Constructs a new instance of this class given its parent
84  * and a style value describing its behavior and appearance.
85  * <p>
86  * The style value is either one of the style constants defined in
87  * class <code>SWT</code> which is applicable to instances of this
88  * class, or must be built by <em>bitwise OR</em>'ing together
89  * (that is, using the <code>int</code> "|" operator) two or more
90  * of those <code>SWT</code> style constants. The class description
91  * lists the style constants that are applicable to the class.
92  * Style bits are also inherited from superclasses.
93  * </p>
94  *
95  * @param parent a composite control which will be the parent of the new instance (cannot be null)
96  * @param style the style of control to construct
97  *
98  * @exception IllegalArgumentException <ul>
99  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
100  * </ul>
101  * @exception SWTException <ul>
102  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
103  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
104  * </ul>
105  *
106  * @see SWT#READ_ONLY
107  * @see SWT#WRAP
108  * @see Widget#checkSubclass
109  * @see Widget#getStyle
110  */
111 public this (Composite parent, int style) {
112     super (parent, checkStyle (style));
113 }
114 
115 /**
116  * Adds the listener to the collection of listeners who will
117  * be notified when the receiver's text is modified, by sending
118  * it one of the messages defined in the <code>ModifyListener</code>
119  * interface.
120  *
121  * @param listener the listener which should be notified
122  *
123  * @exception IllegalArgumentException <ul>
124  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
125  * </ul>
126  * @exception SWTException <ul>
127  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
128  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
129  * </ul>
130  *
131  * @see ModifyListener
132  * @see #removeModifyListener
133  */
134 public void addModifyListener (ModifyListener listener) {
135     checkWidget ();
136     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
137     TypedListener typedListener = new TypedListener (listener);
138     addListener (SWT.Modify, typedListener);
139 }
140 
141 /**
142  * Adds the listener to the collection of listeners who will
143  * be notified when the control is selected by the user, by sending
144  * it one of the messages defined in the <code>SelectionListener</code>
145  * interface.
146  * <p>
147  * <code>widgetSelected</code> is not called for texts.
148  * <code>widgetDefaultSelected</code> is typically called when ENTER is pressed in a single-line text.
149  * </p>
150  *
151  * @param listener the listener which should be notified when the control is selected by the user
152  *
153  * @exception IllegalArgumentException <ul>
154  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
155  * </ul>
156  * @exception SWTException <ul>
157  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
158  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
159  * </ul>
160  *
161  * @see SelectionListener
162  * @see #removeSelectionListener
163  * @see SelectionEvent
164  */
165 public void addSelectionListener(SelectionListener listener) {
166     checkWidget ();
167     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
168     TypedListener typedListener = new TypedListener(listener);
169     addListener(SWT.Selection,typedListener);
170     addListener(SWT.DefaultSelection,typedListener);
171 }
172 
173 /**
174  * Adds the listener to the collection of listeners who will
175  * be notified when the receiver's text is verified, by sending
176  * it one of the messages defined in the <code>VerifyListener</code>
177  * interface.
178  *
179  * @param listener the listener which should be notified
180  *
181  * @exception IllegalArgumentException <ul>
182  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
183  * </ul>
184  * @exception SWTException <ul>
185  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
186  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
187  * </ul>
188  *
189  * @see VerifyListener
190  * @see #removeVerifyListener
191  */
192 void addVerifyListener (VerifyListener listener) {
193     checkWidget();
194     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
195     TypedListener typedListener = new TypedListener (listener);
196     addListener (SWT.Verify, typedListener);
197 }
198 
199 static int checkStyle (int style) {
200     /*
201     * Even though it is legal to create this widget
202     * with scroll bars, they serve no useful purpose
203     * because they do not automatically scroll the
204     * widget's client area.  The fix is to clear
205     * the SWT style.
206     */
207     return style & ~(SWT.H_SCROLL | SWT.V_SCROLL);
208 }
209 
210 protected override void checkSubclass () {
211     if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
212 }
213 
214 public override Point computeSize (int wHint, int hHint, bool changed) {
215     checkWidget ();
216     if (wHint !is SWT.DEFAULT && wHint < 0) wHint = 0;
217     if (hHint !is SWT.DEFAULT && hHint < 0) hHint = 0;
218     int[1] w, h;
219     OS.gtk_widget_realize (handle);
220     auto layout = OS.gtk_entry_get_layout (cast(GtkEntry*)handle);
221     auto adjustment = OS.gtk_spin_button_get_adjustment (cast(GtkSpinButton*)handle);
222     int digits = OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle);
223     for (int i = 0; i < digits; i++) adjustment.upper *= 10;
224     String string = to!(String)( (cast(int) adjustment.upper) );
225     if (digits > 0) {
226         //PROTING_TODO: Efficiency
227         String buffer = string ~ getDecimalSeparator ();
228         ptrdiff_t count = digits - string.length;
229         while (count >= 0) {
230             buffer ~= "0";
231             count--;
232         }
233         string = buffer;
234     }
235     auto buffer1 = string;
236     auto ptr = OS.pango_layout_get_text (layout);
237     String buffer2 = fromStringz( ptr )._idup();
238     OS.pango_layout_set_text (layout, buffer1.ptr, cast(int)/*64bit*/buffer1.length);
239     OS.pango_layout_get_size (layout, w.ptr, h.ptr);
240     OS.pango_layout_set_text (layout, buffer2.ptr, cast(int)/*64bit*/buffer2.length);
241     int width = OS.PANGO_PIXELS (w [0]);
242     int height = OS.PANGO_PIXELS (h [0]);
243     width = wHint is SWT.DEFAULT ? width : wHint;
244     height = hHint is SWT.DEFAULT ? height : hHint;
245     Rectangle trim = computeTrim (0, 0, width, height);
246     return new Point (trim.width, trim.height);
247 }
248 
249 override public Rectangle computeTrim (int x, int y, int width, int height) {
250     checkWidget ();
251     int xborder = 0, yborder = 0;
252     auto style = OS.gtk_widget_get_style (handle);
253     if ((this.style & SWT.BORDER) !is 0) {
254         xborder += OS.gtk_style_get_xthickness (style);
255         yborder += OS.gtk_style_get_ythickness (style);
256     }
257     xborder += INNER_BORDER;
258     yborder += INNER_BORDER;
259     int property;
260     OS.gtk_widget_style_get1 (handle, OS.interior_focus.ptr, &property);
261     if (property is 0) {
262         OS.gtk_widget_style_get1 (handle, OS.focus_line_width.ptr, &property);
263         xborder += property;
264         yborder += property;
265     }
266     auto fontDesc = OS.gtk_style_get_font_desc (style);
267     int fontSize = OS.pango_font_description_get_size (fontDesc);
268     int arrowSize = Math.max (OS.PANGO_PIXELS (fontSize), MIN_ARROW_WIDTH);
269     arrowSize = arrowSize - arrowSize % 2;
270     Rectangle trim = super.computeTrim (x, y, width, height);
271     trim.x -= xborder;
272     trim.y -= yborder;
273     trim.width += 2 * xborder;
274     trim.height += 2 * yborder;
275     trim.width += arrowSize + (2 * OS.gtk_style_get_xthickness (style));
276     return new Rectangle (trim.x, trim.y, trim.width, trim.height);
277 }
278 
279 /**
280  * Copies the selected text.
281  * <p>
282  * The current selection is copied to the clipboard.
283  * </p>
284  *
285  * @exception SWTException <ul>
286  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
287  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
288  * </ul>
289  */
290 public void copy () {
291     checkWidget ();
292     OS.gtk_editable_copy_clipboard (cast(GtkEditable*)handle);
293 }
294 
295 override void createHandle (int index) {
296     state |= HANDLE | MENU;
297     fixedHandle = cast(GtkWidget*)OS.g_object_new (display.gtk_fixed_get_type (), null);
298     if (fixedHandle is null) error (SWT.ERROR_NO_HANDLES);
299     OS.gtk_fixed_set_has_window (cast(GtkFixed*)fixedHandle, true);
300     auto adjustment = OS.gtk_adjustment_new (0, 0, 100, 1, 10, 0);
301     if (adjustment is null) error (SWT.ERROR_NO_HANDLES);
302     handle = cast(GtkWidget*)OS.gtk_spin_button_new (cast(GtkAdjustment*)adjustment, 1, 0);
303     if (handle is null) error (SWT.ERROR_NO_HANDLES);
304     OS.gtk_container_add (cast(GtkContainer*)fixedHandle, handle);
305     OS.gtk_editable_set_editable (cast(GtkEditable*)handle, (style & SWT.READ_ONLY) is 0);
306     OS.gtk_entry_set_has_frame (cast(GtkEntry*)handle, (style & SWT.BORDER) !is 0);
307     OS.gtk_spin_button_set_wrap (cast(GtkSpinButton*)handle, (style & SWT.WRAP) !is 0);
308 }
309 
310 /**
311  * Cuts the selected text.
312  * <p>
313  * The current selection is first copied to the
314  * clipboard and then deleted from the widget.
315  * </p>
316  *
317  * @exception SWTException <ul>
318  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
319  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
320  * </ul>
321  */
322 public void cut () {
323     checkWidget ();
324     OS.gtk_editable_cut_clipboard (cast(GtkEditable*)handle);
325 }
326 
327 override void deregister () {
328     super.deregister ();
329     auto imContext = imContext ();
330     if (imContext !is null) display.removeWidget (cast(GtkWidget*)imContext);
331 }
332 
333 override GdkDrawable* eventWindow () {
334     return paintWindow ();
335 }
336 
337 override GtkWidget* enterExitHandle () {
338     return fixedHandle;
339 }
340 
341 override bool filterKey (int keyval, GdkEventKey* event) {
342     int time = OS.gdk_event_get_time (cast(GdkEvent*)event);
343     if (time !is lastEventTime) {
344         lastEventTime = time;
345         auto imContext = imContext ();
346         if (imContext !is null) {
347             return cast(bool)OS.gtk_im_context_filter_keypress (imContext, event);
348         }
349     }
350     gdkEventKey = event;
351     return false;
352 }
353 
354 void fixIM () {
355     /*
356     *  The IM filter has to be called one time for each key press event.
357     *  When the IM is open the key events are duplicated. The first event
358     *  is filtered by SWT and the second event is filtered by GTK.  In some
359     *  cases the GTK handler does not run (the widget is destroyed, the
360     *  application code consumes the event, etc), for these cases the IM
361     *  filter has to be called by SWT.
362     */
363     if (gdkEventKey !is null && gdkEventKey !is cast(GdkEventKey*)-1) {
364         auto imContext = imContext ();
365         if (imContext !is null) {
366             OS.gtk_im_context_filter_keypress (imContext, gdkEventKey);
367             gdkEventKey = cast(GdkEventKey*)-1;
368             return;
369         }
370     }
371     gdkEventKey = null;
372 }
373 
374 override GdkColor* getBackgroundColor () {
375     return getBaseColor ();
376 }
377 
378 public override int getBorderWidth () {
379     checkWidget();
380     auto style = OS.gtk_widget_get_style (handle);
381     if ((this.style & SWT.BORDER) !is 0) {
382          return OS.gtk_style_get_xthickness (style);
383     }
384     return 0;
385 }
386 
387 override GdkColor* getForegroundColor () {
388     return getTextColor ();
389 }
390 
391 /**
392  * Returns the amount that the receiver's value will be
393  * modified by when the up/down arrows are pressed.
394  *
395  * @return the increment
396  *
397  * @exception SWTException <ul>
398  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
399  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
400  * </ul>
401  */
402 public int getIncrement () {
403     checkWidget ();
404     auto adjustment = OS.gtk_spin_button_get_adjustment (cast(GtkSpinButton*)handle);
405     int digits = OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle);
406     auto value = adjustment.step_increment;
407     for (int i = 0; i < digits; i++) value *= 10;
408     return cast(int) (value > 0 ? value + 0.5 : value - 0.5);
409 }
410 
411 /**
412  * Returns the maximum value which the receiver will allow.
413  *
414  * @return the maximum
415  *
416  * @exception SWTException <ul>
417  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
418  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
419  * </ul>
420  */
421 public int getMaximum () {
422     checkWidget ();
423     auto adjustment = OS.gtk_spin_button_get_adjustment (cast(GtkSpinButton*)handle);
424     int digits = OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle);
425     auto value = adjustment.upper;
426     for (int i = 0; i < digits; i++) value *= 10;
427     return cast(int) (value > 0 ? value + 0.5 : value - 0.5);
428 }
429 
430 /**
431  * Returns the minimum value which the receiver will allow.
432  *
433  * @return the minimum
434  *
435  * @exception SWTException <ul>
436  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
437  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
438  * </ul>
439  */
440 public int getMinimum () {
441     checkWidget ();
442     auto adjustment = OS.gtk_spin_button_get_adjustment (cast(GtkSpinButton*)handle);
443     int digits = OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle);
444     auto value = adjustment.lower;
445     for (int i = 0; i < digits; i++) value *= 10;
446     return cast(int) (value > 0 ? value + 0.5 : value - 0.5);
447 }
448 
449 /**
450  * Returns the amount that the receiver's position will be
451  * modified by when the page up/down keys are pressed.
452  *
453  * @return the page increment
454  *
455  * @exception SWTException <ul>
456  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
457  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
458  * </ul>
459  */
460 public int getPageIncrement () {
461     checkWidget ();
462     auto adjustment = OS.gtk_spin_button_get_adjustment (cast(GtkSpinButton*)handle);
463     int digits = OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle);
464     auto value = adjustment.page_increment;
465     for (int i = 0; i < digits; i++) value *= 10;
466     return cast(int) (value > 0 ? value + 0.5 : value - 0.5);
467 }
468 
469 /**
470  * Returns the <em>selection</em>, which is the receiver's position.
471  *
472  * @return the selection
473  *
474  * @exception SWTException <ul>
475  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
476  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
477  * </ul>
478  */
479 public int getSelection () {
480     checkWidget ();
481     auto adjustment = OS.gtk_spin_button_get_adjustment (cast(GtkSpinButton*)handle);
482     int digits = OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle);
483     auto value = adjustment.value;
484     for (int i = 0; i < digits; i++) value *= 10;
485     return cast(int) (value > 0 ? value + 0.5 : value - 0.5);
486 }
487 
488 /**
489  * Returns a string containing a copy of the contents of the
490  * receiver's text field, or an empty string if there are no
491  * contents.
492  *
493  * @return the receiver's text
494  *
495  * @exception SWTException <ul>
496  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
497  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
498  * </ul>
499  * 
500  * @since 3.4
501  */
502 public String getText () {
503     checkWidget ();
504     auto str = OS.gtk_entry_get_text (handle);
505     return fromStringz(str)._idup();
506 }
507 
508 /**
509  * Returns the maximum number of characters that the receiver's
510  * text field is capable of holding. If this has not been changed
511  * by <code>setTextLimit()</code>, it will be the constant
512  * <code>Spinner.LIMIT</code>.
513  * 
514  * @return the text limit
515  * 
516  * @exception SWTException <ul>
517  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
518  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
519  * </ul>
520  *
521  * @see #LIMIT
522  * 
523  * @since 3.4
524  */
525 public int getTextLimit () {
526     checkWidget ();
527     int limit = OS.gtk_entry_get_max_length (handle);
528     return limit is 0 ? 0xFFFF : limit;
529 }
530 
531 /**
532  * Returns the number of decimal places used by the receiver.
533  *
534  * @return the digits
535  *
536  * @exception SWTException <ul>
537  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
538  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
539  * </ul>
540  */
541 public int getDigits () {
542     checkWidget ();
543     return OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle);
544 }
545 
546 String getDecimalSeparator () {
547     auto ptr = OS.localeconv_decimal_point ();
548     return fromStringz( ptr )._idup();
549 }
550 
551 override int gtk_activate (GtkWidget* widget) {
552     postEvent (SWT.DefaultSelection);
553     return 0;
554 }
555 
556 override int gtk_changed (GtkWidget* widget) {
557     auto str = OS.gtk_entry_get_text (cast(GtkEntry*)handle);
558     int length = OS.strlen (str);
559     if (length > 0) {
560         char* endptr;
561         double value = OS.g_strtod (str, &endptr);
562         if (endptr is str + length) {
563             auto adjustment = OS.gtk_spin_button_get_adjustment (cast(GtkSpinButton*)handle);
564             if (value !is adjustment.value && adjustment.lower <= value && value <= adjustment.upper) {
565                 OS.gtk_spin_button_update (cast(GtkSpinButton*)handle);
566             }
567         }
568     }
569 
570     /*
571     * Feature in GTK.  When the user types, GTK positions
572     * the caret after sending the changed signal.  This
573     * means that application code that attempts to position
574     * the caret during a changed signal will fail.  The fix
575     * is to post the modify event when the user is typing.
576     */
577     bool keyPress = false;
578     auto eventPtr = OS.gtk_get_current_event ();
579     if (eventPtr !is null) {
580         GdkEventKey* gdkEvent = cast(GdkEventKey*)eventPtr;
581         switch (gdkEvent.type) {
582             case OS.GDK_KEY_PRESS:
583                 keyPress = true;
584                 break;
585             default:
586         }
587         OS.gdk_event_free (eventPtr);
588     }
589     if (keyPress) {
590         postEvent (SWT.Modify);
591     } else {
592         sendEvent (SWT.Modify);
593     }
594     return 0;
595 }
596 
597 override
598 int gtk_commit (GtkIMContext* imContext, char* text) {
599     if (text is null) return 0;
600     if (!OS.gtk_editable_get_editable (cast(GtkEditable*)handle)) return 0;
601     char [] chars = fromStringz( text ).dup;
602     if (chars.length is 0) return 0;
603     char [] newChars = sendIMKeyEvent (SWT.KeyDown, null, chars);
604     if (newChars is null) return 0;
605     /*
606     * Feature in GTK.  For a GtkEntry, during the insert-text signal,
607     * GTK allows the programmer to change only the caret location,
608     * not the selection.  If the programmer changes the selection,
609     * the new selection is lost.  The fix is to detect a selection
610     * change and set it after the insert-text signal has completed.
611     */
612     fixStart = fixEnd = -1;
613     OS.g_signal_handlers_block_matched (imContext, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udCOMMIT);
614     int id = OS.g_signal_lookup (OS.commit.ptr, OS.gtk_im_context_get_type ());
615     int mask =  OS.G_SIGNAL_MATCH_DATA | OS.G_SIGNAL_MATCH_ID;
616     OS.g_signal_handlers_unblock_matched (imContext, mask, id, 0, null, null, cast(void*)handle);
617     if (newChars is chars) {
618         OS.g_signal_emit_by_name1 (imContext, OS.commit.ptr, cast(int)text);
619     } else {
620         OS.g_signal_emit_by_name1 (imContext, OS.commit.ptr, cast(int)toStringz(newChars));
621     }
622     OS.g_signal_handlers_unblock_matched (imContext, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udCOMMIT);
623     OS.g_signal_handlers_block_matched (imContext, mask, id, 0, null, null, cast(void*)handle);
624     if (fixStart !is -1 && fixEnd !is -1) {
625         OS.gtk_editable_set_position (cast(GtkEditable*)handle, fixStart);
626         OS.gtk_editable_select_region (cast(GtkEditable*)handle, fixStart, fixEnd);
627     }
628     fixStart = fixEnd = -1;
629     return 0;
630 }
631 
632 override int gtk_delete_text (GtkWidget* widget, ptrdiff_t start_pos, ptrdiff_t end_pos) {
633     if (!hooks (SWT.Verify) && !filters (SWT.Verify)) return 0;
634     String newText = verifyText ("", cast(int)/*64bit*/start_pos, cast(int)/*64bit*/end_pos);
635     if (newText is null) {
636         OS.g_signal_stop_emission_by_name (handle, OS.delete_text.ptr);
637     } else {
638         if (newText.length > 0) {
639             int pos;
640             pos = cast(int)/*64bit*/end_pos;
641             OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udCHANGED);
642             OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udINSERT_TEXT);
643             OS.gtk_editable_insert_text (cast(GtkEditable*)handle, newText.ptr, cast(int)/*64bit*/newText.length, &pos);
644             OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udINSERT_TEXT);
645             OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udCHANGED);
646             OS.gtk_editable_set_position (cast(GtkEditable*)handle, pos);
647         }
648     }
649     return 0;
650 }
651 
652 override int gtk_event_after (GtkWidget* widget, GdkEvent* gdkEvent) {
653     if (cursor !is null) gtk_setCursor (cursor.handle);
654     return super.gtk_event_after (widget, gdkEvent);
655 }
656 
657 override int gtk_focus_out_event (GtkWidget* widget, GdkEventFocus* event) {
658     fixIM ();
659     return super.gtk_focus_out_event (widget, event);
660 }
661 
662 override int gtk_insert_text (GtkEditable* widget, char* new_text, ptrdiff_t new_text_length, ptrdiff_t position) {
663 //  if (!hooks (SWT.Verify) && !filters (SWT.Verify)) return 0;
664     if (new_text is null || new_text_length is 0) return 0;
665     String oldText = new_text[ 0 .. new_text_length ]._idup();
666     int pos;
667     pos = *(cast(int*)position);
668     if (pos is -1) {
669         auto ptr = OS.gtk_entry_get_text (cast(GtkEntry*)handle);
670         pos = cast(int)/*64bit*/OS.g_utf8_strlen (ptr, -1);
671     }
672     String newText = verifyText (oldText, pos, pos);
673     if (newText !is oldText) {
674         int newStart, newEnd;
675         OS.gtk_editable_get_selection_bounds (cast(GtkEditable*)handle, &newStart, &newEnd);
676         if (newText !is null) {
677             if (newStart !is newEnd) {
678                 OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udDELETE_TEXT);
679                 OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udCHANGED);
680                 OS.gtk_editable_delete_selection (cast(GtkEditable*)handle);
681                 OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udDELETE_TEXT);
682                 OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udCHANGED);
683             }
684             OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udINSERT_TEXT);
685             OS.gtk_editable_insert_text (cast(GtkEditable*)handle, newText.ptr, cast(int)/*64bit*/newText.length, &pos);
686             OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udINSERT_TEXT);
687             newStart = newEnd = pos;
688         }
689         pos = newEnd;
690         if (newStart !is newEnd ) {
691             fixStart = newStart ;
692             fixEnd = newEnd ;
693         }
694         *(cast(int*)position) = pos;
695         OS.g_signal_stop_emission_by_name (handle, OS.insert_text.ptr);
696     }
697     return 0;
698 }
699 
700 override int gtk_key_press_event (GtkWidget* widget, GdkEventKey* event) {
701     auto result = super.gtk_key_press_event (widget, event);
702     if (result !is 0) fixIM ();
703     if (gdkEventKey is cast(GdkEventKey*)-1) result = 1;
704     gdkEventKey = null;
705     return result;
706 }
707 
708 override int gtk_populate_popup (GtkWidget* widget, GtkWidget* menu) {
709     if ((style & SWT.RIGHT_TO_LEFT) !is 0) {
710         OS.gtk_widget_set_direction (menu, OS.GTK_TEXT_DIR_RTL);
711         display.doSetDirectionProc( menu, OS.GTK_TEXT_DIR_RTL);
712     }
713     return 0;
714 }
715 
716 override int gtk_value_changed (int  adjustment) {
717     postEvent (SWT.Selection);
718     return 0;
719 }
720 
721 override void hookEvents () {
722     super.hookEvents();
723     OS.g_signal_connect_closure (handle, OS.changed.ptr, display.closures [CHANGED], true);
724     OS.g_signal_connect_closure (handle, OS.insert_text.ptr, display.closures [INSERT_TEXT], false);
725     OS.g_signal_connect_closure (handle, OS.delete_text.ptr, display.closures [DELETE_TEXT], false);
726     OS.g_signal_connect_closure (handle, OS.value_changed.ptr, display.closures [VALUE_CHANGED], false);
727     OS.g_signal_connect_closure (handle, OS.activate.ptr, display.closures [ACTIVATE], false);
728     OS.g_signal_connect_closure (handle, OS.populate_popup.ptr, display.closures [POPULATE_POPUP], false);
729     auto imContext = imContext ();
730     if (imContext !is null) {
731         OS.g_signal_connect_closure (imContext, OS.commit.ptr, display.closures [COMMIT], false);
732         int id = OS.g_signal_lookup (OS.commit.ptr, OS.gtk_im_context_get_type ());
733         int mask =  OS.G_SIGNAL_MATCH_DATA | OS.G_SIGNAL_MATCH_ID;
734         OS.g_signal_handlers_block_matched (imContext, mask, id, 0, null, null, cast(void*)handle);
735     }
736 }
737 
738 GtkIMContext* imContext () {
739     return OS.GTK_ENTRY_IM_CONTEXT (cast(GtkEntry*)handle);
740 }
741 
742 override GdkDrawable* paintWindow () {
743     auto window = super.paintWindow ();
744     auto children = OS.gdk_window_get_children (window);
745     if (children !is null) window = cast(GdkDrawable*)OS.g_list_data (children);
746     OS.g_list_free (children);
747     return window;
748 }
749 
750 /**
751  * Pastes text from clipboard.
752  * <p>
753  * The selected text is deleted from the widget
754  * and new text inserted from the clipboard.
755  * </p>
756  *
757  * @exception SWTException <ul>
758  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
759  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
760  * </ul>
761  */
762 public void paste () {
763     checkWidget ();
764     OS.gtk_editable_paste_clipboard (cast(GtkEditable*)handle);
765 }
766 
767 override void register () {
768     super.register ();
769     auto imContext = imContext ();
770     if (imContext !is null) display.addWidget (cast(GtkWidget*)imContext, this);
771 }
772 
773 override void releaseWidget () {
774     super.releaseWidget ();
775     fixIM ();
776 }
777 
778 /**
779  * Removes the listener from the collection of listeners who will
780  * be notified when the receiver's text is modified.
781  *
782  * @param listener the listener which should no longer be notified
783  *
784  * @exception IllegalArgumentException <ul>
785  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
786  * </ul>
787  * @exception SWTException <ul>
788  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
789  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
790  * </ul>
791  *
792  * @see ModifyListener
793  * @see #addModifyListener
794  */
795 public void removeModifyListener (ModifyListener listener) {
796     checkWidget ();
797     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
798     if (eventTable is null) return;
799     eventTable.unhook (SWT.Modify, listener);
800 }
801 
802 /**
803  * Removes the listener from the collection of listeners who will
804  * be notified when the control is selected by the user.
805  *
806  * @param listener the listener which should no longer be notified
807  *
808  * @exception IllegalArgumentException <ul>
809  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
810  * </ul>
811  * @exception SWTException <ul>
812  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
813  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
814  * </ul>
815  *
816  * @see SelectionListener
817  * @see #addSelectionListener
818  */
819 public void removeSelectionListener(SelectionListener listener) {
820     checkWidget ();
821     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
822     if (eventTable is null) return;
823     eventTable.unhook(SWT.Selection, listener);
824     eventTable.unhook(SWT.DefaultSelection,listener);
825 }
826 
827 /**
828  * Removes the listener from the collection of listeners who will
829  * be notified when the control is verified.
830  *
831  * @param listener the listener which should be notified
832  *
833  * @exception IllegalArgumentException <ul>
834  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
835  * </ul>
836  * @exception SWTException <ul>
837  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
838  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
839  * </ul>
840  *
841  * @see VerifyListener
842  * @see #addVerifyListener
843  */
844 void removeVerifyListener (VerifyListener listener) {
845     checkWidget ();
846     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
847     if (eventTable is null) return;
848     eventTable.unhook (SWT.Verify, listener);
849 }
850 
851 override void setBackgroundColor (GdkColor* color) {
852     super.setBackgroundColor (color);
853     OS.gtk_widget_modify_base (handle, 0, color);
854 }
855 
856 override void gtk_setCursor (GdkCursor* cursor) {
857     GdkCursor* defaultCursor;
858     if (cursor is null) defaultCursor = OS.gdk_cursor_new (OS.GDK_XTERM);
859     super.gtk_setCursor (cursor !is null ? cursor : defaultCursor);
860     if (cursor is null) OS.gdk_cursor_destroy (defaultCursor);
861 }
862 
863 override void setFontDescription (PangoFontDescription* font) {
864     super.setFontDescription (font);
865 }
866 
867 /**
868  * Sets the amount that the receiver's value will be
869  * modified by when the up/down arrows are pressed to
870  * the argument, which must be at least one.
871  *
872  * @param value the new increment (must be greater than zero)
873  *
874  * @exception SWTException <ul>
875  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
876  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
877  * </ul>
878  */
879 public void setIncrement (int value) {
880     checkWidget ();
881     if (value < 1) return;
882     auto adjustment = OS.gtk_spin_button_get_adjustment (cast(GtkSpinButton*)handle);
883     double newValue = value;
884     int digits = OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle);
885     for (int i = 0; i < digits; i++) newValue /= 10;
886     OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udVALUE_CHANGED);
887     OS.gtk_spin_button_set_increments (cast(GtkSpinButton*)handle, newValue, adjustment.page_increment);
888     OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udVALUE_CHANGED);
889 }
890 
891 /**
892  * Sets the maximum value that the receiver will allow.  This new
893  * value will be ignored if it is not greater than the receiver's current
894  * minimum value.  If the new maximum is applied then the receiver's
895  * selection value will be adjusted if necessary to fall within its new range.
896  *
897  * @param value the new maximum, which must be greater than the current minimum
898  *
899  * @exception SWTException <ul>
900  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
901  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
902  * </ul>
903  */
904 public void setMaximum (int value) {
905     checkWidget ();
906     auto adjustment = OS.gtk_spin_button_get_adjustment (cast(GtkSpinButton*)handle);
907     double newValue = value;
908     int digits = OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle);
909     for (int i = 0; i < digits; i++) newValue /= 10;
910     if (newValue <= adjustment.lower) return;
911     OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udVALUE_CHANGED);
912     OS.gtk_spin_button_set_range (cast(GtkSpinButton*)handle, adjustment.lower, newValue);
913     OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udVALUE_CHANGED);
914 }
915 
916 /**
917  * Sets the minimum value that the receiver will allow.  This new
918  * value will be ignored if it is not less than the receiver's
919  * current maximum value.  If the new minimum is applied then the receiver's
920  * selection value will be adjusted if necessary to fall within its new range.
921  *
922  * @param value the new minimum, which must be less than the current maximum
923  *
924  * @exception SWTException <ul>
925  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
926  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
927  * </ul>
928  */
929 public void setMinimum (int value) {
930     checkWidget ();
931     auto adjustment = OS.gtk_spin_button_get_adjustment (cast(GtkSpinButton*)handle);
932     double newValue = value;
933     int digits = OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle);
934     for (int i = 0; i < digits; i++) newValue /= 10;
935     if (newValue >= adjustment.upper) return;
936     OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udVALUE_CHANGED);
937     OS.gtk_spin_button_set_range (cast(GtkSpinButton*)handle, newValue, adjustment.upper);
938     OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udVALUE_CHANGED);
939 }
940 
941 /**
942  * Sets the amount that the receiver's position will be
943  * modified by when the page up/down keys are pressed
944  * to the argument, which must be at least one.
945  *
946  * @param value the page increment (must be greater than zero)
947  *
948  * @exception SWTException <ul>
949  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
950  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
951  * </ul>
952  */
953 public void setPageIncrement (int value) {
954     checkWidget ();
955     if (value < 1) return;
956     auto adjustment = OS.gtk_spin_button_get_adjustment (cast(GtkSpinButton*)handle);
957     double newValue = value;
958     int digits = OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle);
959     for (int i = 0; i < digits; i++) newValue /= 10;
960     OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udVALUE_CHANGED);
961     OS.gtk_spin_button_set_increments (cast(GtkSpinButton*)handle, adjustment.step_increment, newValue);
962     OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udVALUE_CHANGED);
963 }
964 
965 /**
966  * Sets the <em>selection</em>, which is the receiver's
967  * position, to the argument. If the argument is not within
968  * the range specified by minimum and maximum, it will be
969  * adjusted to fall within this range.
970  *
971  * @param value the new selection (must be zero or greater)
972  *
973  * @exception SWTException <ul>
974  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
975  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
976  * </ul>
977  */
978 public void setSelection (int value) {
979     checkWidget ();
980     double newValue = value;
981     int digits = OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle);
982     for (int i = 0; i < digits; i++) newValue /= 10;
983     OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udVALUE_CHANGED);
984     OS.gtk_spin_button_set_value (cast(GtkSpinButton*)handle, newValue);
985     OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udVALUE_CHANGED);
986 }
987 
988 /**
989  * Sets the maximum number of characters that the receiver's
990  * text field is capable of holding to be the argument.
991  * <p>
992  * To reset this value to the default, use <code>setTextLimit(Spinner.LIMIT)</code>.
993  * Specifying a limit value larger than <code>Spinner.LIMIT</code> sets the
994  * receiver's limit to <code>Spinner.LIMIT</code>.
995  * </p>
996  * @param limit new text limit
997  *
998  * @exception IllegalArgumentException <ul>
999  *    <li>ERROR_CANNOT_BE_ZERO - if the limit is zero</li>
1000  * </ul>
1001  * @exception SWTException <ul>
1002  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1003  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1004  * </ul>
1005  * 
1006  * @see #LIMIT
1007  * 
1008  * @since 3.4
1009  */
1010 public void setTextLimit (int limit) {
1011     checkWidget ();
1012     if (limit is 0) error (SWT.ERROR_CANNOT_BE_ZERO);
1013     OS.gtk_entry_set_max_length (handle, limit);
1014 }
1015 
1016 /**
1017  * Sets the number of decimal places used by the receiver.
1018  * <p>
1019  * The digit setting is used to allow for floating point values in the receiver.
1020  * For example, to set the selection to a floating point value of 1.37 call setDigits() with
1021  * a value of 2 and setSelection() with a value of 137. Similarly, if getDigits() has a value
1022  * of 2 and getSelection() returns 137 this should be interpreted as 1.37. This applies to all
1023  * numeric APIs.
1024  * </p>
1025  *
1026  * @param value the new digits (must be greater than or equal to zero)
1027  *
1028  * @exception IllegalArgumentException <ul>
1029  *    <li>ERROR_INVALID_ARGUMENT - if the value is less than zero</li>
1030  * </ul>
1031  * @exception SWTException <ul>
1032  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1033  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1034  * </ul>
1035  */
1036 public void setDigits (int value) {
1037     checkWidget ();
1038     if (value < 0) error (SWT.ERROR_INVALID_ARGUMENT);
1039     int digits = OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle);
1040     if (value is digits) return;
1041     auto adjustment = OS.gtk_spin_button_get_adjustment (cast(GtkSpinButton*)handle);
1042     int diff = Math.abs (value - digits);
1043     int factor = 1;
1044     for (int i = 0; i < diff; i++) factor *= 10;
1045     if (digits > value) {
1046         adjustment.value *= factor;
1047         adjustment.upper *= factor;
1048         adjustment.lower *= factor;
1049         adjustment.step_increment *= factor;
1050         adjustment.page_increment *= factor;
1051     } else {
1052         adjustment.value /= factor;
1053         adjustment.upper /= factor;
1054         adjustment.lower /= factor;
1055         adjustment.step_increment /= factor;
1056         adjustment.page_increment /= factor;
1057     }
1058     OS.gtk_spin_button_set_digits (cast(GtkSpinButton*)handle, value);
1059 }
1060 
1061 /**
1062  * Sets the receiver's selection, minimum value, maximum
1063  * value, digits, increment and page increment all at once.
1064  * <p>
1065  * Note: This is similar to setting the values individually
1066  * using the appropriate methods, but may be implemented in a
1067  * more efficient fashion on some platforms.
1068  * </p>
1069  *
1070  * @param selection the new selection value
1071  * @param minimum the new minimum value
1072  * @param maximum the new maximum value
1073  * @param digits the new digits value
1074  * @param increment the new increment value
1075  * @param pageIncrement the new pageIncrement value
1076  *
1077  * @exception SWTException <ul>
1078  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1079  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1080  * </ul>
1081  *
1082  * @since 3.2
1083  */
1084 public void setValues (int selection, int minimum, int maximum, int digits, int increment, int pageIncrement) {
1085     checkWidget ();
1086     if (maximum <= minimum) return;
1087     if (digits < 0) return;
1088     if (increment < 1) return;
1089     if (pageIncrement < 1) return;
1090     selection = Math.min (Math.max (minimum, selection), maximum);
1091     double factor = 1;
1092     for (int i = 0; i < digits; i++) factor *= 10;
1093     OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udVALUE_CHANGED);
1094     OS.gtk_spin_button_set_range (cast(GtkSpinButton*)handle, minimum / factor, maximum / factor);
1095     OS.gtk_spin_button_set_increments (cast(GtkSpinButton*)handle, increment / factor, pageIncrement / factor);
1096     OS.gtk_spin_button_set_value (cast(GtkSpinButton*)handle, selection / factor);
1097     OS.gtk_spin_button_set_digits (cast(GtkSpinButton*)handle, digits);
1098     OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udVALUE_CHANGED);
1099 }
1100 
1101 override bool translateTraversal (GdkEventKey* keyEvent) {
1102     int key = keyEvent.keyval;
1103     GtkIMContext* imContext = null;
1104     switch (key) {
1105         case OS.GDK_KP_Enter:
1106         case OS.GDK_Return: {
1107             imContext =  this.imContext ();
1108             if (imContext !is null) {
1109                 char* preeditString;
1110                 OS.gtk_im_context_get_preedit_string (imContext, &preeditString, null, null);
1111                 if (preeditString !is null) {
1112                     int length = OS.strlen (preeditString);
1113                     OS.g_free (preeditString);
1114                     if (length !is 0) return false;
1115                 }
1116             }
1117         default:
1118         }
1119     }
1120     return super.translateTraversal (keyEvent);
1121 }
1122 
1123 String verifyText (String string, int start, int end) {
1124     if (string.length is 0 && start is end) return null;
1125     Event event = new Event ();
1126     event.text = string;
1127     event.start = start;
1128     event.end = end;
1129     auto eventPtr = OS.gtk_get_current_event ();
1130     if (eventPtr !is null) {
1131         GdkEventKey* gdkEvent = cast(GdkEventKey*)eventPtr;
1132         switch (gdkEvent.type) {
1133             case OS.GDK_KEY_PRESS:
1134                 setKeyState (event, gdkEvent);
1135                 break;
1136             default:
1137         }
1138         OS.gdk_event_free (eventPtr);
1139     }
1140     int index = 0;
1141     if (OS.gtk_spin_button_get_digits (cast(GtkSpinButton*)handle) > 0) {
1142         String decimalSeparator = getDecimalSeparator ();
1143         index = string.indexOf( decimalSeparator );
1144         if (index !is -1 ) {
1145             string = string.substring( 0, index ) ~ string.substring( index + 1 );
1146         }
1147         index = 0;
1148     }
1149     if (string.length  > 0) {
1150         auto adjustment = OS.gtk_spin_button_get_adjustment (handle);
1151         if (adjustment.lower < 0 && string.charAt (0) is '-') index++;
1152     }
1153     while (index < string.length) {
1154         if (!CharacterIsDigit (string.charAt(index))) break;
1155         index++;
1156     }
1157     event.doit = index is string.length;
1158     /*
1159      * It is possible (but unlikely), that application
1160      * code could have disposed the widget in the verify
1161      * event.  If this happens, answer null to cancel
1162      * the operation.
1163      */
1164     sendEvent (SWT.Verify, event);
1165     if (!event.doit || isDisposed ()) return null;
1166     return event.text;
1167 }
1168 
1169 }