1 /*******************************************************************************
2  * Copyright (c) 2000, 2012 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.swt.widgets;
15 
16 import org.eclipse.swt.*;
17 import org.eclipse.swt.events.*;
18 import org.eclipse.swt.graphics.*;
19 import org.eclipse.swt.internal.win32.*;
20 
21 /**
22  * Instances of this class are selectable user interface
23  * objects that allow the user to enter and modify date
24  * or time values.
25  * <p>
26  * Note that although this class is a subclass of <code>Composite</code>,
27  * it does not make sense to add children to it, or set a layout on it.
28  * </p>
29  * <dl>
30  * <dt><b>Styles:</b></dt>
31  * <dd>DATE, TIME, CALENDAR, SHORT, MEDIUM, LONG, DROP_DOWN, CALENDAR_WEEKNUMBERS</dd>
32  * <dt><b>Events:</b></dt>
33  * <dd>DefaultSelection, Selection</dd>
34  * </dl>
35  * <p>
36  * Note: Only one of the styles DATE, TIME, or CALENDAR may be specified,
37  * and only one of the styles SHORT, MEDIUM, or LONG may be specified.
38  * The DROP_DOWN style is only valid with the DATE style.
39  * </p><p>
40  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
41  * </p>
42  *
43  * @see <a href="http://www.eclipse.org/swt/snippets/#datetime">DateTime snippets</a>
44  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
45  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
46  *
47  * @since 3.3
48  * @noextend This class is not intended to be subclassed by clients.
49  */
50 public class DateTime extends Composite {
51 	static final int MIN_YEAR = 1752; // Gregorian switchover in North America: September 19, 1752
52 	static final int MAX_YEAR = 9999;
53 	boolean doubleClick, ignoreSelection;
54 	SYSTEMTIME lastSystemTime;
55 	SYSTEMTIME time = new SYSTEMTIME (); // only used in calendar mode
56 	static final long DateTimeProc;
57 	static final TCHAR DateTimeClass = new TCHAR (0, OS.DATETIMEPICK_CLASS, true);
58 	static final long CalendarProc;
59 	static final TCHAR CalendarClass = new TCHAR (0, OS.MONTHCAL_CLASS, true);
60 	static {
61 		INITCOMMONCONTROLSEX icex = new INITCOMMONCONTROLSEX ();
62 		icex.dwSize = INITCOMMONCONTROLSEX.sizeof;
63 		icex.dwICC = OS.ICC_DATE_CLASSES;
64 		OS.InitCommonControlsEx (icex);
65 	}
66 	static {
67 		WNDCLASS lpWndClass = new WNDCLASS ();
68 		OS.GetClassInfo (0, DateTimeClass, lpWndClass);
69 		DateTimeProc = lpWndClass.lpfnWndProc;
70 		/*
71 		* Feature in Windows.  The date time window class
72 		* does not include CS_DBLCLKS.  This means that these
73 		* controls will not get double click messages such as
74 		* WM_LBUTTONDBLCLK.  The fix is to register a new
75 		* window class with CS_DBLCLKS.
76 		*
77 		* NOTE:  Screen readers look for the exact class name
78 		* of the control in order to provide the correct kind
79 		* of assistance.  Therefore, it is critical that the
80 		* new window class have the same name.  It is possible
81 		* to register a local window class with the same name
82 		* as a global class.  Since bits that affect the class
83 		* are being changed, it is possible that other native
84 		* code, other than SWT, could create a control with
85 		* this class name, and fail unexpectedly.
86 		*/
87 		lpWndClass.hInstance = OS.GetModuleHandle (null);
88 		lpWndClass.style &= ~OS.CS_GLOBALCLASS;
89 		lpWndClass.style |= OS.CS_DBLCLKS;
OS.RegisterClass(DateTimeClass, lpWndClass)90 		OS.RegisterClass (DateTimeClass, lpWndClass);
91 	}
92 	static {
93 		WNDCLASS lpWndClass = new WNDCLASS ();
94 		OS.GetClassInfo (0, CalendarClass, lpWndClass);
95 		CalendarProc = lpWndClass.lpfnWndProc;
96 		/*
97 		* Feature in Windows.  The date time window class
98 		* does not include CS_DBLCLKS.  This means that these
99 		* controls will not get double click messages such as
100 		* WM_LBUTTONDBLCLK.  The fix is to register a new
101 		* window class with CS_DBLCLKS.
102 		*
103 		* NOTE:  Screen readers look for the exact class name
104 		* of the control in order to provide the correct kind
105 		* of assistance.  Therefore, it is critical that the
106 		* new window class have the same name.  It is possible
107 		* to register a local window class with the same name
108 		* as a global class.  Since bits that affect the class
109 		* are being changed, it is possible that other native
110 		* code, other than SWT, could create a control with
111 		* this class name, and fail unexpectedly.
112 		*/
113 		lpWndClass.hInstance = OS.GetModuleHandle (null);;
114 		lpWndClass.style &= ~OS.CS_GLOBALCLASS;
115 		lpWndClass.style |= OS.CS_DBLCLKS;
OS.RegisterClass(CalendarClass, lpWndClass)116 		OS.RegisterClass (CalendarClass, lpWndClass);
117 	}
118 	static final char SINGLE_QUOTE = '\''; //$NON-NLS-1$ short date format may include quoted text
119 	static final char DAY_FORMAT_CONSTANT = 'd'; //$NON-NLS-1$ 1-4 lowercase 'd's represent day
120 	static final char MONTH_FORMAT_CONSTANT = 'M'; //$NON-NLS-1$ 1-4 uppercase 'M's represent month
121 	static final char YEAR_FORMAT_CONSTANT = 'y'; //$NON-NLS-1$ 1-5 lowercase 'y's represent year
122 	static final char HOURS_FORMAT_CONSTANT = 'h'; //$NON-NLS-1$ 1-2 upper or lowercase 'h's represent hours
123 	static final char MINUTES_FORMAT_CONSTANT = 'm'; //$NON-NLS-1$ 1-2 lowercase 'm's represent minutes
124 	static final char SECONDS_FORMAT_CONSTANT = 's'; //$NON-NLS-1$ 1-2 lowercase 's's represent seconds
125 	static final char AMPM_FORMAT_CONSTANT = 't'; //$NON-NLS-1$ 1-2 lowercase 't's represent am/pm
126 
127 
128 /**
129  * Constructs a new instance of this class given its parent
130  * and a style value describing its behavior and appearance.
131  * <p>
132  * The style value is either one of the style constants defined in
133  * class <code>SWT</code> which is applicable to instances of this
134  * class, or must be built by <em>bitwise OR</em>'ing together
135  * (that is, using the <code>int</code> "|" operator) two or more
136  * of those <code>SWT</code> style constants. The class description
137  * lists the style constants that are applicable to the class.
138  * Style bits are also inherited from superclasses.
139  * </p>
140  *
141  * @param parent a composite control which will be the parent of the new instance (cannot be null)
142  * @param style the style of control to construct
143  *
144  * @exception IllegalArgumentException <ul>
145  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
146  * </ul>
147  * @exception SWTException <ul>
148  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
149  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
150  * </ul>
151  *
152  * @see SWT#DATE
153  * @see SWT#TIME
154  * @see SWT#CALENDAR
155  * @see SWT#CALENDAR_WEEKNUMBERS
156  * @see SWT#SHORT
157  * @see SWT#MEDIUM
158  * @see SWT#LONG
159  * @see SWT#DROP_DOWN
160  * @see Widget#checkSubclass
161  * @see Widget#getStyle
162  */
DateTime(Composite parent, int style)163 public DateTime (Composite parent, int style) {
164 	super (parent, checkStyle (style));
165 	if ((this.style & SWT.SHORT) != 0) {
166 		String buffer = ((this.style & SWT.DATE) != 0) ? getCustomShortDateFormat() : getCustomShortTimeFormat();
167 		TCHAR lpszFormat = new TCHAR (0, buffer, true);
168 		OS.SendMessage (handle, OS.DTM_SETFORMAT, 0, lpszFormat);
169 	}
170 }
171 
172 /**
173  * Adds the listener to the collection of listeners who will
174  * be notified when the control is selected by the user, by sending
175  * it one of the messages defined in the <code>SelectionListener</code>
176  * interface.
177  * <p>
178  * <code>widgetSelected</code> is called when the user changes the control's value.
179  * <code>widgetDefaultSelected</code> is typically called when ENTER is pressed.
180  * </p>
181  *
182  * @param listener the listener which should be notified
183  *
184  * @exception IllegalArgumentException <ul>
185  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
186  * </ul>
187  * @exception SWTException <ul>
188  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
189  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
190  * </ul>
191  *
192  * @see SelectionListener
193  * @see #removeSelectionListener
194  * @see SelectionEvent
195  */
addSelectionListener(SelectionListener listener)196 public void addSelectionListener (SelectionListener listener) {
197 	checkWidget ();
198 	if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
199 	TypedListener typedListener = new TypedListener (listener);
200 	addListener (SWT.Selection, typedListener);
201 	addListener (SWT.DefaultSelection, typedListener);
202 }
203 
204 @Override
callWindowProc(long hwnd, int msg, long wParam, long lParam)205 long callWindowProc (long hwnd, int msg, long wParam, long lParam) {
206 	if (handle == 0) return 0;
207 	return OS.CallWindowProc (windowProc (), hwnd, msg, wParam, lParam);
208 }
209 
checkStyle(int style)210 static int checkStyle (int style) {
211 	/*
212 	* Even though it is legal to create this widget
213 	* with scroll bars, they serve no useful purpose
214 	* because they do not automatically scroll the
215 	* widget's client area.  The fix is to clear
216 	* the SWT style.
217 	*/
218 	style &= ~(SWT.H_SCROLL | SWT.V_SCROLL);
219 	style = checkBits (style, SWT.DATE, SWT.TIME, SWT.CALENDAR, 0, 0, 0);
220 	style = checkBits (style, SWT.MEDIUM, SWT.SHORT, SWT.LONG, 0, 0, 0);
221 	if ((style & SWT.DATE) == 0) style &=~ SWT.DROP_DOWN;
222 	return style;
223 }
224 
225 @Override
checkSubclass()226 protected void checkSubclass () {
227 	if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
228 }
229 
computeSizeInPixels(int wHint, int hHint, boolean changed)230 @Override Point computeSizeInPixels (int wHint, int hHint, boolean changed) {
231 	checkWidget ();
232 	int width = 0, height = 0;
233 	if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) {
234 		if ((style & SWT.CALENDAR) != 0) {
235 			RECT rect = new RECT ();
236 			OS.SendMessage(handle, OS.MCM_GETMINREQRECT, 0, rect);
237 			width = rect.right;
238 			height = rect.bottom;
239 		} else {
240 			// customize the style of the drop-down calendar, to get the correct size
241 			if ((style & SWT.CALENDAR_WEEKNUMBERS) != 0) {
242 				// get current style and add week numbers to the calendar drop-down
243 				int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
244 				OS.SendMessage(handle, OS.DTM_SETMCSTYLE, 0, bits | OS.MCS_WEEKNUMBERS);
245 			}
246 			SIZE size = new SIZE ();
247 			OS.SendMessage(handle, OS.DTM_GETIDEALSIZE, 0, size);
248 			width = size.cx;
249 			height = size.cy;
250 			// TODO: Can maybe use DTM_GETDATETIMEPICKERINFO for this
251 			int upDownHeight = OS.GetSystemMetrics (OS.SM_CYVSCROLL) + 7;
252 			height = Math.max (height, upDownHeight);
253 		}
254 	}
255 	if (width == 0) width = DEFAULT_WIDTH;
256 	if (height == 0) height = DEFAULT_HEIGHT;
257 	if (wHint != SWT.DEFAULT) width = wHint;
258 	if (hHint != SWT.DEFAULT) height = hHint;
259 	int border = getBorderWidthInPixels ();
260 	width += border * 2;
261 	height += border * 2;
262 	return new Point (width, height);
263 }
264 
265 @Override
createHandle()266 void createHandle () {
267 	super.createHandle ();
268 	state &= ~(CANVAS | THEME_BACKGROUND);
269 
270 	if ((style & SWT.BORDER) == 0) {
271 		int bits = OS.GetWindowLong (handle, OS.GWL_EXSTYLE);
272 		bits &= ~(OS.WS_EX_CLIENTEDGE | OS.WS_EX_STATICEDGE);
273 		OS.SetWindowLong (handle, OS.GWL_EXSTYLE, bits);
274 	}
275 }
276 
277 @Override
defaultBackground()278 int defaultBackground () {
279 	return OS.GetSysColor (OS.COLOR_WINDOW);
280 }
281 
getCustomShortDateFormat()282 String getCustomShortDateFormat () {
283 	TCHAR tchar = new TCHAR (getCodePage (), 80);
284 	int size = OS.GetLocaleInfo (OS.LOCALE_USER_DEFAULT, OS.LOCALE_SYEARMONTH, tchar, 80);
285 	return size != 0 ? tchar.toString (0, size - 1) : "M/yyyy"; //$NON-NLS-1$
286 }
287 
getCustomShortTimeFormat()288 String getCustomShortTimeFormat () {
289 	StringBuilder buffer = new StringBuilder (getTimeFormat ());
290 	int length = buffer.length ();
291 	boolean inQuotes = false;
292 	int start = 0, end = 0;
293 	while (start < length) {
294 		char ch = buffer.charAt (start);
295 		if (ch == SINGLE_QUOTE) inQuotes = !inQuotes;
296 		else if (ch == SECONDS_FORMAT_CONSTANT && !inQuotes) {
297 			end = start + 1;
298 			while (end < length && buffer.charAt (end) == SECONDS_FORMAT_CONSTANT) end++;
299 			// skip the preceding separator
300 			while (start > 0 && buffer.charAt (start) != MINUTES_FORMAT_CONSTANT) start--;
301 			start++;
302 			break;
303 		}
304 		start++;
305 	}
306 	if (start < end) buffer.delete (start, end);
307 	return buffer.toString ();
308 }
309 
getTimeFormat()310 String getTimeFormat () {
311 	TCHAR tchar = new TCHAR (getCodePage (), 80);
312 	int size = OS.GetLocaleInfo (OS.LOCALE_USER_DEFAULT, OS.LOCALE_STIMEFORMAT, tchar, 80);
313 	return size > 0 ? tchar.toString (0, size - 1) : "h:mm:ss tt"; //$NON-NLS-1$
314 }
315 
316 /**
317  * Returns the receiver's date, or day of the month.
318  * <p>
319  * The first day of the month is 1, and the last day depends on the month and year.
320  * </p>
321  *
322  * @return a positive integer beginning with 1
323  *
324  * @exception SWTException <ul>
325  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
326  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
327  * </ul>
328  */
getDay()329 public int getDay () {
330 	checkWidget ();
331 	SYSTEMTIME systime = new SYSTEMTIME ();
332 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
333 	OS.SendMessage (handle, msg, 0, systime);
334 	return systime.wDay;
335 }
336 
337 /**
338  * Returns the receiver's hours.
339  * <p>
340  * Hours is an integer between 0 and 23.
341  * </p>
342  *
343  * @return an integer between 0 and 23
344  *
345  * @exception SWTException <ul>
346  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
347  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
348  * </ul>
349  */
getHours()350 public int getHours () {
351 	checkWidget ();
352 	if ((style & SWT.CALENDAR) != 0) return time.wHour;
353 	SYSTEMTIME systime = new SYSTEMTIME ();
354 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
355 	OS.SendMessage (handle, msg, 0, systime);
356 	return systime.wHour;
357 }
358 
359 /**
360  * Returns the receiver's minutes.
361  * <p>
362  * Minutes is an integer between 0 and 59.
363  * </p>
364  *
365  * @return an integer between 0 and 59
366  *
367  * @exception SWTException <ul>
368  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
369  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
370  * </ul>
371  */
getMinutes()372 public int getMinutes () {
373 	checkWidget ();
374 	if ((style & SWT.CALENDAR) != 0) return time.wMinute;
375 	SYSTEMTIME systime = new SYSTEMTIME ();
376 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
377 	OS.SendMessage (handle, msg, 0, systime);
378 	return systime.wMinute;
379 }
380 
381 /**
382  * Returns the receiver's month.
383  * <p>
384  * The first month of the year is 0, and the last month is 11.
385  * </p>
386  *
387  * @return an integer between 0 and 11
388  *
389  * @exception SWTException <ul>
390  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
391  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
392  * </ul>
393  */
getMonth()394 public int getMonth () {
395 	checkWidget ();
396 	SYSTEMTIME systime = new SYSTEMTIME ();
397 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
398 	OS.SendMessage (handle, msg, 0, systime);
399 	return systime.wMonth - 1;
400 }
401 
402 @Override
getNameText()403 String getNameText() {
404 	return (style & SWT.TIME) != 0 ? getHours() + ":" + getMinutes() + ":" + getSeconds()
405 			: (getMonth() + 1) + "/" + getDay() + "/" + getYear();
406 }
407 
408 /**
409  * Returns the receiver's seconds.
410  * <p>
411  * Seconds is an integer between 0 and 59.
412  * </p>
413  *
414  * @return an integer between 0 and 59
415  *
416  * @exception SWTException <ul>
417  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
418  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
419  * </ul>
420  */
getSeconds()421 public int getSeconds () {
422 	checkWidget ();
423 	if ((style & SWT.CALENDAR) != 0) return time.wSecond;
424 	SYSTEMTIME systime = new SYSTEMTIME ();
425 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
426 	OS.SendMessage (handle, msg, 0, systime);
427 	return systime.wSecond;
428 }
429 
430 /**
431  * Returns the receiver's year.
432  * <p>
433  * The first year is 1752 and the last year is 9999.
434  * </p>
435  *
436  * @return an integer between 1752 and 9999
437  *
438  * @exception SWTException <ul>
439  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
440  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
441  * </ul>
442  */
getYear()443 public int getYear () {
444 	checkWidget ();
445 	SYSTEMTIME systime = new SYSTEMTIME ();
446 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
447 	OS.SendMessage (handle, msg, 0, systime);
448 	return systime.wYear;
449 }
450 
451 @Override
releaseWidget()452 void releaseWidget () {
453 	super.releaseWidget ();
454 	lastSystemTime = null;
455 }
456 
457 /**
458  * Removes the listener from the collection of listeners who will
459  * be notified when the control is selected by the user.
460  *
461  * @param listener the listener which should no longer be notified
462  *
463  * @exception IllegalArgumentException <ul>
464  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
465  * </ul>
466  * @exception SWTException <ul>
467  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
468  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
469  * </ul>
470  *
471  * @see SelectionListener
472  * @see #addSelectionListener
473  */
removeSelectionListener(SelectionListener listener)474 public void removeSelectionListener (SelectionListener listener) {
475 	checkWidget ();
476 	if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
477 	if (eventTable == null) return;
478 	eventTable.unhook (SWT.Selection, listener);
479 	eventTable.unhook (SWT.DefaultSelection, listener);
480 }
481 
482 /**
483  * Sets the receiver's year, month, and day in a single operation.
484  * <p>
485  * This is the recommended way to set the date, because setting the year,
486  * month, and day separately may result in invalid intermediate dates.
487  * </p>
488  *
489  * @param year an integer between 1752 and 9999
490  * @param month an integer between 0 and 11
491  * @param day a positive integer beginning with 1
492  *
493  * @exception SWTException <ul>
494  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
495  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
496  * </ul>
497  *
498  * @since 3.4
499  */
setDate(int year, int month, int day)500 public void setDate (int year, int month, int day) {
501 	checkWidget ();
502 	if (year < MIN_YEAR || year > MAX_YEAR) return;
503 	SYSTEMTIME systime = new SYSTEMTIME ();
504 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
505 	OS.SendMessage (handle, msg, 0, systime);
506 	msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
507 	systime.wYear = (short)year;
508 	systime.wMonth = (short)(month + 1);
509 	systime.wDay = (short)day;
510 	OS.SendMessage (handle, msg, 0, systime);
511 	lastSystemTime = null;
512 }
513 
514 /**
515  * Sets the receiver's date, or day of the month, to the specified day.
516  * <p>
517  * The first day of the month is 1, and the last day depends on the month and year.
518  * If the specified day is not valid for the receiver's month and year, then it is ignored.
519  * </p>
520  *
521  * @param day a positive integer beginning with 1
522  *
523  * @exception SWTException <ul>
524  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
525  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
526  * </ul>
527  *
528  * @see #setDate
529  */
setDay(int day)530 public void setDay (int day) {
531 	checkWidget ();
532 	SYSTEMTIME systime = new SYSTEMTIME ();
533 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
534 	OS.SendMessage (handle, msg, 0, systime);
535 	msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
536 	systime.wDay = (short)day;
537 	OS.SendMessage (handle, msg, 0, systime);
538 	lastSystemTime = null;
539 }
540 
541 /**
542  * Sets the receiver's hours.
543  * <p>
544  * Hours is an integer between 0 and 23.
545  * </p>
546  *
547  * @param hours an integer between 0 and 23
548  *
549  * @exception SWTException <ul>
550  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
551  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
552  * </ul>
553  */
setHours(int hours)554 public void setHours (int hours) {
555 	checkWidget ();
556 	if (hours < 0 || hours > 23) return;
557 	SYSTEMTIME systime = new SYSTEMTIME ();
558 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
559 	OS.SendMessage (handle, msg, 0, systime);
560 	msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
561 	systime.wHour = (short)hours;
562 	OS.SendMessage (handle, msg, 0, systime);
563 	if ((style & SWT.CALENDAR) != 0 && hours >= 0 && hours <= 23) time.wHour = (short)hours;
564 }
565 
566 /**
567  * Sets the receiver's minutes.
568  * <p>
569  * Minutes is an integer between 0 and 59.
570  * </p>
571  *
572  * @param minutes an integer between 0 and 59
573  *
574  * @exception SWTException <ul>
575  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
576  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
577  * </ul>
578  */
setMinutes(int minutes)579 public void setMinutes (int minutes) {
580 	checkWidget ();
581 	if (minutes < 0 || minutes > 59) return;
582 	SYSTEMTIME systime = new SYSTEMTIME ();
583 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
584 	OS.SendMessage (handle, msg, 0, systime);
585 	msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
586 	systime.wMinute = (short)minutes;
587 	OS.SendMessage (handle, msg, 0, systime);
588 	if ((style & SWT.CALENDAR) != 0 && minutes >= 0 && minutes <= 59) time.wMinute = (short)minutes;
589 }
590 
591 /**
592  * Sets the receiver's month.
593  * <p>
594  * The first month of the year is 0, and the last month is 11.
595  * If the specified month is not valid for the receiver's day and year, then it is ignored.
596  * </p>
597  *
598  * @param month an integer between 0 and 11
599  *
600  * @exception SWTException <ul>
601  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
602  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
603  * </ul>
604  *
605  * @see #setDate
606  */
setMonth(int month)607 public void setMonth (int month) {
608 	checkWidget ();
609 	SYSTEMTIME systime = new SYSTEMTIME ();
610 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
611 	OS.SendMessage (handle, msg, 0, systime);
612 	msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
613 	systime.wMonth = (short)(month + 1);
614 	OS.SendMessage (handle, msg, 0, systime);
615 	lastSystemTime = null;
616 }
617 
618 @Override
setOrientation(int orientation)619 public void setOrientation (int orientation) {
620 	/* Currently supported only for CALENDAR style. */
621 	if ((style & SWT.CALENDAR) != 0) super.setOrientation (orientation);
622 }
623 /**
624  * Sets the receiver's seconds.
625  * <p>
626  * Seconds is an integer between 0 and 59.
627  * </p>
628  *
629  * @param seconds an integer between 0 and 59
630  *
631  * @exception SWTException <ul>
632  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
633  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
634  * </ul>
635  */
setSeconds(int seconds)636 public void setSeconds (int seconds) {
637 	checkWidget ();
638 	if (seconds < 0 || seconds > 59) return;
639 	SYSTEMTIME systime = new SYSTEMTIME ();
640 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
641 	OS.SendMessage (handle, msg, 0, systime);
642 	msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
643 	systime.wSecond = (short)seconds;
644 	OS.SendMessage (handle, msg, 0, systime);
645 	if ((style & SWT.CALENDAR) != 0 && seconds >= 0 && seconds <= 59) time.wSecond = (short)seconds;
646 }
647 
648 /**
649  * Sets the receiver's hours, minutes, and seconds in a single operation.
650  *
651  * @param hours an integer between 0 and 23
652  * @param minutes an integer between 0 and 59
653  * @param seconds an integer between 0 and 59
654  *
655  * @exception SWTException <ul>
656  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
657  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
658  * </ul>
659  *
660  * @since 3.4
661  */
setTime(int hours, int minutes, int seconds)662 public void setTime (int hours, int minutes, int seconds) {
663 	checkWidget ();
664 	if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59 || seconds < 0 || seconds > 59) return;
665 	SYSTEMTIME systime = new SYSTEMTIME ();
666 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
667 	OS.SendMessage (handle, msg, 0, systime);
668 	msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
669 	systime.wHour = (short)hours;
670 	systime.wMinute = (short)minutes;
671 	systime.wSecond = (short)seconds;
672 	OS.SendMessage (handle, msg, 0, systime);
673 	if ((style & SWT.CALENDAR) != 0
674 			&& hours >= 0 && hours <= 23
675 			&& minutes >= 0 && minutes <= 59
676 			&& seconds >= 0 && seconds <= 59) {
677 		time.wHour = (short)hours;
678 		time.wMinute = (short)minutes;
679 		time.wSecond = (short)seconds;
680 	}
681 }
682 
683 /**
684  * Sets the receiver's year.
685  * <p>
686  * The first year is 1752 and the last year is 9999.
687  * If the specified year is not valid for the receiver's day and month, then it is ignored.
688  * </p>
689  *
690  * @param year an integer between 1752 and 9999
691  *
692  * @exception SWTException <ul>
693  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
694  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
695  * </ul>
696  *
697  * @see #setDate
698  */
setYear(int year)699 public void setYear (int year) {
700 	checkWidget ();
701 	if (year < MIN_YEAR || year > MAX_YEAR) return;
702 	SYSTEMTIME systime = new SYSTEMTIME ();
703 	int msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_GETCURSEL : OS.DTM_GETSYSTEMTIME;
704 	OS.SendMessage (handle, msg, 0, systime);
705 	msg = (style & SWT.CALENDAR) != 0 ? OS.MCM_SETCURSEL : OS.DTM_SETSYSTEMTIME;
706 	systime.wYear = (short)year;
707 	OS.SendMessage (handle, msg, 0, systime);
708 	lastSystemTime = null;
709 }
710 
711 @Override
widgetStyle()712 int widgetStyle () {
713 	int bits = super.widgetStyle () | OS.WS_TABSTOP;
714 	if ((style & SWT.CALENDAR_WEEKNUMBERS) != 0) {
715 		bits |= OS.MCS_WEEKNUMBERS;
716 	}
717 	if ((style & SWT.CALENDAR) != 0) return bits | OS.MCS_NOTODAY;
718 	/*
719 	* Bug in Windows: When WS_CLIPCHILDREN is set in a
720 	* Date and Time Picker, the widget draws on top of
721 	* the updown control. The fix is to clear the bits.
722 	*/
723 	bits &= ~OS.WS_CLIPCHILDREN;
724 	if ((style & SWT.TIME) != 0) bits |= OS.DTS_TIMEFORMAT;
725 	if ((style & SWT.DATE) != 0) {
726 		bits |= ((style & SWT.MEDIUM) != 0 ? OS.DTS_SHORTDATECENTURYFORMAT : OS.DTS_LONGDATEFORMAT);
727 		if ((style & SWT.DROP_DOWN) == 0) bits |= OS.DTS_UPDOWN;
728 	}
729 	return bits;
730 }
731 
732 @Override
windowClass()733 TCHAR windowClass () {
734 	return (style & SWT.CALENDAR) != 0 ? CalendarClass : DateTimeClass;
735 }
736 
737 @Override
windowProc()738 long windowProc () {
739 	return (style & SWT.CALENDAR) != 0 ? CalendarProc : DateTimeProc;
740 }
741 
742 @Override
wmNotifyChild(NMHDR hdr, long wParam, long lParam)743 LRESULT wmNotifyChild (NMHDR hdr, long wParam, long lParam) {
744 	switch (hdr.code) {
745 		case OS.DTN_CLOSEUP: {
746 			/*
747 			* Feature in Windows.  When the user selects the drop-down button,
748 			* the DateTimePicker runs a modal loop and consumes WM_LBUTTONUP.
749 			* This is done without adding a mouse capture.  Since WM_LBUTTONUP
750 			* is not delivered, the normal mechanism where a mouse capture is
751 			* added on mouse down and removed when the mouse is released
752 			* is broken, leaving an unwanted capture.  The fix is to avoid
753 			* setting capture on mouse down right after WM_LBUTTONUP is consumed.
754 			*/
755 			display.captureChanged = true;
756 			break;
757 		}
758 		case OS.MCN_SELCHANGE: {
759 			if (ignoreSelection) break;
760 			SYSTEMTIME systime = new SYSTEMTIME ();
761 			OS.SendMessage (handle, OS.MCM_GETCURSEL, 0, systime);
762 			sendSelectionEvent (SWT.Selection);
763 			break;
764 		}
765 		case OS.DTN_DATETIMECHANGE: {
766 			SYSTEMTIME systime = new SYSTEMTIME ();
767 			OS.SendMessage (handle, OS.DTM_GETSYSTEMTIME, 0, systime);
768 			if (lastSystemTime == null || systime.wDay != lastSystemTime.wDay || systime.wMonth != lastSystemTime.wMonth || systime.wYear != lastSystemTime.wYear) {
769 				sendSelectionEvent (SWT.Selection);
770 				if ((style & SWT.TIME) == 0) lastSystemTime = systime;
771 			}
772 			break;
773 		}
774 	}
775 	return super.wmNotifyChild (hdr, wParam, lParam);
776 }
777 
778 @Override
WM_CHAR(long wParam, long lParam)779 LRESULT WM_CHAR (long wParam, long lParam) {
780 	LRESULT result = super.WM_CHAR (wParam, lParam);
781 	if (result != null) return result;
782 	/*
783 	* Feature in Windows.  For some reason, when the
784 	* user presses tab, return or escape, Windows beeps.
785 	* The fix is to look for these keys and not call
786 	* the window proc.
787 	*/
788 	switch ((int)wParam) {
789 		case SWT.CR:
790 			sendSelectionEvent (SWT.DefaultSelection);
791 			// FALL THROUGH
792 		case SWT.TAB:
793 		case SWT.ESC: return LRESULT.ZERO;
794 	}
795 	return result;
796 }
797 
798 @Override
WM_LBUTTONDBLCLK(long wParam, long lParam)799 LRESULT WM_LBUTTONDBLCLK (long wParam, long lParam) {
800 	LRESULT result = super.WM_LBUTTONDBLCLK (wParam, lParam);
801 	if (isDisposed ()) return LRESULT.ZERO;
802 	if ((style & SWT.CALENDAR) != 0) {
803 		MCHITTESTINFO pMCHitTest = new MCHITTESTINFO ();
804 		pMCHitTest.cbSize = MCHITTESTINFO.sizeof;
805 		POINT pt = new POINT ();
806 		pt.x = OS.GET_X_LPARAM (lParam);
807 		pt.y = OS.GET_Y_LPARAM (lParam);
808 		pMCHitTest.pt = pt;
809 		long code = OS.SendMessage (handle, OS.MCM_HITTEST, 0, pMCHitTest);
810 		if ((code & OS.MCHT_CALENDARDATE) == OS.MCHT_CALENDARDATE) doubleClick = true;
811 	}
812 	return result;
813 }
814 
815 @Override
WM_LBUTTONDOWN(long wParam, long lParam)816 LRESULT WM_LBUTTONDOWN (long wParam, long lParam) {
817 	LRESULT result = super.WM_LBUTTONDOWN (wParam, lParam);
818 	if (result == LRESULT.ZERO) return result;
819 	doubleClick = false;
820 	/*
821 	* Feature in Windows. For some reason, the calendar control
822 	* does not take focus on WM_LBUTTONDOWN.  The fix is to
823 	* explicitly set focus.
824 	*/
825 	if ((style & SWT.CALENDAR) != 0) {
826 		if ((style & SWT.NO_FOCUS) == 0) OS.SetFocus (handle);
827 	}
828 	return result;
829 }
830 
831 @Override
WM_LBUTTONUP(long wParam, long lParam)832 LRESULT WM_LBUTTONUP (long wParam, long lParam) {
833 	LRESULT result = super.WM_LBUTTONUP (wParam, lParam);
834 	if (isDisposed ()) return LRESULT.ZERO;
835 	if (doubleClick) sendSelectionEvent (SWT.DefaultSelection);
836 	doubleClick = false;
837 	return result;
838 }
839 
840 @Override
WM_TIMER(long wParam, long lParam)841 LRESULT WM_TIMER (long wParam, long lParam) {
842 	LRESULT result = super.WM_TIMER (wParam, lParam);
843 	if (result != null) return result;
844 	/*
845 	* Feature in Windows. For some reason, Windows sends WM_NOTIFY with
846 	* MCN_SELCHANGE at regular intervals. This is unexpected. The fix is
847 	* to ignore MCN_SELCHANGE during WM_TIMER.
848 	*/
849 	ignoreSelection = true;
850 	long code = callWindowProc(handle, OS.WM_TIMER, wParam, lParam);
851 	ignoreSelection = false;
852 	return code == 0 ? LRESULT.ZERO : new LRESULT(code);
853 }
854 }
855