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