monthcal.c 59.3 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 337 338
 * 1<day<31, so evertyhing's OK.
 */

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("%d %d %d %d\n", r.left, r.top, r.right, r.bottom);
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, textWidth = infoPtr->textWidth;
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 520
/* draw line under day abbreviatons */

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

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

689
/*eventually draw week numbers*/
690
  if(dwStyle & MCS_WEEKNUMBERS)  {
691
    /* display weeknumbers*/
692 693 694 695 696
    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
697
       LOCALE_IFIRSTWEEKOFYEAR == 2 (e.g. Germany):
698 699 700 701
       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
    */
702 703
    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, buf, countof(buf));
    weeknum = atoiW(buf);
704 705 706 707 708 709 710 711 712 713 714 715 716 717
    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);
718
	if ((infoPtr->firstDay +7 - weeknum1)%7 > mindays)
719 720 721 722
	    weeknum =1;
	else
	  {
	    weeknum = 0;
723
	    for(i=0; i<11; i++)
724 725 726 727 728 729 730 731 732 733 734
	      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;
735
	for(i=0; i<prevMonth-1; i++)
736 737 738 739 740 741 742 743 744 745 746
	  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;
747
    for(i=0; i<6; i++) {
748 749
      if((i==0)&&(weeknum>50))
	{
750
	  wsprintfW(buf, fmt3W, weeknum);
751 752 753 754
	  weeknum=0;
	}
      else if((i==5)&&(weeknum>47))
	{
755
	  wsprintfW(buf, fmt3W, 1);
756 757
	}
      else
758 759
	wsprintfW(buf, fmt3W, weeknum + i);
      DrawTextW(hdc, buf, -1, days, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
760 761
      days->top+=infoPtr->height_increment;
      days->bottom+=infoPtr->height_increment;
762
    }
763

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

767 768
  }
  /* currentFont was font at entering Refresh */
769

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


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

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

783 784
  /* validate parameters */

785
  if((infoPtr==NULL) ||(lpRect == NULL) ) return FALSE;
786

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

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

795
  return TRUE;
796 797
}

798

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

804
  switch((int)wParam) {
805 806 807 808 809 810 811 812 813 814 815 816 817 818 819
    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;
820 821
}

822

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

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

830
  switch((int)wParam) {
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
    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;
  }

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

861

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

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

873

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

879
  TRACE("delta %ld\n", wParam);
880

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


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


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

904
  TRACE("day %ld\n", lParam);
905

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

  localFirstDay = atoiW(buf);

  if(lParam == -1)
912 913 914 915
  {
    infoPtr->firstDay = localFirstDay;
    infoPtr->firstDayHighWord = FALSE;
  }
916
  else if(lParam >= 7)
917 918 919 920
  {
    infoPtr->firstDay = localFirstDay;
    infoPtr->firstDayHighWord = TRUE;
  }
921
  else
922 923 924 925
  {
    infoPtr->firstDay = lParam;
    infoPtr->firstDayHighWord = TRUE;
  }
926

927
  return prev;
928 929 930 931
}


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

936 937 938
  return infoPtr->monthRange;
}

939

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

946

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

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

    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;
963
    }
964 965 966 967
    if (wParam & GDTR_MAX)
    {
        MONTHCAL_CopyTime(&lprgSysTimeArray[1], &infoPtr->maxDate);
        infoPtr->rangeValid |= GDTR_MAX;
968 969
    }

970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993
    /* 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 ;
        }
    }

994
    return TRUE;
995 996
}

997

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

  /* validate parameters */

1006
  if((infoPtr==NULL) || (lprgSysTimeArray==NULL)) return FALSE;
1007

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

  return infoPtr->rangeValid;
}

1014

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

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

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

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

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

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

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

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

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

1055 1056
  if(!MONTHCAL_ValidateTime(*lpSel)) return FALSE;

1057 1058
  infoPtr->currentMonth=lpSel->wMonth;
  infoPtr->currentYear=lpSel->wYear;
1059

1060 1061
  MONTHCAL_CopyTime(lpSel, &infoPtr->minSel);
  MONTHCAL_CopyTime(lpSel, &infoPtr->maxSel);
1062

1063
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1064

1065 1066 1067
  return TRUE;
}

1068

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

1075

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

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

  return TRUE;
}


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

1094
  TRACE("%lx\n", lParam);
1095 1096 1097

  /* validate parameters */

1098
  if((infoPtr==NULL) ||(lprgSysTimeArray==NULL)) return FALSE;
1099

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

1108 1109 1110
  return FALSE;
}

1111

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

1117
  TRACE("%lx\n", lParam);
1118 1119 1120

  /* validate parameters */

1121
  if((infoPtr==NULL) ||(lprgSysTimeArray==NULL)) return FALSE;
1122

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

1131 1132 1133 1134
  return FALSE;
}


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

1140
  TRACE("%lx\n", lParam);
1141 1142 1143

  /* validate parameters */

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


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

1155
  TRACE("%lx\n", lParam);
1156

1157
  /* validate parameters */
1158

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


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


1175 1176 1177
  x = lpht->pt.x;
  y = lpht->pt.y;
  retval = MCHT_NOWHERE;
1178

1179
  ZeroMemory(&lpht->st, sizeof(lpht->st));
1180 1181 1182

  /* 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,
1183 1184 1185 1186 1187 1188 1189 1190 1191
	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);
  */
1192

1193
  /* are we in the header? */
1194

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

1213 1214 1215
    retval = MCHT_TITLE;
    goto done;
  }
1216

1217 1218 1219 1220 1221
  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;
1222
    lpht->st.wDay   = (day < 1)?
1223
      MONTHCAL_MonthLength(infoPtr->currentMonth-1,infoPtr->currentYear) -day : day;
1224 1225
    goto done;
  }
1226 1227
  if(PtInRect(&infoPtr->weeknums, lpht->pt)) {
    retval = MCHT_CALENDARWEEKNUM;
1228
    lpht->st.wYear  = infoPtr->currentYear;
1229 1230
    lpht->st.wMonth = (day < 1) ? infoPtr->currentMonth -1 :
      (day > MONTHCAL_MonthLength(infoPtr->currentMonth,infoPtr->currentYear)) ?
1231
      infoPtr->currentMonth +1 :infoPtr->currentMonth;
1232 1233 1234
    lpht->st.wDay   = (day < 1 ) ?
      MONTHCAL_MonthLength(infoPtr->currentMonth-1,infoPtr->currentYear) -day :
      (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 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266
	{
	  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
1267
	lpht->st.wDayOfWeek   = MONTHCAL_CalculateDayOfWeek(day,lpht->st.wMonth,lpht->st.wYear);
1268 1269 1270 1271
      }
      goto done;
    }
  if(PtInRect(&infoPtr->todayrect, lpht->pt)) {
1272
    retval = MCHT_TODAYLINK;
1273 1274
    goto done;
  }
1275 1276


1277
  /* Hit nothing special? What's left must be background :-) */
1278 1279 1280

  retval = MCHT_CALENDARBK;
 done:
1281
  lpht->uHit = retval;
1282 1283 1284 1285
  return retval;
}


1286
static void MONTHCAL_GoToNextMonth(MONTHCAL_INFO *infoPtr)
1287
{
1288
  DWORD dwStyle = GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE);
1289

1290
  TRACE("MONTHCAL_GoToNextMonth\n");
1291

1292
  infoPtr->currentMonth++;
1293
  if(infoPtr->currentMonth > 12) {
1294 1295 1296
    infoPtr->currentYear++;
    infoPtr->currentMonth = 1;
  }
1297

1298
  if(dwStyle & MCS_DAYSTATE) {
1299 1300
    NMDAYSTATE nmds;
    int i;
1301

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

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


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

1320
  TRACE("\n");
1321 1322

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

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

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

1339
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
1340 1341 1342
        (WPARAM)nmds.nmhdr.idFrom, (LPARAM)&nmds);
    for(i=0; i<infoPtr->monthRange; i++)
       infoPtr->monthdayState[i] = nmds.prgDayState[i];
1343
  }
1344 1345
}

1346
static LRESULT
1347
MONTHCAL_RButtonDown(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1348
{
1349
  static const WCHAR todayW[] = { 'G','o',' ','t','o',' ','T','o','d','a','y',':',0 };
1350 1351
  HMENU hMenu;
  POINT menupoint;
1352
  WCHAR buf[32];
1353

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

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

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

1388 1389
  if (infoPtr->hWndYearUpDown)
    {
1390
      infoPtr->currentYear=SendMessageW( infoPtr->hWndYearUpDown, UDM_SETPOS,   (WPARAM) 0,(LPARAM)0);
1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402
      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;
1403
      InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1404
    }
1405

1406 1407
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
1408
  hit = MONTHCAL_HitTest(infoPtr, (LPARAM)&ht);
1409 1410

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

1428
  if(hit == MCHT_TITLEMONTH) {
1429
    hMenu = CreatePopupMenu();
1430

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

1476
  }
1477
  if(hit == MCHT_TODAYLINK) {
1478 1479
    infoPtr->currentMonth=infoPtr->todaysDate.wMonth;
    infoPtr->currentYear=infoPtr->todaysDate.wYear;
1480
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1481
    return TRUE;
1482
  }
1483
  if(hit == MCHT_CALENDARDATE) {
1484 1485 1486
    SYSTEMTIME selArray[2];
    NMSELCHANGE nmsc;

1487 1488
    MONTHCAL_CopyTime(&ht.st, &selArray[0]);
    MONTHCAL_CopyTime(&ht.st, &selArray[1]);
1489 1490
    MONTHCAL_SetSelRange(infoPtr, (LPARAM)&selArray);
    MONTHCAL_SetCurSel(infoPtr, (LPARAM)&selArray);
1491
    TRACE("MCHT_CALENDARDATE\n");
1492 1493
    nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1494
    nmsc.nmhdr.code     = MCN_SELCHANGE;
1495 1496
    MONTHCAL_CopyTime(&infoPtr->minSel,&nmsc.stSelStart);
    MONTHCAL_CopyTime(&infoPtr->maxSel,&nmsc.stSelEnd);
1497

1498
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
1499
           (WPARAM)nmsc.nmhdr.idFrom,(LPARAM)&nmsc);
1500

1501

1502
    /* redraw both old and new days if the selected day changed */
1503
    if(infoPtr->curSelDay != ht.st.wDay) {
1504
      MONTHCAL_CalcPosFromDay(infoPtr, ht.st.wDay, ht.st.wMonth, &rcDay);
1505
      InvalidateRect(infoPtr->hwndSelf, &rcDay, TRUE);
1506 1507

      MONTHCAL_CalcPosFromDay(infoPtr, infoPtr->curSelDay, infoPtr->currentMonth, &rcDay);
1508
      InvalidateRect(infoPtr->hwndSelf, &rcDay, TRUE);
1509
    }
1510

1511 1512 1513
    infoPtr->firstSelDay = ht.st.wDay;
    infoPtr->curSelDay = ht.st.wDay;
    infoPtr->status = MC_SEL_LBUTDOWN;
1514
    return TRUE;
1515 1516
  }

1517
  return 0;
1518 1519
}

1520

1521
static LRESULT
1522
MONTHCAL_LButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1523
{
1524 1525
  NMSELCHANGE nmsc;
  NMHDR nmhdr;
1526
  BOOL redraw = FALSE;
1527 1528
  MCHITTESTINFO ht;
  DWORD hit;
1529 1530

  TRACE("\n");
1531

1532
  if(infoPtr->status & MC_NEXTPRESSED) {
1533
    KillTimer(infoPtr->hwndSelf, MC_NEXTMONTHTIMER);
1534
    infoPtr->status &= ~MC_NEXTPRESSED;
1535 1536 1537
    redraw = TRUE;
  }
  if(infoPtr->status & MC_PREVPRESSED) {
1538
    KillTimer(infoPtr->hwndSelf, MC_PREVMONTHTIMER);
1539
    infoPtr->status &= ~MC_PREVPRESSED;
1540 1541
    redraw = TRUE;
  }
1542

1543 1544
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
1545
  hit = MONTHCAL_HitTest(infoPtr, (LPARAM)&ht);
1546

1547
  infoPtr->status = MC_SEL_LBUTUP;
1548

1549
  if(hit ==MCHT_CALENDARDATENEXT) {
1550 1551
    MONTHCAL_GoToNextMonth(infoPtr);
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1552 1553
    return TRUE;
  }
1554
  if(hit == MCHT_CALENDARDATEPREV){
1555 1556
    MONTHCAL_GoToPrevMonth(infoPtr);
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1557 1558
    return TRUE;
  }
1559 1560
  nmhdr.hwndFrom = infoPtr->hwndSelf;
  nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1561
  nmhdr.code     = NM_RELEASEDCAPTURE;
1562
  TRACE("Sent notification from %p to %p\n", infoPtr->hwndSelf, infoPtr->hwndNotify);
1563

1564
  SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);
1565
  /* redraw if necessary */
1566
  if(redraw)
1567
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1568 1569
  /* only send MCN_SELECT if currently displayed month's day was selected */
  if(hit == MCHT_CALENDARDATE) {
1570 1571
    nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1572 1573 1574
    nmsc.nmhdr.code     = MCN_SELECT;
    MONTHCAL_CopyTime(&infoPtr->minSel, &nmsc.stSelStart);
    MONTHCAL_CopyTime(&infoPtr->maxSel, &nmsc.stSelEnd);
1575

1576
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, (WPARAM)nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
1577 1578

  }
1579
  return 0;
1580 1581
}

1582

1583
static LRESULT
1584
MONTHCAL_Timer(MONTHCAL_INFO *infoPtr, WPARAM wParam)
1585
{
1586
  BOOL redraw = FALSE;
1587

1588
  TRACE("%ld\n", wParam);
1589

1590
  switch(wParam) {
1591
  case MC_NEXTMONTHTIMER:
1592
    redraw = TRUE;
1593
    MONTHCAL_GoToNextMonth(infoPtr);
1594 1595
    break;
  case MC_PREVMONTHTIMER:
1596
    redraw = TRUE;
1597
    MONTHCAL_GoToPrevMonth(infoPtr);
1598 1599 1600
    break;
  default:
    ERR("got unknown timer\n");
1601
    break;
1602
  }
1603

1604
  /* redraw only if necessary */
1605
  if(redraw)
1606
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1607

1608 1609
  return 0;
}
1610

1611 1612

static LRESULT
1613
MONTHCAL_MouseMove(MONTHCAL_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
1614
{
1615 1616 1617
  MCHITTESTINFO ht;
  int oldselday, selday, hit;
  RECT r;
1618

1619
  if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
1620

1621 1622
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
1623

1624
  hit = MONTHCAL_HitTest(infoPtr, (LPARAM)&ht);
1625

1626
  /* not on the calendar date numbers? bail out */
1627 1628
  TRACE("hit:%x\n",hit);
  if((hit & MCHT_CALENDARDATE) != MCHT_CALENDARDATE) return 0;
1629 1630 1631 1632

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

1635
  if(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & MCS_MULTISELECT)  {
1636 1637 1638
    SYSTEMTIME selArray[2];
    int i;

1639
    MONTHCAL_GetSelRange(infoPtr, (LPARAM)&selArray);
1640
    i = 0;
1641 1642
    if(infoPtr->firstSelDay==selArray[0].wDay) i=1;
    TRACE("oldRange:%d %d %d %d\n", infoPtr->firstSelDay, selArray[0].wDay, selArray[1].wDay, i);
1643
    if(infoPtr->firstSelDay==selArray[1].wDay) {
1644 1645
      /* 1st time we get here: selArray[0]=selArray[1])  */
      /* if we're still at the first selected date, return */
1646 1647
      if(infoPtr->firstSelDay==selday) goto done;
      if(selday<infoPtr->firstSelDay) i = 0;
1648
    }
1649

1650 1651
    if(abs(infoPtr->firstSelDay - selday) >= infoPtr->maxSelCount) {
      if(selday>infoPtr->firstSelDay)
1652 1653 1654 1655
        selday = infoPtr->firstSelDay + infoPtr->maxSelCount;
      else
        selday = infoPtr->firstSelDay - infoPtr->maxSelCount;
    }
1656

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

1660
      selArray[i].wDay = selday;
1661

1662
      if(selArray[0].wDay>selArray[1].wDay) {
1663 1664 1665 1666 1667
        DWORD tempday;
        tempday = selArray[1].wDay;
        selArray[1].wDay = selArray[0].wDay;
        selArray[0].wDay = tempday;
      }
1668

1669
      MONTHCAL_SetSelRange(infoPtr, (LPARAM)&selArray);
1670 1671
    }
  }
1672 1673 1674

done:

1675
  /* only redraw if the currently selected day changed */
1676
  /* FIXME: this should specify a rectangle containing only the days that changed */
1677
  /* using InvalidateRect */
1678
  if(oldselday != infoPtr->curSelDay)
1679
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1680

1681
  return 0;
1682 1683
}

1684

1685
static LRESULT
1686
MONTHCAL_Paint(MONTHCAL_INFO *infoPtr, WPARAM wParam)
1687
{
1688 1689
  HDC hdc;
  PAINTSTRUCT ps;
1690

1691 1692
  if (wParam)
  {
1693
    GetClientRect(infoPtr->hwndSelf, &ps.rcPaint);
1694 1695 1696
    hdc = (HDC)wParam;
  }
  else
1697
    hdc = BeginPaint(infoPtr->hwndSelf, &ps);
1698

1699 1700
  MONTHCAL_Refresh(infoPtr, hdc, &ps);
  if (!wParam) EndPaint(infoPtr->hwndSelf, &ps);
1701
  return 0;
1702 1703
}

1704

1705
static LRESULT
1706
MONTHCAL_KillFocus(const MONTHCAL_INFO *infoPtr)
1707
{
1708
  TRACE("\n");
1709

1710
  InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1711

1712
  return 0;
1713 1714 1715 1716
}


static LRESULT
1717
MONTHCAL_SetFocus(const MONTHCAL_INFO *infoPtr)
1718
{
1719
  TRACE("\n");
1720

1721
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1722

1723
  return 0;
1724 1725
}

1726
/* sets the size information */
1727
static void MONTHCAL_UpdateSize(MONTHCAL_INFO *infoPtr)
1728
{
1729 1730
  static const WCHAR SunW[] = { 'S','u','n',0 };
  static const WCHAR O0W[] = { '0','0',0 };
1731
  HDC hdc = GetDC(infoPtr->hwndSelf);
1732 1733 1734 1735 1736
  RECT *title=&infoPtr->title;
  RECT *prev=&infoPtr->titlebtnprev;
  RECT *next=&infoPtr->titlebtnnext;
  RECT *titlemonth=&infoPtr->titlemonth;
  RECT *titleyear=&infoPtr->titleyear;
1737 1738
  RECT *wdays=&infoPtr->wdays;
  RECT *weeknumrect=&infoPtr->weeknums;
1739
  RECT *days=&infoPtr->days;
1740
  RECT *todayrect=&infoPtr->todayrect;
1741
  SIZE size;
1742
  TEXTMETRICW tm;
1743
  DWORD dwStyle = GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE);
1744
  HFONT currentFont;
1745 1746
  int xdiv, left_offset;
  RECT rcClient;
1747

1748
  GetClientRect(infoPtr->hwndSelf, &rcClient);
1749

1750
  currentFont = SelectObject(hdc, infoPtr->hFont);
1751 1752

  /* get the height and width of each day's text */
1753
  GetTextMetricsW(hdc, &tm);
1754
  infoPtr->textHeight = tm.tmHeight + tm.tmExternalLeading + tm.tmInternalLeading;
1755
  GetTextExtentPoint32W(hdc, SunW, 3, &size);
1756 1757
  infoPtr->textWidth = size.cx + 2;

1758
  /* recalculate the height and width increments and offsets */
1759
  GetTextExtentPoint32W(hdc, O0W, 2, &size);
1760

1761
  xdiv = (dwStyle & MCS_WEEKNUMBERS) ? 8 : 7;
1762

Duane Clark's avatar
Duane Clark committed
1763
  infoPtr->width_increment = size.cx * 2 + 4;
1764 1765
  infoPtr->height_increment = infoPtr->textHeight;
  left_offset = (rcClient.right - rcClient.left) - (infoPtr->width_increment * xdiv);
1766

1767
  /* calculate title area */
1768 1769 1770 1771
  title->top    = rcClient.top;
  title->bottom = title->top + 3 * infoPtr->height_increment / 2;
  title->left   = left_offset;
  title->right  = rcClient.right;
1772 1773 1774

  /* set the dimensions of the next and previous buttons and center */
  /* the month text vertically */
1775 1776 1777
  prev->top    = next->top    = title->top + 4;
  prev->bottom = next->bottom = title->bottom - 4;
  prev->left   = title->left + 4;
1778
  prev->right  = prev->left + (title->bottom - title->top) ;
1779
  next->right  = title->right - 4;
1780
  next->left   = next->right - (title->bottom - title->top);
1781

1782 1783 1784
  /* 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 */
1785 1786
  titlemonth->top    = titleyear->top    = title->top    + (infoPtr->height_increment)/2;
  titlemonth->bottom = titleyear->bottom = title->bottom - (infoPtr->height_increment)/2;
1787

1788 1789
  /* setup the dimensions of the rectangle we draw the names of the */
  /* days of the week in */
1790
  weeknumrect->left = left_offset;
1791
  if(dwStyle & MCS_WEEKNUMBERS)
1792 1793 1794 1795 1796 1797 1798
    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;
1799

1800
  days->top    = weeknumrect->top = wdays->bottom ;
1801
  days->bottom = weeknumrect->bottom = days->top + 6 * infoPtr->height_increment;
1802

1803 1804
  todayrect->left   = rcClient.left;
  todayrect->right  = rcClient.right;
1805 1806 1807
  todayrect->top    = days->bottom;
  todayrect->bottom = days->bottom + infoPtr->height_increment;

1808
  TRACE("dx=%d dy=%d client[%s] title[%s] wdays[%s] days[%s] today[%s]\n",
1809
	infoPtr->width_increment,infoPtr->height_increment,
1810 1811 1812 1813 1814
        wine_dbgstr_rect(&rcClient),
        wine_dbgstr_rect(title),
        wine_dbgstr_rect(wdays),
        wine_dbgstr_rect(days),
        wine_dbgstr_rect(todayrect));
1815

1816
  /* restore the originally selected font */
1817
  SelectObject(hdc, currentFont);
1818

1819
  ReleaseDC(infoPtr->hwndSelf, hdc);
1820 1821
}

1822
static LRESULT MONTHCAL_Size(MONTHCAL_INFO *infoPtr, int Width, int Height)
1823
{
1824
  TRACE("(width=%d, height=%d)\n", Width, Height);
1825

1826
  MONTHCAL_UpdateSize(infoPtr);
1827 1828

  /* invalidate client area and erase background */
1829
  InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1830 1831 1832

  return 0;
}
1833

1834
static LRESULT MONTHCAL_GetFont(const MONTHCAL_INFO *infoPtr)
1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858
{
    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);

    if (redraw)
        InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);

    return (LRESULT)hOldFont;
}

1859
/* update theme after a WM_THEMECHANGED message */
1860
static LRESULT theme_changed (const MONTHCAL_INFO* infoPtr)
1861 1862 1863 1864 1865 1866 1867
{
    HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
    CloseThemeData (theme);
    theme = OpenThemeData (infoPtr->hwndSelf, themeClass);
    return 0;
}

1868
/* FIXME: check whether dateMin/dateMax need to be adjusted. */
1869
static LRESULT
1870
MONTHCAL_Create(HWND hwnd, WPARAM wParam, LPARAM lParam)
1871
{
1872
  MONTHCAL_INFO *infoPtr;
1873

1874
  /* allocate memory for info structure */
1875
  infoPtr =(MONTHCAL_INFO*)Alloc(sizeof(MONTHCAL_INFO));
1876
  SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
1877

1878 1879
  if(infoPtr == NULL) {
    ERR( "could not allocate info memory!\n");
1880 1881
    return 0;
  }
1882

1883
  infoPtr->hwndSelf = hwnd;
1884 1885
  infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;

1886
  MONTHCAL_SetFont(infoPtr, GetStockObject(DEFAULT_GUI_FONT), FALSE);
1887 1888

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

Duane Clark's avatar
Duane Clark committed
1891
  GetLocalTime(&infoPtr->todaysDate);
1892
  infoPtr->firstDayHighWord = FALSE;
1893
  MONTHCAL_SetFirstDayOfWeek(infoPtr, (LPARAM)-1);
1894 1895
  infoPtr->currentMonth = infoPtr->todaysDate.wMonth;
  infoPtr->currentYear = infoPtr->todaysDate.wYear;
1896 1897
  MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->minDate);
  MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->maxDate);
1898 1899
  infoPtr->maxDate.wYear=2050;
  infoPtr->minDate.wYear=1950;
1900
  infoPtr->maxSelCount  = 7;
1901
  infoPtr->monthRange = 3;
1902
  infoPtr->monthdayState = Alloc
1903 1904 1905 1906 1907 1908 1909 1910
                         (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);

1911 1912 1913 1914
  /* set the current day for highlighing */
  infoPtr->minSel.wDay = infoPtr->todaysDate.wDay;
  infoPtr->maxSel.wDay = infoPtr->todaysDate.wDay;

1915 1916
  /* call MONTHCAL_UpdateSize to set all of the dimensions */
  /* of the control */
1917
  MONTHCAL_UpdateSize(infoPtr);
1918 1919
  
  OpenThemeData (infoPtr->hwndSelf, themeClass);
1920 1921

  return 0;
1922 1923 1924 1925
}


static LRESULT
1926
MONTHCAL_Destroy(MONTHCAL_INFO *infoPtr)
1927
{
1928
  /* free month calendar info data */
1929
  Free(infoPtr->monthdayState);
1930
  SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
1931

1932 1933
  CloseThemeData (GetWindowTheme (infoPtr->hwndSelf));
  
1934
  Free(infoPtr);
1935
  return 0;
1936 1937 1938
}


1939
static LRESULT WINAPI
1940
MONTHCAL_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1941
{
1942 1943
  MONTHCAL_INFO *infoPtr;

1944
  TRACE("hwnd=%p msg=%x wparam=%lx lparam=%lx\n", hwnd, uMsg, wParam, lParam);
1945 1946 1947

  infoPtr = MONTHCAL_GetInfoPtr(hwnd);
  if (!infoPtr && (uMsg != WM_CREATE))
1948
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
1949
  switch(uMsg)
1950 1951
  {
  case MCM_GETCURSEL:
1952
    return MONTHCAL_GetCurSel(infoPtr, lParam);
1953

1954
  case MCM_SETCURSEL:
1955
    return MONTHCAL_SetCurSel(infoPtr, lParam);
1956

1957
  case MCM_GETMAXSELCOUNT:
1958
    return MONTHCAL_GetMaxSelCount(infoPtr);
1959

1960
  case MCM_SETMAXSELCOUNT:
1961
    return MONTHCAL_SetMaxSelCount(infoPtr, wParam);
1962

1963
  case MCM_GETSELRANGE:
1964
    return MONTHCAL_GetSelRange(infoPtr, lParam);
1965

1966
  case MCM_SETSELRANGE:
1967
    return MONTHCAL_SetSelRange(infoPtr, lParam);
1968

1969
  case MCM_GETMONTHRANGE:
1970
    return MONTHCAL_GetMonthRange(infoPtr);
1971

1972
  case MCM_SETDAYSTATE:
1973
    return MONTHCAL_SetDayState(infoPtr, wParam, lParam);
1974

1975
  case MCM_GETMINREQRECT:
1976
    return MONTHCAL_GetMinReqRect(infoPtr, lParam);
1977

1978
  case MCM_GETCOLOR:
1979
    return MONTHCAL_GetColor(infoPtr, wParam);
1980

1981
  case MCM_SETCOLOR:
1982
    return MONTHCAL_SetColor(infoPtr, wParam, lParam);
1983

1984
  case MCM_GETTODAY:
1985
    return MONTHCAL_GetToday(infoPtr, lParam);
1986

1987
  case MCM_SETTODAY:
1988
    return MONTHCAL_SetToday(infoPtr, lParam);
1989

1990
  case MCM_HITTEST:
1991
    return MONTHCAL_HitTest(infoPtr, lParam);
1992

1993
  case MCM_GETFIRSTDAYOFWEEK:
1994
    return MONTHCAL_GetFirstDayOfWeek(infoPtr);
1995

1996
  case MCM_SETFIRSTDAYOFWEEK:
1997
    return MONTHCAL_SetFirstDayOfWeek(infoPtr, lParam);
1998

1999
  case MCM_GETRANGE:
2000
    return MONTHCAL_GetRange(hwnd, wParam, lParam);
2001

2002
  case MCM_SETRANGE:
2003
    return MONTHCAL_SetRange(infoPtr, wParam, lParam);
2004

2005
  case MCM_GETMONTHDELTA:
2006
    return MONTHCAL_GetMonthDelta(infoPtr);
2007

2008
  case MCM_SETMONTHDELTA:
2009
    return MONTHCAL_SetMonthDelta(infoPtr, wParam);
2010

2011
  case MCM_GETMAXTODAYWIDTH:
2012
    return MONTHCAL_GetMaxTodayWidth(infoPtr);
2013

2014 2015
  case WM_GETDLGCODE:
    return DLGC_WANTARROWS | DLGC_WANTCHARS;
2016

2017
  case WM_KILLFOCUS:
2018
    return MONTHCAL_KillFocus(infoPtr);
2019

2020
  case WM_RBUTTONDOWN:
2021
    return MONTHCAL_RButtonDown(infoPtr, lParam);
2022

2023
  case WM_LBUTTONDOWN:
2024
    return MONTHCAL_LButtonDown(infoPtr, lParam);
2025

2026
  case WM_MOUSEMOVE:
2027
    return MONTHCAL_MouseMove(infoPtr, wParam, lParam);
2028

2029
  case WM_LBUTTONUP:
2030
    return MONTHCAL_LButtonUp(infoPtr, lParam);
2031

2032
  case WM_PRINTCLIENT:
2033
  case WM_PAINT:
2034
    return MONTHCAL_Paint(infoPtr, wParam);
2035

2036
  case WM_SETFOCUS:
2037
    return MONTHCAL_SetFocus(infoPtr);
2038 2039

  case WM_SIZE:
2040
    return MONTHCAL_Size(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
2041

2042
  case WM_CREATE:
2043
    return MONTHCAL_Create(hwnd, wParam, lParam);
2044

2045 2046 2047 2048 2049 2050
  case WM_SETFONT:
    return MONTHCAL_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);

  case WM_GETFONT:
    return MONTHCAL_GetFont(infoPtr);

2051
  case WM_TIMER:
2052
    return MONTHCAL_Timer(infoPtr, wParam);
2053 2054 2055
    
  case WM_THEMECHANGED:
    return theme_changed (infoPtr);
2056

2057
  case WM_DESTROY:
2058
    return MONTHCAL_Destroy(infoPtr);
2059

2060
  default:
2061
    if ((uMsg >= WM_USER) && (uMsg < WM_APP))
2062
      ERR( "unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
2063
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
2064
  }
2065 2066 2067
}


2068
void
2069
MONTHCAL_Register(void)
2070
{
2071
  WNDCLASSW wndClass;
2072

2073
  ZeroMemory(&wndClass, sizeof(WNDCLASSW));
2074
  wndClass.style         = CS_GLOBALCLASS;
2075
  wndClass.lpfnWndProc   = MONTHCAL_WindowProc;
2076 2077
  wndClass.cbClsExtra    = 0;
  wndClass.cbWndExtra    = sizeof(MONTHCAL_INFO *);
2078
  wndClass.hCursor       = LoadCursorW(0, (LPWSTR)IDC_ARROW);
2079
  wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
2080
  wndClass.lpszClassName = MONTHCAL_CLASSW;
2081

2082
  RegisterClassW(&wndClass);
2083 2084 2085
}


2086
void
2087
MONTHCAL_Unregister(void)
2088
{
2089
    UnregisterClassW(MONTHCAL_CLASSW, NULL);
2090
}