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