First Commit
This commit is contained in:
commit
ae0fab9400
424 files changed
+71006
No files matched your search
@@ -0,0 +1,261 @@
|
||||
// 22 may 2015
|
||||
#include "uipriv_windows.hpp"
|
||||
|
||||
struct uiDateTimePicker {
|
||||
uiWindowsControl c;
|
||||
HWND hwnd;
|
||||
void (*onChanged)(uiDateTimePicker *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
// 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 *) uiprivAlloc((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')
|
||||
uiprivImplBug("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
|
||||
// TODO use a default format if one fails
|
||||
static void setDateTimeFormat(HWND hwnd)
|
||||
{
|
||||
WCHAR *unexpandedDate, *date;
|
||||
WCHAR *time;
|
||||
WCHAR *datetime;
|
||||
int ndate, ntime;
|
||||
|
||||
ndate = GLI(LOCALE_SSHORTDATE, NULL, 0);
|
||||
if (ndate == 0)
|
||||
logLastError(L"error getting date string length");
|
||||
date = (WCHAR *) uiprivAlloc(ndate * sizeof (WCHAR), "WCHAR[]");
|
||||
if (GLI(LOCALE_SSHORTDATE, date, ndate) == 0)
|
||||
logLastError(L"error geting date string");
|
||||
unexpandedDate = date; // so we can free it
|
||||
date = expandYear(unexpandedDate, ndate);
|
||||
uiprivFree(unexpandedDate);
|
||||
|
||||
ntime = GLI(LOCALE_STIMEFORMAT, NULL, 0);
|
||||
if (ndate == 0)
|
||||
logLastError(L"error getting time string length");
|
||||
time = (WCHAR *) uiprivAlloc(ntime * sizeof (WCHAR), "WCHAR[]");
|
||||
if (GLI(LOCALE_STIMEFORMAT, time, ntime) == 0)
|
||||
logLastError(L"error geting time string");
|
||||
|
||||
datetime = strf(L"%s %s", date, time);
|
||||
if (SendMessageW(hwnd, DTM_SETFORMAT, 0, (LPARAM) datetime) == 0)
|
||||
logLastError(L"error applying format string to date/time picker");
|
||||
|
||||
uiprivFree(datetime);
|
||||
uiprivFree(time);
|
||||
uiprivFree(date);
|
||||
}
|
||||
|
||||
// control implementation
|
||||
|
||||
static void uiDateTimePickerDestroy(uiControl *c)
|
||||
{
|
||||
uiDateTimePicker *d = uiDateTimePicker(c);
|
||||
|
||||
uiWindowsUnregisterReceiveWM_WININICHANGE(d->hwnd);
|
||||
uiWindowsUnregisterWM_NOTIFYHandler(d->hwnd);
|
||||
uiWindowsEnsureDestroyWindow(d->hwnd);
|
||||
uiFreeControl(uiControl(d));
|
||||
}
|
||||
|
||||
uiWindowsControlAllDefaultsExceptDestroy(uiDateTimePicker)
|
||||
|
||||
// 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 uiDateTimePickerMinimumSize(uiWindowsControl *c, int *width, int *height)
|
||||
{
|
||||
uiDateTimePicker *d = uiDateTimePicker(c);
|
||||
SIZE s;
|
||||
uiWindowsSizing sizing;
|
||||
int y;
|
||||
|
||||
s.cx = 0;
|
||||
s.cy = 0;
|
||||
SendMessageW(d->hwnd, DTM_GETIDEALSIZE, 0, (LPARAM) (&s));
|
||||
*width = s.cx;
|
||||
|
||||
y = entryHeight;
|
||||
uiWindowsGetSizing(d->hwnd, &sizing);
|
||||
uiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &y);
|
||||
*height = y;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static void fromSystemTime(SYSTEMTIME *systime, struct tm *time)
|
||||
{
|
||||
ZeroMemory(time, sizeof (struct tm));
|
||||
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;
|
||||
}
|
||||
|
||||
static void toSystemTime(const struct tm *time, SYSTEMTIME *systime)
|
||||
{
|
||||
ZeroMemory(systime, sizeof (SYSTEMTIME));
|
||||
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;
|
||||
|
||||
if (SendMessageW(d->hwnd, DTM_GETSYSTEMTIME, 0, (LPARAM) (&systime)) != GDT_VALID)
|
||||
logLastError(L"error getting date and time");
|
||||
fromSystemTime(&systime, time);
|
||||
}
|
||||
|
||||
void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time)
|
||||
{
|
||||
SYSTEMTIME systime;
|
||||
|
||||
toSystemTime(time, &systime);
|
||||
if (SendMessageW(d->hwnd, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM) (&systime)) == 0)
|
||||
logLastError(L"error setting date and time");
|
||||
}
|
||||
|
||||
void uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data)
|
||||
{
|
||||
d->onChanged = f;
|
||||
d->onChangedData = data;
|
||||
}
|
||||
|
||||
static uiDateTimePicker *finishNewDateTimePicker(DWORD style)
|
||||
{
|
||||
uiDateTimePicker *d;
|
||||
|
||||
uiWindowsNewControl(uiDateTimePicker, d);
|
||||
|
||||
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);
|
||||
uiWindowsRegisterWM_NOTIFYHandler(d->hwnd, onWM_NOTIFY, uiControl(d));
|
||||
uiDateTimePickerOnChanged(d, defaultOnChanged, NULL);
|
||||
|
||||
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(L"error removing date-time picker locale change handling subclass");
|
||||
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(L"error subclassing date-time-picker to assist in locale change handling");
|
||||
// TODO set a suitable default in this case
|
||||
return d;
|
||||
}
|
||||
|
||||
uiDateTimePicker *uiNewDatePicker(void)
|
||||
{
|
||||
return finishNewDateTimePicker(DTS_SHORTDATECENTURYFORMAT);
|
||||
}
|
||||
|
||||
uiDateTimePicker *uiNewTimePicker(void)
|
||||
{
|
||||
return finishNewDateTimePicker(DTS_TIMEFORMAT);
|
||||
}
|
||||
Reference in new issue
Block a user