184 lines
5.5 KiB
C
184 lines
5.5 KiB
C
// 22 may 2015
|
|
#include "uipriv_windows.h"
|
|
|
|
struct uiDateTimePicker {
|
|
uiWindowsControl c;
|
|
HWND hwnd;
|
|
};
|
|
|
|
uiWindowsDefineControlWithOnDestroy(
|
|
uiDateTimePicker, // type name
|
|
uiDateTimePickerType, // type function
|
|
uiWindowsUnregisterReceiveWM_WININICHANGE(this->hwnd); // on destroy
|
|
)
|
|
|
|
// utility functions
|
|
|
|
#define GLI(what, buf, n) GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, what, buf, n)
|
|
|
|
// 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
|
|
out = (WCHAR *) uiAlloc((n * 3) * sizeof (WCHAR), "WCHAR[]");
|
|
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')
|
|
complain("unterminated quote in system-provided locale date string in expandYear()");
|
|
*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;
|
|
}
|
|
|
|
// Windows has no combined date/time prebuilt constant; we have to build the format string ourselves
|
|
static void setDateTimeFormat(HWND hwnd)
|
|
{
|
|
WCHAR *unexpandedDate, *date;
|
|
WCHAR *time;
|
|
WCHAR *datetime;
|
|
int ndate, ntime;
|
|
int n;
|
|
|
|
ndate = GLI(LOCALE_SSHORTDATE, NULL, 0);
|
|
if (ndate == 0)
|
|
logLastError("error getting date string length in setDateTimeFormat()");
|
|
date = (WCHAR *) uiAlloc(ndate * sizeof (WCHAR), "WCHAR[]");
|
|
if (GLI(LOCALE_SSHORTDATE, date, ndate) == 0)
|
|
logLastError("error geting date string in setDateTimeFormat()");
|
|
unexpandedDate = date; // so we can free it
|
|
date = expandYear(unexpandedDate, ndate);
|
|
uiFree(unexpandedDate);
|
|
|
|
ntime = GLI(LOCALE_STIMEFORMAT, NULL, 0);
|
|
if (ndate == 0)
|
|
logLastError("error getting time string length in setDateTimeFormat()");
|
|
time = (WCHAR *) uiAlloc(ntime * sizeof (WCHAR), "WCHAR[]");
|
|
if (GLI(LOCALE_STIMEFORMAT, time, ntime) == 0)
|
|
logLastError("error geting time string in setDateTimeFormat()");
|
|
|
|
n = _scwprintf(L"%s %s", date, time);
|
|
datetime = (WCHAR *) uiAlloc((n + 1) * sizeof (WCHAR), "WCHAR[]");
|
|
_snwprintf(datetime, n + 1, L"%s %s", date, time);
|
|
if (SendMessageW(hwnd, DTM_SETFORMAT, 0, (LPARAM) datetime) == 0)
|
|
logLastError("error applying format string to date/time picker in setDateTimeFormat()");
|
|
|
|
uiFree(datetime);
|
|
uiFree(time);
|
|
uiFree(date);
|
|
}
|
|
|
|
// control implementation
|
|
|
|
// 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
|
|
// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing
|
|
#define entryHeight 14
|
|
|
|
static void minimumSize(uiWindowsControl *c, uiWindowsSizing *dd, intmax_t *width, intmax_t *height)
|
|
{
|
|
uiDateTimePicker *d = uiDateTimePicker(c);
|
|
SIZE s;
|
|
|
|
s.cx = 0;
|
|
s.cy = 0;
|
|
SendMessageW(d->hwnd, DTM_GETIDEALSIZE, 0, (LPARAM) (&s));
|
|
*width = s.cx;
|
|
*height = uiWindowsDlgUnitsToY(entryHeight, dd->BaseY);
|
|
}
|
|
|
|
static uiDateTimePicker *finishNewDateTimePicker(DWORD style)
|
|
{
|
|
uiDateTimePicker *d;
|
|
|
|
d = (uiDateTimePicker *) uiNewControl(uiDateTimePickerType());
|
|
|
|
d->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE,
|
|
DATETIMEPICK_CLASSW, L"",
|
|
style | WS_TABSTOP,
|
|
hInstance, NULL,
|
|
TRUE);
|
|
|
|
// 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);
|
|
|
|
uiWindowsFinishNewControl(d, uiDateTimePicker);
|
|
|
|
return d;
|
|
}
|
|
|
|
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)
|
|
logLastError("error removing date-time picker locale change handling subclass in datetimepickerSubProc()");
|
|
break;
|
|
}
|
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
uiDateTimePicker *uiNewDateTimePicker(void)
|
|
{
|
|
uiDateTimePicker *d;
|
|
|
|
d = finishNewDateTimePicker(0);
|
|
setDateTimeFormat(d->hwnd);
|
|
if (SetWindowSubclass(d->hwnd, datetimepickerSubProc, 0, (DWORD_PTR) d) == FALSE)
|
|
logLastError("error subclassing date-time-picker to assist in locale change handling in uiNewDateTimePicker()");
|
|
return d;
|
|
}
|
|
|
|
uiDateTimePicker *uiNewDatePicker(void)
|
|
{
|
|
return finishNewDateTimePicker(DTS_SHORTDATECENTURYFORMAT);
|
|
}
|
|
|
|
uiDateTimePicker *uiNewTimePicker(void)
|
|
{
|
|
return finishNewDateTimePicker(DTS_TIMEFORMAT);
|
|
}
|