Datetime picker updown support

Duane Clark dclark at akamail.com
Wed Apr 13 17:28:06 CDT 2005


This adds support for the updown control in the datetime picker 
(previously it existed but did nothing). Also, several bug fixes, 
including hopefully the ability to post to wine-patches ;) This is 
mostly done in response to:
http://bugs.winehq.org/show_bug.cgi?id=2857

Some of the calculations of the day of week in monthcal and datetime
were done assuming that Monday is '0'. The Microsoft docs and for that
matter Wine's define of EPOCHWEEKDAY both define Monday as '1'. 
Hopefully I found and fixed all the places where this is used.

In Windows, the various fields in the datetime control are fixed widths,
depending on the widest possible value. Wine was readjusting the fields
depending on the contents. I have set fixed widths here, but they are
set assuming english names. Presumably Windows does not do that. I would 
welcome suggestions of a good approach to this.

The response to the updown control may seem a bit cumbersome. When using
the updown buttons, the position reported by WM_VSCROLL can hit max or
min, but the date/time still continues to increase or decrease normally.
So here, I check and save the notification sent immediately prior to the
WM_VSCROLL message, and use that to determine which way to move once the
scroll message arrives. Would it perhaps be better to simply change the 
field in response to the notification, rather than waiting for the 
scroll message?

Changelog:
	Monday is day number '1'.
	Set day of week when a day is selected in the calender.
	Use fixed width fields in datetime.
	DTS_TIMEFORMAT is a two bit field, so test accordingly.
	Reposition and resize the updown control when the datetime
	control is resized.
	Respond to updown inputs.



-------------- next part --------------
Index: dlls/comctl32/datetime.c
===================================================================
RCS file: /home/wine/wine/dlls/comctl32/datetime.c,v
retrieving revision 1.54
diff -u -p -r1.54 datetime.c
--- dlls/comctl32/datetime.c	9 Jan 2005 16:42:54 -0000	1.54
+++ dlls/comctl32/datetime.c	13 Apr 2005 21:47:25 -0000
@@ -82,6 +82,7 @@ typedef struct
     int  *buflen;
     WCHAR textbuf[256];
     POINT monthcal_pos;
+    int pendingUpdown;
 } DATETIME_INFO, *LPDATETIME_INFO;
 
 /* in monthcal.c */
@@ -256,7 +257,7 @@ DATETIME_SetFormatW (DATETIME_INFO *info
 
 	if (infoPtr->dwStyle & DTS_LONGDATEFORMAT)
 	    format_item = LOCALE_SLONGDATE;
-	else if (infoPtr->dwStyle & DTS_TIMEFORMAT)
+	else if ((infoPtr->dwStyle & DTS_TIMEFORMAT) == DTS_TIMEFORMAT)
 	    format_item = LOCALE_STIMEFORMAT;
         else /* DTS_SHORTDATEFORMAT */
 	    format_item = LOCALE_SSHORTDATE;
@@ -405,6 +406,19 @@ DATETIME_ReturnTxt (DATETIME_INFO *infoP
     TRACE ("arg%d=%x->[%s]\n", count, infoPtr->fieldspec[count], debugstr_w(result));
 }
 
+/* Offsets of days in the week to the weekday of january 1 in a leap year. */
+static const int DayOfWeekTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
+
+/* returns the day in the week(0 == sunday, 6 == saturday) */
+/* day(1 == 1st, 2 == 2nd... etc), year is the  year value */
+static int DATETIME_CalculateDayOfWeek(DWORD day, DWORD month, DWORD year)
+{
+    year-=(month < 3);
+    
+    return((year + year/4 - year/100 + year/400 +
+         DayOfWeekTable[month-1] + day ) % 7);
+}
+
 static int wrap(int val, int delta, int minVal, int maxVal)
 {
     val += delta;
@@ -428,12 +442,14 @@ DATETIME_IncreaseField (DATETIME_INFO *i
 	case TWODIGITYEAR:
 	case FULLYEAR:
 	    date->wYear = wrap(date->wYear, delta, 1752, 9999);
+	    date->wDayOfWeek = DATETIME_CalculateDayOfWeek(date->wDay,date->wMonth,date->wYear);
 	    break;
 	case ONEDIGITMONTH:
 	case TWODIGITMONTH:
 	case THREECHARMONTH:
 	case FULLMONTH:
 	    date->wMonth = wrap(date->wMonth, delta, 1, 12);
+	    date->wDayOfWeek = DATETIME_CalculateDayOfWeek(date->wDay,date->wMonth,date->wYear);
 	    delta = 0;
 	    /* fall through */
 	case ONEDIGITDAY:
@@ -441,6 +457,7 @@ DATETIME_IncreaseField (DATETIME_INFO *i
 	case THREECHARDAY:
 	case FULLDAY:
 	    date->wDay = wrap(date->wDay, delta, 1, MONTHCAL_MonthLength(date->wMonth, date->wYear));
+	    date->wDayOfWeek = DATETIME_CalculateDayOfWeek(date->wDay,date->wMonth,date->wYear);
 	    break;
 	case ONELETTERAMPM:
 	case TWOLETTERAMPM:
@@ -481,6 +498,92 @@ DATETIME_IncreaseField (DATETIME_INFO *i
 }
 
 
+static void
+DATETIME_ReturnFieldWidth (DATETIME_INFO *infoPtr, HDC hdc, int count, SHORT *fieldWidthPtr)
+{
+    /* fields are a fixed width, determined by the largest possible string */
+    /* presumably, these widths should be language dependent */
+    static const WCHAR fld_d1W[] = { '2', 0 };
+    static const WCHAR fld_d2W[] = { '2', '2', 0 };
+    static const WCHAR fld_d4W[] = { '2', '2', '2', '2', 0 };
+    static const WCHAR fld_am1[] = { 'A', 0 };
+    static const WCHAR fld_am2[] = { 'A', 'M', 0 };
+    static const WCHAR fld_day[] = { 'W', 'e', 'd', 'n', 'e', 's', 'd', 'a', 'y', 0 };
+    static const WCHAR fld_day3[] = { 'W', 'e', 'd', 0 };
+    static const WCHAR fld_mon[] = { 'S', 'e', 'p', 't', 'e', 'm', 'b', 'e', 'r', 0 };
+    static const WCHAR fld_mon3[] = { 'D', 'e', 'c', 0 };
+    int spec;
+    WCHAR buffer[80], *bufptr;
+    SIZE size;
+
+    TRACE ("%d,%d\n", infoPtr->nrFields, count);
+    if (count>infoPtr->nrFields || count < 0) {
+	WARN ("buffer overrun, have %d want %d\n", infoPtr->nrFields, count);
+	return;
+    }
+
+    if (!infoPtr->fieldspec) return;
+
+    spec = infoPtr->fieldspec[count];
+    if (spec & DT_STRING) {
+	int txtlen = infoPtr->buflen[count];
+
+        if (txtlen > 79)
+            txtlen = 79;
+	memcpy (buffer, infoPtr->textbuf + (spec &~ DT_STRING), txtlen * sizeof(WCHAR));
+	buffer[txtlen] = 0;
+	bufptr = buffer;
+    }
+    else {
+        switch (spec) {
+	    case ONEDIGITDAY:
+	    case ONEDIGIT12HOUR:
+	    case ONEDIGIT24HOUR:
+	    case ONEDIGITSECOND:
+	    case ONEDIGITMINUTE:
+	    case ONEDIGITMONTH:
+	    case ONEDIGITYEAR:
+	        /* these seem to use a two byte field */
+	    case TWODIGITDAY:
+	    case TWODIGIT12HOUR:
+	    case TWODIGIT24HOUR:
+	    case TWODIGITSECOND:
+	    case TWODIGITMINUTE:
+	    case TWODIGITMONTH:
+	    case TWODIGITYEAR:
+	        bufptr = (WCHAR *)fld_d2W;
+	        break;
+            case INVALIDFULLYEAR:
+	    case FULLYEAR:
+	        bufptr = (WCHAR *)fld_d4W;
+	        break;
+	    case THREECHARDAY:
+	        bufptr = (WCHAR *)fld_day3;
+	        break;
+	    case FULLDAY:
+	        bufptr = (WCHAR *)fld_day;
+	        break;
+	    case THREECHARMONTH:
+	        bufptr = (WCHAR *)fld_mon3;
+	        break;
+	    case FULLMONTH:
+	        bufptr = (WCHAR *)fld_mon;
+	        break;
+	    case ONELETTERAMPM:
+	        bufptr = (WCHAR *)fld_am1;
+	        break;
+	    case TWOLETTERAMPM:
+	        bufptr = (WCHAR *)fld_am2;
+	        break;
+	    default:
+	        bufptr = (WCHAR *)fld_d1W;
+	        break;
+        }
+    }
+    GetTextExtentPoint32W (hdc, bufptr, strlenW(bufptr), &size);
+    *fieldWidthPtr = size.cx;
+}
+
 static void 
 DATETIME_Refresh (DATETIME_INFO *infoPtr, HDC hdc)
 {
@@ -491,6 +594,7 @@ DATETIME_Refresh (DATETIME_INFO *infoPtr
     RECT *checkbox = &infoPtr->checkbox;
     SIZE size;
     COLORREF oldTextColor;
+    SHORT fieldWidth;
 
     /* draw control edge */
     TRACE("\n");
@@ -509,9 +613,10 @@ DATETIME_Refresh (DATETIME_INFO *infoPtr
         for (i = 0; i < infoPtr->nrFields; i++) {
             DATETIME_ReturnTxt (infoPtr, i, txt, sizeof(txt)/sizeof(txt[0]));
             GetTextExtentPoint32W (hdc, txt, strlenW(txt), &size);
+            DATETIME_ReturnFieldWidth (infoPtr, hdc, i, &fieldWidth);
             field = &infoPtr->fieldRect[i];
             field->left  = prevright;
-            field->right = prevright+size.cx;
+            field->right = prevright + fieldWidth;
             field->top   = rcDraw->top;
             field->bottom = rcDraw->bottom;
             prevright = field->right;
@@ -720,12 +825,17 @@ DATETIME_Notify (DATETIME_INFO *infoPtr,
         ShowWindow(infoPtr->hMonthCal, SW_HIDE);
         infoPtr->dateValid = TRUE;
         SendMessageW (infoPtr->hMonthCal, MCM_GETCURSEL, 0, (LPARAM)&infoPtr->date);
-        TRACE("got from calendar %04d/%02d/%02d\n", 
-        infoPtr->date.wYear, infoPtr->date.wMonth, infoPtr->date.wDay);
+        TRACE("got from calendar %04d/%02d/%02d day of week %d\n", 
+        infoPtr->date.wYear, infoPtr->date.wMonth, infoPtr->date.wDay, infoPtr->date.wDayOfWeek);
         SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_CHECKED, 0);
         InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
         DATETIME_SendDateTimeChangeNotify (infoPtr);
     }
+    if ((lpnmh->hwndFrom == infoPtr->hUpdown) && (lpnmh->code == UDN_DELTAPOS)) {
+        LPNMUPDOWN lpnmud = (LPNMUPDOWN)lpnmh;
+        TRACE("Delta pos %d\n", lpnmud->iDelta);
+        infoPtr->pendingUpdown = lpnmud->iDelta;
+    }
     return 0;
 }
 
@@ -790,6 +900,30 @@ DATETIME_KeyDown (DATETIME_INFO *infoPtr
 
 
 static LRESULT
+DATETIME_VScroll (DATETIME_INFO *infoPtr, WORD wScroll)
+{
+    int fieldNum = infoPtr->select & DTHT_DATEFIELD;
+
+    if ((SHORT)LOWORD(wScroll) != SB_THUMBPOSITION) return 0;
+    if (!(infoPtr->haveFocus)) return 0;
+    if ((fieldNum==0) && (infoPtr->select)) return 0;
+
+    if (infoPtr->pendingUpdown >= 0) {
+	DATETIME_IncreaseField (infoPtr, fieldNum, 1);
+	DATETIME_SendDateTimeChangeNotify (infoPtr);
+    }
+    else {
+	DATETIME_IncreaseField (infoPtr, fieldNum, -1);
+	DATETIME_SendDateTimeChangeNotify (infoPtr);
+    }
+
+    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
+
+    return 0;
+}
+
+
+static LRESULT
 DATETIME_KillFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
 {
     TRACE("lost focus to %p\n", lostFocus);
@@ -874,13 +1008,23 @@ DATETIME_Size (DATETIME_INFO *infoPtr, W
     TRACE("Height=%ld, Width=%ld\n", infoPtr->rcClient.bottom, infoPtr->rcClient.right);
 
     infoPtr->rcDraw = infoPtr->rcClient;
-
-    /* set the size of the button that drops the calendar down */
-    /* FIXME: account for style that allows button on left side */
-    infoPtr->calbutton.top   = infoPtr->rcDraw.top;
-    infoPtr->calbutton.bottom= infoPtr->rcDraw.bottom;
-    infoPtr->calbutton.left  = infoPtr->rcDraw.right-15;
-    infoPtr->calbutton.right = infoPtr->rcDraw.right;
+    
+    if (infoPtr->dwStyle & DTS_UPDOWN) {
+        /* the updown control seems to ignore SetWindowPos messages */
+	DestroyWindow(infoPtr->hUpdown);
+	/* hmmm... the updown control seems to ignore the width parameter */
+	infoPtr->hUpdown = CreateUpDownControl (WS_CHILD | WS_BORDER | WS_VISIBLE,
+						infoPtr->rcClient.right-14, 0, 20, infoPtr->rcClient.bottom, 
+						infoPtr->hwndSelf, 1, 0, 0, UD_MAXVAL, UD_MINVAL, 0);
+    }
+    else {
+        /* set the size of the button that drops the calendar down */
+        /* FIXME: account for style that allows button on left side */
+        infoPtr->calbutton.top   = infoPtr->rcDraw.top;
+        infoPtr->calbutton.bottom= infoPtr->rcDraw.bottom;
+        infoPtr->calbutton.left  = infoPtr->rcDraw.right-15;
+        infoPtr->calbutton.right = infoPtr->rcDraw.right;
+    }
 
     /* set enable/disable button size for show none style being enabled */
     /* FIXME: these dimensions are completely incorrect */
@@ -1066,6 +1210,9 @@ DATETIME_WindowProc (HWND hwnd, UINT uMs
 
     case WM_LBUTTONUP:
         return DATETIME_LButtonUp (infoPtr, (WORD)wParam);
+
+    case WM_VSCROLL:
+        return DATETIME_VScroll (infoPtr, (WORD)wParam);
 
     case WM_CREATE:
 	return DATETIME_Create (hwnd, (LPCREATESTRUCTW)lParam);
Index: dlls/comctl32/monthcal.c
===================================================================
RCS file: /home/wine/wine/dlls/comctl32/monthcal.c,v
retrieving revision 1.55
diff -u -p -r1.55 monthcal.c
--- dlls/comctl32/monthcal.c	11 Apr 2005 14:21:00 -0000	1.55
+++ dlls/comctl32/monthcal.c	13 Apr 2005 21:47:26 -0000
@@ -122,7 +122,7 @@ typedef struct
 } MONTHCAL_INFO, *LPMONTHCAL_INFO;
 
 
-/* Offsets of days in the week to the weekday of  january 1. */
+/* Offsets of days in the week to the weekday of january 1 in a leap year */
 static const int DayOfWeekTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
 
 
@@ -187,17 +187,17 @@ void MONTHCAL_CopyTime(const SYSTEMTIME 
    Need to find out if we're on a DST place & adjust the clock accordingly.
    Above function assumes we have a valid data.
    Valid for year>1752;  1 <= d <= 31, 1 <= m <= 12.
-   0 = Monday.
+   0 = Sunday.
 */
 
-/* returns the day in the week(0 == monday, 6 == sunday) */
+/* returns the day in the week(0 == sunday, 6 == saturday) */
 /* day(1 == 1st, 2 == 2nd... etc), year is the  year value */
 static int MONTHCAL_CalculateDayOfWeek(DWORD day, DWORD month, DWORD year)
 {
   year-=(month < 3);
 
   return((year + year/4 - year/100 + year/400 +
-         DayOfWeekTable[month-1] + day - 1 ) % 7);
+         DayOfWeekTable[month-1] + day ) % 7);
 }
 
 /* From a given point, calculate the row (weekpos), column(daypos)
@@ -537,7 +537,7 @@ static void MONTHCAL_Refresh(MONTHCAL_IN
   i = infoPtr->firstDay;
 
   for(j=0; j<7; j++) {
-    GetLocaleInfoW( LOCALE_USER_DEFAULT,LOCALE_SABBREVDAYNAME1 + (i +j)%7, buf, countof(buf));
+    GetLocaleInfoW( LOCALE_USER_DEFAULT,LOCALE_SABBREVDAYNAME1 + (i+j+6)%7, buf, countof(buf));
     DrawTextW(hdc, buf, strlenW(buf), days, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
     days->left+=infoPtr->width_increment;
     days->right+=infoPtr->width_increment;
@@ -886,7 +886,7 @@ MONTHCAL_GetFirstDayOfWeek(MONTHCAL_INFO
 
 
 /* sets the first day of the week that will appear in the control */
-/* 0 == Monday, 6 == Sunday */
+/* 0 == Sunday, 6 == Saturday */
 /* FIXME: this needs to be implemented properly in MONTHCAL_Refresh() */
 /* FIXME: we need more error checking here */
 static LRESULT
@@ -904,7 +904,7 @@ MONTHCAL_SetFirstDayOfWeek(MONTHCAL_INFO
     {
       GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, buf, countof(buf));
       TRACE("%s %d\n", debugstr_w(buf), strlenW(buf));
-      infoPtr->firstDay = atoiW(buf);
+      infoPtr->firstDay = (atoiW(buf)+1)%7;
     }
   return prev;
 }
@@ -1238,6 +1238,7 @@ MONTHCAL_HitTest(MONTHCAL_INFO *infoPtr,
 	retval = MCHT_CALENDARDATE;
 	lpht->st.wMonth = infoPtr->currentMonth;
 	lpht->st.wDay   = day;
+	lpht->st.wDayOfWeek   = MONTHCAL_CalculateDayOfWeek(day,lpht->st.wMonth,lpht->st.wYear);
       }
       goto done;
     }


More information about the wine-patches mailing list