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