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