monthcal.c 59.9 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 917 918
  {
    infoPtr->firstDay = localFirstDay;
    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, WPARAM wParam, 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 1175
  x = lpht->pt.x;
  y = lpht->pt.y;
  retval = MCHT_NOWHERE;
1176

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

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

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

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

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

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


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

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


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

1288
  TRACE("MONTHCAL_GoToNextMonth\n");
1289

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

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

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

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


1313
static void MONTHCAL_GoToPrevMonth(MONTHCAL_INFO *infoPtr)
1314
{
1315
  DWORD dwStyle = GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE);
1316

1317
  TRACE("\n");
1318 1319

  infoPtr->currentMonth--;
1320
  if(infoPtr->currentMonth < 1) {
1321 1322 1323 1324
    infoPtr->currentYear--;
    infoPtr->currentMonth = 12;
  }

1325
  if(dwStyle & MCS_DAYSTATE) {
1326 1327 1328
    NMDAYSTATE nmds;
    int i;

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

1336
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmds.nmhdr.idFrom, (LPARAM)&nmds);
1337 1338
    for(i=0; i<infoPtr->monthRange; i++)
       infoPtr->monthdayState[i] = nmds.prgDayState[i];
1339
  }
1340 1341
}

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

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

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

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

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

1402 1403
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
1404
  hit = MONTHCAL_HitTest(infoPtr, (LPARAM)&ht);
1405 1406

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

1424
  if(hit == MCHT_TITLEMONTH) {
1425
    hMenu = CreatePopupMenu();
1426

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

1472
  }
1473
  if(hit == MCHT_TODAYLINK) {
1474 1475
    NMSELCHANGE nmsc;

1476 1477
    infoPtr->curSelDay = infoPtr->todaysDate.wDay;
    infoPtr->firstSelDay = infoPtr->todaysDate.wDay;
1478 1479
    infoPtr->currentMonth=infoPtr->todaysDate.wMonth;
    infoPtr->currentYear=infoPtr->todaysDate.wYear;
1480 1481
    MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->minSel);
    MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->maxSel);
1482
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1483 1484 1485 1486 1487 1488

    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);
1489
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
1490 1491

    nmsc.nmhdr.code     = MCN_SELECT;
1492
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
1493
    return 0;
1494
  }
1495
  if(hit == MCHT_CALENDARDATE) {
1496 1497 1498
    SYSTEMTIME selArray[2];
    NMSELCHANGE nmsc;

1499 1500
    MONTHCAL_CopyTime(&ht.st, &selArray[0]);
    MONTHCAL_CopyTime(&ht.st, &selArray[1]);
1501 1502
    MONTHCAL_SetSelRange(infoPtr, (LPARAM)&selArray);
    MONTHCAL_SetCurSel(infoPtr, (LPARAM)&selArray);
1503
    TRACE("MCHT_CALENDARDATE\n");
1504 1505
    nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1506
    nmsc.nmhdr.code     = MCN_SELCHANGE;
1507 1508
    MONTHCAL_CopyTime(&infoPtr->minSel,&nmsc.stSelStart);
    MONTHCAL_CopyTime(&infoPtr->maxSel,&nmsc.stSelEnd);
1509

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

1512

1513
    /* redraw both old and new days if the selected day changed */
1514
    if(infoPtr->curSelDay != ht.st.wDay) {
1515
      MONTHCAL_CalcPosFromDay(infoPtr, ht.st.wDay, ht.st.wMonth, &rcDay);
1516
      InvalidateRect(infoPtr->hwndSelf, &rcDay, TRUE);
1517 1518

      MONTHCAL_CalcPosFromDay(infoPtr, infoPtr->curSelDay, infoPtr->currentMonth, &rcDay);
1519
      InvalidateRect(infoPtr->hwndSelf, &rcDay, TRUE);
1520
    }
1521

1522 1523 1524
    infoPtr->firstSelDay = ht.st.wDay;
    infoPtr->curSelDay = ht.st.wDay;
    infoPtr->status = MC_SEL_LBUTDOWN;
1525
    return 0;
1526 1527
  }

1528
  return 1;
1529 1530
}

1531

1532
static LRESULT
1533
MONTHCAL_LButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1534
{
1535 1536
  NMSELCHANGE nmsc;
  NMHDR nmhdr;
1537
  BOOL redraw = FALSE;
1538 1539
  MCHITTESTINFO ht;
  DWORD hit;
1540 1541

  TRACE("\n");
1542

1543
  if(infoPtr->status & MC_NEXTPRESSED) {
1544
    KillTimer(infoPtr->hwndSelf, MC_NEXTMONTHTIMER);
1545
    infoPtr->status &= ~MC_NEXTPRESSED;
1546 1547 1548
    redraw = TRUE;
  }
  if(infoPtr->status & MC_PREVPRESSED) {
1549
    KillTimer(infoPtr->hwndSelf, MC_PREVMONTHTIMER);
1550
    infoPtr->status &= ~MC_PREVPRESSED;
1551 1552
    redraw = TRUE;
  }
1553

1554 1555
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
1556
  hit = MONTHCAL_HitTest(infoPtr, (LPARAM)&ht);
1557

1558
  infoPtr->status = MC_SEL_LBUTUP;
1559

1560
  if(hit ==MCHT_CALENDARDATENEXT) {
1561 1562
    MONTHCAL_GoToNextMonth(infoPtr);
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1563 1564
    return TRUE;
  }
1565
  if(hit == MCHT_CALENDARDATEPREV){
1566 1567
    MONTHCAL_GoToPrevMonth(infoPtr);
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1568 1569
    return TRUE;
  }
1570 1571
  nmhdr.hwndFrom = infoPtr->hwndSelf;
  nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1572
  nmhdr.code     = NM_RELEASEDCAPTURE;
1573
  TRACE("Sent notification from %p to %p\n", infoPtr->hwndSelf, infoPtr->hwndNotify);
1574

1575
  SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmhdr.idFrom, (LPARAM)&nmhdr);
1576
  /* redraw if necessary */
1577
  if(redraw)
1578
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1579 1580
  /* only send MCN_SELECT if currently displayed month's day was selected */
  if(hit == MCHT_CALENDARDATE) {
1581 1582
    nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1583 1584 1585
    nmsc.nmhdr.code     = MCN_SELECT;
    MONTHCAL_CopyTime(&infoPtr->minSel, &nmsc.stSelStart);
    MONTHCAL_CopyTime(&infoPtr->maxSel, &nmsc.stSelEnd);
1586

1587
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
1588 1589

  }
1590
  return 0;
1591 1592
}

1593

1594
static LRESULT
1595
MONTHCAL_Timer(MONTHCAL_INFO *infoPtr, WPARAM wParam)
1596
{
1597
  BOOL redraw = FALSE;
1598

1599
  TRACE("%ld\n", wParam);
1600

1601
  switch(wParam) {
1602
  case MC_NEXTMONTHTIMER:
1603
    redraw = TRUE;
1604
    MONTHCAL_GoToNextMonth(infoPtr);
1605 1606
    break;
  case MC_PREVMONTHTIMER:
1607
    redraw = TRUE;
1608
    MONTHCAL_GoToPrevMonth(infoPtr);
1609 1610 1611
    break;
  default:
    ERR("got unknown timer\n");
1612
    break;
1613
  }
1614

1615
  /* redraw only if necessary */
1616
  if(redraw)
1617
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1618

1619 1620
  return 0;
}
1621

1622 1623

static LRESULT
1624
MONTHCAL_MouseMove(MONTHCAL_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
1625
{
1626 1627 1628
  MCHITTESTINFO ht;
  int oldselday, selday, hit;
  RECT r;
1629

1630
  if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
1631

1632 1633
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
1634

1635
  hit = MONTHCAL_HitTest(infoPtr, (LPARAM)&ht);
1636

1637
  /* not on the calendar date numbers? bail out */
1638 1639
  TRACE("hit:%x\n",hit);
  if((hit & MCHT_CALENDARDATE) != MCHT_CALENDARDATE) return 0;
1640 1641 1642 1643

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

1646
  if(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & MCS_MULTISELECT)  {
1647 1648 1649
    SYSTEMTIME selArray[2];
    int i;

1650
    MONTHCAL_GetSelRange(infoPtr, (LPARAM)&selArray);
1651
    i = 0;
1652 1653
    if(infoPtr->firstSelDay==selArray[0].wDay) i=1;
    TRACE("oldRange:%d %d %d %d\n", infoPtr->firstSelDay, selArray[0].wDay, selArray[1].wDay, i);
1654
    if(infoPtr->firstSelDay==selArray[1].wDay) {
1655 1656
      /* 1st time we get here: selArray[0]=selArray[1])  */
      /* if we're still at the first selected date, return */
1657 1658
      if(infoPtr->firstSelDay==selday) goto done;
      if(selday<infoPtr->firstSelDay) i = 0;
1659
    }
1660

1661 1662
    if(abs(infoPtr->firstSelDay - selday) >= infoPtr->maxSelCount) {
      if(selday>infoPtr->firstSelDay)
1663 1664 1665 1666
        selday = infoPtr->firstSelDay + infoPtr->maxSelCount;
      else
        selday = infoPtr->firstSelDay - infoPtr->maxSelCount;
    }
1667

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

1671
      selArray[i].wDay = selday;
1672

1673
      if(selArray[0].wDay>selArray[1].wDay) {
1674 1675 1676 1677 1678
        DWORD tempday;
        tempday = selArray[1].wDay;
        selArray[1].wDay = selArray[0].wDay;
        selArray[0].wDay = tempday;
      }
1679

1680
      MONTHCAL_SetSelRange(infoPtr, (LPARAM)&selArray);
1681 1682
    }
  }
1683 1684 1685

done:

1686
  /* only redraw if the currently selected day changed */
1687
  /* FIXME: this should specify a rectangle containing only the days that changed */
1688
  /* using InvalidateRect */
1689
  if(oldselday != infoPtr->curSelDay)
1690
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1691

1692
  return 0;
1693 1694
}

1695

1696
static LRESULT
1697
MONTHCAL_Paint(MONTHCAL_INFO *infoPtr, WPARAM wParam)
1698
{
1699 1700
  HDC hdc;
  PAINTSTRUCT ps;
1701

1702 1703
  if (wParam)
  {
1704
    GetClientRect(infoPtr->hwndSelf, &ps.rcPaint);
1705 1706 1707
    hdc = (HDC)wParam;
  }
  else
1708
    hdc = BeginPaint(infoPtr->hwndSelf, &ps);
1709

1710 1711
  MONTHCAL_Refresh(infoPtr, hdc, &ps);
  if (!wParam) EndPaint(infoPtr->hwndSelf, &ps);
1712
  return 0;
1713 1714
}

1715

1716
static LRESULT
1717
MONTHCAL_KillFocus(const MONTHCAL_INFO *infoPtr, HWND hFocusWnd)
1718
{
1719
  TRACE("\n");
1720

1721 1722 1723 1724
  if (infoPtr->hwndNotify != hFocusWnd)
    ShowWindow(infoPtr->hwndSelf, SW_HIDE);
  else
    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1725

1726
  return 0;
1727 1728 1729 1730
}


static LRESULT
1731
MONTHCAL_SetFocus(const MONTHCAL_INFO *infoPtr)
1732
{
1733
  TRACE("\n");
1734

1735
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1736

1737
  return 0;
1738 1739
}

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

1762
  GetClientRect(infoPtr->hwndSelf, &rcClient);
1763

1764
  currentFont = SelectObject(hdc, infoPtr->hFont);
1765 1766

  /* get the height and width of each day's text */
1767
  GetTextMetricsW(hdc, &tm);
1768
  infoPtr->textHeight = tm.tmHeight + tm.tmExternalLeading + tm.tmInternalLeading;
1769
  GetTextExtentPoint32W(hdc, SunW, 3, &size);
1770 1771
  infoPtr->textWidth = size.cx + 2;

1772
  /* recalculate the height and width increments and offsets */
1773
  GetTextExtentPoint32W(hdc, O0W, 2, &size);
1774

1775
  xdiv = (dwStyle & MCS_WEEKNUMBERS) ? 8 : 7;
1776

Duane Clark's avatar
Duane Clark committed
1777
  infoPtr->width_increment = size.cx * 2 + 4;
1778 1779
  infoPtr->height_increment = infoPtr->textHeight;
  left_offset = (rcClient.right - rcClient.left) - (infoPtr->width_increment * xdiv);
1780

1781
  /* calculate title area */
1782 1783 1784 1785
  title->top    = rcClient.top;
  title->bottom = title->top + 3 * infoPtr->height_increment / 2;
  title->left   = left_offset;
  title->right  = rcClient.right;
1786 1787 1788

  /* set the dimensions of the next and previous buttons and center */
  /* the month text vertically */
1789 1790 1791
  prev->top    = next->top    = title->top + 4;
  prev->bottom = next->bottom = title->bottom - 4;
  prev->left   = title->left + 4;
1792
  prev->right  = prev->left + (title->bottom - title->top) ;
1793
  next->right  = title->right - 4;
1794
  next->left   = next->right - (title->bottom - title->top);
1795

1796 1797 1798
  /* 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 */
1799 1800
  titlemonth->top    = titleyear->top    = title->top    + (infoPtr->height_increment)/2;
  titlemonth->bottom = titleyear->bottom = title->bottom - (infoPtr->height_increment)/2;
1801

1802 1803
  /* setup the dimensions of the rectangle we draw the names of the */
  /* days of the week in */
1804
  weeknumrect->left = left_offset;
1805
  if(dwStyle & MCS_WEEKNUMBERS)
1806 1807 1808 1809 1810 1811 1812
    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;
1813

1814
  days->top    = weeknumrect->top = wdays->bottom ;
1815
  days->bottom = weeknumrect->bottom = days->top + 6 * infoPtr->height_increment;
1816

1817 1818
  todayrect->left   = rcClient.left;
  todayrect->right  = rcClient.right;
1819 1820 1821
  todayrect->top    = days->bottom;
  todayrect->bottom = days->bottom + infoPtr->height_increment;

1822
  TRACE("dx=%d dy=%d client[%s] title[%s] wdays[%s] days[%s] today[%s]\n",
1823
	infoPtr->width_increment,infoPtr->height_increment,
1824 1825 1826 1827 1828
        wine_dbgstr_rect(&rcClient),
        wine_dbgstr_rect(title),
        wine_dbgstr_rect(wdays),
        wine_dbgstr_rect(days),
        wine_dbgstr_rect(todayrect));
1829

1830
  /* restore the originally selected font */
1831
  SelectObject(hdc, currentFont);
1832

1833
  ReleaseDC(infoPtr->hwndSelf, hdc);
1834 1835
}

1836
static LRESULT MONTHCAL_Size(MONTHCAL_INFO *infoPtr, int Width, int Height)
1837
{
1838
  TRACE("(width=%d, height=%d)\n", Width, Height);
1839

1840
  MONTHCAL_UpdateSize(infoPtr);
1841 1842

  /* invalidate client area and erase background */
1843
  InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1844 1845 1846

  return 0;
}
1847

1848
static LRESULT MONTHCAL_GetFont(const MONTHCAL_INFO *infoPtr)
1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872
{
    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;
}

1873
/* update theme after a WM_THEMECHANGED message */
1874
static LRESULT theme_changed (const MONTHCAL_INFO* infoPtr)
1875 1876 1877 1878 1879 1880 1881
{
    HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
    CloseThemeData (theme);
    theme = OpenThemeData (infoPtr->hwndSelf, themeClass);
    return 0;
}

1882
/* FIXME: check whether dateMin/dateMax need to be adjusted. */
1883
static LRESULT
1884
MONTHCAL_Create(HWND hwnd, WPARAM wParam, LPARAM lParam)
1885
{
1886
  MONTHCAL_INFO *infoPtr;
1887

1888
  /* allocate memory for info structure */
1889
  infoPtr =(MONTHCAL_INFO*)Alloc(sizeof(MONTHCAL_INFO));
1890
  SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
1891

1892 1893
  if(infoPtr == NULL) {
    ERR( "could not allocate info memory!\n");
1894 1895
    return 0;
  }
1896

1897
  infoPtr->hwndSelf = hwnd;
1898 1899
  infoPtr->hwndNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;

1900
  MONTHCAL_SetFont(infoPtr, GetStockObject(DEFAULT_GUI_FONT), FALSE);
1901 1902

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

Duane Clark's avatar
Duane Clark committed
1905
  GetLocalTime(&infoPtr->todaysDate);
1906
  infoPtr->firstDayHighWord = FALSE;
1907
  MONTHCAL_SetFirstDayOfWeek(infoPtr, (LPARAM)-1);
1908 1909
  infoPtr->currentMonth = infoPtr->todaysDate.wMonth;
  infoPtr->currentYear = infoPtr->todaysDate.wYear;
1910 1911
  MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->minDate);
  MONTHCAL_CopyTime(&infoPtr->todaysDate, &infoPtr->maxDate);
1912 1913
  infoPtr->maxDate.wYear=2050;
  infoPtr->minDate.wYear=1950;
1914
  infoPtr->maxSelCount  = 7;
1915
  infoPtr->monthRange = 3;
1916
  infoPtr->monthdayState = Alloc
1917 1918 1919 1920 1921 1922 1923 1924
                         (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);

1925 1926 1927 1928
  /* set the current day for highlighing */
  infoPtr->minSel.wDay = infoPtr->todaysDate.wDay;
  infoPtr->maxSel.wDay = infoPtr->todaysDate.wDay;

1929 1930
  /* call MONTHCAL_UpdateSize to set all of the dimensions */
  /* of the control */
1931
  MONTHCAL_UpdateSize(infoPtr);
1932 1933
  
  OpenThemeData (infoPtr->hwndSelf, themeClass);
1934 1935

  return 0;
1936 1937 1938 1939
}


static LRESULT
1940
MONTHCAL_Destroy(MONTHCAL_INFO *infoPtr)
1941
{
1942
  /* free month calendar info data */
1943
  Free(infoPtr->monthdayState);
1944
  SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
1945

1946 1947
  CloseThemeData (GetWindowTheme (infoPtr->hwndSelf));
  
1948
  Free(infoPtr);
1949
  return 0;
1950 1951 1952
}


1953
static LRESULT WINAPI
1954
MONTHCAL_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1955
{
1956 1957
  MONTHCAL_INFO *infoPtr;

1958
  TRACE("hwnd=%p msg=%x wparam=%lx lparam=%lx\n", hwnd, uMsg, wParam, lParam);
1959 1960 1961

  infoPtr = MONTHCAL_GetInfoPtr(hwnd);
  if (!infoPtr && (uMsg != WM_CREATE))
1962
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
1963
  switch(uMsg)
1964 1965
  {
  case MCM_GETCURSEL:
1966
    return MONTHCAL_GetCurSel(infoPtr, lParam);
1967

1968
  case MCM_SETCURSEL:
1969
    return MONTHCAL_SetCurSel(infoPtr, lParam);
1970

1971
  case MCM_GETMAXSELCOUNT:
1972
    return MONTHCAL_GetMaxSelCount(infoPtr);
1973

1974
  case MCM_SETMAXSELCOUNT:
1975
    return MONTHCAL_SetMaxSelCount(infoPtr, wParam);
1976

1977
  case MCM_GETSELRANGE:
1978
    return MONTHCAL_GetSelRange(infoPtr, lParam);
1979

1980
  case MCM_SETSELRANGE:
1981
    return MONTHCAL_SetSelRange(infoPtr, lParam);
1982

1983
  case MCM_GETMONTHRANGE:
1984
    return MONTHCAL_GetMonthRange(infoPtr);
1985

1986
  case MCM_SETDAYSTATE:
1987
    return MONTHCAL_SetDayState(infoPtr, wParam, lParam);
1988

1989
  case MCM_GETMINREQRECT:
1990
    return MONTHCAL_GetMinReqRect(infoPtr, lParam);
1991

1992
  case MCM_GETCOLOR:
1993
    return MONTHCAL_GetColor(infoPtr, wParam);
1994

1995
  case MCM_SETCOLOR:
1996
    return MONTHCAL_SetColor(infoPtr, wParam, lParam);
1997

1998
  case MCM_GETTODAY:
1999
    return MONTHCAL_GetToday(infoPtr, lParam);
2000

2001
  case MCM_SETTODAY:
2002
    return MONTHCAL_SetToday(infoPtr, lParam);
2003

2004
  case MCM_HITTEST:
2005
    return MONTHCAL_HitTest(infoPtr, lParam);
2006

2007
  case MCM_GETFIRSTDAYOFWEEK:
2008
    return MONTHCAL_GetFirstDayOfWeek(infoPtr);
2009

2010
  case MCM_SETFIRSTDAYOFWEEK:
2011
    return MONTHCAL_SetFirstDayOfWeek(infoPtr, lParam);
2012

2013
  case MCM_GETRANGE:
2014
    return MONTHCAL_GetRange(hwnd, wParam, lParam);
2015

2016
  case MCM_SETRANGE:
2017
    return MONTHCAL_SetRange(infoPtr, wParam, lParam);
2018

2019
  case MCM_GETMONTHDELTA:
2020
    return MONTHCAL_GetMonthDelta(infoPtr);
2021

2022
  case MCM_SETMONTHDELTA:
2023
    return MONTHCAL_SetMonthDelta(infoPtr, wParam);
2024

2025
  case MCM_GETMAXTODAYWIDTH:
2026
    return MONTHCAL_GetMaxTodayWidth(infoPtr);
2027

2028 2029
  case WM_GETDLGCODE:
    return DLGC_WANTARROWS | DLGC_WANTCHARS;
2030

2031
  case WM_KILLFOCUS:
2032
    return MONTHCAL_KillFocus(infoPtr, (HWND)wParam);
2033

2034
  case WM_RBUTTONDOWN:
2035
    return MONTHCAL_RButtonDown(infoPtr, lParam);
2036

2037
  case WM_LBUTTONDOWN:
2038
    return MONTHCAL_LButtonDown(infoPtr, lParam);
2039

2040
  case WM_MOUSEMOVE:
2041
    return MONTHCAL_MouseMove(infoPtr, wParam, lParam);
2042

2043
  case WM_LBUTTONUP:
2044
    return MONTHCAL_LButtonUp(infoPtr, lParam);
2045

2046
  case WM_PRINTCLIENT:
2047
  case WM_PAINT:
2048
    return MONTHCAL_Paint(infoPtr, wParam);
2049

2050
  case WM_SETFOCUS:
2051
    return MONTHCAL_SetFocus(infoPtr);
2052 2053

  case WM_SIZE:
2054
    return MONTHCAL_Size(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
2055

2056
  case WM_CREATE:
2057
    return MONTHCAL_Create(hwnd, wParam, lParam);
2058

2059 2060 2061 2062 2063 2064
  case WM_SETFONT:
    return MONTHCAL_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);

  case WM_GETFONT:
    return MONTHCAL_GetFont(infoPtr);

2065
  case WM_TIMER:
2066
    return MONTHCAL_Timer(infoPtr, wParam);
2067 2068 2069
    
  case WM_THEMECHANGED:
    return theme_changed (infoPtr);
2070

2071
  case WM_DESTROY:
2072
    return MONTHCAL_Destroy(infoPtr);
2073

2074
  default:
2075
    if ((uMsg >= WM_USER) && (uMsg < WM_APP))
2076
      ERR( "unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
2077
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
2078
  }
2079 2080 2081
}


2082
void
2083
MONTHCAL_Register(void)
2084
{
2085
  WNDCLASSW wndClass;
2086

2087
  ZeroMemory(&wndClass, sizeof(WNDCLASSW));
2088
  wndClass.style         = CS_GLOBALCLASS;
2089
  wndClass.lpfnWndProc   = MONTHCAL_WindowProc;
2090 2091
  wndClass.cbClsExtra    = 0;
  wndClass.cbWndExtra    = sizeof(MONTHCAL_INFO *);
2092
  wndClass.hCursor       = LoadCursorW(0, (LPWSTR)IDC_ARROW);
2093
  wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
2094
  wndClass.lpszClassName = MONTHCAL_CLASSW;
2095

2096
  RegisterClassW(&wndClass);
2097 2098 2099
}


2100
void
2101
MONTHCAL_Unregister(void)
2102
{
2103
    UnregisterClassW(MONTHCAL_CLASSW, NULL);
2104
}