root/dwt/widgets/DateTime.d

Revision 249:b3dbd786541a, 38.4 kB (checked in by Frank Benoit <benoit@tionex.de>, 5 months ago)

Fix: compile errors

Line 
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 dwt.widgets.DateTime;
14
15 import dwt.widgets.Composite;
16 import dwt.DWT;
17 import dwt.DWTException;
18 import dwt.events.SelectionEvent;
19 import dwt.events.SelectionListener;
20 import dwt.graphics.Point;
21 import dwt.internal.win32.OS;
22
23 import dwt.widgets.TypedListener;
24
25 import dwt.dwthelper.utils;
26
27 import Integer = tango.text.convert.Integer;
28 import tango.text.convert.Format;
29
30 //TODO - features not yet implemented: read-only, drop-down calendar for date
31 //TODO - font, colors, background image not yet implemented (works on some platforms)
32
33 /**
34  * Instances of this class are selectable user interface
35  * objects that allow the user to enter and modify date
36  * or time values.
37  * <p>
38  * Note that although this class is a subclass of <code>Composite</code>,
39  * it does not make sense to add children to it, or set a layout on it.
40  * </p>
41  * <dl>
42  * <dt><b>Styles:</b></dt>
43  * <dd>DATE, TIME, CALENDAR, SHORT, MEDIUM, LONG</dd>
44  * <dt><b>Events:</b></dt>
45  * <dd>Selection</dd>
46  * </dl>
47  * <p>
48  * Note: Only one of the styles DATE, TIME, or CALENDAR may be specified,
49  * and only one of the styles SHORT, MEDIUM, or LONG may be specified.
50  * </p><p>
51  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
52  * </p>
53  *
54  * @see <a href="http://www.eclipse.org/swt/snippets/#datetime">DateTime snippets</a>
55  * @see <a href="http://www.eclipse.org/swt/examples.php">DWT Example: ControlExample</a>
56  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
57  *
58  * @since 3.3
59  */
60
61 public class DateTime : Composite {
62
63     alias Composite.computeSize computeSize;
64     alias Composite.windowProc windowProc;
65
66     bool ignoreSelection;
67     SYSTEMTIME* lastSystemTime;
68     SYSTEMTIME time; // only used in calendar mode
69     static /+const+/ WNDPROC DateTimeProc;
70     static const TCHAR[] DateTimeClass = OS.DATETIMEPICK_CLASS;
71     static /+const+/ WNDPROC CalendarProc;
72     static const TCHAR[] CalendarClass = OS.MONTHCAL_CLASS;
73
74     private static bool static_this_completed = false;
75     private static void static_this() {
76         if( static_this_completed ){
77             return;
78         }
79         synchronized {
80             if( static_this_completed ){
81                 return;
82             }
83             INITCOMMONCONTROLSEX icex;
84             icex.dwSize = INITCOMMONCONTROLSEX.sizeof;
85             icex.dwICC = OS.ICC_DATE_CLASSES;
86             OS.InitCommonControlsEx (&icex);
87             WNDCLASS lpWndClass;
88             OS.GetClassInfo (null, DateTimeClass.ptr, &lpWndClass);
89             DateTimeProc = lpWndClass.lpfnWndProc;
90             /*
91             * Feature in Windows.  The date time window class
92             * does not include CS_DBLCLKS.  This means that these
93             * controls will not get double click messages such as
94             * WM_LBUTTONDBLCLK.  The fix is to register a new
95             * window class with CS_DBLCLKS.
96             *
97             * NOTE:  Screen readers look for the exact class name
98             * of the control in order to provide the correct kind
99             * of assistance.  Therefore, it is critical that the
100             * new window class have the same name.  It is possible
101             * to register a local window class with the same name
102             * as a global class.  Since bits that affect the class
103             * are being changed, it is possible that other native
104             * code, other than DWT, could create a control with
105             * this class name, and fail unexpectedly.
106             */
107             auto hInstance = OS.GetModuleHandle (null);
108             auto hHeap = OS.GetProcessHeap ();
109             lpWndClass.hInstance = hInstance;
110             lpWndClass.style &= ~OS.CS_GLOBALCLASS;
111             lpWndClass.style |= OS.CS_DBLCLKS;
112             int byteCount = DateTimeClass.length * TCHAR.sizeof;
113             auto lpszClassName = cast(TCHAR*) OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, byteCount);
114             OS.MoveMemory (lpszClassName, DateTimeClass.ptr, byteCount);
115             lpWndClass.lpszClassName = lpszClassName;
116             OS.RegisterClass (&lpWndClass);
117             OS.HeapFree (hHeap, 0, lpszClassName);
118             OS.GetClassInfo (null, CalendarClass.ptr, &lpWndClass);
119             CalendarProc = lpWndClass.lpfnWndProc;
120             /*
121             * Feature in Windows.  The date time window class
122             * does not include CS_DBLCLKS.  This means that these
123             * controls will not get double click messages such as
124             * WM_LBUTTONDBLCLK.  The fix is to register a new
125             * window class with CS_DBLCLKS.
126             *
127             * NOTE:  Screen readers look for the exact class name
128             * of the control in order to provide the correct kind
129             * of assistance.  Therefore, it is critical that the
130             * new window class have the same name.  It is possible
131             * to register a local window class with the same name
132             * as a global class.  Since bits that affect the class
133             * are being changed, it is possible that other native
134             * code, other than DWT, could create a control with
135             * this class name, and fail unexpectedly.
136             */
137             hInstance = OS.GetModuleHandle (null);
138             hHeap = OS.GetProcessHeap ();
139             lpWndClass.hInstance = hInstance;
140             lpWndClass.style &= ~OS.CS_GLOBALCLASS;
141             lpWndClass.style |= OS.CS_DBLCLKS;
142             byteCount = CalendarClass.length * TCHAR.sizeof;
143             lpszClassName = cast(TCHAR*)OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, byteCount);
144             OS.MoveMemory (lpszClassName, CalendarClass.ptr, byteCount);
145             lpWndClass.lpszClassName = lpszClassName;
146             OS.RegisterClass (&lpWndClass);
147             OS.HeapFree (hHeap, 0, lpszClassName);
148             static_this_completed = true;
149         }
150     }
151
152     static const int MARGIN = 4;
153     static const int MAX_DIGIT = 9;
154     static const int MAX_DAY = 31;
155     static const int MAX_12HOUR = 12;
156     static const int MAX_24HOUR = 24;
157     static const int MAX_MINUTE = 60;
158     static const int MONTH_DAY_YEAR = 0;
159     static const int DAY_MONTH_YEAR = 1;
160     static const int YEAR_MONTH_DAY = 2;
161     static const char SINGLE_QUOTE = '\''; //$NON-NLS-1$ short date format may include quoted text
162     static const char DAY_FORMAT_CONSTANT = 'd'; //$NON-NLS-1$ 1-4 lowercase 'd's represent day
163     static const char MONTH_FORMAT_CONSTANT = 'M'; //$NON-NLS-1$ 1-4 uppercase 'M's represent month
164     static const char YEAR_FORMAT_CONSTANT = 'y'; //$NON-NLS-1$ 1-5 lowercase 'y's represent year
165     static const char HOURS_FORMAT_CONSTANT = 'h'; //$NON-NLS-1$ 1-2 upper or lowercase 'h's represent hours
166     static const char MINUTES_FORMAT_CONSTANT = 'm'; //$NON-NLS-1$ 1-2 lowercase 'm's represent minutes
167     static const char SECONDS_FORMAT_CONSTANT = 's'; //$NON-NLS-1$ 1-2 lowercase 's's represent seconds
168     static const char AMPM_FORMAT_CONSTANT = 't'; //$NON-NLS-1$ 1-2 lowercase 't's represent am/pm
169     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];
170
171
172 /**
173  * Constructs a new instance of this class given its parent
174  * and a style value describing its behavior and appearance.
175  * <p>
176  * The style value is either one of the style constants defined in
177  * class <code>DWT</code> which is applicable to instances of this
178  * class, or must be built by <em>bitwise OR</em>'ing together
179  * (that is, using the <code>int</code> "|" operator) two or more
180  * of those <code>DWT</code> style constants. The class description
181  * lists the style constants that are applicable to the class.
182  * Style bits are also inherited from superclasses.
183  * </p>
184  *
185  * @param parent a composite control which will be the parent of the new instance (cannot be null)
186  * @param style the style of control to construct
187  *
188  * @exception IllegalArgumentException <ul>
189  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
190  * </ul>
191  * @exception DWTException <ul>
192  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
193  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
194  * </ul>
195  *
196  * @see DWT#DATE
197  * @see DWT#TIME
198  * @see DWT#CALENDAR
199  * @see Widget#checkSubclass
200  * @see Widget#getStyle
201  */
202 public this (Composite parent, int style) {
203     static_this();
204     super (parent, checkStyle (style));
205     if ((this.style & DWT.SHORT) !is 0) {
206         String buffer = ((this.style & DWT.DATE) !is 0) ? getCustomShortDateFormat() : getCustomShortTimeFormat();
207         TCHAR[] lpszFormat = StrToTCHARs (0, buffer, true);
208         OS.SendMessage (handle, OS.DTM_SETFORMAT, 0, lpszFormat.ptr);
209     }
210 }
211
212 /**
213  * Adds the listener to the collection of listeners who will
214  * be notified when the control is selected by the user, by sending
215  * it one of the messages defined in the <code>SelectionListener</code>
216  * interface.
217  * <p>
218  * <code>widgetSelected</code> is called when the user changes the control's value.
219  * <code>widgetDefaultSelected</code> is not called.
220  * </p>
221  *
222  * @param listener the listener which should be notified
223  *
224  * @exception IllegalArgumentException <ul>
225  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
226  * </ul>
227  * @exception DWTException <ul>
228  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
229  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
230  * </ul>
231  *
232  * @see SelectionListener
233  * @see #removeSelectionListener
234  * @see SelectionEvent
235  */
236 public void addSelectionListener (SelectionListener listener) {
237     checkWidget ();
238     if (listener is null) error (DWT.ERROR_NULL_ARGUMENT);
239     TypedListener typedListener = new TypedListener (listener);
240     addListener (DWT.Selection, typedListener);
241     addListener (DWT.DefaultSelection, typedListener);
242 }
243
244 override int callWindowProc (HWND hwnd, int msg, int wParam, int lParam) {
245     if (handle is null) return 0;
246     return OS.CallWindowProc ( cast(WNDPROC)windowProc(), hwnd, msg, wParam, lParam);
247 }
248
249 static int checkStyle (int style) {
250     /*
251     * Even though it is legal to create this widget
252     * with scroll bars, they serve no useful purpose
253     * because they do not automatically scroll the
254     * widget's client area.  The fix is to clear
255     * the DWT style.
256     */
257     style &= ~(DWT.H_SCROLL | DWT.V_SCROLL);
258     style = checkBits (style, DWT.DATE, DWT.TIME, DWT.CALENDAR, 0, 0, 0);
259     return checkBits (style, DWT.MEDIUM, DWT.SHORT, DWT.LONG, 0, 0, 0);
260 }
261
262 override protected void checkSubclass () {
263     if (!isValidSubclass ()) error (DWT.ERROR_INVALID_SUBCLASS);
264 }
265
266 override public Point computeSize (int wHint, int hHint, bool changed) {
267     checkWidget ();
268     int width = 0, height = 0;
269     if (wHint is DWT.DEFAULT || hHint is DWT.DEFAULT) {
270         if ((style & DWT.CALENDAR) !is 0) {
271             RECT rect;
272             OS.SendMessage(handle, OS.MCM_GETMINREQRECT, 0, &rect);
273             width = rect.right;
274             height = rect.bottom;
275         } else {
276             TCHAR[] buffer = new TCHAR[128];
277             HFONT newFont, oldFont;
278             auto hDC = OS.GetDC (handle);
279             newFont = cast(HFONT) OS.SendMessage (handle, OS.WM_GETFONT, 0, 0);
280             if (newFont !is null) oldFont = OS.SelectObject (hDC, newFont);
281             RECT rect;
282             int flags = OS.DT_CALCRECT | OS.DT_EDITCONTROL | OS.DT_NOPREFIX;
283             SYSTEMTIME systime;
284             if ((style & DWT.DATE) !is 0) {
285                 /* Determine the widest/tallest year string. */
286                 systime.wMonth = 1;
287                 systime.wDay = 1;
288                 int widest = 0, secondWidest = 0, thirdWidest = 0;
289                 for (int i = 0; i <= MAX_DIGIT; i++) {
290                     systime.wYear = cast(short) (2000 + i); // year 2000 + i is guaranteed to exist
291                     int size = OS.GetDateFormat(OS.LOCALE_USER_DEFAULT, OS.DATE_SHORTDATE, &systime, null, buffer.ptr, buffer.length);
292                     if (size is 0) {
293                         buffer = new TCHAR[size];
294                         OS.GetDateFormat(OS.LOCALE_USER_DEFAULT, OS.DATE_SHORTDATE, &systime, null, buffer.ptr, buffer.length);
295                     }
296                     rect.left = rect.top = rect.right = rect.bottom = 0;
297                     OS.DrawText (hDC, buffer.ptr, size, &rect, flags);
298                     if (rect.right - rect.left >= width) {
299                         width = rect.right - rect.left;
300                         thirdWidest = secondWidest;
301                         secondWidest = widest;
302                         widest = i;
303                     }
304                     height = Math.max(height, rect.bottom - rect.top);
305                 }
306                 if (widest > 1) widest = widest * 1000 + widest * 100 + widest * 10 + widest;
307                 else if (secondWidest > 1) widest = secondWidest * 1000 + widest * 100 + widest * 10 + widest;
308                 else widest = thirdWidest * 1000 + widest * 100 + widest * 10 + widest;
309                 systime.wYear = cast(short) widest;
310
311                 /* Determine the widest/tallest month name string. */
312                 width = widest = 0;
313                 for (short i = 0; i < MONTH_NAMES.length; i++) {
314                     int name = MONTH_NAMES [i];
315                     int size = OS.GetLocaleInfo (OS.LOCALE_USER_DEFAULT, name, buffer.ptr, buffer.length);
316                     if (size is 0) {
317                         buffer = new TCHAR[size];
318                         OS.GetLocaleInfo (OS.LOCALE_USER_DEFAULT, name, buffer.ptr, buffer.length);
319                     }
320                     rect.left = rect.top = rect.right = rect.bottom = 0;
321                     OS.DrawText (hDC, buffer.ptr, size, &rect, flags);
322                     if (rect.right - rect.left > width) {
323                         width = rect.right - rect.left;
324                         widest = i;
325                     }
326                     height = Math.max(height, rect.bottom - rect.top);
327                 }
328                 systime.wMonth = cast(short) (widest + 1);
329
330                 /* Determine the widest/tallest date string in the widest month of the widest year. */
331                 int dwFlags = ((style & DWT.MEDIUM) !is 0) ? OS.DATE_SHORTDATE : ((style & DWT.SHORT) !is 0) ? OS.DATE_YEARMONTH : OS.DATE_LONGDATE;
332                 width = 0;
333                 for (short i = 1; i <= MAX_DAY; i++) {
334                     systime.wDay = i;
335                     int size = OS.GetDateFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
336                     if (size is 0) {
337                         buffer = new TCHAR[size];
338                         OS.GetDateFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
339                     }
340                     rect.left = rect.top = rect.right = rect.bottom = 0;
341                     OS.DrawText (hDC, buffer.ptr, size, &rect, flags);
342                     width = Math.max(width, rect.right - rect.left);
343                     height = Math.max(height, rect.bottom - rect.top);
344                     if ((style & DWT.SHORT) !is 0) break;
345                 }
346             } else if ((style & DWT.TIME) !is 0) {
347                 /* Determine the widest/tallest hour string. This code allows for the possibility of ligatures. */
348                 int dwFlags = ((style & DWT.SHORT) !is 0) ? OS.TIME_NOSECONDS : 0;
349                 short widest = 0;
350                 int max = is24HourTime () ? MAX_24HOUR : MAX_12HOUR;
351                 for (short i = 0; i < max; i++) {
352                     systime.wHour = i;
353                     int size = OS.GetTimeFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
354                     if (size is 0) {
355                         buffer = new TCHAR[size];
356                         OS.GetTimeFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
357                     }
358                     rect.left = rect.top = rect.right = rect.bottom = 0;
359                     OS.DrawText (hDC, buffer.ptr, size, &rect, flags);
360                     if (rect.right - rect.left > width) {
361                         width = rect.right - rect.left;
362                         widest = i;
363                     }
364                     height = Math.max(height, rect.bottom - rect.top);
365                 }
366                 systime.wHour = widest;
367
368                 /* Determine the widest/tallest minute and second string. */
369                 width = widest = 0;
370                 for (short i = 0; i < MAX_MINUTE; i++) {
371                     systime.wMinute = i;
372                     int size = OS.GetTimeFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
373                     if (size is 0) {
374                         buffer = new TCHAR[size];
375                         OS.GetTimeFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
376                     }
377                     rect.left = rect.top = rect.right = rect.bottom = 0;
378                     OS.DrawText (hDC, buffer.ptr, size, &rect, flags);
379                     if (rect.right - rect.left > width) {
380                         width = rect.right - rect.left;
381                         widest = i;
382                     }
383                     height = Math.max(height, rect.bottom - rect.top);
384                 }
385                 systime.wMinute = widest;
386                 systime.wSecond = widest;
387
388                 /* Determine the widest/tallest time string for the widest hour, widest minute, and if applicable, widest second. */
389                 int size = OS.GetTimeFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
390                 if (size is 0) {
391                     buffer = new TCHAR[size];
392                     OS.GetTimeFormat(OS.LOCALE_USER_DEFAULT, dwFlags, &systime, null, buffer.ptr, buffer.length);
393                 }
394                 rect.left = rect.top = rect.right = rect.bottom = 0;
395                 OS.DrawText (hDC, buffer.ptr, size, &rect, flags);
396                 width = rect.right - rect.left;
397                 height = Math.max(height, rect.bottom - rect.top);
398             }
399             if (newFont !is null) OS.SelectObject (hDC, oldFont);
400             OS.ReleaseDC (handle, hDC);
401             int upDownWidth = OS.GetSystemMetrics (OS.SM_CXVSCROLL);
402             width += upDownWidth + MARGIN;
403             int upDownHeight = OS.GetSystemMetrics (OS.SM_CYVSCROLL);
404             // TODO: On Vista, can send DTM_GETDATETIMEPICKERINFO to ask the Edit control what its margins are
405             if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) upDownHeight += 7;
406             height = Math.max (height, upDownHeight);
407         }
408     }
409     if (width is 0) width = DEFAULT_WIDTH;
410     if (height is 0) height = DEFAULT_HEIGHT;
411     if (wHint !is DWT.DEFAULT) width = wHint;
412     if (hHint !is DWT.DEFAULT) height = hHint;
413     int border = getBorderWidth ();
414     width += border * 2;
415     height += border * 2;
416     return new Point (width, height);
417 }
418
419 override void createHandle () {
420     super.createHandle ();
421     state &= ~(CANVAS | THEME_BACKGROUND);
422
423     if ((style & DWT.BORDER) is 0) {
424         int bits = OS.GetWindowLong (handle, OS.GWL_EXSTYLE);
425         bits &= ~(OS.WS_EX_CLIENTEDGE | OS.WS_EX_STATICEDGE);
426         OS.SetWindowLong (handle, OS.GWL_EXSTYLE, bits);
427     }
428 }
429
430 override int defaultBackground () {
431     return OS.GetSysColor (OS.COLOR_WINDOW);
432 }
433
434 String getComputeSizeString () {
435     // TODO: Not currently used but might need for WinCE
436     if ((style & DWT.DATE) !is 0) {
437         if ((style & DWT.SHORT) !is 0) return getCustomShortDateFormat ();
438         if ((style & DWT.MEDIUM) !is 0) return getShortDateFormat ();
439         if ((style & DWT.LONG) !is 0) return getLongDateFormat ();
440     }
441     if ((style & DWT.TIME) !is 0) {
442         if ((style & DWT.SHORT) !is 0) return getCustomShortTimeFormat ();
443         return getTimeFormat ();
444     }
445     return "";
446 }
447
448 String getCustomShortDateFormat () {
449     if (true) {
450         TCHAR[] tchar = new TCHAR[80];
451         int size = OS.GetLocaleInfo (OS.LOCALE_USER_DEFAULT, OS.LOCALE_SYEARMONTH, tchar.ptr, 80);
452         return size !is 0 ? TCHARsToStr(tchar[0..size - 1])  : "M/yyyy"; //$NON-NLS-1$
453     }
454
455     //TODO: Not currently used, but may need for WinCE (or if numeric short date is required)
456     String buffer = getShortDateFormat ();
457     int length = buffer.length;
458     bool inQuotes = false;
459     int