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