2015-05-22 09:36:24 -05:00
// 22 may 2015
2016-04-22 19:40:16 -05:00
# include "uipriv_windows.hpp"
2015-05-22 09:36:24 -05:00
2015-08-30 11:25:53 -05:00
struct uiDateTimePicker {
uiWindowsControl c ;
2015-05-22 09:36:24 -05:00
HWND hwnd ;
2018-05-13 11:05:09 -05:00
void ( * onChanged ) ( uiDateTimePicker * , void * ) ;
2018-02-23 18:02:39 -06:00
void * onChangedData ;
2015-05-22 09:36:24 -05:00
} ;
2015-06-04 08:55:28 -05:00
// utility functions
# define GLI(what, buf, n) GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, what, buf, n)
2015-06-05 14:43:18 -05:00
// The real date/time picker does a manual replacement of "yy" with "yyyy" for DTS_SHORTDATECENTURYFORMAT.
// Because we're also duplicating its functionality (see below), we have to do it too.
static WCHAR * expandYear ( WCHAR * dts , int n )
{
WCHAR * out ;
WCHAR * p , * q ;
int ny = 0 ;
// allocate more than we need to be safe
2018-04-15 17:12:58 -05:00
out = ( WCHAR * ) uiprivAlloc ( ( n * 3 ) * sizeof ( WCHAR ) , " WCHAR[] " ) ;
2015-06-05 14:43:18 -05:00
q = out ;
for ( p = dts ; * p ! = L ' \0 ' ; p + + ) {
// first, if the current character is a y, increment the number of consecutive ys
// otherwise, stop counting, and if there were only two, add two more to make four
if ( * p ! = L ' y ' ) {
if ( ny = = 2 ) {
* q + + = L ' y ' ;
* q + + = L ' y ' ;
}
ny = 0 ;
} else
ny + + ;
// next, handle quoted blocks
// we do this AFTER the above so yy'abc' becomes yyyy'abc' and not yy'abc'yy
// this handles the case of 'a''b' elegantly as well
if ( * p = = L ' \' ' ) {
// copy the opening quote
* q + + = * p ;
// copy the contents
for ( ; ; ) {
p + + ;
if ( * p = = L ' \' ' )
break ;
if ( * p = = L ' \0 ' )
2018-04-15 20:46:08 -05:00
uiprivImplBug ( " unterminated quote in system-provided locale date string in expandYear() " ) ;
2015-06-05 14:43:18 -05:00
* q + + = * p ;
}
// and fall through to copy the closing quote
}
// copy the current character
* q + + = * p ;
}
// handle trailing yy
if ( ny = = 2 ) {
* q + + = L ' y ' ;
* q + + = L ' y ' ;
}
* q + + = L ' \0 ' ;
return out ;
}
2015-06-04 08:55:28 -05:00
// Windows has no combined date/time prebuilt constant; we have to build the format string ourselves
2016-04-22 19:40:16 -05:00
// TODO use a default format if one fails
2015-06-04 08:55:28 -05:00
static void setDateTimeFormat ( HWND hwnd )
{
2015-06-05 14:43:18 -05:00
WCHAR * unexpandedDate , * date ;
WCHAR * time ;
WCHAR * datetime ;
2015-06-04 08:55:28 -05:00
int ndate , ntime ;
ndate = GLI ( LOCALE_SSHORTDATE , NULL , 0 ) ;
if ( ndate = = 0 )
2016-04-22 19:40:16 -05:00
logLastError ( L " error getting date string length " ) ;
2018-04-15 17:12:58 -05:00
date = ( WCHAR * ) uiprivAlloc ( ndate * sizeof ( WCHAR ) , " WCHAR[] " ) ;
2015-06-04 08:55:28 -05:00
if ( GLI ( LOCALE_SSHORTDATE , date , ndate ) = = 0 )
2016-04-22 19:40:16 -05:00
logLastError ( L " error geting date string " ) ;
2015-06-05 14:43:18 -05:00
unexpandedDate = date ; // so we can free it
date = expandYear ( unexpandedDate , ndate ) ;
2018-04-15 17:12:58 -05:00
uiprivFree ( unexpandedDate ) ;
2015-06-04 08:55:28 -05:00
ntime = GLI ( LOCALE_STIMEFORMAT , NULL , 0 ) ;
if ( ndate = = 0 )
2016-04-22 19:40:16 -05:00
logLastError ( L " error getting time string length " ) ;
2018-04-15 17:12:58 -05:00
time = ( WCHAR * ) uiprivAlloc ( ntime * sizeof ( WCHAR ) , " WCHAR[] " ) ;
2015-06-04 08:55:28 -05:00
if ( GLI ( LOCALE_STIMEFORMAT , time , ntime ) = = 0 )
2016-04-22 19:40:16 -05:00
logLastError ( L " error geting time string " ) ;
2015-06-04 08:55:28 -05:00
2016-04-22 19:40:16 -05:00
datetime = strf ( L " %s %s " , date , time ) ;
2015-06-04 08:55:28 -05:00
if ( SendMessageW ( hwnd , DTM_SETFORMAT , 0 , ( LPARAM ) datetime ) = = 0 )
2016-04-22 19:40:16 -05:00
logLastError ( L " error applying format string to date/time picker " ) ;
2015-06-04 08:55:28 -05:00
2018-04-15 17:12:58 -05:00
uiprivFree ( datetime ) ;
uiprivFree ( time ) ;
uiprivFree ( date ) ;
2015-06-04 08:55:28 -05:00
}
// control implementation
2016-04-29 12:50:08 -05:00
static void uiDateTimePickerDestroy ( uiControl * c )
{
uiDateTimePicker * d = uiDateTimePicker ( c ) ;
uiWindowsUnregisterReceiveWM_WININICHANGE ( d - > hwnd ) ;
2018-02-23 18:02:39 -06:00
uiWindowsUnregisterWM_NOTIFYHandler ( d - > hwnd ) ;
2016-04-29 12:50:08 -05:00
uiWindowsEnsureDestroyWindow ( d - > hwnd ) ;
2016-04-29 16:08:31 -05:00
uiFreeControl ( uiControl ( d ) ) ;
2016-04-29 12:50:08 -05:00
}
uiWindowsControlAllDefaultsExceptDestroy ( uiDateTimePicker )
2015-06-05 14:53:26 -05:00
// the height returned from DTM_GETIDEALSIZE is unreliable; see http://stackoverflow.com/questions/30626549/what-is-the-proper-use-of-dtm-getidealsize-treating-the-returned-size-as-pixels
2015-05-22 09:36:24 -05:00
// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing
# define entryHeight 14
2016-06-13 20:54:15 -05:00
static void uiDateTimePickerMinimumSize ( uiWindowsControl * c , int * width , int * height )
2015-05-22 09:36:24 -05:00
{
2015-08-30 11:25:53 -05:00
uiDateTimePicker * d = uiDateTimePicker ( c ) ;
2015-06-05 14:53:26 -05:00
SIZE s ;
2016-04-29 12:50:08 -05:00
uiWindowsSizing sizing ;
int y ;
2015-06-05 14:53:26 -05:00
s . cx = 0 ;
s . cy = 0 ;
2015-08-31 16:50:23 -05:00
SendMessageW ( d - > hwnd , DTM_GETIDEALSIZE , 0 , ( LPARAM ) ( & s ) ) ;
2015-06-05 14:53:26 -05:00
* width = s . cx ;
2016-04-29 12:50:08 -05:00
y = entryHeight ;
uiWindowsGetSizing ( d - > hwnd , & sizing ) ;
uiWindowsSizingDlgUnitsToPixels ( & sizing , NULL , & y ) ;
* height = y ;
2015-05-22 09:36:24 -05:00
}
2018-02-23 18:02:39 -06:00
static BOOL onWM_NOTIFY ( uiControl * c , HWND hwnd , NMHDR * nmhdr , LRESULT * lResult )
{
uiDateTimePicker * d = uiDateTimePicker ( c ) ;
if ( nmhdr - > code ! = DTN_DATETIMECHANGE )
return FALSE ;
( * ( d - > onChanged ) ) ( d , d - > onChangedData ) ;
* lResult = 0 ;
return TRUE ;
}
2018-05-05 23:21:25 -05:00
static void fromSystemTime ( SYSTEMTIME * systime , struct tm * time )
2018-02-23 18:02:39 -06:00
{
2018-05-05 23:21:25 -05:00
ZeroMemory ( time , sizeof ( struct tm ) ) ;
2018-02-23 18:02:39 -06:00
time - > tm_sec = systime - > wSecond ;
time - > tm_min = systime - > wMinute ;
time - > tm_hour = systime - > wHour ;
time - > tm_mday = systime - > wDay ;
time - > tm_mon = systime - > wMonth - 1 ;
time - > tm_year = systime - > wYear - 1900 ;
time - > tm_wday = systime - > wDayOfWeek ;
time - > tm_isdst = - 1 ;
}
2018-05-05 23:21:25 -05:00
static void toSystemTime ( const struct tm * time , SYSTEMTIME * systime )
2018-02-23 18:02:39 -06:00
{
2018-05-05 23:21:25 -05:00
ZeroMemory ( systime , sizeof ( SYSTEMTIME ) ) ;
2018-02-23 18:02:39 -06:00
systime - > wYear = time - > tm_year + 1900 ;
systime - > wMonth = time - > tm_mon + 1 ;
systime - > wDayOfWeek = time - > tm_wday ;
systime - > wDay = time - > tm_mday ;
systime - > wHour = time - > tm_hour ;
systime - > wMinute = time - > tm_min ;
systime - > wSecond = time - > tm_sec ;
}
static void defaultOnChanged ( uiDateTimePicker * d , void * data )
{
// do nothing
}
void uiDateTimePickerTime ( uiDateTimePicker * d , struct tm * time )
{
SYSTEMTIME systime ;
2018-05-05 23:21:25 -05:00
if ( SendMessageW ( d - > hwnd , DTM_GETSYSTEMTIME , 0 , ( LPARAM ) ( & systime ) ) ! = GDT_VALID )
2018-02-24 17:27:02 -06:00
logLastError ( L " error getting date and time " ) ;
2018-02-23 18:02:39 -06:00
fromSystemTime ( & systime , time ) ;
}
void uiDateTimePickerSetTime ( uiDateTimePicker * d , const struct tm * time )
{
SYSTEMTIME systime ;
toSystemTime ( time , & systime ) ;
2018-05-05 23:21:25 -05:00
if ( SendMessageW ( d - > hwnd , DTM_SETSYSTEMTIME , GDT_VALID , ( LPARAM ) ( & systime ) ) = = 0 )
2018-02-24 17:27:02 -06:00
logLastError ( L " error setting date and time " ) ;
2018-02-23 18:02:39 -06:00
}
2018-05-13 11:05:09 -05:00
void uiDateTimePickerOnChanged ( uiDateTimePicker * d , void ( * f ) ( uiDateTimePicker * , void * ) , void * data )
2018-02-23 18:02:39 -06:00
{
d - > onChanged = f ;
d - > onChangedData = data ;
}
2015-08-30 11:25:53 -05:00
static uiDateTimePicker * finishNewDateTimePicker ( DWORD style )
2015-05-22 09:36:24 -05:00
{
2015-08-30 11:25:53 -05:00
uiDateTimePicker * d ;
2015-05-22 09:36:24 -05:00
2016-04-29 12:50:08 -05:00
uiWindowsNewControl ( uiDateTimePicker , d ) ;
2015-05-22 09:36:24 -05:00
2015-08-31 11:33:44 -05:00
d - > hwnd = uiWindowsEnsureCreateControlHWND ( WS_EX_CLIENTEDGE ,
2015-05-29 17:03:24 -05:00
DATETIMEPICK_CLASSW , L " " ,
style | WS_TABSTOP ,
hInstance , NULL ,
TRUE ) ;
2015-05-22 09:36:24 -05:00
2015-06-04 09:47:24 -05:00
// automatically update date/time format when user changes locale settings
// for the standard styles, this is in the date-time picker itself
// for our date/time mode, we do it in a subclass assigned in uiNewDateTimePicker()
uiWindowsRegisterReceiveWM_WININICHANGE ( d - > hwnd ) ;
2018-02-23 18:02:39 -06:00
uiWindowsRegisterWM_NOTIFYHandler ( d - > hwnd , onWM_NOTIFY , uiControl ( d ) ) ;
uiDateTimePickerOnChanged ( d , defaultOnChanged , NULL ) ;
2015-06-04 09:47:24 -05:00
2015-08-30 11:25:53 -05:00
return d ;
2015-05-22 09:36:24 -05:00
}
2015-06-04 09:55:54 -05:00
static LRESULT CALLBACK datetimepickerSubProc ( HWND hwnd , UINT uMsg , WPARAM wParam , LPARAM lParam , UINT_PTR uIdSubclass , DWORD_PTR dwRefData )
{
switch ( uMsg ) {
case WM_WININICHANGE :
// we can optimize this by only doing it when the real date/time picker does it
// unfortunately, I don't know when that is :/
// hopefully this won't hurt
setDateTimeFormat ( hwnd ) ;
return 0 ;
case WM_NCDESTROY :
if ( RemoveWindowSubclass ( hwnd , datetimepickerSubProc , uIdSubclass ) = = FALSE )
2016-04-22 19:40:16 -05:00
logLastError ( L " error removing date-time picker locale change handling subclass " ) ;
2015-06-04 09:55:54 -05:00
break ;
}
return DefSubclassProc ( hwnd , uMsg , wParam , lParam ) ;
}
2015-05-22 09:36:24 -05:00
uiDateTimePicker * uiNewDateTimePicker ( void )
{
2015-08-30 11:25:53 -05:00
uiDateTimePicker * d ;
2015-05-22 12:24:07 -05:00
2015-08-30 11:25:53 -05:00
d = finishNewDateTimePicker ( 0 ) ;
2015-06-04 08:55:28 -05:00
setDateTimeFormat ( d - > hwnd ) ;
2015-06-04 09:55:54 -05:00
if ( SetWindowSubclass ( d - > hwnd , datetimepickerSubProc , 0 , ( DWORD_PTR ) d ) = = FALSE )
2016-04-22 19:40:16 -05:00
logLastError ( L " error subclassing date-time-picker to assist in locale change handling " ) ;
// TODO set a suitable default in this case
2015-08-30 11:25:53 -05:00
return d ;
2015-05-22 09:36:24 -05:00
}
uiDateTimePicker * uiNewDatePicker ( void )
{
2015-06-04 08:55:28 -05:00
return finishNewDateTimePicker ( DTS_SHORTDATECENTURYFORMAT ) ;
2015-05-22 09:36:24 -05:00
}
uiDateTimePicker * uiNewTimePicker ( void )
{
2015-06-04 08:55:28 -05:00
return finishNewDateTimePicker ( DTS_TIMEFORMAT ) ;
2015-05-22 09:36:24 -05:00
}