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