# HG changeset patch # User Frank Benoit # Date 1204575808 -3600 # Node ID 934fb859da8ebc044094a0e44a35cfb16249ff5d # Parent ac6ff7065cacc7c9bdeda78502cf2ae2a7ff51b0 Added DateTime diff -r ac6ff7065cac -r 934fb859da8e dwt/dwthelper/utils.d --- a/dwt/dwthelper/utils.d Mon Mar 03 00:34:00 2008 +0100 +++ b/dwt/dwthelper/utils.d Mon Mar 03 21:23:28 2008 +0100 @@ -247,6 +247,15 @@ return res; } +public int lastIndexOf(char[] str, char ch){ + return lastIndexOf( str, ch, str.length ); +} +public int lastIndexOf(char[] str, char ch, int formIndex){ + int res = tango.text.Util.locatePrior( str, ch, formIndex ); + if( res is str.length ) res = -1; + return res; +} + public char[] substring( char[] str, int start ){ return str[ start .. $ ].dup; } @@ -286,6 +295,10 @@ return src == other; } +public bool equalsIgnoreCase( char[] src, char[] other ){ + return tango.text.Unicode.toFold(src) == tango.text.Unicode.toFold(other); +} + public char[] toLowerCase( char[] src ){ return tango.text.Unicode.toLower( src ); } diff -r ac6ff7065cac -r 934fb859da8e dwt/widgets/DateTime.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwt/widgets/DateTime.d Mon Mar 03 21:23:28 2008 +0100 @@ -0,0 +1,1325 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwt.widgets.DateTime; + +import dwt.DWT; +import dwt.DWTException; +import dwt.events.SelectionEvent; +import dwt.events.SelectionListener; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.GC; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.internal.gtk.OS; +import dwt.internal.Compatibility; + +import dwt.widgets.Composite; +import dwt.widgets.Listener; +import dwt.widgets.Button; +import dwt.widgets.Event; +import dwt.widgets.Text; +import dwt.widgets.TypedListener; + +import dwt.dwthelper.Runnable; +import tango.text.convert.Format; +import tango.util.Convert; +import dwt.dwthelper.Runnable; +import dwt.dwthelper.utils; + +static import tango.text.Util; +static import tango.text.locale.Core; +static import tango.text.Text; +static import tango.time.WallClock; +static import tango.time.chrono.Gregorian; +static import tango.time.chrono.Calendar; + +alias tango.text.Text.Text!(char) TangoText; + +private class Calendar{ + enum { + AM, + PM + } + enum { + AM_PM, + HOUR, + MINUTE, + SECOND, + MONTH, + YEAR, + DAY_OF_MONTH, + DAY_SELECTED, + MONTH_CHANGED, + HOUR_OF_DAY, + } + private static const int[] MONTH_DAYS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]; + static private Calendar instance; + + private int second; + private int minute; + private int hour; + private int dayofmonth; + private int month; + private int year; + + static Calendar getInstance(){ + if( instance is null ){ + synchronized { + if( instance is null ){ + instance = new Calendar; + } + } + } + return instance; + } + + int getActualMaximum(int field){ + switch( field ){ + case YEAR: + return 2100; + case MONTH: + return MONTH_DAYS.length -1; + case DAY_OF_MONTH: + return MONTH_DAYS[month]; + case HOUR: + return 11; + case HOUR_OF_DAY: + return 23; + case MINUTE: + return 59; + case SECOND: + return 59; + case AM_PM: + return PM; + default: assert( false, Format( "no matching switch case for field {}.", field )); + } + } + + int getActualMinimum(int field){ + switch( field ){ + case YEAR: + return 1900; + case MONTH: + return 0; + case DAY_OF_MONTH: + return 1; + case HOUR: + case HOUR_OF_DAY: + return 0; + case MINUTE: + return 0; + case SECOND: + return 0; + case AM_PM: + return AM; + default: assert( false, Format( "no matching switch case for field {}.", field )); + } + } + + int getMaximum(int field){ + switch( field ){ + case YEAR: + return 2100; + case MONTH: + return 11; + case DAY_OF_MONTH: + return 31; + case HOUR: + return 11; + case HOUR_OF_DAY: + return 23; + case MINUTE: + return 59; + case SECOND: + return 59; + case AM_PM: + return PM; + default: assert( false, Format( "no matching switch case for field {}.", field )); + } + } + + int getMinimum(int field){ + switch( field ){ + case YEAR: + return 1900; + case MONTH: + return 0; + case DAY_OF_MONTH: + return 1; + case HOUR: + case HOUR_OF_DAY: + return 0; + case MINUTE: + return 0; + case SECOND: + return 0; + case AM_PM: + return AM; + default: assert( false, Format( "no matching switch case for field {}.", field )); + } + } + int get(int field){ + switch( field ){ + case YEAR: + return year; + case MONTH: + return month; + case DAY_OF_MONTH: + return dayofmonth; + case HOUR: + return hour; + case HOUR_OF_DAY: + return hour % 12; + case MINUTE: + return minute; + case SECOND: + return second; + case AM_PM: + return ( hour < 12 ) ? AM : PM; + default: assert( false, Format( "no matching switch case for field {}.", field )); + } + } + + void set(int field, int value){ + switch( field ){ + case YEAR: + year = value; + break; + case MONTH: + assert( value >= 0 && value < 12 ); + month = value; + break; + case DAY_OF_MONTH: + assert( value > 0 && value <= getActualMaximum( DAY_OF_MONTH ) ); + dayofmonth = value; + break; + case HOUR: + assert( value >= 0 && value < 12 ); + hour = value; + break; + case HOUR_OF_DAY: + assert( value >= 0 && value < 24 ); + hour = value; + break; + case MINUTE: + assert( value >= 0 && value < 60 ); + minute = value; + break; + case SECOND: + assert( value >= 0 && value < 60 ); + second = value; + break; + case AM_PM: + if( get(field) is AM ){ + if( value is AM ){ + return; + } + else{ + hour += 12; + } + } + else{ // PM + if( value is AM ){ + hour -= 12; + } + else{ + return; + } + } + break; + default: assert( false, Format( "no matching switch case for field {}.", field )); + } + } + + void roll(int field, int value){ + switch( field ){ + case YEAR: + year = value; + break; + case MONTH: + month += value; + month %= 12; + break; + case DAY_OF_MONTH: + dayofmonth += value; + dayofmonth %= getActualMaximum( DAY_OF_MONTH ); + break; + case HOUR: + case HOUR_OF_DAY: + hour += value; + hour %= 24; + break; + case MINUTE: + minute += value; + minute %= 60; + break; + case SECOND: + second += value; + second %= 60; + break; + case AM_PM: + set( AM_PM, get( AM_PM ) is AM ? PM : AM ); + break; + default: assert( false, Format( "no matching switch case for field {}.", field )); + } + } +} + + +private class DateFormatSymbols { + private const char[][] ampm = [ "AM"[], "PM" ]; + char[][] getAmPmStrings(){ + return ampm; + } +} + +/+ +private int getCalendarActualMaximum( tango.time.Time.Time time, int field ){ + int year = cal.getYear( time ); + int month = cal.getMonth( time ); + tango.time.Time.TimeOfDay tod = tango.time.Time.TimeOfDay( time.ticks ); + switch( field ){ + case YEAR: return tango.time.chrono.Gregorian.Gregorian.MAX_YEAR; + case MONTH: return cal.getMonthsInYear( year, tango.time.chrono.Gregorian.Gregorian.AD_ERA ) -1; // Jan is 0, so max is 11 + case DAY_OF_MONTH:return cal.getDaysInMonth( year, month, tango.time.chrono.Gregorian.Gregorian.AD_ERA ) -1; // 1st is 0 + case HOUR:return 23; + case MINUTE:return 59; + case SECOND:return 59; + case AM_PM:return PM; + default: + } +} + +private int getCalendarActualMinimum( tango.time.Time.Time time, int field ){ + switch( field ){ + case YEAR: return 1800; + case MONTH: return 1; + case DAY_OF_MONTH:return 1; + case HOUR:return 0; + case MINUTE:return 0; + case SECOND:return 0; + case AM_PM:return AM; + default: + } +} +private int getCalendarField( tango.time.Time.Time time, int field ){ + switch( field ){ + case YEAR: return cal.getYear( time ); + case MONTH: return cal.getMonth( time ); + case DAY_OF_MONTH:return cal.getDayOfMonth( time ); + default: + } + tango.time.Time.TimeOfDay tod = tango.time.Time.TimeOfDay( time.ticks ); + switch( field ){ + case HOUR:return tod.hours; + case MINUTE:return tod.minutes; + case SECOND:return tod.seconds; + case AM_PM:return tod.hours > 12; + default: + } + assert(false); +} +enum { + AM, + PM, + AM_PM, + HOUR, + MINUTE, + SECOND, + MONTH, + YEAR, + DAY_OF_MONTH, + DAY_SELECTED, + MONTH_CHANGED, + HOUR_OF_DAY +} + ++/ + +/** + * Instances of this class are selectable user interface + * objects that allow the user to enter and modify date + * or time values. + *

+ * Note that although this class is a subclass of Composite, + * it does not make sense to add children to it, or set a layout on it. + *

+ *
+ *
Styles:
+ *
DATE, TIME, CALENDAR, SHORT, MEDIUM, LONG
+ *
Events:
+ *
Selection
+ *
+ *

+ * Note: Only one of the styles DATE, TIME, or CALENDAR may be specified, + * and only one of the styles SHORT, MEDIUM, or LONG may be specified. + *

+ * IMPORTANT: This class is not intended to be subclassed. + *

+ * + * @since 3.3 + */ +public class DateTime : Composite { + int day, month, year, hours, minutes, seconds; + + static const int MIN_YEAR = 1752; // Gregorian switchover in North America: September 19, 1752 + static const int MAX_YEAR = 9999; + + /* Emulated DATE and TIME variables */ + Calendar calendar; + DateFormatSymbols formatSymbols; + Button down, up; + Text text; + char[] format; + Point[] fieldIndices; + int[] fieldNames; + int fieldCount, currentField = 0, characterCount = 0; + bool ignoreVerify = false; + static const char[] DEFAULT_SHORT_DATE_FORMAT = "MM/YYYY"; + static const char[] DEFAULT_MEDIUM_DATE_FORMAT = "MM/DD/YYYY"; + static const char[] DEFAULT_LONG_DATE_FORMAT = "MM/DD/YYYY"; + static const char[] DEFAULT_SHORT_TIME_FORMAT = "HH:MM AM"; + static const char[] DEFAULT_MEDIUM_TIME_FORMAT = "HH:MM:SS AM"; + static const char[] DEFAULT_LONG_TIME_FORMAT = "HH:MM:SS AM"; + + + +/** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + *

+ * The style value is either one of the style constants defined in + * class DWT which is applicable to instances of this + * class, or must be built by bitwise OR'ing together + * (that is, using the int "|" operator) two or more + * of those DWT style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + *

+ * + * @param parent a composite control which will be the parent of the new instance (cannot be null) + * @param style the style of control to construct + * + * @exception IllegalArgumentException + * @exception DWTException + * + * @see DWT#DATE + * @see DWT#TIME + * @see DWT#CALENDAR + * @see Widget#checkSubclass + * @see Widget#getStyle + */ +public this (Composite parent, int style) { + super (parent, checkStyle (style)); + if ((this.style & DWT.CALENDAR) is 0) { + /* DWT.DATE and DWT.TIME */ + calendar = Calendar.getInstance(); + formatSymbols = new DateFormatSymbols(); + + text = new Text(this, DWT.SINGLE); + if ((this.style & DWT.DATE) !is 0) { + setFormat((this.style & DWT.SHORT) !is 0 ? DEFAULT_SHORT_DATE_FORMAT : (this.style & DWT.LONG) !is 0 ? DEFAULT_LONG_DATE_FORMAT : DEFAULT_MEDIUM_DATE_FORMAT); + } else { // DWT.TIME + setFormat((this.style & DWT.SHORT) !is 0 ? DEFAULT_SHORT_TIME_FORMAT : (this.style & DWT.LONG) !is 0 ? DEFAULT_LONG_TIME_FORMAT : DEFAULT_MEDIUM_TIME_FORMAT); + } + text.setText(getFormattedString(this.style)); + Listener listener = new class () Listener { + public void handleEvent(Event event) { + switch(event.type) { + case DWT.KeyDown: onKeyDown(event); break; + case DWT.FocusIn: onFocusIn(event); break; + case DWT.FocusOut: onFocusOut(event); break; + case DWT.MouseDown: onMouseClick(event); break; + case DWT.MouseUp: onMouseClick(event); break; + case DWT.Verify: onVerify(event); break; + default: + } + } + }; + text.addListener(DWT.KeyDown, listener); + text.addListener(DWT.FocusIn, listener); + text.addListener(DWT.FocusOut, listener); + text.addListener(DWT.MouseDown, listener); + text.addListener(DWT.MouseUp, listener); + text.addListener(DWT.Verify, listener); + up = new Button(this, DWT.ARROW | DWT.UP); + //up.setToolTipText(DWT.getMessage ("SWT_Up")); //$NON-NLS-1$ + down = new Button(this, DWT.ARROW | DWT.DOWN); + //down.setToolTipText(DWT.getMessage ("SWT_Down")); //$NON-NLS-1$ + up.addListener(DWT.Selection, new class() Listener { + public void handleEvent(Event event) { + incrementField(+1); + text.setFocus(); + } + }); + down.addListener(DWT.Selection, new class() Listener { + public void handleEvent(Event event) { + incrementField(-1); + text.setFocus(); + } + }); + addListener(DWT.Resize, new class() Listener { + public void handleEvent(Event event) { + onResize(event); + } + }); + } +} + +static int checkStyle (int style) { + /* + * Even though it is legal to create this widget + * with scroll bars, they serve no useful purpose + * because they do not automatically scroll the + * widget's client area. The fix is to clear + * the DWT style. + */ + style &= ~(DWT.H_SCROLL | DWT.V_SCROLL); + style = checkBits (style, DWT.DATE, DWT.TIME, DWT.CALENDAR, 0, 0, 0); + return checkBits (style, DWT.MEDIUM, DWT.SHORT, DWT.LONG, 0, 0, 0); +} + +/** + * Adds the listener to the collection of listeners who will + * be notified when the control is selected by the user, by sending + * it one of the messages defined in the SelectionListener + * interface. + *

+ * widgetSelected is called when the user changes the control's value. + * widgetDefaultSelected is not called. + *

+ * + * @param listener the listener which should be notified + * + * @exception IllegalArgumentException + * @exception DWTException + * + * @see SelectionListener + * @see #removeSelectionListener + * @see SelectionEvent + */ +public void addSelectionListener (SelectionListener listener) { + checkWidget (); + if (listener is null) error (DWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (DWT.Selection, typedListener); + addListener (DWT.DefaultSelection, typedListener); +} + +protected void checkSubclass () { + if (!isValidSubclass ()) error (DWT.ERROR_INVALID_SUBCLASS); +} + +public Point computeSize (int wHint, int hHint, bool changed) { + checkWidget (); + int width = 0, height = 0; + if (wHint is DWT.DEFAULT || hHint is DWT.DEFAULT) { + if ((style & DWT.CALENDAR) !is 0) { + // TODO: CALENDAR computeSize + width = 300; + height = 200; + } else { + /* DWT.DATE and DWT.TIME */ + GC gc = new GC(text); + Point textSize = gc.stringExtent(getComputeSizeString(style)); + gc.dispose(); + Rectangle trim = text.computeTrim(0, 0, textSize.x, textSize.y); + Point buttonSize = up.computeSize(DWT.DEFAULT, DWT.DEFAULT, changed); + width = trim.width + buttonSize.x; + height = Math.max(trim.height, buttonSize.y); + } + } + if (width is 0) width = DEFAULT_WIDTH; + if (height is 0) height = DEFAULT_HEIGHT; + if (wHint !is DWT.DEFAULT) width = wHint; + if (hHint !is DWT.DEFAULT) height = hHint; + int border = getBorderWidth (); + width += border * 2; height += border * 2; + return new Point (width, height); +} + +void createHandle (int index) { + if ((style & DWT.CALENDAR) !is 0) { + state |= HANDLE; + fixedHandle = cast(GtkWidget*)OS.g_object_new (display.gtk_fixed_get_type (), null); + if (fixedHandle is null) error (DWT.ERROR_NO_HANDLES); + OS.gtk_fixed_set_has_window (fixedHandle, true); + handle = cast(GtkWidget*)OS.gtk_calendar_new (); + if (handle is null) error (DWT.ERROR_NO_HANDLES); + OS.gtk_container_add (fixedHandle, handle); + if (OS.GTK_VERSION >= OS.buildVERSION(2, 4, 0)) { + OS.gtk_calendar_set_display_options(handle, OS.GTK_CALENDAR_SHOW_HEADING | OS.GTK_CALENDAR_SHOW_DAY_NAMES); + } else { + OS.gtk_calendar_display_options(handle, OS.GTK_CALENDAR_SHOW_HEADING | OS.GTK_CALENDAR_SHOW_DAY_NAMES); + } + } else { + super.createHandle(index); + } +} + +void createWidget (int index) { + super.createWidget (index); + if ((style & DWT.CALENDAR) !is 0) { + getDate(); + } +} + +void commitCurrentField() { + if (characterCount > 0) { + characterCount = 0; + int fieldName = fieldNames[currentField]; + int start = fieldIndices[currentField].x; + int end = fieldIndices[currentField].y; + char[] value = text.getText(start, end - 1); + int s = value.lastIndexOf(' '); + if (s !is -1) value = value.substring(s + 1); + int newValue = unformattedIntValue(fieldName, value, characterCount is 0, calendar.getActualMaximum(fieldName)); + if (newValue !is -1) setTextField(fieldName, newValue, true, true); + } +} + +char[] formattedStringValue(int fieldName, int value, bool adjust) { + if (fieldName is Calendar.AM_PM) { + char[][] ampm = formatSymbols.getAmPmStrings(); + return ampm[value]; + } + if (adjust) { + if (fieldName is Calendar.HOUR && value is 0) { + return to!(char[])(12); + } + if (fieldName is Calendar.MONTH) { + return to!(char[])(value + 1); + } + } + return to!(char[])(value); +} + +char[] getComputeSizeString(int style) { + if ((style & DWT.DATE) !is 0) { + return (style & DWT.SHORT) !is 0 ? DEFAULT_SHORT_DATE_FORMAT : (style & DWT.LONG) !is 0 ? DEFAULT_LONG_DATE_FORMAT : DEFAULT_MEDIUM_DATE_FORMAT; + } + // DWT.TIME + return (style & DWT.SHORT) !is 0 ? DEFAULT_SHORT_TIME_FORMAT : (style & DWT.LONG) !is 0 ? DEFAULT_LONG_TIME_FORMAT : DEFAULT_MEDIUM_TIME_FORMAT; +} + +int getFieldIndex(int fieldName) { + for (int i = 0; i < fieldCount; i++) { + if (fieldNames[i] is fieldName) { + return i; + } + } + return -1; +} + +char[] getFormattedString(int style) { + if ((style & DWT.TIME) !is 0) { + char[][] ampm = formatSymbols.getAmPmStrings(); + int h = calendar.get(Calendar.HOUR); if (h is 0) h = 12; + int m = calendar.get(Calendar.MINUTE); + int s = calendar.get(Calendar.SECOND); + int a = calendar.get(Calendar.AM_PM); + if ((style & DWT.SHORT) !is 0) return "" ~ (h < 10 ? " " : "") ~ to!(char[])(h) ~ ":" ~ (m < 10 ? " " : "") ~ to!(char[])(m) ~ " " ~ ampm[a]; + return "" ~ (h < 10 ? " " : "") ~ to!(char[])(h) ~ ":" ~ (m < 10 ? " " : "") ~ to!(char[])(m) ~ ":" ~ (s < 10 ? " " : "") ~ to!(char[])(s) ~ " " ~ ampm[a]; + } + /* DWT.DATE */ + int y = calendar.get(Calendar.YEAR); + int m = calendar.get(Calendar.MONTH) + 1; + int d = calendar.get(Calendar.DAY_OF_MONTH); + if ((style & DWT.SHORT) !is 0) return "" ~ (m < 10 ? " " : "") ~ to!(char[])(m) ~ "/" ~ to!(char[])(y); + return "" ~ (m < 10 ? " " : "") ~ to!(char[])(m) ~ "/" ~ (d < 10 ? " " : "") ~ to!(char[])(d) ~ "/" ~ to!(char[])(y); +} + +void getDate() { + uint y; + uint m; + uint d; + OS.gtk_calendar_get_date(handle, &y, &m, &d); + year = y; + month = m; + day = d; +} + +/** + * Returns the receiver's date, or day of the month. + *

+ * The first day of the month is 1, and the last day depends on the month and year. + *

+ * + * @return a positive integer beginning with 1 + * + * @exception DWTException + */ +public int getDay () { + checkWidget (); + if ((style & DWT.CALENDAR) !is 0) { + getDate(); + return day; + } else { + return calendar.get(Calendar.DAY_OF_MONTH); + } +} + +/** + * Returns the receiver's hours. + *

+ * Hours is an integer between 0 and 23. + *

+ * + * @return an integer between 0 and 23 + * + * @exception DWTException + */ +public int getHours () { + checkWidget (); + if ((style & DWT.CALENDAR) !is 0) { + return hours; + } else { + return calendar.get(Calendar.HOUR_OF_DAY); + } +} + +/** + * Returns the receiver's minutes. + *

+ * Minutes is an integer between 0 and 59. + *

+ * + * @return an integer between 0 and 59 + * + * @exception DWTException + */ +public int getMinutes () { + checkWidget (); + if ((style & DWT.CALENDAR) !is 0) { + return minutes; + } else { + return calendar.get(Calendar.MINUTE); + } +} + +/** + * Returns the receiver's month. + *

+ * The first month of the year is 0, and the last month is 11. + *

+ * + * @return an integer between 0 and 11 + * + * @exception DWTException + */ +public int getMonth () { + checkWidget (); + if ((style & DWT.CALENDAR) !is 0) { + getDate(); + return month; + } else { + return calendar.get(Calendar.MONTH); + } +} + +/** + * Returns the receiver's seconds. + *

+ * Seconds is an integer between 0 and 59. + *

+ * + * @return an integer between 0 and 59 + * + * @exception DWTException + */ +public int getSeconds () { + checkWidget (); + if ((style & DWT.CALENDAR) !is 0) { + return seconds; + } else { + return calendar.get(Calendar.SECOND); + } +} + +/** + * Returns the receiver's year. + *

+ * The first year is 1752 and the last year is 9999. + *

+ * + * @return an integer between 1752 and 9999 + * + * @exception DWTException + */ +public int getYear () { + checkWidget (); + if ((style & DWT.CALENDAR) !is 0) { + getDate(); + return year; + } else { + return calendar.get(Calendar.YEAR); + } +} + +override int gtk_day_selected (GtkWidget* widget) { + sendSelectionEvent (); + return 0; +} + +override int gtk_month_changed (GtkWidget* widget) { + sendSelectionEvent (); + return 0; +} + +void hookEvents () { + super.hookEvents(); + if ((style & DWT.CALENDAR) !is 0) { + OS.g_signal_connect_closure (handle, OS.day_selected.ptr, display.closures [DAY_SELECTED], false); + OS.g_signal_connect_closure (handle, OS.month_changed.ptr, display.closures [MONTH_CHANGED], false); + } +} + +bool isValid(int fieldName, int value) { + Calendar validCalendar; + if ((style & DWT.CALENDAR) !is 0) { + validCalendar = Calendar.getInstance(); + validCalendar.set(Calendar.YEAR, year); + validCalendar.set(Calendar.MONTH, month); + } else { + validCalendar = calendar; + } + int min = validCalendar.getActualMinimum(fieldName); + int max = validCalendar.getActualMaximum(fieldName); + return value >= min && value <= max; +} + +void incrementField(int amount) { + int fieldName = fieldNames[currentField]; + int value = calendar.get(fieldName); + if (fieldName is Calendar.HOUR) { + int max = calendar.getMaximum(Calendar.HOUR); + int min = calendar.getMinimum(Calendar.HOUR); + if ((value is max && amount is 1) || (value is min && amount is -1)) { + int temp = currentField; + currentField = getFieldIndex(Calendar.AM_PM); + setTextField(Calendar.AM_PM, (calendar.get(Calendar.AM_PM) + 1) % 2, true, true); + currentField = temp; + } + } + setTextField(fieldName, value + amount, true, true); +} + +void onKeyDown(Event event) { + int fieldName; + switch (event.keyCode) { + case DWT.ARROW_RIGHT: + case DWT.KEYPAD_DIVIDE: + // a right arrow or a valid separator navigates to the field on the right, with wraping + selectField((currentField + 1) % fieldCount); + break; + case DWT.ARROW_LEFT: + // navigate to the field on the left, with wrapping + int index = currentField - 1; + selectField(index < 0 ? fieldCount - 1 : index); + break; + case DWT.ARROW_UP: + case DWT.KEYPAD_ADD: + // set the value of the current field to value + 1, with wrapping + commitCurrentField(); + incrementField(+1); + break; + case DWT.ARROW_DOWN: + case DWT.KEYPAD_SUBTRACT: + // set the value of the current field to value - 1, with wrapping + commitCurrentField(); + incrementField(-1); + break; + case DWT.HOME: + // set the value of the current field to its minimum + fieldName = fieldNames[currentField]; + setTextField(fieldName, calendar.getActualMinimum(fieldName), true, true); + break; + case DWT.END: + // set the value of the current field to its maximum + fieldName = fieldNames[currentField]; + setTextField(fieldName, calendar.getActualMaximum(fieldName), true, true); + break; + default: + switch (event.character) { + case '/': + case ':': + case '-': + case '.': + // a valid separator navigates to the field on the right, with wraping + selectField((currentField + 1) % fieldCount); + break; + default: + } + } +} + +void onFocusIn(Event event) { + selectField(currentField); +} + +void onFocusOut(Event event) { + commitCurrentField(); +} + +void onMouseClick(Event event) { + if (event.button !is 1) return; + Point sel = text.getSelection(); + for (int i = 0; i < fieldCount; i++) { + if (sel.x >= fieldIndices[i].x && sel.x <= fieldIndices[i].y) { + currentField = i; + break; + } + } + selectField(currentField); +} + +void onResize(Event event) { + Rectangle rect = getClientArea (); + int width = rect.width; + int height = rect.height; + Point buttonSize = up.computeSize(DWT.DEFAULT, height); + int buttonHeight = buttonSize.y / 2; + text.setBounds(0, 0, width - buttonSize.x, height); + up.setBounds(width - buttonSize.x, 0, buttonSize.x, buttonHeight); + down.setBounds(width - buttonSize.x, buttonHeight, buttonSize.x, buttonHeight); +} + +void onVerify(Event event) { + if (ignoreVerify) return; + event.doit = false; + int fieldName = fieldNames[currentField]; + int start = fieldIndices[currentField].x; + int end = fieldIndices[currentField].y; + int length_ = end - start; + char[] newText = event.text; + if (fieldName is Calendar.AM_PM) { + char[][] ampm = formatSymbols.getAmPmStrings(); + if (newText.equalsIgnoreCase(ampm[Calendar.AM].substring(0, 1)) || newText.equalsIgnoreCase(ampm[Calendar.AM])) { + setTextField(fieldName, Calendar.AM, true, false); + } else if (newText.equalsIgnoreCase(ampm[Calendar.PM].substring(0, 1)) || newText.equalsIgnoreCase(ampm[Calendar.PM])) { + setTextField(fieldName, Calendar.PM, true, false); + } + return; + } + if (characterCount > 0) { + try { + to!(int)(newText); + } catch (ConversionException ex) { + return; + } + char[] value = text.getText(start, end - 1); + int s = value.lastIndexOf(' '); + if (s !is -1) value = value.substring(s + 1); + newText = value ~ newText; + } + int newTextLength = newText.length; + bool first = characterCount is 0; + characterCount = (newTextLength < length_) ? newTextLength : 0; + int max = calendar.getActualMaximum(fieldName); + int min = calendar.getActualMinimum(fieldName); + int newValue = unformattedIntValue(fieldName, newText, characterCount is 0, max); + if (newValue is -1) { + characterCount = 0; + return; + } + if (first && newValue is 0 && length_ > 1) { + setTextField(fieldName, newValue, false, false); + } else if (min <= newValue && newValue <= max) { + setTextField(fieldName, newValue, characterCount is 0, characterCount is 0); + } else { + if (newTextLength >= length_) { + newText = newText.substring(newTextLength - length_ + 1); + newValue = unformattedIntValue(fieldName, newText, characterCount is 0, max); + if (newValue !is -1) { + characterCount = length_ - 1; + if (min <= newValue && newValue <= max) { + setTextField(fieldName, newValue, characterCount is 0, true); + } + } + } + } +} + +void releaseWidget () { + super.releaseWidget(); + //TODO: need to do anything here? +} + +/** + * Removes the listener from the collection of listeners who will + * be notified when the control is selected by the user. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException + * @exception DWTException + * + * @see SelectionListener + * @see #addSelectionListener + */ +public void removeSelectionListener (SelectionListener listener) { + checkWidget (); + if (listener is null) error (DWT.ERROR_NULL_ARGUMENT); + if (eventTable is null) return; + eventTable.unhook (DWT.Selection, listener); + eventTable.unhook (DWT.DefaultSelection, listener); +} + +void selectField(int index) { + if (index !is currentField) { + commitCurrentField(); + } + final int start = fieldIndices[index].x; + final int end = fieldIndices[index].y; + Point pt = text.getSelection(); + if (index is currentField && start is pt.x && end is pt.y) return; + currentField = index; + display.asyncExec(new class( start, end ) Runnable { + int start, end; + this( int start, int end ){ + this.start = start; this.end = end; + } + public void run() { + if (!text.isDisposed()) { + char[] value = text.getText(start, end - 1); + int s = value.lastIndexOf(' '); + if (s is -1 ) s = start; + else s = start + s + 1; + text.setSelection(s, end); + } + } + }); +} + +void sendSelectionEvent () { + uint y; + uint m; + uint d; + OS.gtk_calendar_get_date(handle, &y, &m, &d); + //TODO: hours, minutes, seconds? + if (d !is day || + m !is month || + y !is year) { + year = y; + month = m; + day = d; + postEvent (DWT.Selection); + } +} + +public void setBackground(Color color) { + checkWidget(); + super.setBackground(color); + if (text !is null) text.setBackground(color); +} + +public void setFont(Font font) { + checkWidget(); + super.setFont(font); + if (text !is null) text.setFont(font); + redraw(); +} + +public void setForeground(Color color) { + checkWidget(); + super.setForeground(color); + if (text !is null) text.setForeground(color); +} + +/*public*/ void setFormat(char[] string) { + checkWidget(); + // TODO: this needs to be locale sensitive + fieldCount = (style & DWT.DATE) !is 0 ? ((style & DWT.SHORT) !is 0 ? 2 : 3) : ((style & DWT.SHORT) !is 0 ? 3 : 4); + fieldIndices = new Point[fieldCount]; + fieldNames = new int[fieldCount]; + if ((style & DWT.DATE) !is 0) { + fieldNames[0] = Calendar.MONTH; + fieldIndices[0] = new Point(0, 2); + if ((style & DWT.SHORT) !is 0) { + fieldNames[1] = Calendar.YEAR; + fieldIndices[1] = new Point(3, 7); + } else { + fieldNames[1] = Calendar.DAY_OF_MONTH; + fieldIndices[1] = new Point(3, 5); + fieldNames[2] = Calendar.YEAR; + fieldIndices[2] = new Point(6, 10); + } + } else { /* DWT.TIME */ + fieldNames[0] = Calendar.HOUR; + fieldIndices[0] = new Point(0, 2); + fieldNames[1] = Calendar.MINUTE; + fieldIndices[1] = new Point(3, 5); + if ((style & DWT.SHORT) !is 0) { + fieldNames[2] = Calendar.AM_PM; + fieldIndices[2] = new Point(6, 8); + } else { + fieldNames[2] = Calendar.SECOND; + fieldIndices[2] = new Point(6, 8); + fieldNames[3] = Calendar.AM_PM; + fieldIndices[3] = new Point(9, 11); + } + } +} + +void setField(int fieldName, int value) { + if (calendar.get(fieldName) is value) return; + if (fieldName is Calendar.AM_PM) { + calendar.roll(Calendar.HOUR_OF_DAY, 12); // TODO: needs more work for setFormat and locale + } + calendar.set(fieldName, value); + notifyListeners(DWT.Selection, new Event()); +} + +void setTextField(int fieldName, int value, bool commit, bool adjust) { + if (commit) { + int max = calendar.getActualMaximum(fieldName); + int min = calendar.getActualMinimum(fieldName); + if (fieldName is Calendar.YEAR) { + max = MAX_YEAR; + min = MIN_YEAR; + /* Special case: convert 1 or 2-digit years into reasonable 4-digit years. */ + int currentYear = Calendar.getInstance().get(Calendar.YEAR); + int currentCentury = (currentYear / 100) * 100; + if (value < (currentYear + 30) % 100) value += currentCentury; + else if (value < 100) value += currentCentury - 100; + } + if (value > max) value = min; // wrap + if (value < min) value = max; // wrap + } + int start = fieldIndices[currentField].x; + int end = fieldIndices[currentField].y; + text.setSelection(start, end); + char[] newValue = formattedStringValue(fieldName, value, adjust); + TangoText buffer = new TangoText(newValue); + /* Convert leading 0's into spaces. */ + int prependCount = end - start - buffer.length(); + for (int i = 0; i < prependCount; i++) { + buffer.prepend(' '); + } + newValue = buffer.toString(); + ignoreVerify = true; + text.insert(newValue); + ignoreVerify = false; + selectField(currentField); + if (commit) setField(fieldName, value); +} + +/** + * Sets the receiver's date, or day of the month, to the specified day. + *

+ * The first day of the month is 1, and the last day depends on the month and year. + *

+ * + * @param day a positive integer beginning with 1 + * + * @exception DWTException + */ +public void setDay (int day) { + checkWidget (); + if (!isValid(Calendar.DAY_OF_MONTH, day)) return; + if ((style & DWT.CALENDAR) !is 0) { + this.day = day; + OS.gtk_calendar_select_day(handle, day); + } else { + calendar.set(Calendar.DAY_OF_MONTH, day); + updateControl(); + } +} + +/** + * Sets the receiver's hours. + *

+ * Hours is an integer between 0 and 23. + *

+ * + * @param hours an integer between 0 and 23 + * + * @exception DWTException + */ +public void setHours (int hours) { + checkWidget (); + if (!isValid(Calendar.HOUR_OF_DAY, hours)) return; + if ((style & DWT.CALENDAR) !is 0) { + this.hours = hours; + } else { + calendar.set(Calendar.HOUR_OF_DAY, hours); + updateControl(); + } +} + +/** + * Sets the receiver's minutes. + *

+ * Minutes is an integer between 0 and 59. + *

+ * + * @param minutes an integer between 0 and 59 + * + * @exception DWTException + */ +public void setMinutes (int minutes) { + checkWidget (); + if (!isValid(Calendar.MINUTE, minutes)) return; + if ((style & DWT.CALENDAR) !is 0) { + this.minutes = minutes; + } else { + calendar.set(Calendar.MINUTE, minutes); + updateControl(); + } +} + +/** + * Sets the receiver's month. + *

+ * The first month of the year is 0, and the last month is 11. + *

+ * + * @param month an integer between 0 and 11 + * + * @exception DWTException + */ +public void setMonth (int month) { + checkWidget (); + if (!isValid(Calendar.MONTH, month)) return; + if ((style & DWT.CALENDAR) !is 0) { + this.month = month; + OS.gtk_calendar_select_month(handle, month, year); + } else { + calendar.set(Calendar.MONTH, month); + updateControl(); + } +} + +/** + * Sets the receiver's seconds. + *

+ * Seconds is an integer between 0 and 59. + *

+ * + * @param seconds an integer between 0 and 59 + * + * @exception DWTException + */ +public void setSeconds (int seconds) { + checkWidget (); + if (!isValid(Calendar.SECOND, seconds)) return; + if ((style & DWT.CALENDAR) !is 0) { + this.seconds = seconds; + } else { + calendar.set(Calendar.SECOND, seconds); + updateControl(); + } +} + +/** + * Sets the receiver's year. + *

+ * The first year is 1752 and the last year is 9999. + *

+ * + * @param year an integer between 1752 and 9999 + * + * @exception DWTException + */ +public void setYear (int year) { + checkWidget (); + //if (!isValid(Calendar.YEAR, year)) return; + if (year < MIN_YEAR || year > MAX_YEAR) return; + if ((style & DWT.CALENDAR) !is 0) { + this.year = year; + OS.gtk_calendar_select_month(handle, month, year); + } else { + calendar.set(Calendar.YEAR, year); + updateControl(); + } +} + +int unformattedIntValue(int fieldName, char[] newText, bool adjust, int max) { + int newValue; + try { + newValue = to!(int)(newText); + } catch (ConversionException ex) { + return -1; + } + if (fieldName is Calendar.MONTH && adjust) { + newValue--; + if (newValue is -1) newValue = max; + } + if (fieldName is Calendar.HOUR && adjust) { + if (newValue is 12) newValue = 0; // TODO: needs more work for setFormat and locale + } + return newValue; +} + +public void updateControl() { + if (text !is null) { + char[] string = getFormattedString(style); + ignoreVerify = true; + text.setText(string); + ignoreVerify = false; + } + redraw(); +} +}