view dwt/widgets/DateTime.d @ 212:ab60f3309436

reverted the char[] to String and use the an alias.
author Frank Benoit <benoit@tionex.de>
date Mon, 05 May 2008 00:12:38 +0200
parents 219551956439
children 36f5cb12e1a2
line wrap: on
line source

/*******************************************************************************
 * 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 <benoit@tionex.de>
 *******************************************************************************/
module dwt.widgets.DateTime;

import dwt.widgets.Composite;

import dwt.DWT;
import dwt.DWTException;
import dwt.events.SelectionEvent;
import dwt.events.SelectionListener;
import dwt.graphics.Point;
import dwt.internal.win32.OS;

import dwt.widgets.TypedListener;

import dwt.dwthelper.utils;

import Integer = tango.text.convert.Integer;

//TODO - features not yet implemented: read-only, drop-down calendar for date
//TODO - font, colors, background image not yet implemented (works on some platforms)

/**
 * Instances of this class are selectable user interface
 * objects that allow the user to enter and modify date
 * or time values.
 * <p>
 * Note that although this class is a subclass of <code>Composite</code>,
 * it does not make sense to add children to it, or set a layout on it.
 * </p>
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>DATE, TIME, CALENDAR, SHORT, MEDIUM, LONG</dd>
 * <dt><b>Events:</b></dt>
 * <dd>Selection</dd>
 * </dl>
 * <p>
 * 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.
 * </p><p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p>
 *
 * @since 3.3
 */

public class DateTime : Composite {

    alias Composite.computeSize computeSize;
    alias Composite.windowProc windowProc;

    static /+const+/ WNDPROC DateTimeProc;
    static const TCHAR* DateTimeClass = OS.DATETIMEPICK_CLASS.ptr;
    static /+const+/ WNDPROC CalendarProc;
    static const TCHAR* CalendarClass = OS.MONTHCAL_CLASS.ptr;

    private static bool static_this_completed = false;
    private static void static_this() {
        if( static_this_completed ){
            return;
        }
        synchronized {
            if( static_this_completed ){
                return;
            }
            INITCOMMONCONTROLSEX icex;
            icex.dwSize = INITCOMMONCONTROLSEX.sizeof;
            icex.dwICC = OS.ICC_DATE_CLASSES;
            OS.InitCommonControlsEx (&icex);
            WNDCLASS lpWndClass;
            OS.GetClassInfo (null, DateTimeClass, &lpWndClass);
            DateTimeProc = lpWndClass.lpfnWndProc;
            OS.GetClassInfo (null, CalendarClass, &lpWndClass);
            CalendarProc = lpWndClass.lpfnWndProc;
            static_this_completed = true;
        }
    }

    static const int MARGIN = 4;
    static const int MAX_DIGIT = 9;
    static const int MAX_DAY = 31;
    static const int MAX_12HOUR = 12;
    static const int MAX_24HOUR = 24;
    static const int MAX_MINUTE = 60;
    static const int MONTH_DAY_YEAR = 0;
    static const int DAY_MONTH_YEAR = 1;
    static const int YEAR_MONTH_DAY = 2;
    static const char SINGLE_QUOTE = '\''; //$NON-NLS-1$ short date format may include quoted text
    static const char DAY_FORMAT_CONSTANT = 'd'; //$NON-NLS-1$ 1-4 lowercase 'd's represent day
    static const char MONTH_FORMAT_CONSTANT = 'M'; //$NON-NLS-1$ 1-4 uppercase 'M's represent month
    static const char YEAR_FORMAT_CONSTANT = 'y'; //$NON-NLS-1$ 1-5 lowercase 'y's represent year
    static const char HOURS_FORMAT_CONSTANT = 'h'; //$NON-NLS-1$ 1-2 upper or lowercase 'h's represent hours
    static const char MINUTES_FORMAT_CONSTANT = 'm'; //$NON-NLS-1$ 1-2 lowercase 'm's represent minutes
    static const char SECONDS_FORMAT_CONSTANT = 's'; //$NON-NLS-1$ 1-2 lowercase 's's represent seconds
    static const char AMPM_FORMAT_CONSTANT = 't'; //$NON-NLS-1$ 1-2 lowercase 't's represent am/pm
    static const int[] MONTH_NAMES = [OS.LOCALE_SMONTHNAME1, OS.LOCALE_SMONTHNAME2, OS.LOCALE_SMONTHNAME3, OS.LOCALE_SMONTHNAME4, OS.LOCALE_SMONTHNAME5, OS.LOCALE_SMONTHNAME6, OS.LOCALE_SMONTHNAME7, OS.LOCALE_SMONTHNAME8, OS.LOCALE_SMONTHNAME9, OS.LOCALE_SMONTHNAME10, OS.LOCALE_SMONTHNAME11, OS.LOCALE_SMONTHNAME12];


/**
 * Constructs a new instance of this class given its parent
 * and a style value describing its behavior and appearance.
 * <p>
 * The style value is either one of the style constants defined in
 * class <code>DWT</code> which is applicable to instances of this
 * class, or must be built by <em>bitwise OR</em>'ing together
 * (that is, using the <code>int</code> "|" operator) two or more
 * of those <code>DWT</code> style constants. The class description
 * lists the style constants that are applicable to the class.
 * Style bits are also inherited from superclasses.
 * </p>
 *
 * @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 <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
 * </ul>
 * @exception DWTException <ul>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
 *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
 * </ul>
 *
 * @see DWT#DATE
 * @see DWT#TIME
 * @see DWT#CALENDAR
 * @see Widget#checkSubclass
 * @see Widget#getStyle
 */
public this (Composite parent, int style) {
    static_this();
    super (parent, checkStyle (style));
    if ((this.style & DWT.SHORT) !is 0) {
        String buffer = ((this.style & DWT.DATE) !is 0) ? getCustomShortDateFormat() : getCustomShortTimeFormat();
        TCHAR[] lpszFormat = StrToTCHARs (0, buffer, true);
        OS.SendMessage (handle, OS.DTM_SETFORMAT, 0, lpszFormat.ptr);
    }
}

/**
 * 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 <code>SelectionListener</code>
 * interface.
 * <p>
 * <code>widgetSelected</code> is called when the user changes the control's value.
 * <code>widgetDefaultSelected</code> is not called.
 * </p>
 *
 * @param listener the listener which should be notified
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
 * </ul>
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 * @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);
}

override int callWindowProc (HWND hwnd, int msg, int wParam, int lParam) {
    if (handle is null) return 0;
    return OS.CallWindowProc ( cast(WNDPROC)windowProc(), hwnd, msg, wParam, lParam);
}

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);
}

override protected void checkSubclass () {
    if (!isValidSubclass ()) error (DWT.ERROR_INVALID_SUBCLASS);
}

override 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) {
            RECT rect;
            OS.SendMessage(handle, OS.MCM_GETMINREQRECT, 0, &rect);
            width = rect.right;
            height = rect.bottom;
        } else {
            TCHAR[] buffer = new TCHAR[128];
            HFONT newFont, oldFont;
            auto hDC = OS.GetDC (handle);
            newFont = cast(HFONT) OS.SendMessage (handle, OS.WM_GETFONT, 0, 0);
            if (newFont !is null) oldFont = OS.SelectObject (hDC, newFont);
            RECT rect;
            int flags = OS.DT_CALCRECT | OS.DT_EDITCONTROL | OS.DT_NOPREFIX;
            SYSTEMTIME systime;
            if ((style & DWT.DATE) !is 0) {
                /* Determine the widest/tallest year string. */
                systime.wMonth = 1;
                systime.wDay = 1;
                int widest = 0, secondWidest = 0, thirdWidest = 0;
                for (int i = 0; i <= MAX_DIGIT; i++) {
                    systime.wYear = cast(short) (2000 + i); // year 2000 + i is guaranteed to exist
                    int size = OS.GetDateFormat(OS.LOCALE_USER_DEFAULT, OS.DATE_SHORTDATE, &systime, null, buffer.ptr, buffer.length);
                    if (size is 0) {
                        buffer = new TCHAR[size];
                        OS.GetDateFormat(OS.LOCALE_USER_DEFAULT, OS.DATE_SHORTDATE, &systime, null, buffer.ptr, buffer.length);
                    }
                    rect.left = rect.top = rect.right = rect.bottom = 0;
                    OS.DrawText (hDC, buffer.ptr, size, &rect, flags);
                    if (rect.right - rect.left >= width) {
                        width = rect.right - rect.left;
                        thirdWidest = secondWidest;
                        secondWidest = widest;
                        widest = i;
                    }
                    height = Math.max(height, rect.bottom - rect.top);
                }
                if (widest > 1) widest = widest * 1000 + widest * 100 + widest * 10 + widest;
                else if (secondWidest > 1) widest = secondWidest * 1000 + widest * 100 + widest * 10 + widest;
                else widest = thirdWidest * 1000 + widest * 100 + widest * 10 + widest;
                systime.wYear = cast(short) widest;

                /* Determine the widest/tallest month name string. */
                width = widest = 0;
                for (short i = 0; i < MONTH_NAMES.length; i++) {
                    int name = MONTH_NAMES [i];
                    int size = OS.GetLocaleInfo (OS.LOCALE_USER_DEFAULT, name, buffer.ptr, buffer.length);
                    if (size is 0) {
                        buffer = new TCHAR[size];
                        OS.GetLocaleInfo (OS.LOCALE_USER_DEFAULT, name, buffer.ptr, buffer.length);
                    }
                    rect.left = rect.top = rect.right = rect.bottom = 0;
                    OS.DrawText (hDC, buffer.ptr, size, &rect, flags);
                    if (rect.right - rect.left > width) {
                        width = rect.right - rect.left;
                        widest = i;
                    }
                    height = Math.max(height, rect.bottom - rect.top);
                }
                systime.wMonth = cast(short) (widest + 1);

                /* Determine the widest/tallest date string in the widest month of the widest year. */
                int dwFlags = ((style & DWT.MEDIUM) !is 0) ? OS.DATE_SHORTDATE : ((style & DWT.SHORT) !is 0) ? OS.DATE_YEARMONTH : OS.DATE_LONGDATE;
                width = 0;
                for (short i = 1; i <= MAX_DAY; i++) {
                    systime.wDay = i;
                    int size = OS.GetDateFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
                    if (size is 0) {
                        buffer = new TCHAR[size];
                        OS.GetDateFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
                    }
                    rect.left = rect.top = rect.right = rect.bottom = 0;
                    OS.DrawText (hDC, buffer.ptr, size, &rect, flags);
                    width = Math.max(width, rect.right - rect.left);
                    height = Math.max(height, rect.bottom - rect.top);
                    if ((style & DWT.SHORT) !is 0) break;
                }
            } else if ((style & DWT.TIME) !is 0) {
                /* Determine the widest/tallest hour string. This code allows for the possibility of ligatures. */
                int dwFlags = ((style & DWT.SHORT) !is 0) ? OS.TIME_NOSECONDS : 0;
                short widest = 0;
                int max = is24HourTime () ? MAX_24HOUR : MAX_12HOUR;
                for (short i = 0; i < max; i++) {
                    systime.wHour = i;
                    int size = OS.GetTimeFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
                    if (size is 0) {
                        buffer = new TCHAR[size];
                        OS.GetTimeFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
                    }
                    rect.left = rect.top = rect.right = rect.bottom = 0;
                    OS.DrawText (hDC, buffer.ptr, size, &rect, flags);
                    if (rect.right - rect.left > width) {
                        width = rect.right - rect.left;
                        widest = i;
                    }
                    height = Math.max(height, rect.bottom - rect.top);
                }
                systime.wHour = widest;

                /* Determine the widest/tallest minute and second string. */
                width = widest = 0;
                for (short i = 0; i < MAX_MINUTE; i++) {
                    systime.wMinute = i;
                    int size = OS.GetTimeFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
                    if (size is 0) {
                        buffer = new TCHAR[size];
                        OS.GetTimeFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
                    }
                    rect.left = rect.top = rect.right = rect.bottom = 0;
                    OS.DrawText (hDC, buffer.ptr, size, &rect, flags);
                    if (rect.right - rect.left > width) {
                        width = rect.right - rect.left;
                        widest = i;
                    }
                    height = Math.max(height, rect.bottom - rect.top);
                }
                systime.wMinute = widest;
                systime.wSecond = widest;

                /* Determine the widest/tallest time string for the widest hour, widest minute, and if applicable, widest second. */
                int size = OS.GetTimeFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
                if (size is 0) {
                    buffer = new TCHAR[size];
                    OS.GetTimeFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
                }
                rect.left = rect.top = rect.right = rect.bottom = 0;
                OS.DrawText (hDC, buffer.ptr, size, &rect, flags);
                width = rect.right - rect.left;
                height = Math.max(height, rect.bottom - rect.top);
            }
            if (newFont !is null) OS.SelectObject (hDC, oldFont);
            OS.ReleaseDC (handle, hDC);
            int upDownWidth = OS.GetSystemMetrics (OS.SM_CXVSCROLL);
            width += upDownWidth + MARGIN;
            int upDownHeight = OS.GetSystemMetrics (OS.SM_CYVSCROLL);
            // TODO: On Vista, can send DTM_GETDATETIMEPICKERINFO to ask the Edit control what its margins are
            if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) upDownHeight += 7;
            height = Math.max (height, upDownHeight);
        }
    }
    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);
}

override void createHandle () {
    super.createHandle ();
    state &= ~(CANVAS | THEME_BACKGROUND);
}

override int defaultBackground () {
    return OS.GetSysColor (OS.COLOR_WINDOW);
}

String getComputeSizeString () {
    // TODO: Not currently used but might need for WinCE
    if ((style & DWT.DATE) !is 0) {
        if ((style & DWT.SHORT) !is 0) return getCustomShortDateFormat ();
        if ((style & DWT.MEDIUM) !is 0) return getShortDateFormat ();
        if ((style & DWT.LONG) !is 0) return getLongDateFormat ();
    }
    if ((style & DWT.TIME) !is 0) {
        if ((style & DWT.SHORT) !is 0) return getCustomShortTimeFormat ();
        return getTimeFormat ();
    }
    return "";
}

String getCustomShortDateFormat () {
    if (true) {
        TCHAR[] tchar = new TCHAR[80];
        int size = OS.GetLocaleInfo (OS.LOCALE_USER_DEFAULT, OS.LOCALE_SYEARMONTH, tchar.ptr, 80);
        return size !is 0 ? TCHARsToStr(tchar[0..size - 1])  : "M/yyyy"; //$NON-NLS-1$
    }

    //TODO: Not currently used, but may need for WinCE (or if numeric short date is required)
    String buffer = getShortDateFormat ();
    int length = buffer.length;
    bool inQuotes = false;
    int start = 0, end = 0;
    while (start < length) {
        char ch = buffer.charAt (start);
        if (ch is SINGLE_QUOTE) inQuotes = !inQuotes;
        else if (ch is DAY_FORMAT_CONSTANT && !inQuotes) {
            end = start + 1;
            while (end < length && buffer.charAt (end) is DAY_FORMAT_CONSTANT) end++;
            int ordering = getShortDateFormatOrdering ();
            switch (ordering) {
            case MONTH_DAY_YEAR:
                // skip the following separator
                while (end < length && buffer.charAt (end) !is YEAR_FORMAT_CONSTANT) end++;
                break;
            case DAY_MONTH_YEAR:
                // skip the following separator
                while (end < length && buffer.charAt (end) !is MONTH_FORMAT_CONSTANT) end++;
                break;
            case YEAR_MONTH_DAY:
                // skip the preceding separator
                while (start > 0 && buffer.charAt (start) !is MONTH_FORMAT_CONSTANT) start--;
                break;
            default:
            }
            break;
        }
        start++;
    }
    if (start < end) buffer.length = start - 1;
    return buffer;
}

String getCustomShortTimeFormat () {
    String buffer = getTimeFormat ();
    int length = buffer.length;
    bool inQuotes = false;
    int start = 0, end = 0;
    while (start < length) {
        char ch = buffer.charAt (start);
        if (ch is SINGLE_QUOTE) inQuotes = !inQuotes;
        else if (ch is SECONDS_FORMAT_CONSTANT && !inQuotes) {
            end = start + 1;
            while (end < length && buffer.charAt (end) is SECONDS_FORMAT_CONSTANT) end++;
            // skip the preceding separator
            while (start > 0 && buffer.charAt (start) !is MINUTES_FORMAT_CONSTANT) start--;
            start++;
            break;
        }
        start++;
    }
    if (start < end) buffer.length = start - 1;
    return buffer;
}

String getLongDateFormat () {
    //TODO: Not currently used, but may need for WinCE
    TCHAR tchar[80];
    int size = OS.GetLocaleInfo (OS.LOCALE_USER_DEFAULT, OS.LOCALE_SLONGDATE, tchar.ptr, 80);
    return size > 0 ? TCHARsToStr(tchar[0..size - 1]) : "dddd, MMMM dd, yyyy"; //$NON-NLS-1$
}

String getShortDateFormat () {
    //TODO: Not currently used, but may need for WinCE
    TCHAR tchar[80];
    //TODO: May need to OR with LOCALE_ICENTURY
    int size = OS.GetLocaleInfo (OS.LOCALE_USER_DEFAULT, OS.LOCALE_SSHORTDATE, tchar.ptr, 80);
    return size > 0 ? TCHARsToStr(tchar[0..size - 1]) : "M/d/yyyy"; //$NON-NLS-1$
}

int getShortDateFormatOrdering () {
    //TODO: Not currently used, but may need for WinCE
    TCHAR tchar[80];
    int size = OS.GetLocaleInfo (OS.LOCALE_USER_DEFAULT, OS.LOCALE_IDATE, tchar.ptr, 4);
    if (size > 0) {
        String number = TCHARsToStr(tchar[0..size - 1]);
        return Integer.parse (number);
    }
    return 0;
}

String getTimeFormat () {
    TCHAR tchar[80];
    int size = OS.GetLocaleInfo (OS.LOCALE_USER_DEFAULT, OS.LOCALE_STIMEFORMAT, tchar.ptr, 80);
    return size > 0 ? TCHARsToStr(tchar[0..size - 1]) : "h:mm:ss tt"; //$NON-NLS-1$
}

bool is24HourTime () {
    TCHAR tchar[4];
    int size = OS.GetLocaleInfo (OS.LOCALE_USER_DEFAULT, OS.LOCALE_ITIME, tchar.ptr, 4);
    if (size > 0) {
        String number = TCHARsToStr(tchar[0..size - 1]);
        return Integer.parse (number) !is 0;
    }
    return true;
}

/**
 * Returns the receiver's date, or day of the month.
 * <p>
 * The first day of the month is 1, and the last day depends on the month and year.
 * </p>
 *
 * @return a positive integer beginning with 1
 *
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public int getDay () {
    checkWidget ();
    SYSTEMTIME systime;
    int msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
    OS.SendMessage (handle, msg, 0, &systime);
    return systime.wDay;
}

/**
 * Returns the receiver's hours.
 * <p>
 * Hours is an integer between 0 and 23.
 * </p>
 *
 * @return an integer between 0 and 23
 *
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public int getHours () {
    checkWidget ();
    SYSTEMTIME systime;
    int msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
    OS.SendMessage (handle, msg, 0, &systime);
    return systime.wHour;
}

/**
 * Returns the receiver's minutes.
 * <p>
 * Minutes is an integer between 0 and 59.
 * </p>
 *
 * @return an integer between 0 and 59
 *
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public int getMinutes () {
    checkWidget ();
    SYSTEMTIME systime;
    int msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
    OS.SendMessage (handle, msg, 0, &systime);
    return systime.wMinute;
}

/**
 * Returns the receiver's month.
 * <p>
 * The first month of the year is 0, and the last month is 11.
 * </p>
 *
 * @return an integer between 0 and 11
 *
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public int getMonth () {
    checkWidget ();
    SYSTEMTIME systime;
    int msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
    OS.SendMessage (handle, msg, 0, &systime);
    return systime.wMonth - 1;
}

override String getNameText () {
    return "DateTime";
}

/**
 * Returns the receiver's seconds.
 * <p>
 * Seconds is an integer between 0 and 59.
 * </p>
 *
 * @return an integer between 0 and 59
 *
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public int getSeconds () {
    checkWidget ();
    SYSTEMTIME systime;
    int msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
    OS.SendMessage (handle, msg, 0, &systime);
    return systime.wSecond;
}

/**
 * Returns the receiver's year.
 * <p>
 * The first year is 1752 and the last year is 9999.
 * </p>
 *
 * @return an integer between 1752 and 9999
 *
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public int getYear () {
    checkWidget ();
    SYSTEMTIME systime;
    int msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
    OS.SendMessage (handle, msg, 0, &systime);
    return systime.wYear;
}

/**
 * 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 <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
 * </ul>
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 * @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);
}

/**
 * Sets the receiver's date, or day of the month, to the specified day.
 * <p>
 * The first day of the month is 1, and the last day depends on the month and year.
 * </p>
 *
 * @param day a positive integer beginning with 1
 *
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public void setDay (int day) {
    checkWidget ();
    SYSTEMTIME systime;
    int msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
    OS.SendMessage (handle, msg, 0, &systime);
    msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
    systime.wDay = cast(short)day;
    OS.SendMessage (handle, msg, 0, &systime);
}

/**
 * Sets the receiver's hours.
 * <p>
 * Hours is an integer between 0 and 23.
 * </p>
 *
 * @param hours an integer between 0 and 23
 *
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public void setHours (int hours) {
    checkWidget ();
    SYSTEMTIME systime;
    int msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
    OS.SendMessage (handle, msg, 0, &systime);
    msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
    systime.wHour = cast(short)hours;
    OS.SendMessage (handle, msg, 0, &systime);
}

/**
 * Sets the receiver's minutes.
 * <p>
 * Minutes is an integer between 0 and 59.
 * </p>
 *
 * @param minutes an integer between 0 and 59
 *
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public void setMinutes (int minutes) {
    checkWidget ();
    SYSTEMTIME systime;
    int msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
    OS.SendMessage (handle, msg, 0, &systime);
    msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
    systime.wMinute = cast(short)minutes;
    OS.SendMessage (handle, msg, 0, &systime);
}

/**
 * Sets the receiver's month.
 * <p>
 * The first month of the year is 0, and the last month is 11.
 * </p>
 *
 * @param month an integer between 0 and 11
 *
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public void setMonth (int month) {
    checkWidget ();
    SYSTEMTIME systime;
    int msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
    OS.SendMessage (handle, msg, 0, &systime);
    msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
    systime.wMonth = cast(short)(month + 1);
    OS.SendMessage (handle, msg, 0, &systime);
}

/**
 * Sets the receiver's seconds.
 * <p>
 * Seconds is an integer between 0 and 59.
 * </p>
 *
 * @param seconds an integer between 0 and 59
 *
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public void setSeconds (int seconds) {
    checkWidget ();
    SYSTEMTIME systime;
    int msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
    OS.SendMessage (handle, msg, 0, &systime);
    msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
    systime.wSecond = cast(short)seconds;
    OS.SendMessage (handle, msg, 0, &systime);
}

/**
 * Sets the receiver's year.
 * <p>
 * The first year is 1752 and the last year is 9999.
 * </p>
 *
 * @param year an integer between 1752 and 9999
 *
 * @exception DWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public void setYear (int year) {
    checkWidget ();
    SYSTEMTIME systime;
    int msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
    OS.SendMessage (handle, msg, 0, &systime);
    msg = (style & DWT.CALENDAR) !is 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
    systime.wYear = cast(short)year;
    OS.SendMessage (handle, msg, 0, &systime);
}

override int widgetStyle () {
    int bits = super.widgetStyle () | OS.WS_TABSTOP;
    if ((style & DWT.CALENDAR) !is 0) return bits | OS.MCS_NOTODAY;
    /*
    * Bug in Windows: When WS_CLIPCHILDREN is set in a
    * Date and Time Picker, the widget draws on top of
    * the updown control. The fix is to clear the bits.
    */
    bits &= ~OS.WS_CLIPCHILDREN;
    if ((style & DWT.TIME) !is 0) bits |= OS.DTS_TIMEFORMAT;
    if ((style & DWT.DATE) !is 0) bits |= ((style & DWT.MEDIUM) !is 0 ? OS.DTS_SHORTDATECENTURYFORMAT : OS.DTS_LONGDATEFORMAT) | OS.DTS_UPDOWN;
    return bits;
}

override String windowClass () {
    return (style & DWT.CALENDAR) !is 0 ? TCHARzToStr(CalendarClass) : TCHARzToStr(DateTimeClass);
}

override int windowProc () {
    return (style & DWT.CALENDAR) !is 0 ? cast(int)CalendarProc : cast(int)DateTimeProc;
}

override LRESULT wmNotifyChild (NMHDR* hdr, int wParam, int lParam) {
    switch (hdr.code) {
        case OS.MCN_SELCHANGE: //SENT WHEN YOU SET IT?
        case OS.DTN_DATETIMECHANGE:
            sendEvent (DWT.Selection);
            break;
        default:
    }
    return super.wmNotifyChild (hdr, wParam, lParam);
}
}