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