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