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.DateTime;
14 
15 import org.eclipse.swt.SWT;
16 import org.eclipse.swt.SWTException;
17 import org.eclipse.swt.events.SelectionEvent;
18 import org.eclipse.swt.events.SelectionListener;
19 import org.eclipse.swt.graphics.Color;
20 import org.eclipse.swt.graphics.Font;
21 import org.eclipse.swt.graphics.GC;
22 import org.eclipse.swt.graphics.Point;
23 import org.eclipse.swt.graphics.Rectangle;
24 import org.eclipse.swt.internal.gtk.OS;
25 import org.eclipse.swt.internal.Compatibility;
26 
27 import org.eclipse.swt.widgets.Composite;
28 import org.eclipse.swt.widgets.Listener;
29 import org.eclipse.swt.widgets.Button;
30 import org.eclipse.swt.widgets.Event;
31 import org.eclipse.swt.widgets.Text;
32 import org.eclipse.swt.widgets.TypedListener;
33 
34 import java.lang.all;
35 
36 version(Tango){
37     import tango.util.Convert;
38 
39     static import tango.text.Util;
40     //static import tango.text.locale.Core;
41     static import tango.time.Time;
42     static import tango.time.WallClock;
43     static import tango.time.chrono.Gregorian;
44     static import tango.time.chrono.Calendar;
45 } else { // Phobos
46     import std.conv;
47     static import std.datetime;
48 }
49 
50 
51 private class Calendar{
52     enum {
53         AM,
54         PM
55     }
56     enum {
57         AM_PM,
58         HOUR,
59         MINUTE,
60         SECOND,
61         MONTH,
62         YEAR,
63         DAY_OF_MONTH,
64         DAY_SELECTED,
65         MONTH_CHANGED,
66         HOUR_OF_DAY,
67     }
68     private static const int[] MONTH_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
69     static private Calendar instance;
70 
71     private int second;
72     private int minute;
73     private int hour;
74     private int dayofmonth;
75     private int month;
76     private int year;
77 
78     static Calendar getInstance(){
79         if( instance is null ){
80             synchronized {
81                 if( instance is null ){
82                     instance = new Calendar;
83                 }
84             }
85         }
86         return instance;
87     }
88 
89     public this(){
90         version(Tango){
91             tango.time.Time.Time time = tango.time.WallClock.WallClock.now();
92             tango.time.Time.TimeSpan span = time.time.span;
93             this.second = span.seconds % 60;
94             this.minute = span.minutes % 60;
95             this.hour   = span.hours;
96             auto greg = tango.time.chrono.Gregorian.Gregorian.generic;
97             this.dayofmonth = greg.getDayOfMonth( time );
98             this.month      = greg.getMonth( time );
99             this.year       = greg.getYear( time );
100         } else { // Phobos
101             auto time = std.datetime.Clock.currTime();
102             this.second     = time.second;
103             this.minute     = time.minute;
104             this.hour       = time.hour;
105             this.dayofmonth = time.day;
106             this.month      = time.month;
107             this.year       = time.year;
108         }
109     }
110     int getActualMaximum(int field){
111         switch( field ){
112         case YEAR:
113             return 2100;
114         case MONTH:
115             return cast(int)MONTH_DAYS.length -1;
116         case DAY_OF_MONTH:
117             return MONTH_DAYS[month];
118         case HOUR:
119             return 11;
120         case HOUR_OF_DAY:
121             return 23;
122         case MINUTE:
123             return 59;
124         case SECOND:
125             return 59;
126         case AM_PM:
127             return PM;
128         default: assert( false, Format( "no matching switch case for field {}.", field ));
129         }
130     }
131 
132     int getActualMinimum(int field){
133         switch( field ){
134         case YEAR:
135             return 1900;
136         case MONTH:
137             return 0;
138         case DAY_OF_MONTH:
139             return 1;
140         case HOUR:
141         case HOUR_OF_DAY:
142             return 0;
143         case MINUTE:
144             return 0;
145         case SECOND:
146             return 0;
147         case AM_PM:
148             return AM;
149         default: assert( false, Format( "no matching switch case for field {}.", field ));
150         }
151     }
152 
153     int getMaximum(int field){
154         switch( field ){
155         case YEAR:
156             return 2100;
157         case MONTH:
158             return 11;
159         case DAY_OF_MONTH:
160             return 31;
161         case HOUR:
162             return 11;
163         case HOUR_OF_DAY:
164             return 23;
165         case MINUTE:
166             return 59;
167         case SECOND:
168             return 59;
169         case AM_PM:
170             return PM;
171         default: assert( false, Format( "no matching switch case for field {}.", field ));
172         }
173     }
174 
175     int getMinimum(int field){
176         switch( field ){
177         case YEAR:
178             return 1900;
179         case MONTH:
180             return 0;
181         case DAY_OF_MONTH:
182             return 1;
183         case HOUR:
184         case HOUR_OF_DAY:
185             return 0;
186         case MINUTE:
187             return 0;
188         case SECOND:
189             return 0;
190         case AM_PM:
191             return AM;
192         default: assert( false, Format( "no matching switch case for field {}.", field ));
193         }
194     }
195     int get(int field){
196         switch( field ){
197         case YEAR:
198             return year;
199         case MONTH:
200             return month;
201         case DAY_OF_MONTH:
202             return dayofmonth;
203         case HOUR:
204             return hour;
205         case HOUR_OF_DAY:
206             return hour % 12;
207         case MINUTE:
208             return minute;
209         case SECOND:
210             return second;
211         case AM_PM:
212             return ( hour < 12 ) ? AM : PM;
213         default: assert( false, Format( "no matching switch case for field {}.", field ));
214         }
215     }
216     void set( int year, int month, int day ){
217         this.year = year;
218         this.month = month;
219         this.dayofmonth = day;
220     }
221     void set(int field, int value){
222         switch( field ){
223         case YEAR:
224             year = value;
225             break;
226         case MONTH:
227             assert( value >= 0 && value < 12 );
228             month = value;
229             break;
230         case DAY_OF_MONTH:
231             assert( value > 0 && value <= getActualMaximum( DAY_OF_MONTH ) );
232             dayofmonth = value;
233             break;
234         case HOUR:
235             assert( value >= 0 && value < 12 );
236             hour = value;
237             break;
238         case HOUR_OF_DAY:
239             assert( value >= 0 && value < 24 );
240             hour = value;
241             break;
242         case MINUTE:
243             assert( value >= 0 && value < 60 );
244             minute = value;
245             break;
246         case SECOND:
247             assert( value >= 0 && value < 60 );
248             second = value;
249             break;
250         case AM_PM:
251             if( get(field) is AM ){
252                 if( value is AM ){
253                     return;
254                 }
255                 else{
256                     hour += 12;
257                 }
258             }
259             else{ // PM
260                 if( value is AM ){
261                     hour -= 12;
262                 }
263                 else{
264                     return;
265                 }
266             }
267             break;
268         default: assert( false, Format( "no matching switch case for field {}.", field ));
269         }
270     }
271 
272     void roll(int field, int value){
273         switch( field ){
274         case YEAR:
275             year = value;
276             break;
277         case MONTH:
278             month += value;
279             month %= 12;
280             break;
281         case DAY_OF_MONTH:
282             dayofmonth += value;
283             dayofmonth %= getActualMaximum( DAY_OF_MONTH );
284             break;
285         case HOUR:
286         case HOUR_OF_DAY:
287             hour += value;
288             hour %= 24;
289             break;
290         case MINUTE:
291             minute += value;
292             minute %= 60;
293             break;
294         case SECOND:
295             second += value;
296             second %= 60;
297             break;
298         case AM_PM:
299             set( AM_PM, get( AM_PM ) is AM ? PM : AM );
300             break;
301         default: assert( false, Format( "no matching switch case for field {}.", field ));
302         }
303     }
304 }
305 
306 
307 private class DateFormatSymbols {
308     private enum String[] ampm = [ "AM"[], "PM" ];
309     TryConst!(String[]) getAmPmStrings(){
310         return ampm;
311     }
312 }
313 
314 
315 /**
316  * Instances of this class are selectable user interface
317  * objects that allow the user to enter and modify date
318  * or time values.
319  * <p>
320  * Note that although this class is a subclass of <code>Composite</code>,
321  * it does not make sense to add children to it, or set a layout on it.
322  * </p>
323  * <dl>
324  * <dt><b>Styles:</b></dt>
325  * <dd>DATE, TIME, CALENDAR, SHORT, MEDIUM, LONG</dd>
326  * <dt><b>Events:</b></dt>
327  * <dd>Selection</dd>
328  * </dl>
329  * <p>
330  * Note: Only one of the styles DATE, TIME, or CALENDAR may be specified,
331  * and only one of the styles SHORT, MEDIUM, or LONG may be specified.
332  * </p><p>
333  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
334  * </p>
335  *
336  * @see <a href="http://www.eclipse.org/swt/snippets/#datetime">DateTime snippets</a>
337  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
338  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
339  *
340  * @since 3.3
341  */
342 public class DateTime : Composite {
343     int day, month, year, hours, minutes, seconds;
344 
345     static const int MIN_YEAR = 1752; // Gregorian switchover in North America: September 19, 1752
346     static const int MAX_YEAR = 9999;
347 
348     /* Emulated DATE and TIME variables */
349     Calendar calendar;
350     DateFormatSymbols formatSymbols;
351     Button down, up;
352     Text text;
353     String format;
354     Point[] fieldIndices;
355     int[] fieldNames;
356     int fieldCount, currentField = 0, characterCount = 0;
357     bool ignoreVerify = false;
358     static const String DEFAULT_SHORT_DATE_FORMAT = "MM/YYYY";
359     static const String DEFAULT_MEDIUM_DATE_FORMAT = "MM/DD/YYYY";
360     static const String DEFAULT_LONG_DATE_FORMAT = "MM/DD/YYYY";
361     static const String DEFAULT_SHORT_TIME_FORMAT = "HH:MM AM";
362     static const String DEFAULT_MEDIUM_TIME_FORMAT = "HH:MM:SS AM";
363     static const String DEFAULT_LONG_TIME_FORMAT = "HH:MM:SS AM";
364 
365 
366 
367 /**
368  * Constructs a new instance of this class given its parent
369  * and a style value describing its behavior and appearance.
370  * <p>
371  * The style value is either one of the style constants defined in
372  * class <code>SWT</code> which is applicable to instances of this
373  * class, or must be built by <em>bitwise OR</em>'ing together
374  * (that is, using the <code>int</code> "|" operator) two or more
375  * of those <code>SWT</code> style constants. The class description
376  * lists the style constants that are applicable to the class.
377  * Style bits are also inherited from superclasses.
378  * </p>
379  *
380  * @param parent a composite control which will be the parent of the new instance (cannot be null)
381  * @param style the style of control to construct
382  *
383  * @exception IllegalArgumentException <ul>
384  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
385  * </ul>
386  * @exception SWTException <ul>
387  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
388  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
389  * </ul>
390  *
391  * @see SWT#DATE
392  * @see SWT#TIME
393  * @see SWT#CALENDAR
394  * @see Widget#checkSubclass
395  * @see Widget#getStyle
396  */
397 public this (Composite parent, int style) {
398     super (parent, checkStyle (style));
399     if ((this.style & SWT.CALENDAR) is 0) {
400         /* SWT.DATE and SWT.TIME */
401         calendar = Calendar.getInstance();
402         formatSymbols = new DateFormatSymbols();
403 
404         text = new Text(this, SWT.SINGLE);
405         /* disable the native drag and drop for the date/time text field */
406         OS.gtk_drag_dest_unset(text.handle);
407         if ((this.style & SWT.DATE) !is 0) {
408             setFormat((this.style & SWT.SHORT) !is 0 ? DEFAULT_SHORT_DATE_FORMAT : (this.style & SWT.LONG) !is 0 ? DEFAULT_LONG_DATE_FORMAT : DEFAULT_MEDIUM_DATE_FORMAT);
409         } else { // SWT.TIME
410             setFormat((this.style & SWT.SHORT) !is 0 ? DEFAULT_SHORT_TIME_FORMAT : (this.style & SWT.LONG) !is 0 ? DEFAULT_LONG_TIME_FORMAT : DEFAULT_MEDIUM_TIME_FORMAT);
411         }
412         text.setText(getFormattedString(this.style));
413         Listener listener = new class () Listener {
414             public void handleEvent(Event event) {
415                 switch(event.type) {
416                     case SWT.KeyDown: onKeyDown(event); break;
417                     case SWT.FocusIn: onFocusIn(event); break;
418                     case SWT.FocusOut: onFocusOut(event); break;
419                     case SWT.MouseDown: onMouseClick(event); break;
420                     case SWT.MouseUp: onMouseClick(event); break;
421                     case SWT.Verify: onVerify(event); break;
422                     default:
423                 }
424             }
425         };
426         text.addListener(SWT.KeyDown, listener);
427         text.addListener(SWT.FocusIn, listener);
428         text.addListener(SWT.FocusOut, listener);
429         text.addListener(SWT.MouseDown, listener);
430         text.addListener(SWT.MouseUp, listener);
431         text.addListener(SWT.Verify, listener);
432         up = new Button(this, SWT.ARROW | SWT.UP);
433         //up.setToolTipText(SWT.getMessage ("SWT_Up")); //$NON-NLS-1$
434         down = new Button(this, SWT.ARROW | SWT.DOWN);
435         //down.setToolTipText(SWT.getMessage ("SWT_Down")); //$NON-NLS-1$
436         up.addListener(SWT.Selection, new class() Listener {
437             public void handleEvent(Event event) {
438                 incrementField(+1);
439                 text.setFocus();
440             }
441         });
442         down.addListener(SWT.Selection, new class() Listener {
443             public void handleEvent(Event event) {
444                 incrementField(-1);
445                 text.setFocus();
446             }
447         });
448         addListener(SWT.Resize, new class() Listener {
449             public void handleEvent(Event event) {
450                 onResize(event);
451             }
452         });
453     }
454 }
455 
456 static int checkStyle (int style) {
457     /*
458     * Even though it is legal to create this widget
459     * with scroll bars, they serve no useful purpose
460     * because they do not automatically scroll the
461     * widget's client area.  The fix is to clear
462     * the SWT style.
463     */
464     style &= ~(SWT.H_SCROLL | SWT.V_SCROLL);
465     style = checkBits (style, SWT.DATE, SWT.TIME, SWT.CALENDAR, 0, 0, 0);
466     return checkBits (style, SWT.MEDIUM, SWT.SHORT, SWT.LONG, 0, 0, 0);
467 }
468 
469 /**
470  * Adds the listener to the collection of listeners who will
471  * be notified when the control is selected by the user, by sending
472  * it one of the messages defined in the <code>SelectionListener</code>
473  * interface.
474  * <p>
475  * <code>widgetSelected</code> is called when the user changes the control's value.
476  * <code>widgetDefaultSelected</code> is not called.
477  * </p>
478  *
479  * @param listener the listener which should be notified
480  *
481  * @exception IllegalArgumentException <ul>
482  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
483  * </ul>
484  * @exception SWTException <ul>
485  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
486  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
487  * </ul>
488  *
489  * @see SelectionListener
490  * @see #removeSelectionListener
491  * @see SelectionEvent
492  */
493 public void addSelectionListener (SelectionListener listener) {
494     checkWidget ();
495     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
496     TypedListener typedListener = new TypedListener (listener);
497     addListener (SWT.Selection, typedListener);
498     addListener (SWT.DefaultSelection, typedListener);
499 }
500 
501 override
502 protected void checkSubclass () {
503     if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
504 }
505 
506 override
507 public Point computeSize (int wHint, int hHint, bool changed) {
508     checkWidget ();
509     int width = 0, height = 0;
510     if (wHint is SWT.DEFAULT || hHint is SWT.DEFAULT) {
511         if ((style & SWT.CALENDAR) !is 0) {
512             // TODO: CALENDAR computeSize
513             width = 300;
514             height = 200;
515         } else {
516             /* SWT.DATE and SWT.TIME */
517             GC gc = new GC(text);
518             Point textSize = gc.stringExtent(getComputeSizeString(style));
519             gc.dispose();
520             Rectangle trim = text.computeTrim(0, 0, textSize.x, textSize.y);
521             Point buttonSize = up.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed);
522             width = trim.width + buttonSize.x;
523             height = Math.max(trim.height, buttonSize.y);
524         }
525     }
526     if (width is 0) width = DEFAULT_WIDTH;
527     if (height is 0) height = DEFAULT_HEIGHT;
528     if (wHint !is SWT.DEFAULT) width = wHint;
529     if (hHint !is SWT.DEFAULT) height = hHint;
530     int border = getBorderWidth ();
531     width += border * 2; height += border * 2;
532     return new Point (width, height);
533 }
534 
535 override
536 void createHandle (int index) {
537     if ((style & SWT.CALENDAR) !is 0) {
538         state |= HANDLE;
539         fixedHandle = cast(GtkWidget*)OS.g_object_new (display.gtk_fixed_get_type (), null);
540         if (fixedHandle is null) error (SWT.ERROR_NO_HANDLES);
541         OS.gtk_fixed_set_has_window (fixedHandle, true);
542         handle = cast(GtkWidget*)OS.gtk_calendar_new ();
543         if (handle is null) error (SWT.ERROR_NO_HANDLES);
544         OS.gtk_container_add (fixedHandle, handle);
545         if (OS.GTK_VERSION >= OS.buildVERSION(2, 4, 0)) {
546             OS.gtk_calendar_set_display_options(handle, OS.GTK_CALENDAR_SHOW_HEADING | OS.GTK_CALENDAR_SHOW_DAY_NAMES);
547         } else {
548             OS.gtk_calendar_display_options(handle, OS.GTK_CALENDAR_SHOW_HEADING | OS.GTK_CALENDAR_SHOW_DAY_NAMES);
549         }
550     } else {
551         super.createHandle(index);
552     }
553 }
554 
555 override
556 void createWidget (int index) {
557     super.createWidget (index);
558     if ((style & SWT.CALENDAR) !is 0) {
559         getDate();
560     }
561 }
562 
563 void commitCurrentField() {
564     if (characterCount > 0) {
565         characterCount = 0;
566         int fieldName = fieldNames[currentField];
567         int start = fieldIndices[currentField].x;
568         int end = fieldIndices[currentField].y;
569         String value = text.getText(start, end - 1);
570         int s = value.lastIndexOf(' ');
571         if (s !is -1) value = value.substring(s + 1);
572         int newValue = unformattedIntValue(fieldName, value, characterCount is 0, calendar.getActualMaximum(fieldName));
573         if (newValue !is -1) setTextField(fieldName, newValue, true, true);
574     }
575 }
576 
577 String formattedStringValue(int fieldName, int value, bool adjust) {
578     if (fieldName is Calendar.AM_PM) {
579         return formatSymbols.getAmPmStrings()[value];
580     }
581     if (adjust) {
582         if (fieldName is Calendar.HOUR && value is 0) {
583             return to!(String)(12);
584         }
585         if (fieldName is Calendar.MONTH) {
586             return to!(String)(value + 1);
587         }
588     }
589     return to!(String)(value);
590 }
591 
592 String getComputeSizeString(int style) {
593     if ((style & SWT.DATE) !is 0) {
594         return (style & SWT.SHORT) !is 0 ? DEFAULT_SHORT_DATE_FORMAT : (style & SWT.LONG) !is 0 ? DEFAULT_LONG_DATE_FORMAT : DEFAULT_MEDIUM_DATE_FORMAT;
595     }
596     // SWT.TIME
597     return (style & SWT.SHORT) !is 0 ? DEFAULT_SHORT_TIME_FORMAT : (style & SWT.LONG) !is 0 ? DEFAULT_LONG_TIME_FORMAT : DEFAULT_MEDIUM_TIME_FORMAT;
598 }
599 
600 int getFieldIndex(int fieldName) {
601     for (int i = 0; i < fieldCount; i++) {
602         if (fieldNames[i] is fieldName) {
603             return i;
604         }
605     }
606     return -1;
607 }
608 
609 String getFormattedString(int style) {
610     if ((style & SWT.TIME) !is 0) {
611         auto ampm = formatSymbols.getAmPmStrings();
612         int h = calendar.get(Calendar.HOUR); if (h is 0) h = 12;
613         int m = calendar.get(Calendar.MINUTE);
614         int s = calendar.get(Calendar.SECOND);
615         int a = calendar.get(Calendar.AM_PM);
616         if ((style & SWT.SHORT) !is 0) return "" ~ (h < 10 ? " " : "") ~ to!(String)(h) ~ ":" ~ (m < 10 ? "0" : "") ~ to!(String)(m) ~ " " ~ ampm[a];
617         return "" ~ (h < 10 ? " " : "") ~ to!(String)(h) ~ ":" ~ (m < 10 ? "0" : "") ~ to!(String)(m) ~ ":" ~ (s < 10 ? "0" : "") ~ to!(String)(s) ~ " " ~ ampm[a];
618     }
619     /* SWT.DATE */
620     int y = calendar.get(Calendar.YEAR);
621     int m = calendar.get(Calendar.MONTH) + 1;
622     int d = calendar.get(Calendar.DAY_OF_MONTH);
623     if ((style & SWT.SHORT) !is 0) return "" ~ (m < 10 ? " " : "") ~ to!(String)(m) ~ "/" ~ to!(String)(y);
624     return "" ~ (m < 10 ? " " : "") ~ to!(String)(m) ~ "/" ~ (d < 10 ? " " : "") ~ to!(String)(d) ~ "/" ~ to!(String)(y);
625 }
626 
627 void getDate() {
628     uint y;
629     uint m;
630     uint d;
631     OS.gtk_calendar_get_date(handle, &y, &m, &d);
632     year = y;
633     month = m;
634     day = d;
635 }
636 
637 /**
638  * Returns the receiver's date, or day of the month.
639  * <p>
640  * The first day of the month is 1, and the last day depends on the month and year.
641  * </p>
642  *
643  * @return a positive integer beginning with 1
644  *
645  * @exception SWTException <ul>
646  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
647  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
648  * </ul>
649  */
650 public int getDay () {
651     checkWidget ();
652     if ((style & SWT.CALENDAR) !is 0) {
653         getDate();
654         return day;
655     } else {
656         return calendar.get(Calendar.DAY_OF_MONTH);
657     }
658 }
659 
660 /**
661  * Returns the receiver's hours.
662  * <p>
663  * Hours is an integer between 0 and 23.
664  * </p>
665  *
666  * @return an integer between 0 and 23
667  *
668  * @exception SWTException <ul>
669  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
670  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
671  * </ul>
672  */
673 public int getHours () {
674     checkWidget ();
675     if ((style & SWT.CALENDAR) !is 0) {
676         return hours;
677     } else {
678         return calendar.get(Calendar.HOUR_OF_DAY);
679     }
680 }
681 
682 /**
683  * Returns the receiver's minutes.
684  * <p>
685  * Minutes is an integer between 0 and 59.
686  * </p>
687  *
688  * @return an integer between 0 and 59
689  *
690  * @exception SWTException <ul>
691  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
692  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
693  * </ul>
694  */
695 public int getMinutes () {
696     checkWidget ();
697     if ((style & SWT.CALENDAR) !is 0) {
698         return minutes;
699     } else {
700         return calendar.get(Calendar.MINUTE);
701     }
702 }
703 
704 /**
705  * Returns the receiver's month.
706  * <p>
707  * The first month of the year is 0, and the last month is 11.
708  * </p>
709  *
710  * @return an integer between 0 and 11
711  *
712  * @exception SWTException <ul>
713  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
714  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
715  * </ul>
716  */
717 public int getMonth () {
718     checkWidget ();
719     if ((style & SWT.CALENDAR) !is 0) {
720         getDate();
721         return month;
722     } else {
723         return calendar.get(Calendar.MONTH);
724     }
725 }
726 
727 override
728 String getNameText() {
729     if((style & SWT.TIME) !is 0){
730         return Format( "{}:{}:{}", getHours(), getMinutes(), getSeconds() );
731     }
732     else{
733         return Format( "{}/{}/{}", (getMonth() + 1), getDay(), getYear() );
734     }
735 }
736 
737 /**
738  * Returns the receiver's seconds.
739  * <p>
740  * Seconds is an integer between 0 and 59.
741  * </p>
742  *
743  * @return an integer between 0 and 59
744  *
745  * @exception SWTException <ul>
746  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
747  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
748  * </ul>
749  */
750 public int getSeconds () {
751     checkWidget ();
752     if ((style & SWT.CALENDAR) !is 0) {
753         return seconds;
754     } else {
755         return calendar.get(Calendar.SECOND);
756     }
757 }
758 
759 /**
760  * Returns the receiver's year.
761  * <p>
762  * The first year is 1752 and the last year is 9999.
763  * </p>
764  *
765  * @return an integer between 1752 and 9999
766  *
767  * @exception SWTException <ul>
768  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
769  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
770  * </ul>
771  */
772 public int getYear () {
773     checkWidget ();
774     if ((style & SWT.CALENDAR) !is 0) {
775         getDate();
776         return year;
777     } else {
778         return calendar.get(Calendar.YEAR);
779     }
780 }
781 
782 override int gtk_day_selected (GtkWidget* widget) {
783     sendSelectionEvent ();
784     return 0;
785 }
786 
787 override int gtk_month_changed (GtkWidget* widget) {
788     sendSelectionEvent ();
789     return 0;
790 }
791 
792 override
793 void hookEvents () {
794     super.hookEvents();
795     if ((style & SWT.CALENDAR) !is 0) {
796         OS.g_signal_connect_closure (handle, OS.day_selected.ptr, display.closures [DAY_SELECTED], false);
797         OS.g_signal_connect_closure (handle, OS.month_changed.ptr, display.closures [MONTH_CHANGED], false);
798     }
799 }
800 
801 bool isValid(int fieldName, int value) {
802     Calendar validCalendar;
803     if ((style & SWT.CALENDAR) !is 0) {
804         validCalendar = Calendar.getInstance();
805         validCalendar.set(Calendar.YEAR, year);
806         validCalendar.set(Calendar.MONTH, month);
807     } else {
808         validCalendar = calendar;
809     }
810     int min = validCalendar.getActualMinimum(fieldName);
811     int max = validCalendar.getActualMaximum(fieldName);
812     return value >= min && value <= max;
813 }
814 
815 bool isValid(int year, int month, int day) {
816     Calendar valid = Calendar.getInstance();
817     valid.set(year, month, day);
818     return valid.get(Calendar.YEAR) is year && valid.get(Calendar.MONTH) is month && valid.get(Calendar.DAY_OF_MONTH) is day;
819 }
820 
821 void incrementField(int amount) {
822     int fieldName = fieldNames[currentField];
823     int value = calendar.get(fieldName);
824     if (fieldName is Calendar.HOUR) {
825         int max = calendar.getMaximum(Calendar.HOUR);
826         int min = calendar.getMinimum(Calendar.HOUR);
827         if ((value is max && amount is 1) || (value is min && amount is -1)) {
828             int temp = currentField;
829             currentField = getFieldIndex(Calendar.AM_PM);
830             setTextField(Calendar.AM_PM, (calendar.get(Calendar.AM_PM) + 1) % 2, true, true);
831             currentField = temp;
832         }
833     }
834     setTextField(fieldName, value + amount, true, true);
835 }
836 
837 void onKeyDown(Event event) {
838     int fieldName;
839     switch (event.keyCode) {
840         case SWT.ARROW_RIGHT:
841         case SWT.KEYPAD_DIVIDE:
842             // a right arrow or a valid separator navigates to the field on the right, with wraping
843             selectField((currentField + 1) % fieldCount);
844             break;
845         case SWT.ARROW_LEFT:
846             // navigate to the field on the left, with wrapping
847             int index = currentField - 1;
848             selectField(index < 0 ? fieldCount - 1 : index);
849             break;
850         case SWT.ARROW_UP:
851         case SWT.KEYPAD_ADD:
852             // set the value of the current field to value + 1, with wrapping
853             commitCurrentField();
854             incrementField(+1);
855             break;
856         case SWT.ARROW_DOWN:
857         case SWT.KEYPAD_SUBTRACT:
858             // set the value of the current field to value - 1, with wrapping
859             commitCurrentField();
860             incrementField(-1);
861             break;
862         case SWT.HOME:
863             // set the value of the current field to its minimum
864             fieldName = fieldNames[currentField];
865             setTextField(fieldName, calendar.getActualMinimum(fieldName), true, true);
866             break;
867         case SWT.END:
868             // set the value of the current field to its maximum
869             fieldName = fieldNames[currentField];
870             setTextField(fieldName, calendar.getActualMaximum(fieldName), true, true);
871             break;
872         default:
873             switch (event.character) {
874                 case '/':
875                 case ':':
876                 case '-':
877                 case '.':
878                     // a valid separator navigates to the field on the right, with wraping
879                     selectField((currentField + 1) % fieldCount);
880                     break;
881                 default:
882             }
883     }
884 }
885 
886 void onFocusIn(Event event) {
887     selectField(currentField);
888 }
889 
890 void onFocusOut(Event event) {
891     commitCurrentField();
892 }
893 
894 void onMouseClick(Event event) {
895     if (event.button !is 1) return;
896     Point sel = text.getSelection();
897     for (int i = 0; i < fieldCount; i++) {
898         if (sel.x >= fieldIndices[i].x && sel.x <= fieldIndices[i].y) {
899             currentField = i;
900             break;
901         }
902     }
903     selectField(currentField);
904 }
905 
906 void onResize(Event event) {
907     Rectangle rect = getClientArea ();
908     int width = rect.width;
909     int height = rect.height;
910     Point buttonSize = up.computeSize(SWT.DEFAULT, height);
911     int buttonHeight = buttonSize.y / 2;
912     text.setBounds(0, 0, width - buttonSize.x, height);
913     up.setBounds(width - buttonSize.x, 0, buttonSize.x, buttonHeight);
914     down.setBounds(width - buttonSize.x, buttonHeight, buttonSize.x, buttonHeight);
915 }
916 
917 void onVerify(Event event) {
918     if (ignoreVerify) return;
919     event.doit = false;
920     int fieldName = fieldNames[currentField];
921     int start = fieldIndices[currentField].x;
922     int end = fieldIndices[currentField].y;
923     int length_ = end - start;
924     String newText = event.text;
925     if (fieldName is Calendar.AM_PM) {
926         auto ampm = formatSymbols.getAmPmStrings();
927         if (newText.equalsIgnoreCase(ampm[Calendar.AM].substring(0, 1)) || newText.equalsIgnoreCase(ampm[Calendar.AM])) {
928             setTextField(fieldName, Calendar.AM, true, false);
929         } else if (newText.equalsIgnoreCase(ampm[Calendar.PM].substring(0, 1)) || newText.equalsIgnoreCase(ampm[Calendar.PM])) {
930             setTextField(fieldName, Calendar.PM, true, false);
931         }
932         return;
933     }
934     if (characterCount > 0) {
935         try {
936             Integer.parseInt(newText);
937         } catch (NumberFormatException ex) {
938             return;
939         }
940         String value = text.getText(start, end - 1);
941         int s = value.lastIndexOf(' ');
942         if (s !is -1) value = value.substring(s + 1);
943         newText = value ~ newText;
944     }
945     ptrdiff_t newTextLength = newText.length;
946     bool first = characterCount is 0;
947     characterCount = (newTextLength < length_) ? cast(int)/*64bit*/newTextLength : 0;
948     int max = calendar.getActualMaximum(fieldName);
949     int min = calendar.getActualMinimum(fieldName);
950     int newValue = unformattedIntValue(fieldName, newText, characterCount is 0, max);
951     if (newValue is -1) {
952         characterCount = 0;
953         return;
954     }
955     if (first && newValue is 0 && length_ > 1) {
956         setTextField(fieldName, newValue, false, false);
957     } else if (min <= newValue && newValue <= max) {
958         setTextField(fieldName, newValue, characterCount is 0, characterCount is 0);
959     } else {
960         if (newTextLength >= length_) {
961             newText = newText.substring(cast(int)/*64bit*/(newTextLength - length_ + 1));
962             newValue = unformattedIntValue(fieldName, newText, characterCount is 0, max);
963             if (newValue !is -1) {
964                 characterCount = length_ - 1;
965                 if (min <= newValue && newValue <= max) {
966                     setTextField(fieldName, newValue, characterCount is 0, true);
967                 }
968             }
969         }
970     }
971 }
972 
973 override
974 void releaseWidget () {
975     super.releaseWidget();
976     //TODO: need to do anything here?
977 }
978 
979 /**
980  * Removes the listener from the collection of listeners who will
981  * be notified when the control is selected by the user.
982  *
983  * @param listener the listener which should no longer be notified
984  *
985  * @exception IllegalArgumentException <ul>
986  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
987  * </ul>
988  * @exception SWTException <ul>
989  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
990  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
991  * </ul>
992  *
993  * @see SelectionListener
994  * @see #addSelectionListener
995  */
996 public void removeSelectionListener (SelectionListener listener) {
997     checkWidget ();
998     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
999     if (eventTable is null) return;
1000     eventTable.unhook (SWT.Selection, listener);
1001     eventTable.unhook (SWT.DefaultSelection, listener);
1002 }
1003 
1004 void selectField(int index) {
1005     if (index !is currentField) {
1006         commitCurrentField();
1007     }
1008     int start = fieldIndices[index].x;
1009     int end = fieldIndices[index].y;
1010     Point pt = text.getSelection();
1011     if (index is currentField && start is pt.x && end is pt.y) return;
1012     currentField = index;
1013     display.asyncExec(new class( start, end ) Runnable {
1014         int start, end;
1015         this( int start, int end ){
1016             this.start = start; this.end = end;
1017         }
1018         public void run() {
1019             if (!text.isDisposed()) {
1020                 String value = text.getText(start, end - 1);
1021                 int s = value.lastIndexOf(' ');
1022                 if (s is -1 ) s = start;
1023                 else s = start + s + 1;
1024                 text.setSelection(s, end);
1025             }
1026         }
1027     });
1028 }
1029 
1030 void sendSelectionEvent () {
1031     uint y;
1032     uint m;
1033     uint d;
1034     OS.gtk_calendar_get_date(handle, &y, &m, &d);
1035     //TODO: hours, minutes, seconds?
1036     if (d !is day ||
1037         m !is month ||
1038         y !is year) {
1039         year = y;
1040         month = m;
1041         day = d;
1042         postEvent (SWT.Selection);
1043     }
1044 }
1045 
1046 override
1047 public void setBackground(Color color) {
1048     checkWidget();
1049     super.setBackground(color);
1050     if (text !is null) text.setBackground(color);
1051 }
1052 
1053 override
1054 public void setFont(Font font) {
1055     checkWidget();
1056     super.setFont(font);
1057     if (text !is null) text.setFont(font);
1058     redraw();
1059 }
1060 
1061 override
1062 public void setForeground(Color color) {
1063     checkWidget();
1064     super.setForeground(color);
1065     if (text !is null) text.setForeground(color);
1066 }
1067 
1068 /*public*/ void setFormat(String string) {
1069     checkWidget();
1070     // TODO: this needs to be locale sensitive
1071     fieldCount = (style & SWT.DATE) !is 0 ? ((style & SWT.SHORT) !is 0 ? 2 : 3) : ((style & SWT.SHORT) !is 0 ? 3 : 4);
1072     fieldIndices = new Point[fieldCount];
1073     fieldNames = new int[fieldCount];
1074     if ((style & SWT.DATE) !is 0) {
1075         fieldNames[0] = Calendar.MONTH;
1076         fieldIndices[0] = new Point(0, 2);
1077         if ((style & SWT.SHORT) !is 0) {
1078             fieldNames[1] = Calendar.YEAR;
1079             fieldIndices[1] = new Point(3, 7);
1080         } else {
1081             fieldNames[1] = Calendar.DAY_OF_MONTH;
1082             fieldIndices[1] = new Point(3, 5);
1083             fieldNames[2] = Calendar.YEAR;
1084             fieldIndices[2] = new Point(6, 10);
1085         }
1086     } else { /* SWT.TIME */
1087         fieldNames[0] = Calendar.HOUR;
1088         fieldIndices[0] = new Point(0, 2);
1089         fieldNames[1] = Calendar.MINUTE;
1090         fieldIndices[1] = new Point(3, 5);
1091         if ((style & SWT.SHORT) !is 0) {
1092             fieldNames[2] = Calendar.AM_PM;
1093             fieldIndices[2] = new Point(6, 8);
1094         } else {
1095             fieldNames[2] = Calendar.SECOND;
1096             fieldIndices[2] = new Point(6, 8);
1097             fieldNames[3] = Calendar.AM_PM;
1098             fieldIndices[3] = new Point(9, 11);
1099         }
1100     }
1101 }
1102 
1103 void setField(int fieldName, int value) {
1104     if (calendar.get(fieldName) is value) return;
1105     if (fieldName is Calendar.AM_PM) {
1106         calendar.roll(Calendar.HOUR_OF_DAY, 12); // TODO: needs more work for setFormat and locale
1107     }
1108     calendar.set(fieldName, value);
1109     postEvent(SWT.Selection);
1110 }
1111 
1112 void setTextField(int fieldName, int value, bool commit, bool adjust) {
1113     if (commit) {
1114         int max = calendar.getActualMaximum(fieldName);
1115         int min = calendar.getActualMinimum(fieldName);
1116         if (fieldName is Calendar.YEAR) {
1117             max = MAX_YEAR;
1118             min = MIN_YEAR;
1119             /* Special case: convert 1 or 2-digit years into reasonable 4-digit years. */
1120             int currentYear = Calendar.getInstance().get(Calendar.YEAR);
1121             int currentCentury = (currentYear / 100) * 100;
1122             if (value < (currentYear + 30) % 100) value += currentCentury;
1123             else if (value < 100) value += currentCentury - 100;
1124         }
1125         if (value > max) value = min; // wrap
1126         if (value < min) value = max; // wrap
1127     }
1128     int start = fieldIndices[currentField].x;
1129     int end = fieldIndices[currentField].y;
1130     text.setSelection(start, end);
1131     String newValue = formattedStringValue(fieldName, value, adjust);
1132     StringBuffer buffer = new StringBuffer(newValue);
1133     /* Convert leading 0's into spaces. */
1134     int prependCount = end - start - buffer.length();
1135     for (int i = 0; i < prependCount; i++) {
1136         switch (fieldName) {
1137         case Calendar.MINUTE:
1138         case Calendar.SECOND:
1139             buffer.insert(0, 0);
1140         break;
1141         default:
1142             buffer.insert(0, ' ');
1143         break;
1144         }
1145     }
1146     newValue = buffer.toString();
1147     ignoreVerify = true;
1148     text.insert(newValue);
1149     ignoreVerify = false;
1150     selectField(currentField);
1151     if (commit) setField(fieldName, value);
1152 }
1153 
1154 /**
1155  * Sets the receiver's year, month, and day in a single operation.
1156  * <p>
1157  * This is the recommended way to set the date, because setting the year,
1158  * month, and day separately may result in invalid intermediate dates.
1159  * </p>
1160  *
1161  * @param year an integer between 1752 and 9999
1162  * @param month an integer between 0 and 11
1163  * @param day a positive integer beginning with 1
1164  *
1165  * @exception SWTException <ul>
1166  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1167  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1168  * </ul>
1169  *
1170  * @since 3.4
1171  */
1172 public void setDate (int year, int month, int day) {
1173     checkWidget ();
1174     if (!isValid(year, month, day)) return;
1175     if ((style & SWT.CALENDAR) !is 0) {
1176         this.year = year;
1177         this.month = month;
1178         this.day = day;
1179         OS.gtk_calendar_select_month(handle, month, year);
1180         OS.gtk_calendar_select_day(handle, day);
1181     } else {
1182         calendar.set(Calendar.YEAR, year);
1183         calendar.set(Calendar.MONTH, month);
1184         calendar.set(Calendar.DAY_OF_MONTH, day);
1185         updateControl();
1186     }
1187 }
1188 
1189 /**
1190  * Sets the receiver's date, or day of the month, to the specified day.
1191  * <p>
1192  * The first day of the month is 1, and the last day depends on the month and year.
1193  * </p>
1194  *
1195  * @param day a positive integer beginning with 1
1196  *
1197  * @exception SWTException <ul>
1198  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1199  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1200  * </ul>
1201  */
1202 public void setDay (int day) {
1203     checkWidget ();
1204     if (!isValid(Calendar.DAY_OF_MONTH, day)) return;
1205     if ((style & SWT.CALENDAR) !is 0) {
1206         this.day = day;
1207         OS.gtk_calendar_select_day(handle, day);
1208     } else {
1209         calendar.set(Calendar.DAY_OF_MONTH, day);
1210         updateControl();
1211     }
1212 }
1213 
1214 /**
1215  * Sets the receiver's hours.
1216  * <p>
1217  * Hours is an integer between 0 and 23.
1218  * </p>
1219  *
1220  * @param hours an integer between 0 and 23
1221  *
1222  * @exception SWTException <ul>
1223  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1224  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1225  * </ul>
1226  */
1227 public void setHours (int hours) {
1228     checkWidget ();
1229     if (!isValid(Calendar.HOUR_OF_DAY, hours)) return;
1230     if ((style & SWT.CALENDAR) !is 0) {
1231         this.hours = hours;
1232     } else {
1233         calendar.set(Calendar.HOUR_OF_DAY, hours);
1234         updateControl();
1235     }
1236 }
1237 
1238 /**
1239  * Sets the receiver's minutes.
1240  * <p>
1241  * Minutes is an integer between 0 and 59.
1242  * </p>
1243  *
1244  * @param minutes an integer between 0 and 59
1245  *
1246  * @exception SWTException <ul>
1247  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1248  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1249  * </ul>
1250  */
1251 public void setMinutes (int minutes) {
1252     checkWidget ();
1253     if (!isValid(Calendar.MINUTE, minutes)) return;
1254     if ((style & SWT.CALENDAR) !is 0) {
1255         this.minutes = minutes;
1256     } else {
1257         calendar.set(Calendar.MINUTE, minutes);
1258         updateControl();
1259     }
1260 }
1261 
1262 /**
1263  * Sets the receiver's month.
1264  * <p>
1265  * The first month of the year is 0, and the last month is 11.
1266  * </p>
1267  *
1268  * @param month an integer between 0 and 11
1269  *
1270  * @exception SWTException <ul>
1271  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1272  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1273  * </ul>
1274  */
1275 public void setMonth (int month) {
1276     checkWidget ();
1277     if (!isValid(Calendar.MONTH, month)) return;
1278     if ((style & SWT.CALENDAR) !is 0) {
1279         this.month = month;
1280         OS.gtk_calendar_select_month(handle, month, year);
1281     } else {
1282         calendar.set(Calendar.MONTH, month);
1283         updateControl();
1284     }
1285 }
1286 
1287 /**
1288  * Sets the receiver's seconds.
1289  * <p>
1290  * Seconds is an integer between 0 and 59.
1291  * </p>
1292  *
1293  * @param seconds an integer between 0 and 59
1294  *
1295  * @exception SWTException <ul>
1296  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1297  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1298  * </ul>
1299  */
1300 public void setSeconds (int seconds) {
1301     checkWidget ();
1302     if (!isValid(Calendar.SECOND, seconds)) return;
1303     if ((style & SWT.CALENDAR) !is 0) {
1304         this.seconds = seconds;
1305     } else {
1306         calendar.set(Calendar.SECOND, seconds);
1307         updateControl();
1308     }
1309 }
1310 
1311 /**
1312  * Sets the receiver's hours, minutes, and seconds in a single operation.
1313  *
1314  * @param hours an integer between 0 and 23
1315  * @param minutes an integer between 0 and 59
1316  * @param seconds an integer between 0 and 59
1317  *
1318  * @exception SWTException <ul>
1319  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1320  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1321  * </ul>
1322  *
1323  * @since 3.4
1324  */
1325 public void setTime (int hours, int minutes, int seconds) {
1326     checkWidget ();
1327     if (!isValid(Calendar.HOUR_OF_DAY, hours)) return;
1328     if (!isValid(Calendar.MINUTE, minutes)) return;
1329     if (!isValid(Calendar.SECOND, seconds)) return;
1330     if ((style & SWT.CALENDAR) !is 0) {
1331         this.hours = hours;
1332         this.minutes = minutes;
1333         this.seconds = seconds;
1334     } else {
1335         calendar.set(Calendar.HOUR_OF_DAY, hours);
1336         calendar.set(Calendar.MINUTE, minutes);
1337         calendar.set(Calendar.SECOND, seconds);
1338         updateControl();
1339     }
1340 }
1341 
1342 /**
1343  * Sets the receiver's year.
1344  * <p>
1345  * The first year is 1752 and the last year is 9999.
1346  * </p>
1347  *
1348  * @param year an integer between 1752 and 9999
1349  *
1350  * @exception SWTException <ul>
1351  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1352  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1353  * </ul>
1354  */
1355 public void setYear (int year) {
1356     checkWidget ();
1357     //if (!isValid(Calendar.YEAR, year)) return;
1358     if (year < MIN_YEAR || year > MAX_YEAR) return;
1359     if ((style & SWT.CALENDAR) !is 0) {
1360         this.year = year;
1361         OS.gtk_calendar_select_month(handle, month, year);
1362     } else {
1363         calendar.set(Calendar.YEAR, year);
1364         updateControl();
1365     }
1366 }
1367 
1368 int unformattedIntValue(int fieldName, String newText, bool adjust, int max) {
1369     int newValue;
1370     try {
1371         newValue = Integer.parseInt(newText);
1372     } catch (NumberFormatException ex) {
1373         return -1;
1374     }
1375     if (fieldName is Calendar.MONTH && adjust) {
1376         newValue--;
1377         if (newValue is -1) newValue = max;
1378     }
1379     if (fieldName is Calendar.HOUR && adjust) {
1380         if (newValue is 12) newValue = 0; // TODO: needs more work for setFormat and locale
1381     }
1382     return newValue;
1383 }
1384 
1385 public void updateControl() {
1386     if (text !is null) {
1387         String string = getFormattedString(style);
1388         ignoreVerify = true;
1389         text.setText(string);
1390         ignoreVerify = false;
1391     }
1392     redraw();
1393 }
1394 }