monthcal.c 60.2 KB
Newer Older
1 2
/* Month calendar control

3
 *
4
 * Copyright 1998, 1999 Eric Kohl (ekohl@abo.rhein-zeitung.de)
5 6 7
 * Copyright 1999 Alex Priem (alexp@sci.kun.nl)
 * Copyright 1999 Chris Morgan <cmorgan@wpi.edu> and
 *		  James Abbatiello <abbeyj@wpi.edu>
8
 * Copyright 2000 Uwe Bonnes <bon@elektron.ikp.physik.tu-darmstadt.de>
9
 *
10 11 12 13 14 15 16 17 18 19 20 21
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
22
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23
 *
24 25 26 27 28 29 30 31 32
 * NOTE
 * 
 * This code was audited for completeness against the documented features
 * of Comctl32.dll version 6.0 on Oct. 20, 2004, by Dimitrie O. Paun.
 * 
 * Unless otherwise noted, we believe this code to be complete, as per
 * the specification mentioned above.
 * If you discover missing features, or bugs, please note them below.
 * 
33
 * TODO:
34 35 36 37 38 39
 *    -- MCM_[GS]ETUNICODEFORMAT
 *    -- MONTHCAL_GetMonthRange
 *    -- handle resources better (doesn't work now); 
 *    -- take care of internationalization.
 *    -- keyboard handling.
 *    -- GetRange: At the moment, we copy ranges anyway, regardless of
40
 *                 infoPtr->rangeValid; an invalid range is simply filled 
41 42
 *                 with zeros in SetRange.  Is this the right behavior?
 *    -- search for FIXME
43 44
 */

45
#include <math.h>
46
#include <stdarg.h>
47
#include <stdio.h>
48
#include <stdlib.h>
49
#include <string.h>
50

51
#include "windef.h"
52
#include "winbase.h"
53
#include "wingdi.h"
54 55
#include "winuser.h"
#include "winnls.h"
56
#include "commctrl.h"
57
#include "comctl32.h"
58 59
#include "uxtheme.h"
#include "tmschema.h"
60
#include "wine/unicode.h"
61
#include "wine/debug.h"
62

63
WINE_DEFAULT_DEBUG_CHANNEL(monthcal);
64

65 66 67 68 69 70 71 72
#define MC_SEL_LBUTUP	    1	/* Left button released */
#define MC_SEL_LBUTDOWN	    2	/* Left button pressed in calendar */
#define MC_PREVPRESSED      4   /* Prev month button pressed */
#define MC_NEXTPRESSED      8   /* Next month button pressed */
#define MC_NEXTMONTHDELAY   350	/* when continuously pressing `next */
										/* month', wait 500 ms before going */
										/* to the next month */
#define MC_NEXTMONTHTIMER   1			/* Timer ID's */
73
#define MC_PREVMONTHTIMER   2
74

75 76
#define countof(arr) (sizeof(arr)/sizeof(arr[0]))

77 78
typedef struct
{
79
    HWND hwndSelf;
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
    COLORREF	bk;
    COLORREF	txt;
    COLORREF	titlebk;
    COLORREF	titletxt;
    COLORREF	monthbk;
    COLORREF	trailingtxt;
    HFONT	hFont;
    HFONT	hBoldFont;
    int		textHeight;
    int		textWidth;
    int		height_increment;
    int		width_increment;
    int		firstDayplace; /* place of the first day of the current month */
    int		delta;	/* scroll rate; # of months that the */
                        /* control moves when user clicks a scroll button */
    int		visible;	/* # of months visible */
    int		firstDay;	/* Start month calendar with firstDay's day */
97
    int		firstDayHighWord;    /* High word only used externally */
98 99 100 101 102 103 104 105 106 107 108 109 110 111
    int		monthRange;
    MONTHDAYSTATE *monthdayState;
    SYSTEMTIME	todaysDate;
    DWORD	currentMonth;
    DWORD	currentYear;
    int		status;		/* See MC_SEL flags */
    int		curSelDay;	/* current selected day */
    int		firstSelDay;	/* first selected day */
    int		maxSelCount;
    SYSTEMTIME	minSel;
    SYSTEMTIME	maxSel;
    DWORD	rangeValid;
    SYSTEMTIME	minDate;
    SYSTEMTIME	maxDate;
112

113 114
    RECT title;		/* rect for the header above the calendar */
    RECT titlebtnnext;	/* the `next month' button in the header */
115
    RECT titlebtnprev;  /* the `prev month' button in the header */
116 117
    RECT titlemonth;	/* the `month name' txt in the header */
    RECT titleyear;	/* the `year number' txt in the header */
118 119
    RECT wdays;		/* week days at top */
    RECT days;		/* calendar area */
120
    RECT weeknums;	/* week numbers at left side */
121
    RECT todayrect;	/* `today: xx/xx/xx' text rect */
122
    HWND hwndNotify;    /* Window to receive the notifications */
123 124
    HWND hWndYearEdit;  /* Window Handle of edit box to handle years */
    HWND hWndYearUpDown;/* Window Handle of updown box to handle years */
125 126 127
} MONTHCAL_INFO, *LPMONTHCAL_INFO;


Duane Clark's avatar
Duane Clark committed
128
/* Offsets of days in the week to the weekday of january 1 in a leap year */
129
static const int DayOfWeekTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
130

131
static const WCHAR themeClass[] = { 'S','c','r','o','l','l','b','a','r',0 };
132

133
#define MONTHCAL_GetInfoPtr(hwnd) ((MONTHCAL_INFO *)GetWindowLongPtrW(hwnd, 0))
134

135 136
/* helper functions  */

137
/* returns the number of days in any given month, checking for leap days */
138
/* january is 1, december is 12 */
139
int MONTHCAL_MonthLength(int month, int year)
140
{
141
  const int mdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
Francois Gouget's avatar
Francois Gouget committed
142
  /*Wrap around, this eases handling*/
143 144 145 146 147
  if(month == 0)
    month = 12;
  if(month == 13)
    month = 1;

148 149 150 151 152
  /* if we have a leap year add 1 day to February */
  /* a leap year is a year either divisible by 400 */
  /* or divisible by 4 and not by 100 */
  if(month == 2) { /* February */
    return mdays[month - 1] + ((year%400 == 0) ? 1 : ((year%100 != 0) &&
153
     (year%4 == 0)) ? 1 : 0);
154 155 156 157 158
  }
  else {
    return mdays[month - 1];
  }
}
159 160


161
/* make sure that time is valid */
162
static int MONTHCAL_ValidateTime(SYSTEMTIME time)
163
{
164
  if(time.wMonth < 1 || time.wMonth > 12 ) return FALSE;
165 166 167 168
  if(time.wDayOfWeek > 6) return FALSE;
  if(time.wDay > MONTHCAL_MonthLength(time.wMonth, time.wYear))
	  return FALSE;

169
  return TRUE;
170 171 172
}


173
/* Note:Depending on DST, this may be offset by a day.
174 175
   Need to find out if we're on a DST place & adjust the clock accordingly.
   Above function assumes we have a valid data.
176
   Valid for year>1752;  1 <= d <= 31, 1 <= m <= 12.
Duane Clark's avatar
Duane Clark committed
177
   0 = Sunday.
178 179
*/

Duane Clark's avatar
Duane Clark committed
180
/* returns the day in the week(0 == sunday, 6 == saturday) */
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 +
Duane Clark's avatar
Duane Clark committed
187
         DayOfWeekTable[month-1] + day ) % 7);
188 189
}

190 191 192
/* From a given point, calculate the row (weekpos), column(daypos)
   and day in the calendar. day== 0 mean the last day of tha last month
*/
193
static int MONTHCAL_CalcDayFromPos(const MONTHCAL_INFO *infoPtr, int x, int y,
194
				   int *daypos,int *weekpos)
195
{
196
  int retval, firstDay;
197 198 199
  RECT rcClient;

  GetClientRect(infoPtr->hwndSelf, &rcClient);
200

201
  /* if the point is outside the x bounds of the window put
202
  it at the boundary */
203 204 205
  if (x > rcClient.right)
    x = rcClient.right;

206

207 208
  *daypos = (x - infoPtr->days.left ) / infoPtr->width_increment;
  *weekpos = (y - infoPtr->days.top ) / infoPtr->height_increment;
209

210 211
  firstDay = (MONTHCAL_CalculateDayOfWeek(1, infoPtr->currentMonth, infoPtr->currentYear)+6 - infoPtr->firstDay)%7;
  retval = *daypos + (7 * *weekpos) - firstDay;
212 213 214 215 216
  return retval;
}

/* day is the day of the month, 1 == 1st day of the month */
/* sets x and y to be the position of the day */
217
/* x == day, y == week where(0,0) == firstDay, 1st week */
218
static void MONTHCAL_CalcDayXY(const MONTHCAL_INFO *infoPtr, int day, int month,
219 220
                                 int *x, int *y)
{
221
  int firstDay, prevMonth;
222

223
  firstDay = (MONTHCAL_CalculateDayOfWeek(1, infoPtr->currentMonth, infoPtr->currentYear) +6 - infoPtr->firstDay)%7;
224

225
  if(month==infoPtr->currentMonth) {
226 227 228 229
    *x = (day + firstDay) % 7;
    *y = (day + firstDay - *x) / 7;
    return;
  }
230
  if(month < infoPtr->currentMonth) {
231
    prevMonth = month - 1;
232
    if(prevMonth==0)
233
       prevMonth = 12;
234

235 236 237 238 239 240 241 242
    *x = (MONTHCAL_MonthLength(prevMonth, infoPtr->currentYear) - firstDay) % 7;
    *y = 0;
    return;
  }

  *y = MONTHCAL_MonthLength(month, infoPtr->currentYear - 1) / 7;
  *x = (day + firstDay + MONTHCAL_MonthLength(month,
       infoPtr->currentYear)) % 7;
243 244 245
}


246
/* x: column(day), y: row(week) */
247
static void MONTHCAL_CalcDayRect(const MONTHCAL_INFO *infoPtr, RECT *r, int x, int y)
248
{
249
  r->left = infoPtr->days.left + x * infoPtr->width_increment;
250
  r->right = r->left + infoPtr->width_increment;
251
  r->top  = infoPtr->days.top  + y * infoPtr->height_increment;
252 253 254
  r->bottom = r->top + infoPtr->textHeight;
}

255 256 257 258

/* sets the RECT struct r to the rectangle around the day and month */
/* day is the day value of the month(1 == 1st), month is the month */
/* value(january == 1, december == 12) */
259
static inline void MONTHCAL_CalcPosFromDay(const MONTHCAL_INFO *infoPtr,
260 261
                                            int day, int month, RECT *r)
{
262
  int x, y;
263

264 265
  MONTHCAL_CalcDayXY(infoPtr, day, month, &x, &y);
  MONTHCAL_CalcDayRect(infoPtr, r, x, y);
266 267 268
}


269 270
/* day is the day in the month(1 == 1st of the month) */
/* month is the month value(1 == january, 12 == december) */
271
static void MONTHCAL_CircleDay(const MONTHCAL_INFO *infoPtr, HDC hdc, int day, int month)
272
{
273 274
  HPEN hRedPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
  HPEN hOldPen2 = SelectObject(hdc, hRedPen);
275
  POINT points[13];
276
  int x, y;
277
  RECT day_rect;
278 279


280
  MONTHCAL_CalcPosFromDay(infoPtr, day, month, &day_rect);
281

282 283
  x = day_rect.left;
  y = day_rect.top;
284

285 286 287 288 289 290 291
  points[0].x = x;
  points[0].y = y - 1;
  points[1].x = x + 0.8 * infoPtr->width_increment;
  points[1].y = y - 1;
  points[2].x = x + 0.9 * infoPtr->width_increment;
  points[2].y = y;
  points[3].x = x + infoPtr->width_increment;
292
  points[3].y = y + 0.5 * infoPtr->height_increment;
293

294
  points[4].x = x + infoPtr->width_increment;
295
  points[4].y = y + 0.9 * infoPtr->height_increment;
296
  points[5].x = x + 0.6 * infoPtr->width_increment;
297
  points[5].y = y + 0.9 * infoPtr->height_increment;
298
  points[6].x = x + 0.5 * infoPtr->width_increment;
299
  points[6].y = y + 0.9 * infoPtr->height_increment; /* bring the bottom up just
300
				a hair to fit inside the day rectangle */
301

302
  points[7].x = x + 0.2 * infoPtr->width_increment;
303
  points[7].y = y + 0.8 * infoPtr->height_increment;
304
  points[8].x = x + 0.1 * infoPtr->width_increment;
305
  points[8].y = y + 0.8 * infoPtr->height_increment;
306
  points[9].x = x;
307
  points[9].y = y + 0.5 * infoPtr->height_increment;
308 309

  points[10].x = x + 0.1 * infoPtr->width_increment;
310
  points[10].y = y + 0.2 * infoPtr->height_increment;
311
  points[11].x = x + 0.2 * infoPtr->width_increment;
312 313 314
  points[11].y = y + 0.3 * infoPtr->height_increment;
  points[12].x = x + 0.4 * infoPtr->width_increment;
  points[12].y = y + 0.2 * infoPtr->height_increment;
315

316 317 318
  PolyBezier(hdc, points, 13);
  DeleteObject(hRedPen);
  SelectObject(hdc, hOldPen2);
319
}
320 321


322
static void MONTHCAL_DrawDay(const MONTHCAL_INFO *infoPtr, HDC hdc, int day, int month,
323
                             int x, int y, int bold)
324
{
325 326
  static const WCHAR fmtW[] = { '%','d',0 };
  WCHAR buf[10];
327
  RECT r;
328
  static int haveBoldFont, haveSelectedDay = FALSE;
329 330 331
  HBRUSH hbr;
  COLORREF oldCol = 0;
  COLORREF oldBk = 0;
332

333
  wsprintfW(buf, fmtW, day);
334

335
/* No need to check styles: when selection is not valid, it is set to zero.
336
 * 1<day<31, so everything is OK.
337 338
 */

339
  MONTHCAL_CalcDayRect(infoPtr, &r, x, y);
340

341
  if((day>=infoPtr->minSel.wDay) && (day<=infoPtr->maxSel.wDay)
342 343 344 345
       && (month==infoPtr->currentMonth)) {
    HRGN hrgn;
    RECT r2;

346
    TRACE("%d %d %d\n",day, infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
347
    TRACE("%s\n", wine_dbgstr_rect(&r));
348 349 350 351 352
    oldCol = SetTextColor(hdc, infoPtr->monthbk);
    oldBk = SetBkColor(hdc, infoPtr->trailingtxt);
    hbr = GetSysColorBrush(COLOR_GRAYTEXT);
    hrgn = CreateEllipticRgn(r.left, r.top, r.right, r.bottom);
    FillRgn(hdc, hrgn, hbr);
353 354 355 356 357 358 359

    /* FIXME: this may need to be changed now b/c of the other
	drawing changes 11/3/99 CMM */
    r2.left   = r.left - 0.25 * infoPtr->textWidth;
    r2.top    = r.top;
    r2.right  = r.left + 0.5 * infoPtr->textWidth;
    r2.bottom = r.bottom;
360
    if(haveSelectedDay) FillRect(hdc, &r2, hbr);
361 362 363 364
      haveSelectedDay = TRUE;
  } else {
    haveSelectedDay = FALSE;
  }
365

366
  /* need to add some code for multiple selections */
367

368 369
  if((bold) &&(!haveBoldFont)) {
    SelectObject(hdc, infoPtr->hBoldFont);
370 371
    haveBoldFont = TRUE;
  }
372 373
  if((!bold) &&(haveBoldFont)) {
    SelectObject(hdc, infoPtr->hFont);
374 375 376
    haveBoldFont = FALSE;
  }

377
  if(haveSelectedDay) {
378 379 380
    SetTextColor(hdc, oldCol);
    SetBkColor(hdc, oldBk);
  }
381

382
  SetBkMode(hdc,TRANSPARENT);
383
  DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
384

385
  /* draw a rectangle around the currently selected days text */
386 387
  if((day==infoPtr->curSelDay) && (month==infoPtr->currentMonth))
    DrawFocusRect(hdc, &r);
388 389 390
}


391
static void paint_button (const MONTHCAL_INFO *infoPtr, HDC hdc, BOOL btnNext,
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
                          BOOL pressed, RECT* r)
{
    HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
    
    if (theme)
    {
        static const int states[] = {
            /* Prev button */
            ABS_LEFTNORMAL,  ABS_LEFTPRESSED,  ABS_LEFTDISABLED,
            /* Next button */
            ABS_RIGHTNORMAL, ABS_RIGHTPRESSED, ABS_RIGHTDISABLED
        };
        int stateNum = btnNext ? 3 : 0;
        if (pressed)
            stateNum += 1;
        else
        {
            DWORD dwStyle = GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE);
            if (dwStyle & WS_DISABLED) stateNum += 2;
        }
        DrawThemeBackground (theme, hdc, SBP_ARROWBTN, states[stateNum], r, NULL);
    }
    else
    {
        int style = btnNext ? DFCS_SCROLLRIGHT : DFCS_SCROLLLEFT;
        if (pressed)
            style |= DFCS_PUSHED;
        else
        {
            DWORD dwStyle = GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE);
            if (dwStyle & WS_DISABLED) style |= DFCS_INACTIVE;
        }
        
        DrawFrameControl(hdc, r, DFC_SCROLL, style);
    }
}


430
static void MONTHCAL_Refresh(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
431
{
432 433 434 435
  static const WCHAR todayW[] = { 'T','o','d','a','y',':',0 };
  static const WCHAR fmt1W[] = { '%','s',' ','%','l','d',0 };
  static const WCHAR fmt2W[] = { '%','s',' ','%','s',0 };
  static const WCHAR fmt3W[] = { '%','d',0 };
436 437 438 439 440
  RECT *title=&infoPtr->title;
  RECT *prev=&infoPtr->titlebtnprev;
  RECT *next=&infoPtr->titlebtnnext;
  RECT *titlemonth=&infoPtr->titlemonth;
  RECT *titleyear=&infoPtr->titleyear;
441 442
  RECT dayrect;
  RECT *days=&dayrect;
443 444
  RECT rtoday;
  int i, j, m, mask, day, firstDay, weeknum, weeknum1,prevMonth;
445
  int textHeight = infoPtr->textHeight;
446 447 448
  SIZE size;
  HBRUSH hbr;
  HFONT currentFont;
449 450 451
  WCHAR buf[20];
  WCHAR buf1[20];
  WCHAR buf2[32];
452
  COLORREF oldTextColor, oldBkColor;
453
  DWORD dwStyle = GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE);
454 455
  RECT rcTemp;
  RECT rcDay; /* used in MONTHCAL_CalcDayRect() */
456 457
  SYSTEMTIME localtime;
  int startofprescal;
458

459
  oldTextColor = SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
460

461 462
  /* fill background */
  hbr = CreateSolidBrush (infoPtr->bk);
463
  FillRect(hdc, &ps->rcPaint, hbr);
464
  DeleteObject(hbr);
465

466
  /* draw header */
467 468 469 470
  if(IntersectRect(&rcTemp, &(ps->rcPaint), title))
  {
    hbr =  CreateSolidBrush(infoPtr->titlebk);
    FillRect(hdc, title, hbr);
471
    DeleteObject(hbr);
472
  }
473

474
  /* if the previous button is pressed draw it depressed */
475
  if(IntersectRect(&rcTemp, &(ps->rcPaint), prev))
476
    paint_button (infoPtr, hdc, FALSE, infoPtr->status & MC_PREVPRESSED, prev);
477

478
  /* if next button is depressed draw it depressed */
479
  if(IntersectRect(&rcTemp, &(ps->rcPaint), next))
480
    paint_button (infoPtr, hdc, TRUE, infoPtr->status & MC_NEXTPRESSED, next);
481

482
  oldBkColor = SetBkColor(hdc, infoPtr->titlebk);
483
  SetTextColor(hdc, infoPtr->titletxt);
484
  currentFont = SelectObject(hdc, infoPtr->hBoldFont);
485

486 487 488
  GetLocaleInfoW( LOCALE_USER_DEFAULT,LOCALE_SMONTHNAME1+infoPtr->currentMonth -1,
		  buf1,countof(buf1));
  wsprintfW(buf, fmt1W, buf1, infoPtr->currentYear);
489

Duane Clark's avatar
Duane Clark committed
490
  if(IntersectRect(&rcTemp, &(ps->rcPaint), title))
491
  {
Duane Clark's avatar
Duane Clark committed
492
    DrawTextW(hdc, buf, strlenW(buf), title,
493
                        DT_CENTER | DT_VCENTER | DT_SINGLELINE);
494 495
  }

496
/* titlemonth left/right contained rect for whole titletxt('June  1999')
497
  * MCM_HitTestInfo wants month & year rects, so prepare these now.
498
  *(no, we can't draw them separately; the whole text is centered)
499
  */
500
  GetTextExtentPoint32W(hdc, buf, strlenW(buf), &size);
Duane Clark's avatar
Duane Clark committed
501 502
  titlemonth->left = title->right / 2 + title->left / 2 - size.cx / 2;
  titleyear->right = title->right / 2 + title->left / 2 + size.cx / 2;
503
  GetTextExtentPoint32W(hdc, buf1, strlenW(buf1), &size);
504
  titlemonth->right = titlemonth->left + size.cx;
505
  titleyear->left = titlemonth->right;
506

507 508 509 510 511 512 513 514 515 516 517
  /* draw month area */
  rcTemp.top=infoPtr->wdays.top;
  rcTemp.left=infoPtr->wdays.left;
  rcTemp.bottom=infoPtr->todayrect.bottom;
  rcTemp.right =infoPtr->todayrect.right;
  if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcTemp))
  {
    hbr =  CreateSolidBrush(infoPtr->monthbk);
    FillRect(hdc, &rcTemp, hbr);
    DeleteObject(hbr);
  }
518

519
/* draw line under day abbreviations */
520

521
  MoveToEx(hdc, infoPtr->days.left + 3, title->bottom + textHeight + 1, NULL);
522
  LineTo(hdc, infoPtr->days.right - 3, title->bottom + textHeight + 1);
523

524 525 526 527 528
  prevMonth = infoPtr->currentMonth - 1;
  if(prevMonth == 0) /* if currentMonth is january(1) prevMonth is */
    prevMonth = 12;    /* december(12) of the previous year */

  infoPtr->wdays.left   = infoPtr->days.left   = infoPtr->weeknums.right;
529
/* draw day abbreviations */
530

Duane Clark's avatar
Duane Clark committed
531
  SelectObject(hdc, infoPtr->hFont);
532
  SetBkColor(hdc, infoPtr->monthbk);
533
  SetTextColor(hdc, infoPtr->trailingtxt);
534

535 536
  /* copy this rect so we can change the values without changing */
  /* the original version */
537 538 539 540
  days->left = infoPtr->wdays.left;
  days->right = days->left + infoPtr->width_increment;
  days->top = infoPtr->wdays.top;
  days->bottom = infoPtr->wdays.bottom;
541

542
  i = infoPtr->firstDay;
543

544
  for(j=0; j<7; j++) {
Duane Clark's avatar
Duane Clark committed
545
    GetLocaleInfoW( LOCALE_USER_DEFAULT,LOCALE_SABBREVDAYNAME1 + (i+j+6)%7, buf, countof(buf));
546
    DrawTextW(hdc, buf, strlenW(buf), days, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
547 548 549
    days->left+=infoPtr->width_increment;
    days->right+=infoPtr->width_increment;
  }
550 551

/* draw day numbers; first, the previous month */
552

553
  firstDay = MONTHCAL_CalculateDayOfWeek(1, infoPtr->currentMonth, infoPtr->currentYear);
554 555

  day = MONTHCAL_MonthLength(prevMonth, infoPtr->currentYear)  +
556 557 558 559
    (infoPtr->firstDay + 7  - firstDay)%7 + 1;
  if (day > MONTHCAL_MonthLength(prevMonth, infoPtr->currentYear))
    day -=7;
  startofprescal = day;
560 561 562 563
  mask = 1<<(day-1);

  i = 0;
  m = 0;
564
  while(day <= MONTHCAL_MonthLength(prevMonth, infoPtr->currentYear)) {
565 566 567
    MONTHCAL_CalcDayRect(infoPtr, &rcDay, i, 0);
    if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcDay))
    {
568
      MONTHCAL_DrawDay(infoPtr, hdc, day, prevMonth, i, 0,
569
          infoPtr->monthdayState[m] & mask);
570 571
    }

572 573 574 575 576
    mask<<=1;
    day++;
    i++;
  }

577 578
/* draw `current' month  */

579 580 581 582 583 584 585 586
  day = 1; /* start at the beginning of the current month */

  infoPtr->firstDayplace = i;
  SetTextColor(hdc, infoPtr->txt);
  m++;
  mask = 1;

  /* draw the first week of the current month */
587
  while(i<7) {
588 589 590 591
    MONTHCAL_CalcDayRect(infoPtr, &rcDay, i, 0);
    if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcDay))
    {

592
      MONTHCAL_DrawDay(infoPtr, hdc, day, infoPtr->currentMonth, i, 0,
593 594
	infoPtr->monthdayState[m] & mask);

595 596 597
      if((infoPtr->currentMonth==infoPtr->todaysDate.wMonth) &&
          (day==infoPtr->todaysDate.wDay) &&
	  (infoPtr->currentYear == infoPtr->todaysDate.wYear)) {
598
        if(!(dwStyle & MCS_NOTODAYCIRCLE))
599
	  MONTHCAL_CircleDay(infoPtr, hdc, day, infoPtr->currentMonth);
600
      }
601 602 603 604 605 606 607 608 609
    }

    mask<<=1;
    day++;
    i++;
  }

  j = 1; /* move to the 2nd week of the current month */
  i = 0; /* move back to sunday */
610
  while(day <= MONTHCAL_MonthLength(infoPtr->currentMonth, infoPtr->currentYear)) {
611 612 613
    MONTHCAL_CalcDayRect(infoPtr, &rcDay, i, j);
    if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcDay))
    {
614
      MONTHCAL_DrawDay(infoPtr, hdc, day, infoPtr->currentMonth, i, j,
615 616
          infoPtr->monthdayState[m] & mask);

617 618
      if((infoPtr->currentMonth==infoPtr->todaysDate.wMonth) &&
          (day==infoPtr->todaysDate.wDay) &&
619 620
          (infoPtr->currentYear == infoPtr->todaysDate.wYear))
        if(!(dwStyle & MCS_NOTODAYCIRCLE))
621
	  MONTHCAL_CircleDay(infoPtr, hdc, day, infoPtr->currentMonth);
622
    }
623 624 625
    mask<<=1;
    day++;
    i++;
626
    if(i>6) { /* past saturday, goto the next weeks sunday */
627 628 629 630
      i = 0;
      j++;
    }
  }
631 632 633

/*  draw `next' month */

634 635 636 637 638
  day = 1; /* start at the first day of the next month */
  m++;
  mask = 1;

  SetTextColor(hdc, infoPtr->trailingtxt);
639
  while((i<7) &&(j<6)) {
640 641
    MONTHCAL_CalcDayRect(infoPtr, &rcDay, i, j);
    if(IntersectRect(&rcTemp, &(ps->rcPaint), &rcDay))
642
    {
643
      MONTHCAL_DrawDay(infoPtr, hdc, day, infoPtr->currentMonth + 1, i, j,
644
		infoPtr->monthdayState[m] & mask);
645
    }
646 647 648

    mask<<=1;
    day++;
649
    i++;
650
    if(i==7) { /* past saturday, go to next week's sunday */
651 652 653 654 655
      i = 0;
      j++;
    }
  }
  SetTextColor(hdc, infoPtr->txt);
656 657 658


/* draw `today' date if style allows it, and draw a circle before today's
659
 * date if necessary */
660

661
  if(!(dwStyle & MCS_NOTODAY))  {
662
    if(!(dwStyle & MCS_NOTODAYCIRCLE))  {
663
      /*day is the number of days from nextmonth we put on the calendar */
664
      MONTHCAL_CircleDay(infoPtr, hdc,
665
			 day+MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear),
666
			 infoPtr->currentMonth);
667
    }
668
    if (!LoadStringW(COMCTL32_hModule,IDM_TODAY,buf1,countof(buf1)))
669 670
      {
	WARN("Can't load resource\n");
671
	strcpyW(buf1, todayW);
672 673 674
      }
    MONTHCAL_CalcDayRect(infoPtr, &rtoday, 1, 6);
    MONTHCAL_CopyTime(&infoPtr->todaysDate,&localtime);
675 676
    GetDateFormatW(LOCALE_USER_DEFAULT,DATE_SHORTDATE,&localtime,NULL,buf2,countof(buf2));
    wsprintfW(buf, fmt2W, buf1, buf2);
677
    SelectObject(hdc, infoPtr->hBoldFont);
678

Duane Clark's avatar
Duane Clark committed
679
    DrawTextW(hdc, buf, -1, &rtoday, DT_CALCRECT | DT_LEFT | DT_VCENTER | DT_SINGLELINE);
680
    if(IntersectRect(&rcTemp, &(ps->rcPaint), &rtoday))
681
    {
682
      DrawTextW(hdc, buf, -1, &rtoday, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
683
    }
684
    SelectObject(hdc, infoPtr->hFont);
685
  }
686

687
/*eventually draw week numbers*/
688
  if(dwStyle & MCS_WEEKNUMBERS)  {
689
    /* display weeknumbers*/
690 691 692 693 694
    int mindays;

    /* Rules what week to call the first week of a new year:
       LOCALE_IFIRSTWEEKOFYEAR == 0 (e.g US?):
       The week containing Jan 1 is the first week of year
695
       LOCALE_IFIRSTWEEKOFYEAR == 2 (e.g. Germany):
696 697 698 699
       First week of year must contain 4 days of the new year
       LOCALE_IFIRSTWEEKOFYEAR == 1  (what contries?)
       The first week of the year must contain only days of the new year
    */
700 701
    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, buf, countof(buf));
    weeknum = atoiW(buf);
702 703 704 705 706 707 708 709 710 711 712 713 714 715
    switch (weeknum)
      {
      case 1: mindays = 6;
	break;
      case 2: mindays = 3;
	break;
      case 0:
      default:
	mindays = 0;
      }
    if (infoPtr->currentMonth < 2)
      {
	/* calculate all those exceptions for january */
	weeknum1=MONTHCAL_CalculateDayOfWeek(1,1,infoPtr->currentYear);
716
	if ((infoPtr->firstDay +7 - weeknum1)%7 > mindays)
717 718 719 720
	    weeknum =1;
	else
	  {
	    weeknum = 0;
721
	    for(i=0; i<11; i++)
722 723 724 725 726 727 728 729 730 731 732
	      weeknum+=MONTHCAL_MonthLength(i+1, infoPtr->currentYear-1);
	    weeknum +=startofprescal+ 7;
	    weeknum /=7;
	    weeknum1=MONTHCAL_CalculateDayOfWeek(1,1,infoPtr->currentYear-1);
	    if ((infoPtr->firstDay + 7 - weeknum1)%7 > mindays)
	      weeknum++;
	  }
      }
    else
      {
	weeknum = 0;
733
	for(i=0; i<prevMonth-1; i++)
734 735 736 737 738 739 740 741 742 743 744
	  weeknum+=MONTHCAL_MonthLength(i+1, infoPtr->currentYear);
	weeknum +=startofprescal+ 7;
	weeknum /=7;
	weeknum1=MONTHCAL_CalculateDayOfWeek(1,1,infoPtr->currentYear);
	if ((infoPtr->firstDay + 7 - weeknum1)%7 > mindays)
	  weeknum++;
      }
    days->left = infoPtr->weeknums.left;
    days->right = infoPtr->weeknums.right;
    days->top = infoPtr->weeknums.top;
    days->bottom = days->top +infoPtr->height_increment;
745
    for(i=0; i<6; i++) {
746 747
      if((i==0)&&(weeknum>50))
	{
748
	  wsprintfW(buf, fmt3W, weeknum);
749 750 751 752
	  weeknum=0;
	}
      else if((i==5)&&(weeknum>47))
	{
753
	  wsprintfW(buf, fmt3W, 1);
754 755
	}
      else
756 757
	wsprintfW(buf, fmt3W, weeknum + i);
      DrawTextW(hdc, buf, -1, days, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
758 759
      days->top+=infoPtr->height_increment;
      days->bottom+=infoPtr->height_increment;
760
    }
761

762 763
    MoveToEx(hdc, infoPtr->weeknums.right, infoPtr->weeknums.top + 3 , NULL);
    LineTo(hdc,   infoPtr->weeknums.right, infoPtr->weeknums.bottom );
764

765 766
  }
  /* currentFont was font at entering Refresh */
767

768
  SetBkColor(hdc, oldBkColor);
769
  SelectObject(hdc, currentFont);
770
  SetTextColor(hdc, oldTextColor);
771 772 773
}


774
static LRESULT
775
MONTHCAL_GetMinReqRect(const MONTHCAL_INFO *infoPtr, LPARAM lParam)
776
{
777
  LPRECT lpRect = (LPRECT) lParam;
778 779

  TRACE("rect %p\n", lpRect);
780

781 782
  /* validate parameters */

783
  if((infoPtr==NULL) ||(lpRect == NULL) ) return FALSE;
784

785 786 787 788
  lpRect->left = infoPtr->title.left;
  lpRect->top = infoPtr->title.top;
  lpRect->right = infoPtr->title.right;
  lpRect->bottom = infoPtr->todayrect.bottom;
789
  AdjustWindowRect(lpRect, GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE), FALSE);
790 791 792

  TRACE("%s\n", wine_dbgstr_rect(lpRect));

793
  return TRUE;
794 795
}

796

797
static LRESULT
798
MONTHCAL_GetColor(const MONTHCAL_INFO *infoPtr, WPARAM wParam)
799
{
800
  TRACE("\n");
801

802
  switch((int)wParam) {
803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
    case MCSC_BACKGROUND:
      return infoPtr->bk;
    case MCSC_TEXT:
      return infoPtr->txt;
    case MCSC_TITLEBK:
      return infoPtr->titlebk;
    case MCSC_TITLETEXT:
      return infoPtr->titletxt;
    case MCSC_MONTHBK:
      return infoPtr->monthbk;
    case MCSC_TRAILINGTEXT:
      return infoPtr->trailingtxt;
  }

  return -1;
818 819
}

820

821
static LRESULT
822
MONTHCAL_SetColor(MONTHCAL_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
823
{
824
  int prev = -1;
825

826
  TRACE("%ld: color %08lx\n", wParam, lParam);
827

828
  switch((int)wParam) {
829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854
    case MCSC_BACKGROUND:
      prev = infoPtr->bk;
      infoPtr->bk = (COLORREF)lParam;
      break;
    case MCSC_TEXT:
      prev = infoPtr->txt;
      infoPtr->txt = (COLORREF)lParam;
      break;
    case MCSC_TITLEBK:
      prev = infoPtr->titlebk;
      infoPtr->titlebk = (COLORREF)lParam;
      break;
    case MCSC_TITLETEXT:
      prev=infoPtr->titletxt;
      infoPtr->titletxt = (COLORREF)lParam;
      break;
    case MCSC_MONTHBK:
      prev = infoPtr->monthbk;
      infoPtr->monthbk = (COLORREF)lParam;
      break;
    case MCSC_TRAILINGTEXT:
      prev = infoPtr->trailingtxt;
      infoPtr->trailingtxt = (COLORREF)lParam;
      break;
  }

855
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
856
  return prev;
857 858
}

859

860
static LRESULT
861
MONTHCAL_GetMonthDelta(const MONTHCAL_INFO *infoPtr)
862
{
863
  TRACE("\n");
864

865
  if(infoPtr->delta)
866 867 868
    return infoPtr->delta;
  else
    return infoPtr->visible;
869 870
}

871

872
static LRESULT
873
MONTHCAL_SetMonthDelta(MONTHCAL_INFO *infoPtr, WPARAM wParam)
874
{
875
  int prev = infoPtr->delta;
876

877
  TRACE("delta %ld\n", wParam);
878

879 880
  infoPtr->delta = (int)wParam;
  return prev;
881 882 883
}


884
static LRESULT
885
MONTHCAL_GetFirstDayOfWeek(const MONTHCAL_INFO *infoPtr)
886
{
887
  return MAKELONG(infoPtr->firstDay, infoPtr->firstDayHighWord);
888 889 890
}


891
/* sets the first day of the week that will appear in the control */
Duane Clark's avatar
Duane Clark committed
892
/* 0 == Sunday, 6 == Saturday */
893 894
/* FIXME: this needs to be implemented properly in MONTHCAL_Refresh() */
/* FIXME: we need more error checking here */
895
static LRESULT
896
MONTHCAL_SetFirstDayOfWeek(MONTHCAL_INFO *infoPtr, LPARAM lParam)
897
{
898
  int prev = MAKELONG(infoPtr->firstDay, infoPtr->firstDayHighWord);
899
  int localFirstDay;
900
  WCHAR buf[40];
901

902
  TRACE("day %ld\n", lParam);
903

904 905 906 907 908 909
  GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, buf, countof(buf));
  TRACE("%s %d\n", debugstr_w(buf), strlenW(buf));

  localFirstDay = atoiW(buf);

  if(lParam == -1)
910 911 912 913
  {
    infoPtr->firstDay = localFirstDay;
    infoPtr->firstDayHighWord = FALSE;
  }
914
  else if(lParam >= 7)
915
  {
916
    infoPtr->firstDay = 6; /* max first day allowed */
917 918
    infoPtr->firstDayHighWord = TRUE;
  }
919
  else
920 921 922 923
  {
    infoPtr->firstDay = lParam;
    infoPtr->firstDayHighWord = TRUE;
  }
924

925
  return prev;
926 927 928 929
}


static LRESULT
930
MONTHCAL_GetMonthRange(const MONTHCAL_INFO *infoPtr)
931
{
932
  TRACE("\n");
933

934 935 936
  return infoPtr->monthRange;
}

937

938
static LRESULT
939
MONTHCAL_GetMaxTodayWidth(const MONTHCAL_INFO *infoPtr)
940
{
941
  return(infoPtr->todayrect.right - infoPtr->todayrect.left);
942 943
}

944

945
static LRESULT
946
MONTHCAL_SetRange(MONTHCAL_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
947
{
948
    SYSTEMTIME *lprgSysTimeArray=(SYSTEMTIME *)lParam;
949
    FILETIME ft_min, ft_max;
950

951
    TRACE("%lx %lx\n", wParam, lParam);
952 953 954 955 956 957 958 959 960

    if ((wParam & GDTR_MIN && !MONTHCAL_ValidateTime(lprgSysTimeArray[0])) ||
        (wParam & GDTR_MAX && !MONTHCAL_ValidateTime(lprgSysTimeArray[1])))
        return FALSE;

    if (wParam & GDTR_MIN)
    {
        MONTHCAL_CopyTime(&lprgSysTimeArray[0], &infoPtr->minDate);
        infoPtr->rangeValid |= GDTR_MIN;
961
    }
962 963 964 965
    if (wParam & GDTR_MAX)
    {
        MONTHCAL_CopyTime(&lprgSysTimeArray[1], &infoPtr->maxDate);
        infoPtr->rangeValid |= GDTR_MAX;
966 967
    }

968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991
    /* Only one limit set - we are done */
    if ((infoPtr->rangeValid & (GDTR_MIN | GDTR_MAX)) != (GDTR_MIN | GDTR_MAX))
        return TRUE;
    
    SystemTimeToFileTime(&infoPtr->maxDate, &ft_max);
    SystemTimeToFileTime(&infoPtr->minDate, &ft_min);

    if (CompareFileTime(&ft_min, &ft_max) > 0)
    {
        if ((wParam & (GDTR_MIN | GDTR_MAX)) == (GDTR_MIN | GDTR_MAX))
        {
            /* Native swaps limits only when both limits are being set. */
            SYSTEMTIME st_tmp = infoPtr->minDate;
            infoPtr->minDate  = infoPtr->maxDate;
            infoPtr->maxDate  = st_tmp;
        }
        else
        {
            /* Reset the other limit. */
            /* FIXME: native sets date&time to 0. Should we do this too? */
            infoPtr->rangeValid &= wParam & GDTR_MIN ? ~GDTR_MAX : ~GDTR_MIN ;
        }
    }

992
    return TRUE;
993 994
}

995

996
static LRESULT
997
MONTHCAL_GetRange(HWND hwnd, LPARAM lParam)
998
{
999 1000
  MONTHCAL_INFO *infoPtr = MONTHCAL_GetInfoPtr(hwnd);
  SYSTEMTIME *lprgSysTimeArray = (SYSTEMTIME *)lParam;
1001 1002 1003

  /* validate parameters */

1004
  if((infoPtr==NULL) || (lprgSysTimeArray==NULL)) return FALSE;
1005

1006 1007
  MONTHCAL_CopyTime(&infoPtr->maxDate, &lprgSysTimeArray[1]);
  MONTHCAL_CopyTime(&infoPtr->minDate, &lprgSysTimeArray[0]);
1008 1009 1010 1011

  return infoPtr->rangeValid;
}

1012

1013
static LRESULT
1014
MONTHCAL_SetDayState(const MONTHCAL_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
1015 1016

{
1017 1018
  int i, iMonths = (int)wParam;
  MONTHDAYSTATE *dayStates = (LPMONTHDAYSTATE)lParam;
1019

1020
  TRACE("%lx %lx\n", wParam, lParam);
1021
  if(iMonths!=infoPtr->monthRange) return 0;
1022

1023
  for(i=0; i<iMonths; i++)
1024
    infoPtr->monthdayState[i] = dayStates[i];
1025 1026 1027
  return 1;
}

1028
static LRESULT
1029
MONTHCAL_GetCurSel(const MONTHCAL_INFO *infoPtr, LPARAM lParam)
1030
{
1031
  SYSTEMTIME *lpSel = (SYSTEMTIME *) lParam;
1032

1033
  TRACE("%lx\n", lParam);
1034
  if((infoPtr==NULL) ||(lpSel==NULL)) return FALSE;
1035
  if(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & MCS_MULTISELECT) return FALSE;
1036

1037
  MONTHCAL_CopyTime(&infoPtr->minSel, lpSel);
1038
  TRACE("%d/%d/%d\n", lpSel->wYear, lpSel->wMonth, lpSel->wDay);
1039 1040 1041 1042 1043
  return TRUE;
}

/* FIXME: if the specified date is not visible, make it visible */
/* FIXME: redraw? */
1044
static LRESULT
1045
MONTHCAL_SetCurSel(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1046
{
1047
  SYSTEMTIME *lpSel = (SYSTEMTIME *)lParam;
1048

1049
  TRACE("%lx\n", lParam);
1050
  if((infoPtr==NULL) ||(lpSel==NULL)) return FALSE;
1051
  if(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & MCS_MULTISELECT) return FALSE;
1052

1053 1054
  if(!MONTHCAL_ValidateTime(*lpSel)) return FALSE;

1055 1056
  infoPtr->currentMonth=lpSel->wMonth;
  infoPtr->currentYear=lpSel->wYear;
1057

1058 1059
  MONTHCAL_CopyTime(lpSel, &infoPtr->minSel);
  MONTHCAL_CopyTime(lpSel, &infoPtr->maxSel);
1060

1061
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1062

1063 1064 1065
  return TRUE;
}

1066

1067
static LRESULT
1068
MONTHCAL_GetMaxSelCount(const MONTHCAL_INFO *infoPtr)
1069 1070 1071 1072
{
  return infoPtr->maxSelCount;
}

1073

1074
static LRESULT
1075
MONTHCAL_SetMaxSelCount(MONTHCAL_INFO *infoPtr, WPARAM wParam)
1076
{
1077
  TRACE("%lx\n", wParam);
1078

1079
  if(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & MCS_MULTISELECT)  {
1080
    infoPtr->maxSelCount = wParam;
1081 1082 1083 1084 1085 1086
  }

  return TRUE;
}


1087
static LRESULT
1088
MONTHCAL_GetSelRange(const MONTHCAL_INFO *infoPtr, LPARAM lParam)
1089
{
1090
  SYSTEMTIME *lprgSysTimeArray = (SYSTEMTIME *) lParam;
1091

1092
  TRACE("%lx\n", lParam);
1093 1094 1095

  /* validate parameters */

1096
  if((infoPtr==NULL) ||(lprgSysTimeArray==NULL)) return FALSE;
1097

1098
  if(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & MCS_MULTISELECT)
1099 1100 1101 1102
  {
    MONTHCAL_CopyTime(&infoPtr->maxSel, &lprgSysTimeArray[1]);
    MONTHCAL_CopyTime(&infoPtr->minSel, &lprgSysTimeArray[0]);
    TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
1103
    return TRUE;
1104
  }
1105

1106 1107 1108
  return FALSE;
}

1109

1110
static LRESULT
1111
MONTHCAL_SetSelRange(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1112
{
1113
  SYSTEMTIME *lprgSysTimeArray = (SYSTEMTIME *) lParam;
1114

1115
  TRACE("%lx\n", lParam);
1116 1117 1118

  /* validate parameters */

1119
  if((infoPtr==NULL) ||(lprgSysTimeArray==NULL)) return FALSE;
1120

1121
  if(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & MCS_MULTISELECT)
1122 1123 1124 1125
  {
    MONTHCAL_CopyTime(&lprgSysTimeArray[1], &infoPtr->maxSel);
    MONTHCAL_CopyTime(&lprgSysTimeArray[0], &infoPtr->minSel);
    TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
1126
    return TRUE;
1127
  }
1128

1129 1130 1131 1132
  return FALSE;
}


1133
static LRESULT
1134
MONTHCAL_GetToday(const MONTHCAL_INFO *infoPtr, LPARAM lParam)
1135
{
1136
  SYSTEMTIME *lpToday = (SYSTEMTIME *) lParam;
1137

1138
  TRACE("%lx\n", lParam);
1139 1140 1141

  /* validate parameters */

1142 1143
  if((infoPtr==NULL) || (lpToday==NULL)) return FALSE;
  MONTHCAL_CopyTime(&infoPtr->todaysDate, lpToday);
1144 1145 1146 1147
  return TRUE;
}


1148
static LRESULT
1149
MONTHCAL_SetToday(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1150
{
1151
  SYSTEMTIME *lpToday = (SYSTEMTIME *) lParam;
1152

1153
  TRACE("%lx\n", lParam);
1154

1155
  /* validate parameters */
1156

1157 1158
  if((infoPtr==NULL) ||(lpToday==NULL)) return FALSE;
  MONTHCAL_CopyTime(lpToday, &infoPtr->todaysDate);
1159
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1160
  return TRUE;
1161 1162 1163 1164
}


static LRESULT
1165
MONTHCAL_HitTest(const MONTHCAL_INFO *infoPtr, LPARAM lParam)
1166
{
1167 1168 1169 1170
  PMCHITTESTINFO lpht = (PMCHITTESTINFO)lParam;
  UINT x,y;
  DWORD retval;
  int day,wday,wnum;
1171 1172


1173 1174
  x = lpht->pt.x;
  y = lpht->pt.y;
1175

1176
  ZeroMemory(&lpht->st, sizeof(lpht->st));
1177 1178 1179

  /* Comment in for debugging...
  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,
1180 1181 1182 1183 1184 1185 1186 1187 1188
	infoPtr->wdays.left, infoPtr->wdays.right,
	infoPtr->wdays.top, infoPtr->wdays.bottom,
	infoPtr->days.left, infoPtr->days.right,
	infoPtr->days.top, infoPtr->days.bottom,
	infoPtr->todayrect.left, infoPtr->todayrect.right,
	infoPtr->todayrect.top, infoPtr->todayrect.bottom,
	infoPtr->weeknums.left, infoPtr->weeknums.right,
	infoPtr->weeknums.top, infoPtr->weeknums.bottom);
  */
1189

1190
  /* are we in the header? */
1191

1192 1193
  if(PtInRect(&infoPtr->title, lpht->pt)) {
    if(PtInRect(&infoPtr->titlebtnprev, lpht->pt)) {
1194 1195 1196
      retval = MCHT_TITLEBTNPREV;
      goto done;
    }
1197
    if(PtInRect(&infoPtr->titlebtnnext, lpht->pt)) {
1198 1199 1200
      retval = MCHT_TITLEBTNNEXT;
      goto done;
    }
1201
    if(PtInRect(&infoPtr->titlemonth, lpht->pt)) {
1202 1203 1204
      retval = MCHT_TITLEMONTH;
      goto done;
    }
1205
    if(PtInRect(&infoPtr->titleyear, lpht->pt)) {
1206 1207 1208
      retval = MCHT_TITLEYEAR;
      goto done;
    }
1209

1210 1211 1212
    retval = MCHT_TITLE;
    goto done;
  }
1213

1214 1215 1216 1217 1218
  day = MONTHCAL_CalcDayFromPos(infoPtr,x,y,&wday,&wnum);
  if(PtInRect(&infoPtr->wdays, lpht->pt)) {
    retval = MCHT_CALENDARDAY;
    lpht->st.wYear  = infoPtr->currentYear;
    lpht->st.wMonth = (day < 1)? infoPtr->currentMonth -1 : infoPtr->currentMonth;
1219
    lpht->st.wDay   = (day < 1)?
1220
      MONTHCAL_MonthLength(infoPtr->currentMonth-1,infoPtr->currentYear) -day : day;
1221 1222
    goto done;
  }
1223 1224
  if(PtInRect(&infoPtr->weeknums, lpht->pt)) {
    retval = MCHT_CALENDARWEEKNUM;
1225
    lpht->st.wYear  = infoPtr->currentYear;
1226 1227
    lpht->st.wMonth = (day < 1) ? infoPtr->currentMonth -1 :
      (day > MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear)) ?
1228
      infoPtr->currentMonth +1 :infoPtr->currentMonth;
1229 1230 1231
    lpht->st.wDay   = (day < 1 ) ?
      MONTHCAL_MonthLength(infoPtr->currentMonth-1,infoPtr->currentYear) -day :
      (day > MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear)) ?
1232
      day - MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear) : day;
1233
    goto done;
1234
  }
1235
  if(PtInRect(&infoPtr->days, lpht->pt))
1236 1237
    {
      lpht->st.wYear  = infoPtr->currentYear;
1238
      if ( day < 1)
1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263
	{
	  retval = MCHT_CALENDARDATEPREV;
	  lpht->st.wMonth = infoPtr->currentMonth - 1;
	  if (lpht->st.wMonth <1)
	    {
	      lpht->st.wMonth = 12;
	      lpht->st.wYear--;
	    }
	  lpht->st.wDay   = MONTHCAL_MonthLength(lpht->st.wMonth,lpht->st.wYear) -day;
	}
      else if (day > MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear))
	{
	  retval = MCHT_CALENDARDATENEXT;
	  lpht->st.wMonth = infoPtr->currentMonth + 1;
	  if (lpht->st.wMonth <12)
	    {
	      lpht->st.wMonth = 1;
	      lpht->st.wYear++;
	    }
	  lpht->st.wDay   = day - MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear) ;
	}
      else {
	retval = MCHT_CALENDARDATE;
	lpht->st.wMonth = infoPtr->currentMonth;
	lpht->st.wDay   = day;
Duane Clark's avatar
Duane Clark committed
1264
	lpht->st.wDayOfWeek   = MONTHCAL_CalculateDayOfWeek(day,lpht->st.wMonth,lpht->st.wYear);
1265 1266 1267 1268
      }
      goto done;
    }
  if(PtInRect(&infoPtr->todayrect, lpht->pt)) {
1269
    retval = MCHT_TODAYLINK;
1270 1271
    goto done;
  }
1272 1273


1274
  /* Hit nothing special? What's left must be background :-) */
1275 1276 1277

  retval = MCHT_CALENDARBK;
 done:
1278
  lpht->uHit = retval;
1279 1280 1281 1282
  return retval;
}


1283
static void MONTHCAL_GoToNextMonth(MONTHCAL_INFO *infoPtr)
1284
{
1285
  DWORD dwStyle = GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE);
1286

1287
  TRACE("MONTHCAL_GoToNextMonth\n");
1288

1289
  infoPtr->currentMonth++;
1290
  if(infoPtr->currentMonth > 12) {
1291 1292 1293
    infoPtr->currentYear++;
    infoPtr->currentMonth = 1;
  }
1294

1295
  if(dwStyle & MCS_DAYSTATE) {
1296 1297
    NMDAYSTATE nmds;
    int i;
1298

1299 1300
    nmds.nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmds.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1301 1302
    nmds.nmhdr.code     = MCN_GETDAYSTATE;
    nmds.cDayState	= infoPtr->monthRange;
1303
    nmds.prgDayState	= Alloc(infoPtr->monthRange * sizeof(MONTHDAYSTATE));
1304

1305 1306 1307 1308 1309
    nmds.stStart = infoPtr->todaysDate;
    nmds.stStart.wYear = infoPtr->currentYear;
    nmds.stStart.wMonth = infoPtr->currentMonth;
    nmds.stStart.wDay = 1;

1310
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmds.nmhdr.idFrom, (LPARAM)&nmds);
1311
    for(i=0; i<infoPtr->monthRange; i++)
1312 1313
      infoPtr->monthdayState[i] = nmds.prgDayState[i];
  }
1314 1315 1316
}


1317
static void MONTHCAL_GoToPrevMonth(MONTHCAL_INFO *infoPtr)
1318
{
1319
  DWORD dwStyle = GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE);
1320

1321
  TRACE("\n");
1322 1323

  infoPtr->currentMonth--;
1324
  if(infoPtr->currentMonth < 1) {
1325 1326 1327 1328
    infoPtr->currentYear--;
    infoPtr->currentMonth = 12;
  }

1329
  if(dwStyle & MCS_DAYSTATE) {
1330 1331 1332
    NMDAYSTATE nmds;
    int i;

1333 1334
    nmds.nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmds.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1335 1336
    nmds.nmhdr.code     = MCN_GETDAYSTATE;
    nmds.cDayState	= infoPtr->monthRange;
1337
    nmds.prgDayState	= Alloc
1338
                        (infoPtr->monthRange * sizeof(MONTHDAYSTATE));
1339

1340 1341 1342 1343 1344
    nmds.stStart = infoPtr->todaysDate;
    nmds.stStart.wYear = infoPtr->currentYear;
    nmds.stStart.wMonth = infoPtr->currentMonth;
    nmds.stStart.wDay = 1;

1345
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmds.nmhdr.idFrom, (LPARAM)&nmds);
1346 1347
    for(i=0; i<infoPtr->monthRange; i++)
       infoPtr->monthdayState[i] = nmds.prgDayState[i];
1348
  }
1349 1350
}

1351
static LRESULT
1352
MONTHCAL_RButtonDown(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1353
{
1354
  static const WCHAR todayW[] = { 'G','o',' ','t','o',' ','T','o','d','a','y',':',0 };
1355 1356
  HMENU hMenu;
  POINT menupoint;
1357
  WCHAR buf[32];
1358

1359
  hMenu = CreatePopupMenu();
1360
  if (!LoadStringW(COMCTL32_hModule,IDM_GOTODAY,buf,countof(buf)))
1361 1362
    {
      WARN("Can't load resource\n");
1363
      strcpyW(buf, todayW);
1364
    }
1365
  AppendMenuW(hMenu, MF_STRING|MF_ENABLED,1, buf);
1366 1367
  menupoint.x=(short)LOWORD(lParam);
  menupoint.y=(short)HIWORD(lParam);
1368
  ClientToScreen(infoPtr->hwndSelf, &menupoint);
1369
  if( TrackPopupMenu(hMenu,TPM_RIGHTBUTTON| TPM_NONOTIFY|TPM_RETURNCMD,
1370
		     menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL))
1371 1372 1373
    {
      infoPtr->currentMonth=infoPtr->todaysDate.wMonth;
      infoPtr->currentYear=infoPtr->todaysDate.wYear;
1374
      InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1375
    }
1376 1377
  return 0;
}
1378

1379
static LRESULT
1380
MONTHCAL_LButtonDown(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1381
{
1382
  static const WCHAR EditW[] = { 'E','D','I','T',0 };
1383 1384 1385
  MCHITTESTINFO ht;
  DWORD hit;
  HMENU hMenu;
1386
  RECT rcDay; /* used in determining area to invalidate */
1387
  WCHAR buf[32];
1388 1389
  int i;
  POINT menupoint;
1390 1391

  TRACE("%lx\n", lParam);
1392

1393 1394
  if (infoPtr->hWndYearUpDown)
    {
1395
      infoPtr->currentYear=SendMessageW(infoPtr->hWndYearUpDown, UDM_SETPOS, 0, 0);
1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407
      if(!DestroyWindow(infoPtr->hWndYearUpDown))
	{
	  FIXME("Can't destroy Updown Control\n");
	}
      else
	infoPtr->hWndYearUpDown=0;
      if(!DestroyWindow(infoPtr->hWndYearEdit))
	{
	  FIXME("Can't destroy Updown Control\n");
	}
      else
	infoPtr->hWndYearEdit=0;
1408
      InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1409
    }
1410

1411 1412
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
1413
  hit = MONTHCAL_HitTest(infoPtr, (LPARAM)&ht);
1414 1415

  /* FIXME: these flags should be checked by */
1416
  /*((hit & MCHT_XXX) == MCHT_XXX) b/c some of the flags are */
1417
  /* multi-bit */
1418
  if(hit ==MCHT_TITLEBTNNEXT) {
1419
    MONTHCAL_GoToNextMonth(infoPtr);
1420
    infoPtr->status = MC_NEXTPRESSED;
1421 1422
    SetTimer(infoPtr->hwndSelf, MC_NEXTMONTHTIMER, MC_NEXTMONTHDELAY, 0);
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1423
    return 0;
1424
  }
1425
  if(hit == MCHT_TITLEBTNPREV){
1426
    MONTHCAL_GoToPrevMonth(infoPtr);
1427
    infoPtr->status = MC_PREVPRESSED;
1428 1429
    SetTimer(infoPtr->hwndSelf, MC_PREVMONTHTIMER, MC_NEXTMONTHDELAY, 0);
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1430
    return 0;
1431 1432
  }

1433
  if(hit == MCHT_TITLEMONTH) {
1434
    hMenu = CreatePopupMenu();
1435

1436 1437
    for (i=0; i<12;i++)
      {
1438 1439
	GetLocaleInfoW(LOCALE_USER_DEFAULT,LOCALE_SMONTHNAME1+i, buf,countof(buf));
	AppendMenuW(hMenu, MF_STRING|MF_ENABLED,i+1, buf);
1440 1441 1442
      }
    menupoint.x=infoPtr->titlemonth.right;
    menupoint.y=infoPtr->titlemonth.bottom;
1443
    ClientToScreen(infoPtr->hwndSelf, &menupoint);
1444
    i= TrackPopupMenu(hMenu,TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON | TPM_RETURNCMD,
1445
		      menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL);
1446 1447 1448
    if ((i>0) && (i<13))
      {
	infoPtr->currentMonth=i;
1449
	InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1450
      }
1451
  }
1452
  if(hit == MCHT_TITLEYEAR) {
1453 1454
    infoPtr->hWndYearEdit=CreateWindowExW(0,
			 EditW,
1455 1456 1457
			   0,
			 WS_VISIBLE | WS_CHILD |UDS_SETBUDDYINT,
			 infoPtr->titleyear.left+3,infoPtr->titlebtnnext.top,
Duane Clark's avatar
Duane Clark committed
1458
			 infoPtr->titleyear.right-infoPtr->titleyear.left+4,
1459
			 infoPtr->textHeight,
1460
			 infoPtr->hwndSelf,
1461 1462
			 NULL,
			 NULL,
1463
			 NULL);
Duane Clark's avatar
Duane Clark committed
1464
    SendMessageW( infoPtr->hWndYearEdit, WM_SETFONT, (WPARAM) infoPtr->hBoldFont, (LPARAM)TRUE);
1465 1466
    infoPtr->hWndYearUpDown=CreateWindowExW(0,
			 UPDOWN_CLASSW,
1467 1468
			   0,
			 WS_VISIBLE | WS_CHILD |UDS_SETBUDDYINT|UDS_NOTHOUSANDS|UDS_ARROWKEYS,
Duane Clark's avatar
Duane Clark committed
1469 1470
			 infoPtr->titleyear.right+7,infoPtr->titlebtnnext.top,
			 18,
1471
			 infoPtr->textHeight,
1472
			 infoPtr->hwndSelf,
1473 1474
			 NULL,
			 NULL,
1475
			 NULL);
1476 1477 1478
    SendMessageW(infoPtr->hWndYearUpDown, UDM_SETRANGE, 0, MAKELONG (9999, 1753));
    SendMessageW(infoPtr->hWndYearUpDown, UDM_SETBUDDY, (WPARAM) infoPtr->hWndYearEdit, 0);
    SendMessageW(infoPtr->hWndYearUpDown, UDM_SETPOS, 0, infoPtr->currentYear);
1479
    return 0;
1480

1481
  }
1482
  if(hit == MCHT_TODAYLINK) {
1483 1484
    NMSELCHANGE nmsc;

1485 1486
    infoPtr->curSelDay = infoPtr->todaysDate.wDay;
    infoPtr->firstSelDay = infoPtr->todaysDate.wDay;
1487 1488
    infoPtr->currentMonth=infoPtr->todaysDate.wMonth;
    infoPtr->currentYear=infoPtr->todaysDate.wYear;
1489 1490
    MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->minSel);
    MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->maxSel);
1491
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1492 1493 1494 1495 1496 1497

    nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
    nmsc.nmhdr.code     = MCN_SELCHANGE;
    MONTHCAL_CopyTime(&infoPtr->minSel, &nmsc.stSelStart);
    MONTHCAL_CopyTime(&infoPtr->maxSel, &nmsc.stSelEnd);
1498
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
1499 1500

    nmsc.nmhdr.code     = MCN_SELECT;
1501
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
1502
    return 0;
1503
  }
1504
  if(hit == MCHT_CALENDARDATE) {
1505 1506 1507
    SYSTEMTIME selArray[2];
    NMSELCHANGE nmsc;

1508 1509
    MONTHCAL_CopyTime(&ht.st, &selArray[0]);
    MONTHCAL_CopyTime(&ht.st, &selArray[1]);
1510 1511
    MONTHCAL_SetSelRange(infoPtr, (LPARAM)selArray);
    MONTHCAL_SetCurSel(infoPtr, (LPARAM)selArray);
1512
    TRACE("MCHT_CALENDARDATE\n");
1513 1514
    nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1515
    nmsc.nmhdr.code     = MCN_SELCHANGE;
1516 1517
    MONTHCAL_CopyTime(&infoPtr->minSel,&nmsc.stSelStart);
    MONTHCAL_CopyTime(&infoPtr->maxSel,&nmsc.stSelEnd);
1518

1519
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
1520

1521

1522
    /* redraw both old and new days if the selected day changed */
1523
    if(infoPtr->curSelDay != ht.st.wDay) {
1524
      MONTHCAL_CalcPosFromDay(infoPtr, ht.st.wDay, ht.st.wMonth, &rcDay);
1525
      InvalidateRect(infoPtr->hwndSelf, &rcDay, TRUE);
1526 1527

      MONTHCAL_CalcPosFromDay(infoPtr, infoPtr->curSelDay, infoPtr->currentMonth, &rcDay);
1528
      InvalidateRect(infoPtr->hwndSelf, &rcDay, TRUE);
1529
    }
1530

1531 1532 1533
    infoPtr->firstSelDay = ht.st.wDay;
    infoPtr->curSelDay = ht.st.wDay;
    infoPtr->status = MC_SEL_LBUTDOWN;
1534
    return 0;
1535 1536
  }

1537
  return 1;
1538 1539
}

1540

1541
static LRESULT
1542
MONTHCAL_LButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1543
{
1544 1545
  NMSELCHANGE nmsc;
  NMHDR nmhdr;
1546
  BOOL redraw = FALSE;
1547 1548
  MCHITTESTINFO ht;
  DWORD hit;
1549 1550

  TRACE("\n");
1551

1552
  if(infoPtr->status & MC_NEXTPRESSED) {
1553
    KillTimer(infoPtr->hwndSelf, MC_NEXTMONTHTIMER);
1554
    infoPtr->status &= ~MC_NEXTPRESSED;
1555 1556 1557
    redraw = TRUE;
  }
  if(infoPtr->status & MC_PREVPRESSED) {
1558
    KillTimer(infoPtr->hwndSelf, MC_PREVMONTHTIMER);
1559
    infoPtr->status &= ~MC_PREVPRESSED;
1560 1561
    redraw = TRUE;
  }
1562

1563 1564
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
1565
  hit = MONTHCAL_HitTest(infoPtr, (LPARAM)&ht);
1566

1567
  infoPtr->status = MC_SEL_LBUTUP;
1568

1569
  if(hit ==MCHT_CALENDARDATENEXT) {
1570 1571
    MONTHCAL_GoToNextMonth(infoPtr);
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1572 1573
    return TRUE;
  }
1574
  if(hit == MCHT_CALENDARDATEPREV){
1575 1576
    MONTHCAL_GoToPrevMonth(infoPtr);
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1577 1578
    return TRUE;
  }
1579 1580
  nmhdr.hwndFrom = infoPtr->hwndSelf;
  nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1581
  nmhdr.code     = NM_RELEASEDCAPTURE;
1582
  TRACE("Sent notification from %p to %p\n", infoPtr->hwndSelf, infoPtr->hwndNotify);
1583

1584
  SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmhdr.idFrom, (LPARAM)&nmhdr);
1585
  /* redraw if necessary */
1586
  if(redraw)
1587
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1588 1589
  /* only send MCN_SELECT if currently displayed month's day was selected */
  if(hit == MCHT_CALENDARDATE) {
1590 1591
    nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1592 1593 1594
    nmsc.nmhdr.code     = MCN_SELECT;
    MONTHCAL_CopyTime(&infoPtr->minSel, &nmsc.stSelStart);
    MONTHCAL_CopyTime(&infoPtr->maxSel, &nmsc.stSelEnd);
1595

1596
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
1597 1598

  }
1599
  return 0;
1600 1601
}

1602

1603
static LRESULT
1604
MONTHCAL_Timer(MONTHCAL_INFO *infoPtr, WPARAM wParam)
1605
{
1606
  BOOL redraw = FALSE;
1607

1608
  TRACE("%ld\n", wParam);
1609

1610
  switch(wParam) {
1611
  case MC_NEXTMONTHTIMER:
1612
    redraw = TRUE;
1613
    MONTHCAL_GoToNextMonth(infoPtr);
1614 1615
    break;
  case MC_PREVMONTHTIMER:
1616
    redraw = TRUE;
1617
    MONTHCAL_GoToPrevMonth(infoPtr);
1618 1619 1620
    break;
  default:
    ERR("got unknown timer\n");
1621
    break;
1622
  }
1623

1624
  /* redraw only if necessary */
1625
  if(redraw)
1626
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1627

1628 1629
  return 0;
}
1630

1631 1632

static LRESULT
1633
MONTHCAL_MouseMove(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1634
{
1635 1636 1637
  MCHITTESTINFO ht;
  int oldselday, selday, hit;
  RECT r;
1638

1639
  if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
1640

1641 1642
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
1643

1644
  hit = MONTHCAL_HitTest(infoPtr, (LPARAM)&ht);
1645

1646
  /* not on the calendar date numbers? bail out */
1647 1648
  TRACE("hit:%x\n",hit);
  if((hit & MCHT_CALENDARDATE) != MCHT_CALENDARDATE) return 0;
1649 1650 1651 1652

  selday = ht.st.wDay;
  oldselday = infoPtr->curSelDay;
  infoPtr->curSelDay = selday;
1653
  MONTHCAL_CalcPosFromDay(infoPtr, selday, ht.st. wMonth, &r);
1654

1655
  if(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & MCS_MULTISELECT)  {
1656 1657 1658
    SYSTEMTIME selArray[2];
    int i;

1659
    MONTHCAL_GetSelRange(infoPtr, (LPARAM)selArray);
1660
    i = 0;
1661 1662
    if(infoPtr->firstSelDay==selArray[0].wDay) i=1;
    TRACE("oldRange:%d %d %d %d\n", infoPtr->firstSelDay, selArray[0].wDay, selArray[1].wDay, i);
1663
    if(infoPtr->firstSelDay==selArray[1].wDay) {
1664 1665
      /* 1st time we get here: selArray[0]=selArray[1])  */
      /* if we're still at the first selected date, return */
1666 1667
      if(infoPtr->firstSelDay==selday) goto done;
      if(selday<infoPtr->firstSelDay) i = 0;
1668
    }
1669

1670 1671
    if(abs(infoPtr->firstSelDay - selday) >= infoPtr->maxSelCount) {
      if(selday>infoPtr->firstSelDay)
1672 1673 1674 1675
        selday = infoPtr->firstSelDay + infoPtr->maxSelCount;
      else
        selday = infoPtr->firstSelDay - infoPtr->maxSelCount;
    }
1676

1677 1678
    if(selArray[i].wDay!=selday) {
      TRACE("newRange:%d %d %d %d\n", infoPtr->firstSelDay, selArray[0].wDay, selArray[1].wDay, i);
1679

1680
      selArray[i].wDay = selday;
1681

1682
      if(selArray[0].wDay>selArray[1].wDay) {
1683 1684 1685 1686 1687
        DWORD tempday;
        tempday = selArray[1].wDay;
        selArray[1].wDay = selArray[0].wDay;
        selArray[0].wDay = tempday;
      }
1688

1689
      MONTHCAL_SetSelRange(infoPtr, (LPARAM)selArray);
1690 1691
    }
  }
1692 1693 1694

done:

1695
  /* only redraw if the currently selected day changed */
1696
  /* FIXME: this should specify a rectangle containing only the days that changed */
1697
  /* using InvalidateRect */
1698
  if(oldselday != infoPtr->curSelDay)
1699
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1700

1701
  return 0;
1702 1703
}

1704

1705
static LRESULT
1706
MONTHCAL_Paint(MONTHCAL_INFO *infoPtr, WPARAM wParam)
1707
{
1708 1709
  HDC hdc;
  PAINTSTRUCT ps;
1710

1711 1712
  if (wParam)
  {
1713
    GetClientRect(infoPtr->hwndSelf, &ps.rcPaint);
1714 1715 1716
    hdc = (HDC)wParam;
  }
  else
1717
    hdc = BeginPaint(infoPtr->hwndSelf, &ps);
1718

1719 1720
  MONTHCAL_Refresh(infoPtr, hdc, &ps);
  if (!wParam) EndPaint(infoPtr->hwndSelf, &ps);
1721
  return 0;
1722 1723
}

1724

1725
static LRESULT
1726
MONTHCAL_KillFocus(const MONTHCAL_INFO *infoPtr, HWND hFocusWnd)
1727
{
1728
  TRACE("\n");
1729

1730 1731 1732 1733
  if (infoPtr->hwndNotify != hFocusWnd)
    ShowWindow(infoPtr->hwndSelf, SW_HIDE);
  else
    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1734

1735
  return 0;
1736 1737 1738 1739
}


static LRESULT
1740
MONTHCAL_SetFocus(const MONTHCAL_INFO *infoPtr)
1741
{
1742
  TRACE("\n");
1743

1744
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1745

1746
  return 0;
1747 1748
}

1749
/* sets the size information */
1750
static void MONTHCAL_UpdateSize(MONTHCAL_INFO *infoPtr)
1751
{
1752 1753
  static const WCHAR SunW[] = { 'S','u','n',0 };
  static const WCHAR O0W[] = { '0','0',0 };
1754
  HDC hdc = GetDC(infoPtr->hwndSelf);
1755 1756 1757 1758 1759
  RECT *title=&infoPtr->title;
  RECT *prev=&infoPtr->titlebtnprev;
  RECT *next=&infoPtr->titlebtnnext;
  RECT *titlemonth=&infoPtr->titlemonth;
  RECT *titleyear=&infoPtr->titleyear;
1760 1761
  RECT *wdays=&infoPtr->wdays;
  RECT *weeknumrect=&infoPtr->weeknums;
1762
  RECT *days=&infoPtr->days;
1763
  RECT *todayrect=&infoPtr->todayrect;
1764
  SIZE size;
1765
  TEXTMETRICW tm;
1766
  DWORD dwStyle = GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE);
1767
  HFONT currentFont;
1768 1769
  int xdiv, left_offset;
  RECT rcClient;
1770

1771
  GetClientRect(infoPtr->hwndSelf, &rcClient);
1772

1773
  currentFont = SelectObject(hdc, infoPtr->hFont);
1774 1775

  /* get the height and width of each day's text */
1776
  GetTextMetricsW(hdc, &tm);
1777
  infoPtr->textHeight = tm.tmHeight + tm.tmExternalLeading + tm.tmInternalLeading;
1778
  GetTextExtentPoint32W(hdc, SunW, 3, &size);
1779 1780
  infoPtr->textWidth = size.cx + 2;

1781
  /* recalculate the height and width increments and offsets */
1782
  GetTextExtentPoint32W(hdc, O0W, 2, &size);
1783

1784
  xdiv = (dwStyle & MCS_WEEKNUMBERS) ? 8 : 7;
1785

Duane Clark's avatar
Duane Clark committed
1786
  infoPtr->width_increment = size.cx * 2 + 4;
1787 1788
  infoPtr->height_increment = infoPtr->textHeight;
  left_offset = (rcClient.right - rcClient.left) - (infoPtr->width_increment * xdiv);
1789

1790
  /* calculate title area */
1791 1792 1793 1794
  title->top    = rcClient.top;
  title->bottom = title->top + 3 * infoPtr->height_increment / 2;
  title->left   = left_offset;
  title->right  = rcClient.right;
1795 1796 1797

  /* set the dimensions of the next and previous buttons and center */
  /* the month text vertically */
1798 1799 1800
  prev->top    = next->top    = title->top + 4;
  prev->bottom = next->bottom = title->bottom - 4;
  prev->left   = title->left + 4;
1801
  prev->right  = prev->left + (title->bottom - title->top) ;
1802
  next->right  = title->right - 4;
1803
  next->left   = next->right - (title->bottom - title->top);
1804

1805 1806 1807
  /* titlemonth->left and right change based upon the current month */
  /* and are recalculated in refresh as the current month may change */
  /* without the control being resized */
1808 1809
  titlemonth->top    = titleyear->top    = title->top    + (infoPtr->height_increment)/2;
  titlemonth->bottom = titleyear->bottom = title->bottom - (infoPtr->height_increment)/2;
1810

1811 1812
  /* setup the dimensions of the rectangle we draw the names of the */
  /* days of the week in */
1813
  weeknumrect->left = left_offset;
1814
  if(dwStyle & MCS_WEEKNUMBERS)
1815 1816 1817 1818 1819 1820 1821
    weeknumrect->right=prev->right;
  else
    weeknumrect->right=weeknumrect->left;
  wdays->left   = days->left   = weeknumrect->right;
  wdays->right  = days->right  = wdays->left + 7 * infoPtr->width_increment;
  wdays->top    = title->bottom ;
  wdays->bottom = wdays->top + infoPtr->height_increment;
1822

1823
  days->top    = weeknumrect->top = wdays->bottom ;
1824
  days->bottom = weeknumrect->bottom = days->top + 6 * infoPtr->height_increment;
1825

1826 1827
  todayrect->left   = rcClient.left;
  todayrect->right  = rcClient.right;
1828 1829 1830
  todayrect->top    = days->bottom;
  todayrect->bottom = days->bottom + infoPtr->height_increment;

1831
  TRACE("dx=%d dy=%d client[%s] title[%s] wdays[%s] days[%s] today[%s]\n",
1832
	infoPtr->width_increment,infoPtr->height_increment,
1833 1834 1835 1836 1837
        wine_dbgstr_rect(&rcClient),
        wine_dbgstr_rect(title),
        wine_dbgstr_rect(wdays),
        wine_dbgstr_rect(days),
        wine_dbgstr_rect(todayrect));
1838

1839
  /* restore the originally selected font */
1840
  SelectObject(hdc, currentFont);
1841

1842
  ReleaseDC(infoPtr->hwndSelf, hdc);
1843 1844
}

1845
static LRESULT MONTHCAL_Size(MONTHCAL_INFO *infoPtr, int Width, int Height)
1846
{
1847
  TRACE("(width=%d, height=%d)\n", Width, Height);
1848

1849
  MONTHCAL_UpdateSize(infoPtr);
1850 1851

  /* invalidate client area and erase background */
1852
  InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1853 1854 1855

  return 0;
}
1856

1857
static LRESULT MONTHCAL_GetFont(const MONTHCAL_INFO *infoPtr)
1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875
{
    return (LRESULT)infoPtr->hFont;
}

static LRESULT MONTHCAL_SetFont(MONTHCAL_INFO *infoPtr, HFONT hFont, BOOL redraw)
{
    HFONT hOldFont;
    LOGFONTW lf;

    if (!hFont) return 0;

    hOldFont = infoPtr->hFont;
    infoPtr->hFont = hFont;

    GetObjectW(infoPtr->hFont, sizeof(lf), &lf);
    lf.lfWeight = FW_BOLD;
    infoPtr->hBoldFont = CreateFontIndirectW(&lf);

1876 1877
    MONTHCAL_UpdateSize(infoPtr);

1878 1879 1880 1881 1882 1883
    if (redraw)
        InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);

    return (LRESULT)hOldFont;
}

1884
/* update theme after a WM_THEMECHANGED message */
1885
static LRESULT theme_changed (const MONTHCAL_INFO* infoPtr)
1886 1887 1888
{
    HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
    CloseThemeData (theme);
1889
    OpenThemeData (infoPtr->hwndSelf, themeClass);
1890 1891 1892
    return 0;
}

1893
/* FIXME: check whether dateMin/dateMax need to be adjusted. */
1894
static LRESULT
1895
MONTHCAL_Create(HWND hwnd, LPARAM lParam)
1896
{
1897
  MONTHCAL_INFO *infoPtr;
1898

1899
  /* allocate memory for info structure */
1900
  infoPtr = Alloc(sizeof(MONTHCAL_INFO));
1901
  SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
1902

1903 1904
  if(infoPtr == NULL) {
    ERR( "could not allocate info memory!\n");
1905 1906
    return 0;
  }
1907

1908
  infoPtr->hwndSelf = hwnd;
1909 1910
  infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;

1911
  MONTHCAL_SetFont(infoPtr, GetStockObject(DEFAULT_GUI_FONT), FALSE);
1912 1913

  /* initialize info structure */
1914
  /* FIXME: calculate systemtime ->> localtime(substract timezoneinfo) */
1915

Duane Clark's avatar
Duane Clark committed
1916
  GetLocalTime(&infoPtr->todaysDate);
1917
  infoPtr->firstDayHighWord = FALSE;
1918
  MONTHCAL_SetFirstDayOfWeek(infoPtr, (LPARAM)-1);
1919 1920
  infoPtr->currentMonth = infoPtr->todaysDate.wMonth;
  infoPtr->currentYear = infoPtr->todaysDate.wYear;
1921 1922
  MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->minDate);
  MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->maxDate);
1923 1924
  infoPtr->maxDate.wYear=2050;
  infoPtr->minDate.wYear=1950;
1925
  infoPtr->maxSelCount  = 7;
1926
  infoPtr->monthRange = 3;
1927
  infoPtr->monthdayState = Alloc
1928 1929 1930 1931 1932 1933 1934 1935
                         (infoPtr->monthRange * sizeof(MONTHDAYSTATE));
  infoPtr->titlebk     = GetSysColor(COLOR_ACTIVECAPTION);
  infoPtr->titletxt    = GetSysColor(COLOR_WINDOW);
  infoPtr->monthbk     = GetSysColor(COLOR_WINDOW);
  infoPtr->trailingtxt = GetSysColor(COLOR_GRAYTEXT);
  infoPtr->bk          = GetSysColor(COLOR_WINDOW);
  infoPtr->txt	       = GetSysColor(COLOR_WINDOWTEXT);

1936 1937 1938 1939
  /* set the current day for highlighing */
  infoPtr->minSel.wDay = infoPtr->todaysDate.wDay;
  infoPtr->maxSel.wDay = infoPtr->todaysDate.wDay;

1940 1941
  /* call MONTHCAL_UpdateSize to set all of the dimensions */
  /* of the control */
1942
  MONTHCAL_UpdateSize(infoPtr);
1943 1944
  
  OpenThemeData (infoPtr->hwndSelf, themeClass);
1945 1946

  return 0;
1947 1948 1949 1950
}


static LRESULT
1951
MONTHCAL_Destroy(MONTHCAL_INFO *infoPtr)
1952
{
1953
  /* free month calendar info data */
1954
  Free(infoPtr->monthdayState);
1955
  SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
1956

1957 1958
  CloseThemeData (GetWindowTheme (infoPtr->hwndSelf));
  
1959
  Free(infoPtr);
1960
  return 0;
1961 1962 1963
}


1964
static LRESULT WINAPI
1965
MONTHCAL_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1966
{
1967 1968
  MONTHCAL_INFO *infoPtr;

1969
  TRACE("hwnd=%p msg=%x wparam=%lx lparam=%lx\n", hwnd, uMsg, wParam, lParam);
1970 1971 1972

  infoPtr = MONTHCAL_GetInfoPtr(hwnd);
  if (!infoPtr && (uMsg != WM_CREATE))
1973
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
1974
  switch(uMsg)
1975 1976
  {
  case MCM_GETCURSEL:
1977
    return MONTHCAL_GetCurSel(infoPtr, lParam);
1978

1979
  case MCM_SETCURSEL:
1980
    return MONTHCAL_SetCurSel(infoPtr, lParam);
1981

1982
  case MCM_GETMAXSELCOUNT:
1983
    return MONTHCAL_GetMaxSelCount(infoPtr);
1984

1985
  case MCM_SETMAXSELCOUNT:
1986
    return MONTHCAL_SetMaxSelCount(infoPtr, wParam);
1987

1988
  case MCM_GETSELRANGE:
1989
    return MONTHCAL_GetSelRange(infoPtr, lParam);
1990

1991
  case MCM_SETSELRANGE:
1992
    return MONTHCAL_SetSelRange(infoPtr, lParam);
1993

1994
  case MCM_GETMONTHRANGE:
1995
    return MONTHCAL_GetMonthRange(infoPtr);
1996

1997
  case MCM_SETDAYSTATE:
1998
    return MONTHCAL_SetDayState(infoPtr, wParam, lParam);
1999

2000
  case MCM_GETMINREQRECT:
2001
    return MONTHCAL_GetMinReqRect(infoPtr, lParam);
2002

2003
  case MCM_GETCOLOR:
2004
    return MONTHCAL_GetColor(infoPtr, wParam);
2005

2006
  case MCM_SETCOLOR:
2007
    return MONTHCAL_SetColor(infoPtr, wParam, lParam);
2008

2009
  case MCM_GETTODAY:
2010
    return MONTHCAL_GetToday(infoPtr, lParam);
2011

2012
  case MCM_SETTODAY:
2013
    return MONTHCAL_SetToday(infoPtr, lParam);
2014

2015
  case MCM_HITTEST:
2016
    return MONTHCAL_HitTest(infoPtr, lParam);
2017

2018
  case MCM_GETFIRSTDAYOFWEEK:
2019
    return MONTHCAL_GetFirstDayOfWeek(infoPtr);
2020

2021
  case MCM_SETFIRSTDAYOFWEEK:
2022
    return MONTHCAL_SetFirstDayOfWeek(infoPtr, lParam);
2023

2024
  case MCM_GETRANGE:
2025
    return MONTHCAL_GetRange(hwnd, lParam);
2026

2027
  case MCM_SETRANGE:
2028
    return MONTHCAL_SetRange(infoPtr, wParam, lParam);
2029

2030
  case MCM_GETMONTHDELTA:
2031
    return MONTHCAL_GetMonthDelta(infoPtr);
2032

2033
  case MCM_SETMONTHDELTA:
2034
    return MONTHCAL_SetMonthDelta(infoPtr, wParam);
2035

2036
  case MCM_GETMAXTODAYWIDTH:
2037
    return MONTHCAL_GetMaxTodayWidth(infoPtr);
2038

2039 2040
  case WM_GETDLGCODE:
    return DLGC_WANTARROWS | DLGC_WANTCHARS;
2041

2042
  case WM_KILLFOCUS:
2043
    return MONTHCAL_KillFocus(infoPtr, (HWND)wParam);
2044

2045
  case WM_RBUTTONDOWN:
2046
    return MONTHCAL_RButtonDown(infoPtr, lParam);
2047

2048
  case WM_LBUTTONDOWN:
2049
    return MONTHCAL_LButtonDown(infoPtr, lParam);
2050

2051
  case WM_MOUSEMOVE:
2052
    return MONTHCAL_MouseMove(infoPtr, lParam);
2053

2054
  case WM_LBUTTONUP:
2055
    return MONTHCAL_LButtonUp(infoPtr, lParam);
2056

2057
  case WM_PRINTCLIENT:
2058
  case WM_PAINT:
2059
    return MONTHCAL_Paint(infoPtr, wParam);
2060

2061
  case WM_SETFOCUS:
2062
    return MONTHCAL_SetFocus(infoPtr);
2063 2064

  case WM_SIZE:
2065
    return MONTHCAL_Size(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
2066

2067
  case WM_CREATE:
2068
    return MONTHCAL_Create(hwnd, lParam);
2069

2070 2071 2072 2073 2074 2075
  case WM_SETFONT:
    return MONTHCAL_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);

  case WM_GETFONT:
    return MONTHCAL_GetFont(infoPtr);

2076
  case WM_TIMER:
2077
    return MONTHCAL_Timer(infoPtr, wParam);
2078 2079 2080
    
  case WM_THEMECHANGED:
    return theme_changed (infoPtr);
2081

2082
  case WM_DESTROY:
2083
    return MONTHCAL_Destroy(infoPtr);
2084

2085
  default:
2086
    if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
2087
      ERR( "unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
2088
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
2089
  }
2090 2091 2092
}


2093
void
2094
MONTHCAL_Register(void)
2095
{
2096
  WNDCLASSW wndClass;
2097

2098
  ZeroMemory(&wndClass, sizeof(WNDCLASSW));
2099
  wndClass.style         = CS_GLOBALCLASS;
2100
  wndClass.lpfnWndProc   = MONTHCAL_WindowProc;
2101 2102
  wndClass.cbClsExtra    = 0;
  wndClass.cbWndExtra    = sizeof(MONTHCAL_INFO *);
2103
  wndClass.hCursor       = LoadCursorW(0, (LPWSTR)IDC_ARROW);
2104
  wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
2105
  wndClass.lpszClassName = MONTHCAL_CLASSW;
2106

2107
  RegisterClassW(&wndClass);
2108 2109 2110
}


2111
void
2112
MONTHCAL_Unregister(void)
2113
{
2114
    UnregisterClassW(MONTHCAL_CLASSW, NULL);
2115
}