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