xref: /reactos/dll/win32/comctl32/monthcal.c (revision 845faec4)
1 /*
2  * Month calendar control
3  *
4  * Copyright 1998, 1999 Eric Kohl (ekohl@abo.rhein-zeitung.de)
5  * Copyright 1999 Alex Priem (alexp@sci.kun.nl)
6  * Copyright 1999 Chris Morgan <cmorgan@wpi.edu> and
7  *		  James Abbatiello <abbeyj@wpi.edu>
8  * Copyright 2000 Uwe Bonnes <bon@elektron.ikp.physik.tu-darmstadt.de>
9  * Copyright 2009-2011 Nikolay Sivov
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this library; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24  *
25  * NOTE
26  *
27  * This code was audited for completeness against the documented features
28  * of Comctl32.dll version 6.0 on Oct. 20, 2004, by Dimitrie O. Paun.
29  *
30  * Unless otherwise noted, we believe this code to be complete, as per
31  * the specification mentioned above.
32  * If you discover missing features, or bugs, please note them below.
33  *
34  * TODO:
35  *    -- MCM_[GS]ETUNICODEFORMAT
36  *    -- handle resources better (doesn't work now);
37  *    -- take care of internationalization.
38  *    -- keyboard handling.
39  *    -- search for FIXME
40  */
41 
42 #include "comctl32.h"
43 
44 WINE_DEFAULT_DEBUG_CHANNEL(monthcal);
45 
46 /* FIXME: Inspect */
47 #define MCS_NOSELCHANGEONNAV 0x0100
48 
49 #define MC_SEL_LBUTUP	    1	/* Left button released */
50 #define MC_SEL_LBUTDOWN	    2	/* Left button pressed in calendar */
51 #define MC_PREVPRESSED      4   /* Prev month button pressed */
52 #define MC_NEXTPRESSED      8   /* Next month button pressed */
53 #define MC_PREVNEXTMONTHDELAY   350	/* when continuously pressing `next/prev
54 					   month', wait 350 ms before going
55 					   to the next/prev month */
56 #define MC_TODAYUPDATEDELAY 120000 /* time between today check for update (2 min) */
57 
58 #define MC_PREVNEXTMONTHTIMER   1	/* Timer IDs */
59 #define MC_TODAYUPDATETIMER     2
60 
61 #define MC_CALENDAR_PADDING     6
62 
63 #define countof(arr) (sizeof(arr)/sizeof(arr[0]))
64 
65 /* convert from days to 100 nanoseconds unit - used as FILETIME unit */
66 #define DAYSTO100NSECS(days) (((ULONGLONG)(days))*24*60*60*10000000)
67 
68 enum CachedPen
69 {
70     PenRed = 0,
71     PenText,
72     PenLast
73 };
74 
75 enum CachedBrush
76 {
77     BrushTitle = 0,
78     BrushMonth,
79     BrushBackground,
80     BrushLast
81 };
82 
83 /* single calendar data */
84 typedef struct _CALENDAR_INFO
85 {
86     RECT title;      /* rect for the header above the calendar */
87     RECT titlemonth; /* the 'month name' text in the header */
88     RECT titleyear;  /* the 'year number' text in the header */
89     RECT wdays;      /* week days at top */
90     RECT days;       /* calendar area */
91     RECT weeknums;   /* week numbers at left side */
92 
93     SYSTEMTIME month;/* contains calendar main month/year */
94 } CALENDAR_INFO;
95 
96 typedef struct
97 {
98     HWND	hwndSelf;
99     DWORD	dwStyle; /* cached GWL_STYLE */
100 
101     COLORREF    colors[MCSC_TRAILINGTEXT+1];
102     HBRUSH      brushes[BrushLast];
103     HPEN        pens[PenLast];
104 
105     HFONT	hFont;
106     HFONT	hBoldFont;
107     int		textHeight;
108     int		height_increment;
109     int		width_increment;
110     INT		delta;	/* scroll rate; # of months that the */
111                         /* control moves when user clicks a scroll button */
112     int		firstDay;	/* Start month calendar with firstDay's day,
113 				   stored in SYSTEMTIME format */
114     BOOL	firstDaySet;    /* first week day differs from locale defined */
115 
116     BOOL	isUnicode;      /* value set with MCM_SETUNICODE format */
117 
118     MONTHDAYSTATE *monthdayState;
119     SYSTEMTIME	todaysDate;
120     BOOL	todaySet;       /* Today was forced with MCM_SETTODAY */
121     int		status;		/* See MC_SEL flags */
122     SYSTEMTIME	firstSel;	/* first selected day */
123     INT		maxSelCount;
124     SYSTEMTIME	minSel;         /* contains single selection when used without MCS_MULTISELECT */
125     SYSTEMTIME	maxSel;
126     SYSTEMTIME  focusedSel;     /* date currently focused with mouse movement */
127     DWORD	rangeValid;
128     SYSTEMTIME	minDate;
129     SYSTEMTIME	maxDate;
130 
131     RECT titlebtnnext;	/* the `next month' button in the header */
132     RECT titlebtnprev;  /* the `prev month' button in the header */
133     RECT todayrect;	/* `today: xx/xx/xx' text rect */
134     HWND hwndNotify;    /* Window to receive the notifications */
135     HWND hWndYearEdit;  /* Window Handle of edit box to handle years */
136     HWND hWndYearUpDown;/* Window Handle of updown box to handle years */
137     WNDPROC EditWndProc;  /* original Edit window procedure */
138 
139     CALENDAR_INFO *calendars;
140     SIZE dim;           /* [cx,cy] - dimensions of calendars matrix, row/column count */
141 } MONTHCAL_INFO, *LPMONTHCAL_INFO;
142 
143 static const WCHAR themeClass[] = { 'S','c','r','o','l','l','b','a','r',0 };
144 
145 /* empty SYSTEMTIME const */
146 static const SYSTEMTIME st_null;
147 /* valid date limits */
148 static const SYSTEMTIME max_allowed_date = { /* wYear */ 9999, /* wMonth */ 12, /* wDayOfWeek */ 0, /* wDay */ 31 };
149 static const SYSTEMTIME min_allowed_date = { /* wYear */ 1752, /* wMonth */ 9,  /* wDayOfWeek */ 0, /* wDay */ 14 };
150 
151 /* Prev/Next buttons */
152 enum nav_direction
153 {
154     DIRECTION_BACKWARD,
155     DIRECTION_FORWARD
156 };
157 
158 /* helper functions  */
159 static inline INT MONTHCAL_GetCalCount(const MONTHCAL_INFO *infoPtr)
160 {
161    return infoPtr->dim.cx * infoPtr->dim.cy;
162 }
163 
164 /* send a single MCN_SELCHANGE notification */
165 static inline void MONTHCAL_NotifySelectionChange(const MONTHCAL_INFO *infoPtr)
166 {
167     NMSELCHANGE nmsc;
168 
169     nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
170     nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
171     nmsc.nmhdr.code     = MCN_SELCHANGE;
172     nmsc.stSelStart     = infoPtr->minSel;
173     nmsc.stSelStart.wDayOfWeek = 0;
174     if(infoPtr->dwStyle & MCS_MULTISELECT){
175         nmsc.stSelEnd = infoPtr->maxSel;
176         nmsc.stSelEnd.wDayOfWeek = 0;
177     }
178     else
179         nmsc.stSelEnd = st_null;
180 
181     SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
182 }
183 
184 /* send a single MCN_SELECT notification */
185 static inline void MONTHCAL_NotifySelect(const MONTHCAL_INFO *infoPtr)
186 {
187     NMSELCHANGE nmsc;
188 
189     nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
190     nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
191     nmsc.nmhdr.code     = MCN_SELECT;
192     nmsc.stSelStart     = infoPtr->minSel;
193     nmsc.stSelStart.wDayOfWeek = 0;
194     if(infoPtr->dwStyle & MCS_MULTISELECT){
195         nmsc.stSelEnd = infoPtr->maxSel;
196         nmsc.stSelEnd.wDayOfWeek = 0;
197     }
198     else
199         nmsc.stSelEnd = st_null;
200 
201     SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
202 }
203 
204 static inline int MONTHCAL_MonthDiff(const SYSTEMTIME *left, const SYSTEMTIME *right)
205 {
206     return (right->wYear - left->wYear)*12 + right->wMonth - left->wMonth;
207 }
208 
209 /* returns the number of days in any given month, checking for leap days */
210 /* January is 1, December is 12 */
211 int MONTHCAL_MonthLength(int month, int year)
212 {
213   const int mdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
214   /* Wrap around, this eases handling. Getting length only we shouldn't care
215      about year change here cause January and December have
216      the same day quantity */
217   if(month == 0)
218     month = 12;
219   else if(month == 13)
220     month = 1;
221 
222   /* special case for calendar transition year */
223   if(month == min_allowed_date.wMonth && year == min_allowed_date.wYear) return 19;
224 
225   /* if we have a leap year add 1 day to February */
226   /* a leap year is a year either divisible by 400 */
227   /* or divisible by 4 and not by 100 */
228   if(month == 2) { /* February */
229     return mdays[month - 1] + ((year%400 == 0) ? 1 : ((year%100 != 0) &&
230      (year%4 == 0)) ? 1 : 0);
231   }
232   else {
233     return mdays[month - 1];
234   }
235 }
236 
237 /* compares timestamps using date part only */
238 static inline BOOL MONTHCAL_IsDateEqual(const SYSTEMTIME *first, const SYSTEMTIME *second)
239 {
240   return (first->wYear == second->wYear) && (first->wMonth == second->wMonth) &&
241          (first->wDay  == second->wDay);
242 }
243 
244 /* make sure that date fields are valid */
245 static BOOL MONTHCAL_ValidateDate(const SYSTEMTIME *time)
246 {
247     if (time->wMonth < 1 || time->wMonth > 12 )
248         return FALSE;
249     if (time->wDay == 0 || time->wDay > MONTHCAL_MonthLength(time->wMonth, time->wYear))
250         return FALSE;
251 
252     return TRUE;
253 }
254 
255 /* Copies timestamp part only.
256  *
257  * PARAMETERS
258  *
259  *  [I] from : source date
260  *  [O] to   : dest date
261  */
262 static void MONTHCAL_CopyTime(const SYSTEMTIME *from, SYSTEMTIME *to)
263 {
264   to->wHour   = from->wHour;
265   to->wMinute = from->wMinute;
266   to->wSecond = from->wSecond;
267 }
268 
269 /* Copies date part only.
270  *
271  * PARAMETERS
272  *
273  *  [I] from : source date
274  *  [O] to   : dest date
275  */
276 static void MONTHCAL_CopyDate(const SYSTEMTIME *from, SYSTEMTIME *to)
277 {
278   to->wYear  = from->wYear;
279   to->wMonth = from->wMonth;
280   to->wDay   = from->wDay;
281   to->wDayOfWeek = from->wDayOfWeek;
282 }
283 
284 /* Compares two dates in SYSTEMTIME format
285  *
286  * PARAMETERS
287  *
288  *  [I] first  : pointer to valid first date data to compare
289  *  [I] second : pointer to valid second date data to compare
290  *
291  * RETURN VALUE
292  *
293  *  -1 : first <  second
294  *   0 : first == second
295  *   1 : first >  second
296  *
297  *  Note that no date validation performed, already validated values expected.
298  */
299 LONG MONTHCAL_CompareSystemTime(const SYSTEMTIME *first, const SYSTEMTIME *second)
300 {
301   FILETIME ft_first, ft_second;
302 
303   SystemTimeToFileTime(first, &ft_first);
304   SystemTimeToFileTime(second, &ft_second);
305 
306   return CompareFileTime(&ft_first, &ft_second);
307 }
308 
309 static LONG MONTHCAL_CompareMonths(const SYSTEMTIME *first, const SYSTEMTIME *second)
310 {
311   SYSTEMTIME st_first, st_second;
312 
313   st_first = st_second = st_null;
314   MONTHCAL_CopyDate(first, &st_first);
315   MONTHCAL_CopyDate(second, &st_second);
316   st_first.wDay = st_second.wDay = 1;
317 
318   return MONTHCAL_CompareSystemTime(&st_first, &st_second);
319 }
320 
321 static LONG MONTHCAL_CompareDate(const SYSTEMTIME *first, const SYSTEMTIME *second)
322 {
323   SYSTEMTIME st_first, st_second;
324 
325   st_first = st_second = st_null;
326   MONTHCAL_CopyDate(first, &st_first);
327   MONTHCAL_CopyDate(second, &st_second);
328 
329   return MONTHCAL_CompareSystemTime(&st_first, &st_second);
330 }
331 
332 /* Checks largest possible date range and configured one
333  *
334  * PARAMETERS
335  *
336  *  [I] infoPtr : valid pointer to control data
337  *  [I] date    : pointer to valid date data to check
338  *  [I] fix     : make date fit valid range
339  *
340  * RETURN VALUE
341  *
342  *  TRUE  - date within largest and configured range
343  *  FALSE - date is outside largest or configured range
344  */
345 static BOOL MONTHCAL_IsDateInValidRange(const MONTHCAL_INFO *infoPtr,
346                                         SYSTEMTIME *date, BOOL fix)
347 {
348   const SYSTEMTIME *fix_st = NULL;
349 
350   if(MONTHCAL_CompareSystemTime(date, &max_allowed_date) == 1) {
351      fix_st = &max_allowed_date;
352   }
353   else if(MONTHCAL_CompareSystemTime(date, &min_allowed_date) == -1) {
354      fix_st = &min_allowed_date;
355   }
356   else {
357      if(infoPtr->rangeValid & GDTR_MAX) {
358         if((MONTHCAL_CompareSystemTime(date, &infoPtr->maxDate) == 1)) {
359            fix_st = &infoPtr->maxDate;
360         }
361      }
362 
363      if(infoPtr->rangeValid & GDTR_MIN) {
364         if((MONTHCAL_CompareSystemTime(date, &infoPtr->minDate) == -1)) {
365            fix_st = &infoPtr->minDate;
366         }
367      }
368   }
369 
370   if (fix && fix_st) {
371     date->wYear  = fix_st->wYear;
372     date->wMonth = fix_st->wMonth;
373   }
374 
375   return !fix_st;
376 }
377 
378 /* Checks passed range width with configured maximum selection count
379  *
380  * PARAMETERS
381  *
382  *  [I] infoPtr : valid pointer to control data
383  *  [I] range0  : pointer to valid date data (requested bound)
384  *  [I] range1  : pointer to valid date data (primary bound)
385  *  [O] adjust  : returns adjusted range bound to fit maximum range (optional)
386  *
387  *  Adjust value computed basing on primary bound and current maximum selection
388  *  count. For simple range check (without adjusted value required) (range0, range1)
389  *  relation means nothing.
390  *
391  * RETURN VALUE
392  *
393  *  TRUE  - range is shorter or equal to maximum
394  *  FALSE - range is larger than maximum
395  */
396 static BOOL MONTHCAL_IsSelRangeValid(const MONTHCAL_INFO *infoPtr,
397                                      const SYSTEMTIME *range0,
398                                      const SYSTEMTIME *range1,
399                                      SYSTEMTIME *adjust)
400 {
401   ULARGE_INTEGER ul_range0, ul_range1, ul_diff;
402   FILETIME ft_range0, ft_range1;
403   LONG cmp;
404 
405   SystemTimeToFileTime(range0, &ft_range0);
406   SystemTimeToFileTime(range1, &ft_range1);
407 
408   ul_range0.u.LowPart  = ft_range0.dwLowDateTime;
409   ul_range0.u.HighPart = ft_range0.dwHighDateTime;
410   ul_range1.u.LowPart  = ft_range1.dwLowDateTime;
411   ul_range1.u.HighPart = ft_range1.dwHighDateTime;
412 
413   cmp = CompareFileTime(&ft_range0, &ft_range1);
414 
415   if(cmp == 1)
416      ul_diff.QuadPart = ul_range0.QuadPart - ul_range1.QuadPart;
417   else
418      ul_diff.QuadPart = -ul_range0.QuadPart + ul_range1.QuadPart;
419 
420   if(ul_diff.QuadPart >= DAYSTO100NSECS(infoPtr->maxSelCount)) {
421 
422      if(adjust) {
423        if(cmp == 1)
424           ul_range0.QuadPart = ul_range1.QuadPart + DAYSTO100NSECS(infoPtr->maxSelCount - 1);
425        else
426           ul_range0.QuadPart = ul_range1.QuadPart - DAYSTO100NSECS(infoPtr->maxSelCount - 1);
427 
428        ft_range0.dwLowDateTime  = ul_range0.u.LowPart;
429        ft_range0.dwHighDateTime = ul_range0.u.HighPart;
430        FileTimeToSystemTime(&ft_range0, adjust);
431      }
432 
433      return FALSE;
434   }
435   else return TRUE;
436 }
437 
438 /* Used in MCM_SETRANGE/MCM_SETSELRANGE to determine resulting time part.
439    Milliseconds are intentionally not validated. */
440 static BOOL MONTHCAL_ValidateTime(const SYSTEMTIME *time)
441 {
442   if((time->wHour > 24) || (time->wMinute > 59) || (time->wSecond > 59))
443     return FALSE;
444   else
445     return TRUE;
446 }
447 
448 /* Note:Depending on DST, this may be offset by a day.
449    Need to find out if we're on a DST place & adjust the clock accordingly.
450    Above function assumes we have a valid data.
451    Valid for year>1752;  1 <= d <= 31, 1 <= m <= 12.
452    0 = Sunday.
453 */
454 
455 /* Returns the day in the week
456  *
457  * PARAMETERS
458  *  [i] date    : input date
459  *  [I] inplace : set calculated value back to date structure
460  *
461  * RETURN VALUE
462  *   day of week in SYSTEMTIME format: (0 == sunday,..., 6 == saturday)
463  */
464 int MONTHCAL_CalculateDayOfWeek(SYSTEMTIME *date, BOOL inplace)
465 {
466   SYSTEMTIME st = st_null;
467   FILETIME ft;
468 
469   MONTHCAL_CopyDate(date, &st);
470 
471   SystemTimeToFileTime(&st, &ft);
472   FileTimeToSystemTime(&ft, &st);
473 
474   if (inplace) date->wDayOfWeek = st.wDayOfWeek;
475 
476   return st.wDayOfWeek;
477 }
478 
479 /* add/subtract 'months' from date */
480 static inline void MONTHCAL_GetMonth(SYSTEMTIME *date, INT months)
481 {
482   INT length, m = date->wMonth + months;
483 
484   date->wYear += m > 0 ? (m - 1) / 12 : m / 12 - 1;
485   date->wMonth = m > 0 ? (m - 1) % 12 + 1 : 12 + m % 12;
486   /* fix moving from last day in a month */
487   length = MONTHCAL_MonthLength(date->wMonth, date->wYear);
488   if(date->wDay > length) date->wDay = length;
489   MONTHCAL_CalculateDayOfWeek(date, TRUE);
490 }
491 
492 /* properly updates date to point on next month */
493 static inline void MONTHCAL_GetNextMonth(SYSTEMTIME *date)
494 {
495   MONTHCAL_GetMonth(date, 1);
496 }
497 
498 /* properly updates date to point on prev month */
499 static inline void MONTHCAL_GetPrevMonth(SYSTEMTIME *date)
500 {
501   MONTHCAL_GetMonth(date, -1);
502 }
503 
504 /* Returns full date for a first currently visible day */
505 static void MONTHCAL_GetMinDate(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *date)
506 {
507   /* zero indexed calendar has the earliest date */
508   SYSTEMTIME st_first = infoPtr->calendars[0].month;
509   INT firstDay;
510 
511   st_first.wDay = 1;
512   firstDay = MONTHCAL_CalculateDayOfWeek(&st_first, FALSE);
513 
514   *date = infoPtr->calendars[0].month;
515   MONTHCAL_GetPrevMonth(date);
516 
517   date->wDay = MONTHCAL_MonthLength(date->wMonth, date->wYear) +
518                (infoPtr->firstDay - firstDay) % 7 + 1;
519 
520   if(date->wDay > MONTHCAL_MonthLength(date->wMonth, date->wYear))
521     date->wDay -= 7;
522 
523   /* fix day of week */
524   MONTHCAL_CalculateDayOfWeek(date, TRUE);
525 }
526 
527 /* Returns full date for a last currently visible day */
528 static void MONTHCAL_GetMaxDate(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *date)
529 {
530   /* the latest date is in latest calendar */
531   SYSTEMTIME st, *lt_month = &infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
532   INT first_day;
533 
534   *date = *lt_month;
535   st = *lt_month;
536 
537   /* day of week of first day of current month */
538   st.wDay = 1;
539   first_day = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
540 
541   MONTHCAL_GetNextMonth(date);
542   MONTHCAL_GetPrevMonth(&st);
543 
544   /* last calendar starts with some date from previous month that not displayed */
545   st.wDay = MONTHCAL_MonthLength(st.wMonth, st.wYear) +
546              (infoPtr->firstDay - first_day) % 7 + 1;
547   if (st.wDay > MONTHCAL_MonthLength(st.wMonth, st.wYear)) st.wDay -= 7;
548 
549   /* Use month length to get max day. 42 means max day count in calendar area */
550   date->wDay = 42 - (MONTHCAL_MonthLength(st.wMonth, st.wYear) - st.wDay + 1) -
551                      MONTHCAL_MonthLength(lt_month->wMonth, lt_month->wYear);
552 
553   /* fix day of week */
554   MONTHCAL_CalculateDayOfWeek(date, TRUE);
555 }
556 
557 /* From a given point calculate the row, column and day in the calendar,
558    'day == 0' means the last day of the last month. */
559 static int MONTHCAL_GetDayFromPos(const MONTHCAL_INFO *infoPtr, POINT pt, INT calIdx)
560 {
561   SYSTEMTIME st = infoPtr->calendars[calIdx].month;
562   int firstDay, col, row;
563   RECT client;
564 
565   GetClientRect(infoPtr->hwndSelf, &client);
566 
567   /* if the point is outside the x bounds of the window put it at the boundary */
568   if (pt.x > client.right) pt.x = client.right;
569 
570   col = (pt.x - infoPtr->calendars[calIdx].days.left ) / infoPtr->width_increment;
571   row = (pt.y - infoPtr->calendars[calIdx].days.top  ) / infoPtr->height_increment;
572 
573   st.wDay = 1;
574   firstDay = (MONTHCAL_CalculateDayOfWeek(&st, FALSE) + 6 - infoPtr->firstDay) % 7;
575   return col + 7 * row - firstDay;
576 }
577 
578 /* Get day position for given date and calendar
579  *
580  * PARAMETERS
581  *
582  *  [I] infoPtr : pointer to control data
583  *  [I] date : date value
584  *  [O] col : day column (zero based)
585  *  [O] row : week column (zero based)
586  *  [I] calIdx : calendar index
587  */
588 static void MONTHCAL_GetDayPos(const MONTHCAL_INFO *infoPtr, const SYSTEMTIME *date,
589     INT *col, INT *row, INT calIdx)
590 {
591   SYSTEMTIME st = infoPtr->calendars[calIdx].month;
592   INT first;
593 
594   st.wDay = 1;
595   first = (MONTHCAL_CalculateDayOfWeek(&st, FALSE) + 6 - infoPtr->firstDay) % 7;
596 
597   if (calIdx == 0 || calIdx == MONTHCAL_GetCalCount(infoPtr)-1) {
598       const SYSTEMTIME *cal = &infoPtr->calendars[calIdx].month;
599       LONG cmp = MONTHCAL_CompareMonths(date, &st);
600 
601       /* previous month */
602       if (cmp == -1) {
603         *col = (first - MONTHCAL_MonthLength(date->wMonth, cal->wYear) + date->wDay) % 7;
604         *row = 0;
605         return;
606       }
607 
608       /* next month calculation is same as for current, just add current month length */
609       if (cmp == 1)
610           first += MONTHCAL_MonthLength(cal->wMonth, cal->wYear);
611   }
612 
613   *col = (date->wDay + first) % 7;
614   *row = (date->wDay + first - *col) / 7;
615 }
616 
617 /* returns bounding box for day in given position in given calendar */
618 static inline void MONTHCAL_GetDayRectI(const MONTHCAL_INFO *infoPtr, RECT *r,
619   INT col, INT row, INT calIdx)
620 {
621   r->left   = infoPtr->calendars[calIdx].days.left + col * infoPtr->width_increment;
622   r->right  = r->left + infoPtr->width_increment;
623   r->top    = infoPtr->calendars[calIdx].days.top  + row * infoPtr->height_increment;
624   r->bottom = r->top + infoPtr->textHeight;
625 }
626 
627 /* Returns bounding box for given date
628  *
629  * NOTE: when calendar index is unknown pass -1
630  */
631 static BOOL MONTHCAL_GetDayRect(const MONTHCAL_INFO *infoPtr, const SYSTEMTIME *date, RECT *r, INT calIdx)
632 {
633   INT col, row;
634 
635   if (!MONTHCAL_ValidateDate(date))
636   {
637       SetRectEmpty(r);
638       return FALSE;
639   }
640 
641   if (calIdx == -1)
642   {
643       INT cmp = MONTHCAL_CompareMonths(date, &infoPtr->calendars[0].month);
644 
645       if (cmp <= 0)
646           calIdx = 0;
647       else
648       {
649           cmp = MONTHCAL_CompareMonths(date, &infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month);
650           if (cmp >= 0)
651               calIdx = MONTHCAL_GetCalCount(infoPtr)-1;
652           else
653           {
654               for (calIdx = 1; calIdx < MONTHCAL_GetCalCount(infoPtr)-1; calIdx++)
655                   if (MONTHCAL_CompareMonths(date, &infoPtr->calendars[calIdx].month) == 0)
656                       break;
657           }
658       }
659   }
660 
661   MONTHCAL_GetDayPos(infoPtr, date, &col, &row, calIdx);
662   MONTHCAL_GetDayRectI(infoPtr, r, col, row, calIdx);
663 
664   return TRUE;
665 }
666 
667 static LRESULT
668 MONTHCAL_GetMonthRange(const MONTHCAL_INFO *infoPtr, DWORD flag, SYSTEMTIME *st)
669 {
670   INT range;
671 
672   TRACE("flag=%d, st=%p\n", flag, st);
673 
674   switch (flag) {
675   case GMR_VISIBLE:
676   {
677       if (st)
678       {
679           st[0] = infoPtr->calendars[0].month;
680           st[1] = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
681 
682           if (st[0].wMonth == min_allowed_date.wMonth &&
683               st[0].wYear  == min_allowed_date.wYear)
684           {
685               st[0].wDay = min_allowed_date.wDay;
686           }
687           else
688               st[0].wDay = 1;
689           MONTHCAL_CalculateDayOfWeek(&st[0], TRUE);
690 
691           st[1].wDay = MONTHCAL_MonthLength(st[1].wMonth, st[1].wYear);
692           MONTHCAL_CalculateDayOfWeek(&st[1], TRUE);
693       }
694 
695       range = MONTHCAL_GetCalCount(infoPtr);
696       break;
697   }
698   case GMR_DAYSTATE:
699   {
700       if (st)
701       {
702           MONTHCAL_GetMinDate(infoPtr, &st[0]);
703           MONTHCAL_GetMaxDate(infoPtr, &st[1]);
704       }
705       /* include two partially visible months */
706       range = MONTHCAL_GetCalCount(infoPtr) + 2;
707       break;
708   }
709   default:
710       WARN("Unknown flag value, got %d\n", flag);
711       range = 0;
712   }
713 
714   return range;
715 }
716 
717 /* Focused day helper:
718 
719    - set focused date to given value;
720    - reset to zero value if NULL passed;
721    - invalidate previous and new day rectangle only if needed.
722 
723    Returns TRUE if focused day changed, FALSE otherwise.
724 */
725 static BOOL MONTHCAL_SetDayFocus(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *st)
726 {
727   RECT r;
728 
729   if(st)
730   {
731     /* there's nothing to do if it's the same date,
732        mouse move within same date rectangle case */
733     if(MONTHCAL_IsDateEqual(&infoPtr->focusedSel, st)) return FALSE;
734 
735     /* invalidate old focused day */
736     if (MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1))
737       InvalidateRect(infoPtr->hwndSelf, &r, FALSE);
738 
739     infoPtr->focusedSel = *st;
740   }
741 
742   /* On set invalidates new day, on reset clears previous focused day. */
743   if (MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1))
744     InvalidateRect(infoPtr->hwndSelf, &r, FALSE);
745 
746   if(!st && MONTHCAL_ValidateDate(&infoPtr->focusedSel))
747     infoPtr->focusedSel = st_null;
748 
749   return TRUE;
750 }
751 
752 /* draw today boundary box for specified rectangle */
753 static void MONTHCAL_Circle(const MONTHCAL_INFO *infoPtr, HDC hdc, const RECT *r)
754 {
755   HPEN old_pen = SelectObject(hdc, infoPtr->pens[PenRed]);
756   HBRUSH old_brush;
757 
758   old_brush = SelectObject(hdc, GetStockObject(NULL_BRUSH));
759   Rectangle(hdc, r->left, r->top, r->right, r->bottom);
760 
761   SelectObject(hdc, old_brush);
762   SelectObject(hdc, old_pen);
763 }
764 
765 /* Draw today day mark rectangle
766  *
767  * [I] hdc  : context to draw in
768  * [I] date : day to mark with rectangle
769  *
770  */
771 static void MONTHCAL_CircleDay(const MONTHCAL_INFO *infoPtr, HDC hdc,
772                                const SYSTEMTIME *date)
773 {
774   RECT r;
775 
776   MONTHCAL_GetDayRect(infoPtr, date, &r, -1);
777   MONTHCAL_Circle(infoPtr, hdc, &r);
778 }
779 
780 static void MONTHCAL_DrawDay(const MONTHCAL_INFO *infoPtr, HDC hdc, const SYSTEMTIME *st,
781                              int bold, const PAINTSTRUCT *ps)
782 {
783   static const WCHAR fmtW[] = { '%','d',0 };
784   WCHAR buf[10];
785   RECT r, r_temp;
786   COLORREF oldCol = 0;
787   COLORREF oldBk  = 0;
788   INT old_bkmode, selection;
789 
790   /* no need to check styles: when selection is not valid, it is set to zero.
791      1 < day < 31, so everything is OK */
792   MONTHCAL_GetDayRect(infoPtr, st, &r, -1);
793   if(!IntersectRect(&r_temp, &(ps->rcPaint), &r)) return;
794 
795   if ((MONTHCAL_CompareDate(st, &infoPtr->minSel) >= 0) &&
796       (MONTHCAL_CompareDate(st, &infoPtr->maxSel) <= 0))
797   {
798     TRACE("%d %d %d\n", st->wDay, infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
799     TRACE("%s\n", wine_dbgstr_rect(&r));
800     oldCol = SetTextColor(hdc, infoPtr->colors[MCSC_MONTHBK]);
801     oldBk = SetBkColor(hdc, infoPtr->colors[MCSC_TRAILINGTEXT]);
802     FillRect(hdc, &r, infoPtr->brushes[BrushTitle]);
803 
804     selection = 1;
805   }
806   else
807     selection = 0;
808 
809   SelectObject(hdc, bold ? infoPtr->hBoldFont : infoPtr->hFont);
810 
811   old_bkmode = SetBkMode(hdc, TRANSPARENT);
812   wsprintfW(buf, fmtW, st->wDay);
813   DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
814   SetBkMode(hdc, old_bkmode);
815 
816   if (selection)
817   {
818     SetTextColor(hdc, oldCol);
819     SetBkColor(hdc, oldBk);
820   }
821 }
822 
823 static void MONTHCAL_PaintButton(MONTHCAL_INFO *infoPtr, HDC hdc, enum nav_direction button)
824 {
825     HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
826     RECT *r = button == DIRECTION_FORWARD ? &infoPtr->titlebtnnext : &infoPtr->titlebtnprev;
827     BOOL pressed = button == DIRECTION_FORWARD ? infoPtr->status & MC_NEXTPRESSED :
828                                                  infoPtr->status & MC_PREVPRESSED;
829     if (theme)
830     {
831         static const int states[] = {
832             /* Prev button */
833             ABS_LEFTNORMAL,  ABS_LEFTPRESSED,  ABS_LEFTDISABLED,
834             /* Next button */
835             ABS_RIGHTNORMAL, ABS_RIGHTPRESSED, ABS_RIGHTDISABLED
836         };
837         int stateNum = button == DIRECTION_FORWARD ? 3 : 0;
838         if (pressed)
839             stateNum += 1;
840         else
841         {
842             if (infoPtr->dwStyle & WS_DISABLED) stateNum += 2;
843         }
844         DrawThemeBackground (theme, hdc, SBP_ARROWBTN, states[stateNum], r, NULL);
845     }
846     else
847     {
848         int style = button == DIRECTION_FORWARD ? DFCS_SCROLLRIGHT : DFCS_SCROLLLEFT;
849         if (pressed)
850             style |= DFCS_PUSHED;
851         else
852         {
853             if (infoPtr->dwStyle & WS_DISABLED) style |= DFCS_INACTIVE;
854         }
855 
856         DrawFrameControl(hdc, r, DFC_SCROLL, style);
857     }
858 }
859 
860 /* paint a title with buttons and month/year string */
861 static void MONTHCAL_PaintTitle(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
862 {
863   static const WCHAR mmmmW[] = {'M','M','M','M',0};
864   static const WCHAR mmmW[] = {'M','M','M',0};
865   static const WCHAR mmW[] = {'M','M',0};
866   static const WCHAR fmtyearW[] = {'%','l','d',0};
867   static const WCHAR fmtmmW[] = {'%','0','2','d',0};
868   static const WCHAR fmtmW[] = {'%','d',0};
869   RECT *title = &infoPtr->calendars[calIdx].title;
870   const SYSTEMTIME *st = &infoPtr->calendars[calIdx].month;
871   WCHAR monthW[80], strW[80], fmtW[80], yearW[6] /* valid year range is 1601-30827 */;
872   int yearoffset, monthoffset, shiftX;
873   SIZE sz;
874 
875   /* fill header box */
876   FillRect(hdc, title, infoPtr->brushes[BrushTitle]);
877 
878   /* month/year string */
879   SetBkColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
880   SetTextColor(hdc, infoPtr->colors[MCSC_TITLETEXT]);
881   SelectObject(hdc, infoPtr->hBoldFont);
882 
883   /* draw formatted date string */
884   GetDateFormatW(LOCALE_USER_DEFAULT, DATE_YEARMONTH, st, NULL, strW, countof(strW));
885   DrawTextW(hdc, strW, strlenW(strW), title, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
886 
887   GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SYEARMONTH, fmtW, countof(fmtW));
888   wsprintfW(yearW, fmtyearW, st->wYear);
889 
890   /* month is trickier as it's possible to have different format pictures, we'll
891      test for M, MM, MMM, and MMMM */
892   if (strstrW(fmtW, mmmmW))
893     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+st->wMonth-1, monthW, countof(monthW));
894   else if (strstrW(fmtW, mmmW))
895     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVMONTHNAME1+st->wMonth-1, monthW, countof(monthW));
896   else if (strstrW(fmtW, mmW))
897     wsprintfW(monthW, fmtmmW, st->wMonth);
898   else
899     wsprintfW(monthW, fmtmW, st->wMonth);
900 
901   /* update hit boxes */
902   yearoffset = 0;
903   while (strW[yearoffset])
904   {
905     if (!strncmpW(&strW[yearoffset], yearW, strlenW(yearW)))
906         break;
907     yearoffset++;
908   }
909 
910   monthoffset = 0;
911   while (strW[monthoffset])
912   {
913     if (!strncmpW(&strW[monthoffset], monthW, strlenW(monthW)))
914         break;
915     monthoffset++;
916   }
917 
918   /* for left limits use offsets */
919   sz.cx = 0;
920   if (yearoffset)
921     GetTextExtentPoint32W(hdc, strW, yearoffset, &sz);
922   infoPtr->calendars[calIdx].titleyear.left = sz.cx;
923 
924   sz.cx = 0;
925   if (monthoffset)
926     GetTextExtentPoint32W(hdc, strW, monthoffset, &sz);
927   infoPtr->calendars[calIdx].titlemonth.left = sz.cx;
928 
929   /* for right limits use actual string parts lengths */
930   GetTextExtentPoint32W(hdc, &strW[yearoffset], strlenW(yearW), &sz);
931   infoPtr->calendars[calIdx].titleyear.right = infoPtr->calendars[calIdx].titleyear.left + sz.cx;
932 
933   GetTextExtentPoint32W(hdc, monthW, strlenW(monthW), &sz);
934   infoPtr->calendars[calIdx].titlemonth.right = infoPtr->calendars[calIdx].titlemonth.left + sz.cx;
935 
936   /* Finally translate rectangles to match center aligned string,
937      hit rectangles are relative to title rectangle before translation. */
938   GetTextExtentPoint32W(hdc, strW, strlenW(strW), &sz);
939   shiftX = (title->right - title->left - sz.cx) / 2 + title->left;
940   OffsetRect(&infoPtr->calendars[calIdx].titleyear, shiftX, 0);
941   OffsetRect(&infoPtr->calendars[calIdx].titlemonth, shiftX, 0);
942 }
943 
944 static void MONTHCAL_PaintWeeknumbers(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
945 {
946   const SYSTEMTIME *date = &infoPtr->calendars[calIdx].month;
947   static const WCHAR fmt_weekW[] = { '%','d',0 };
948   INT mindays, weeknum, weeknum1, startofprescal;
949   INT i, prev_month;
950   SYSTEMTIME st;
951   WCHAR buf[80];
952   HPEN old_pen;
953   RECT r;
954 
955   if (!(infoPtr->dwStyle & MCS_WEEKNUMBERS)) return;
956 
957   MONTHCAL_GetMinDate(infoPtr, &st);
958   startofprescal = st.wDay;
959   st = *date;
960 
961   prev_month = date->wMonth - 1;
962   if(prev_month == 0) prev_month = 12;
963 
964   /*
965      Rules what week to call the first week of a new year:
966      LOCALE_IFIRSTWEEKOFYEAR == 0 (e.g US?):
967      The week containing Jan 1 is the first week of year
968      LOCALE_IFIRSTWEEKOFYEAR == 2 (e.g. Germany):
969      First week of year must contain 4 days of the new year
970      LOCALE_IFIRSTWEEKOFYEAR == 1  (what countries?)
971      The first week of the year must contain only days of the new year
972   */
973   GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, buf, countof(buf));
974   weeknum = atoiW(buf);
975   switch (weeknum)
976   {
977     case 1: mindays = 6;
978 	break;
979     case 2: mindays = 3;
980 	break;
981     case 0: mindays = 0;
982         break;
983     default:
984         WARN("Unknown LOCALE_IFIRSTWEEKOFYEAR value %d, defaulting to 0\n", weeknum);
985 	mindays = 0;
986   }
987 
988   if (date->wMonth == 1)
989   {
990     /* calculate all those exceptions for January */
991     st.wDay = st.wMonth = 1;
992     weeknum1 = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
993     if ((infoPtr->firstDay - weeknum1) % 7 > mindays)
994 	weeknum = 1;
995     else
996     {
997 	weeknum = 0;
998 	for(i = 0; i < 11; i++)
999 	   weeknum += MONTHCAL_MonthLength(i+1, date->wYear - 1);
1000 
1001 	weeknum  += startofprescal + 7;
1002 	weeknum  /= 7;
1003 	st.wYear -= 1;
1004 	weeknum1  = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
1005 	if ((infoPtr->firstDay - weeknum1) % 7 > mindays) weeknum++;
1006     }
1007   }
1008   else
1009   {
1010     weeknum = 0;
1011     for(i = 0; i < prev_month - 1; i++)
1012 	weeknum += MONTHCAL_MonthLength(i+1, date->wYear);
1013 
1014     weeknum += startofprescal + 7;
1015     weeknum /= 7;
1016     st.wDay = st.wMonth = 1;
1017     weeknum1 = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
1018     if ((infoPtr->firstDay - weeknum1) % 7 > mindays) weeknum++;
1019   }
1020 
1021   r = infoPtr->calendars[calIdx].weeknums;
1022 
1023   /* erase whole week numbers area */
1024   FillRect(hdc, &r, infoPtr->brushes[BrushMonth]);
1025   SetTextColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
1026 
1027   /* reduce rectangle to one week number */
1028   r.bottom = r.top + infoPtr->height_increment;
1029 
1030   for(i = 0; i < 6; i++) {
1031     if((i == 0) && (weeknum > 50))
1032     {
1033         wsprintfW(buf, fmt_weekW, weeknum);
1034         weeknum = 0;
1035     }
1036     else if((i == 5) && (weeknum > 47))
1037     {
1038 	wsprintfW(buf, fmt_weekW, 1);
1039     }
1040     else
1041 	wsprintfW(buf, fmt_weekW, weeknum + i);
1042 
1043     DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
1044     OffsetRect(&r, 0, infoPtr->height_increment);
1045   }
1046 
1047   /* line separator for week numbers column */
1048   old_pen = SelectObject(hdc, infoPtr->pens[PenText]);
1049   MoveToEx(hdc, infoPtr->calendars[calIdx].weeknums.right, infoPtr->calendars[calIdx].weeknums.top + 3 , NULL);
1050   LineTo(hdc,   infoPtr->calendars[calIdx].weeknums.right, infoPtr->calendars[calIdx].weeknums.bottom);
1051   SelectObject(hdc, old_pen);
1052 }
1053 
1054 /* bottom today date */
1055 static void MONTHCAL_PaintTodayTitle(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
1056 {
1057   static const WCHAR fmt_todayW[] = { '%','s',' ','%','s',0 };
1058   WCHAR buf_todayW[30], buf_dateW[20], buf[80];
1059   RECT text_rect, box_rect;
1060   HFONT old_font;
1061   INT col;
1062 
1063   if(infoPtr->dwStyle & MCS_NOTODAY) return;
1064 
1065   LoadStringW(COMCTL32_hModule, IDM_TODAY, buf_todayW, countof(buf_todayW));
1066   col = infoPtr->dwStyle & MCS_NOTODAYCIRCLE ? 0 : 1;
1067   if (infoPtr->dwStyle & MCS_WEEKNUMBERS) col--;
1068   /* label is located below first calendar last row */
1069   MONTHCAL_GetDayRectI(infoPtr, &text_rect, col, 6, infoPtr->dim.cx * infoPtr->dim.cy - infoPtr->dim.cx);
1070   box_rect = text_rect;
1071 
1072   GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &infoPtr->todaysDate, NULL,
1073                                                       buf_dateW, countof(buf_dateW));
1074   old_font = SelectObject(hdc, infoPtr->hBoldFont);
1075   SetTextColor(hdc, infoPtr->colors[MCSC_TEXT]);
1076 
1077   wsprintfW(buf, fmt_todayW, buf_todayW, buf_dateW);
1078   DrawTextW(hdc, buf, -1, &text_rect, DT_CALCRECT | DT_LEFT | DT_VCENTER | DT_SINGLELINE);
1079   DrawTextW(hdc, buf, -1, &text_rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
1080 
1081   if(!(infoPtr->dwStyle & MCS_NOTODAYCIRCLE)) {
1082     OffsetRect(&box_rect, -infoPtr->width_increment, 0);
1083     MONTHCAL_Circle(infoPtr, hdc, &box_rect);
1084   }
1085 
1086   SelectObject(hdc, old_font);
1087 }
1088 
1089 /* today mark + focus */
1090 static void MONTHCAL_PaintFocusAndCircle(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
1091 {
1092   /* circle today date if only it's in fully visible month */
1093   if (!(infoPtr->dwStyle & MCS_NOTODAYCIRCLE))
1094   {
1095     INT i;
1096 
1097     for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1098       if (!MONTHCAL_CompareMonths(&infoPtr->todaysDate, &infoPtr->calendars[i].month))
1099       {
1100         MONTHCAL_CircleDay(infoPtr, hdc, &infoPtr->todaysDate);
1101         break;
1102       }
1103   }
1104 
1105   if (!MONTHCAL_IsDateEqual(&infoPtr->focusedSel, &st_null))
1106   {
1107     RECT r;
1108     MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1);
1109     DrawFocusRect(hdc, &r);
1110   }
1111 }
1112 
1113 /* months before first calendar month and after last calendar month */
1114 static void MONTHCAL_PaintLeadTrailMonths(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
1115 {
1116   INT mask, index;
1117   UINT length;
1118   SYSTEMTIME st_max, st;
1119 
1120   if (infoPtr->dwStyle & MCS_NOTRAILINGDATES) return;
1121 
1122   SetTextColor(hdc, infoPtr->colors[MCSC_TRAILINGTEXT]);
1123 
1124   /* draw prev month */
1125   MONTHCAL_GetMinDate(infoPtr, &st);
1126   mask = 1 << (st.wDay-1);
1127   /* December and January both 31 days long, so no worries if wrapped */
1128   length = MONTHCAL_MonthLength(infoPtr->calendars[0].month.wMonth - 1,
1129                                 infoPtr->calendars[0].month.wYear);
1130   index = 0;
1131   while(st.wDay <= length)
1132   {
1133       MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[index] & mask, ps);
1134       mask <<= 1;
1135       st.wDay++;
1136   }
1137 
1138   /* draw next month */
1139   st = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
1140   st.wDay = 1;
1141   MONTHCAL_GetNextMonth(&st);
1142   MONTHCAL_GetMaxDate(infoPtr, &st_max);
1143   mask = 1;
1144   index = MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)-1;
1145   while(st.wDay <= st_max.wDay)
1146   {
1147       MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[index] & mask, ps);
1148       mask <<= 1;
1149       st.wDay++;
1150   }
1151 }
1152 
1153 static int get_localized_dayname(const MONTHCAL_INFO *infoPtr, unsigned int day, WCHAR *buff, unsigned int count)
1154 {
1155   LCTYPE lctype;
1156 
1157   if (infoPtr->dwStyle & MCS_SHORTDAYSOFWEEK)
1158       lctype = LOCALE_SSHORTESTDAYNAME1 + day;
1159   else
1160       lctype = LOCALE_SABBREVDAYNAME1 + day;
1161 
1162   return GetLocaleInfoW(LOCALE_USER_DEFAULT, lctype, buff, count);
1163 }
1164 
1165 /* paint a calendar area */
1166 static void MONTHCAL_PaintCalendar(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
1167 {
1168   const SYSTEMTIME *date = &infoPtr->calendars[calIdx].month;
1169   INT i, j;
1170   UINT length;
1171   RECT r, fill_bk_rect;
1172   SYSTEMTIME st;
1173   WCHAR buf[80];
1174   HPEN old_pen;
1175   int mask;
1176 
1177   /* fill whole days area - from week days area to today note rectangle */
1178   fill_bk_rect = infoPtr->calendars[calIdx].wdays;
1179   fill_bk_rect.bottom = infoPtr->calendars[calIdx].days.bottom +
1180                           (infoPtr->todayrect.bottom - infoPtr->todayrect.top);
1181 
1182   FillRect(hdc, &fill_bk_rect, infoPtr->brushes[BrushMonth]);
1183 
1184   /* draw line under day abbreviations */
1185   old_pen = SelectObject(hdc, infoPtr->pens[PenText]);
1186   MoveToEx(hdc, infoPtr->calendars[calIdx].days.left + 3,
1187                 infoPtr->calendars[calIdx].title.bottom + infoPtr->textHeight + 1, NULL);
1188   LineTo(hdc, infoPtr->calendars[calIdx].days.right - 3,
1189               infoPtr->calendars[calIdx].title.bottom + infoPtr->textHeight + 1);
1190   SelectObject(hdc, old_pen);
1191 
1192   infoPtr->calendars[calIdx].wdays.left = infoPtr->calendars[calIdx].days.left =
1193       infoPtr->calendars[calIdx].weeknums.right;
1194 
1195   /* draw day abbreviations */
1196   SelectObject(hdc, infoPtr->hFont);
1197   SetBkColor(hdc, infoPtr->colors[MCSC_MONTHBK]);
1198   SetTextColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
1199   /* rectangle to draw a single day abbreviation within */
1200   r = infoPtr->calendars[calIdx].wdays;
1201   r.right = r.left + infoPtr->width_increment;
1202 
1203   i = infoPtr->firstDay;
1204   for(j = 0; j < 7; j++) {
1205     get_localized_dayname(infoPtr, (i + j + 6) % 7, buf, countof(buf));
1206     DrawTextW(hdc, buf, strlenW(buf), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
1207     OffsetRect(&r, infoPtr->width_increment, 0);
1208   }
1209 
1210   /* draw current month */
1211   SetTextColor(hdc, infoPtr->colors[MCSC_TEXT]);
1212   st = *date;
1213   st.wDay = 1;
1214   mask = 1;
1215   length = MONTHCAL_MonthLength(date->wMonth, date->wYear);
1216   while(st.wDay <= length)
1217   {
1218     MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[calIdx+1] & mask, ps);
1219     mask <<= 1;
1220     st.wDay++;
1221   }
1222 }
1223 
1224 static void MONTHCAL_Refresh(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
1225 {
1226   COLORREF old_text_clr, old_bk_clr;
1227   HFONT old_font;
1228   INT i;
1229 
1230   old_text_clr = SetTextColor(hdc, comctl32_color.clrWindowText);
1231   old_bk_clr   = GetBkColor(hdc);
1232   old_font     = GetCurrentObject(hdc, OBJ_FONT);
1233 
1234   for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1235   {
1236     RECT *title = &infoPtr->calendars[i].title;
1237     RECT r;
1238 
1239     /* draw title, redraw all its elements */
1240     if (IntersectRect(&r, &(ps->rcPaint), title))
1241         MONTHCAL_PaintTitle(infoPtr, hdc, ps, i);
1242 
1243     /* draw calendar area */
1244     UnionRect(&r, &infoPtr->calendars[i].wdays, &infoPtr->todayrect);
1245     if (IntersectRect(&r, &(ps->rcPaint), &r))
1246         MONTHCAL_PaintCalendar(infoPtr, hdc, ps, i);
1247 
1248     /* week numbers */
1249     MONTHCAL_PaintWeeknumbers(infoPtr, hdc, ps, i);
1250   }
1251 
1252   /* partially visible months */
1253   MONTHCAL_PaintLeadTrailMonths(infoPtr, hdc, ps);
1254 
1255   /* focus and today rectangle */
1256   MONTHCAL_PaintFocusAndCircle(infoPtr, hdc, ps);
1257 
1258   /* today at the bottom left */
1259   MONTHCAL_PaintTodayTitle(infoPtr, hdc, ps);
1260 
1261   /* navigation buttons */
1262   MONTHCAL_PaintButton(infoPtr, hdc, DIRECTION_BACKWARD);
1263   MONTHCAL_PaintButton(infoPtr, hdc, DIRECTION_FORWARD);
1264 
1265   /* restore context */
1266   SetBkColor(hdc, old_bk_clr);
1267   SelectObject(hdc, old_font);
1268   SetTextColor(hdc, old_text_clr);
1269 }
1270 
1271 static LRESULT
1272 MONTHCAL_GetMinReqRect(const MONTHCAL_INFO *infoPtr, RECT *rect)
1273 {
1274   TRACE("rect %p\n", rect);
1275 
1276   if(!rect) return FALSE;
1277 
1278   *rect = infoPtr->calendars[0].title;
1279   rect->bottom = infoPtr->calendars[0].days.bottom + infoPtr->todayrect.bottom -
1280                  infoPtr->todayrect.top;
1281 
1282   AdjustWindowRect(rect, infoPtr->dwStyle, FALSE);
1283 
1284   /* minimal rectangle is zero based */
1285   OffsetRect(rect, -rect->left, -rect->top);
1286 
1287   TRACE("%s\n", wine_dbgstr_rect(rect));
1288 
1289   return TRUE;
1290 }
1291 
1292 static COLORREF
1293 MONTHCAL_GetColor(const MONTHCAL_INFO *infoPtr, UINT index)
1294 {
1295   TRACE("%p, %d\n", infoPtr, index);
1296 
1297   if (index > MCSC_TRAILINGTEXT) return -1;
1298   return infoPtr->colors[index];
1299 }
1300 
1301 static LRESULT
1302 MONTHCAL_SetColor(MONTHCAL_INFO *infoPtr, UINT index, COLORREF color)
1303 {
1304   enum CachedBrush type;
1305   COLORREF prev;
1306 
1307   TRACE("%p, %d: color %08x\n", infoPtr, index, color);
1308 
1309   if (index > MCSC_TRAILINGTEXT) return -1;
1310 
1311   prev = infoPtr->colors[index];
1312   infoPtr->colors[index] = color;
1313 
1314   /* update cached brush */
1315   switch (index)
1316   {
1317   case MCSC_BACKGROUND:
1318     type = BrushBackground;
1319     break;
1320   case MCSC_TITLEBK:
1321     type = BrushTitle;
1322     break;
1323   case MCSC_MONTHBK:
1324     type = BrushMonth;
1325     break;
1326   default:
1327     type = BrushLast;
1328   }
1329 
1330   if (type != BrushLast)
1331   {
1332     DeleteObject(infoPtr->brushes[type]);
1333     infoPtr->brushes[type] = CreateSolidBrush(color);
1334   }
1335 
1336   /* update cached pen */
1337   if (index == MCSC_TEXT)
1338   {
1339     DeleteObject(infoPtr->pens[PenText]);
1340     infoPtr->pens[PenText] = CreatePen(PS_SOLID, 1, infoPtr->colors[index]);
1341   }
1342 
1343   InvalidateRect(infoPtr->hwndSelf, NULL, index == MCSC_BACKGROUND);
1344   return prev;
1345 }
1346 
1347 static LRESULT
1348 MONTHCAL_GetMonthDelta(const MONTHCAL_INFO *infoPtr)
1349 {
1350   TRACE("\n");
1351 
1352   if(infoPtr->delta)
1353     return infoPtr->delta;
1354 
1355   return MONTHCAL_GetMonthRange(infoPtr, GMR_VISIBLE, NULL);
1356 }
1357 
1358 
1359 static LRESULT
1360 MONTHCAL_SetMonthDelta(MONTHCAL_INFO *infoPtr, INT delta)
1361 {
1362   INT prev = infoPtr->delta;
1363 
1364   TRACE("delta %d\n", delta);
1365 
1366   infoPtr->delta = delta;
1367   return prev;
1368 }
1369 
1370 
1371 static inline LRESULT
1372 MONTHCAL_GetFirstDayOfWeek(const MONTHCAL_INFO *infoPtr)
1373 {
1374   int day;
1375 
1376   /* convert from SYSTEMTIME to locale format */
1377   day = (infoPtr->firstDay >= 0) ? (infoPtr->firstDay+6)%7 : infoPtr->firstDay;
1378 
1379   return MAKELONG(day, infoPtr->firstDaySet);
1380 }
1381 
1382 
1383 /* Sets the first day of the week that will appear in the control
1384  *
1385  *
1386  * PARAMETERS:
1387  *  [I] infoPtr : valid pointer to control data
1388  *  [I] day : day number to set as new first day (0 == Monday,...,6 == Sunday)
1389  *
1390  *
1391  * RETURN VALUE:
1392  *  Low word contains previous first day,
1393  *  high word indicates was first day forced with this message before or is
1394  *  locale defined (TRUE - was forced, FALSE - wasn't).
1395  *
1396  * FIXME: this needs to be implemented properly in MONTHCAL_Refresh()
1397  * FIXME: we need more error checking here
1398  */
1399 static LRESULT
1400 MONTHCAL_SetFirstDayOfWeek(MONTHCAL_INFO *infoPtr, INT day)
1401 {
1402   LRESULT prev = MONTHCAL_GetFirstDayOfWeek(infoPtr);
1403   int new_day;
1404 
1405   TRACE("%d\n", day);
1406 
1407   if(day == -1)
1408   {
1409     WCHAR buf[80];
1410 
1411     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, buf, countof(buf));
1412     TRACE("%s %d\n", debugstr_w(buf), strlenW(buf));
1413 
1414     new_day = atoiW(buf);
1415 
1416     infoPtr->firstDaySet = FALSE;
1417   }
1418   else if(day >= 7)
1419   {
1420     new_day = 6; /* max first day allowed */
1421     infoPtr->firstDaySet = TRUE;
1422   }
1423   else
1424   {
1425     /* Native behaviour for that case is broken: invalid date number >31
1426        got displayed at (0,0) position, current month starts always from
1427        (1,0) position. Should be implemented here as well only if there's
1428        nothing else to do. */
1429     if (day < -1)
1430       FIXME("No bug compatibility for day=%d\n", day);
1431 
1432     new_day = day;
1433     infoPtr->firstDaySet = TRUE;
1434   }
1435 
1436   /* convert from locale to SYSTEMTIME format */
1437   infoPtr->firstDay = (new_day >= 0) ? (++new_day) % 7 : new_day;
1438 
1439   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1440 
1441   return prev;
1442 }
1443 
1444 static LRESULT
1445 MONTHCAL_GetMaxTodayWidth(const MONTHCAL_INFO *infoPtr)
1446 {
1447   return(infoPtr->todayrect.right - infoPtr->todayrect.left);
1448 }
1449 
1450 static LRESULT
1451 MONTHCAL_SetRange(MONTHCAL_INFO *infoPtr, SHORT limits, SYSTEMTIME *range)
1452 {
1453     FILETIME ft_min, ft_max;
1454 
1455     TRACE("%x %p\n", limits, range);
1456 
1457     if ((limits & GDTR_MIN && !MONTHCAL_ValidateDate(&range[0])) ||
1458         (limits & GDTR_MAX && !MONTHCAL_ValidateDate(&range[1])))
1459         return FALSE;
1460 
1461     infoPtr->rangeValid = 0;
1462     infoPtr->minDate = infoPtr->maxDate = st_null;
1463 
1464     if (limits & GDTR_MIN)
1465     {
1466         if (!MONTHCAL_ValidateTime(&range[0]))
1467             MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]);
1468 
1469         infoPtr->minDate = range[0];
1470         infoPtr->rangeValid |= GDTR_MIN;
1471     }
1472     if (limits & GDTR_MAX)
1473     {
1474         if (!MONTHCAL_ValidateTime(&range[1]))
1475             MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]);
1476 
1477         infoPtr->maxDate = range[1];
1478         infoPtr->rangeValid |= GDTR_MAX;
1479     }
1480 
1481     /* Only one limit set - we are done */
1482     if ((infoPtr->rangeValid & (GDTR_MIN | GDTR_MAX)) != (GDTR_MIN | GDTR_MAX))
1483         return TRUE;
1484 
1485     SystemTimeToFileTime(&infoPtr->maxDate, &ft_max);
1486     SystemTimeToFileTime(&infoPtr->minDate, &ft_min);
1487 
1488     if (CompareFileTime(&ft_min, &ft_max) >= 0)
1489     {
1490         if ((limits & (GDTR_MIN | GDTR_MAX)) == (GDTR_MIN | GDTR_MAX))
1491         {
1492             /* Native swaps limits only when both limits are being set. */
1493             SYSTEMTIME st_tmp = infoPtr->minDate;
1494             infoPtr->minDate  = infoPtr->maxDate;
1495             infoPtr->maxDate  = st_tmp;
1496         }
1497         else
1498         {
1499             /* reset the other limit */
1500             if (limits & GDTR_MIN) infoPtr->maxDate = st_null;
1501             if (limits & GDTR_MAX) infoPtr->minDate = st_null;
1502             infoPtr->rangeValid &= limits & GDTR_MIN ? ~GDTR_MAX : ~GDTR_MIN;
1503         }
1504     }
1505 
1506     return TRUE;
1507 }
1508 
1509 
1510 static LRESULT
1511 MONTHCAL_GetRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1512 {
1513   TRACE("%p\n", range);
1514 
1515   if (!range) return 0;
1516 
1517   range[1] = infoPtr->maxDate;
1518   range[0] = infoPtr->minDate;
1519 
1520   return infoPtr->rangeValid;
1521 }
1522 
1523 
1524 static LRESULT
1525 MONTHCAL_SetDayState(const MONTHCAL_INFO *infoPtr, INT months, MONTHDAYSTATE *states)
1526 {
1527   TRACE("%p %d %p\n", infoPtr, months, states);
1528 
1529   if (!(infoPtr->dwStyle & MCS_DAYSTATE)) return 0;
1530   if (months != MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)) return 0;
1531 
1532   memcpy(infoPtr->monthdayState, states, months*sizeof(MONTHDAYSTATE));
1533 
1534   return 1;
1535 }
1536 
1537 static LRESULT
1538 MONTHCAL_GetCurSel(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel)
1539 {
1540   TRACE("%p\n", curSel);
1541   if(!curSel) return FALSE;
1542   if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
1543 
1544   *curSel = infoPtr->minSel;
1545   TRACE("%d/%d/%d\n", curSel->wYear, curSel->wMonth, curSel->wDay);
1546   return TRUE;
1547 }
1548 
1549 static LRESULT
1550 MONTHCAL_SetCurSel(MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel)
1551 {
1552   SYSTEMTIME prev = infoPtr->minSel, selection;
1553   INT diff;
1554   WORD day;
1555 
1556   TRACE("%p\n", curSel);
1557   if(!curSel) return FALSE;
1558   if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
1559 
1560   if(!MONTHCAL_ValidateDate(curSel)) return FALSE;
1561   /* exit earlier if selection equals current */
1562   if (MONTHCAL_IsDateEqual(&infoPtr->minSel, curSel)) return TRUE;
1563 
1564   selection = *curSel;
1565   selection.wHour = selection.wMinute = selection.wSecond = selection.wMilliseconds = 0;
1566   MONTHCAL_CalculateDayOfWeek(&selection, TRUE);
1567 
1568   if(!MONTHCAL_IsDateInValidRange(infoPtr, &selection, FALSE)) return FALSE;
1569 
1570   /* scroll calendars only if we have to */
1571   diff = MONTHCAL_MonthDiff(&infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month, curSel);
1572   if (diff <= 0)
1573   {
1574     diff = MONTHCAL_MonthDiff(&infoPtr->calendars[0].month, curSel);
1575     if (diff > 0) diff = 0;
1576   }
1577 
1578   if (diff != 0)
1579   {
1580     INT i;
1581 
1582     for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1583       MONTHCAL_GetMonth(&infoPtr->calendars[i].month, diff);
1584   }
1585 
1586   /* we need to store time part as it is */
1587   selection = *curSel;
1588   MONTHCAL_CalculateDayOfWeek(&selection, TRUE);
1589   infoPtr->minSel = infoPtr->maxSel = selection;
1590 
1591   /* if selection is still in current month, reduce rectangle */
1592   day = prev.wDay;
1593   prev.wDay = curSel->wDay;
1594   if (MONTHCAL_IsDateEqual(&prev, curSel))
1595   {
1596     RECT r_prev, r_new;
1597 
1598     prev.wDay = day;
1599     MONTHCAL_GetDayRect(infoPtr, &prev, &r_prev, -1);
1600     MONTHCAL_GetDayRect(infoPtr, curSel, &r_new, -1);
1601 
1602     InvalidateRect(infoPtr->hwndSelf, &r_prev, FALSE);
1603     InvalidateRect(infoPtr->hwndSelf, &r_new,  FALSE);
1604   }
1605   else
1606     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1607 
1608   return TRUE;
1609 }
1610 
1611 
1612 static LRESULT
1613 MONTHCAL_GetMaxSelCount(const MONTHCAL_INFO *infoPtr)
1614 {
1615   return infoPtr->maxSelCount;
1616 }
1617 
1618 
1619 static LRESULT
1620 MONTHCAL_SetMaxSelCount(MONTHCAL_INFO *infoPtr, INT max)
1621 {
1622   TRACE("%d\n", max);
1623 
1624   if(!(infoPtr->dwStyle & MCS_MULTISELECT)) return FALSE;
1625   if(max <= 0) return FALSE;
1626 
1627   infoPtr->maxSelCount = max;
1628 
1629   return TRUE;
1630 }
1631 
1632 
1633 static LRESULT
1634 MONTHCAL_GetSelRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1635 {
1636   TRACE("%p\n", range);
1637 
1638   if(!range) return FALSE;
1639 
1640   if(infoPtr->dwStyle & MCS_MULTISELECT)
1641   {
1642     range[1] = infoPtr->maxSel;
1643     range[0] = infoPtr->minSel;
1644     TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
1645     return TRUE;
1646   }
1647 
1648   return FALSE;
1649 }
1650 
1651 
1652 static LRESULT
1653 MONTHCAL_SetSelRange(MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1654 {
1655   SYSTEMTIME old_range[2];
1656   INT diff;
1657 
1658   TRACE("%p\n", range);
1659 
1660   if(!range || !(infoPtr->dwStyle & MCS_MULTISELECT)) return FALSE;
1661 
1662   /* adjust timestamps */
1663   if(!MONTHCAL_ValidateTime(&range[0])) MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]);
1664   if(!MONTHCAL_ValidateTime(&range[1])) MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]);
1665 
1666   /* maximum range exceeded */
1667   if(!MONTHCAL_IsSelRangeValid(infoPtr, &range[0], &range[1], NULL)) return FALSE;
1668 
1669   old_range[0] = infoPtr->minSel;
1670   old_range[1] = infoPtr->maxSel;
1671 
1672   /* swap if min > max */
1673   if(MONTHCAL_CompareSystemTime(&range[0], &range[1]) <= 0)
1674   {
1675     infoPtr->minSel = range[0];
1676     infoPtr->maxSel = range[1];
1677   }
1678   else
1679   {
1680     infoPtr->minSel = range[1];
1681     infoPtr->maxSel = range[0];
1682   }
1683 
1684   diff = MONTHCAL_MonthDiff(&infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month, &infoPtr->maxSel);
1685   if (diff < 0)
1686   {
1687     diff = MONTHCAL_MonthDiff(&infoPtr->calendars[0].month, &infoPtr->maxSel);
1688     if (diff > 0) diff = 0;
1689   }
1690 
1691   if (diff != 0)
1692   {
1693     INT i;
1694 
1695     for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1696       MONTHCAL_GetMonth(&infoPtr->calendars[i].month, diff);
1697   }
1698 
1699   /* update day of week */
1700   MONTHCAL_CalculateDayOfWeek(&infoPtr->minSel, TRUE);
1701   MONTHCAL_CalculateDayOfWeek(&infoPtr->maxSel, TRUE);
1702 
1703   /* redraw if bounds changed */
1704   /* FIXME: no actual need to redraw everything */
1705   if(!MONTHCAL_IsDateEqual(&old_range[0], &range[0]) ||
1706      !MONTHCAL_IsDateEqual(&old_range[1], &range[1]))
1707   {
1708      InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1709   }
1710 
1711   TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
1712   return TRUE;
1713 }
1714 
1715 
1716 static LRESULT
1717 MONTHCAL_GetToday(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *today)
1718 {
1719   TRACE("%p\n", today);
1720 
1721   if(!today) return FALSE;
1722   *today = infoPtr->todaysDate;
1723   return TRUE;
1724 }
1725 
1726 /* Internal helper for MCM_SETTODAY handler and auto update timer handler
1727  *
1728  * RETURN VALUE
1729  *
1730  *  TRUE  - today date changed
1731  *  FALSE - today date isn't changed
1732  */
1733 static BOOL
1734 MONTHCAL_UpdateToday(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *today)
1735 {
1736     RECT rect;
1737 
1738     if (MONTHCAL_IsDateEqual(today, &infoPtr->todaysDate))
1739         return FALSE;
1740 
1741     /* Invalidate old and new today day rectangle, and today label. */
1742     if (MONTHCAL_GetDayRect(infoPtr, &infoPtr->todaysDate, &rect, -1))
1743         InvalidateRect(infoPtr->hwndSelf, &rect, FALSE);
1744 
1745     if (MONTHCAL_GetDayRect(infoPtr, today, &rect, -1))
1746         InvalidateRect(infoPtr->hwndSelf, &rect, FALSE);
1747 
1748     infoPtr->todaysDate = *today;
1749 
1750     InvalidateRect(infoPtr->hwndSelf, &infoPtr->todayrect, FALSE);
1751     return TRUE;
1752 }
1753 
1754 /* MCM_SETTODAT handler */
1755 static LRESULT
1756 MONTHCAL_SetToday(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *today)
1757 {
1758   TRACE("%p\n", today);
1759 
1760   if (today)
1761   {
1762     /* remember if date was set successfully */
1763     if (MONTHCAL_UpdateToday(infoPtr, today)) infoPtr->todaySet = TRUE;
1764   }
1765 
1766   return 0;
1767 }
1768 
1769 /* returns calendar index containing specified point, or -1 if it's background */
1770 static INT MONTHCAL_GetCalendarFromPoint(const MONTHCAL_INFO *infoPtr, const POINT *pt)
1771 {
1772   RECT r;
1773   INT i;
1774 
1775   for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1776   {
1777      /* whole bounding rectangle allows some optimization to compute */
1778      r.left   = infoPtr->calendars[i].title.left;
1779      r.top    = infoPtr->calendars[i].title.top;
1780      r.bottom = infoPtr->calendars[i].days.bottom;
1781      r.right  = infoPtr->calendars[i].days.right;
1782 
1783      if (PtInRect(&r, *pt)) return i;
1784   }
1785 
1786   return -1;
1787 }
1788 
1789 static inline UINT fill_hittest_info(const MCHITTESTINFO *src, MCHITTESTINFO *dest)
1790 {
1791   dest->uHit = src->uHit;
1792   dest->st = src->st;
1793 
1794   if (dest->cbSize == sizeof(MCHITTESTINFO))
1795     memcpy(&dest->rc, &src->rc, sizeof(MCHITTESTINFO) - MCHITTESTINFO_V1_SIZE);
1796 
1797   return src->uHit;
1798 }
1799 
1800 static LRESULT
1801 MONTHCAL_HitTest(const MONTHCAL_INFO *infoPtr, MCHITTESTINFO *lpht)
1802 {
1803   MCHITTESTINFO htinfo;
1804   SYSTEMTIME *ht_month;
1805   INT day, calIdx;
1806 
1807   if(!lpht || lpht->cbSize < MCHITTESTINFO_V1_SIZE) return -1;
1808 
1809   htinfo.st = st_null;
1810 
1811   /* we should preserve passed fields if hit area doesn't need them */
1812   if (lpht->cbSize == sizeof(MCHITTESTINFO))
1813     memcpy(&htinfo.rc, &lpht->rc, sizeof(MCHITTESTINFO) - MCHITTESTINFO_V1_SIZE);
1814 
1815   /* guess in what calendar we are */
1816   calIdx = MONTHCAL_GetCalendarFromPoint(infoPtr, &lpht->pt);
1817   if (calIdx == -1)
1818   {
1819     if (PtInRect(&infoPtr->todayrect, lpht->pt))
1820     {
1821       htinfo.uHit = MCHT_TODAYLINK;
1822       htinfo.rc = infoPtr->todayrect;
1823     }
1824     else
1825       /* outside of calendar area? What's left must be background :-) */
1826       htinfo.uHit = MCHT_CALENDARBK;
1827 
1828     return fill_hittest_info(&htinfo, lpht);
1829   }
1830 
1831   /* are we in the header? */
1832   if (PtInRect(&infoPtr->calendars[calIdx].title, lpht->pt)) {
1833     /* FIXME: buttons hittesting could be optimized cause maximum
1834               two calendars have buttons */
1835     if (calIdx == 0 && PtInRect(&infoPtr->titlebtnprev, lpht->pt))
1836     {
1837       htinfo.uHit = MCHT_TITLEBTNPREV;
1838       htinfo.rc = infoPtr->titlebtnprev;
1839     }
1840     else if (PtInRect(&infoPtr->titlebtnnext, lpht->pt))
1841     {
1842       htinfo.uHit = MCHT_TITLEBTNNEXT;
1843       htinfo.rc = infoPtr->titlebtnnext;
1844     }
1845     else if (PtInRect(&infoPtr->calendars[calIdx].titlemonth, lpht->pt))
1846     {
1847       htinfo.uHit = MCHT_TITLEMONTH;
1848       htinfo.rc = infoPtr->calendars[calIdx].titlemonth;
1849       htinfo.iOffset = calIdx;
1850     }
1851     else if (PtInRect(&infoPtr->calendars[calIdx].titleyear, lpht->pt))
1852     {
1853       htinfo.uHit = MCHT_TITLEYEAR;
1854       htinfo.rc = infoPtr->calendars[calIdx].titleyear;
1855       htinfo.iOffset = calIdx;
1856     }
1857     else
1858     {
1859       htinfo.uHit = MCHT_TITLE;
1860       htinfo.rc = infoPtr->calendars[calIdx].title;
1861       htinfo.iOffset = calIdx;
1862     }
1863 
1864     return fill_hittest_info(&htinfo, lpht);
1865   }
1866 
1867   ht_month = &infoPtr->calendars[calIdx].month;
1868   /* days area (including week days and week numbers) */
1869   day = MONTHCAL_GetDayFromPos(infoPtr, lpht->pt, calIdx);
1870   if (PtInRect(&infoPtr->calendars[calIdx].wdays, lpht->pt))
1871   {
1872     htinfo.uHit = MCHT_CALENDARDAY;
1873     htinfo.iOffset = calIdx;
1874     htinfo.st.wYear  = ht_month->wYear;
1875     htinfo.st.wMonth = (day < 1) ? ht_month->wMonth -1 : ht_month->wMonth;
1876     htinfo.st.wDay   = (day < 1) ?
1877       MONTHCAL_MonthLength(ht_month->wMonth-1, ht_month->wYear) - day : day;
1878 
1879     MONTHCAL_GetDayPos(infoPtr, &htinfo.st, &htinfo.iCol, &htinfo.iRow, calIdx);
1880   }
1881   else if(PtInRect(&infoPtr->calendars[calIdx].weeknums, lpht->pt))
1882   {
1883     htinfo.uHit = MCHT_CALENDARWEEKNUM;
1884     htinfo.st.wYear = ht_month->wYear;
1885     htinfo.iOffset = calIdx;
1886 
1887     if (day < 1)
1888     {
1889       htinfo.st.wMonth = ht_month->wMonth - 1;
1890       htinfo.st.wDay = MONTHCAL_MonthLength(ht_month->wMonth-1, ht_month->wYear) - day;
1891     }
1892     else if (day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear))
1893     {
1894       htinfo.st.wMonth = ht_month->wMonth + 1;
1895       htinfo.st.wDay = day - MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear);
1896     }
1897     else
1898     {
1899       htinfo.st.wMonth = ht_month->wMonth;
1900       htinfo.st.wDay = day;
1901     }
1902   }
1903   else if(PtInRect(&infoPtr->calendars[calIdx].days, lpht->pt))
1904   {
1905       htinfo.iOffset = calIdx;
1906       htinfo.st.wDay = ht_month->wDay;
1907       htinfo.st.wYear  = ht_month->wYear;
1908       htinfo.st.wMonth = ht_month->wMonth;
1909       /* previous month only valid for first calendar */
1910       if (day < 1 && calIdx == 0)
1911       {
1912 	  htinfo.uHit = MCHT_CALENDARDATEPREV;
1913 	  MONTHCAL_GetPrevMonth(&htinfo.st);
1914 	  htinfo.st.wDay = MONTHCAL_MonthLength(htinfo.st.wMonth, htinfo.st.wYear) + day;
1915       }
1916       /* next month only valid for last calendar */
1917       else if (day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear) &&
1918                calIdx == MONTHCAL_GetCalCount(infoPtr)-1)
1919       {
1920 	  htinfo.uHit = MCHT_CALENDARDATENEXT;
1921 	  MONTHCAL_GetNextMonth(&htinfo.st);
1922 	  htinfo.st.wDay = day - MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear);
1923       }
1924       /* multiple calendars case - blank areas for previous/next month */
1925       else if (day < 1 || day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear))
1926       {
1927           htinfo.uHit = MCHT_CALENDARBK;
1928       }
1929       else
1930       {
1931 	htinfo.uHit = MCHT_CALENDARDATE;
1932 	htinfo.st.wDay = day;
1933       }
1934 
1935       MONTHCAL_GetDayPos(infoPtr, &htinfo.st, &htinfo.iCol, &htinfo.iRow, calIdx);
1936       MONTHCAL_GetDayRectI(infoPtr, &htinfo.rc, htinfo.iCol, htinfo.iRow, calIdx);
1937       /* always update day of week */
1938       MONTHCAL_CalculateDayOfWeek(&htinfo.st, TRUE);
1939   }
1940 
1941   return fill_hittest_info(&htinfo, lpht);
1942 }
1943 
1944 /* MCN_GETDAYSTATE notification helper */
1945 static void MONTHCAL_NotifyDayState(MONTHCAL_INFO *infoPtr)
1946 {
1947   MONTHDAYSTATE *state;
1948   NMDAYSTATE nmds;
1949 
1950   if (!(infoPtr->dwStyle & MCS_DAYSTATE)) return;
1951 
1952   nmds.nmhdr.hwndFrom = infoPtr->hwndSelf;
1953   nmds.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1954   nmds.nmhdr.code     = MCN_GETDAYSTATE;
1955   nmds.cDayState      = MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0);
1956   nmds.prgDayState    = state = Alloc(nmds.cDayState * sizeof(MONTHDAYSTATE));
1957 
1958   MONTHCAL_GetMinDate(infoPtr, &nmds.stStart);
1959   nmds.stStart.wDay = 1;
1960 
1961   SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmds.nmhdr.idFrom, (LPARAM)&nmds);
1962   memcpy(infoPtr->monthdayState, nmds.prgDayState,
1963       MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)*sizeof(MONTHDAYSTATE));
1964 
1965   Free(state);
1966 }
1967 
1968 /* no valid range check performed */
1969 static void MONTHCAL_Scroll(MONTHCAL_INFO *infoPtr, INT delta, BOOL keep_selection)
1970 {
1971   INT i, selIdx = -1;
1972 
1973   for(i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1974   {
1975     /* save selection position to shift it later */
1976     if (selIdx == -1 && MONTHCAL_CompareMonths(&infoPtr->minSel, &infoPtr->calendars[i].month) == 0)
1977       selIdx = i;
1978 
1979     MONTHCAL_GetMonth(&infoPtr->calendars[i].month, delta);
1980   }
1981 
1982   if (keep_selection)
1983     return;
1984 
1985   /* selection is always shifted to first calendar */
1986   if (infoPtr->dwStyle & MCS_MULTISELECT)
1987   {
1988     SYSTEMTIME range[2];
1989 
1990     MONTHCAL_GetSelRange(infoPtr, range);
1991     MONTHCAL_GetMonth(&range[0], delta - selIdx);
1992     MONTHCAL_GetMonth(&range[1], delta - selIdx);
1993     MONTHCAL_SetSelRange(infoPtr, range);
1994   }
1995   else
1996   {
1997     SYSTEMTIME st = infoPtr->minSel;
1998 
1999     MONTHCAL_GetMonth(&st, delta - selIdx);
2000     MONTHCAL_SetCurSel(infoPtr, &st);
2001   }
2002 }
2003 
2004 static void MONTHCAL_GoToMonth(MONTHCAL_INFO *infoPtr, enum nav_direction direction)
2005 {
2006   INT delta = infoPtr->delta ? infoPtr->delta : MONTHCAL_GetCalCount(infoPtr);
2007   BOOL keep_selection;
2008   SYSTEMTIME st;
2009 
2010   TRACE("%s\n", direction == DIRECTION_BACKWARD ? "back" : "fwd");
2011 
2012   /* check if change allowed by range set */
2013   if(direction == DIRECTION_BACKWARD)
2014   {
2015     st = infoPtr->calendars[0].month;
2016     MONTHCAL_GetMonth(&st, -delta);
2017   }
2018   else
2019   {
2020     st = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
2021     MONTHCAL_GetMonth(&st, delta);
2022   }
2023 
2024   if(!MONTHCAL_IsDateInValidRange(infoPtr, &st, FALSE)) return;
2025 
2026   keep_selection = infoPtr->dwStyle & MCS_NOSELCHANGEONNAV;
2027   MONTHCAL_Scroll(infoPtr, direction == DIRECTION_BACKWARD ? -delta : delta, keep_selection);
2028   MONTHCAL_NotifyDayState(infoPtr);
2029   if (!keep_selection)
2030     MONTHCAL_NotifySelectionChange(infoPtr);
2031 }
2032 
2033 static LRESULT
2034 MONTHCAL_RButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2035 {
2036   HMENU hMenu;
2037   POINT menupoint;
2038   WCHAR buf[32];
2039 
2040   hMenu = CreatePopupMenu();
2041   LoadStringW(COMCTL32_hModule, IDM_GOTODAY, buf, countof(buf));
2042   AppendMenuW(hMenu, MF_STRING|MF_ENABLED, 1, buf);
2043   menupoint.x = (short)LOWORD(lParam);
2044   menupoint.y = (short)HIWORD(lParam);
2045   ClientToScreen(infoPtr->hwndSelf, &menupoint);
2046   if( TrackPopupMenu(hMenu, TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD,
2047 		     menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL))
2048   {
2049       if (infoPtr->dwStyle & MCS_MULTISELECT)
2050       {
2051           SYSTEMTIME range[2];
2052 
2053           range[0] = range[1] = infoPtr->todaysDate;
2054           MONTHCAL_SetSelRange(infoPtr, range);
2055       }
2056       else
2057           MONTHCAL_SetCurSel(infoPtr, &infoPtr->todaysDate);
2058 
2059       MONTHCAL_NotifySelectionChange(infoPtr);
2060       MONTHCAL_NotifySelect(infoPtr);
2061   }
2062 
2063   return 0;
2064 }
2065 
2066 /***
2067  * DESCRIPTION:
2068  * Subclassed edit control windproc function
2069  *
2070  * PARAMETER(S):
2071  * [I] hwnd : the edit window handle
2072  * [I] uMsg : the message that is to be processed
2073  * [I] wParam : first message parameter
2074  * [I] lParam : second message parameter
2075  *
2076  */
2077 static LRESULT CALLBACK EditWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2078 {
2079     MONTHCAL_INFO *infoPtr = (MONTHCAL_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
2080 
2081     TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx)\n",
2082 	  hwnd, uMsg, wParam, lParam);
2083 
2084     switch (uMsg)
2085     {
2086 	case WM_GETDLGCODE:
2087 	  return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
2088 
2089 	case WM_DESTROY:
2090 	{
2091 	    WNDPROC editProc = infoPtr->EditWndProc;
2092 	    infoPtr->EditWndProc = NULL;
2093 	    SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
2094 	    return CallWindowProcW(editProc, hwnd, uMsg, wParam, lParam);
2095 	}
2096 
2097 	case WM_KILLFOCUS:
2098 	    break;
2099 
2100 	case WM_KEYDOWN:
2101 	    if ((VK_ESCAPE == (INT)wParam) || (VK_RETURN == (INT)wParam))
2102 		break;
2103 
2104 	default:
2105 	    return CallWindowProcW(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam);
2106     }
2107 
2108     SendMessageW(infoPtr->hWndYearUpDown, WM_CLOSE, 0, 0);
2109     SendMessageW(hwnd, WM_CLOSE, 0, 0);
2110     return 0;
2111 }
2112 
2113 /* creates updown control and edit box */
2114 static void MONTHCAL_EditYear(MONTHCAL_INFO *infoPtr, INT calIdx)
2115 {
2116     RECT *rc = &infoPtr->calendars[calIdx].titleyear;
2117     RECT *title = &infoPtr->calendars[calIdx].title;
2118 
2119     infoPtr->hWndYearEdit =
2120 	CreateWindowExW(0, WC_EDITW, 0, WS_VISIBLE | WS_CHILD | ES_READONLY,
2121 			rc->left + 3, (title->bottom + title->top - infoPtr->textHeight) / 2,
2122 			rc->right - rc->left + 4,
2123 			infoPtr->textHeight, infoPtr->hwndSelf,
2124 			NULL, NULL, NULL);
2125 
2126     SendMessageW(infoPtr->hWndYearEdit, WM_SETFONT, (WPARAM)infoPtr->hBoldFont, TRUE);
2127 
2128     infoPtr->hWndYearUpDown =
2129 	CreateWindowExW(0, UPDOWN_CLASSW, 0,
2130 			WS_VISIBLE | WS_CHILD | UDS_SETBUDDYINT | UDS_NOTHOUSANDS | UDS_ARROWKEYS,
2131 			rc->right + 7, (title->bottom + title->top - infoPtr->textHeight) / 2,
2132 			18, infoPtr->textHeight, infoPtr->hwndSelf,
2133 			NULL, NULL, NULL);
2134 
2135     /* attach edit box */
2136     SendMessageW(infoPtr->hWndYearUpDown, UDM_SETRANGE, 0,
2137                  MAKELONG(max_allowed_date.wYear, min_allowed_date.wYear));
2138     SendMessageW(infoPtr->hWndYearUpDown, UDM_SETBUDDY, (WPARAM)infoPtr->hWndYearEdit, 0);
2139     SendMessageW(infoPtr->hWndYearUpDown, UDM_SETPOS, 0, infoPtr->calendars[calIdx].month.wYear);
2140 
2141     /* subclass edit box */
2142     infoPtr->EditWndProc = (WNDPROC)SetWindowLongPtrW(infoPtr->hWndYearEdit,
2143                                   GWLP_WNDPROC, (DWORD_PTR)EditWndProc);
2144 
2145     SetFocus(infoPtr->hWndYearEdit);
2146 }
2147 
2148 static LRESULT
2149 MONTHCAL_LButtonDown(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2150 {
2151   MCHITTESTINFO ht;
2152   DWORD hit;
2153 
2154   /* Actually we don't need input focus for calendar, this is used to kill
2155      year updown and its buddy edit box */
2156   if (IsWindow(infoPtr->hWndYearUpDown))
2157   {
2158       SetFocus(infoPtr->hwndSelf);
2159       return 0;
2160   }
2161 
2162   SetCapture(infoPtr->hwndSelf);
2163 
2164   ht.cbSize = sizeof(MCHITTESTINFO);
2165   ht.pt.x = (short)LOWORD(lParam);
2166   ht.pt.y = (short)HIWORD(lParam);
2167 
2168   hit = MONTHCAL_HitTest(infoPtr, &ht);
2169 
2170   TRACE("%x at %s\n", hit, wine_dbgstr_point(&ht.pt));
2171 
2172   switch(hit)
2173   {
2174   case MCHT_TITLEBTNNEXT:
2175     MONTHCAL_GoToMonth(infoPtr, DIRECTION_FORWARD);
2176     infoPtr->status = MC_NEXTPRESSED;
2177     SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0);
2178     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2179     return 0;
2180 
2181   case MCHT_TITLEBTNPREV:
2182     MONTHCAL_GoToMonth(infoPtr, DIRECTION_BACKWARD);
2183     infoPtr->status = MC_PREVPRESSED;
2184     SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0);
2185     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2186     return 0;
2187 
2188   case MCHT_TITLEMONTH:
2189   {
2190     HMENU hMenu = CreatePopupMenu();
2191     WCHAR buf[32];
2192     POINT menupoint;
2193     INT i;
2194 
2195     for (i = 0; i < 12; i++)
2196     {
2197 	GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+i, buf, countof(buf));
2198 	AppendMenuW(hMenu, MF_STRING|MF_ENABLED, i + 1, buf);
2199     }
2200     menupoint.x = ht.pt.x;
2201     menupoint.y = ht.pt.y;
2202     ClientToScreen(infoPtr->hwndSelf, &menupoint);
2203     i = TrackPopupMenu(hMenu,TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON | TPM_RETURNCMD,
2204 		       menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL);
2205 
2206     if ((i > 0) && (i < 13) && infoPtr->calendars[ht.iOffset].month.wMonth != i)
2207     {
2208         INT delta = i - infoPtr->calendars[ht.iOffset].month.wMonth;
2209         SYSTEMTIME st;
2210 
2211         /* check if change allowed by range set */
2212         st = delta < 0 ? infoPtr->calendars[0].month :
2213                          infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
2214         MONTHCAL_GetMonth(&st, delta);
2215 
2216         if (MONTHCAL_IsDateInValidRange(infoPtr, &st, FALSE))
2217         {
2218             MONTHCAL_Scroll(infoPtr, delta, FALSE);
2219             MONTHCAL_NotifyDayState(infoPtr);
2220             MONTHCAL_NotifySelectionChange(infoPtr);
2221             InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2222         }
2223     }
2224     return 0;
2225   }
2226   case MCHT_TITLEYEAR:
2227   {
2228     MONTHCAL_EditYear(infoPtr, ht.iOffset);
2229     return 0;
2230   }
2231   case MCHT_TODAYLINK:
2232   {
2233     if (infoPtr->dwStyle & MCS_MULTISELECT)
2234     {
2235         SYSTEMTIME range[2];
2236 
2237         range[0] = range[1] = infoPtr->todaysDate;
2238         MONTHCAL_SetSelRange(infoPtr, range);
2239     }
2240     else
2241         MONTHCAL_SetCurSel(infoPtr, &infoPtr->todaysDate);
2242 
2243     MONTHCAL_NotifySelectionChange(infoPtr);
2244     MONTHCAL_NotifySelect(infoPtr);
2245     return 0;
2246   }
2247   case MCHT_CALENDARDATENEXT:
2248   case MCHT_CALENDARDATEPREV:
2249   case MCHT_CALENDARDATE:
2250   {
2251     SYSTEMTIME st[2];
2252 
2253     MONTHCAL_CopyDate(&ht.st, &infoPtr->firstSel);
2254 
2255     st[0] = st[1] = ht.st;
2256     /* clear selection range */
2257     MONTHCAL_SetSelRange(infoPtr, st);
2258 
2259     infoPtr->status = MC_SEL_LBUTDOWN;
2260     MONTHCAL_SetDayFocus(infoPtr, &ht.st);
2261     return 0;
2262   }
2263   }
2264 
2265   return 1;
2266 }
2267 
2268 
2269 static LRESULT
2270 MONTHCAL_LButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2271 {
2272   NMHDR nmhdr;
2273   MCHITTESTINFO ht;
2274   DWORD hit;
2275 
2276   TRACE("\n");
2277 
2278   if(infoPtr->status & (MC_PREVPRESSED | MC_NEXTPRESSED)) {
2279     RECT *r;
2280 
2281     KillTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER);
2282     r = infoPtr->status & MC_PREVPRESSED ? &infoPtr->titlebtnprev : &infoPtr->titlebtnnext;
2283     infoPtr->status &= ~(MC_PREVPRESSED | MC_NEXTPRESSED);
2284 
2285     InvalidateRect(infoPtr->hwndSelf, r, FALSE);
2286   }
2287 
2288   ReleaseCapture();
2289 
2290   /* always send NM_RELEASEDCAPTURE notification */
2291   nmhdr.hwndFrom = infoPtr->hwndSelf;
2292   nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
2293   nmhdr.code     = NM_RELEASEDCAPTURE;
2294   TRACE("Sent notification from %p to %p\n", infoPtr->hwndSelf, infoPtr->hwndNotify);
2295 
2296   SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmhdr.idFrom, (LPARAM)&nmhdr);
2297 
2298   if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
2299 
2300   ht.cbSize = sizeof(MCHITTESTINFO);
2301   ht.pt.x = (short)LOWORD(lParam);
2302   ht.pt.y = (short)HIWORD(lParam);
2303   hit = MONTHCAL_HitTest(infoPtr, &ht);
2304 
2305   infoPtr->status = MC_SEL_LBUTUP;
2306   MONTHCAL_SetDayFocus(infoPtr, NULL);
2307 
2308   if((hit & MCHT_CALENDARDATE) == MCHT_CALENDARDATE)
2309   {
2310     SYSTEMTIME sel = infoPtr->minSel;
2311 
2312     /* will be invalidated here */
2313     MONTHCAL_SetCurSel(infoPtr, &ht.st);
2314 
2315     /* send MCN_SELCHANGE only if new date selected */
2316     if (!MONTHCAL_IsDateEqual(&sel, &ht.st))
2317         MONTHCAL_NotifySelectionChange(infoPtr);
2318 
2319     MONTHCAL_NotifySelect(infoPtr);
2320   }
2321 
2322   return 0;
2323 }
2324 
2325 
2326 static LRESULT
2327 MONTHCAL_Timer(MONTHCAL_INFO *infoPtr, WPARAM id)
2328 {
2329   TRACE("%ld\n", id);
2330 
2331   switch(id) {
2332   case MC_PREVNEXTMONTHTIMER:
2333     if(infoPtr->status & MC_NEXTPRESSED) MONTHCAL_GoToMonth(infoPtr, DIRECTION_FORWARD);
2334     if(infoPtr->status & MC_PREVPRESSED) MONTHCAL_GoToMonth(infoPtr, DIRECTION_BACKWARD);
2335     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2336     break;
2337   case MC_TODAYUPDATETIMER:
2338   {
2339     SYSTEMTIME st;
2340 
2341     if(infoPtr->todaySet) return 0;
2342 
2343     GetLocalTime(&st);
2344     MONTHCAL_UpdateToday(infoPtr, &st);
2345 
2346     /* notification sent anyway */
2347     MONTHCAL_NotifySelectionChange(infoPtr);
2348 
2349     return 0;
2350   }
2351   default:
2352     ERR("got unknown timer %ld\n", id);
2353     break;
2354   }
2355 
2356   return 0;
2357 }
2358 
2359 
2360 static LRESULT
2361 MONTHCAL_MouseMove(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2362 {
2363   MCHITTESTINFO ht;
2364   SYSTEMTIME st_ht;
2365   INT hit;
2366   RECT r;
2367 
2368   if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
2369 
2370   ht.cbSize = sizeof(MCHITTESTINFO);
2371   ht.pt.x = (short)LOWORD(lParam);
2372   ht.pt.y = (short)HIWORD(lParam);
2373   ht.iOffset = -1;
2374 
2375   hit = MONTHCAL_HitTest(infoPtr, &ht);
2376 
2377   /* not on the calendar date numbers? bail out */
2378   TRACE("hit:%x\n",hit);
2379   if((hit & MCHT_CALENDARDATE) != MCHT_CALENDARDATE)
2380   {
2381     MONTHCAL_SetDayFocus(infoPtr, NULL);
2382     return 0;
2383   }
2384 
2385   st_ht = ht.st;
2386 
2387   /* if pointer is over focused day still there's nothing to do */
2388   if(!MONTHCAL_SetDayFocus(infoPtr, &ht.st)) return 0;
2389 
2390   MONTHCAL_GetDayRect(infoPtr, &ht.st, &r, ht.iOffset);
2391 
2392   if(infoPtr->dwStyle & MCS_MULTISELECT) {
2393     SYSTEMTIME st[2];
2394 
2395     MONTHCAL_GetSelRange(infoPtr, st);
2396 
2397     /* If we're still at the first selected date and range is empty, return.
2398        If range isn't empty we should change range to a single firstSel */
2399     if(MONTHCAL_IsDateEqual(&infoPtr->firstSel, &st_ht) &&
2400        MONTHCAL_IsDateEqual(&st[0], &st[1])) goto done;
2401 
2402     MONTHCAL_IsSelRangeValid(infoPtr, &st_ht, &infoPtr->firstSel, &st_ht);
2403 
2404     st[0] = infoPtr->firstSel;
2405     /* we should overwrite timestamp here */
2406     MONTHCAL_CopyDate(&st_ht, &st[1]);
2407 
2408     /* bounds will be swapped here if needed */
2409     MONTHCAL_SetSelRange(infoPtr, st);
2410 
2411     return 0;
2412   }
2413 
2414 done:
2415 
2416   /* FIXME: this should specify a rectangle containing only the days that changed
2417      using InvalidateRect */
2418   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2419 
2420   return 0;
2421 }
2422 
2423 
2424 static LRESULT
2425 MONTHCAL_Paint(MONTHCAL_INFO *infoPtr, HDC hdc_paint)
2426 {
2427   HDC hdc;
2428   PAINTSTRUCT ps;
2429 
2430   if (hdc_paint)
2431   {
2432     GetClientRect(infoPtr->hwndSelf, &ps.rcPaint);
2433     hdc = hdc_paint;
2434   }
2435   else
2436     hdc = BeginPaint(infoPtr->hwndSelf, &ps);
2437 
2438   MONTHCAL_Refresh(infoPtr, hdc, &ps);
2439   if (!hdc_paint) EndPaint(infoPtr->hwndSelf, &ps);
2440   return 0;
2441 }
2442 
2443 static LRESULT
2444 MONTHCAL_EraseBkgnd(const MONTHCAL_INFO *infoPtr, HDC hdc)
2445 {
2446   RECT rc;
2447 
2448   if (!GetClipBox(hdc, &rc)) return FALSE;
2449 
2450   FillRect(hdc, &rc, infoPtr->brushes[BrushBackground]);
2451 
2452   return TRUE;
2453 }
2454 
2455 static LRESULT
2456 MONTHCAL_PrintClient(MONTHCAL_INFO *infoPtr, HDC hdc, DWORD options)
2457 {
2458   FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
2459 
2460   if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
2461       return 0;
2462 
2463   if (options & PRF_ERASEBKGND)
2464       MONTHCAL_EraseBkgnd(infoPtr, hdc);
2465 
2466   if (options & PRF_CLIENT)
2467       MONTHCAL_Paint(infoPtr, hdc);
2468 
2469   return 0;
2470 }
2471 
2472 static LRESULT
2473 MONTHCAL_SetFocus(const MONTHCAL_INFO *infoPtr)
2474 {
2475   TRACE("\n");
2476 
2477   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2478 
2479   return 0;
2480 }
2481 
2482 /* sets the size information */
2483 static void MONTHCAL_UpdateSize(MONTHCAL_INFO *infoPtr)
2484 {
2485   static const WCHAR O0W[] = { '0','0',0 };
2486   RECT *title=&infoPtr->calendars[0].title;
2487   RECT *prev=&infoPtr->titlebtnprev;
2488   RECT *next=&infoPtr->titlebtnnext;
2489   RECT *titlemonth=&infoPtr->calendars[0].titlemonth;
2490   RECT *titleyear=&infoPtr->calendars[0].titleyear;
2491   RECT *wdays=&infoPtr->calendars[0].wdays;
2492   RECT *weeknumrect=&infoPtr->calendars[0].weeknums;
2493   RECT *days=&infoPtr->calendars[0].days;
2494   RECT *todayrect=&infoPtr->todayrect;
2495 
2496   INT xdiv, dx, dy, i, j, x, y, c_dx, c_dy;
2497   WCHAR buff[80];
2498   TEXTMETRICW tm;
2499   INT day_width;
2500   RECT client;
2501   HFONT font;
2502   SIZE size;
2503   HDC hdc;
2504 
2505   GetClientRect(infoPtr->hwndSelf, &client);
2506 
2507   hdc = GetDC(infoPtr->hwndSelf);
2508   font = SelectObject(hdc, infoPtr->hFont);
2509 
2510   /* get the height and width of each day's text */
2511   GetTextMetricsW(hdc, &tm);
2512   infoPtr->textHeight = tm.tmHeight + tm.tmExternalLeading + tm.tmInternalLeading;
2513 
2514   /* find widest day name for current locale and font */
2515   day_width = 0;
2516   for (i = 0; i < 7; i++)
2517   {
2518       SIZE sz;
2519 
2520       if (get_localized_dayname(infoPtr, i, buff, countof(buff)))
2521       {
2522           GetTextExtentPoint32W(hdc, buff, lstrlenW(buff), &sz);
2523           if (sz.cx > day_width) day_width = sz.cx;
2524       }
2525       else /* locale independent fallback on failure */
2526       {
2527           static const WCHAR sunW[] = { 'S','u','n' };
2528           GetTextExtentPoint32W(hdc, sunW, countof(sunW), &sz);
2529           day_width = sz.cx;
2530           break;
2531       }
2532   }
2533 
2534   day_width += 2;
2535 
2536   /* recalculate the height and width increments and offsets */
2537   size.cx = 0;
2538   GetTextExtentPoint32W(hdc, O0W, 2, &size);
2539 
2540   /* restore the originally selected font */
2541   SelectObject(hdc, font);
2542   ReleaseDC(infoPtr->hwndSelf, hdc);
2543 
2544   xdiv = (infoPtr->dwStyle & MCS_WEEKNUMBERS) ? 8 : 7;
2545 
2546   infoPtr->width_increment  = max(day_width, size.cx * 2 + 4);
2547   infoPtr->height_increment = infoPtr->textHeight;
2548 
2549   /* calculate title area */
2550   title->top    = 0;
2551   title->bottom = 3 * infoPtr->height_increment / 2;
2552   title->left   = 0;
2553   title->right  = infoPtr->width_increment * xdiv;
2554 
2555   /* set the dimensions of the next and previous buttons and center */
2556   /* the month text vertically */
2557   prev->top    = next->top    = title->top + 4;
2558   prev->bottom = next->bottom = title->bottom - 4;
2559   prev->left   = title->left + 4;
2560   prev->right  = prev->left + (title->bottom - title->top);
2561   next->right  = title->right - 4;
2562   next->left   = next->right - (title->bottom - title->top);
2563 
2564   /* titlemonth->left and right change based upon the current month
2565      and are recalculated in refresh as the current month may change
2566      without the control being resized */
2567   titlemonth->top    = titleyear->top    = title->top    + (infoPtr->height_increment)/2;
2568   titlemonth->bottom = titleyear->bottom = title->bottom - (infoPtr->height_increment)/2;
2569 
2570   /* week numbers */
2571   weeknumrect->left  = 0;
2572   weeknumrect->right = infoPtr->dwStyle & MCS_WEEKNUMBERS ? prev->right : 0;
2573 
2574   /* days abbreviated names */
2575   wdays->left   = days->left   = weeknumrect->right;
2576   wdays->right  = days->right  = wdays->left + 7 * infoPtr->width_increment;
2577   wdays->top    = title->bottom;
2578   wdays->bottom = wdays->top + infoPtr->height_increment;
2579 
2580   days->top    = weeknumrect->top = wdays->bottom;
2581   days->bottom = weeknumrect->bottom = days->top + 6 * infoPtr->height_increment;
2582 
2583   todayrect->left   = 0;
2584   todayrect->right  = title->right;
2585   todayrect->top    = days->bottom;
2586   todayrect->bottom = days->bottom + infoPtr->height_increment;
2587 
2588   /* compute calendar count, update all calendars */
2589   x = (client.right  + MC_CALENDAR_PADDING) / (title->right - title->left + MC_CALENDAR_PADDING);
2590   /* today label affects whole height */
2591   if (infoPtr->dwStyle & MCS_NOTODAY)
2592     y = (client.bottom + MC_CALENDAR_PADDING) / (days->bottom - title->top + MC_CALENDAR_PADDING);
2593   else
2594     y = (client.bottom - todayrect->bottom + todayrect->top + MC_CALENDAR_PADDING) /
2595          (days->bottom - title->top + MC_CALENDAR_PADDING);
2596 
2597   /* TODO: ensure that count is properly adjusted to fit 12 months constraint */
2598   if (x == 0) x = 1;
2599   if (y == 0) y = 1;
2600 
2601   if (x*y != MONTHCAL_GetCalCount(infoPtr))
2602   {
2603       infoPtr->dim.cx = x;
2604       infoPtr->dim.cy = y;
2605       infoPtr->calendars = ReAlloc(infoPtr->calendars, MONTHCAL_GetCalCount(infoPtr)*sizeof(CALENDAR_INFO));
2606 
2607       infoPtr->monthdayState = ReAlloc(infoPtr->monthdayState,
2608           MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)*sizeof(MONTHDAYSTATE));
2609       MONTHCAL_NotifyDayState(infoPtr);
2610 
2611       /* update pointers that we'll need */
2612       title = &infoPtr->calendars[0].title;
2613       wdays = &infoPtr->calendars[0].wdays;
2614       days  = &infoPtr->calendars[0].days;
2615   }
2616 
2617   for (i = 1; i < MONTHCAL_GetCalCount(infoPtr); i++)
2618   {
2619       /* set months */
2620       infoPtr->calendars[i] = infoPtr->calendars[0];
2621       MONTHCAL_GetMonth(&infoPtr->calendars[i].month, i);
2622   }
2623 
2624   /* offset all rectangles to center in client area */
2625   c_dx = (client.right  - x * title->right - MC_CALENDAR_PADDING * (x-1)) / 2;
2626   c_dy = (client.bottom - y * todayrect->bottom - MC_CALENDAR_PADDING * (y-1)) / 2;
2627 
2628   /* if calendar doesn't fit client area show it at left/top bounds */
2629   if (title->left + c_dx < 0) c_dx = 0;
2630   if (title->top  + c_dy < 0) c_dy = 0;
2631 
2632   for (i = 0; i < y; i++)
2633   {
2634       for (j = 0; j < x; j++)
2635       {
2636           dx = j*(title->right - title->left + MC_CALENDAR_PADDING) + c_dx;
2637           dy = i*(days->bottom - title->top  + MC_CALENDAR_PADDING) + c_dy;
2638 
2639           OffsetRect(&infoPtr->calendars[i*x+j].title, dx, dy);
2640           OffsetRect(&infoPtr->calendars[i*x+j].titlemonth, dx, dy);
2641           OffsetRect(&infoPtr->calendars[i*x+j].titleyear, dx, dy);
2642           OffsetRect(&infoPtr->calendars[i*x+j].wdays, dx, dy);
2643           OffsetRect(&infoPtr->calendars[i*x+j].weeknums, dx, dy);
2644           OffsetRect(&infoPtr->calendars[i*x+j].days, dx, dy);
2645       }
2646   }
2647 
2648   OffsetRect(prev, c_dx, c_dy);
2649   OffsetRect(next, (x-1)*(title->right - title->left + MC_CALENDAR_PADDING) + c_dx, c_dy);
2650 
2651   i = infoPtr->dim.cx * infoPtr->dim.cy - infoPtr->dim.cx;
2652   todayrect->left   = infoPtr->calendars[i].title.left;
2653   todayrect->right  = infoPtr->calendars[i].title.right;
2654   todayrect->top    = infoPtr->calendars[i].days.bottom;
2655   todayrect->bottom = infoPtr->calendars[i].days.bottom + infoPtr->height_increment;
2656 
2657   TRACE("dx=%d dy=%d client[%s] title[%s] wdays[%s] days[%s] today[%s]\n",
2658 	infoPtr->width_increment,infoPtr->height_increment,
2659         wine_dbgstr_rect(&client),
2660         wine_dbgstr_rect(title),
2661         wine_dbgstr_rect(wdays),
2662         wine_dbgstr_rect(days),
2663         wine_dbgstr_rect(todayrect));
2664 }
2665 
2666 static LRESULT MONTHCAL_Size(MONTHCAL_INFO *infoPtr, int Width, int Height)
2667 {
2668   TRACE("(width=%d, height=%d)\n", Width, Height);
2669 
2670   MONTHCAL_UpdateSize(infoPtr);
2671   InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
2672 
2673   return 0;
2674 }
2675 
2676 static LRESULT MONTHCAL_GetFont(const MONTHCAL_INFO *infoPtr)
2677 {
2678     return (LRESULT)infoPtr->hFont;
2679 }
2680 
2681 static LRESULT MONTHCAL_SetFont(MONTHCAL_INFO *infoPtr, HFONT hFont, BOOL redraw)
2682 {
2683     HFONT hOldFont;
2684     LOGFONTW lf;
2685 
2686     if (!hFont) return 0;
2687 
2688     hOldFont = infoPtr->hFont;
2689     infoPtr->hFont = hFont;
2690 
2691     GetObjectW(infoPtr->hFont, sizeof(lf), &lf);
2692     lf.lfWeight = FW_BOLD;
2693     infoPtr->hBoldFont = CreateFontIndirectW(&lf);
2694 
2695     MONTHCAL_UpdateSize(infoPtr);
2696 
2697     if (redraw)
2698         InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2699 
2700     return (LRESULT)hOldFont;
2701 }
2702 
2703 /* update theme after a WM_THEMECHANGED message */
2704 static LRESULT theme_changed (const MONTHCAL_INFO* infoPtr)
2705 {
2706     HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
2707     CloseThemeData (theme);
2708     OpenThemeData (infoPtr->hwndSelf, themeClass);
2709     return 0;
2710 }
2711 
2712 static INT MONTHCAL_StyleChanged(MONTHCAL_INFO *infoPtr, WPARAM wStyleType,
2713                                  const STYLESTRUCT *lpss)
2714 {
2715     TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
2716           wStyleType, lpss->styleOld, lpss->styleNew);
2717 
2718     if (wStyleType != GWL_STYLE) return 0;
2719 
2720     infoPtr->dwStyle = lpss->styleNew;
2721 
2722     /* make room for week numbers */
2723     if ((lpss->styleNew ^ lpss->styleOld) & (MCS_WEEKNUMBERS | MCS_SHORTDAYSOFWEEK))
2724         MONTHCAL_UpdateSize(infoPtr);
2725 
2726     return 0;
2727 }
2728 
2729 static INT MONTHCAL_StyleChanging(MONTHCAL_INFO *infoPtr, WPARAM wStyleType,
2730                                   STYLESTRUCT *lpss)
2731 {
2732     TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
2733           wStyleType, lpss->styleOld, lpss->styleNew);
2734 
2735     /* block MCS_MULTISELECT change */
2736     if ((lpss->styleNew ^ lpss->styleOld) & MCS_MULTISELECT)
2737     {
2738         if (lpss->styleOld & MCS_MULTISELECT)
2739             lpss->styleNew |= MCS_MULTISELECT;
2740         else
2741             lpss->styleNew &= ~MCS_MULTISELECT;
2742     }
2743 
2744     /* block MCS_DAYSTATE change */
2745     if ((lpss->styleNew ^ lpss->styleOld) & MCS_DAYSTATE)
2746     {
2747         if (lpss->styleOld & MCS_DAYSTATE)
2748             lpss->styleNew |= MCS_DAYSTATE;
2749         else
2750             lpss->styleNew &= ~MCS_DAYSTATE;
2751     }
2752 
2753     return 0;
2754 }
2755 
2756 /* FIXME: check whether dateMin/dateMax need to be adjusted. */
2757 static LRESULT
2758 MONTHCAL_Create(HWND hwnd, LPCREATESTRUCTW lpcs)
2759 {
2760   MONTHCAL_INFO *infoPtr;
2761 
2762   /* allocate memory for info structure */
2763   infoPtr = Alloc(sizeof(MONTHCAL_INFO));
2764   SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2765 
2766   if (infoPtr == NULL) {
2767     ERR("could not allocate info memory!\n");
2768     return 0;
2769   }
2770 
2771   infoPtr->hwndSelf = hwnd;
2772   infoPtr->hwndNotify = lpcs->hwndParent;
2773   infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
2774   infoPtr->dim.cx = infoPtr->dim.cy = 1;
2775   infoPtr->calendars = Alloc(sizeof(CALENDAR_INFO));
2776   if (!infoPtr->calendars) goto fail;
2777   infoPtr->monthdayState = Alloc(3*sizeof(MONTHDAYSTATE));
2778   if (!infoPtr->monthdayState) goto fail;
2779 
2780   /* initialize info structure */
2781   /* FIXME: calculate systemtime ->> localtime(subtract timezoneinfo) */
2782 
2783   GetLocalTime(&infoPtr->todaysDate);
2784   MONTHCAL_SetFirstDayOfWeek(infoPtr, -1);
2785 
2786   infoPtr->maxSelCount   = (infoPtr->dwStyle & MCS_MULTISELECT) ? 7 : 1;
2787 
2788   infoPtr->colors[MCSC_BACKGROUND]   = comctl32_color.clrWindow;
2789   infoPtr->colors[MCSC_TEXT]         = comctl32_color.clrWindowText;
2790   infoPtr->colors[MCSC_TITLEBK]      = comctl32_color.clrActiveCaption;
2791   infoPtr->colors[MCSC_TITLETEXT]    = comctl32_color.clrWindow;
2792   infoPtr->colors[MCSC_MONTHBK]      = comctl32_color.clrWindow;
2793   infoPtr->colors[MCSC_TRAILINGTEXT] = comctl32_color.clrGrayText;
2794 
2795   infoPtr->brushes[BrushBackground]  = CreateSolidBrush(infoPtr->colors[MCSC_BACKGROUND]);
2796   infoPtr->brushes[BrushTitle]       = CreateSolidBrush(infoPtr->colors[MCSC_TITLEBK]);
2797   infoPtr->brushes[BrushMonth]       = CreateSolidBrush(infoPtr->colors[MCSC_MONTHBK]);
2798 
2799   infoPtr->pens[PenRed]  = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
2800   infoPtr->pens[PenText] = CreatePen(PS_SOLID, 1, infoPtr->colors[MCSC_TEXT]);
2801 
2802   infoPtr->minSel = infoPtr->todaysDate;
2803   infoPtr->maxSel = infoPtr->todaysDate;
2804   infoPtr->calendars[0].month = infoPtr->todaysDate;
2805   infoPtr->isUnicode = TRUE;
2806 
2807   /* setup control layout and day state data */
2808   MONTHCAL_UpdateSize(infoPtr);
2809 
2810   /* today auto update timer, to be freed only on control destruction */
2811   SetTimer(infoPtr->hwndSelf, MC_TODAYUPDATETIMER, MC_TODAYUPDATEDELAY, 0);
2812 
2813   OpenThemeData (infoPtr->hwndSelf, themeClass);
2814 
2815   return 0;
2816 
2817 fail:
2818   Free(infoPtr->monthdayState);
2819   Free(infoPtr->calendars);
2820   Free(infoPtr);
2821   return 0;
2822 }
2823 
2824 static LRESULT
2825 MONTHCAL_Destroy(MONTHCAL_INFO *infoPtr)
2826 {
2827   INT i;
2828 
2829   /* free month calendar info data */
2830   Free(infoPtr->monthdayState);
2831   Free(infoPtr->calendars);
2832   SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
2833 
2834   CloseThemeData (GetWindowTheme (infoPtr->hwndSelf));
2835 
2836   for (i = 0; i < BrushLast; i++) DeleteObject(infoPtr->brushes[i]);
2837   for (i = 0; i < PenLast; i++) DeleteObject(infoPtr->pens[i]);
2838 
2839   Free(infoPtr);
2840   return 0;
2841 }
2842 
2843 /*
2844  * Handler for WM_NOTIFY messages
2845  */
2846 static LRESULT
2847 MONTHCAL_Notify(MONTHCAL_INFO *infoPtr, NMHDR *hdr)
2848 {
2849   /* notification from year edit updown */
2850   if (hdr->code == UDN_DELTAPOS)
2851   {
2852     NMUPDOWN *nmud = (NMUPDOWN*)hdr;
2853 
2854     if (hdr->hwndFrom == infoPtr->hWndYearUpDown && nmud->iDelta)
2855     {
2856       /* year value limits are set up explicitly after updown creation */
2857       MONTHCAL_Scroll(infoPtr, 12 * nmud->iDelta, FALSE);
2858       MONTHCAL_NotifyDayState(infoPtr);
2859       MONTHCAL_NotifySelectionChange(infoPtr);
2860     }
2861   }
2862   return 0;
2863 }
2864 
2865 static inline BOOL
2866 MONTHCAL_SetUnicodeFormat(MONTHCAL_INFO *infoPtr, BOOL isUnicode)
2867 {
2868   BOOL prev = infoPtr->isUnicode;
2869   infoPtr->isUnicode = isUnicode;
2870   return prev;
2871 }
2872 
2873 static inline BOOL
2874 MONTHCAL_GetUnicodeFormat(const MONTHCAL_INFO *infoPtr)
2875 {
2876   return infoPtr->isUnicode;
2877 }
2878 
2879 static LRESULT WINAPI
2880 MONTHCAL_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2881 {
2882   MONTHCAL_INFO *infoPtr = (MONTHCAL_INFO *)GetWindowLongPtrW(hwnd, 0);
2883 
2884   TRACE("hwnd=%p msg=%x wparam=%lx lparam=%lx\n", hwnd, uMsg, wParam, lParam);
2885 
2886   if (!infoPtr && (uMsg != WM_CREATE))
2887     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
2888   switch(uMsg)
2889   {
2890   case MCM_GETCURSEL:
2891     return MONTHCAL_GetCurSel(infoPtr, (LPSYSTEMTIME)lParam);
2892 
2893   case MCM_SETCURSEL:
2894     return MONTHCAL_SetCurSel(infoPtr, (LPSYSTEMTIME)lParam);
2895 
2896   case MCM_GETMAXSELCOUNT:
2897     return MONTHCAL_GetMaxSelCount(infoPtr);
2898 
2899   case MCM_SETMAXSELCOUNT:
2900     return MONTHCAL_SetMaxSelCount(infoPtr, wParam);
2901 
2902   case MCM_GETSELRANGE:
2903     return MONTHCAL_GetSelRange(infoPtr, (LPSYSTEMTIME)lParam);
2904 
2905   case MCM_SETSELRANGE:
2906     return MONTHCAL_SetSelRange(infoPtr, (LPSYSTEMTIME)lParam);
2907 
2908   case MCM_GETMONTHRANGE:
2909     return MONTHCAL_GetMonthRange(infoPtr, wParam, (SYSTEMTIME*)lParam);
2910 
2911   case MCM_SETDAYSTATE:
2912     return MONTHCAL_SetDayState(infoPtr, (INT)wParam, (LPMONTHDAYSTATE)lParam);
2913 
2914   case MCM_GETMINREQRECT:
2915     return MONTHCAL_GetMinReqRect(infoPtr, (LPRECT)lParam);
2916 
2917   case MCM_GETCOLOR:
2918     return MONTHCAL_GetColor(infoPtr, wParam);
2919 
2920   case MCM_SETCOLOR:
2921     return MONTHCAL_SetColor(infoPtr, wParam, (COLORREF)lParam);
2922 
2923   case MCM_GETTODAY:
2924     return MONTHCAL_GetToday(infoPtr, (LPSYSTEMTIME)lParam);
2925 
2926   case MCM_SETTODAY:
2927     return MONTHCAL_SetToday(infoPtr, (LPSYSTEMTIME)lParam);
2928 
2929   case MCM_HITTEST:
2930     return MONTHCAL_HitTest(infoPtr, (PMCHITTESTINFO)lParam);
2931 
2932   case MCM_GETFIRSTDAYOFWEEK:
2933     return MONTHCAL_GetFirstDayOfWeek(infoPtr);
2934 
2935   case MCM_SETFIRSTDAYOFWEEK:
2936     return MONTHCAL_SetFirstDayOfWeek(infoPtr, (INT)lParam);
2937 
2938   case MCM_GETRANGE:
2939     return MONTHCAL_GetRange(infoPtr, (LPSYSTEMTIME)lParam);
2940 
2941   case MCM_SETRANGE:
2942     return MONTHCAL_SetRange(infoPtr, (SHORT)wParam, (LPSYSTEMTIME)lParam);
2943 
2944   case MCM_GETMONTHDELTA:
2945     return MONTHCAL_GetMonthDelta(infoPtr);
2946 
2947   case MCM_SETMONTHDELTA:
2948     return MONTHCAL_SetMonthDelta(infoPtr, wParam);
2949 
2950   case MCM_GETMAXTODAYWIDTH:
2951     return MONTHCAL_GetMaxTodayWidth(infoPtr);
2952 
2953   case MCM_SETUNICODEFORMAT:
2954     return MONTHCAL_SetUnicodeFormat(infoPtr, (BOOL)wParam);
2955 
2956   case MCM_GETUNICODEFORMAT:
2957     return MONTHCAL_GetUnicodeFormat(infoPtr);
2958 
2959   case MCM_GETCALENDARCOUNT:
2960     return MONTHCAL_GetCalCount(infoPtr);
2961 
2962   case WM_GETDLGCODE:
2963     return DLGC_WANTARROWS | DLGC_WANTCHARS;
2964 
2965   case WM_RBUTTONUP:
2966     return MONTHCAL_RButtonUp(infoPtr, lParam);
2967 
2968   case WM_LBUTTONDOWN:
2969     return MONTHCAL_LButtonDown(infoPtr, lParam);
2970 
2971   case WM_MOUSEMOVE:
2972     return MONTHCAL_MouseMove(infoPtr, lParam);
2973 
2974   case WM_LBUTTONUP:
2975     return MONTHCAL_LButtonUp(infoPtr, lParam);
2976 
2977   case WM_PAINT:
2978     return MONTHCAL_Paint(infoPtr, (HDC)wParam);
2979 
2980   case WM_PRINTCLIENT:
2981     return MONTHCAL_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
2982 
2983   case WM_ERASEBKGND:
2984     return MONTHCAL_EraseBkgnd(infoPtr, (HDC)wParam);
2985 
2986   case WM_SETFOCUS:
2987     return MONTHCAL_SetFocus(infoPtr);
2988 
2989   case WM_SIZE:
2990     return MONTHCAL_Size(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
2991 
2992   case WM_NOTIFY:
2993     return MONTHCAL_Notify(infoPtr, (NMHDR*)lParam);
2994 
2995   case WM_CREATE:
2996     return MONTHCAL_Create(hwnd, (LPCREATESTRUCTW)lParam);
2997 
2998   case WM_SETFONT:
2999     return MONTHCAL_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);
3000 
3001   case WM_GETFONT:
3002     return MONTHCAL_GetFont(infoPtr);
3003 
3004   case WM_TIMER:
3005     return MONTHCAL_Timer(infoPtr, wParam);
3006 
3007   case WM_THEMECHANGED:
3008     return theme_changed (infoPtr);
3009 
3010   case WM_DESTROY:
3011     return MONTHCAL_Destroy(infoPtr);
3012 
3013   case WM_SYSCOLORCHANGE:
3014     COMCTL32_RefreshSysColors();
3015     return 0;
3016 
3017   case WM_STYLECHANGED:
3018     return MONTHCAL_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3019 
3020   case WM_STYLECHANGING:
3021     return MONTHCAL_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
3022 
3023   default:
3024     if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
3025       ERR( "unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
3026     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3027   }
3028 }
3029 
3030 
3031 void
3032 MONTHCAL_Register(void)
3033 {
3034   WNDCLASSW wndClass;
3035 
3036   ZeroMemory(&wndClass, sizeof(WNDCLASSW));
3037   wndClass.style         = CS_GLOBALCLASS;
3038   wndClass.lpfnWndProc   = MONTHCAL_WindowProc;
3039   wndClass.cbClsExtra    = 0;
3040   wndClass.cbWndExtra    = sizeof(MONTHCAL_INFO *);
3041   wndClass.hCursor       = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3042   wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
3043   wndClass.lpszClassName = MONTHCAL_CLASSW;
3044 
3045   RegisterClassW(&wndClass);
3046 }
3047 
3048 
3049 void
3050 MONTHCAL_Unregister(void)
3051 {
3052     UnregisterClassW(MONTHCAL_CLASSW, NULL);
3053 }
3054