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