comctl32/monthcal: Handle over/underflow while navigation with month menu.
[wine] / dlls / comctl32 / monthcal.c
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 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  *    -- MONTHCAL_GetMonthRange
37  *    -- handle resources better (doesn't work now); 
38  *    -- take care of internationalization.
39  *    -- keyboard handling.
40  *    -- search for FIXME
41  */
42
43 #include <math.h>
44 #include <stdarg.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48
49 #include "windef.h"
50 #include "winbase.h"
51 #include "wingdi.h"
52 #include "winuser.h"
53 #include "winnls.h"
54 #include "commctrl.h"
55 #include "comctl32.h"
56 #include "uxtheme.h"
57 #include "tmschema.h"
58 #include "wine/unicode.h"
59 #include "wine/debug.h"
60
61 WINE_DEFAULT_DEBUG_CHANNEL(monthcal);
62
63 #define MC_SEL_LBUTUP       1   /* Left button released */
64 #define MC_SEL_LBUTDOWN     2   /* Left button pressed in calendar */
65 #define MC_PREVPRESSED      4   /* Prev month button pressed */
66 #define MC_NEXTPRESSED      8   /* Next month button pressed */
67 #define MC_PREVNEXTMONTHDELAY   350     /* when continuously pressing `next/prev
68                                            month', wait 500 ms before going
69                                            to the next/prev month */
70 #define MC_TODAYUPDATEDELAY 120000 /* time between today check for update (2 min) */
71
72 #define MC_PREVNEXTMONTHTIMER   1       /* Timer ID's */
73 #define MC_TODAYUPDATETIMER     2
74
75 #define countof(arr) (sizeof(arr)/sizeof(arr[0]))
76
77 /* convert from days to 100 nanoseconds unit - used as FILETIME unit */
78 #define DAYSTO100NSECS(days) (((ULONGLONG)(days))*24*60*60*10000000)
79
80 typedef struct
81 {
82     HWND        hwndSelf;
83     DWORD       dwStyle; /* cached GWL_STYLE */
84     COLORREF    bk;
85     COLORREF    txt;
86     COLORREF    titlebk;
87     COLORREF    titletxt;
88     COLORREF    monthbk;
89     COLORREF    trailingtxt;
90     HFONT       hFont;
91     HFONT       hBoldFont;
92     int         textHeight;
93     int         textWidth;
94     int         height_increment;
95     int         width_increment;
96     int         firstDayplace; /* place of the first day of the current month */
97     INT         delta;  /* scroll rate; # of months that the */
98                         /* control moves when user clicks a scroll button */
99     int         visible;        /* # of months visible */
100     int         firstDay;       /* Start month calendar with firstDay's day,
101                                    stored in SYSTEMTIME format */
102     BOOL        firstDaySet;    /* first week day differs from locale defined */
103
104     int         monthRange;
105     MONTHDAYSTATE *monthdayState;
106     SYSTEMTIME  todaysDate;
107     BOOL        todaySet;       /* Today was forced with MCM_SETTODAY */
108     int         status;         /* See MC_SEL flags */
109     SYSTEMTIME  firstSel;       /* first selected day */
110     INT         maxSelCount;
111     SYSTEMTIME  minSel;
112     SYSTEMTIME  maxSel;
113     SYSTEMTIME  curSel;         /* contains currently selected year, month and day */
114     SYSTEMTIME  focusedSel;     /* date currently focused with mouse movement */
115     DWORD       rangeValid;
116     SYSTEMTIME  minDate;
117     SYSTEMTIME  maxDate;
118
119     RECT title;         /* rect for the header above the calendar */
120     RECT titlebtnnext;  /* the `next month' button in the header */
121     RECT titlebtnprev;  /* the `prev month' button in the header */
122     RECT titlemonth;    /* the `month name' txt in the header */
123     RECT titleyear;     /* the `year number' txt in the header */
124     RECT wdays;         /* week days at top */
125     RECT days;          /* calendar area */
126     RECT weeknums;      /* week numbers at left side */
127     RECT todayrect;     /* `today: xx/xx/xx' text rect */
128     HWND hwndNotify;    /* Window to receive the notifications */
129     HWND hWndYearEdit;  /* Window Handle of edit box to handle years */
130     HWND hWndYearUpDown;/* Window Handle of updown box to handle years */
131     WNDPROC EditWndProc;  /* original Edit window procedure */
132 } MONTHCAL_INFO, *LPMONTHCAL_INFO;
133
134 static const WCHAR themeClass[] = { 'S','c','r','o','l','l','b','a','r',0 };
135
136 /* empty SYSTEMTIME const */
137 static const SYSTEMTIME st_null;
138 /* valid date limits */
139 static const SYSTEMTIME max_allowed_date = { .wYear = 9999, .wMonth = 12, .wDay = 31 };
140 static const SYSTEMTIME min_allowed_date = { .wYear = 1752, .wMonth = 9,  .wDay = 14 };
141
142
143 #define MONTHCAL_GetInfoPtr(hwnd) ((MONTHCAL_INFO *)GetWindowLongPtrW(hwnd, 0))
144
145 /* helper functions  */
146
147 /* send a single MCN_SELCHANGE notification */
148 static inline void MONTHCAL_NotifySelectionChange(const MONTHCAL_INFO *infoPtr)
149 {
150     NMSELCHANGE nmsc;
151
152     nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
153     nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
154     nmsc.nmhdr.code     = MCN_SELCHANGE;
155     nmsc.stSelStart     = infoPtr->minSel;
156     nmsc.stSelEnd       = infoPtr->maxSel;
157     SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
158 }
159
160 /* send a single MCN_SELECT notification */
161 static inline void MONTHCAL_NotifySelect(const MONTHCAL_INFO *infoPtr)
162 {
163     NMSELCHANGE nmsc;
164
165     nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
166     nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
167     nmsc.nmhdr.code     = MCN_SELECT;
168     nmsc.stSelStart     = infoPtr->minSel;
169     nmsc.stSelEnd       = infoPtr->maxSel;
170
171     SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
172 }
173
174 /* returns the number of days in any given month, checking for leap days */
175 /* january is 1, december is 12 */
176 int MONTHCAL_MonthLength(int month, int year)
177 {
178   const int mdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
179   /* Wrap around, this eases handling. Getting length only we shouldn't care
180      about year change here cause January and December have
181      the same day quantity */
182   if(month == 0)
183     month = 12;
184   else if(month == 13)
185     month = 1;
186
187   /* special case for calendar transition year */
188   if(month == min_allowed_date.wMonth && year == min_allowed_date.wYear) return 19;
189
190   /* if we have a leap year add 1 day to February */
191   /* a leap year is a year either divisible by 400 */
192   /* or divisible by 4 and not by 100 */
193   if(month == 2) { /* February */
194     return mdays[month - 1] + ((year%400 == 0) ? 1 : ((year%100 != 0) &&
195      (year%4 == 0)) ? 1 : 0);
196   }
197   else {
198     return mdays[month - 1];
199   }
200 }
201
202 /* compares timestamps using date part only */
203 static inline BOOL MONTHCAL_IsDateEqual(const SYSTEMTIME *first, const SYSTEMTIME *second)
204 {
205   return (first->wYear == second->wYear) && (first->wMonth == second->wMonth) &&
206          (first->wDay  == second->wDay);
207 }
208
209 /* make sure that date fields are valid */
210 static BOOL MONTHCAL_ValidateDate(const SYSTEMTIME *time)
211 {
212   if(time->wMonth < 1 || time->wMonth > 12 ) return FALSE;
213   if(time->wDayOfWeek > 6) return FALSE;
214   if(time->wDay > MONTHCAL_MonthLength(time->wMonth, time->wYear))
215           return FALSE;
216
217   return TRUE;
218 }
219
220 /* Compares two dates in SYSTEMTIME format
221  *
222  * PARAMETERS
223  *
224  *  [I] first  : pointer to valid first date data to compare
225  *  [I] second : pointer to valid second date data to compare
226  *
227  * RETURN VALUE
228  *
229  *  -1 : first <  second
230  *   0 : first == second
231  *   1 : first >  second
232  *
233  *  Note that no date validation performed, alreadt validated values expected.
234  */
235 static LONG MONTHCAL_CompareSystemTime(const SYSTEMTIME *first, const SYSTEMTIME *second)
236 {
237   FILETIME ft_first, ft_second;
238
239   SystemTimeToFileTime(first, &ft_first);
240   SystemTimeToFileTime(second, &ft_second);
241
242   return CompareFileTime(&ft_first, &ft_second);
243 }
244
245 /* Checks largest possible date range and configured one
246  *
247  * PARAMETERS
248  *
249  *  [I] infoPtr : valid pointer to control data
250  *  [I] date    : pointer to valid date data to check
251  *  [I] fix     : make date fit valid range
252  *
253  * RETURN VALUE
254  *
255  *  TRUE  - date whithin largest and configured range
256  *  FALSE - date is outside largest or configured range
257  */
258 static BOOL MONTHCAL_IsDateInValidRange(const MONTHCAL_INFO *infoPtr,
259                                         SYSTEMTIME *date, BOOL fix)
260 {
261   const SYSTEMTIME *fix_st = NULL;
262
263   if(MONTHCAL_CompareSystemTime(date, &max_allowed_date) == 1) {
264      fix_st = &max_allowed_date;
265   }
266   else if(MONTHCAL_CompareSystemTime(date, &min_allowed_date) == -1) {
267      fix_st = &min_allowed_date;
268   }
269   else if(infoPtr->rangeValid & GDTR_MAX) {
270      if((MONTHCAL_CompareSystemTime(date, &infoPtr->maxDate) == 1)) {
271        fix_st = &infoPtr->maxDate;
272      }
273   }
274   else if(infoPtr->rangeValid & GDTR_MIN) {
275      if((MONTHCAL_CompareSystemTime(date, &infoPtr->minDate) == -1)) {
276        fix_st = &infoPtr->minDate;
277      }
278   }
279
280   if (fix && fix_st) {
281     date->wYear  = fix_st->wYear;
282     date->wMonth = fix_st->wMonth;
283   }
284
285   return fix_st ? FALSE : TRUE;
286 }
287
288 /* Checks passed range width with configured maximum selection count
289  *
290  * PARAMETERS
291  *
292  *  [I] infoPtr : valid pointer to control data
293  *  [I] range0  : pointer to valid date data (requested bound)
294  *  [I] range1  : pointer to valid date data (primary bound)
295  *  [O] adjust  : returns adjusted range bound to fit maximum range (optional)
296  *
297  *  Adjust value computed basing on primary bound and current maximum selection
298  *  count. For simple range check (without adjusted value required) (range0, range1)
299  *  relation means nothing.
300  *
301  * RETURN VALUE
302  *
303  *  TRUE  - range is shorter or equal to maximum
304  *  FALSE - range is larger than maximum
305  */
306 static BOOL MONTHCAL_IsSelRangeValid(const MONTHCAL_INFO *infoPtr,
307                                      const SYSTEMTIME *range0,
308                                      const SYSTEMTIME *range1,
309                                      SYSTEMTIME *adjust)
310 {
311   ULARGE_INTEGER ul_range0, ul_range1, ul_diff;
312   FILETIME ft_range0, ft_range1;
313   LONG cmp;
314
315   SystemTimeToFileTime(range0, &ft_range0);
316   SystemTimeToFileTime(range1, &ft_range1);
317
318   ul_range0.LowPart  = ft_range0.dwLowDateTime;
319   ul_range0.HighPart = ft_range0.dwHighDateTime;
320   ul_range1.LowPart  = ft_range1.dwLowDateTime;
321   ul_range1.HighPart = ft_range1.dwHighDateTime;
322
323   cmp = CompareFileTime(&ft_range0, &ft_range1);
324
325   if(cmp == 1)
326      ul_diff.QuadPart = ul_range0.QuadPart - ul_range1.QuadPart;
327   else
328      ul_diff.QuadPart = -ul_range0.QuadPart + ul_range1.QuadPart;
329
330   if(ul_diff.QuadPart >= DAYSTO100NSECS(infoPtr->maxSelCount)) {
331
332      if(adjust) {
333        if(cmp == 1)
334           ul_range0.QuadPart = ul_range1.QuadPart + DAYSTO100NSECS(infoPtr->maxSelCount - 1);
335        else
336           ul_range0.QuadPart = ul_range1.QuadPart - DAYSTO100NSECS(infoPtr->maxSelCount - 1);
337
338        ft_range0.dwLowDateTime  = ul_range0.LowPart;
339        ft_range0.dwHighDateTime = ul_range0.HighPart;
340        FileTimeToSystemTime(&ft_range0, adjust);
341      }
342
343      return FALSE;
344   }
345   else return TRUE;
346 }
347
348 /* Used in MCM_SETRANGE/MCM_SETSELRANGE to determine resulting time part.
349    Milliseconds are intentionally not validated. */
350 static BOOL MONTHCAL_ValidateTime(const SYSTEMTIME *time)
351 {
352   if((time->wHour > 24) || (time->wMinute > 59) || (time->wSecond > 59))
353     return FALSE;
354   else
355     return TRUE;
356 }
357
358 /* Copies timestamp part only.
359  *
360  * PARAMETERS
361  *
362  *  [I] from : source date
363  *  [O] to   : dest date
364  */
365 static void MONTHCAL_CopyTime(const SYSTEMTIME *from, SYSTEMTIME *to)
366 {
367   to->wHour   = from->wHour;
368   to->wMinute = from->wMinute;
369   to->wSecond = from->wSecond;
370 }
371
372 /* Copies date part only.
373  *
374  * PARAMETERS
375  *
376  *  [I] from : source date
377  *  [O] to   : dest date
378  */
379 static void MONTHCAL_CopyDate(const SYSTEMTIME *from, SYSTEMTIME *to)
380 {
381   to->wYear  = from->wYear;
382   to->wMonth = from->wMonth;
383   to->wDay   = from->wDay;
384   to->wDayOfWeek = from->wDayOfWeek;
385 }
386
387 /* Note:Depending on DST, this may be offset by a day.
388    Need to find out if we're on a DST place & adjust the clock accordingly.
389    Above function assumes we have a valid data.
390    Valid for year>1752;  1 <= d <= 31, 1 <= m <= 12.
391    0 = Sunday.
392 */
393
394 /* Returns the day in the week
395  *
396  * PARAMETERS
397  *  [i] day : day of month [1, 31]
398  *  [I] month : month number [1, 12]
399  *  [I] year : year value
400  *
401  * RETURN VALUE
402  *   day of week in SYSTEMTIME format: (0 == sunday,..., 6 == saturday)
403  */
404 int MONTHCAL_CalculateDayOfWeek(WORD day, WORD month, WORD year)
405 {
406   FILETIME ft;
407   SYSTEMTIME st;
408
409   st.wYear  = year;
410   st.wMonth = month;
411   st.wDay   = day;
412   st.wHour  = st.wMinute = st.wSecond = st.wMilliseconds = 0;
413
414   SystemTimeToFileTime(&st, &ft);
415   FileTimeToSystemTime(&ft, &st);
416
417   return st.wDayOfWeek;
418 }
419
420 /* properly updates date to point on next month */
421 static inline void MONTHCAL_GetNextMonth(SYSTEMTIME *date)
422 {
423   if(++date->wMonth > 12)
424   {
425     date->wMonth = 1;
426     date->wYear++;
427   }
428   date->wDayOfWeek = MONTHCAL_CalculateDayOfWeek(date->wDay, date->wMonth,
429                                                  date->wYear);
430 }
431
432 /* properly updates date to point on prev month */
433 static inline void MONTHCAL_GetPrevMonth(SYSTEMTIME *date)
434 {
435   if(--date->wMonth < 1)
436   {
437     date->wMonth = 12;
438     date->wYear--;
439   }
440   date->wDayOfWeek = MONTHCAL_CalculateDayOfWeek(date->wDay, date->wMonth,
441                                                  date->wYear);
442 }
443
444 /* Returns full date for a first currently visible day */
445 static void MONTHCAL_GetMinDate(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *date)
446 {
447   int firstDay;
448
449   firstDay = MONTHCAL_CalculateDayOfWeek(1, infoPtr->curSel.wMonth, infoPtr->curSel.wYear);
450
451   *date = infoPtr->curSel;
452   MONTHCAL_GetPrevMonth(date);
453
454   date->wDay = MONTHCAL_MonthLength(date->wMonth, date->wYear) +
455                (infoPtr->firstDay - firstDay) % 7 + 1;
456
457   if(date->wDay > MONTHCAL_MonthLength(date->wMonth, date->wYear))
458     date->wDay -= 7;
459
460   /* fix day of week */
461   date->wDayOfWeek = MONTHCAL_CalculateDayOfWeek(date->wDay, date->wMonth,
462                                                  date->wYear);
463 }
464
465 /* Returns full date for a last currently visible day */
466 static void MONTHCAL_GetMaxDate(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *date)
467 {
468   SYSTEMTIME st;
469
470   *date = infoPtr->curSel;
471   MONTHCAL_GetNextMonth(date);
472
473   MONTHCAL_GetMinDate(infoPtr, &st);
474   /* Use month length to get max day. 42 means max day count in calendar area */
475   date->wDay = 42 - (MONTHCAL_MonthLength(st.wMonth, st.wYear) - st.wDay + 1) -
476                      MONTHCAL_MonthLength(infoPtr->curSel.wMonth, infoPtr->curSel.wYear);
477
478   /* fix day of week */
479   date->wDayOfWeek = MONTHCAL_CalculateDayOfWeek(date->wDay, date->wMonth,
480                                                  date->wYear);
481 }
482
483 /* From a given point, calculate the row (weekpos), column(daypos)
484    and day in the calendar. day== 0 mean the last day of tha last month
485 */
486 static int MONTHCAL_CalcDayFromPos(const MONTHCAL_INFO *infoPtr, int x, int y,
487                                    int *daypos,int *weekpos)
488 {
489   int retval, firstDay;
490   RECT rcClient;
491
492   GetClientRect(infoPtr->hwndSelf, &rcClient);
493
494   /* if the point is outside the x bounds of the window put
495   it at the boundary */
496   if (x > rcClient.right)
497     x = rcClient.right;
498
499
500   *daypos = (x - infoPtr->days.left ) / infoPtr->width_increment;
501   *weekpos = (y - infoPtr->days.top ) / infoPtr->height_increment;
502
503   firstDay = (MONTHCAL_CalculateDayOfWeek(1, infoPtr->curSel.wMonth, infoPtr->curSel.wYear)+6 - infoPtr->firstDay)%7;
504   retval = *daypos + (7 * *weekpos) - firstDay;
505   return retval;
506 }
507
508 /* day is the day of the month, 1 == 1st day of the month */
509 /* sets x and y to be the position of the day */
510 /* x == day, y == week where(0,0) == firstDay, 1st week */
511 static void MONTHCAL_CalcDayXY(const MONTHCAL_INFO *infoPtr, int day, int month,
512                                  int *x, int *y)
513 {
514   int firstDay, prevMonth;
515
516   firstDay = (MONTHCAL_CalculateDayOfWeek(1, infoPtr->curSel.wMonth, infoPtr->curSel.wYear) +6 - infoPtr->firstDay)%7;
517
518   if(month==infoPtr->curSel.wMonth) {
519     *x = (day + firstDay) % 7;
520     *y = (day + firstDay - *x) / 7;
521     return;
522   }
523   if(month < infoPtr->curSel.wMonth) {
524     prevMonth = month - 1;
525     if(prevMonth==0)
526        prevMonth = 12;
527
528     *x = (MONTHCAL_MonthLength(prevMonth, infoPtr->curSel.wYear) - firstDay) % 7;
529     *y = 0;
530     return;
531   }
532
533   *y = MONTHCAL_MonthLength(month, infoPtr->curSel.wYear - 1) / 7;
534   *x = (day + firstDay + MONTHCAL_MonthLength(month,
535        infoPtr->curSel.wYear)) % 7;
536 }
537
538
539 /* x: column(day), y: row(week) */
540 static void MONTHCAL_CalcDayRect(const MONTHCAL_INFO *infoPtr, RECT *r, int x, int y)
541 {
542   r->left = infoPtr->days.left + x * infoPtr->width_increment;
543   r->right = r->left + infoPtr->width_increment;
544   r->top  = infoPtr->days.top  + y * infoPtr->height_increment;
545   r->bottom = r->top + infoPtr->textHeight;
546 }
547
548
549 /* sets the RECT struct r to the rectangle around the day and month */
550 /* day is the day value of the month(1 == 1st), month is the month */
551 /* value(january == 1, december == 12) */
552 static inline void MONTHCAL_CalcPosFromDay(const MONTHCAL_INFO *infoPtr,
553                                             int day, int month, RECT *r)
554 {
555   int x, y;
556
557   MONTHCAL_CalcDayXY(infoPtr, day, month, &x, &y);
558   MONTHCAL_CalcDayRect(infoPtr, r, x, y);
559 }
560
561 /* Focused day helper:
562
563    - set focused date to given value;
564    - reset to zero value if NULL passed;
565    - invalidate previous and new day rectangle only if needed.
566
567    Returns TRUE if focused day changed, FALSE otherwise.
568 */
569 static BOOL MONTHCAL_SetDayFocus(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *st)
570 {
571   RECT r;
572
573   if(st)
574   {
575     /* there's nothing to do if it's the same date,
576        mouse move within same date rectangle case */
577     if(MONTHCAL_IsDateEqual(&infoPtr->focusedSel, st)) return FALSE;
578
579     /* invalidate old focused day */
580     MONTHCAL_CalcPosFromDay(infoPtr, infoPtr->focusedSel.wDay,
581                                      infoPtr->focusedSel.wMonth, &r);
582     InvalidateRect(infoPtr->hwndSelf, &r, FALSE);
583
584     infoPtr->focusedSel = *st;
585   }
586
587   MONTHCAL_CalcPosFromDay(infoPtr, infoPtr->focusedSel.wDay,
588                                    infoPtr->focusedSel.wMonth, &r);
589
590   if(!st && MONTHCAL_ValidateDate(&infoPtr->focusedSel))
591     infoPtr->focusedSel = st_null;
592
593   /* on set invalidates new day, on reset clears previous focused day */
594   InvalidateRect(infoPtr->hwndSelf, &r, FALSE);
595
596   return TRUE;
597 }
598
599 /* day is the day in the month(1 == 1st of the month) */
600 /* month is the month value(1 == january, 12 == december) */
601 static void MONTHCAL_CircleDay(const MONTHCAL_INFO *infoPtr, HDC hdc, int day, int month)
602 {
603   HPEN hRedPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
604   HPEN hOldPen2 = SelectObject(hdc, hRedPen);
605   HBRUSH hOldBrush;
606   RECT day_rect;
607
608   MONTHCAL_CalcPosFromDay(infoPtr, day, month, &day_rect);
609
610   hOldBrush = SelectObject(hdc, GetStockObject(NULL_BRUSH));
611   Rectangle(hdc, day_rect.left, day_rect.top, day_rect.right, day_rect.bottom);
612
613   SelectObject(hdc, hOldBrush);
614   DeleteObject(hRedPen);
615   SelectObject(hdc, hOldPen2);
616 }
617
618 static void MONTHCAL_DrawDay(const MONTHCAL_INFO *infoPtr, HDC hdc, int day, int month,
619                              int x, int y, int bold)
620 {
621   static const WCHAR fmtW[] = { '%','d',0 };
622   WCHAR buf[10];
623   RECT r;
624   static BOOL haveBoldFont, haveSelectedDay = FALSE;
625   HBRUSH hbr;
626   COLORREF oldCol = 0;
627   COLORREF oldBk = 0;
628
629   wsprintfW(buf, fmtW, day);
630
631 /* No need to check styles: when selection is not valid, it is set to zero.
632  * 1<day<31, so everything is OK.
633  */
634
635   MONTHCAL_CalcDayRect(infoPtr, &r, x, y);
636
637   if((day>=infoPtr->minSel.wDay) && (day<=infoPtr->maxSel.wDay)
638        && (month == infoPtr->curSel.wMonth)) {
639     RECT r2;
640
641     TRACE("%d %d %d\n",day, infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
642     TRACE("%s\n", wine_dbgstr_rect(&r));
643     oldCol = SetTextColor(hdc, infoPtr->monthbk);
644     oldBk = SetBkColor(hdc, infoPtr->trailingtxt);
645     hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
646     FillRect(hdc, &r, hbr);
647
648     /* FIXME: this may need to be changed now b/c of the other
649         drawing changes 11/3/99 CMM */
650     r2.left   = r.left - 0.25 * infoPtr->textWidth;
651     r2.top    = r.top;
652     r2.right  = r.left + 0.5 * infoPtr->textWidth;
653     r2.bottom = r.bottom;
654     if(haveSelectedDay) FillRect(hdc, &r2, hbr);
655       haveSelectedDay = TRUE;
656   } else {
657     haveSelectedDay = FALSE;
658   }
659
660   /* need to add some code for multiple selections */
661
662   if((bold) &&(!haveBoldFont)) {
663     SelectObject(hdc, infoPtr->hBoldFont);
664     haveBoldFont = TRUE;
665   }
666   if((!bold) &&(haveBoldFont)) {
667     SelectObject(hdc, infoPtr->hFont);
668     haveBoldFont = FALSE;
669   }
670
671   SetBkMode(hdc,TRANSPARENT);
672   DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
673
674   if(haveSelectedDay) {
675     SetTextColor(hdc, oldCol);
676     SetBkColor(hdc, oldBk);
677   }
678 }
679
680
681 static void paint_button (MONTHCAL_INFO *infoPtr, HDC hdc, BOOL btnNext)
682 {
683     HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
684     RECT *r = btnNext ? &infoPtr->titlebtnnext : &infoPtr->titlebtnprev;
685     BOOL pressed = btnNext ? (infoPtr->status & MC_NEXTPRESSED) :
686                              (infoPtr->status & MC_PREVPRESSED);
687     if (theme)
688     {
689         static const int states[] = {
690             /* Prev button */
691             ABS_LEFTNORMAL,  ABS_LEFTPRESSED,  ABS_LEFTDISABLED,
692             /* Next button */
693             ABS_RIGHTNORMAL, ABS_RIGHTPRESSED, ABS_RIGHTDISABLED
694         };
695         int stateNum = btnNext ? 3 : 0;
696         if (pressed)
697             stateNum += 1;
698         else
699         {
700             if (infoPtr->dwStyle & WS_DISABLED) stateNum += 2;
701         }
702         DrawThemeBackground (theme, hdc, SBP_ARROWBTN, states[stateNum], r, NULL);
703     }
704     else
705     {
706         int style = btnNext ? DFCS_SCROLLRIGHT : DFCS_SCROLLLEFT;
707         if (pressed)
708             style |= DFCS_PUSHED;
709         else
710         {
711             if (infoPtr->dwStyle & WS_DISABLED) style |= DFCS_INACTIVE;
712         }
713         
714         DrawFrameControl(hdc, r, DFC_SCROLL, style);
715     }
716 }
717
718
719 static void MONTHCAL_Refresh(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
720 {
721   static const WCHAR fmt_monthW[] = { '%','s',' ','%','l','d',0 };
722   RECT *title=&infoPtr->title;
723   RECT *titlemonth=&infoPtr->titlemonth;
724   RECT *titleyear=&infoPtr->titleyear;
725   RECT dayrect;
726   int i, j, m, mask, day, prevMonth;
727   int textHeight = infoPtr->textHeight;
728   SIZE size;
729   HBRUSH hbr;
730   HFONT currentFont;
731   WCHAR buf[20];
732   WCHAR buf1[20];
733   WCHAR buf2[32];
734   COLORREF oldTextColor, oldBkColor;
735   RECT rcTemp;
736   RECT rcDay; /* used in MONTHCAL_CalcDayRect() */
737   int startofprescal;
738   SYSTEMTIME st;
739
740   oldTextColor = SetTextColor(hdc, comctl32_color.clrWindowText);
741
742   /* fill background */
743   hbr = CreateSolidBrush (infoPtr->bk);
744   FillRect(hdc, &ps->rcPaint, hbr);
745   DeleteObject(hbr);
746
747   /* draw header */
748   if(IntersectRect(&rcTemp, &(ps->rcPaint), title))
749   {
750     hbr =  CreateSolidBrush(infoPtr->titlebk);
751     FillRect(hdc, title, hbr);
752     DeleteObject(hbr);
753   }
754
755   /* if the previous button is pressed draw it depressed */
756   if(IntersectRect(&rcTemp, &(ps->rcPaint), &infoPtr->titlebtnprev))
757     paint_button(infoPtr, hdc, FALSE);
758
759   /* if next button is depressed draw it depressed */
760   if(IntersectRect(&rcTemp, &(ps->rcPaint), &infoPtr->titlebtnnext))
761     paint_button(infoPtr, hdc, TRUE);
762
763   oldBkColor = SetBkColor(hdc, infoPtr->titlebk);
764   SetTextColor(hdc, infoPtr->titletxt);
765   currentFont = SelectObject(hdc, infoPtr->hBoldFont);
766
767   GetLocaleInfoW( LOCALE_USER_DEFAULT,LOCALE_SMONTHNAME1+infoPtr->curSel.wMonth -1,
768                   buf1,countof(buf1));
769   wsprintfW(buf, fmt_monthW, buf1, infoPtr->curSel.wYear);
770
771   if(IntersectRect(&rcTemp, &(ps->rcPaint), title))
772   {
773     DrawTextW(hdc, buf, strlenW(buf), title,
774                         DT_CENTER | DT_VCENTER | DT_SINGLELINE);
775   }
776
777 /* titlemonth left/right contained rect for whole titletxt('June  1999')
778   * MCM_HitTestInfo wants month & year rects, so prepare these now.
779   *(no, we can't draw them separately; the whole text is centered)
780   */
781   GetTextExtentPoint32W(hdc, buf, strlenW(buf), &size);
782   titlemonth->left = title->right / 2 + title->left / 2 - size.cx / 2;
783   titleyear->right = title->right / 2 + title->left / 2 + size.cx / 2;
784   GetTextExtentPoint32W(hdc, buf1, strlenW(buf1), &size);
785   titlemonth->right = titlemonth->left + size.cx;
786   titleyear->left = titlemonth->right;
787
788   /* draw month area */
789   rcTemp.top=infoPtr->wdays.top;
790   rcTemp.left=infoPtr->wdays.left;
791   rcTemp.bottom=infoPtr->todayrect.bottom;
792   rcTemp.right =infoPtr->todayrect.right;
793   if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcTemp))
794   {
795     hbr =  CreateSolidBrush(infoPtr->monthbk);
796     FillRect(hdc, &rcTemp, hbr);
797     DeleteObject(hbr);
798   }
799
800 /* draw line under day abbreviations */
801
802   MoveToEx(hdc, infoPtr->days.left + 3, title->bottom + textHeight + 1, NULL);
803   LineTo(hdc, infoPtr->days.right - 3, title->bottom + textHeight + 1);
804
805   prevMonth = infoPtr->curSel.wMonth - 1;
806   if(prevMonth == 0) /* if curSel.wMonth is january(1) prevMonth is */
807     prevMonth = 12;    /* december(12) of the previous year */
808
809   infoPtr->wdays.left   = infoPtr->days.left   = infoPtr->weeknums.right;
810
811   /* draw day abbreviations */
812   SelectObject(hdc, infoPtr->hFont);
813   SetBkColor(hdc, infoPtr->monthbk);
814   SetTextColor(hdc, infoPtr->trailingtxt);
815
816   /* rectangle to draw a single day abbreviation within */
817   dayrect = infoPtr->wdays;
818   dayrect.right = dayrect.left + infoPtr->width_increment;
819
820   i = infoPtr->firstDay;
821
822   for(j = 0; j < 7; j++) {
823     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1 + (i+j+6)%7, buf, countof(buf));
824     DrawTextW(hdc, buf, strlenW(buf), &dayrect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
825     dayrect.left  += infoPtr->width_increment;
826     dayrect.right += infoPtr->width_increment;
827   }
828
829   /* draw day numbers; first, the previous month */
830   MONTHCAL_GetMinDate(infoPtr, &st);
831   day = st.wDay;
832   startofprescal = day;
833   mask = 1<<(day-1);
834
835   i = 0;
836   m = 0;
837   while(day <= MONTHCAL_MonthLength(prevMonth, infoPtr->curSel.wYear)) {
838     MONTHCAL_CalcDayRect(infoPtr, &rcDay, i, 0);
839     if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcDay))
840     {
841       MONTHCAL_DrawDay(infoPtr, hdc, day, prevMonth, i, 0,
842           infoPtr->monthdayState[m] & mask);
843     }
844
845     mask<<=1;
846     day++;
847     i++;
848   }
849
850 /* draw `current' month  */
851
852   day = 1; /* start at the beginning of the current month */
853
854   infoPtr->firstDayplace = i;
855   SetTextColor(hdc, infoPtr->txt);
856   m++;
857   mask = 1;
858
859   /* draw the first week of the current month */
860   while(i<7) {
861     MONTHCAL_CalcDayRect(infoPtr, &rcDay, i, 0);
862     if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcDay))
863     {
864       MONTHCAL_DrawDay(infoPtr, hdc, day, infoPtr->curSel.wMonth, i, 0,
865         infoPtr->monthdayState[m] & mask);
866     }
867
868     mask<<=1;
869     day++;
870     i++;
871   }
872
873   j = 1; /* move to the 2nd week of the current month */
874   i = 0; /* move back to sunday */
875   while(day <= MONTHCAL_MonthLength(infoPtr->curSel.wMonth, infoPtr->curSel.wYear)) {
876     MONTHCAL_CalcDayRect(infoPtr, &rcDay, i, j);
877     if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcDay))
878     {
879       MONTHCAL_DrawDay(infoPtr, hdc, day, infoPtr->curSel.wMonth, i, j,
880           infoPtr->monthdayState[m] & mask);
881     }
882     mask<<=1;
883     day++;
884     i++;
885     if(i>6) { /* past saturday, goto the next weeks sunday */
886       i = 0;
887       j++;
888     }
889   }
890
891 /*  draw `next' month */
892
893   day = 1; /* start at the first day of the next month */
894   m++;
895   mask = 1;
896
897   SetTextColor(hdc, infoPtr->trailingtxt);
898   while((i<7) &&(j<6)) {
899     MONTHCAL_CalcDayRect(infoPtr, &rcDay, i, j);
900     if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcDay))
901     {
902       MONTHCAL_DrawDay(infoPtr, hdc, day, infoPtr->curSel.wMonth + 1, i, j,
903                 infoPtr->monthdayState[m] & mask);
904     }
905
906     mask<<=1;
907     day++;
908     i++;
909     if(i==7) { /* past saturday, go to next week's sunday */
910       i = 0;
911       j++;
912     }
913   }
914   SetTextColor(hdc, infoPtr->txt);
915
916   /* draw today mark rectangle */
917   if((infoPtr->curSel.wMonth == infoPtr->todaysDate.wMonth) &&
918      (infoPtr->curSel.wYear == infoPtr->todaysDate.wYear) &&
919     !(infoPtr->dwStyle & MCS_NOTODAYCIRCLE))
920   {
921     MONTHCAL_CircleDay(infoPtr, hdc, infoPtr->todaysDate.wDay, infoPtr->todaysDate.wMonth);
922   }
923
924   /* draw focused day */
925   if(!MONTHCAL_IsDateEqual(&infoPtr->focusedSel, &st_null))
926   {
927     MONTHCAL_CalcPosFromDay(infoPtr, infoPtr->focusedSel.wDay,
928                                      infoPtr->focusedSel.wMonth, &rcDay);
929
930     DrawFocusRect(hdc, &rcDay);
931   }
932
933   /* draw `today' date if style allows it, and draw a circle before today's
934    * date if necessary */
935   if(!(infoPtr->dwStyle & MCS_NOTODAY))  {
936     static const WCHAR todayW[] = { 'T','o','d','a','y',':',0 };
937     static const WCHAR fmt_todayW[] = { '%','s',' ','%','s',0 };
938     RECT rtoday;
939
940     if(!(infoPtr->dwStyle & MCS_NOTODAYCIRCLE))  {
941       /*day is the number of days from nextmonth we put on the calendar */
942       MONTHCAL_CircleDay(infoPtr, hdc,
943                          day+MONTHCAL_MonthLength(infoPtr->curSel.wMonth, infoPtr->curSel.wYear),
944                          infoPtr->curSel.wMonth);
945     }
946     if (!LoadStringW(COMCTL32_hModule, IDM_TODAY, buf1, countof(buf1)))
947     {
948         WARN("Can't load resource\n");
949         strcpyW(buf1, todayW);
950     }
951     MONTHCAL_CalcDayRect(infoPtr, &rtoday, 1, 6);
952     GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &infoPtr->todaysDate, NULL,
953                                                         buf2, countof(buf2));
954     wsprintfW(buf, fmt_todayW, buf1, buf2);
955     SelectObject(hdc, infoPtr->hBoldFont);
956
957     DrawTextW(hdc, buf, -1, &rtoday, DT_CALCRECT | DT_LEFT | DT_VCENTER | DT_SINGLELINE);
958     if(IntersectRect(&rcTemp, &(ps->rcPaint), &rtoday))
959     {
960       DrawTextW(hdc, buf, -1, &rtoday, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
961     }
962     SelectObject(hdc, infoPtr->hFont);
963   }
964
965   /* eventually draw week numbers */
966   if(infoPtr->dwStyle & MCS_WEEKNUMBERS) {
967     static const WCHAR fmt_weekW[] = { '%','d',0 }; /* week numbers format */
968     int mindays, weeknum, weeknum1;
969
970     /* Rules what week to call the first week of a new year:
971        LOCALE_IFIRSTWEEKOFYEAR == 0 (e.g US?):
972        The week containing Jan 1 is the first week of year
973        LOCALE_IFIRSTWEEKOFYEAR == 2 (e.g. Germany):
974        First week of year must contain 4 days of the new year
975        LOCALE_IFIRSTWEEKOFYEAR == 1  (what contries?)
976        The first week of the year must contain only days of the new year
977     */
978     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, buf, countof(buf));
979     weeknum = atoiW(buf);
980     switch (weeknum)
981     {
982       case 1: mindays = 6;
983         break;
984       case 2: mindays = 3;
985         break;
986       case 0:
987       default:
988         mindays = 0;
989     }
990     if (infoPtr->curSel.wMonth < 2)
991     {
992         /* calculate all those exceptions for january */
993         weeknum1 = MONTHCAL_CalculateDayOfWeek(1, 1, infoPtr->curSel.wYear);
994         if ((infoPtr->firstDay - weeknum1) % 7 > mindays)
995             weeknum = 1;
996         else
997         {
998             weeknum = 0;
999             for(i = 0; i < 11; i++)
1000               weeknum += MONTHCAL_MonthLength(i+1, infoPtr->curSel.wYear - 1);
1001
1002             weeknum += startofprescal + 7;
1003             weeknum /= 7;
1004             weeknum1 = MONTHCAL_CalculateDayOfWeek(1, 1, infoPtr->curSel.wYear - 1);
1005             if ((infoPtr->firstDay - weeknum1) % 7 > mindays) weeknum++;
1006         }
1007     }
1008     else
1009     {
1010         weeknum = 0;
1011         for(i = 0; i < prevMonth - 1; i++)
1012           weeknum += MONTHCAL_MonthLength(i+1, infoPtr->curSel.wYear);
1013
1014         weeknum += startofprescal + 7;
1015         weeknum /= 7;
1016         weeknum1 = MONTHCAL_CalculateDayOfWeek(1, 1, infoPtr->curSel.wYear);
1017         if ((infoPtr->firstDay - weeknum1) % 7 > mindays) weeknum++;
1018     }
1019
1020     dayrect = infoPtr->weeknums;
1021     dayrect.bottom = dayrect.top + infoPtr->height_increment;
1022
1023     for(i = 0; i < 6; i++) {
1024       if((i == 0) && (weeknum > 50))
1025       {
1026           wsprintfW(buf, fmt_weekW, weeknum);
1027           weeknum = 0;
1028       }
1029       else if((i == 5) && (weeknum > 47))
1030       {
1031           wsprintfW(buf, fmt_weekW, 1);
1032       }
1033       else
1034           wsprintfW(buf, fmt_weekW, weeknum + i);
1035
1036       DrawTextW(hdc, buf, -1, &dayrect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
1037       dayrect.top    += infoPtr->height_increment;
1038       dayrect.bottom += infoPtr->height_increment;
1039     }
1040
1041     MoveToEx(hdc, infoPtr->weeknums.right, infoPtr->weeknums.top + 3 , NULL);
1042     LineTo(hdc,   infoPtr->weeknums.right, infoPtr->weeknums.bottom);
1043   }
1044
1045   /* currentFont was font at entering Refresh */
1046   SetBkColor(hdc, oldBkColor);
1047   SelectObject(hdc, currentFont);
1048   SetTextColor(hdc, oldTextColor);
1049 }
1050
1051
1052 static LRESULT
1053 MONTHCAL_GetMinReqRect(const MONTHCAL_INFO *infoPtr, LPRECT lpRect)
1054 {
1055   TRACE("rect %p\n", lpRect);
1056
1057   if(!lpRect) return FALSE;
1058
1059   lpRect->left   = infoPtr->title.left;
1060   lpRect->top    = infoPtr->title.top;
1061   lpRect->right  = infoPtr->title.right;
1062   lpRect->bottom = infoPtr->todayrect.bottom;
1063
1064   AdjustWindowRect(lpRect, infoPtr->dwStyle, FALSE);
1065
1066   /* minimal rectangle is zero based */
1067   OffsetRect(lpRect, -lpRect->left, -lpRect->top);
1068
1069   TRACE("%s\n", wine_dbgstr_rect(lpRect));
1070
1071   return TRUE;
1072 }
1073
1074
1075 static LRESULT
1076 MONTHCAL_GetColor(const MONTHCAL_INFO *infoPtr, INT index)
1077 {
1078   TRACE("\n");
1079
1080   switch(index) {
1081     case MCSC_BACKGROUND:
1082       return infoPtr->bk;
1083     case MCSC_TEXT:
1084       return infoPtr->txt;
1085     case MCSC_TITLEBK:
1086       return infoPtr->titlebk;
1087     case MCSC_TITLETEXT:
1088       return infoPtr->titletxt;
1089     case MCSC_MONTHBK:
1090       return infoPtr->monthbk;
1091     case MCSC_TRAILINGTEXT:
1092       return infoPtr->trailingtxt;
1093   }
1094
1095   return -1;
1096 }
1097
1098
1099 static LRESULT
1100 MONTHCAL_SetColor(MONTHCAL_INFO *infoPtr, INT index, COLORREF color)
1101 {
1102   COLORREF prev = -1;
1103
1104   TRACE("%d: color %08x\n", index, color);
1105
1106   switch(index) {
1107     case MCSC_BACKGROUND:
1108       prev = infoPtr->bk;
1109       infoPtr->bk = color;
1110       break;
1111     case MCSC_TEXT:
1112       prev = infoPtr->txt;
1113       infoPtr->txt = color;
1114       break;
1115     case MCSC_TITLEBK:
1116       prev = infoPtr->titlebk;
1117       infoPtr->titlebk = color;
1118       break;
1119     case MCSC_TITLETEXT:
1120       prev=infoPtr->titletxt;
1121       infoPtr->titletxt = color;
1122       break;
1123     case MCSC_MONTHBK:
1124       prev = infoPtr->monthbk;
1125       infoPtr->monthbk = color;
1126       break;
1127     case MCSC_TRAILINGTEXT:
1128       prev = infoPtr->trailingtxt;
1129       infoPtr->trailingtxt = color;
1130       break;
1131   }
1132
1133   InvalidateRect(infoPtr->hwndSelf, NULL, index == MCSC_BACKGROUND ? TRUE : FALSE);
1134   return prev;
1135 }
1136
1137
1138 static LRESULT
1139 MONTHCAL_GetMonthDelta(const MONTHCAL_INFO *infoPtr)
1140 {
1141   TRACE("\n");
1142
1143   if(infoPtr->delta)
1144     return infoPtr->delta;
1145   else
1146     return infoPtr->visible;
1147 }
1148
1149
1150 static LRESULT
1151 MONTHCAL_SetMonthDelta(MONTHCAL_INFO *infoPtr, INT delta)
1152 {
1153   INT prev = infoPtr->delta;
1154
1155   TRACE("delta %d\n", delta);
1156
1157   infoPtr->delta = delta;
1158   return prev;
1159 }
1160
1161
1162 static inline LRESULT
1163 MONTHCAL_GetFirstDayOfWeek(const MONTHCAL_INFO *infoPtr)
1164 {
1165   int day;
1166
1167   /* convert from SYSTEMTIME to locale format */
1168   day = (infoPtr->firstDay >= 0) ? (infoPtr->firstDay+6)%7 : infoPtr->firstDay;
1169
1170   return MAKELONG(day, infoPtr->firstDaySet);
1171 }
1172
1173
1174 /* Sets the first day of the week that will appear in the control
1175  *
1176  *
1177  * PARAMETERS:
1178  *  [I] infoPtr : valid pointer to control data
1179  *  [I] day : day number to set as new first day (0 == Monday,...,6 == Sunday)
1180  *
1181  *
1182  * RETURN VALUE:
1183  *  Low word contains previous first day,
1184  *  high word indicates was first day forced with this message before or is
1185  *  locale difined (TRUE - was forced, FALSE - wasn't).
1186  *
1187  * FIXME: this needs to be implemented properly in MONTHCAL_Refresh()
1188  * FIXME: we need more error checking here
1189  */
1190 static LRESULT
1191 MONTHCAL_SetFirstDayOfWeek(MONTHCAL_INFO *infoPtr, INT day)
1192 {
1193   LRESULT prev = MONTHCAL_GetFirstDayOfWeek(infoPtr);
1194   int new_day;
1195
1196   TRACE("%d\n", day);
1197
1198   if(day == -1)
1199   {
1200     WCHAR buf[80];
1201
1202     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, buf, countof(buf));
1203     TRACE("%s %d\n", debugstr_w(buf), strlenW(buf));
1204
1205     new_day = atoiW(buf);
1206
1207     infoPtr->firstDaySet = FALSE;
1208   }
1209   else if(day >= 7)
1210   {
1211     new_day = 6; /* max first day allowed */
1212     infoPtr->firstDaySet = TRUE;
1213   }
1214   else
1215   {
1216     /* Native behaviour for that case is broken: invalid date number >31
1217        got displayed at (0,0) position, current month starts always from
1218        (1,0) position. Should be implemnted here as well. */
1219     if (day < -1)
1220       FIXME("No bug compatibility for day=%d\n", day);
1221
1222     new_day = day;
1223     infoPtr->firstDaySet = TRUE;
1224   }
1225
1226   /* convert from locale to SYSTEMTIME format */
1227   infoPtr->firstDay = (new_day >= 0) ? (++new_day) % 7 : new_day;
1228
1229   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1230
1231   return prev;
1232 }
1233
1234
1235 static LRESULT
1236 MONTHCAL_GetMonthRange(const MONTHCAL_INFO *infoPtr, DWORD flag, SYSTEMTIME *st)
1237 {
1238   TRACE("\n");
1239
1240   if(st)
1241   {
1242     switch (flag) {
1243     case GMR_VISIBLE:
1244     {
1245         /*FIXME: currently multicalendar feature isn't implemented, so entirely
1246                  visible month is current */
1247         st[0] = st[1] = infoPtr->curSel;
1248
1249         if (infoPtr->curSel.wMonth == min_allowed_date.wMonth &&
1250             infoPtr->curSel.wYear  == min_allowed_date.wYear)
1251         {
1252             st[0].wDay = min_allowed_date.wDay;
1253         }
1254         else
1255             st[0].wDay = 1;
1256         st[0].wDayOfWeek = MONTHCAL_CalculateDayOfWeek(1, st[0].wMonth, st[0].wYear);
1257
1258         st[1].wDay = MONTHCAL_MonthLength(st[1].wMonth, st[1].wYear);
1259         st[1].wDayOfWeek = MONTHCAL_CalculateDayOfWeek(st[1].wDay, st[1].wMonth,
1260                                                        st[1].wYear);
1261         /* a single current month used */
1262         return 1;
1263     }
1264     case GMR_DAYSTATE:
1265     {
1266         /*FIXME: currently multicalendar feature isn't implemented,
1267                  min date from previous month and max date from next one returned */
1268         MONTHCAL_GetMinDate(infoPtr, &st[0]);
1269         MONTHCAL_GetMaxDate(infoPtr, &st[1]);
1270         break;
1271     }
1272     default:
1273         WARN("Unknown flag value, got %d\n", flag);
1274     }
1275   }
1276
1277   return infoPtr->monthRange;
1278 }
1279
1280
1281 static LRESULT
1282 MONTHCAL_GetMaxTodayWidth(const MONTHCAL_INFO *infoPtr)
1283 {
1284   return(infoPtr->todayrect.right - infoPtr->todayrect.left);
1285 }
1286
1287
1288 static LRESULT
1289 MONTHCAL_SetRange(MONTHCAL_INFO *infoPtr, SHORT limits, SYSTEMTIME *range)
1290 {
1291     FILETIME ft_min, ft_max;
1292
1293     TRACE("%x %p\n", limits, range);
1294
1295     if ((limits & GDTR_MIN && !MONTHCAL_ValidateDate(&range[0])) ||
1296         (limits & GDTR_MAX && !MONTHCAL_ValidateDate(&range[1])))
1297         return FALSE;
1298
1299     if (limits & GDTR_MIN)
1300     {
1301         if (!MONTHCAL_ValidateTime(&range[0]))
1302             MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]);
1303
1304         infoPtr->minDate = range[0];
1305         infoPtr->rangeValid |= GDTR_MIN;
1306     }
1307     if (limits & GDTR_MAX)
1308     {
1309         if (!MONTHCAL_ValidateTime(&range[1]))
1310             MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]);
1311
1312         infoPtr->maxDate = range[1];
1313         infoPtr->rangeValid |= GDTR_MAX;
1314     }
1315
1316     /* Only one limit set - we are done */
1317     if ((infoPtr->rangeValid & (GDTR_MIN | GDTR_MAX)) != (GDTR_MIN | GDTR_MAX))
1318         return TRUE;
1319
1320     SystemTimeToFileTime(&infoPtr->maxDate, &ft_max);
1321     SystemTimeToFileTime(&infoPtr->minDate, &ft_min);
1322
1323     if (CompareFileTime(&ft_min, &ft_max) >= 0)
1324     {
1325         if ((limits & (GDTR_MIN | GDTR_MAX)) == (GDTR_MIN | GDTR_MAX))
1326         {
1327             /* Native swaps limits only when both limits are being set. */
1328             SYSTEMTIME st_tmp = infoPtr->minDate;
1329             infoPtr->minDate  = infoPtr->maxDate;
1330             infoPtr->maxDate  = st_tmp;
1331         }
1332         else
1333         {
1334             /* reset the other limit */
1335             if (limits & GDTR_MIN) infoPtr->maxDate = st_null;
1336             if (limits & GDTR_MAX) infoPtr->minDate = st_null;
1337             infoPtr->rangeValid &= limits & GDTR_MIN ? ~GDTR_MAX : ~GDTR_MIN;
1338         }
1339     }
1340
1341     return TRUE;
1342 }
1343
1344
1345 static LRESULT
1346 MONTHCAL_GetRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1347 {
1348   TRACE("%p\n", range);
1349
1350   if(!range) return FALSE;
1351
1352   range[1] = infoPtr->maxDate;
1353   range[0] = infoPtr->minDate;
1354
1355   return infoPtr->rangeValid;
1356 }
1357
1358
1359 static LRESULT
1360 MONTHCAL_SetDayState(const MONTHCAL_INFO *infoPtr, INT months, MONTHDAYSTATE *states)
1361 {
1362   int i;
1363
1364   TRACE("%d %p\n", months, states);
1365   if(months != infoPtr->monthRange) return 0;
1366
1367   for(i = 0; i < months; i++)
1368     infoPtr->monthdayState[i] = states[i];
1369
1370   return 1;
1371 }
1372
1373 static LRESULT
1374 MONTHCAL_GetCurSel(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel)
1375 {
1376   TRACE("%p\n", curSel);
1377   if(!curSel) return FALSE;
1378   if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
1379
1380   *curSel = infoPtr->curSel;
1381   TRACE("%d/%d/%d\n", curSel->wYear, curSel->wMonth, curSel->wDay);
1382   return TRUE;
1383 }
1384
1385 /* FIXME: if the specified date is not visible, make it visible */
1386 static LRESULT
1387 MONTHCAL_SetCurSel(MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel)
1388 {
1389   TRACE("%p\n", curSel);
1390   if(!curSel) return FALSE;
1391   if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
1392
1393   if(!MONTHCAL_ValidateDate(curSel)) return FALSE;
1394   /* exit earlier if selection equals current */
1395   if (MONTHCAL_IsDateEqual(&infoPtr->curSel, curSel)) return TRUE;
1396
1397   if(!MONTHCAL_IsDateInValidRange(infoPtr, curSel, FALSE)) return FALSE;
1398
1399   infoPtr->minSel = *curSel;
1400   infoPtr->maxSel = *curSel;
1401
1402   infoPtr->curSel = *curSel;
1403
1404   /* FIXME: it's possible to reduce rectangle here */
1405   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1406
1407   return TRUE;
1408 }
1409
1410
1411 static LRESULT
1412 MONTHCAL_GetMaxSelCount(const MONTHCAL_INFO *infoPtr)
1413 {
1414   return infoPtr->maxSelCount;
1415 }
1416
1417
1418 static LRESULT
1419 MONTHCAL_SetMaxSelCount(MONTHCAL_INFO *infoPtr, INT max)
1420 {
1421   TRACE("%d\n", max);
1422
1423   if(!(infoPtr->dwStyle & MCS_MULTISELECT)) return FALSE;
1424   if(max <= 0) return FALSE;
1425
1426   infoPtr->maxSelCount = max;
1427
1428   return TRUE;
1429 }
1430
1431
1432 static LRESULT
1433 MONTHCAL_GetSelRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1434 {
1435   TRACE("%p\n", range);
1436
1437   if(!range) return FALSE;
1438
1439   if(infoPtr->dwStyle & MCS_MULTISELECT)
1440   {
1441     range[1] = infoPtr->maxSel;
1442     range[0] = infoPtr->minSel;
1443     TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
1444     return TRUE;
1445   }
1446
1447   return FALSE;
1448 }
1449
1450
1451 static LRESULT
1452 MONTHCAL_SetSelRange(MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1453 {
1454   TRACE("%p\n", range);
1455
1456   if(!range) return FALSE;
1457
1458   if(infoPtr->dwStyle & MCS_MULTISELECT)
1459   {
1460     SYSTEMTIME old_range[2];
1461
1462     /* adjust timestamps */
1463     if(!MONTHCAL_ValidateTime(&range[0]))
1464       MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]);
1465     if(!MONTHCAL_ValidateTime(&range[1]))
1466       MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]);
1467
1468     /* maximum range exceeded */
1469     if(!MONTHCAL_IsSelRangeValid(infoPtr, &range[0], &range[1], NULL)) return FALSE;
1470
1471     old_range[0] = infoPtr->minSel;
1472     old_range[1] = infoPtr->maxSel;
1473
1474     /* swap if min > max */
1475     if(MONTHCAL_CompareSystemTime(&range[0], &range[1]) <= 0)
1476     {
1477       infoPtr->minSel = range[0];
1478       infoPtr->maxSel = range[1];
1479     }
1480     else
1481     {
1482       infoPtr->minSel = range[1];
1483       infoPtr->maxSel = range[0];
1484     }
1485
1486     /* update day of week */
1487     infoPtr->minSel.wDayOfWeek =
1488             MONTHCAL_CalculateDayOfWeek(infoPtr->minSel.wDay, infoPtr->minSel.wMonth,
1489                                                               infoPtr->minSel.wYear);
1490     infoPtr->maxSel.wDayOfWeek =
1491             MONTHCAL_CalculateDayOfWeek(infoPtr->maxSel.wDay, infoPtr->maxSel.wMonth,
1492                                                               infoPtr->maxSel.wYear);
1493
1494     /* redraw if bounds changed */
1495     /* FIXME: no actual need to redraw everything */
1496     if(!MONTHCAL_IsDateEqual(&old_range[0], &range[0]) ||
1497        !MONTHCAL_IsDateEqual(&old_range[1], &range[1]))
1498     {
1499        InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1500     }
1501
1502     TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
1503     return TRUE;
1504   }
1505
1506   return FALSE;
1507 }
1508
1509
1510 static LRESULT
1511 MONTHCAL_GetToday(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *today)
1512 {
1513   TRACE("%p\n", today);
1514
1515   if(!today) return FALSE;
1516   *today = infoPtr->todaysDate;
1517   return TRUE;
1518 }
1519
1520 /* Internal helper for MCM_SETTODAY handler and auto update timer handler
1521  *
1522  * RETURN VALUE
1523  *
1524  *  TRUE  - today date changed
1525  *  FALSE - today date isn't changed
1526  */
1527 static BOOL
1528 MONTHCAL_UpdateToday(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *today)
1529 {
1530   RECT new_r, old_r;
1531
1532   if(MONTHCAL_IsDateEqual(today, &infoPtr->todaysDate)) return FALSE;
1533
1534   MONTHCAL_CalcPosFromDay(infoPtr, infoPtr->todaysDate.wDay,
1535                                    infoPtr->todaysDate.wMonth, &old_r);
1536   MONTHCAL_CalcPosFromDay(infoPtr, today->wDay, today->wMonth, &new_r);
1537
1538   infoPtr->todaysDate = *today;
1539
1540   /* only two days need redrawing */
1541   InvalidateRect(infoPtr->hwndSelf, &old_r, FALSE);
1542   InvalidateRect(infoPtr->hwndSelf, &new_r, FALSE);
1543   return TRUE;
1544 }
1545
1546 /* MCM_SETTODAT handler */
1547 static LRESULT
1548 MONTHCAL_SetToday(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *today)
1549 {
1550   TRACE("%p\n", today);
1551
1552   if(!today) return FALSE;
1553
1554   /* remember if date was set successfully */
1555   if(MONTHCAL_UpdateToday(infoPtr, today)) infoPtr->todaySet = TRUE;
1556
1557   return TRUE;
1558 }
1559
1560 static LRESULT
1561 MONTHCAL_HitTest(const MONTHCAL_INFO *infoPtr, MCHITTESTINFO *lpht)
1562 {
1563   UINT x,y;
1564   DWORD retval;
1565   int day,wday,wnum;
1566
1567   if(!lpht || lpht->cbSize < MCHITTESTINFO_V1_SIZE) return -1;
1568
1569   x = lpht->pt.x;
1570   y = lpht->pt.y;
1571
1572   ZeroMemory(&lpht->st, sizeof(lpht->st));
1573
1574   /* Comment in for debugging...
1575   TRACE("%d %d wd[%d %d %d %d] d[%d %d %d %d] t[%d %d %d %d] wn[%d %d %d %d]\n", x, y,
1576         infoPtr->wdays.left, infoPtr->wdays.right,
1577         infoPtr->wdays.top, infoPtr->wdays.bottom,
1578         infoPtr->days.left, infoPtr->days.right,
1579         infoPtr->days.top, infoPtr->days.bottom,
1580         infoPtr->todayrect.left, infoPtr->todayrect.right,
1581         infoPtr->todayrect.top, infoPtr->todayrect.bottom,
1582         infoPtr->weeknums.left, infoPtr->weeknums.right,
1583         infoPtr->weeknums.top, infoPtr->weeknums.bottom);
1584   */
1585
1586   /* are we in the header? */
1587
1588   if(PtInRect(&infoPtr->title, lpht->pt)) {
1589     if(PtInRect(&infoPtr->titlebtnprev, lpht->pt)) {
1590       retval = MCHT_TITLEBTNPREV;
1591       goto done;
1592     }
1593     if(PtInRect(&infoPtr->titlebtnnext, lpht->pt)) {
1594       retval = MCHT_TITLEBTNNEXT;
1595       goto done;
1596     }
1597     if(PtInRect(&infoPtr->titlemonth, lpht->pt)) {
1598       retval = MCHT_TITLEMONTH;
1599       goto done;
1600     }
1601     if(PtInRect(&infoPtr->titleyear, lpht->pt)) {
1602       retval = MCHT_TITLEYEAR;
1603       goto done;
1604     }
1605
1606     retval = MCHT_TITLE;
1607     goto done;
1608   }
1609
1610   day = MONTHCAL_CalcDayFromPos(infoPtr,x,y,&wday,&wnum);
1611   if(PtInRect(&infoPtr->wdays, lpht->pt)) {
1612     retval = MCHT_CALENDARDAY;
1613     lpht->st.wYear  = infoPtr->curSel.wYear;
1614     lpht->st.wMonth = (day < 1)? infoPtr->curSel.wMonth -1 : infoPtr->curSel.wMonth;
1615     lpht->st.wDay   = (day < 1)?
1616       MONTHCAL_MonthLength(infoPtr->curSel.wMonth-1, infoPtr->curSel.wYear) -day : day;
1617     goto done;
1618   }
1619   if(PtInRect(&infoPtr->weeknums, lpht->pt)) {
1620     retval = MCHT_CALENDARWEEKNUM;
1621     lpht->st.wYear  = infoPtr->curSel.wYear;
1622     lpht->st.wMonth = (day < 1) ? infoPtr->curSel.wMonth -1 :
1623       (day > MONTHCAL_MonthLength(infoPtr->curSel.wMonth,infoPtr->curSel.wYear)) ?
1624       infoPtr->curSel.wMonth +1 :infoPtr->curSel.wMonth;
1625     lpht->st.wDay   = (day < 1 ) ?
1626       MONTHCAL_MonthLength(infoPtr->curSel.wMonth-1,infoPtr->curSel.wYear) -day :
1627       (day > MONTHCAL_MonthLength(infoPtr->curSel.wMonth,infoPtr->curSel.wYear)) ?
1628       day - MONTHCAL_MonthLength(infoPtr->curSel.wMonth,infoPtr->curSel.wYear) : day;
1629     goto done;
1630   }
1631   if(PtInRect(&infoPtr->days, lpht->pt))
1632   {
1633       lpht->st.wYear  = infoPtr->curSel.wYear;
1634       lpht->st.wMonth = infoPtr->curSel.wMonth;
1635       if (day < 1)
1636       {
1637           retval = MCHT_CALENDARDATEPREV;
1638           MONTHCAL_GetPrevMonth(&lpht->st);
1639           lpht->st.wDay = MONTHCAL_MonthLength(lpht->st.wMonth, lpht->st.wYear) + day;
1640       }
1641       else if (day > MONTHCAL_MonthLength(infoPtr->curSel.wMonth, infoPtr->curSel.wYear))
1642       {
1643           retval = MCHT_CALENDARDATENEXT;
1644           MONTHCAL_GetNextMonth(&lpht->st);
1645           lpht->st.wDay = day - MONTHCAL_MonthLength(infoPtr->curSel.wMonth, infoPtr->curSel.wYear);
1646       }
1647       else {
1648         retval = MCHT_CALENDARDATE;
1649         lpht->st.wDay = day;
1650       }
1651       /* always update day of week */
1652       lpht->st.wDayOfWeek = MONTHCAL_CalculateDayOfWeek(lpht->st.wDay, lpht->st.wMonth,
1653                                                              lpht->st.wYear);
1654       goto done;
1655   }
1656   if(PtInRect(&infoPtr->todayrect, lpht->pt)) {
1657     retval = MCHT_TODAYLINK;
1658     goto done;
1659   }
1660
1661
1662   /* Hit nothing special? What's left must be background :-) */
1663
1664   retval = MCHT_CALENDARBK;
1665  done:
1666   lpht->uHit = retval;
1667   return retval;
1668 }
1669
1670 /* MCN_GETDAYSTATE notification helper */
1671 static void MONTHCAL_NotifyDayState(MONTHCAL_INFO *infoPtr)
1672 {
1673   if(infoPtr->dwStyle & MCS_DAYSTATE) {
1674     NMDAYSTATE nmds;
1675     INT i;
1676
1677     nmds.nmhdr.hwndFrom = infoPtr->hwndSelf;
1678     nmds.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1679     nmds.nmhdr.code     = MCN_GETDAYSTATE;
1680     nmds.cDayState      = infoPtr->monthRange;
1681     nmds.prgDayState    = Alloc(infoPtr->monthRange * sizeof(MONTHDAYSTATE));
1682
1683     nmds.stStart = infoPtr->todaysDate;
1684     nmds.stStart.wYear  = infoPtr->curSel.wYear;
1685     nmds.stStart.wMonth = infoPtr->curSel.wMonth;
1686     nmds.stStart.wDay = 1;
1687
1688     SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmds.nmhdr.idFrom, (LPARAM)&nmds);
1689     for(i = 0; i < infoPtr->monthRange; i++)
1690       infoPtr->monthdayState[i] = nmds.prgDayState[i];
1691
1692     Free(nmds.prgDayState);
1693   }
1694 }
1695
1696 static void MONTHCAL_GoToNextMonth(MONTHCAL_INFO *infoPtr)
1697 {
1698   SYSTEMTIME next = infoPtr->curSel;
1699
1700   TRACE("\n");
1701
1702   MONTHCAL_GetNextMonth(&next);
1703
1704   if(!MONTHCAL_IsDateInValidRange(infoPtr, &next, FALSE)) return;
1705
1706   infoPtr->curSel = next;
1707
1708   MONTHCAL_NotifyDayState(infoPtr);
1709 }
1710
1711
1712 static void MONTHCAL_GoToPrevMonth(MONTHCAL_INFO *infoPtr)
1713 {
1714   SYSTEMTIME prev = infoPtr->curSel;
1715
1716   TRACE("\n");
1717
1718   MONTHCAL_GetPrevMonth(&prev);
1719
1720   if(!MONTHCAL_IsDateInValidRange(infoPtr, &prev, FALSE)) return;
1721
1722   infoPtr->curSel = prev;
1723
1724   MONTHCAL_NotifyDayState(infoPtr);
1725 }
1726
1727 static LRESULT
1728 MONTHCAL_RButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1729 {
1730   static const WCHAR todayW[] = { 'G','o',' ','t','o',' ','T','o','d','a','y',':',0 };
1731   HMENU hMenu;
1732   POINT menupoint;
1733   WCHAR buf[32];
1734
1735   hMenu = CreatePopupMenu();
1736   if (!LoadStringW(COMCTL32_hModule, IDM_GOTODAY, buf, countof(buf)))
1737   {
1738       WARN("Can't load resource\n");
1739       strcpyW(buf, todayW);
1740   }
1741   AppendMenuW(hMenu, MF_STRING|MF_ENABLED, 1, buf);
1742   menupoint.x = (short)LOWORD(lParam);
1743   menupoint.y = (short)HIWORD(lParam);
1744   ClientToScreen(infoPtr->hwndSelf, &menupoint);
1745   if( TrackPopupMenu(hMenu, TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD,
1746                      menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL))
1747   {
1748       infoPtr->curSel = infoPtr->todaysDate;
1749       infoPtr->minSel = infoPtr->todaysDate;
1750       infoPtr->maxSel = infoPtr->todaysDate;
1751       InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1752   }
1753
1754   return 0;
1755 }
1756
1757 /***
1758  * DESCRIPTION:
1759  * Subclassed edit control windproc function
1760  *
1761  * PARAMETER(S):
1762  * [I] hwnd : the edit window handle
1763  * [I] uMsg : the message that is to be processed
1764  * [I] wParam : first message parameter
1765  * [I] lParam : second message parameter
1766  *
1767  */
1768 static LRESULT CALLBACK EditWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1769 {
1770     MONTHCAL_INFO *infoPtr = (MONTHCAL_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
1771
1772     TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx)\n",
1773           hwnd, uMsg, wParam, lParam);
1774
1775     switch (uMsg)
1776     {
1777         case WM_GETDLGCODE:
1778           return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
1779
1780         case WM_DESTROY:
1781         {
1782             WNDPROC editProc = infoPtr->EditWndProc;
1783             infoPtr->EditWndProc = NULL;
1784             SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
1785             return CallWindowProcW(editProc, hwnd, uMsg, wParam, lParam);
1786         }
1787
1788         case WM_KILLFOCUS:
1789             break;
1790
1791         case WM_KEYDOWN:
1792             if ((VK_ESCAPE == (INT)wParam) || (VK_RETURN == (INT)wParam))
1793                 break;
1794
1795         default:
1796             return CallWindowProcW(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam);
1797     }
1798
1799     SendMessageW(infoPtr->hWndYearUpDown, WM_CLOSE, 0, 0);
1800     SendMessageW(hwnd, WM_CLOSE, 0, 0);
1801     return 0;
1802 }
1803
1804 /* creates updown control and edit box */
1805 static void MONTHCAL_EditYear(MONTHCAL_INFO *infoPtr)
1806 {
1807     infoPtr->hWndYearEdit =
1808         CreateWindowExW(0, WC_EDITW, 0, WS_VISIBLE | WS_CHILD | ES_READONLY,
1809                         infoPtr->titleyear.left + 3, infoPtr->titlebtnnext.top,
1810                         infoPtr->titleyear.right - infoPtr->titleyear.left + 4,
1811                         infoPtr->textHeight, infoPtr->hwndSelf,
1812                         NULL, NULL, NULL);
1813
1814     SendMessageW(infoPtr->hWndYearEdit, WM_SETFONT, (WPARAM)infoPtr->hBoldFont, TRUE);
1815
1816     infoPtr->hWndYearUpDown =
1817         CreateWindowExW(0, UPDOWN_CLASSW, 0,
1818                         WS_VISIBLE | WS_CHILD | UDS_SETBUDDYINT | UDS_NOTHOUSANDS | UDS_ARROWKEYS,
1819                         infoPtr->titleyear.right + 7, infoPtr->titlebtnnext.top,
1820                         18, infoPtr->textHeight, infoPtr->hwndSelf,
1821                         NULL, NULL, NULL);
1822
1823     /* attach edit box */
1824     SendMessageW(infoPtr->hWndYearUpDown, UDM_SETRANGE, 0,
1825                  MAKELONG(max_allowed_date.wYear, min_allowed_date.wYear));
1826     SendMessageW(infoPtr->hWndYearUpDown, UDM_SETBUDDY, (WPARAM)infoPtr->hWndYearEdit, 0);
1827     SendMessageW(infoPtr->hWndYearUpDown, UDM_SETPOS, 0, infoPtr->curSel.wYear);
1828
1829     /* subclass edit box */
1830     infoPtr->EditWndProc = (WNDPROC)SetWindowLongPtrW(infoPtr->hWndYearEdit,
1831                                   GWLP_WNDPROC, (DWORD_PTR)EditWndProc);
1832
1833     SetFocus(infoPtr->hWndYearEdit);
1834 }
1835
1836 static LRESULT
1837 MONTHCAL_LButtonDown(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1838 {
1839   MCHITTESTINFO ht;
1840   DWORD hit;
1841
1842   /* Actually we don't need input focus for calendar, this is used to kill
1843      year updown and its buddy edit box */
1844   if (IsWindow(infoPtr->hWndYearUpDown))
1845   {
1846       SetFocus(infoPtr->hwndSelf);
1847       return 0;
1848   }
1849
1850   SetCapture(infoPtr->hwndSelf);
1851
1852   ht.cbSize = sizeof(MCHITTESTINFO);
1853   ht.pt.x = (short)LOWORD(lParam);
1854   ht.pt.y = (short)HIWORD(lParam);
1855
1856   hit = MONTHCAL_HitTest(infoPtr, &ht);
1857
1858   TRACE("%x at (%d, %d)\n", hit, ht.pt.x, ht.pt.y);
1859
1860   switch(hit)
1861   {
1862   case MCHT_TITLEBTNNEXT:
1863     MONTHCAL_GoToNextMonth(infoPtr);
1864     infoPtr->status = MC_NEXTPRESSED;
1865     SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0);
1866     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1867     return 0;
1868
1869   case MCHT_TITLEBTNPREV:
1870     MONTHCAL_GoToPrevMonth(infoPtr);
1871     infoPtr->status = MC_PREVPRESSED;
1872     SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0);
1873     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1874     return 0;
1875
1876   case MCHT_TITLEMONTH:
1877   {
1878     HMENU hMenu = CreatePopupMenu();
1879     WCHAR buf[32];
1880     POINT menupoint;
1881     INT i;
1882
1883     for (i = 0; i < 12; i++)
1884     {
1885         GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+i, buf, countof(buf));
1886         AppendMenuW(hMenu, MF_STRING|MF_ENABLED, i + 1, buf);
1887     }
1888     menupoint.x = ht.pt.x;
1889     menupoint.y = ht.pt.y;
1890     ClientToScreen(infoPtr->hwndSelf, &menupoint);
1891     i = TrackPopupMenu(hMenu,TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON | TPM_RETURNCMD,
1892                        menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL);
1893
1894     if ((i > 0) && (i < 13) && infoPtr->curSel.wMonth != i)
1895     {
1896         infoPtr->curSel.wMonth = i;
1897         MONTHCAL_IsDateInValidRange(infoPtr, &infoPtr->curSel, TRUE);
1898         InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1899     }
1900     return 0;
1901   }
1902   case MCHT_TITLEYEAR:
1903   {
1904     MONTHCAL_EditYear(infoPtr);
1905     return 0;
1906   }
1907   case MCHT_TODAYLINK:
1908   {
1909     infoPtr->curSel = infoPtr->todaysDate;
1910     infoPtr->minSel = infoPtr->todaysDate;
1911     infoPtr->maxSel = infoPtr->todaysDate;
1912     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1913
1914     MONTHCAL_NotifySelectionChange(infoPtr);
1915     MONTHCAL_NotifySelect(infoPtr);
1916     return 0;
1917   }
1918   case MCHT_CALENDARDATENEXT:
1919   case MCHT_CALENDARDATEPREV:
1920   case MCHT_CALENDARDATE:
1921   {
1922     MONTHCAL_CopyDate(&ht.st, &infoPtr->firstSel);
1923
1924     if(infoPtr->dwStyle & MCS_MULTISELECT)
1925     {
1926       SYSTEMTIME st[2];
1927
1928       st[0] = st[1] = ht.st;
1929
1930       /* clear selection range */
1931       MONTHCAL_SetSelRange(infoPtr, st);
1932     }
1933
1934     infoPtr->status = MC_SEL_LBUTDOWN;
1935     MONTHCAL_SetDayFocus(infoPtr, &ht.st);
1936     return 0;
1937   }
1938   }
1939
1940   return 1;
1941 }
1942
1943
1944 static LRESULT
1945 MONTHCAL_LButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1946 {
1947   NMHDR nmhdr;
1948   MCHITTESTINFO ht;
1949   DWORD hit;
1950
1951   TRACE("\n");
1952
1953   if(infoPtr->status & (MC_PREVPRESSED | MC_NEXTPRESSED)) {
1954     RECT *r;
1955
1956     KillTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER);
1957     r = infoPtr->status & MC_PREVPRESSED ? &infoPtr->titlebtnprev : &infoPtr->titlebtnnext;
1958     infoPtr->status &= ~(MC_PREVPRESSED | MC_NEXTPRESSED);
1959
1960     InvalidateRect(infoPtr->hwndSelf, r, FALSE);
1961   }
1962
1963   ReleaseCapture();
1964
1965   /* always send NM_RELEASEDCAPTURE notification */
1966   nmhdr.hwndFrom = infoPtr->hwndSelf;
1967   nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1968   nmhdr.code     = NM_RELEASEDCAPTURE;
1969   TRACE("Sent notification from %p to %p\n", infoPtr->hwndSelf, infoPtr->hwndNotify);
1970
1971   SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmhdr.idFrom, (LPARAM)&nmhdr);
1972
1973   if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
1974
1975   ht.cbSize = sizeof(MCHITTESTINFO);
1976   ht.pt.x = (short)LOWORD(lParam);
1977   ht.pt.y = (short)HIWORD(lParam);
1978   hit = MONTHCAL_HitTest(infoPtr, &ht);
1979
1980   infoPtr->status = MC_SEL_LBUTUP;
1981   MONTHCAL_SetDayFocus(infoPtr, NULL);
1982
1983   if((hit & MCHT_CALENDARDATE) == MCHT_CALENDARDATE)
1984   {
1985     SYSTEMTIME sel = infoPtr->curSel;
1986
1987     if(!(infoPtr->dwStyle & MCS_MULTISELECT))
1988     {
1989         SYSTEMTIME st[2];
1990
1991         st[0] = st[1] = ht.st;
1992         MONTHCAL_SetSelRange(infoPtr, st);
1993         /* will be invalidated here */
1994         MONTHCAL_SetCurSel(infoPtr, &st[0]);
1995     }
1996
1997     /* send MCN_SELCHANGE only if new date selected */
1998     if (!MONTHCAL_IsDateEqual(&sel, &ht.st))
1999         MONTHCAL_NotifySelectionChange(infoPtr);
2000
2001     MONTHCAL_NotifySelect(infoPtr);
2002   }
2003
2004   return 0;
2005 }
2006
2007
2008 static LRESULT
2009 MONTHCAL_Timer(MONTHCAL_INFO *infoPtr, WPARAM id)
2010 {
2011   TRACE("%ld\n", id);
2012
2013   switch(id) {
2014   case MC_PREVNEXTMONTHTIMER:
2015     if(infoPtr->status & MC_NEXTPRESSED) MONTHCAL_GoToNextMonth(infoPtr);
2016     if(infoPtr->status & MC_PREVPRESSED) MONTHCAL_GoToPrevMonth(infoPtr);
2017     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2018     break;
2019   case MC_TODAYUPDATETIMER:
2020   {
2021     SYSTEMTIME st;
2022
2023     if(infoPtr->todaySet) return 0;
2024
2025     GetLocalTime(&st);
2026     MONTHCAL_UpdateToday(infoPtr, &st);
2027
2028     /* notification sent anyway */
2029     MONTHCAL_NotifySelectionChange(infoPtr);
2030
2031     return 0;
2032   }
2033   default:
2034     ERR("got unknown timer %ld\n", id);
2035     break;
2036   }
2037
2038   return 0;
2039 }
2040
2041
2042 static LRESULT
2043 MONTHCAL_MouseMove(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2044 {
2045   MCHITTESTINFO ht;
2046   SYSTEMTIME old_focused, st_ht;
2047   INT hit;
2048   RECT r;
2049
2050   if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
2051
2052   ht.cbSize = sizeof(MCHITTESTINFO);
2053   ht.pt.x = (short)LOWORD(lParam);
2054   ht.pt.y = (short)HIWORD(lParam);
2055
2056   hit = MONTHCAL_HitTest(infoPtr, &ht);
2057
2058   /* not on the calendar date numbers? bail out */
2059   TRACE("hit:%x\n",hit);
2060   if((hit & MCHT_CALENDARDATE) != MCHT_CALENDARDATE)
2061   {
2062     MONTHCAL_SetDayFocus(infoPtr, NULL);
2063     return 0;
2064   }
2065
2066   st_ht = ht.st;
2067   old_focused = infoPtr->focusedSel;
2068
2069   /* if pointer is over focused day still there's nothing to do */
2070   if(!MONTHCAL_SetDayFocus(infoPtr, &ht.st)) return 0;
2071
2072   MONTHCAL_CalcPosFromDay(infoPtr, ht.st.wDay, ht.st.wMonth, &r);
2073
2074   if(infoPtr->dwStyle & MCS_MULTISELECT) {
2075     SYSTEMTIME st[2];
2076
2077     MONTHCAL_GetSelRange(infoPtr, st);
2078
2079     /* If we're still at the first selected date and range is empty, return.
2080        If range isn't empty we should change range to a single firstSel */
2081     if(MONTHCAL_IsDateEqual(&infoPtr->firstSel, &st_ht) &&
2082        MONTHCAL_IsDateEqual(&st[0], &st[1])) goto done;
2083
2084     MONTHCAL_IsSelRangeValid(infoPtr, &st_ht, &infoPtr->firstSel, &st_ht);
2085
2086     st[0] = infoPtr->firstSel;
2087     /* we should overwrite timestamp here */
2088     MONTHCAL_CopyDate(&st_ht, &st[1]);
2089
2090     /* bounds will be swapped here if needed */
2091     MONTHCAL_SetSelRange(infoPtr, st);
2092
2093     return 0;
2094   }
2095
2096 done:
2097
2098   /* FIXME: this should specify a rectangle containing only the days that changed
2099      using InvalidateRect */
2100   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2101
2102   return 0;
2103 }
2104
2105
2106 static LRESULT
2107 MONTHCAL_Paint(MONTHCAL_INFO *infoPtr, HDC hdc_paint)
2108 {
2109   HDC hdc;
2110   PAINTSTRUCT ps;
2111
2112   if (hdc_paint)
2113   {
2114     GetClientRect(infoPtr->hwndSelf, &ps.rcPaint);
2115     hdc = hdc_paint;
2116   }
2117   else
2118     hdc = BeginPaint(infoPtr->hwndSelf, &ps);
2119
2120   MONTHCAL_Refresh(infoPtr, hdc, &ps);
2121   if (!hdc_paint) EndPaint(infoPtr->hwndSelf, &ps);
2122   return 0;
2123 }
2124
2125 static LRESULT
2126 MONTHCAL_SetFocus(const MONTHCAL_INFO *infoPtr)
2127 {
2128   TRACE("\n");
2129
2130   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2131
2132   return 0;
2133 }
2134
2135 /* sets the size information */
2136 static void MONTHCAL_UpdateSize(MONTHCAL_INFO *infoPtr)
2137 {
2138   static const WCHAR O0W[] = { '0','0',0 };
2139   HDC hdc = GetDC(infoPtr->hwndSelf);
2140   RECT *title=&infoPtr->title;
2141   RECT *prev=&infoPtr->titlebtnprev;
2142   RECT *next=&infoPtr->titlebtnnext;
2143   RECT *titlemonth=&infoPtr->titlemonth;
2144   RECT *titleyear=&infoPtr->titleyear;
2145   RECT *wdays=&infoPtr->wdays;
2146   RECT *weeknumrect=&infoPtr->weeknums;
2147   RECT *days=&infoPtr->days;
2148   RECT *todayrect=&infoPtr->todayrect;
2149   SIZE size, sz;
2150   TEXTMETRICW tm;
2151   HFONT currentFont;
2152   INT xdiv, dx, dy, i;
2153   RECT rcClient;
2154   WCHAR buff[80];
2155
2156   GetClientRect(infoPtr->hwndSelf, &rcClient);
2157
2158   currentFont = SelectObject(hdc, infoPtr->hFont);
2159
2160   /* get the height and width of each day's text */
2161   GetTextMetricsW(hdc, &tm);
2162   infoPtr->textHeight = tm.tmHeight + tm.tmExternalLeading + tm.tmInternalLeading;
2163
2164   /* find largest abbreviated day name for current locale */
2165   size.cx = sz.cx = 0;
2166   for (i = 0; i < 7; i++)
2167   {
2168       if(GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1 + i,
2169                         buff, countof(buff)))
2170       {
2171           GetTextExtentPoint32W(hdc, buff, lstrlenW(buff), &sz);
2172           if (sz.cx > size.cx) size.cx = sz.cx;
2173       }
2174       else /* locale independent fallback on failure */
2175       {
2176           static const WCHAR SunW[] = { 'S','u','n',0 };
2177
2178           GetTextExtentPoint32W(hdc, SunW, lstrlenW(SunW), &size);
2179           break;
2180       }
2181   }
2182
2183   infoPtr->textWidth = size.cx + 2;
2184
2185   /* recalculate the height and width increments and offsets */
2186   GetTextExtentPoint32W(hdc, O0W, 2, &size);
2187
2188   xdiv = (infoPtr->dwStyle & MCS_WEEKNUMBERS) ? 8 : 7;
2189
2190   infoPtr->width_increment  = size.cx * 2 + 4;
2191   infoPtr->height_increment = infoPtr->textHeight;
2192
2193   /* calculate title area */
2194   title->top    = 0;
2195   title->bottom = 3 * infoPtr->height_increment / 2;
2196   title->left   = 0;
2197   title->right  = infoPtr->width_increment * xdiv;
2198
2199   /* set the dimensions of the next and previous buttons and center */
2200   /* the month text vertically */
2201   prev->top    = next->top    = title->top + 4;
2202   prev->bottom = next->bottom = title->bottom - 4;
2203   prev->left   = title->left + 4;
2204   prev->right  = prev->left + (title->bottom - title->top);
2205   next->right  = title->right - 4;
2206   next->left   = next->right - (title->bottom - title->top);
2207
2208   /* titlemonth->left and right change based upon the current month */
2209   /* and are recalculated in refresh as the current month may change */
2210   /* without the control being resized */
2211   titlemonth->top    = titleyear->top    = title->top    + (infoPtr->height_increment)/2;
2212   titlemonth->bottom = titleyear->bottom = title->bottom - (infoPtr->height_increment)/2;
2213
2214   /* setup the dimensions of the rectangle we draw the names of the */
2215   /* days of the week in */
2216   weeknumrect->left = 0;
2217
2218   if(infoPtr->dwStyle & MCS_WEEKNUMBERS)
2219     weeknumrect->right = prev->right;
2220   else
2221     weeknumrect->right = weeknumrect->left;
2222
2223   wdays->left   = days->left   = weeknumrect->right;
2224   wdays->right  = days->right  = wdays->left + 7 * infoPtr->width_increment;
2225   wdays->top    = title->bottom;
2226   wdays->bottom = wdays->top + infoPtr->height_increment;
2227
2228   days->top    = weeknumrect->top = wdays->bottom;
2229   days->bottom = weeknumrect->bottom = days->top + 6 * infoPtr->height_increment;
2230
2231   todayrect->left   = 0;
2232   todayrect->right  = title->right;
2233   todayrect->top    = days->bottom;
2234   todayrect->bottom = days->bottom + infoPtr->height_increment;
2235
2236   /* offset all rectangles to center in client area */
2237   dx = (rcClient.right  - title->right) / 2;
2238   dy = (rcClient.bottom - todayrect->bottom) / 2;
2239
2240   /* if calendar doesn't fit client area show it at left/top bounds */
2241   if (title->left + dx < 0) dx = 0;
2242   if (title->top  + dy < 0) dy = 0;
2243
2244   if (dx != 0 || dy != 0)
2245   {
2246     OffsetRect(title, dx, dy);
2247     OffsetRect(prev,  dx, dy);
2248     OffsetRect(next,  dx, dy);
2249     OffsetRect(titlemonth, dx, dy);
2250     OffsetRect(titleyear, dx, dy);
2251     OffsetRect(wdays, dx, dy);
2252     OffsetRect(weeknumrect, dx, dy);
2253     OffsetRect(days, dx, dy);
2254     OffsetRect(todayrect, dx, dy);
2255   }
2256
2257   TRACE("dx=%d dy=%d client[%s] title[%s] wdays[%s] days[%s] today[%s]\n",
2258         infoPtr->width_increment,infoPtr->height_increment,
2259         wine_dbgstr_rect(&rcClient),
2260         wine_dbgstr_rect(title),
2261         wine_dbgstr_rect(wdays),
2262         wine_dbgstr_rect(days),
2263         wine_dbgstr_rect(todayrect));
2264
2265   /* restore the originally selected font */
2266   SelectObject(hdc, currentFont);
2267
2268   ReleaseDC(infoPtr->hwndSelf, hdc);
2269 }
2270
2271 static LRESULT MONTHCAL_Size(MONTHCAL_INFO *infoPtr, int Width, int Height)
2272 {
2273   TRACE("(width=%d, height=%d)\n", Width, Height);
2274
2275   MONTHCAL_UpdateSize(infoPtr);
2276
2277   /* invalidate client area and erase background */
2278   InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
2279
2280   return 0;
2281 }
2282
2283 static LRESULT MONTHCAL_GetFont(const MONTHCAL_INFO *infoPtr)
2284 {
2285     return (LRESULT)infoPtr->hFont;
2286 }
2287
2288 static LRESULT MONTHCAL_SetFont(MONTHCAL_INFO *infoPtr, HFONT hFont, BOOL redraw)
2289 {
2290     HFONT hOldFont;
2291     LOGFONTW lf;
2292
2293     if (!hFont) return 0;
2294
2295     hOldFont = infoPtr->hFont;
2296     infoPtr->hFont = hFont;
2297
2298     GetObjectW(infoPtr->hFont, sizeof(lf), &lf);
2299     lf.lfWeight = FW_BOLD;
2300     infoPtr->hBoldFont = CreateFontIndirectW(&lf);
2301
2302     MONTHCAL_UpdateSize(infoPtr);
2303
2304     if (redraw)
2305         InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2306
2307     return (LRESULT)hOldFont;
2308 }
2309
2310 /* update theme after a WM_THEMECHANGED message */
2311 static LRESULT theme_changed (const MONTHCAL_INFO* infoPtr)
2312 {
2313     HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
2314     CloseThemeData (theme);
2315     OpenThemeData (infoPtr->hwndSelf, themeClass);
2316     return 0;
2317 }
2318
2319 static INT MONTHCAL_StyleChanged(MONTHCAL_INFO *infoPtr, WPARAM wStyleType,
2320                                  const STYLESTRUCT *lpss)
2321 {
2322     TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
2323           wStyleType, lpss->styleOld, lpss->styleNew);
2324
2325     if (wStyleType != GWL_STYLE) return 0;
2326
2327     infoPtr->dwStyle = lpss->styleNew;
2328
2329     /* make room for week numbers */
2330     if ((lpss->styleNew ^ lpss->styleOld) & MCS_WEEKNUMBERS)
2331         MONTHCAL_UpdateSize(infoPtr);
2332
2333     return 0;
2334 }
2335
2336 static INT MONTHCAL_StyleChanging(MONTHCAL_INFO *infoPtr, WPARAM wStyleType,
2337                                   STYLESTRUCT *lpss)
2338 {
2339     TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
2340           wStyleType, lpss->styleOld, lpss->styleNew);
2341
2342     /* block MCS_MULTISELECT change */
2343     if ((lpss->styleNew ^ lpss->styleOld) & MCS_MULTISELECT)
2344     {
2345         if (lpss->styleOld & MCS_MULTISELECT)
2346             lpss->styleNew |= MCS_MULTISELECT;
2347         else
2348             lpss->styleNew &= ~MCS_MULTISELECT;
2349     }
2350
2351     return 0;
2352 }
2353
2354 /* FIXME: check whether dateMin/dateMax need to be adjusted. */
2355 static LRESULT
2356 MONTHCAL_Create(HWND hwnd, LPCREATESTRUCTW lpcs)
2357 {
2358   MONTHCAL_INFO *infoPtr;
2359
2360   /* allocate memory for info structure */
2361   infoPtr = Alloc(sizeof(MONTHCAL_INFO));
2362   SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2363
2364   if(infoPtr == NULL) {
2365     ERR( "could not allocate info memory!\n");
2366     return 0;
2367   }
2368
2369   infoPtr->hwndSelf = hwnd;
2370   infoPtr->hwndNotify = lpcs->hwndParent;
2371   infoPtr->dwStyle  = GetWindowLongW(hwnd, GWL_STYLE);
2372
2373   MONTHCAL_SetFont(infoPtr, GetStockObject(DEFAULT_GUI_FONT), FALSE);
2374
2375   /* initialize info structure */
2376   /* FIXME: calculate systemtime ->> localtime(substract timezoneinfo) */
2377
2378   GetLocalTime(&infoPtr->todaysDate);
2379   MONTHCAL_SetFirstDayOfWeek(infoPtr, -1);
2380
2381   infoPtr->maxSelCount   = (infoPtr->dwStyle & MCS_MULTISELECT) ? 7 : 1;
2382   infoPtr->monthRange    = 3;
2383   infoPtr->monthdayState = Alloc(infoPtr->monthRange * sizeof(MONTHDAYSTATE));
2384   infoPtr->titlebk       = comctl32_color.clrActiveCaption;
2385   infoPtr->titletxt      = comctl32_color.clrWindow;
2386   infoPtr->monthbk       = comctl32_color.clrWindow;
2387   infoPtr->trailingtxt   = comctl32_color.clrGrayText;
2388   infoPtr->bk            = comctl32_color.clrWindow;
2389   infoPtr->txt           = comctl32_color.clrWindowText;
2390
2391   infoPtr->minSel = infoPtr->todaysDate;
2392   infoPtr->maxSel = infoPtr->todaysDate;
2393   infoPtr->curSel = infoPtr->todaysDate;
2394
2395   /* call MONTHCAL_UpdateSize to set all of the dimensions */
2396   /* of the control */
2397   MONTHCAL_UpdateSize(infoPtr);
2398
2399   /* today auto update timer, to be freed only on control destruction */
2400   SetTimer(infoPtr->hwndSelf, MC_TODAYUPDATETIMER, MC_TODAYUPDATEDELAY, 0);
2401
2402   OpenThemeData (infoPtr->hwndSelf, themeClass);
2403
2404   return 0;
2405 }
2406
2407
2408 static LRESULT
2409 MONTHCAL_Destroy(MONTHCAL_INFO *infoPtr)
2410 {
2411   /* free month calendar info data */
2412   Free(infoPtr->monthdayState);
2413   SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
2414
2415   CloseThemeData (GetWindowTheme (infoPtr->hwndSelf));
2416   
2417   Free(infoPtr);
2418   return 0;
2419 }
2420
2421 /*
2422  * Handler for WM_NOTIFY messages
2423  */
2424 static LRESULT
2425 MONTHCAL_Notify(MONTHCAL_INFO *infoPtr, NMHDR *hdr)
2426 {
2427   /* notification from year edit updown */
2428   if (hdr->code == UDN_DELTAPOS)
2429   {
2430     NMUPDOWN *nmud = (NMUPDOWN*)hdr;
2431
2432     if (hdr->hwndFrom == infoPtr->hWndYearUpDown)
2433     {
2434       /* year value limits are set up explicitly after updown creation */
2435       if ((nmud->iDelta + nmud->iPos) != infoPtr->curSel.wYear)
2436       {
2437         SYSTEMTIME new_date = infoPtr->curSel;
2438
2439         new_date.wYear = nmud->iDelta + nmud->iPos;
2440         MONTHCAL_SetCurSel(infoPtr, &new_date);
2441       }
2442     }
2443   }
2444   return 0;
2445 }
2446
2447 static LRESULT WINAPI
2448 MONTHCAL_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2449 {
2450   MONTHCAL_INFO *infoPtr;
2451
2452   TRACE("hwnd=%p msg=%x wparam=%lx lparam=%lx\n", hwnd, uMsg, wParam, lParam);
2453
2454   infoPtr = MONTHCAL_GetInfoPtr(hwnd);
2455   if (!infoPtr && (uMsg != WM_CREATE))
2456     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
2457   switch(uMsg)
2458   {
2459   case MCM_GETCURSEL:
2460     return MONTHCAL_GetCurSel(infoPtr, (LPSYSTEMTIME)lParam);
2461
2462   case MCM_SETCURSEL:
2463     return MONTHCAL_SetCurSel(infoPtr, (LPSYSTEMTIME)lParam);
2464
2465   case MCM_GETMAXSELCOUNT:
2466     return MONTHCAL_GetMaxSelCount(infoPtr);
2467
2468   case MCM_SETMAXSELCOUNT:
2469     return MONTHCAL_SetMaxSelCount(infoPtr, wParam);
2470
2471   case MCM_GETSELRANGE:
2472     return MONTHCAL_GetSelRange(infoPtr, (LPSYSTEMTIME)lParam);
2473
2474   case MCM_SETSELRANGE:
2475     return MONTHCAL_SetSelRange(infoPtr, (LPSYSTEMTIME)lParam);
2476
2477   case MCM_GETMONTHRANGE:
2478     return MONTHCAL_GetMonthRange(infoPtr, wParam, (SYSTEMTIME*)lParam);
2479
2480   case MCM_SETDAYSTATE:
2481     return MONTHCAL_SetDayState(infoPtr, (INT)wParam, (LPMONTHDAYSTATE)lParam);
2482
2483   case MCM_GETMINREQRECT:
2484     return MONTHCAL_GetMinReqRect(infoPtr, (LPRECT)lParam);
2485
2486   case MCM_GETCOLOR:
2487     return MONTHCAL_GetColor(infoPtr, wParam);
2488
2489   case MCM_SETCOLOR:
2490     return MONTHCAL_SetColor(infoPtr, wParam, (COLORREF)lParam);
2491
2492   case MCM_GETTODAY:
2493     return MONTHCAL_GetToday(infoPtr, (LPSYSTEMTIME)lParam);
2494
2495   case MCM_SETTODAY:
2496     return MONTHCAL_SetToday(infoPtr, (LPSYSTEMTIME)lParam);
2497
2498   case MCM_HITTEST:
2499     return MONTHCAL_HitTest(infoPtr, (PMCHITTESTINFO)lParam);
2500
2501   case MCM_GETFIRSTDAYOFWEEK:
2502     return MONTHCAL_GetFirstDayOfWeek(infoPtr);
2503
2504   case MCM_SETFIRSTDAYOFWEEK:
2505     return MONTHCAL_SetFirstDayOfWeek(infoPtr, (INT)lParam);
2506
2507   case MCM_GETRANGE:
2508     return MONTHCAL_GetRange(infoPtr, (LPSYSTEMTIME)lParam);
2509
2510   case MCM_SETRANGE:
2511     return MONTHCAL_SetRange(infoPtr, (SHORT)wParam, (LPSYSTEMTIME)lParam);
2512
2513   case MCM_GETMONTHDELTA:
2514     return MONTHCAL_GetMonthDelta(infoPtr);
2515
2516   case MCM_SETMONTHDELTA:
2517     return MONTHCAL_SetMonthDelta(infoPtr, wParam);
2518
2519   case MCM_GETMAXTODAYWIDTH:
2520     return MONTHCAL_GetMaxTodayWidth(infoPtr);
2521
2522   case WM_GETDLGCODE:
2523     return DLGC_WANTARROWS | DLGC_WANTCHARS;
2524
2525   case WM_RBUTTONUP:
2526     return MONTHCAL_RButtonUp(infoPtr, lParam);
2527
2528   case WM_LBUTTONDOWN:
2529     return MONTHCAL_LButtonDown(infoPtr, lParam);
2530
2531   case WM_MOUSEMOVE:
2532     return MONTHCAL_MouseMove(infoPtr, lParam);
2533
2534   case WM_LBUTTONUP:
2535     return MONTHCAL_LButtonUp(infoPtr, lParam);
2536
2537   case WM_PRINTCLIENT:
2538   case WM_PAINT:
2539     return MONTHCAL_Paint(infoPtr, (HDC)wParam);
2540
2541   case WM_SETFOCUS:
2542     return MONTHCAL_SetFocus(infoPtr);
2543
2544   case WM_SIZE:
2545     return MONTHCAL_Size(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
2546
2547   case WM_NOTIFY:
2548     return MONTHCAL_Notify(infoPtr, (NMHDR*)lParam);
2549
2550   case WM_CREATE:
2551     return MONTHCAL_Create(hwnd, (LPCREATESTRUCTW)lParam);
2552
2553   case WM_SETFONT:
2554     return MONTHCAL_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);
2555
2556   case WM_GETFONT:
2557     return MONTHCAL_GetFont(infoPtr);
2558
2559   case WM_TIMER:
2560     return MONTHCAL_Timer(infoPtr, wParam);
2561     
2562   case WM_THEMECHANGED:
2563     return theme_changed (infoPtr);
2564
2565   case WM_DESTROY:
2566     return MONTHCAL_Destroy(infoPtr);
2567
2568   case WM_SYSCOLORCHANGE:
2569     COMCTL32_RefreshSysColors();
2570     return 0;
2571
2572   case WM_STYLECHANGED:
2573     return MONTHCAL_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
2574
2575   case WM_STYLECHANGING:
2576     return MONTHCAL_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
2577
2578   default:
2579     if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
2580       ERR( "unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
2581     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
2582   }
2583 }
2584
2585
2586 void
2587 MONTHCAL_Register(void)
2588 {
2589   WNDCLASSW wndClass;
2590
2591   ZeroMemory(&wndClass, sizeof(WNDCLASSW));
2592   wndClass.style         = CS_GLOBALCLASS;
2593   wndClass.lpfnWndProc   = MONTHCAL_WindowProc;
2594   wndClass.cbClsExtra    = 0;
2595   wndClass.cbWndExtra    = sizeof(MONTHCAL_INFO *);
2596   wndClass.hCursor       = LoadCursorW(0, (LPWSTR)IDC_ARROW);
2597   wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
2598   wndClass.lpszClassName = MONTHCAL_CLASSW;
2599
2600   RegisterClassW(&wndClass);
2601 }
2602
2603
2604 void
2605 MONTHCAL_Unregister(void)
2606 {
2607     UnregisterClassW(MONTHCAL_CLASSW, NULL);
2608 }