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