jscript: Added Object function invocation implementation.
[wine] / dlls / comctl32 / monthcal.c
1 /* Month calendar control
2
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  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23  *
24  * NOTE
25  * 
26  * This code was audited for completeness against the documented features
27  * of Comctl32.dll version 6.0 on Oct. 20, 2004, by Dimitrie O. Paun.
28  * 
29  * Unless otherwise noted, we believe this code to be complete, as per
30  * the specification mentioned above.
31  * If you discover missing features, or bugs, please note them below.
32  * 
33  * TODO:
34  *    -- MCM_[GS]ETUNICODEFORMAT
35  *    -- MONTHCAL_GetMonthRange
36  *    -- handle resources better (doesn't work now); 
37  *    -- take care of internationalization.
38  *    -- keyboard handling.
39  *    -- GetRange: At the moment, we copy ranges anyway, regardless of
40  *                 infoPtr->rangeValid; an invalid range is simply filled 
41  *                 with zeros in SetRange.  Is this the right behavior?
42  *    -- search for FIXME
43  */
44
45 #include <math.h>
46 #include <stdarg.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50
51 #include "windef.h"
52 #include "winbase.h"
53 #include "wingdi.h"
54 #include "winuser.h"
55 #include "winnls.h"
56 #include "commctrl.h"
57 #include "comctl32.h"
58 #include "uxtheme.h"
59 #include "tmschema.h"
60 #include "wine/unicode.h"
61 #include "wine/debug.h"
62
63 WINE_DEFAULT_DEBUG_CHANNEL(monthcal);
64
65 #define MC_SEL_LBUTUP       1   /* Left button released */
66 #define MC_SEL_LBUTDOWN     2   /* Left button pressed in calendar */
67 #define MC_PREVPRESSED      4   /* Prev month button pressed */
68 #define MC_NEXTPRESSED      8   /* Next month button pressed */
69 #define MC_NEXTMONTHDELAY   350 /* when continuously pressing `next */
70                                                                                 /* month', wait 500 ms before going */
71                                                                                 /* to the next month */
72 #define MC_NEXTMONTHTIMER   1                   /* Timer ID's */
73 #define MC_PREVMONTHTIMER   2
74
75 #define countof(arr) (sizeof(arr)/sizeof(arr[0]))
76
77 typedef struct
78 {
79     HWND        hwndSelf;
80     DWORD       dwStyle; /* cached GWL_STYLE */
81     COLORREF    bk;
82     COLORREF    txt;
83     COLORREF    titlebk;
84     COLORREF    titletxt;
85     COLORREF    monthbk;
86     COLORREF    trailingtxt;
87     HFONT       hFont;
88     HFONT       hBoldFont;
89     int         textHeight;
90     int         textWidth;
91     int         height_increment;
92     int         width_increment;
93     int         firstDayplace; /* place of the first day of the current month */
94     int         delta;  /* scroll rate; # of months that the */
95                         /* control moves when user clicks a scroll button */
96     int         visible;        /* # of months visible */
97     int         firstDay;       /* Start month calendar with firstDay's day */
98     int         firstDayHighWord;    /* High word only used externally */
99     int         monthRange;
100     MONTHDAYSTATE *monthdayState;
101     SYSTEMTIME  todaysDate;
102     DWORD       currentMonth;
103     DWORD       currentYear;
104     int         status;         /* See MC_SEL flags */
105     int         curSelDay;      /* current selected day */
106     int         firstSelDay;    /* first selected day */
107     int         maxSelCount;
108     SYSTEMTIME  minSel;
109     SYSTEMTIME  maxSel;
110     DWORD       rangeValid;
111     SYSTEMTIME  minDate;
112     SYSTEMTIME  maxDate;
113
114     RECT title;         /* rect for the header above the calendar */
115     RECT titlebtnnext;  /* the `next month' button in the header */
116     RECT titlebtnprev;  /* the `prev month' button in the header */
117     RECT titlemonth;    /* the `month name' txt in the header */
118     RECT titleyear;     /* the `year number' txt in the header */
119     RECT wdays;         /* week days at top */
120     RECT days;          /* calendar area */
121     RECT weeknums;      /* week numbers at left side */
122     RECT todayrect;     /* `today: xx/xx/xx' text rect */
123     HWND hwndNotify;    /* Window to receive the notifications */
124     HWND hWndYearEdit;  /* Window Handle of edit box to handle years */
125     HWND hWndYearUpDown;/* Window Handle of updown box to handle years */
126 } MONTHCAL_INFO, *LPMONTHCAL_INFO;
127
128
129 /* Offsets of days in the week to the weekday of january 1 in a leap year */
130 static const int DayOfWeekTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
131
132 static const WCHAR themeClass[] = { 'S','c','r','o','l','l','b','a','r',0 };
133
134 #define MONTHCAL_GetInfoPtr(hwnd) ((MONTHCAL_INFO *)GetWindowLongPtrW(hwnd, 0))
135
136 /* helper functions  */
137
138 /* returns the number of days in any given month, checking for leap days */
139 /* january is 1, december is 12 */
140 int MONTHCAL_MonthLength(int month, int year)
141 {
142   const int mdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
143   /*Wrap around, this eases handling*/
144   if(month == 0)
145     month = 12;
146   if(month == 13)
147     month = 1;
148
149   /* if we have a leap year add 1 day to February */
150   /* a leap year is a year either divisible by 400 */
151   /* or divisible by 4 and not by 100 */
152   if(month == 2) { /* February */
153     return mdays[month - 1] + ((year%400 == 0) ? 1 : ((year%100 != 0) &&
154      (year%4 == 0)) ? 1 : 0);
155   }
156   else {
157     return mdays[month - 1];
158   }
159 }
160
161
162 /* make sure that time is valid */
163 static int MONTHCAL_ValidateTime(SYSTEMTIME time)
164 {
165   if(time.wMonth < 1 || time.wMonth > 12 ) return FALSE;
166   if(time.wDayOfWeek > 6) return FALSE;
167   if(time.wDay > MONTHCAL_MonthLength(time.wMonth, time.wYear))
168           return FALSE;
169
170   return TRUE;
171 }
172
173
174 /* Note:Depending on DST, this may be offset by a day.
175    Need to find out if we're on a DST place & adjust the clock accordingly.
176    Above function assumes we have a valid data.
177    Valid for year>1752;  1 <= d <= 31, 1 <= m <= 12.
178    0 = Sunday.
179 */
180
181 /* returns the day in the week(0 == sunday, 6 == saturday) */
182 /* day(1 == 1st, 2 == 2nd... etc), year is the  year value */
183 static int MONTHCAL_CalculateDayOfWeek(DWORD day, DWORD month, DWORD year)
184 {
185   year-=(month < 3);
186
187   return((year + year/4 - year/100 + year/400 +
188          DayOfWeekTable[month-1] + day ) % 7);
189 }
190
191 /* From a given point, calculate the row (weekpos), column(daypos)
192    and day in the calendar. day== 0 mean the last day of tha last month
193 */
194 static int MONTHCAL_CalcDayFromPos(const MONTHCAL_INFO *infoPtr, int x, int y,
195                                    int *daypos,int *weekpos)
196 {
197   int retval, firstDay;
198   RECT rcClient;
199
200   GetClientRect(infoPtr->hwndSelf, &rcClient);
201
202   /* if the point is outside the x bounds of the window put
203   it at the boundary */
204   if (x > rcClient.right)
205     x = rcClient.right;
206
207
208   *daypos = (x - infoPtr->days.left ) / infoPtr->width_increment;
209   *weekpos = (y - infoPtr->days.top ) / infoPtr->height_increment;
210
211   firstDay = (MONTHCAL_CalculateDayOfWeek(1, infoPtr->currentMonth, infoPtr->currentYear)+6 - infoPtr->firstDay)%7;
212   retval = *daypos + (7 * *weekpos) - firstDay;
213   return retval;
214 }
215
216 /* day is the day of the month, 1 == 1st day of the month */
217 /* sets x and y to be the position of the day */
218 /* x == day, y == week where(0,0) == firstDay, 1st week */
219 static void MONTHCAL_CalcDayXY(const MONTHCAL_INFO *infoPtr, int day, int month,
220                                  int *x, int *y)
221 {
222   int firstDay, prevMonth;
223
224   firstDay = (MONTHCAL_CalculateDayOfWeek(1, infoPtr->currentMonth, infoPtr->currentYear) +6 - infoPtr->firstDay)%7;
225
226   if(month==infoPtr->currentMonth) {
227     *x = (day + firstDay) % 7;
228     *y = (day + firstDay - *x) / 7;
229     return;
230   }
231   if(month < infoPtr->currentMonth) {
232     prevMonth = month - 1;
233     if(prevMonth==0)
234        prevMonth = 12;
235
236     *x = (MONTHCAL_MonthLength(prevMonth, infoPtr->currentYear) - firstDay) % 7;
237     *y = 0;
238     return;
239   }
240
241   *y = MONTHCAL_MonthLength(month, infoPtr->currentYear - 1) / 7;
242   *x = (day + firstDay + MONTHCAL_MonthLength(month,
243        infoPtr->currentYear)) % 7;
244 }
245
246
247 /* x: column(day), y: row(week) */
248 static void MONTHCAL_CalcDayRect(const MONTHCAL_INFO *infoPtr, RECT *r, int x, int y)
249 {
250   r->left = infoPtr->days.left + x * infoPtr->width_increment;
251   r->right = r->left + infoPtr->width_increment;
252   r->top  = infoPtr->days.top  + y * infoPtr->height_increment;
253   r->bottom = r->top + infoPtr->textHeight;
254 }
255
256
257 /* sets the RECT struct r to the rectangle around the day and month */
258 /* day is the day value of the month(1 == 1st), month is the month */
259 /* value(january == 1, december == 12) */
260 static inline void MONTHCAL_CalcPosFromDay(const MONTHCAL_INFO *infoPtr,
261                                             int day, int month, RECT *r)
262 {
263   int x, y;
264
265   MONTHCAL_CalcDayXY(infoPtr, day, month, &x, &y);
266   MONTHCAL_CalcDayRect(infoPtr, r, x, y);
267 }
268
269
270 /* day is the day in the month(1 == 1st of the month) */
271 /* month is the month value(1 == january, 12 == december) */
272 static void MONTHCAL_CircleDay(const MONTHCAL_INFO *infoPtr, HDC hdc, int day, int month)
273 {
274   HPEN hRedPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
275   HPEN hOldPen2 = SelectObject(hdc, hRedPen);
276   HBRUSH hOldBrush;
277   RECT day_rect;
278
279   MONTHCAL_CalcPosFromDay(infoPtr, day, month, &day_rect);
280
281   hOldBrush = SelectObject(hdc, GetStockObject(NULL_BRUSH));
282   Rectangle(hdc, day_rect.left, day_rect.top, day_rect.right, day_rect.bottom);
283
284   SelectObject(hdc, hOldBrush);
285   DeleteObject(hRedPen);
286   SelectObject(hdc, hOldPen2);
287 }
288
289 static void MONTHCAL_DrawDay(const MONTHCAL_INFO *infoPtr, HDC hdc, int day, int month,
290                              int x, int y, int bold)
291 {
292   static const WCHAR fmtW[] = { '%','d',0 };
293   WCHAR buf[10];
294   RECT r;
295   static BOOL haveBoldFont, haveSelectedDay = FALSE;
296   HBRUSH hbr;
297   COLORREF oldCol = 0;
298   COLORREF oldBk = 0;
299
300   wsprintfW(buf, fmtW, day);
301
302 /* No need to check styles: when selection is not valid, it is set to zero.
303  * 1<day<31, so everything is OK.
304  */
305
306   MONTHCAL_CalcDayRect(infoPtr, &r, x, y);
307
308   if((day>=infoPtr->minSel.wDay) && (day<=infoPtr->maxSel.wDay)
309        && (month==infoPtr->currentMonth)) {
310     RECT r2;
311
312     TRACE("%d %d %d\n",day, infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
313     TRACE("%s\n", wine_dbgstr_rect(&r));
314     oldCol = SetTextColor(hdc, infoPtr->monthbk);
315     oldBk = SetBkColor(hdc, infoPtr->trailingtxt);
316     hbr = GetSysColorBrush(COLOR_HIGHLIGHT);
317     FillRect(hdc, &r, hbr);
318
319     /* FIXME: this may need to be changed now b/c of the other
320         drawing changes 11/3/99 CMM */
321     r2.left   = r.left - 0.25 * infoPtr->textWidth;
322     r2.top    = r.top;
323     r2.right  = r.left + 0.5 * infoPtr->textWidth;
324     r2.bottom = r.bottom;
325     if(haveSelectedDay) FillRect(hdc, &r2, hbr);
326       haveSelectedDay = TRUE;
327   } else {
328     haveSelectedDay = FALSE;
329   }
330
331   /* need to add some code for multiple selections */
332
333   if((bold) &&(!haveBoldFont)) {
334     SelectObject(hdc, infoPtr->hBoldFont);
335     haveBoldFont = TRUE;
336   }
337   if((!bold) &&(haveBoldFont)) {
338     SelectObject(hdc, infoPtr->hFont);
339     haveBoldFont = FALSE;
340   }
341
342   SetBkMode(hdc,TRANSPARENT);
343   DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
344
345   if(haveSelectedDay) {
346     SetTextColor(hdc, oldCol);
347     SetBkColor(hdc, oldBk);
348   }
349
350   /* draw a rectangle around the currently selected days text */
351   if((day==infoPtr->curSelDay) && (month==infoPtr->currentMonth))
352     DrawFocusRect(hdc, &r);
353 }
354
355
356 static void paint_button (const MONTHCAL_INFO *infoPtr, HDC hdc, BOOL btnNext,
357                           BOOL pressed, RECT* r)
358 {
359     HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
360     
361     if (theme)
362     {
363         static const int states[] = {
364             /* Prev button */
365             ABS_LEFTNORMAL,  ABS_LEFTPRESSED,  ABS_LEFTDISABLED,
366             /* Next button */
367             ABS_RIGHTNORMAL, ABS_RIGHTPRESSED, ABS_RIGHTDISABLED
368         };
369         int stateNum = btnNext ? 3 : 0;
370         if (pressed)
371             stateNum += 1;
372         else
373         {
374             DWORD dwStyle = GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE);
375             if (dwStyle & WS_DISABLED) stateNum += 2;
376         }
377         DrawThemeBackground (theme, hdc, SBP_ARROWBTN, states[stateNum], r, NULL);
378     }
379     else
380     {
381         int style = btnNext ? DFCS_SCROLLRIGHT : DFCS_SCROLLLEFT;
382         if (pressed)
383             style |= DFCS_PUSHED;
384         else
385         {
386             DWORD dwStyle = GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE);
387             if (dwStyle & WS_DISABLED) style |= DFCS_INACTIVE;
388         }
389         
390         DrawFrameControl(hdc, r, DFC_SCROLL, style);
391     }
392 }
393
394
395 static void MONTHCAL_Refresh(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
396 {
397   static const WCHAR todayW[] = { 'T','o','d','a','y',':',0 };
398   static const WCHAR fmt1W[] = { '%','s',' ','%','l','d',0 };
399   static const WCHAR fmt2W[] = { '%','s',' ','%','s',0 };
400   static const WCHAR fmt3W[] = { '%','d',0 };
401   RECT *title=&infoPtr->title;
402   RECT *prev=&infoPtr->titlebtnprev;
403   RECT *next=&infoPtr->titlebtnnext;
404   RECT *titlemonth=&infoPtr->titlemonth;
405   RECT *titleyear=&infoPtr->titleyear;
406   RECT dayrect;
407   RECT *days=&dayrect;
408   RECT rtoday;
409   int i, j, m, mask, day, firstDay, weeknum, weeknum1,prevMonth;
410   int textHeight = infoPtr->textHeight;
411   SIZE size;
412   HBRUSH hbr;
413   HFONT currentFont;
414   WCHAR buf[20];
415   WCHAR buf1[20];
416   WCHAR buf2[32];
417   COLORREF oldTextColor, oldBkColor;
418   DWORD dwStyle = GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE);
419   RECT rcTemp;
420   RECT rcDay; /* used in MONTHCAL_CalcDayRect() */
421   SYSTEMTIME localtime;
422   int startofprescal;
423
424   oldTextColor = SetTextColor(hdc, comctl32_color.clrWindowText);
425
426   /* fill background */
427   hbr = CreateSolidBrush (infoPtr->bk);
428   FillRect(hdc, &ps->rcPaint, hbr);
429   DeleteObject(hbr);
430
431   /* draw header */
432   if(IntersectRect(&rcTemp, &(ps->rcPaint), title))
433   {
434     hbr =  CreateSolidBrush(infoPtr->titlebk);
435     FillRect(hdc, title, hbr);
436     DeleteObject(hbr);
437   }
438
439   /* if the previous button is pressed draw it depressed */
440   if(IntersectRect(&rcTemp, &(ps->rcPaint), prev))
441     paint_button (infoPtr, hdc, FALSE, infoPtr->status & MC_PREVPRESSED, prev);
442
443   /* if next button is depressed draw it depressed */
444   if(IntersectRect(&rcTemp, &(ps->rcPaint), next))
445     paint_button (infoPtr, hdc, TRUE, infoPtr->status & MC_NEXTPRESSED, next);
446
447   oldBkColor = SetBkColor(hdc, infoPtr->titlebk);
448   SetTextColor(hdc, infoPtr->titletxt);
449   currentFont = SelectObject(hdc, infoPtr->hBoldFont);
450
451   GetLocaleInfoW( LOCALE_USER_DEFAULT,LOCALE_SMONTHNAME1+infoPtr->currentMonth -1,
452                   buf1,countof(buf1));
453   wsprintfW(buf, fmt1W, buf1, infoPtr->currentYear);
454
455   if(IntersectRect(&rcTemp, &(ps->rcPaint), title))
456   {
457     DrawTextW(hdc, buf, strlenW(buf), title,
458                         DT_CENTER | DT_VCENTER | DT_SINGLELINE);
459   }
460
461 /* titlemonth left/right contained rect for whole titletxt('June  1999')
462   * MCM_HitTestInfo wants month & year rects, so prepare these now.
463   *(no, we can't draw them separately; the whole text is centered)
464   */
465   GetTextExtentPoint32W(hdc, buf, strlenW(buf), &size);
466   titlemonth->left = title->right / 2 + title->left / 2 - size.cx / 2;
467   titleyear->right = title->right / 2 + title->left / 2 + size.cx / 2;
468   GetTextExtentPoint32W(hdc, buf1, strlenW(buf1), &size);
469   titlemonth->right = titlemonth->left + size.cx;
470   titleyear->left = titlemonth->right;
471
472   /* draw month area */
473   rcTemp.top=infoPtr->wdays.top;
474   rcTemp.left=infoPtr->wdays.left;
475   rcTemp.bottom=infoPtr->todayrect.bottom;
476   rcTemp.right =infoPtr->todayrect.right;
477   if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcTemp))
478   {
479     hbr =  CreateSolidBrush(infoPtr->monthbk);
480     FillRect(hdc, &rcTemp, hbr);
481     DeleteObject(hbr);
482   }
483
484 /* draw line under day abbreviations */
485
486   MoveToEx(hdc, infoPtr->days.left + 3, title->bottom + textHeight + 1, NULL);
487   LineTo(hdc, infoPtr->days.right - 3, title->bottom + textHeight + 1);
488
489   prevMonth = infoPtr->currentMonth - 1;
490   if(prevMonth == 0) /* if currentMonth is january(1) prevMonth is */
491     prevMonth = 12;    /* december(12) of the previous year */
492
493   infoPtr->wdays.left   = infoPtr->days.left   = infoPtr->weeknums.right;
494 /* draw day abbreviations */
495
496   SelectObject(hdc, infoPtr->hFont);
497   SetBkColor(hdc, infoPtr->monthbk);
498   SetTextColor(hdc, infoPtr->trailingtxt);
499
500   /* copy this rect so we can change the values without changing */
501   /* the original version */
502   days->left = infoPtr->wdays.left;
503   days->right = days->left + infoPtr->width_increment;
504   days->top = infoPtr->wdays.top;
505   days->bottom = infoPtr->wdays.bottom;
506
507   i = infoPtr->firstDay;
508
509   for(j=0; j<7; j++) {
510     GetLocaleInfoW( LOCALE_USER_DEFAULT,LOCALE_SABBREVDAYNAME1 + (i+j+6)%7, buf, countof(buf));
511     DrawTextW(hdc, buf, strlenW(buf), days, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
512     days->left+=infoPtr->width_increment;
513     days->right+=infoPtr->width_increment;
514   }
515
516 /* draw day numbers; first, the previous month */
517
518   firstDay = MONTHCAL_CalculateDayOfWeek(1, infoPtr->currentMonth, infoPtr->currentYear);
519
520   day = MONTHCAL_MonthLength(prevMonth, infoPtr->currentYear)  +
521     (infoPtr->firstDay + 7  - firstDay)%7 + 1;
522   if (day > MONTHCAL_MonthLength(prevMonth, infoPtr->currentYear))
523     day -=7;
524   startofprescal = day;
525   mask = 1<<(day-1);
526
527   i = 0;
528   m = 0;
529   while(day <= MONTHCAL_MonthLength(prevMonth, infoPtr->currentYear)) {
530     MONTHCAL_CalcDayRect(infoPtr, &rcDay, i, 0);
531     if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcDay))
532     {
533       MONTHCAL_DrawDay(infoPtr, hdc, day, prevMonth, i, 0,
534           infoPtr->monthdayState[m] & mask);
535     }
536
537     mask<<=1;
538     day++;
539     i++;
540   }
541
542 /* draw `current' month  */
543
544   day = 1; /* start at the beginning of the current month */
545
546   infoPtr->firstDayplace = i;
547   SetTextColor(hdc, infoPtr->txt);
548   m++;
549   mask = 1;
550
551   /* draw the first week of the current month */
552   while(i<7) {
553     MONTHCAL_CalcDayRect(infoPtr, &rcDay, i, 0);
554     if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcDay))
555     {
556
557       MONTHCAL_DrawDay(infoPtr, hdc, day, infoPtr->currentMonth, i, 0,
558         infoPtr->monthdayState[m] & mask);
559
560       if((infoPtr->currentMonth==infoPtr->todaysDate.wMonth) &&
561           (day==infoPtr->todaysDate.wDay) &&
562           (infoPtr->currentYear == infoPtr->todaysDate.wYear)) {
563         if(!(dwStyle & MCS_NOTODAYCIRCLE))
564           MONTHCAL_CircleDay(infoPtr, hdc, day, infoPtr->currentMonth);
565       }
566     }
567
568     mask<<=1;
569     day++;
570     i++;
571   }
572
573   j = 1; /* move to the 2nd week of the current month */
574   i = 0; /* move back to sunday */
575   while(day <= MONTHCAL_MonthLength(infoPtr->currentMonth, infoPtr->currentYear)) {
576     MONTHCAL_CalcDayRect(infoPtr, &rcDay, i, j);
577     if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcDay))
578     {
579       MONTHCAL_DrawDay(infoPtr, hdc, day, infoPtr->currentMonth, i, j,
580           infoPtr->monthdayState[m] & mask);
581
582       if((infoPtr->currentMonth==infoPtr->todaysDate.wMonth) &&
583           (day==infoPtr->todaysDate.wDay) &&
584           (infoPtr->currentYear == infoPtr->todaysDate.wYear))
585         if(!(dwStyle & MCS_NOTODAYCIRCLE))
586           MONTHCAL_CircleDay(infoPtr, hdc, day, infoPtr->currentMonth);
587     }
588     mask<<=1;
589     day++;
590     i++;
591     if(i>6) { /* past saturday, goto the next weeks sunday */
592       i = 0;
593       j++;
594     }
595   }
596
597 /*  draw `next' month */
598
599   day = 1; /* start at the first day of the next month */
600   m++;
601   mask = 1;
602
603   SetTextColor(hdc, infoPtr->trailingtxt);
604   while((i<7) &&(j<6)) {
605     MONTHCAL_CalcDayRect(infoPtr, &rcDay, i, j);
606     if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcDay))
607     {
608       MONTHCAL_DrawDay(infoPtr, hdc, day, infoPtr->currentMonth + 1, i, j,
609                 infoPtr->monthdayState[m] & mask);
610     }
611
612     mask<<=1;
613     day++;
614     i++;
615     if(i==7) { /* past saturday, go to next week's sunday */
616       i = 0;
617       j++;
618     }
619   }
620   SetTextColor(hdc, infoPtr->txt);
621
622
623 /* draw `today' date if style allows it, and draw a circle before today's
624  * date if necessary */
625
626   if(!(dwStyle & MCS_NOTODAY))  {
627     if(!(dwStyle & MCS_NOTODAYCIRCLE))  {
628       /*day is the number of days from nextmonth we put on the calendar */
629       MONTHCAL_CircleDay(infoPtr, hdc,
630                          day+MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear),
631                          infoPtr->currentMonth);
632     }
633     if (!LoadStringW(COMCTL32_hModule,IDM_TODAY,buf1,countof(buf1)))
634       {
635         WARN("Can't load resource\n");
636         strcpyW(buf1, todayW);
637       }
638     MONTHCAL_CalcDayRect(infoPtr, &rtoday, 1, 6);
639     MONTHCAL_CopyTime(&infoPtr->todaysDate,&localtime);
640     GetDateFormatW(LOCALE_USER_DEFAULT,DATE_SHORTDATE,&localtime,NULL,buf2,countof(buf2));
641     wsprintfW(buf, fmt2W, buf1, buf2);
642     SelectObject(hdc, infoPtr->hBoldFont);
643
644     DrawTextW(hdc, buf, -1, &rtoday, DT_CALCRECT | DT_LEFT | DT_VCENTER | DT_SINGLELINE);
645     if(IntersectRect(&rcTemp, &(ps->rcPaint), &rtoday))
646     {
647       DrawTextW(hdc, buf, -1, &rtoday, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
648     }
649     SelectObject(hdc, infoPtr->hFont);
650   }
651
652 /*eventually draw week numbers*/
653   if(dwStyle & MCS_WEEKNUMBERS)  {
654     /* display weeknumbers*/
655     int mindays;
656
657     /* Rules what week to call the first week of a new year:
658        LOCALE_IFIRSTWEEKOFYEAR == 0 (e.g US?):
659        The week containing Jan 1 is the first week of year
660        LOCALE_IFIRSTWEEKOFYEAR == 2 (e.g. Germany):
661        First week of year must contain 4 days of the new year
662        LOCALE_IFIRSTWEEKOFYEAR == 1  (what contries?)
663        The first week of the year must contain only days of the new year
664     */
665     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, buf, countof(buf));
666     weeknum = atoiW(buf);
667     switch (weeknum)
668       {
669       case 1: mindays = 6;
670         break;
671       case 2: mindays = 3;
672         break;
673       case 0:
674       default:
675         mindays = 0;
676       }
677     if (infoPtr->currentMonth < 2)
678       {
679         /* calculate all those exceptions for january */
680         weeknum1=MONTHCAL_CalculateDayOfWeek(1,1,infoPtr->currentYear);
681         if ((infoPtr->firstDay +7 - weeknum1)%7 > mindays)
682             weeknum =1;
683         else
684           {
685             weeknum = 0;
686             for(i=0; i<11; i++)
687               weeknum+=MONTHCAL_MonthLength(i+1, infoPtr->currentYear-1);
688             weeknum +=startofprescal+ 7;
689             weeknum /=7;
690             weeknum1=MONTHCAL_CalculateDayOfWeek(1,1,infoPtr->currentYear-1);
691             if ((infoPtr->firstDay + 7 - weeknum1)%7 > mindays)
692               weeknum++;
693           }
694       }
695     else
696       {
697         weeknum = 0;
698         for(i=0; i<prevMonth-1; i++)
699           weeknum+=MONTHCAL_MonthLength(i+1, infoPtr->currentYear);
700         weeknum +=startofprescal+ 7;
701         weeknum /=7;
702         weeknum1=MONTHCAL_CalculateDayOfWeek(1,1,infoPtr->currentYear);
703         if ((infoPtr->firstDay + 7 - weeknum1)%7 > mindays)
704           weeknum++;
705       }
706     days->left = infoPtr->weeknums.left;
707     days->right = infoPtr->weeknums.right;
708     days->top = infoPtr->weeknums.top;
709     days->bottom = days->top +infoPtr->height_increment;
710     for(i=0; i<6; i++) {
711       if((i==0)&&(weeknum>50))
712         {
713           wsprintfW(buf, fmt3W, weeknum);
714           weeknum=0;
715         }
716       else if((i==5)&&(weeknum>47))
717         {
718           wsprintfW(buf, fmt3W, 1);
719         }
720       else
721         wsprintfW(buf, fmt3W, weeknum + i);
722       DrawTextW(hdc, buf, -1, days, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
723       days->top+=infoPtr->height_increment;
724       days->bottom+=infoPtr->height_increment;
725     }
726
727     MoveToEx(hdc, infoPtr->weeknums.right, infoPtr->weeknums.top + 3 , NULL);
728     LineTo(hdc,   infoPtr->weeknums.right, infoPtr->weeknums.bottom );
729
730   }
731   /* currentFont was font at entering Refresh */
732
733   SetBkColor(hdc, oldBkColor);
734   SelectObject(hdc, currentFont);
735   SetTextColor(hdc, oldTextColor);
736 }
737
738
739 static LRESULT
740 MONTHCAL_GetMinReqRect(const MONTHCAL_INFO *infoPtr, LPARAM lParam)
741 {
742   LPRECT lpRect = (LPRECT) lParam;
743
744   TRACE("rect %p\n", lpRect);
745
746   /* validate parameters */
747
748   if((infoPtr==NULL) ||(lpRect == NULL) ) return FALSE;
749
750   lpRect->left = infoPtr->title.left;
751   lpRect->top = infoPtr->title.top;
752   lpRect->right = infoPtr->title.right;
753   lpRect->bottom = infoPtr->todayrect.bottom;
754   AdjustWindowRect(lpRect, GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE), FALSE);
755
756   TRACE("%s\n", wine_dbgstr_rect(lpRect));
757
758   return TRUE;
759 }
760
761
762 static LRESULT
763 MONTHCAL_GetColor(const MONTHCAL_INFO *infoPtr, WPARAM wParam)
764 {
765   TRACE("\n");
766
767   switch((int)wParam) {
768     case MCSC_BACKGROUND:
769       return infoPtr->bk;
770     case MCSC_TEXT:
771       return infoPtr->txt;
772     case MCSC_TITLEBK:
773       return infoPtr->titlebk;
774     case MCSC_TITLETEXT:
775       return infoPtr->titletxt;
776     case MCSC_MONTHBK:
777       return infoPtr->monthbk;
778     case MCSC_TRAILINGTEXT:
779       return infoPtr->trailingtxt;
780   }
781
782   return -1;
783 }
784
785
786 static LRESULT
787 MONTHCAL_SetColor(MONTHCAL_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
788 {
789   int prev = -1;
790
791   TRACE("%ld: color %08lx\n", wParam, lParam);
792
793   switch((int)wParam) {
794     case MCSC_BACKGROUND:
795       prev = infoPtr->bk;
796       infoPtr->bk = (COLORREF)lParam;
797       break;
798     case MCSC_TEXT:
799       prev = infoPtr->txt;
800       infoPtr->txt = (COLORREF)lParam;
801       break;
802     case MCSC_TITLEBK:
803       prev = infoPtr->titlebk;
804       infoPtr->titlebk = (COLORREF)lParam;
805       break;
806     case MCSC_TITLETEXT:
807       prev=infoPtr->titletxt;
808       infoPtr->titletxt = (COLORREF)lParam;
809       break;
810     case MCSC_MONTHBK:
811       prev = infoPtr->monthbk;
812       infoPtr->monthbk = (COLORREF)lParam;
813       break;
814     case MCSC_TRAILINGTEXT:
815       prev = infoPtr->trailingtxt;
816       infoPtr->trailingtxt = (COLORREF)lParam;
817       break;
818   }
819
820   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
821   return prev;
822 }
823
824
825 static LRESULT
826 MONTHCAL_GetMonthDelta(const MONTHCAL_INFO *infoPtr)
827 {
828   TRACE("\n");
829
830   if(infoPtr->delta)
831     return infoPtr->delta;
832   else
833     return infoPtr->visible;
834 }
835
836
837 static LRESULT
838 MONTHCAL_SetMonthDelta(MONTHCAL_INFO *infoPtr, WPARAM wParam)
839 {
840   int prev = infoPtr->delta;
841
842   TRACE("delta %ld\n", wParam);
843
844   infoPtr->delta = (int)wParam;
845   return prev;
846 }
847
848
849 static LRESULT
850 MONTHCAL_GetFirstDayOfWeek(const MONTHCAL_INFO *infoPtr)
851 {
852   return MAKELONG(infoPtr->firstDay, infoPtr->firstDayHighWord);
853 }
854
855
856 /* sets the first day of the week that will appear in the control */
857 /* 0 == Sunday, 6 == Saturday */
858 /* FIXME: this needs to be implemented properly in MONTHCAL_Refresh() */
859 /* FIXME: we need more error checking here */
860 static LRESULT
861 MONTHCAL_SetFirstDayOfWeek(MONTHCAL_INFO *infoPtr, LPARAM lParam)
862 {
863   int prev = MAKELONG(infoPtr->firstDay, infoPtr->firstDayHighWord);
864   int localFirstDay;
865   WCHAR buf[40];
866
867   TRACE("day %ld\n", lParam);
868
869   GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, buf, countof(buf));
870   TRACE("%s %d\n", debugstr_w(buf), strlenW(buf));
871
872   localFirstDay = atoiW(buf);
873
874   if(lParam == -1)
875   {
876     infoPtr->firstDay = localFirstDay;
877     infoPtr->firstDayHighWord = FALSE;
878   }
879   else if(lParam >= 7)
880   {
881     infoPtr->firstDay = 6; /* max first day allowed */
882     infoPtr->firstDayHighWord = TRUE;
883   }
884   else
885   {
886     infoPtr->firstDay = lParam;
887     infoPtr->firstDayHighWord = TRUE;
888   }
889
890   return prev;
891 }
892
893
894 static LRESULT
895 MONTHCAL_GetMonthRange(const MONTHCAL_INFO *infoPtr)
896 {
897   TRACE("\n");
898
899   return infoPtr->monthRange;
900 }
901
902
903 static LRESULT
904 MONTHCAL_GetMaxTodayWidth(const MONTHCAL_INFO *infoPtr)
905 {
906   return(infoPtr->todayrect.right - infoPtr->todayrect.left);
907 }
908
909
910 static LRESULT
911 MONTHCAL_SetRange(MONTHCAL_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
912 {
913     SYSTEMTIME *lprgSysTimeArray=(SYSTEMTIME *)lParam;
914     FILETIME ft_min, ft_max;
915
916     TRACE("%lx %lx\n", wParam, lParam);
917
918     if ((wParam & GDTR_MIN && !MONTHCAL_ValidateTime(lprgSysTimeArray[0])) ||
919         (wParam & GDTR_MAX && !MONTHCAL_ValidateTime(lprgSysTimeArray[1])))
920         return FALSE;
921
922     if (wParam & GDTR_MIN)
923     {
924         MONTHCAL_CopyTime(&lprgSysTimeArray[0], &infoPtr->minDate);
925         infoPtr->rangeValid |= GDTR_MIN;
926     }
927     if (wParam & GDTR_MAX)
928     {
929         MONTHCAL_CopyTime(&lprgSysTimeArray[1], &infoPtr->maxDate);
930         infoPtr->rangeValid |= GDTR_MAX;
931     }
932
933     /* Only one limit set - we are done */
934     if ((infoPtr->rangeValid & (GDTR_MIN | GDTR_MAX)) != (GDTR_MIN | GDTR_MAX))
935         return TRUE;
936     
937     SystemTimeToFileTime(&infoPtr->maxDate, &ft_max);
938     SystemTimeToFileTime(&infoPtr->minDate, &ft_min);
939
940     if (CompareFileTime(&ft_min, &ft_max) > 0)
941     {
942         if ((wParam & (GDTR_MIN | GDTR_MAX)) == (GDTR_MIN | GDTR_MAX))
943         {
944             /* Native swaps limits only when both limits are being set. */
945             SYSTEMTIME st_tmp = infoPtr->minDate;
946             infoPtr->minDate  = infoPtr->maxDate;
947             infoPtr->maxDate  = st_tmp;
948         }
949         else
950         {
951             /* Reset the other limit. */
952             /* FIXME: native sets date&time to 0. Should we do this too? */
953             infoPtr->rangeValid &= wParam & GDTR_MIN ? ~GDTR_MAX : ~GDTR_MIN ;
954         }
955     }
956
957     return TRUE;
958 }
959
960
961 static LRESULT
962 MONTHCAL_GetRange(HWND hwnd, LPARAM lParam)
963 {
964   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
965   SYSTEMTIME *lprgSysTimeArray = (SYSTEMTIME *)lParam;
966
967   /* validate parameters */
968
969   if((infoPtr==NULL) || (lprgSysTimeArray==NULL)) return FALSE;
970
971   MONTHCAL_CopyTime(&infoPtr->maxDate, &lprgSysTimeArray[1]);
972   MONTHCAL_CopyTime(&infoPtr->minDate, &lprgSysTimeArray[0]);
973
974   return infoPtr->rangeValid;
975 }
976
977
978 static LRESULT
979 MONTHCAL_SetDayState(const MONTHCAL_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
980
981 {
982   int i, iMonths = (int)wParam;
983   MONTHDAYSTATE *dayStates = (LPMONTHDAYSTATE)lParam;
984
985   TRACE("%lx %lx\n", wParam, lParam);
986   if(iMonths!=infoPtr->monthRange) return 0;
987
988   for(i=0; i<iMonths; i++)
989     infoPtr->monthdayState[i] = dayStates[i];
990   return 1;
991 }
992
993 static LRESULT
994 MONTHCAL_GetCurSel(const MONTHCAL_INFO *infoPtr, LPARAM lParam)
995 {
996   SYSTEMTIME *lpSel = (SYSTEMTIME *) lParam;
997
998   TRACE("%lx\n", lParam);
999   if((infoPtr==NULL) ||(lpSel==NULL)) return FALSE;
1000   if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
1001
1002   MONTHCAL_CopyTime(&infoPtr->minSel, lpSel);
1003   TRACE("%d/%d/%d\n", lpSel->wYear, lpSel->wMonth, lpSel->wDay);
1004   return TRUE;
1005 }
1006
1007 /* FIXME: if the specified date is not visible, make it visible */
1008 /* FIXME: redraw? */
1009 static LRESULT
1010 MONTHCAL_SetCurSel(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1011 {
1012   SYSTEMTIME *lpSel = (SYSTEMTIME *)lParam;
1013
1014   TRACE("%lx\n", lParam);
1015   if((infoPtr==NULL) ||(lpSel==NULL)) return FALSE;
1016   if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
1017
1018   if(!MONTHCAL_ValidateTime(*lpSel)) return FALSE;
1019
1020   infoPtr->currentMonth=lpSel->wMonth;
1021   infoPtr->currentYear=lpSel->wYear;
1022
1023   MONTHCAL_CopyTime(lpSel, &infoPtr->minSel);
1024   MONTHCAL_CopyTime(lpSel, &infoPtr->maxSel);
1025
1026   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1027
1028   return TRUE;
1029 }
1030
1031
1032 static LRESULT
1033 MONTHCAL_GetMaxSelCount(const MONTHCAL_INFO *infoPtr)
1034 {
1035   return infoPtr->maxSelCount;
1036 }
1037
1038
1039 static LRESULT
1040 MONTHCAL_SetMaxSelCount(MONTHCAL_INFO *infoPtr, WPARAM wParam)
1041 {
1042   TRACE("%lx\n", wParam);
1043
1044   if(infoPtr->dwStyle & MCS_MULTISELECT)  {
1045     infoPtr->maxSelCount = wParam;
1046   }
1047
1048   return TRUE;
1049 }
1050
1051
1052 static LRESULT
1053 MONTHCAL_GetSelRange(const MONTHCAL_INFO *infoPtr, LPARAM lParam)
1054 {
1055   SYSTEMTIME *lprgSysTimeArray = (SYSTEMTIME *) lParam;
1056
1057   TRACE("%lx\n", lParam);
1058
1059   /* validate parameters */
1060
1061   if((infoPtr==NULL) ||(lprgSysTimeArray==NULL)) return FALSE;
1062
1063   if(infoPtr->dwStyle & MCS_MULTISELECT)
1064   {
1065     MONTHCAL_CopyTime(&infoPtr->maxSel, &lprgSysTimeArray[1]);
1066     MONTHCAL_CopyTime(&infoPtr->minSel, &lprgSysTimeArray[0]);
1067     TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
1068     return TRUE;
1069   }
1070
1071   return FALSE;
1072 }
1073
1074
1075 static LRESULT
1076 MONTHCAL_SetSelRange(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1077 {
1078   SYSTEMTIME *lprgSysTimeArray = (SYSTEMTIME *) lParam;
1079
1080   TRACE("%lx\n", lParam);
1081
1082   /* validate parameters */
1083
1084   if((infoPtr==NULL) ||(lprgSysTimeArray==NULL)) return FALSE;
1085
1086   if(infoPtr->dwStyle & MCS_MULTISELECT)
1087   {
1088     MONTHCAL_CopyTime(&lprgSysTimeArray[1], &infoPtr->maxSel);
1089     MONTHCAL_CopyTime(&lprgSysTimeArray[0], &infoPtr->minSel);
1090     TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
1091     return TRUE;
1092   }
1093
1094   return FALSE;
1095 }
1096
1097
1098 static LRESULT
1099 MONTHCAL_GetToday(const MONTHCAL_INFO *infoPtr, LPARAM lParam)
1100 {
1101   SYSTEMTIME *lpToday = (SYSTEMTIME *) lParam;
1102
1103   TRACE("%lx\n", lParam);
1104
1105   /* validate parameters */
1106
1107   if((infoPtr==NULL) || (lpToday==NULL)) return FALSE;
1108   MONTHCAL_CopyTime(&infoPtr->todaysDate, lpToday);
1109   return TRUE;
1110 }
1111
1112
1113 static LRESULT
1114 MONTHCAL_SetToday(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1115 {
1116   SYSTEMTIME *lpToday = (SYSTEMTIME *) lParam;
1117
1118   TRACE("%lx\n", lParam);
1119
1120   /* validate parameters */
1121
1122   if((infoPtr==NULL) ||(lpToday==NULL)) return FALSE;
1123   MONTHCAL_CopyTime(lpToday, &infoPtr->todaysDate);
1124   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1125   return TRUE;
1126 }
1127
1128
1129 static LRESULT
1130 MONTHCAL_HitTest(const MONTHCAL_INFO *infoPtr, LPARAM lParam)
1131 {
1132   PMCHITTESTINFO lpht = (PMCHITTESTINFO)lParam;
1133   UINT x,y;
1134   DWORD retval;
1135   int day,wday,wnum;
1136
1137
1138   x = lpht->pt.x;
1139   y = lpht->pt.y;
1140
1141   ZeroMemory(&lpht->st, sizeof(lpht->st));
1142
1143   /* Comment in for debugging...
1144   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,
1145         infoPtr->wdays.left, infoPtr->wdays.right,
1146         infoPtr->wdays.top, infoPtr->wdays.bottom,
1147         infoPtr->days.left, infoPtr->days.right,
1148         infoPtr->days.top, infoPtr->days.bottom,
1149         infoPtr->todayrect.left, infoPtr->todayrect.right,
1150         infoPtr->todayrect.top, infoPtr->todayrect.bottom,
1151         infoPtr->weeknums.left, infoPtr->weeknums.right,
1152         infoPtr->weeknums.top, infoPtr->weeknums.bottom);
1153   */
1154
1155   /* are we in the header? */
1156
1157   if(PtInRect(&infoPtr->title, lpht->pt)) {
1158     if(PtInRect(&infoPtr->titlebtnprev, lpht->pt)) {
1159       retval = MCHT_TITLEBTNPREV;
1160       goto done;
1161     }
1162     if(PtInRect(&infoPtr->titlebtnnext, lpht->pt)) {
1163       retval = MCHT_TITLEBTNNEXT;
1164       goto done;
1165     }
1166     if(PtInRect(&infoPtr->titlemonth, lpht->pt)) {
1167       retval = MCHT_TITLEMONTH;
1168       goto done;
1169     }
1170     if(PtInRect(&infoPtr->titleyear, lpht->pt)) {
1171       retval = MCHT_TITLEYEAR;
1172       goto done;
1173     }
1174
1175     retval = MCHT_TITLE;
1176     goto done;
1177   }
1178
1179   day = MONTHCAL_CalcDayFromPos(infoPtr,x,y,&wday,&wnum);
1180   if(PtInRect(&infoPtr->wdays, lpht->pt)) {
1181     retval = MCHT_CALENDARDAY;
1182     lpht->st.wYear  = infoPtr->currentYear;
1183     lpht->st.wMonth = (day < 1)? infoPtr->currentMonth -1 : infoPtr->currentMonth;
1184     lpht->st.wDay   = (day < 1)?
1185       MONTHCAL_MonthLength(infoPtr->currentMonth-1,infoPtr->currentYear) -day : day;
1186     goto done;
1187   }
1188   if(PtInRect(&infoPtr->weeknums, lpht->pt)) {
1189     retval = MCHT_CALENDARWEEKNUM;
1190     lpht->st.wYear  = infoPtr->currentYear;
1191     lpht->st.wMonth = (day < 1) ? infoPtr->currentMonth -1 :
1192       (day > MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear)) ?
1193       infoPtr->currentMonth +1 :infoPtr->currentMonth;
1194     lpht->st.wDay   = (day < 1 ) ?
1195       MONTHCAL_MonthLength(infoPtr->currentMonth-1,infoPtr->currentYear) -day :
1196       (day > MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear)) ?
1197       day - MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear) : day;
1198     goto done;
1199   }
1200   if(PtInRect(&infoPtr->days, lpht->pt))
1201     {
1202       lpht->st.wYear  = infoPtr->currentYear;
1203       if ( day < 1)
1204         {
1205           retval = MCHT_CALENDARDATEPREV;
1206           lpht->st.wMonth = infoPtr->currentMonth - 1;
1207           if (lpht->st.wMonth <1)
1208             {
1209               lpht->st.wMonth = 12;
1210               lpht->st.wYear--;
1211             }
1212           lpht->st.wDay   = MONTHCAL_MonthLength(lpht->st.wMonth,lpht->st.wYear) -day;
1213         }
1214       else if (day > MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear))
1215         {
1216           retval = MCHT_CALENDARDATENEXT;
1217           lpht->st.wMonth = infoPtr->currentMonth + 1;
1218           if (lpht->st.wMonth <12)
1219             {
1220               lpht->st.wMonth = 1;
1221               lpht->st.wYear++;
1222             }
1223           lpht->st.wDay   = day - MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear) ;
1224         }
1225       else {
1226         retval = MCHT_CALENDARDATE;
1227         lpht->st.wMonth = infoPtr->currentMonth;
1228         lpht->st.wDay   = day;
1229         lpht->st.wDayOfWeek   = MONTHCAL_CalculateDayOfWeek(day,lpht->st.wMonth,lpht->st.wYear);
1230       }
1231       goto done;
1232     }
1233   if(PtInRect(&infoPtr->todayrect, lpht->pt)) {
1234     retval = MCHT_TODAYLINK;
1235     goto done;
1236   }
1237
1238
1239   /* Hit nothing special? What's left must be background :-) */
1240
1241   retval = MCHT_CALENDARBK;
1242  done:
1243   lpht->uHit = retval;
1244   return retval;
1245 }
1246
1247
1248 static void MONTHCAL_GoToNextMonth(MONTHCAL_INFO *infoPtr)
1249 {
1250   TRACE("MONTHCAL_GoToNextMonth\n");
1251
1252   infoPtr->currentMonth++;
1253   if(infoPtr->currentMonth > 12) {
1254     infoPtr->currentYear++;
1255     infoPtr->currentMonth = 1;
1256   }
1257
1258   if(infoPtr->dwStyle & MCS_DAYSTATE) {
1259     NMDAYSTATE nmds;
1260     int i;
1261
1262     nmds.nmhdr.hwndFrom = infoPtr->hwndSelf;
1263     nmds.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1264     nmds.nmhdr.code     = MCN_GETDAYSTATE;
1265     nmds.cDayState      = infoPtr->monthRange;
1266     nmds.prgDayState    = Alloc(infoPtr->monthRange * sizeof(MONTHDAYSTATE));
1267
1268     nmds.stStart = infoPtr->todaysDate;
1269     nmds.stStart.wYear = infoPtr->currentYear;
1270     nmds.stStart.wMonth = infoPtr->currentMonth;
1271     nmds.stStart.wDay = 1;
1272
1273     SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmds.nmhdr.idFrom, (LPARAM)&nmds);
1274     for(i=0; i<infoPtr->monthRange; i++)
1275       infoPtr->monthdayState[i] = nmds.prgDayState[i];
1276   }
1277 }
1278
1279
1280 static void MONTHCAL_GoToPrevMonth(MONTHCAL_INFO *infoPtr)
1281 {
1282   TRACE("\n");
1283
1284   infoPtr->currentMonth--;
1285   if(infoPtr->currentMonth < 1) {
1286     infoPtr->currentYear--;
1287     infoPtr->currentMonth = 12;
1288   }
1289
1290   if(infoPtr->dwStyle & MCS_DAYSTATE) {
1291     NMDAYSTATE nmds;
1292     int i;
1293
1294     nmds.nmhdr.hwndFrom = infoPtr->hwndSelf;
1295     nmds.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1296     nmds.nmhdr.code     = MCN_GETDAYSTATE;
1297     nmds.cDayState      = infoPtr->monthRange;
1298     nmds.prgDayState    = Alloc
1299                         (infoPtr->monthRange * sizeof(MONTHDAYSTATE));
1300
1301     nmds.stStart = infoPtr->todaysDate;
1302     nmds.stStart.wYear = infoPtr->currentYear;
1303     nmds.stStart.wMonth = infoPtr->currentMonth;
1304     nmds.stStart.wDay = 1;
1305
1306     SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmds.nmhdr.idFrom, (LPARAM)&nmds);
1307     for(i=0; i<infoPtr->monthRange; i++)
1308        infoPtr->monthdayState[i] = nmds.prgDayState[i];
1309   }
1310 }
1311
1312 static LRESULT
1313 MONTHCAL_RButtonDown(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1314 {
1315   static const WCHAR todayW[] = { 'G','o',' ','t','o',' ','T','o','d','a','y',':',0 };
1316   HMENU hMenu;
1317   POINT menupoint;
1318   WCHAR buf[32];
1319
1320   hMenu = CreatePopupMenu();
1321   if (!LoadStringW(COMCTL32_hModule,IDM_GOTODAY,buf,countof(buf)))
1322     {
1323       WARN("Can't load resource\n");
1324       strcpyW(buf, todayW);
1325     }
1326   AppendMenuW(hMenu, MF_STRING|MF_ENABLED,1, buf);
1327   menupoint.x=(short)LOWORD(lParam);
1328   menupoint.y=(short)HIWORD(lParam);
1329   ClientToScreen(infoPtr->hwndSelf, &menupoint);
1330   if( TrackPopupMenu(hMenu,TPM_RIGHTBUTTON| TPM_NONOTIFY|TPM_RETURNCMD,
1331                      menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL))
1332     {
1333       infoPtr->currentMonth=infoPtr->todaysDate.wMonth;
1334       infoPtr->currentYear=infoPtr->todaysDate.wYear;
1335       InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1336     }
1337   return 0;
1338 }
1339
1340 static LRESULT
1341 MONTHCAL_LButtonDown(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1342 {
1343   static const WCHAR EditW[] = { 'E','D','I','T',0 };
1344   MCHITTESTINFO ht;
1345   DWORD hit;
1346   HMENU hMenu;
1347   RECT rcDay; /* used in determining area to invalidate */
1348   WCHAR buf[32];
1349   int i;
1350   POINT menupoint;
1351
1352   TRACE("%lx\n", lParam);
1353
1354   if (infoPtr->hWndYearUpDown)
1355     {
1356       infoPtr->currentYear=SendMessageW(infoPtr->hWndYearUpDown, UDM_SETPOS, 0, 0);
1357       if(!DestroyWindow(infoPtr->hWndYearUpDown))
1358         {
1359           FIXME("Can't destroy Updown Control\n");
1360         }
1361       else
1362         infoPtr->hWndYearUpDown=0;
1363       if(!DestroyWindow(infoPtr->hWndYearEdit))
1364         {
1365           FIXME("Can't destroy Updown Control\n");
1366         }
1367       else
1368         infoPtr->hWndYearEdit=0;
1369       InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1370     }
1371
1372   ht.pt.x = (short)LOWORD(lParam);
1373   ht.pt.y = (short)HIWORD(lParam);
1374   hit = MONTHCAL_HitTest(infoPtr, (LPARAM)&ht);
1375
1376   /* FIXME: these flags should be checked by */
1377   /*((hit & MCHT_XXX) == MCHT_XXX) b/c some of the flags are */
1378   /* multi-bit */
1379   if(hit ==MCHT_TITLEBTNNEXT) {
1380     MONTHCAL_GoToNextMonth(infoPtr);
1381     infoPtr->status = MC_NEXTPRESSED;
1382     SetTimer(infoPtr->hwndSelf, MC_NEXTMONTHTIMER, MC_NEXTMONTHDELAY, 0);
1383     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1384     return 0;
1385   }
1386   if(hit == MCHT_TITLEBTNPREV){
1387     MONTHCAL_GoToPrevMonth(infoPtr);
1388     infoPtr->status = MC_PREVPRESSED;
1389     SetTimer(infoPtr->hwndSelf, MC_PREVMONTHTIMER, MC_NEXTMONTHDELAY, 0);
1390     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1391     return 0;
1392   }
1393
1394   if(hit == MCHT_TITLEMONTH) {
1395     hMenu = CreatePopupMenu();
1396
1397     for (i=0; i<12;i++)
1398       {
1399         GetLocaleInfoW(LOCALE_USER_DEFAULT,LOCALE_SMONTHNAME1+i, buf,countof(buf));
1400         AppendMenuW(hMenu, MF_STRING|MF_ENABLED,i+1, buf);
1401       }
1402     menupoint.x=infoPtr->titlemonth.right;
1403     menupoint.y=infoPtr->titlemonth.bottom;
1404     ClientToScreen(infoPtr->hwndSelf, &menupoint);
1405     i= TrackPopupMenu(hMenu,TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON | TPM_RETURNCMD,
1406                       menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL);
1407     if ((i>0) && (i<13))
1408       {
1409         infoPtr->currentMonth=i;
1410         InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1411       }
1412   }
1413   if(hit == MCHT_TITLEYEAR) {
1414     infoPtr->hWndYearEdit=CreateWindowExW(0,
1415                          EditW,
1416                            0,
1417                          WS_VISIBLE | WS_CHILD |UDS_SETBUDDYINT,
1418                          infoPtr->titleyear.left+3,infoPtr->titlebtnnext.top,
1419                          infoPtr->titleyear.right-infoPtr->titleyear.left+4,
1420                          infoPtr->textHeight,
1421                          infoPtr->hwndSelf,
1422                          NULL,
1423                          NULL,
1424                          NULL);
1425     SendMessageW( infoPtr->hWndYearEdit, WM_SETFONT, (WPARAM) infoPtr->hBoldFont, (LPARAM)TRUE);
1426     infoPtr->hWndYearUpDown=CreateWindowExW(0,
1427                          UPDOWN_CLASSW,
1428                            0,
1429                          WS_VISIBLE | WS_CHILD |UDS_SETBUDDYINT|UDS_NOTHOUSANDS|UDS_ARROWKEYS,
1430                          infoPtr->titleyear.right+7,infoPtr->titlebtnnext.top,
1431                          18,
1432                          infoPtr->textHeight,
1433                          infoPtr->hwndSelf,
1434                          NULL,
1435                          NULL,
1436                          NULL);
1437     SendMessageW(infoPtr->hWndYearUpDown, UDM_SETRANGE, 0, MAKELONG (9999, 1753));
1438     SendMessageW(infoPtr->hWndYearUpDown, UDM_SETBUDDY, (WPARAM) infoPtr->hWndYearEdit, 0);
1439     SendMessageW(infoPtr->hWndYearUpDown, UDM_SETPOS, 0, infoPtr->currentYear);
1440     return 0;
1441
1442   }
1443   if(hit == MCHT_TODAYLINK) {
1444     NMSELCHANGE nmsc;
1445
1446     infoPtr->curSelDay = infoPtr->todaysDate.wDay;
1447     infoPtr->firstSelDay = infoPtr->todaysDate.wDay;
1448     infoPtr->currentMonth=infoPtr->todaysDate.wMonth;
1449     infoPtr->currentYear=infoPtr->todaysDate.wYear;
1450     MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->minSel);
1451     MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->maxSel);
1452     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1453
1454     nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
1455     nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1456     nmsc.nmhdr.code     = MCN_SELCHANGE;
1457     MONTHCAL_CopyTime(&infoPtr->minSel, &nmsc.stSelStart);
1458     MONTHCAL_CopyTime(&infoPtr->maxSel, &nmsc.stSelEnd);
1459     SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
1460
1461     nmsc.nmhdr.code     = MCN_SELECT;
1462     SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
1463     return 0;
1464   }
1465   if(hit == MCHT_CALENDARDATE) {
1466     SYSTEMTIME selArray[2];
1467     NMSELCHANGE nmsc;
1468
1469     MONTHCAL_CopyTime(&ht.st, &selArray[0]);
1470     MONTHCAL_CopyTime(&ht.st, &selArray[1]);
1471     MONTHCAL_SetSelRange(infoPtr, (LPARAM)selArray);
1472     MONTHCAL_SetCurSel(infoPtr, (LPARAM)selArray);
1473     TRACE("MCHT_CALENDARDATE\n");
1474     nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
1475     nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1476     nmsc.nmhdr.code     = MCN_SELCHANGE;
1477     MONTHCAL_CopyTime(&infoPtr->minSel,&nmsc.stSelStart);
1478     MONTHCAL_CopyTime(&infoPtr->maxSel,&nmsc.stSelEnd);
1479
1480     SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
1481
1482
1483     /* redraw both old and new days if the selected day changed */
1484     if(infoPtr->curSelDay != ht.st.wDay) {
1485       MONTHCAL_CalcPosFromDay(infoPtr, ht.st.wDay, ht.st.wMonth, &rcDay);
1486       InvalidateRect(infoPtr->hwndSelf, &rcDay, TRUE);
1487
1488       MONTHCAL_CalcPosFromDay(infoPtr, infoPtr->curSelDay, infoPtr->currentMonth, &rcDay);
1489       InvalidateRect(infoPtr->hwndSelf, &rcDay, TRUE);
1490     }
1491
1492     infoPtr->firstSelDay = ht.st.wDay;
1493     infoPtr->curSelDay = ht.st.wDay;
1494     infoPtr->status = MC_SEL_LBUTDOWN;
1495     return 0;
1496   }
1497
1498   return 1;
1499 }
1500
1501
1502 static LRESULT
1503 MONTHCAL_LButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1504 {
1505   NMSELCHANGE nmsc;
1506   NMHDR nmhdr;
1507   BOOL redraw = FALSE;
1508   MCHITTESTINFO ht;
1509   DWORD hit;
1510
1511   TRACE("\n");
1512
1513   if(infoPtr->status & MC_NEXTPRESSED) {
1514     KillTimer(infoPtr->hwndSelf, MC_NEXTMONTHTIMER);
1515     infoPtr->status &= ~MC_NEXTPRESSED;
1516     redraw = TRUE;
1517   }
1518   if(infoPtr->status & MC_PREVPRESSED) {
1519     KillTimer(infoPtr->hwndSelf, MC_PREVMONTHTIMER);
1520     infoPtr->status &= ~MC_PREVPRESSED;
1521     redraw = TRUE;
1522   }
1523
1524   ht.pt.x = (short)LOWORD(lParam);
1525   ht.pt.y = (short)HIWORD(lParam);
1526   hit = MONTHCAL_HitTest(infoPtr, (LPARAM)&ht);
1527
1528   infoPtr->status = MC_SEL_LBUTUP;
1529
1530   if(hit ==MCHT_CALENDARDATENEXT) {
1531     MONTHCAL_GoToNextMonth(infoPtr);
1532     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1533     return TRUE;
1534   }
1535   if(hit == MCHT_CALENDARDATEPREV){
1536     MONTHCAL_GoToPrevMonth(infoPtr);
1537     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1538     return TRUE;
1539   }
1540   nmhdr.hwndFrom = infoPtr->hwndSelf;
1541   nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1542   nmhdr.code     = NM_RELEASEDCAPTURE;
1543   TRACE("Sent notification from %p to %p\n", infoPtr->hwndSelf, infoPtr->hwndNotify);
1544
1545   SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmhdr.idFrom, (LPARAM)&nmhdr);
1546   /* redraw if necessary */
1547   if(redraw)
1548     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1549   /* only send MCN_SELECT if currently displayed month's day was selected */
1550   if(hit == MCHT_CALENDARDATE) {
1551     nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
1552     nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1553     nmsc.nmhdr.code     = MCN_SELECT;
1554     MONTHCAL_CopyTime(&infoPtr->minSel, &nmsc.stSelStart);
1555     MONTHCAL_CopyTime(&infoPtr->maxSel, &nmsc.stSelEnd);
1556
1557     SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
1558
1559   }
1560   return 0;
1561 }
1562
1563
1564 static LRESULT
1565 MONTHCAL_Timer(MONTHCAL_INFO *infoPtr, WPARAM wParam)
1566 {
1567   BOOL redraw = FALSE;
1568
1569   TRACE("%ld\n", wParam);
1570
1571   switch(wParam) {
1572   case MC_NEXTMONTHTIMER:
1573     redraw = TRUE;
1574     MONTHCAL_GoToNextMonth(infoPtr);
1575     break;
1576   case MC_PREVMONTHTIMER:
1577     redraw = TRUE;
1578     MONTHCAL_GoToPrevMonth(infoPtr);
1579     break;
1580   default:
1581     ERR("got unknown timer\n");
1582     break;
1583   }
1584
1585   /* redraw only if necessary */
1586   if(redraw)
1587     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1588
1589   return 0;
1590 }
1591
1592
1593 static LRESULT
1594 MONTHCAL_MouseMove(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1595 {
1596   MCHITTESTINFO ht;
1597   int oldselday, selday, hit;
1598   RECT r;
1599
1600   if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
1601
1602   ht.pt.x = (short)LOWORD(lParam);
1603   ht.pt.y = (short)HIWORD(lParam);
1604
1605   hit = MONTHCAL_HitTest(infoPtr, (LPARAM)&ht);
1606
1607   /* not on the calendar date numbers? bail out */
1608   TRACE("hit:%x\n",hit);
1609   if((hit & MCHT_CALENDARDATE) != MCHT_CALENDARDATE) return 0;
1610
1611   selday = ht.st.wDay;
1612   oldselday = infoPtr->curSelDay;
1613   infoPtr->curSelDay = selday;
1614   MONTHCAL_CalcPosFromDay(infoPtr, selday, ht.st. wMonth, &r);
1615
1616   if(infoPtr->dwStyle & MCS_MULTISELECT)  {
1617     SYSTEMTIME selArray[2];
1618     int i;
1619
1620     MONTHCAL_GetSelRange(infoPtr, (LPARAM)selArray);
1621     i = 0;
1622     if(infoPtr->firstSelDay==selArray[0].wDay) i=1;
1623     TRACE("oldRange:%d %d %d %d\n", infoPtr->firstSelDay, selArray[0].wDay, selArray[1].wDay, i);
1624     if(infoPtr->firstSelDay==selArray[1].wDay) {
1625       /* 1st time we get here: selArray[0]=selArray[1])  */
1626       /* if we're still at the first selected date, return */
1627       if(infoPtr->firstSelDay==selday) goto done;
1628       if(selday<infoPtr->firstSelDay) i = 0;
1629     }
1630
1631     if(abs(infoPtr->firstSelDay - selday) >= infoPtr->maxSelCount) {
1632       if(selday>infoPtr->firstSelDay)
1633         selday = infoPtr->firstSelDay + infoPtr->maxSelCount;
1634       else
1635         selday = infoPtr->firstSelDay - infoPtr->maxSelCount;
1636     }
1637
1638     if(selArray[i].wDay!=selday) {
1639       TRACE("newRange:%d %d %d %d\n", infoPtr->firstSelDay, selArray[0].wDay, selArray[1].wDay, i);
1640
1641       selArray[i].wDay = selday;
1642
1643       if(selArray[0].wDay>selArray[1].wDay) {
1644         DWORD tempday;
1645         tempday = selArray[1].wDay;
1646         selArray[1].wDay = selArray[0].wDay;
1647         selArray[0].wDay = tempday;
1648       }
1649
1650       MONTHCAL_SetSelRange(infoPtr, (LPARAM)selArray);
1651     }
1652   }
1653
1654 done:
1655
1656   /* only redraw if the currently selected day changed */
1657   /* FIXME: this should specify a rectangle containing only the days that changed */
1658   /* using InvalidateRect */
1659   if(oldselday != infoPtr->curSelDay)
1660     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1661
1662   return 0;
1663 }
1664
1665
1666 static LRESULT
1667 MONTHCAL_Paint(MONTHCAL_INFO *infoPtr, WPARAM wParam)
1668 {
1669   HDC hdc;
1670   PAINTSTRUCT ps;
1671
1672   if (wParam)
1673   {
1674     GetClientRect(infoPtr->hwndSelf, &ps.rcPaint);
1675     hdc = (HDC)wParam;
1676   }
1677   else
1678     hdc = BeginPaint(infoPtr->hwndSelf, &ps);
1679
1680   MONTHCAL_Refresh(infoPtr, hdc, &ps);
1681   if (!wParam) EndPaint(infoPtr->hwndSelf, &ps);
1682   return 0;
1683 }
1684
1685
1686 static LRESULT
1687 MONTHCAL_KillFocus(const MONTHCAL_INFO *infoPtr, HWND hFocusWnd)
1688 {
1689   TRACE("\n");
1690
1691   if (infoPtr->hwndNotify != hFocusWnd)
1692     ShowWindow(infoPtr->hwndSelf, SW_HIDE);
1693   else
1694     InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1695
1696   return 0;
1697 }
1698
1699
1700 static LRESULT
1701 MONTHCAL_SetFocus(const MONTHCAL_INFO *infoPtr)
1702 {
1703   TRACE("\n");
1704
1705   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1706
1707   return 0;
1708 }
1709
1710 /* sets the size information */
1711 static void MONTHCAL_UpdateSize(MONTHCAL_INFO *infoPtr)
1712 {
1713   static const WCHAR SunW[] = { 'S','u','n',0 };
1714   static const WCHAR O0W[] = { '0','0',0 };
1715   HDC hdc = GetDC(infoPtr->hwndSelf);
1716   RECT *title=&infoPtr->title;
1717   RECT *prev=&infoPtr->titlebtnprev;
1718   RECT *next=&infoPtr->titlebtnnext;
1719   RECT *titlemonth=&infoPtr->titlemonth;
1720   RECT *titleyear=&infoPtr->titleyear;
1721   RECT *wdays=&infoPtr->wdays;
1722   RECT *weeknumrect=&infoPtr->weeknums;
1723   RECT *days=&infoPtr->days;
1724   RECT *todayrect=&infoPtr->todayrect;
1725   SIZE size;
1726   TEXTMETRICW tm;
1727   HFONT currentFont;
1728   int xdiv, left_offset;
1729   RECT rcClient;
1730
1731   GetClientRect(infoPtr->hwndSelf, &rcClient);
1732
1733   currentFont = SelectObject(hdc, infoPtr->hFont);
1734
1735   /* get the height and width of each day's text */
1736   GetTextMetricsW(hdc, &tm);
1737   infoPtr->textHeight = tm.tmHeight + tm.tmExternalLeading + tm.tmInternalLeading;
1738   GetTextExtentPoint32W(hdc, SunW, 3, &size);
1739   infoPtr->textWidth = size.cx + 2;
1740
1741   /* recalculate the height and width increments and offsets */
1742   GetTextExtentPoint32W(hdc, O0W, 2, &size);
1743
1744   xdiv = (infoPtr->dwStyle & MCS_WEEKNUMBERS) ? 8 : 7;
1745
1746   infoPtr->width_increment = size.cx * 2 + 4;
1747   infoPtr->height_increment = infoPtr->textHeight;
1748   left_offset = (rcClient.right - rcClient.left) - (infoPtr->width_increment * xdiv);
1749
1750   /* calculate title area */
1751   title->top    = rcClient.top;
1752   title->bottom = title->top + 3 * infoPtr->height_increment / 2;
1753   title->left   = left_offset;
1754   title->right  = rcClient.right;
1755
1756   /* set the dimensions of the next and previous buttons and center */
1757   /* the month text vertically */
1758   prev->top    = next->top    = title->top + 4;
1759   prev->bottom = next->bottom = title->bottom - 4;
1760   prev->left   = title->left + 4;
1761   prev->right  = prev->left + (title->bottom - title->top) ;
1762   next->right  = title->right - 4;
1763   next->left   = next->right - (title->bottom - title->top);
1764
1765   /* titlemonth->left and right change based upon the current month */
1766   /* and are recalculated in refresh as the current month may change */
1767   /* without the control being resized */
1768   titlemonth->top    = titleyear->top    = title->top    + (infoPtr->height_increment)/2;
1769   titlemonth->bottom = titleyear->bottom = title->bottom - (infoPtr->height_increment)/2;
1770
1771   /* setup the dimensions of the rectangle we draw the names of the */
1772   /* days of the week in */
1773   weeknumrect->left = left_offset;
1774   if(infoPtr->dwStyle & MCS_WEEKNUMBERS)
1775     weeknumrect->right=prev->right;
1776   else
1777     weeknumrect->right=weeknumrect->left;
1778   wdays->left   = days->left   = weeknumrect->right;
1779   wdays->right  = days->right  = wdays->left + 7 * infoPtr->width_increment;
1780   wdays->top    = title->bottom ;
1781   wdays->bottom = wdays->top + infoPtr->height_increment;
1782
1783   days->top    = weeknumrect->top = wdays->bottom ;
1784   days->bottom = weeknumrect->bottom = days->top + 6 * infoPtr->height_increment;
1785
1786   todayrect->left   = rcClient.left;
1787   todayrect->right  = rcClient.right;
1788   todayrect->top    = days->bottom;
1789   todayrect->bottom = days->bottom + infoPtr->height_increment;
1790
1791   TRACE("dx=%d dy=%d client[%s] title[%s] wdays[%s] days[%s] today[%s]\n",
1792         infoPtr->width_increment,infoPtr->height_increment,
1793         wine_dbgstr_rect(&rcClient),
1794         wine_dbgstr_rect(title),
1795         wine_dbgstr_rect(wdays),
1796         wine_dbgstr_rect(days),
1797         wine_dbgstr_rect(todayrect));
1798
1799   /* restore the originally selected font */
1800   SelectObject(hdc, currentFont);
1801
1802   ReleaseDC(infoPtr->hwndSelf, hdc);
1803 }
1804
1805 static LRESULT MONTHCAL_Size(MONTHCAL_INFO *infoPtr, int Width, int Height)
1806 {
1807   TRACE("(width=%d, height=%d)\n", Width, Height);
1808
1809   MONTHCAL_UpdateSize(infoPtr);
1810
1811   /* invalidate client area and erase background */
1812   InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1813
1814   return 0;
1815 }
1816
1817 static LRESULT MONTHCAL_GetFont(const MONTHCAL_INFO *infoPtr)
1818 {
1819     return (LRESULT)infoPtr->hFont;
1820 }
1821
1822 static LRESULT MONTHCAL_SetFont(MONTHCAL_INFO *infoPtr, HFONT hFont, BOOL redraw)
1823 {
1824     HFONT hOldFont;
1825     LOGFONTW lf;
1826
1827     if (!hFont) return 0;
1828
1829     hOldFont = infoPtr->hFont;
1830     infoPtr->hFont = hFont;
1831
1832     GetObjectW(infoPtr->hFont, sizeof(lf), &lf);
1833     lf.lfWeight = FW_BOLD;
1834     infoPtr->hBoldFont = CreateFontIndirectW(&lf);
1835
1836     MONTHCAL_UpdateSize(infoPtr);
1837
1838     if (redraw)
1839         InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1840
1841     return (LRESULT)hOldFont;
1842 }
1843
1844 /* update theme after a WM_THEMECHANGED message */
1845 static LRESULT theme_changed (const MONTHCAL_INFO* infoPtr)
1846 {
1847     HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
1848     CloseThemeData (theme);
1849     OpenThemeData (infoPtr->hwndSelf, themeClass);
1850     return 0;
1851 }
1852
1853 static INT MONTHCAL_StyleChanged(MONTHCAL_INFO *infoPtr, WPARAM wStyleType,
1854                                  const STYLESTRUCT *lpss)
1855 {
1856     TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
1857           wStyleType, lpss->styleOld, lpss->styleNew);
1858
1859     if (wStyleType != GWL_STYLE) return 0;
1860
1861     infoPtr->dwStyle = lpss->styleNew;
1862
1863     return 0;
1864 }
1865
1866 /* FIXME: check whether dateMin/dateMax need to be adjusted. */
1867 static LRESULT
1868 MONTHCAL_Create(HWND hwnd, LPARAM lParam)
1869 {
1870   MONTHCAL_INFO *infoPtr;
1871
1872   /* allocate memory for info structure */
1873   infoPtr = Alloc(sizeof(MONTHCAL_INFO));
1874   SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
1875
1876   if(infoPtr == NULL) {
1877     ERR( "could not allocate info memory!\n");
1878     return 0;
1879   }
1880
1881   infoPtr->hwndSelf = hwnd;
1882   infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
1883   infoPtr->dwStyle  = GetWindowLongW(hwnd, GWL_STYLE);
1884
1885   MONTHCAL_SetFont(infoPtr, GetStockObject(DEFAULT_GUI_FONT), FALSE);
1886
1887   /* initialize info structure */
1888   /* FIXME: calculate systemtime ->> localtime(substract timezoneinfo) */
1889
1890   GetLocalTime(&infoPtr->todaysDate);
1891   infoPtr->firstDayHighWord = FALSE;
1892   MONTHCAL_SetFirstDayOfWeek(infoPtr, (LPARAM)-1);
1893   infoPtr->currentMonth = infoPtr->todaysDate.wMonth;
1894   infoPtr->currentYear = infoPtr->todaysDate.wYear;
1895   MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->minDate);
1896   MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->maxDate);
1897   infoPtr->maxDate.wYear=2050;
1898   infoPtr->minDate.wYear=1950;
1899   infoPtr->maxSelCount  = 7;
1900   infoPtr->monthRange = 3;
1901   infoPtr->monthdayState = Alloc
1902                          (infoPtr->monthRange * sizeof(MONTHDAYSTATE));
1903   infoPtr->titlebk     = comctl32_color.clrActiveCaption;
1904   infoPtr->titletxt    = comctl32_color.clrWindow;
1905   infoPtr->monthbk     = comctl32_color.clrWindow;
1906   infoPtr->trailingtxt = comctl32_color.clrGrayText;
1907   infoPtr->bk          = comctl32_color.clrWindow;
1908   infoPtr->txt         = comctl32_color.clrWindowText;
1909
1910   /* set the current day for highlighing */
1911   infoPtr->minSel.wDay = infoPtr->todaysDate.wDay;
1912   infoPtr->maxSel.wDay = infoPtr->todaysDate.wDay;
1913
1914   /* call MONTHCAL_UpdateSize to set all of the dimensions */
1915   /* of the control */
1916   MONTHCAL_UpdateSize(infoPtr);
1917   
1918   OpenThemeData (infoPtr->hwndSelf, themeClass);
1919
1920   return 0;
1921 }
1922
1923
1924 static LRESULT
1925 MONTHCAL_Destroy(MONTHCAL_INFO *infoPtr)
1926 {
1927   /* free month calendar info data */
1928   Free(infoPtr->monthdayState);
1929   SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
1930
1931   CloseThemeData (GetWindowTheme (infoPtr->hwndSelf));
1932   
1933   Free(infoPtr);
1934   return 0;
1935 }
1936
1937
1938 static LRESULT WINAPI
1939 MONTHCAL_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1940 {
1941   MONTHCAL_INFO *infoPtr;
1942
1943   TRACE("hwnd=%p msg=%x wparam=%lx lparam=%lx\n", hwnd, uMsg, wParam, lParam);
1944
1945   infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1946   if (!infoPtr && (uMsg != WM_CREATE))
1947     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
1948   switch(uMsg)
1949   {
1950   case MCM_GETCURSEL:
1951     return MONTHCAL_GetCurSel(infoPtr, lParam);
1952
1953   case MCM_SETCURSEL:
1954     return MONTHCAL_SetCurSel(infoPtr, lParam);
1955
1956   case MCM_GETMAXSELCOUNT:
1957     return MONTHCAL_GetMaxSelCount(infoPtr);
1958
1959   case MCM_SETMAXSELCOUNT:
1960     return MONTHCAL_SetMaxSelCount(infoPtr, wParam);
1961
1962   case MCM_GETSELRANGE:
1963     return MONTHCAL_GetSelRange(infoPtr, lParam);
1964
1965   case MCM_SETSELRANGE:
1966     return MONTHCAL_SetSelRange(infoPtr, lParam);
1967
1968   case MCM_GETMONTHRANGE:
1969     return MONTHCAL_GetMonthRange(infoPtr);
1970
1971   case MCM_SETDAYSTATE:
1972     return MONTHCAL_SetDayState(infoPtr, wParam, lParam);
1973
1974   case MCM_GETMINREQRECT:
1975     return MONTHCAL_GetMinReqRect(infoPtr, lParam);
1976
1977   case MCM_GETCOLOR:
1978     return MONTHCAL_GetColor(infoPtr, wParam);
1979
1980   case MCM_SETCOLOR:
1981     return MONTHCAL_SetColor(infoPtr, wParam, lParam);
1982
1983   case MCM_GETTODAY:
1984     return MONTHCAL_GetToday(infoPtr, lParam);
1985
1986   case MCM_SETTODAY:
1987     return MONTHCAL_SetToday(infoPtr, lParam);
1988
1989   case MCM_HITTEST:
1990     return MONTHCAL_HitTest(infoPtr, lParam);
1991
1992   case MCM_GETFIRSTDAYOFWEEK:
1993     return MONTHCAL_GetFirstDayOfWeek(infoPtr);
1994
1995   case MCM_SETFIRSTDAYOFWEEK:
1996     return MONTHCAL_SetFirstDayOfWeek(infoPtr, lParam);
1997
1998   case MCM_GETRANGE:
1999     return MONTHCAL_GetRange(hwnd, lParam);
2000
2001   case MCM_SETRANGE:
2002     return MONTHCAL_SetRange(infoPtr, wParam, lParam);
2003
2004   case MCM_GETMONTHDELTA:
2005     return MONTHCAL_GetMonthDelta(infoPtr);
2006
2007   case MCM_SETMONTHDELTA:
2008     return MONTHCAL_SetMonthDelta(infoPtr, wParam);
2009
2010   case MCM_GETMAXTODAYWIDTH:
2011     return MONTHCAL_GetMaxTodayWidth(infoPtr);
2012
2013   case WM_GETDLGCODE:
2014     return DLGC_WANTARROWS | DLGC_WANTCHARS;
2015
2016   case WM_KILLFOCUS:
2017     return MONTHCAL_KillFocus(infoPtr, (HWND)wParam);
2018
2019   case WM_RBUTTONDOWN:
2020     return MONTHCAL_RButtonDown(infoPtr, lParam);
2021
2022   case WM_LBUTTONDOWN:
2023     return MONTHCAL_LButtonDown(infoPtr, lParam);
2024
2025   case WM_MOUSEMOVE:
2026     return MONTHCAL_MouseMove(infoPtr, lParam);
2027
2028   case WM_LBUTTONUP:
2029     return MONTHCAL_LButtonUp(infoPtr, lParam);
2030
2031   case WM_PRINTCLIENT:
2032   case WM_PAINT:
2033     return MONTHCAL_Paint(infoPtr, wParam);
2034
2035   case WM_SETFOCUS:
2036     return MONTHCAL_SetFocus(infoPtr);
2037
2038   case WM_SIZE:
2039     return MONTHCAL_Size(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
2040
2041   case WM_CREATE:
2042     return MONTHCAL_Create(hwnd, lParam);
2043
2044   case WM_SETFONT:
2045     return MONTHCAL_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);
2046
2047   case WM_GETFONT:
2048     return MONTHCAL_GetFont(infoPtr);
2049
2050   case WM_TIMER:
2051     return MONTHCAL_Timer(infoPtr, wParam);
2052     
2053   case WM_THEMECHANGED:
2054     return theme_changed (infoPtr);
2055
2056   case WM_DESTROY:
2057     return MONTHCAL_Destroy(infoPtr);
2058
2059   case WM_SYSCOLORCHANGE:
2060     COMCTL32_RefreshSysColors();
2061     return 0;
2062
2063   case WM_STYLECHANGED:
2064     return MONTHCAL_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
2065
2066   default:
2067     if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
2068       ERR( "unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
2069     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
2070   }
2071 }
2072
2073
2074 void
2075 MONTHCAL_Register(void)
2076 {
2077   WNDCLASSW wndClass;
2078
2079   ZeroMemory(&wndClass, sizeof(WNDCLASSW));
2080   wndClass.style         = CS_GLOBALCLASS;
2081   wndClass.lpfnWndProc   = MONTHCAL_WindowProc;
2082   wndClass.cbClsExtra    = 0;
2083   wndClass.cbWndExtra    = sizeof(MONTHCAL_INFO *);
2084   wndClass.hCursor       = LoadCursorW(0, (LPWSTR)IDC_ARROW);
2085   wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
2086   wndClass.lpszClassName = MONTHCAL_CLASSW;
2087
2088   RegisterClassW(&wndClass);
2089 }
2090
2091
2092 void
2093 MONTHCAL_Unregister(void)
2094 {
2095     UnregisterClassW(MONTHCAL_CLASSW, NULL);
2096 }