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