- define additional shell paths for CSIDL_... constants
[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[1];
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   return TRUE;
1037 }
1038
1039 /* FIXME: if the specified date is not visible, make it visible */
1040 /* FIXME: redraw? */
1041 static LRESULT
1042 MONTHCAL_SetCurSel(HWND hwnd, WPARAM wParam, LPARAM lParam)
1043 {
1044   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1045   SYSTEMTIME *lpSel = (SYSTEMTIME *)lParam;
1046
1047   TRACE("%x %lx\n", wParam, lParam);
1048   if((infoPtr==NULL) ||(lpSel==NULL)) return FALSE;
1049   if(GetWindowLongA(hwnd, GWL_STYLE) & MCS_MULTISELECT) return FALSE;
1050
1051   TRACE("%d %d\n", lpSel->wMonth, lpSel->wDay);
1052
1053   MONTHCAL_CopyTime(lpSel, &infoPtr->minSel);
1054   MONTHCAL_CopyTime(lpSel, &infoPtr->maxSel);
1055
1056   InvalidateRect(hwnd, NULL, FALSE);
1057
1058   return TRUE;
1059 }
1060
1061
1062 static LRESULT
1063 MONTHCAL_GetMaxSelCount(HWND hwnd, WPARAM wParam, LPARAM lParam)
1064 {
1065   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1066
1067   TRACE("%x %lx\n", wParam, lParam);
1068   return infoPtr->maxSelCount;
1069 }
1070
1071
1072 static LRESULT
1073 MONTHCAL_SetMaxSelCount(HWND hwnd, WPARAM wParam, LPARAM lParam)
1074 {
1075   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1076
1077   TRACE("%x %lx\n", wParam, lParam);
1078   if(GetWindowLongA(hwnd, GWL_STYLE) & MCS_MULTISELECT)  {
1079     infoPtr->maxSelCount = wParam;
1080   }
1081
1082   return TRUE;
1083 }
1084
1085
1086 static LRESULT
1087 MONTHCAL_GetSelRange(HWND hwnd, WPARAM wParam, LPARAM lParam)
1088 {
1089   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1090   SYSTEMTIME *lprgSysTimeArray = (SYSTEMTIME *) lParam;
1091
1092   TRACE("%x %lx\n", wParam, lParam);
1093
1094   /* validate parameters */
1095
1096   if((infoPtr==NULL) ||(lprgSysTimeArray==NULL)) return FALSE;
1097
1098   if(GetWindowLongA(hwnd, GWL_STYLE) & MCS_MULTISELECT)
1099   {
1100     MONTHCAL_CopyTime(&infoPtr->maxSel, &lprgSysTimeArray[1]);
1101     MONTHCAL_CopyTime(&infoPtr->minSel, &lprgSysTimeArray[0]);
1102     TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
1103     return TRUE;
1104   }
1105
1106   return FALSE;
1107 }
1108
1109
1110 static LRESULT
1111 MONTHCAL_SetSelRange(HWND hwnd, WPARAM wParam, LPARAM lParam)
1112 {
1113   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1114   SYSTEMTIME *lprgSysTimeArray = (SYSTEMTIME *) lParam;
1115
1116   TRACE("%x %lx\n", wParam, lParam);
1117
1118   /* validate parameters */
1119
1120   if((infoPtr==NULL) ||(lprgSysTimeArray==NULL)) return FALSE;
1121
1122   if(GetWindowLongA( hwnd, GWL_STYLE) & MCS_MULTISELECT)
1123   {
1124     MONTHCAL_CopyTime(&lprgSysTimeArray[1], &infoPtr->maxSel);
1125     MONTHCAL_CopyTime(&lprgSysTimeArray[0], &infoPtr->minSel);
1126     TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
1127     return TRUE;
1128   }
1129
1130   return FALSE;
1131 }
1132
1133
1134 static LRESULT
1135 MONTHCAL_GetToday(HWND hwnd, WPARAM wParam, LPARAM lParam)
1136 {
1137   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1138   SYSTEMTIME *lpToday = (SYSTEMTIME *) lParam;
1139
1140   TRACE("%x %lx\n", wParam, lParam);
1141
1142   /* validate parameters */
1143
1144   if((infoPtr==NULL) || (lpToday==NULL)) return FALSE;
1145   MONTHCAL_CopyTime(&infoPtr->todaysDate, lpToday);
1146   return TRUE;
1147 }
1148
1149
1150 static LRESULT
1151 MONTHCAL_SetToday(HWND hwnd, WPARAM wParam, LPARAM lParam)
1152 {
1153   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1154   SYSTEMTIME *lpToday = (SYSTEMTIME *) lParam;
1155
1156   TRACE("%x %lx\n", wParam, lParam);
1157
1158   /* validate parameters */
1159
1160   if((infoPtr==NULL) ||(lpToday==NULL)) return FALSE;
1161   MONTHCAL_CopyTime(lpToday, &infoPtr->todaysDate);
1162   InvalidateRect(hwnd, NULL, FALSE);
1163   return TRUE;
1164 }
1165
1166
1167 static LRESULT
1168 MONTHCAL_HitTest(HWND hwnd, LPARAM lParam)
1169 {
1170   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1171   PMCHITTESTINFO lpht = (PMCHITTESTINFO)lParam;
1172   UINT x,y;
1173   DWORD retval;
1174   int day,wday,wnum;
1175
1176
1177   x = lpht->pt.x;
1178   y = lpht->pt.y;
1179   retval = MCHT_NOWHERE;
1180
1181
1182   /* Comment in for debugging...
1183   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,
1184         infoPtr->wdays.left, infoPtr->wdays.right,
1185         infoPtr->wdays.top, infoPtr->wdays.bottom,
1186         infoPtr->days.left, infoPtr->days.right,
1187         infoPtr->days.top, infoPtr->days.bottom,
1188         infoPtr->todayrect.left, infoPtr->todayrect.right,
1189         infoPtr->todayrect.top, infoPtr->todayrect.bottom,
1190         infoPtr->weeknums.left, infoPtr->weeknums.right,
1191         infoPtr->weeknums.top, infoPtr->weeknums.bottom);
1192   */
1193
1194   /* are we in the header? */
1195
1196   if(PtInRect(&infoPtr->title, lpht->pt)) {
1197     if(PtInRect(&infoPtr->titlebtnprev, lpht->pt)) {
1198       retval = MCHT_TITLEBTNPREV;
1199       goto done;
1200     }
1201     if(PtInRect(&infoPtr->titlebtnnext, lpht->pt)) {
1202       retval = MCHT_TITLEBTNNEXT;
1203       goto done;
1204     }
1205     if(PtInRect(&infoPtr->titlemonth, lpht->pt)) {
1206       retval = MCHT_TITLEMONTH;
1207       goto done;
1208     }
1209     if(PtInRect(&infoPtr->titleyear, lpht->pt)) {
1210       retval = MCHT_TITLEYEAR;
1211       goto done;
1212     }
1213
1214     retval = MCHT_TITLE;
1215     goto done;
1216   }
1217
1218   day = MONTHCAL_CalcDayFromPos(infoPtr,x,y,&wday,&wnum);
1219   if(PtInRect(&infoPtr->wdays, lpht->pt)) {
1220     retval = MCHT_CALENDARDAY;
1221     lpht->st.wYear  = infoPtr->currentYear;
1222     lpht->st.wMonth = (day < 1)? infoPtr->currentMonth -1 : infoPtr->currentMonth;
1223     lpht->st.wDay   = (day < 1)?
1224       MONTHCAL_MonthLength(infoPtr->currentMonth-1,infoPtr->currentYear) -day : day;
1225     goto done;
1226   }
1227   if(PtInRect(&infoPtr->weeknums, lpht->pt)) {
1228     retval = MCHT_CALENDARWEEKNUM;
1229     lpht->st.wYear  = infoPtr->currentYear;
1230     lpht->st.wMonth = (day < 1) ? infoPtr->currentMonth -1 :
1231       (day > MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear)) ?
1232       infoPtr->currentMonth +1 :infoPtr->currentMonth;
1233     lpht->st.wDay   = (day < 1 ) ?
1234       MONTHCAL_MonthLength(infoPtr->currentMonth-1,infoPtr->currentYear) -day :
1235       (day > MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear)) ?
1236       day - MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear) : day;
1237     goto done;
1238   }
1239   if(PtInRect(&infoPtr->days, lpht->pt))
1240     {
1241       lpht->st.wYear  = infoPtr->currentYear;
1242       if ( day < 1)
1243         {
1244           retval = MCHT_CALENDARDATEPREV;
1245           lpht->st.wMonth = infoPtr->currentMonth - 1;
1246           if (lpht->st.wMonth <1)
1247             {
1248               lpht->st.wMonth = 12;
1249               lpht->st.wYear--;
1250             }
1251           lpht->st.wDay   = MONTHCAL_MonthLength(lpht->st.wMonth,lpht->st.wYear) -day;
1252         }
1253       else if (day > MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear))
1254         {
1255           retval = MCHT_CALENDARDATENEXT;
1256           lpht->st.wMonth = infoPtr->currentMonth + 1;
1257           if (lpht->st.wMonth <12)
1258             {
1259               lpht->st.wMonth = 1;
1260               lpht->st.wYear++;
1261             }
1262           lpht->st.wDay   = day - MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear) ;
1263         }
1264       else {
1265         retval = MCHT_CALENDARDATE;
1266         lpht->st.wMonth = infoPtr->currentMonth;
1267         lpht->st.wDay   = day;
1268       }
1269       goto done;
1270     }
1271   if(PtInRect(&infoPtr->todayrect, lpht->pt)) {
1272     retval = MCHT_TODAYLINK;
1273     goto done;
1274   }
1275
1276
1277   /* Hit nothing special? What's left must be background :-) */
1278
1279   retval = MCHT_CALENDARBK;
1280  done:
1281   lpht->uHit = retval;
1282   return retval;
1283 }
1284
1285
1286 static void MONTHCAL_GoToNextMonth(HWND hwnd, MONTHCAL_INFO *infoPtr)
1287 {
1288   DWORD dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
1289
1290   TRACE("MONTHCAL_GoToNextMonth\n");
1291
1292   infoPtr->currentMonth++;
1293   if(infoPtr->currentMonth > 12) {
1294     infoPtr->currentYear++;
1295     infoPtr->currentMonth = 1;
1296   }
1297
1298   if(dwStyle & MCS_DAYSTATE) {
1299     NMDAYSTATE nmds;
1300     int i;
1301
1302     nmds.nmhdr.hwndFrom = hwnd;
1303     nmds.nmhdr.idFrom   = GetWindowLongA(hwnd, GWL_ID);
1304     nmds.nmhdr.code     = MCN_GETDAYSTATE;
1305     nmds.cDayState      = infoPtr->monthRange;
1306     nmds.prgDayState    = Alloc(infoPtr->monthRange * sizeof(MONTHDAYSTATE));
1307
1308     SendMessageA(infoPtr->hwndNotify, WM_NOTIFY,
1309     (WPARAM)nmds.nmhdr.idFrom, (LPARAM)&nmds);
1310     for(i=0; i<infoPtr->monthRange; i++)
1311       infoPtr->monthdayState[i] = nmds.prgDayState[i];
1312   }
1313 }
1314
1315
1316 static void MONTHCAL_GoToPrevMonth(HWND hwnd,  MONTHCAL_INFO *infoPtr)
1317 {
1318   DWORD dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
1319
1320   TRACE("MONTHCAL_GoToPrevMonth\n");
1321
1322   infoPtr->currentMonth--;
1323   if(infoPtr->currentMonth < 1) {
1324     infoPtr->currentYear--;
1325     infoPtr->currentMonth = 12;
1326   }
1327
1328   if(dwStyle & MCS_DAYSTATE) {
1329     NMDAYSTATE nmds;
1330     int i;
1331
1332     nmds.nmhdr.hwndFrom = hwnd;
1333     nmds.nmhdr.idFrom   = GetWindowLongA(hwnd, GWL_ID);
1334     nmds.nmhdr.code     = MCN_GETDAYSTATE;
1335     nmds.cDayState      = infoPtr->monthRange;
1336     nmds.prgDayState    = Alloc
1337                         (infoPtr->monthRange * sizeof(MONTHDAYSTATE));
1338
1339     SendMessageA(infoPtr->hwndNotify, WM_NOTIFY,
1340         (WPARAM)nmds.nmhdr.idFrom, (LPARAM)&nmds);
1341     for(i=0; i<infoPtr->monthRange; i++)
1342        infoPtr->monthdayState[i] = nmds.prgDayState[i];
1343   }
1344 }
1345
1346 static LRESULT
1347 MONTHCAL_RButtonDown(HWND hwnd, WPARAM wParam, LPARAM lParam)
1348 {
1349   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1350   HMENU hMenu;
1351   POINT menupoint;
1352   char buf[32];
1353
1354   hMenu = CreatePopupMenu();
1355   if (!LoadStringA(COMCTL32_hModule,IDM_GOTODAY,buf,sizeof(buf)))
1356     {
1357       WARN("Can't load resource\n");
1358       strcpy(buf,"Go to Today:");
1359     }
1360   AppendMenuA(hMenu, MF_STRING|MF_ENABLED,1, buf);
1361   menupoint.x=(INT)LOWORD(lParam);
1362   menupoint.y=(INT)HIWORD(lParam);
1363   ClientToScreen(hwnd, &menupoint);
1364   if( TrackPopupMenu(hMenu,TPM_RIGHTBUTTON| TPM_NONOTIFY|TPM_RETURNCMD,
1365                      menupoint.x,menupoint.y,0,hwnd,NULL))
1366     {
1367       infoPtr->currentMonth=infoPtr->todaysDate.wMonth;
1368       infoPtr->currentYear=infoPtr->todaysDate.wYear;
1369       InvalidateRect(hwnd, NULL, FALSE);
1370     }
1371   return 0;
1372 }
1373
1374 static LRESULT
1375 MONTHCAL_LButtonDown(HWND hwnd, WPARAM wParam, LPARAM lParam)
1376 {
1377   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1378   MCHITTESTINFO ht;
1379   DWORD hit;
1380   HMENU hMenu;
1381   RECT rcDay; /* used in determining area to invalidate */
1382   char buf[32];
1383   int i;
1384   POINT menupoint;
1385   TRACE("%x %lx\n", wParam, lParam);
1386
1387   if (infoPtr->hWndYearUpDown)
1388     {
1389       infoPtr->currentYear=SendMessageA( infoPtr->hWndYearUpDown, UDM_SETPOS,   (WPARAM) 0,(LPARAM)0);
1390       if(!DestroyWindow(infoPtr->hWndYearUpDown))
1391         {
1392           FIXME("Can't destroy Updown Control\n");
1393         }
1394       else
1395         infoPtr->hWndYearUpDown=0;
1396       if(!DestroyWindow(infoPtr->hWndYearEdit))
1397         {
1398           FIXME("Can't destroy Updown Control\n");
1399         }
1400       else
1401         infoPtr->hWndYearEdit=0;
1402       InvalidateRect(hwnd, NULL, FALSE);
1403     }
1404
1405   ht.pt.x = (INT)LOWORD(lParam);
1406   ht.pt.y = (INT)HIWORD(lParam);
1407   hit = MONTHCAL_HitTest(hwnd, (LPARAM)&ht);
1408
1409   /* FIXME: these flags should be checked by */
1410   /*((hit & MCHT_XXX) == MCHT_XXX) b/c some of the flags are */
1411   /* multi-bit */
1412   if(hit ==MCHT_TITLEBTNNEXT) {
1413     MONTHCAL_GoToNextMonth(hwnd, infoPtr);
1414     infoPtr->status = MC_NEXTPRESSED;
1415     SetTimer(hwnd, MC_NEXTMONTHTIMER, MC_NEXTMONTHDELAY, 0);
1416     InvalidateRect(hwnd, NULL, FALSE);
1417     return TRUE;
1418   }
1419   if(hit == MCHT_TITLEBTNPREV){
1420     MONTHCAL_GoToPrevMonth(hwnd, infoPtr);
1421     infoPtr->status = MC_PREVPRESSED;
1422     SetTimer(hwnd, MC_PREVMONTHTIMER, MC_NEXTMONTHDELAY, 0);
1423     InvalidateRect(hwnd, NULL, FALSE);
1424     return TRUE;
1425   }
1426
1427   if(hit == MCHT_TITLEMONTH) {
1428     hMenu = CreatePopupMenu();
1429
1430     for (i=0; i<12;i++)
1431       {
1432         GetLocaleInfoA( LOCALE_USER_DEFAULT,LOCALE_SMONTHNAME1+i,
1433                   buf,sizeof(buf));
1434         AppendMenuA(hMenu, MF_STRING|MF_ENABLED,i+1, buf);
1435       }
1436     menupoint.x=infoPtr->titlemonth.right;
1437     menupoint.y=infoPtr->titlemonth.bottom;
1438     ClientToScreen(hwnd, &menupoint);
1439     i= TrackPopupMenu(hMenu,TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON | TPM_RETURNCMD,
1440                       menupoint.x,menupoint.y,0,hwnd,NULL);
1441     if ((i>0) && (i<13))
1442       {
1443         infoPtr->currentMonth=i;
1444         InvalidateRect(hwnd, NULL, FALSE);
1445       }
1446   }
1447   if(hit == MCHT_TITLEYEAR) {
1448     infoPtr->hWndYearEdit=CreateWindowExA(0,
1449                          "EDIT",
1450                            0,
1451                          WS_VISIBLE | WS_CHILD |UDS_SETBUDDYINT,
1452                          infoPtr->titleyear.left+3,infoPtr->titlebtnnext.top,
1453                          infoPtr->titleyear.right-infoPtr->titleyear.left,
1454                          infoPtr->textHeight,
1455                          hwnd,
1456                          NULL,
1457                          NULL,
1458                          NULL);
1459     infoPtr->hWndYearUpDown=CreateWindowExA(0,
1460                          UPDOWN_CLASSA,
1461                            0,
1462                          WS_VISIBLE | WS_CHILD |UDS_SETBUDDYINT|UDS_NOTHOUSANDS|UDS_ARROWKEYS,
1463                          infoPtr->titleyear.right+6,infoPtr->titlebtnnext.top,
1464                          20,
1465                          infoPtr->textHeight,
1466                          hwnd,
1467                          NULL,
1468                          NULL,
1469                          NULL);
1470     SendMessageA( infoPtr->hWndYearUpDown, UDM_SETRANGE, (WPARAM) 0, MAKELONG (9999, 1753));
1471     SendMessageA( infoPtr->hWndYearUpDown, UDM_SETBUDDY, (WPARAM) infoPtr->hWndYearEdit, (LPARAM)0 );
1472     SendMessageA( infoPtr->hWndYearUpDown, UDM_SETPOS,   (WPARAM) 0,(LPARAM)infoPtr->currentYear );
1473     return TRUE;
1474
1475   }
1476   if(hit == MCHT_TODAYLINK) {
1477     infoPtr->currentMonth=infoPtr->todaysDate.wMonth;
1478     infoPtr->currentYear=infoPtr->todaysDate.wYear;
1479     InvalidateRect(hwnd, NULL, FALSE);
1480     return TRUE;
1481   }
1482   if(hit && MCHT_CALENDARDATE) {
1483     SYSTEMTIME selArray[2];
1484     NMSELCHANGE nmsc;
1485
1486     TRACE("MCHT_CALENDARDATE\n");
1487     nmsc.nmhdr.hwndFrom = hwnd;
1488     nmsc.nmhdr.idFrom   = GetWindowLongA(hwnd, GWL_ID);
1489     nmsc.nmhdr.code     = MCN_SELCHANGE;
1490     MONTHCAL_CopyTime(&nmsc.stSelStart, &infoPtr->minSel);
1491     MONTHCAL_CopyTime(&nmsc.stSelEnd, &infoPtr->maxSel);
1492
1493     SendMessageA(infoPtr->hwndNotify, WM_NOTIFY,
1494            (WPARAM)nmsc.nmhdr.idFrom,(LPARAM)&nmsc);
1495
1496     MONTHCAL_CopyTime(&ht.st, &selArray[0]);
1497     MONTHCAL_CopyTime(&ht.st, &selArray[1]);
1498     MONTHCAL_SetSelRange(hwnd,0,(LPARAM) &selArray);
1499
1500     /* redraw both old and new days if the selected day changed */
1501     if(infoPtr->curSelDay != ht.st.wDay) {
1502       MONTHCAL_CalcPosFromDay(infoPtr, ht.st.wDay, ht.st.wMonth, &rcDay);
1503       InvalidateRect(hwnd, &rcDay, TRUE);
1504
1505       MONTHCAL_CalcPosFromDay(infoPtr, infoPtr->curSelDay, infoPtr->currentMonth, &rcDay);
1506       InvalidateRect(hwnd, &rcDay, TRUE);
1507     }
1508
1509     infoPtr->firstSelDay = ht.st.wDay;
1510     infoPtr->curSelDay = ht.st.wDay;
1511     infoPtr->status = MC_SEL_LBUTDOWN;
1512     return TRUE;
1513   }
1514
1515   return 0;
1516 }
1517
1518
1519 static LRESULT
1520 MONTHCAL_LButtonUp(HWND hwnd, WPARAM wParam, LPARAM lParam)
1521 {
1522   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1523   NMSELCHANGE nmsc;
1524   NMHDR nmhdr;
1525   BOOL redraw = FALSE;
1526   MCHITTESTINFO ht;
1527   DWORD hit;
1528
1529   TRACE("\n");
1530
1531   if(infoPtr->status & MC_NEXTPRESSED) {
1532     KillTimer(hwnd, MC_NEXTMONTHTIMER);
1533     redraw = TRUE;
1534   }
1535   if(infoPtr->status & MC_PREVPRESSED) {
1536     KillTimer(hwnd, MC_PREVMONTHTIMER);
1537     redraw = TRUE;
1538   }
1539
1540   ht.pt.x = (INT)LOWORD(lParam);
1541   ht.pt.y = (INT)HIWORD(lParam);
1542   hit = MONTHCAL_HitTest(hwnd, (LPARAM)&ht);
1543
1544   infoPtr->status = MC_SEL_LBUTUP;
1545
1546   if(hit ==MCHT_CALENDARDATENEXT) {
1547     MONTHCAL_GoToNextMonth(hwnd, infoPtr);
1548     InvalidateRect(hwnd, NULL, FALSE);
1549     return TRUE;
1550   }
1551   if(hit == MCHT_CALENDARDATEPREV){
1552     MONTHCAL_GoToPrevMonth(hwnd, infoPtr);
1553     InvalidateRect(hwnd, NULL, FALSE);
1554     return TRUE;
1555   }
1556   nmhdr.hwndFrom = hwnd;
1557   nmhdr.idFrom   = GetWindowLongA( hwnd, GWL_ID);
1558   nmhdr.code     = NM_RELEASEDCAPTURE;
1559   TRACE("Sent notification from %p to %p\n", hwnd, infoPtr->hwndNotify);
1560
1561   SendMessageA(infoPtr->hwndNotify, WM_NOTIFY,
1562                                 (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);
1563
1564   nmsc.nmhdr.hwndFrom = hwnd;
1565   nmsc.nmhdr.idFrom   = GetWindowLongA(hwnd, GWL_ID);
1566   nmsc.nmhdr.code     = MCN_SELECT;
1567   MONTHCAL_CopyTime(&nmsc.stSelStart, &infoPtr->minSel);
1568   MONTHCAL_CopyTime(&nmsc.stSelEnd, &infoPtr->maxSel);
1569
1570   SendMessageA(infoPtr->hwndNotify, WM_NOTIFY,
1571            (WPARAM)nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
1572
1573   /* redraw if necessary */
1574   if(redraw)
1575     InvalidateRect(hwnd, NULL, FALSE);
1576
1577   return 0;
1578 }
1579
1580
1581 static LRESULT
1582 MONTHCAL_Timer(HWND hwnd, WPARAM wParam, LPARAM lParam)
1583 {
1584   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1585   BOOL redraw = FALSE;
1586
1587   TRACE(" %d\n", wParam);
1588   if(!infoPtr) return 0;
1589
1590   switch(wParam) {
1591   case MC_NEXTMONTHTIMER:
1592     redraw = TRUE;
1593     MONTHCAL_GoToNextMonth(hwnd, infoPtr);
1594     break;
1595   case MC_PREVMONTHTIMER:
1596     redraw = TRUE;
1597     MONTHCAL_GoToPrevMonth(hwnd, infoPtr);
1598     break;
1599   default:
1600     ERR("got unknown timer\n");
1601   }
1602
1603   /* redraw only if necessary */
1604   if(redraw)
1605     InvalidateRect(hwnd, NULL, FALSE);
1606
1607   return 0;
1608 }
1609
1610
1611 static LRESULT
1612 MONTHCAL_MouseMove(HWND hwnd, WPARAM wParam, LPARAM lParam)
1613 {
1614   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1615   MCHITTESTINFO ht;
1616   int oldselday, selday, hit;
1617   RECT r;
1618
1619   if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
1620
1621   ht.pt.x = LOWORD(lParam);
1622   ht.pt.y = HIWORD(lParam);
1623
1624   hit = MONTHCAL_HitTest(hwnd, (LPARAM)&ht);
1625
1626   /* not on the calendar date numbers? bail out */
1627   TRACE("hit:%x\n",hit);
1628   if((hit & MCHT_CALENDARDATE) != MCHT_CALENDARDATE) return 0;
1629
1630   selday = ht.st.wDay;
1631   oldselday = infoPtr->curSelDay;
1632   infoPtr->curSelDay = selday;
1633   MONTHCAL_CalcPosFromDay(infoPtr, selday, ht.st. wMonth, &r);
1634
1635   if(GetWindowLongA(hwnd, GWL_STYLE) & MCS_MULTISELECT)  {
1636     SYSTEMTIME selArray[2];
1637     int i;
1638
1639     MONTHCAL_GetSelRange(hwnd, 0, (LPARAM)&selArray);
1640     i = 0;
1641     if(infoPtr->firstSelDay==selArray[0].wDay) i=1;
1642     TRACE("oldRange:%d %d %d %d\n", infoPtr->firstSelDay, selArray[0].wDay, selArray[1].wDay, i);
1643     if(infoPtr->firstSelDay==selArray[1].wDay) {
1644       /* 1st time we get here: selArray[0]=selArray[1])  */
1645       /* if we're still at the first selected date, return */
1646       if(infoPtr->firstSelDay==selday) goto done;
1647       if(selday<infoPtr->firstSelDay) i = 0;
1648     }
1649
1650     if(abs(infoPtr->firstSelDay - selday) >= infoPtr->maxSelCount) {
1651       if(selday>infoPtr->firstSelDay)
1652         selday = infoPtr->firstSelDay + infoPtr->maxSelCount;
1653       else
1654         selday = infoPtr->firstSelDay - infoPtr->maxSelCount;
1655     }
1656
1657     if(selArray[i].wDay!=selday) {
1658       TRACE("newRange:%d %d %d %d\n", infoPtr->firstSelDay, selArray[0].wDay, selArray[1].wDay, i);
1659
1660       selArray[i].wDay = selday;
1661
1662       if(selArray[0].wDay>selArray[1].wDay) {
1663         DWORD tempday;
1664         tempday = selArray[1].wDay;
1665         selArray[1].wDay = selArray[0].wDay;
1666         selArray[0].wDay = tempday;
1667       }
1668
1669       MONTHCAL_SetSelRange(hwnd, 0, (LPARAM)&selArray);
1670     }
1671   }
1672
1673 done:
1674
1675   /* only redraw if the currently selected day changed */
1676   /* FIXME: this should specify a rectangle containing only the days that changed */
1677   /* using InvalidateRect */
1678   if(oldselday != infoPtr->curSelDay)
1679     InvalidateRect(hwnd, NULL, FALSE);
1680
1681   return 0;
1682 }
1683
1684
1685 static LRESULT
1686 MONTHCAL_Paint(HWND hwnd, WPARAM wParam)
1687 {
1688   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1689   HDC hdc;
1690   PAINTSTRUCT ps;
1691
1692   /* fill ps.rcPaint with a default rect */
1693   memcpy(&(ps.rcPaint), &(infoPtr->rcClient), sizeof(infoPtr->rcClient));
1694
1695   hdc = (wParam==0 ? BeginPaint(hwnd, &ps) : (HDC)wParam);
1696   MONTHCAL_Refresh(hwnd, hdc, &ps);
1697   if(!wParam) EndPaint(hwnd, &ps);
1698   return 0;
1699 }
1700
1701
1702 static LRESULT
1703 MONTHCAL_KillFocus(HWND hwnd, WPARAM wParam, LPARAM lParam)
1704 {
1705   TRACE("\n");
1706
1707   InvalidateRect(hwnd, NULL, TRUE);
1708
1709   return 0;
1710 }
1711
1712
1713 static LRESULT
1714 MONTHCAL_SetFocus(HWND hwnd, WPARAM wParam, LPARAM lParam)
1715 {
1716   TRACE("\n");
1717
1718   InvalidateRect(hwnd, NULL, FALSE);
1719
1720   return 0;
1721 }
1722
1723 /* sets the size information */
1724 static void MONTHCAL_UpdateSize(HWND hwnd)
1725 {
1726   HDC hdc = GetDC(hwnd);
1727   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1728   RECT *rcClient=&infoPtr->rcClient;
1729   RECT *rcDraw=&infoPtr->rcDraw;
1730   RECT *title=&infoPtr->title;
1731   RECT *prev=&infoPtr->titlebtnprev;
1732   RECT *next=&infoPtr->titlebtnnext;
1733   RECT *titlemonth=&infoPtr->titlemonth;
1734   RECT *titleyear=&infoPtr->titleyear;
1735   RECT *wdays=&infoPtr->wdays;
1736   RECT *weeknumrect=&infoPtr->weeknums;
1737   RECT *days=&infoPtr->days;
1738   RECT *todayrect=&infoPtr->todayrect;
1739   SIZE size;
1740   TEXTMETRICA tm;
1741   DWORD dwStyle = GetWindowLongA(hwnd, GWL_STYLE);
1742   HFONT currentFont;
1743   double xdiv;
1744
1745   currentFont = SelectObject(hdc, infoPtr->hFont);
1746
1747   /* FIXME: need a way to determine current font, without setting it */
1748   /*
1749   if(infoPtr->hFont!=currentFont) {
1750     SelectObject(hdc, currentFont);
1751     infoPtr->hFont=currentFont;
1752     GetObjectA(currentFont, sizeof(LOGFONTA), &logFont);
1753     logFont.lfWeight=FW_BOLD;
1754     infoPtr->hBoldFont = CreateFontIndirectA(&logFont);
1755   }
1756   */
1757
1758   /* get the height and width of each day's text */
1759   GetTextMetricsA(hdc, &tm);
1760   infoPtr->textHeight = tm.tmHeight + tm.tmExternalLeading;
1761   GetTextExtentPoint32A(hdc, "Sun", 3, &size);
1762   infoPtr->textWidth = size.cx + 2;
1763
1764   /* retrieve the controls client rectangle info infoPtr->rcClient */
1765   GetClientRect(hwnd, rcClient);
1766
1767   /* rcDraw is the rectangle the control is drawn in */
1768   rcDraw->left = rcClient->left;
1769   rcDraw->right = rcClient->right;
1770   rcDraw->top = rcClient->top;
1771   rcDraw->bottom = rcClient->bottom;
1772
1773   /* recalculate the height and width increments and offsets */
1774   /* FIXME: We use up all available width. This will inhibit having multiple
1775      calendars in a row, like win doesn
1776   */
1777   if(dwStyle & MCS_WEEKNUMBERS)
1778     xdiv=8.0;
1779   else
1780     xdiv=7.0;
1781   infoPtr->width_increment = (infoPtr->rcDraw.right - infoPtr->rcDraw.left) / xdiv;
1782   infoPtr->height_increment = (infoPtr->rcDraw.bottom - infoPtr->rcDraw.top) / 10.0;
1783   infoPtr->left_offset = (infoPtr->rcDraw.right - infoPtr->rcDraw.left) - (infoPtr->width_increment * xdiv);
1784   infoPtr->top_offset = (infoPtr->rcDraw.bottom - infoPtr->rcDraw.top) - (infoPtr->height_increment * 10.0);
1785
1786   rcDraw->bottom = rcDraw->top + 10 * infoPtr->height_increment;
1787   /* this is correct, the control does NOT expand vertically */
1788   /* like it does horizontally */
1789   /* make sure we don't move the controls bottom out of the client */
1790   /* area */
1791   /* title line has about 3 text heights, abrev days line, 6 weeksline and today circle line*/
1792   /*if((rcDraw->top + 9 * infoPtr->textHeight + 5) < rcDraw->bottom) {
1793     rcDraw->bottom = rcDraw->top + 9 * infoPtr->textHeight + 5;
1794     }*/
1795
1796   /* calculate title area */
1797   title->top    = rcClient->top;
1798   title->bottom = title->top + 2 * infoPtr->height_increment;
1799   title->left   = rcClient->left;
1800   title->right  = rcClient->right;
1801
1802   /* set the dimensions of the next and previous buttons and center */
1803   /* the month text vertically */
1804   prev->top    = next->top    = title->top + 6;
1805   prev->bottom = next->bottom = title->bottom - 6;
1806   prev->left   = title->left  + 6;
1807   prev->right  = prev->left + (title->bottom - title->top) ;
1808   next->right  = title->right - 6;
1809   next->left   = next->right - (title->bottom - title->top);
1810
1811   /* titlemonth->left and right change based upon the current month */
1812   /* and are recalculated in refresh as the current month may change */
1813   /* without the control being resized */
1814   titlemonth->top    = titleyear->top    = title->top    + (infoPtr->height_increment)/2;
1815   titlemonth->bottom = titleyear->bottom = title->bottom - (infoPtr->height_increment)/2;
1816
1817   /* setup the dimensions of the rectangle we draw the names of the */
1818   /* days of the week in */
1819   weeknumrect->left =infoPtr->left_offset;
1820   if(dwStyle & MCS_WEEKNUMBERS)
1821     weeknumrect->right=prev->right;
1822   else
1823     weeknumrect->right=weeknumrect->left;
1824   wdays->left   = days->left   = weeknumrect->right;
1825   wdays->right  = days->right  = wdays->left + 7 * infoPtr->width_increment;
1826   wdays->top    = title->bottom ;
1827   wdays->bottom = wdays->top + infoPtr->height_increment;
1828
1829   days->top    = weeknumrect->top = wdays->bottom ;
1830   days->bottom = weeknumrect->bottom = days->top     + 6 * infoPtr->height_increment;
1831
1832   todayrect->left   = rcClient->left;
1833   todayrect->right  = rcClient->right;
1834   todayrect->top    = days->bottom;
1835   todayrect->bottom = days->bottom + infoPtr->height_increment;
1836
1837   /* uncomment for excessive debugging
1838   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",
1839         infoPtr->width_increment,infoPtr->height_increment,
1840          rcClient->left, rcClient->right, rcClient->top, rcClient->bottom,
1841             title->left,    title->right,    title->top,    title->bottom,
1842             wdays->left,    wdays->right,    wdays->top,    wdays->bottom,
1843              days->left,     days->right,     days->top,     days->bottom,
1844         todayrect->left,todayrect->right,todayrect->top,todayrect->bottom);
1845   */
1846
1847   /* restore the originally selected font */
1848   SelectObject(hdc, currentFont);
1849
1850   ReleaseDC(hwnd, hdc);
1851 }
1852
1853 static LRESULT MONTHCAL_Size(HWND hwnd, int Width, int Height)
1854 {
1855   TRACE("(hwnd=%p, width=%d, height=%d)\n", hwnd, Width, Height);
1856
1857   MONTHCAL_UpdateSize(hwnd);
1858
1859   /* invalidate client area and erase background */
1860   InvalidateRect(hwnd, NULL, TRUE);
1861
1862   return 0;
1863 }
1864
1865 /* FIXME: check whether dateMin/dateMax need to be adjusted. */
1866 static LRESULT
1867 MONTHCAL_Create(HWND hwnd, WPARAM wParam, LPARAM lParam)
1868 {
1869   MONTHCAL_INFO *infoPtr;
1870   LOGFONTA      logFont;
1871
1872   /* allocate memory for info structure */
1873   infoPtr =(MONTHCAL_INFO*)Alloc(sizeof(MONTHCAL_INFO));
1874   SetWindowLongA(hwnd, 0, (DWORD)infoPtr);
1875
1876   if(infoPtr == NULL) {
1877     ERR( "could not allocate info memory!\n");
1878     return 0;
1879   }
1880   if((MONTHCAL_INFO*)GetWindowLongA(hwnd, 0) != infoPtr) {
1881     ERR( "pointer assignment error!\n");
1882     return 0;
1883   }
1884
1885   infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
1886
1887   infoPtr->hFont = GetStockObject(DEFAULT_GUI_FONT);
1888   GetObjectA(infoPtr->hFont, sizeof(LOGFONTA), &logFont);
1889   logFont.lfWeight = FW_BOLD;
1890   infoPtr->hBoldFont = CreateFontIndirectA(&logFont);
1891
1892   /* initialize info structure */
1893   /* FIXME: calculate systemtime ->> localtime(substract timezoneinfo) */
1894
1895   GetSystemTime(&infoPtr->todaysDate);
1896   MONTHCAL_SetFirstDayOfWeek(hwnd,0,(LPARAM)-1);
1897   infoPtr->currentMonth = infoPtr->todaysDate.wMonth;
1898   infoPtr->currentYear = infoPtr->todaysDate.wYear;
1899   MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->minDate);
1900   MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->maxDate);
1901   infoPtr->maxSelCount  = 7;
1902   infoPtr->monthRange = 3;
1903   infoPtr->monthdayState = Alloc
1904                          (infoPtr->monthRange * sizeof(MONTHDAYSTATE));
1905   infoPtr->titlebk     = GetSysColor(COLOR_ACTIVECAPTION);
1906   infoPtr->titletxt    = GetSysColor(COLOR_WINDOW);
1907   infoPtr->monthbk     = GetSysColor(COLOR_WINDOW);
1908   infoPtr->trailingtxt = GetSysColor(COLOR_GRAYTEXT);
1909   infoPtr->bk          = GetSysColor(COLOR_WINDOW);
1910   infoPtr->txt         = GetSysColor(COLOR_WINDOWTEXT);
1911
1912   /* call MONTHCAL_UpdateSize to set all of the dimensions */
1913   /* of the control */
1914   MONTHCAL_UpdateSize(hwnd);
1915
1916   return 0;
1917 }
1918
1919
1920 static LRESULT
1921 MONTHCAL_Destroy(HWND hwnd, WPARAM wParam, LPARAM lParam)
1922 {
1923   MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
1924
1925   /* free month calendar info data */
1926   if(infoPtr->monthdayState)
1927       Free(infoPtr->monthdayState);
1928   Free(infoPtr);
1929   SetWindowLongA(hwnd, 0, 0);
1930   return 0;
1931 }
1932
1933
1934 static LRESULT WINAPI
1935 MONTHCAL_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1936 {
1937   TRACE("hwnd=%p msg=%x wparam=%x lparam=%lx\n", hwnd, uMsg, wParam, lParam);
1938   if (!MONTHCAL_GetInfoPtr(hwnd) && (uMsg != WM_CREATE))
1939     return DefWindowProcA(hwnd, uMsg, wParam, lParam);
1940   switch(uMsg)
1941   {
1942   case MCM_GETCURSEL:
1943     return MONTHCAL_GetCurSel(hwnd, wParam, lParam);
1944
1945   case MCM_SETCURSEL:
1946     return MONTHCAL_SetCurSel(hwnd, wParam, lParam);
1947
1948   case MCM_GETMAXSELCOUNT:
1949     return MONTHCAL_GetMaxSelCount(hwnd, wParam, lParam);
1950
1951   case MCM_SETMAXSELCOUNT:
1952     return MONTHCAL_SetMaxSelCount(hwnd, wParam, lParam);
1953
1954   case MCM_GETSELRANGE:
1955     return MONTHCAL_GetSelRange(hwnd, wParam, lParam);
1956
1957   case MCM_SETSELRANGE:
1958     return MONTHCAL_SetSelRange(hwnd, wParam, lParam);
1959
1960   case MCM_GETMONTHRANGE:
1961     return MONTHCAL_GetMonthRange(hwnd, wParam, lParam);
1962
1963   case MCM_SETDAYSTATE:
1964     return MONTHCAL_SetDayState(hwnd, wParam, lParam);
1965
1966   case MCM_GETMINREQRECT:
1967     return MONTHCAL_GetMinReqRect(hwnd, wParam, lParam);
1968
1969   case MCM_GETCOLOR:
1970     return MONTHCAL_GetColor(hwnd, wParam, lParam);
1971
1972   case MCM_SETCOLOR:
1973     return MONTHCAL_SetColor(hwnd, wParam, lParam);
1974
1975   case MCM_GETTODAY:
1976     return MONTHCAL_GetToday(hwnd, wParam, lParam);
1977
1978   case MCM_SETTODAY:
1979     return MONTHCAL_SetToday(hwnd, wParam, lParam);
1980
1981   case MCM_HITTEST:
1982     return MONTHCAL_HitTest(hwnd,lParam);
1983
1984   case MCM_GETFIRSTDAYOFWEEK:
1985     return MONTHCAL_GetFirstDayOfWeek(hwnd, wParam, lParam);
1986
1987   case MCM_SETFIRSTDAYOFWEEK:
1988     return MONTHCAL_SetFirstDayOfWeek(hwnd, wParam, lParam);
1989
1990   case MCM_GETRANGE:
1991     return MONTHCAL_GetRange(hwnd, wParam, lParam);
1992
1993   case MCM_SETRANGE:
1994     return MONTHCAL_SetRange(hwnd, wParam, lParam);
1995
1996   case MCM_GETMONTHDELTA:
1997     return MONTHCAL_GetMonthDelta(hwnd, wParam, lParam);
1998
1999   case MCM_SETMONTHDELTA:
2000     return MONTHCAL_SetMonthDelta(hwnd, wParam, lParam);
2001
2002   case MCM_GETMAXTODAYWIDTH:
2003     return MONTHCAL_GetMaxTodayWidth(hwnd);
2004
2005   case WM_GETDLGCODE:
2006     return DLGC_WANTARROWS | DLGC_WANTCHARS;
2007
2008   case WM_KILLFOCUS:
2009     return MONTHCAL_KillFocus(hwnd, wParam, lParam);
2010
2011   case WM_RBUTTONDOWN:
2012     return MONTHCAL_RButtonDown(hwnd, wParam, lParam);
2013
2014   case WM_LBUTTONDOWN:
2015     return MONTHCAL_LButtonDown(hwnd, wParam, lParam);
2016
2017   case WM_MOUSEMOVE:
2018     return MONTHCAL_MouseMove(hwnd, wParam, lParam);
2019
2020   case WM_LBUTTONUP:
2021     return MONTHCAL_LButtonUp(hwnd, wParam, lParam);
2022
2023   case WM_PAINT:
2024     return MONTHCAL_Paint(hwnd, wParam);
2025
2026   case WM_SETFOCUS:
2027     return MONTHCAL_SetFocus(hwnd, wParam, lParam);
2028
2029   case WM_SIZE:
2030     return MONTHCAL_Size(hwnd, (short)LOWORD(lParam), (short)HIWORD(lParam));
2031
2032   case WM_CREATE:
2033     return MONTHCAL_Create(hwnd, wParam, lParam);
2034
2035   case WM_TIMER:
2036     return MONTHCAL_Timer(hwnd, wParam, lParam);
2037
2038   case WM_DESTROY:
2039     return MONTHCAL_Destroy(hwnd, wParam, lParam);
2040
2041   default:
2042     if ((uMsg >= WM_USER) && (uMsg < WM_APP))
2043       ERR( "unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
2044     return DefWindowProcA(hwnd, uMsg, wParam, lParam);
2045   }
2046   return 0;
2047 }
2048
2049
2050 void
2051 MONTHCAL_Register(void)
2052 {
2053   WNDCLASSA wndClass;
2054
2055   ZeroMemory(&wndClass, sizeof(WNDCLASSA));
2056   wndClass.style         = CS_GLOBALCLASS;
2057   wndClass.lpfnWndProc   = (WNDPROC)MONTHCAL_WindowProc;
2058   wndClass.cbClsExtra    = 0;
2059   wndClass.cbWndExtra    = sizeof(MONTHCAL_INFO *);
2060   wndClass.hCursor       = LoadCursorA(0, (LPSTR)IDC_ARROW);
2061   wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
2062   wndClass.lpszClassName = MONTHCAL_CLASSA;
2063
2064   RegisterClassA(&wndClass);
2065 }
2066
2067
2068 void
2069 MONTHCAL_Unregister(void)
2070 {
2071     UnregisterClassA(MONTHCAL_CLASSA, NULL);
2072 }