monthcal.c 85.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
 * Copyright 2009-2011 Nikolay Sivov
10
 *
11 12 13 14 15 16 17 18 19 20 21 22
 * 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
23
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24
 *
25 26 27 28 29 30 31 32 33
 * 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.
 * 
34
 * TODO:
35 36 37 38 39
 *    -- MCM_[GS]ETUNICODEFORMAT
 *    -- handle resources better (doesn't work now); 
 *    -- take care of internationalization.
 *    -- keyboard handling.
 *    -- search for FIXME
40 41
 */

42
#include <math.h>
43
#include <stdarg.h>
44
#include <stdio.h>
45
#include <stdlib.h>
46
#include <string.h>
47

48
#include "windef.h"
49
#include "winbase.h"
50
#include "wingdi.h"
51 52
#include "winuser.h"
#include "winnls.h"
53
#include "commctrl.h"
54
#include "comctl32.h"
55
#include "uxtheme.h"
56
#include "vssym32.h"
57
#include "wine/unicode.h"
58
#include "wine/debug.h"
59

60
WINE_DEFAULT_DEBUG_CHANNEL(monthcal);
61

62 63 64 65
#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 */
66
#define MC_PREVNEXTMONTHDELAY   350	/* when continuously pressing `next/prev
67
					   month', wait 350 ms before going
68 69 70
					   to the next/prev month */
#define MC_TODAYUPDATEDELAY 120000 /* time between today check for update (2 min) */

71
#define MC_PREVNEXTMONTHTIMER   1	/* Timer IDs */
72
#define MC_TODAYUPDATETIMER     2
73

74 75
#define MC_CALENDAR_PADDING     6

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

78 79 80
/* convert from days to 100 nanoseconds unit - used as FILETIME unit */
#define DAYSTO100NSECS(days) (((ULONGLONG)(days))*24*60*60*10000000)

81 82 83 84 85 86 87
enum CachedPen
{
    PenRed = 0,
    PenText,
    PenLast
};

88 89 90 91 92 93 94 95
enum CachedBrush
{
    BrushTitle = 0,
    BrushMonth,
    BrushBackground,
    BrushLast
};

96 97 98 99 100 101 102 103 104
/* single calendar data */
typedef struct _CALENDAR_INFO
{
    RECT title;      /* rect for the header above the calendar */
    RECT titlemonth; /* the 'month name' text in the header */
    RECT titleyear;  /* the 'year number' text in the header */
    RECT wdays;      /* week days at top */
    RECT days;       /* calendar area */
    RECT weeknums;   /* week numbers at left side */
105 106

    SYSTEMTIME month;/* contains calendar main month/year */
107 108
} CALENDAR_INFO;

109 110
typedef struct
{
111 112
    HWND	hwndSelf;
    DWORD	dwStyle; /* cached GWL_STYLE */
113 114

    COLORREF    colors[MCSC_TRAILINGTEXT+1];
115
    HBRUSH      brushes[BrushLast];
116
    HPEN        pens[PenLast];
117

118 119 120 121 122 123
    HFONT	hFont;
    HFONT	hBoldFont;
    int		textHeight;
    int		textWidth;
    int		height_increment;
    int		width_increment;
124
    INT		delta;	/* scroll rate; # of months that the */
125 126
                        /* control moves when user clicks a scroll button */
    int		visible;	/* # of months visible */
127 128 129 130
    int		firstDay;	/* Start month calendar with firstDay's day,
				   stored in SYSTEMTIME format */
    BOOL	firstDaySet;    /* first week day differs from locale defined */

131 132
    BOOL	isUnicode;      /* value set with MCM_SETUNICODE format */

133 134
    MONTHDAYSTATE *monthdayState;
    SYSTEMTIME	todaysDate;
135
    BOOL	todaySet;       /* Today was forced with MCM_SETTODAY */
136
    int		status;		/* See MC_SEL flags */
137
    SYSTEMTIME	firstSel;	/* first selected day */
138
    INT		maxSelCount;
139
    SYSTEMTIME	minSel;         /* contains single selection when used without MCS_MULTISELECT */
140
    SYSTEMTIME	maxSel;
141
    SYSTEMTIME  focusedSel;     /* date currently focused with mouse movement */
142 143 144
    DWORD	rangeValid;
    SYSTEMTIME	minDate;
    SYSTEMTIME	maxDate;
145

146
    RECT titlebtnnext;	/* the `next month' button in the header */
147
    RECT titlebtnprev;  /* the `prev month' button in the header */
148
    RECT todayrect;	/* `today: xx/xx/xx' text rect */
149
    HWND hwndNotify;    /* Window to receive the notifications */
150 151
    HWND hWndYearEdit;  /* Window Handle of edit box to handle years */
    HWND hWndYearUpDown;/* Window Handle of updown box to handle years */
152
    WNDPROC EditWndProc;  /* original Edit window procedure */
153 154

    CALENDAR_INFO *calendars;
155
    SIZE dim;           /* [cx,cy] - dimensions of calendars matrix, row/column count */
156 157
} MONTHCAL_INFO, *LPMONTHCAL_INFO;

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

160 161
/* empty SYSTEMTIME const */
static const SYSTEMTIME st_null;
162
/* valid date limits */
163 164
static const SYSTEMTIME max_allowed_date = { /* wYear */ 9999, /* wMonth */ 12, /* wDayOfWeek */ 0, /* wDay */ 31 };
static const SYSTEMTIME min_allowed_date = { /* wYear */ 1752, /* wMonth */ 9,  /* wDayOfWeek */ 0, /* wDay */ 14 };
165

166 167 168 169 170 171
/* Prev/Next buttons */
enum nav_direction
{
    DIRECTION_BACKWARD,
    DIRECTION_FORWARD
};
172

173
/* helper functions  */
174 175 176 177
static inline INT MONTHCAL_GetCalCount(const MONTHCAL_INFO *infoPtr)
{
   return infoPtr->dim.cx * infoPtr->dim.cy;
}
178

179 180 181 182 183 184 185 186 187
/* send a single MCN_SELCHANGE notification */
static inline void MONTHCAL_NotifySelectionChange(const MONTHCAL_INFO *infoPtr)
{
    NMSELCHANGE nmsc;

    nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
    nmsc.nmhdr.code     = MCN_SELCHANGE;
    nmsc.stSelStart     = infoPtr->minSel;
188 189 190 191 192 193 194 195
    nmsc.stSelStart.wDayOfWeek = 0;
    if(infoPtr->dwStyle & MCS_MULTISELECT){
        nmsc.stSelEnd = infoPtr->maxSel;
        nmsc.stSelEnd.wDayOfWeek = 0;
    }
    else
        nmsc.stSelEnd = st_null;

196 197 198 199 200 201 202 203 204 205 206 207
    SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
}

/* send a single MCN_SELECT notification */
static inline void MONTHCAL_NotifySelect(const MONTHCAL_INFO *infoPtr)
{
    NMSELCHANGE nmsc;

    nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
    nmsc.nmhdr.code     = MCN_SELECT;
    nmsc.stSelStart     = infoPtr->minSel;
208 209 210 211 212 213 214
    nmsc.stSelStart.wDayOfWeek = 0;
    if(infoPtr->dwStyle & MCS_MULTISELECT){
        nmsc.stSelEnd = infoPtr->maxSel;
        nmsc.stSelEnd.wDayOfWeek = 0;
    }
    else
        nmsc.stSelEnd = st_null;
215 216 217 218

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

219 220 221 222 223
static inline int MONTHCAL_MonthDiff(const SYSTEMTIME *left, const SYSTEMTIME *right)
{
    return (right->wYear - left->wYear)*12 + right->wMonth - left->wMonth;
}

224
/* returns the number of days in any given month, checking for leap days */
225
/* January is 1, December is 12 */
226
int MONTHCAL_MonthLength(int month, int year)
227
{
228
  const int mdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
229 230 231
  /* Wrap around, this eases handling. Getting length only we shouldn't care
     about year change here cause January and December have
     the same day quantity */
232 233
  if(month == 0)
    month = 12;
234
  else if(month == 13)
235 236
    month = 1;

237 238 239
  /* special case for calendar transition year */
  if(month == min_allowed_date.wMonth && year == min_allowed_date.wYear) return 19;

240 241 242 243 244
  /* 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) &&
245
     (year%4 == 0)) ? 1 : 0);
246 247 248 249 250
  }
  else {
    return mdays[month - 1];
  }
}
251

252 253 254 255 256 257
/* compares timestamps using date part only */
static inline BOOL MONTHCAL_IsDateEqual(const SYSTEMTIME *first, const SYSTEMTIME *second)
{
  return (first->wYear == second->wYear) && (first->wMonth == second->wMonth) &&
         (first->wDay  == second->wDay);
}
258

259 260
/* make sure that date fields are valid */
static BOOL MONTHCAL_ValidateDate(const SYSTEMTIME *time)
261
{
262
  if(time->wMonth < 1 || time->wMonth > 12 ) return FALSE;
263
  if(time->wDay > MONTHCAL_MonthLength(time->wMonth, time->wYear)) return FALSE;
264

265
  return TRUE;
266 267
}

268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
/* Copies timestamp part only.
 *
 * PARAMETERS
 *
 *  [I] from : source date
 *  [O] to   : dest date
 */
static void MONTHCAL_CopyTime(const SYSTEMTIME *from, SYSTEMTIME *to)
{
  to->wHour   = from->wHour;
  to->wMinute = from->wMinute;
  to->wSecond = from->wSecond;
}

/* Copies date part only.
 *
 * PARAMETERS
 *
 *  [I] from : source date
 *  [O] to   : dest date
 */
static void MONTHCAL_CopyDate(const SYSTEMTIME *from, SYSTEMTIME *to)
{
  to->wYear  = from->wYear;
  to->wMonth = from->wMonth;
  to->wDay   = from->wDay;
  to->wDayOfWeek = from->wDayOfWeek;
}

297 298 299 300 301 302 303 304 305 306 307 308 309
/* Compares two dates in SYSTEMTIME format
 *
 * PARAMETERS
 *
 *  [I] first  : pointer to valid first date data to compare
 *  [I] second : pointer to valid second date data to compare
 *
 * RETURN VALUE
 *
 *  -1 : first <  second
 *   0 : first == second
 *   1 : first >  second
 *
310
 *  Note that no date validation performed, already validated values expected.
311
 */
312
LONG MONTHCAL_CompareSystemTime(const SYSTEMTIME *first, const SYSTEMTIME *second)
313 314 315 316 317 318 319 320 321
{
  FILETIME ft_first, ft_second;

  SystemTimeToFileTime(first, &ft_first);
  SystemTimeToFileTime(second, &ft_second);

  return CompareFileTime(&ft_first, &ft_second);
}

322 323 324 325 326 327 328 329 330 331 332 333
static LONG MONTHCAL_CompareMonths(const SYSTEMTIME *first, const SYSTEMTIME *second)
{
  SYSTEMTIME st_first, st_second;

  st_first = st_second = st_null;
  MONTHCAL_CopyDate(first, &st_first);
  MONTHCAL_CopyDate(second, &st_second);
  st_first.wDay = st_second.wDay = 1;

  return MONTHCAL_CompareSystemTime(&st_first, &st_second);
}

334 335 336 337 338 339 340 341 342 343 344
static LONG MONTHCAL_CompareDate(const SYSTEMTIME *first, const SYSTEMTIME *second)
{
  SYSTEMTIME st_first, st_second;

  st_first = st_second = st_null;
  MONTHCAL_CopyDate(first, &st_first);
  MONTHCAL_CopyDate(second, &st_second);

  return MONTHCAL_CompareSystemTime(&st_first, &st_second);
}

345 346 347 348 349 350
/* Checks largest possible date range and configured one
 *
 * PARAMETERS
 *
 *  [I] infoPtr : valid pointer to control data
 *  [I] date    : pointer to valid date data to check
351
 *  [I] fix     : make date fit valid range
352 353 354
 *
 * RETURN VALUE
 *
355
 *  TRUE  - date within largest and configured range
356 357
 *  FALSE - date is outside largest or configured range
 */
358 359
static BOOL MONTHCAL_IsDateInValidRange(const MONTHCAL_INFO *infoPtr,
                                        SYSTEMTIME *date, BOOL fix)
360
{
361
  const SYSTEMTIME *fix_st = NULL;
362

363 364 365 366 367 368
  if(MONTHCAL_CompareSystemTime(date, &max_allowed_date) == 1) {
     fix_st = &max_allowed_date;
  }
  else if(MONTHCAL_CompareSystemTime(date, &min_allowed_date) == -1) {
     fix_st = &min_allowed_date;
  }
369 370 371 372 373
  else {
     if(infoPtr->rangeValid & GDTR_MAX) {
        if((MONTHCAL_CompareSystemTime(date, &infoPtr->maxDate) == 1)) {
           fix_st = &infoPtr->maxDate;
        }
374
     }
375 376 377 378 379

     if(infoPtr->rangeValid & GDTR_MIN) {
        if((MONTHCAL_CompareSystemTime(date, &infoPtr->minDate) == -1)) {
           fix_st = &infoPtr->minDate;
        }
380
     }
381 382
  }

383 384 385
  if (fix && fix_st) {
    date->wYear  = fix_st->wYear;
    date->wMonth = fix_st->wMonth;
386 387
  }

388
  return !fix_st;
389 390
}

391 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
/* Checks passed range width with configured maximum selection count
 *
 * PARAMETERS
 *
 *  [I] infoPtr : valid pointer to control data
 *  [I] range0  : pointer to valid date data (requested bound)
 *  [I] range1  : pointer to valid date data (primary bound)
 *  [O] adjust  : returns adjusted range bound to fit maximum range (optional)
 *
 *  Adjust value computed basing on primary bound and current maximum selection
 *  count. For simple range check (without adjusted value required) (range0, range1)
 *  relation means nothing.
 *
 * RETURN VALUE
 *
 *  TRUE  - range is shorter or equal to maximum
 *  FALSE - range is larger than maximum
 */
static BOOL MONTHCAL_IsSelRangeValid(const MONTHCAL_INFO *infoPtr,
                                     const SYSTEMTIME *range0,
                                     const SYSTEMTIME *range1,
                                     SYSTEMTIME *adjust)
{
  ULARGE_INTEGER ul_range0, ul_range1, ul_diff;
  FILETIME ft_range0, ft_range1;
  LONG cmp;

  SystemTimeToFileTime(range0, &ft_range0);
  SystemTimeToFileTime(range1, &ft_range1);

421 422 423 424
  ul_range0.u.LowPart  = ft_range0.dwLowDateTime;
  ul_range0.u.HighPart = ft_range0.dwHighDateTime;
  ul_range1.u.LowPart  = ft_range1.dwLowDateTime;
  ul_range1.u.HighPart = ft_range1.dwHighDateTime;
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440

  cmp = CompareFileTime(&ft_range0, &ft_range1);

  if(cmp == 1)
     ul_diff.QuadPart = ul_range0.QuadPart - ul_range1.QuadPart;
  else
     ul_diff.QuadPart = -ul_range0.QuadPart + ul_range1.QuadPart;

  if(ul_diff.QuadPart >= DAYSTO100NSECS(infoPtr->maxSelCount)) {

     if(adjust) {
       if(cmp == 1)
          ul_range0.QuadPart = ul_range1.QuadPart + DAYSTO100NSECS(infoPtr->maxSelCount - 1);
       else
          ul_range0.QuadPart = ul_range1.QuadPart - DAYSTO100NSECS(infoPtr->maxSelCount - 1);

441 442
       ft_range0.dwLowDateTime  = ul_range0.u.LowPart;
       ft_range0.dwHighDateTime = ul_range0.u.HighPart;
443 444 445 446 447 448 449 450
       FileTimeToSystemTime(&ft_range0, adjust);
     }

     return FALSE;
  }
  else return TRUE;
}

451
/* Used in MCM_SETRANGE/MCM_SETSELRANGE to determine resulting time part.
452
   Milliseconds are intentionally not validated. */
453 454 455 456 457 458 459 460
static BOOL MONTHCAL_ValidateTime(const SYSTEMTIME *time)
{
  if((time->wHour > 24) || (time->wMinute > 59) || (time->wSecond > 59))
    return FALSE;
  else
    return TRUE;
}

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

468 469 470
/* Returns the day in the week
 *
 * PARAMETERS
471 472
 *  [i] date    : input date
 *  [I] inplace : set calculated value back to date structure
473 474 475 476
 *
 * RETURN VALUE
 *   day of week in SYSTEMTIME format: (0 == sunday,..., 6 == saturday)
 */
477
int MONTHCAL_CalculateDayOfWeek(SYSTEMTIME *date, BOOL inplace)
478
{
479
  SYSTEMTIME st = st_null;
480 481
  FILETIME ft;

482
  MONTHCAL_CopyDate(date, &st);
483 484 485

  SystemTimeToFileTime(&st, &ft);
  FileTimeToSystemTime(&ft, &st);
486

487 488
  if (inplace) date->wDayOfWeek = st.wDayOfWeek;

489
  return st.wDayOfWeek;
490 491
}

492
/* add/subtract 'months' from date */
493
static inline void MONTHCAL_GetMonth(SYSTEMTIME *date, INT months)
494
{
495
  INT length, m = date->wMonth + months;
496

497 498
  date->wYear += m > 0 ? (m - 1) / 12 : m / 12 - 1;
  date->wMonth = m > 0 ? (m - 1) % 12 + 1 : 12 + m % 12;
499 500 501
  /* fix moving from last day in a month */
  length = MONTHCAL_MonthLength(date->wMonth, date->wYear);
  if(date->wDay > length) date->wDay = length;
502
  MONTHCAL_CalculateDayOfWeek(date, TRUE);
503 504
}

505 506 507
/* properly updates date to point on next month */
static inline void MONTHCAL_GetNextMonth(SYSTEMTIME *date)
{
508
  MONTHCAL_GetMonth(date, 1);
509 510
}

511
/* properly updates date to point on prev month */
512
static inline void MONTHCAL_GetPrevMonth(SYSTEMTIME *date)
513
{
514
  MONTHCAL_GetMonth(date, -1);
515 516
}

517 518 519
/* Returns full date for a first currently visible day */
static void MONTHCAL_GetMinDate(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *date)
{
520 521 522
  /* zero indexed calendar has the earliest date */
  SYSTEMTIME st_first = infoPtr->calendars[0].month;
  INT firstDay;
523

524 525
  st_first.wDay = 1;
  firstDay = MONTHCAL_CalculateDayOfWeek(&st_first, FALSE);
526

527
  *date = infoPtr->calendars[0].month;
528 529 530 531 532 533 534 535 536
  MONTHCAL_GetPrevMonth(date);

  date->wDay = MONTHCAL_MonthLength(date->wMonth, date->wYear) +
               (infoPtr->firstDay - firstDay) % 7 + 1;

  if(date->wDay > MONTHCAL_MonthLength(date->wMonth, date->wYear))
    date->wDay -= 7;

  /* fix day of week */
537
  MONTHCAL_CalculateDayOfWeek(date, TRUE);
538 539 540 541 542
}

/* Returns full date for a last currently visible day */
static void MONTHCAL_GetMaxDate(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *date)
{
543
  /* the latest date is in latest calendar */
544 545 546 547 548 549 550 551 552
  SYSTEMTIME st, *lt_month = &infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
  INT first_day;

  *date = *lt_month;
  st = *lt_month;

  /* day of week of first day of current month */
  st.wDay = 1;
  first_day = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
553 554

  MONTHCAL_GetNextMonth(date);
555 556 557 558 559 560
  MONTHCAL_GetPrevMonth(&st);

  /* last calendar starts with some date from previous month that not displayed */
  st.wDay = MONTHCAL_MonthLength(st.wMonth, st.wYear) +
             (infoPtr->firstDay - first_day) % 7 + 1;
  if (st.wDay > MONTHCAL_MonthLength(st.wMonth, st.wYear)) st.wDay -= 7;
561 562 563

  /* Use month length to get max day. 42 means max day count in calendar area */
  date->wDay = 42 - (MONTHCAL_MonthLength(st.wMonth, st.wYear) - st.wDay + 1) -
564
                     MONTHCAL_MonthLength(lt_month->wMonth, lt_month->wYear);
565 566

  /* fix day of week */
567
  MONTHCAL_CalculateDayOfWeek(date, TRUE);
568 569
}

570 571 572
/* From a given point calculate the row, column and day in the calendar,
   'day == 0' means the last day of the last month. */
static int MONTHCAL_GetDayFromPos(const MONTHCAL_INFO *infoPtr, POINT pt, INT calIdx)
573
{
574 575 576
  SYSTEMTIME st = infoPtr->calendars[calIdx].month;
  int firstDay, col, row;
  RECT client;
577

578
  GetClientRect(infoPtr->hwndSelf, &client);
579

580 581
  /* if the point is outside the x bounds of the window put it at the boundary */
  if (pt.x > client.right) pt.x = client.right;
582

583 584
  col = (pt.x - infoPtr->calendars[calIdx].days.left ) / infoPtr->width_increment;
  row = (pt.y - infoPtr->calendars[calIdx].days.top  ) / infoPtr->height_increment;
585

586 587
  st.wDay = 1;
  firstDay = (MONTHCAL_CalculateDayOfWeek(&st, FALSE) + 6 - infoPtr->firstDay) % 7;
588
  return col + 7 * row - firstDay;
589 590
}

591
/* Get day position for given date and calendar
592 593 594 595 596
 *
 * PARAMETERS
 *
 *  [I] infoPtr : pointer to control data
 *  [I] date : date value
597 598 599
 *  [O] col : day column (zero based)
 *  [O] row : week column (zero based)
 *  [I] calIdx : calendar index
600
 */
601 602
static void MONTHCAL_GetDayPos(const MONTHCAL_INFO *infoPtr, const SYSTEMTIME *date,
    INT *col, INT *row, INT calIdx)
603
{
604 605
  SYSTEMTIME st = infoPtr->calendars[calIdx].month;
  INT first;
606

607 608
  st.wDay = 1;
  first = (MONTHCAL_CalculateDayOfWeek(&st, FALSE) + 6 - infoPtr->firstDay) % 7;
609

610
  if (calIdx == 0 || calIdx == MONTHCAL_GetCalCount(infoPtr)-1) {
611 612
      const SYSTEMTIME *cal = &infoPtr->calendars[calIdx].month;
      LONG cmp = MONTHCAL_CompareMonths(date, &st);
613

614 615 616 617 618 619
      /* previous month */
      if (cmp == -1) {
        *col = (first - MONTHCAL_MonthLength(date->wMonth, cal->wYear) + date->wDay) % 7;
        *row = 0;
        return;
      }
620

621 622 623
      /* next month calculation is same as for current, just add current month length */
      if (cmp == 1)
          first += MONTHCAL_MonthLength(cal->wMonth, cal->wYear);
624 625
  }

626 627
  *col = (date->wDay + first) % 7;
  *row = (date->wDay + first - *col) / 7;
628 629
}

630 631 632
/* returns bounding box for day in given position in given calendar */
static inline void MONTHCAL_GetDayRectI(const MONTHCAL_INFO *infoPtr, RECT *r,
  INT col, INT row, INT calIdx)
633
{
634 635 636
  r->left   = infoPtr->calendars[calIdx].days.left + col * infoPtr->width_increment;
  r->right  = r->left + infoPtr->width_increment;
  r->top    = infoPtr->calendars[calIdx].days.top  + row * infoPtr->height_increment;
637 638 639
  r->bottom = r->top + infoPtr->textHeight;
}

640 641 642 643 644 645
/* Returns bounding box for given date
 *
 * NOTE: when calendar index is unknown pass -1
 */
static inline void MONTHCAL_GetDayRect(const MONTHCAL_INFO *infoPtr, const SYSTEMTIME *date,
    RECT *r, INT calIdx)
646
{
647 648 649 650 651
  INT col, row;

  if (calIdx == -1)
  {
      INT cmp = MONTHCAL_CompareMonths(date, &infoPtr->calendars[0].month);
652

653 654 655 656
      if (cmp <= 0)
          calIdx = 0;
      else
      {
657
          cmp = MONTHCAL_CompareMonths(date, &infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month);
658
          if (cmp >= 0)
659
              calIdx = MONTHCAL_GetCalCount(infoPtr)-1;
660 661
          else
          {
662
              for (calIdx = 1; calIdx < MONTHCAL_GetCalCount(infoPtr)-1; calIdx++)
663 664 665 666 667 668 669 670
                  if (MONTHCAL_CompareMonths(date, &infoPtr->calendars[calIdx].month) == 0)
                      break;
          }
      }
  }

  MONTHCAL_GetDayPos(infoPtr, date, &col, &row, calIdx);
  MONTHCAL_GetDayRectI(infoPtr, r, col, row, calIdx);
671 672
}

673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722
static LRESULT
MONTHCAL_GetMonthRange(const MONTHCAL_INFO *infoPtr, DWORD flag, SYSTEMTIME *st)
{
  INT range;

  TRACE("flag=%d, st=%p\n", flag, st);

  switch (flag) {
  case GMR_VISIBLE:
  {
      if (st)
      {
          st[0] = infoPtr->calendars[0].month;
          st[1] = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;

          if (st[0].wMonth == min_allowed_date.wMonth &&
              st[0].wYear  == min_allowed_date.wYear)
          {
              st[0].wDay = min_allowed_date.wDay;
          }
          else
              st[0].wDay = 1;
          MONTHCAL_CalculateDayOfWeek(&st[0], TRUE);

          st[1].wDay = MONTHCAL_MonthLength(st[1].wMonth, st[1].wYear);
          MONTHCAL_CalculateDayOfWeek(&st[1], TRUE);
      }

      range = MONTHCAL_GetCalCount(infoPtr);
      break;
  }
  case GMR_DAYSTATE:
  {
      if (st)
      {
          MONTHCAL_GetMinDate(infoPtr, &st[0]);
          MONTHCAL_GetMaxDate(infoPtr, &st[1]);
      }
      /* include two partially visible months */
      range = MONTHCAL_GetCalCount(infoPtr) + 2;
      break;
  }
  default:
      WARN("Unknown flag value, got %d\n", flag);
      range = 0;
  }

  return range;
}

723 724 725 726 727
/* Focused day helper:

   - set focused date to given value;
   - reset to zero value if NULL passed;
   - invalidate previous and new day rectangle only if needed.
728 729

   Returns TRUE if focused day changed, FALSE otherwise.
730
*/
731
static BOOL MONTHCAL_SetDayFocus(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *st)
732 733 734 735 736 737 738
{
  RECT r;

  if(st)
  {
    /* there's nothing to do if it's the same date,
       mouse move within same date rectangle case */
739
    if(MONTHCAL_IsDateEqual(&infoPtr->focusedSel, st)) return FALSE;
740 741

    /* invalidate old focused day */
742
    MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1);
743 744 745 746 747
    InvalidateRect(infoPtr->hwndSelf, &r, FALSE);

    infoPtr->focusedSel = *st;
  }

748
  MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1);
749

750
  if(!st && MONTHCAL_ValidateDate(&infoPtr->focusedSel))
751 752 753 754
    infoPtr->focusedSel = st_null;

  /* on set invalidates new day, on reset clears previous focused day */
  InvalidateRect(infoPtr->hwndSelf, &r, FALSE);
755 756

  return TRUE;
757
}
758

759 760 761
/* draw today boundary box for specified rectangle */
static void MONTHCAL_Circle(const MONTHCAL_INFO *infoPtr, HDC hdc, const RECT *r)
{
762
  HPEN old_pen = SelectObject(hdc, infoPtr->pens[PenRed]);
763 764 765 766 767 768 769 770 771
  HBRUSH old_brush;

  old_brush = SelectObject(hdc, GetStockObject(NULL_BRUSH));
  Rectangle(hdc, r->left, r->top, r->right, r->bottom);

  SelectObject(hdc, old_brush);
  SelectObject(hdc, old_pen);
}

772 773
/* Draw today day mark rectangle
 *
774 775
 * [I] hdc  : context to draw in
 * [I] date : day to mark with rectangle
776 777 778 779
 *
 */
static void MONTHCAL_CircleDay(const MONTHCAL_INFO *infoPtr, HDC hdc,
                               const SYSTEMTIME *date)
780
{
781 782 783 784
  RECT r;

  MONTHCAL_GetDayRect(infoPtr, date, &r, -1);
  MONTHCAL_Circle(infoPtr, hdc, &r);
785
}
786

787
static void MONTHCAL_DrawDay(const MONTHCAL_INFO *infoPtr, HDC hdc, const SYSTEMTIME *st,
788
                             int bold, const PAINTSTRUCT *ps)
789
{
790 791
  static const WCHAR fmtW[] = { '%','d',0 };
  WCHAR buf[10];
792
  RECT r, r_temp;
793
  COLORREF oldCol = 0;
794
  COLORREF oldBk  = 0;
795
  INT old_bkmode, selection;
796

797 798
  /* no need to check styles: when selection is not valid, it is set to zero.
     1 < day < 31, so everything is OK */
799
  MONTHCAL_GetDayRect(infoPtr, st, &r, -1);
800
  if(!IntersectRect(&r_temp, &(ps->rcPaint), &r)) return;
801

802
  if ((MONTHCAL_CompareDate(st, &infoPtr->minSel) >= 0) &&
803 804
      (MONTHCAL_CompareDate(st, &infoPtr->maxSel) <= 0))
  {
805
    TRACE("%d %d %d\n", st->wDay, infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
806
    TRACE("%s\n", wine_dbgstr_rect(&r));
807 808
    oldCol = SetTextColor(hdc, infoPtr->colors[MCSC_MONTHBK]);
    oldBk = SetBkColor(hdc, infoPtr->colors[MCSC_TRAILINGTEXT]);
809
    FillRect(hdc, &r, infoPtr->brushes[BrushTitle]);
810

811
    selection = 1;
812
  }
813 814
  else
    selection = 0;
815

816
  SelectObject(hdc, bold ? infoPtr->hBoldFont : infoPtr->hFont);
817

818
  old_bkmode = SetBkMode(hdc, TRANSPARENT);
819
  wsprintfW(buf, fmtW, st->wDay);
820
  DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
821
  SetBkMode(hdc, old_bkmode);
822

823 824
  if (selection)
  {
825 826 827
    SetTextColor(hdc, oldCol);
    SetBkColor(hdc, oldBk);
  }
828 829
}

830
static void MONTHCAL_PaintButton(MONTHCAL_INFO *infoPtr, HDC hdc, enum nav_direction button)
831 832
{
    HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
833 834 835
    RECT *r = button == DIRECTION_FORWARD ? &infoPtr->titlebtnnext : &infoPtr->titlebtnprev;
    BOOL pressed = button == DIRECTION_FORWARD ? infoPtr->status & MC_NEXTPRESSED :
                                                 infoPtr->status & MC_PREVPRESSED;
836 837 838 839 840 841 842 843
    if (theme)
    {
        static const int states[] = {
            /* Prev button */
            ABS_LEFTNORMAL,  ABS_LEFTPRESSED,  ABS_LEFTDISABLED,
            /* Next button */
            ABS_RIGHTNORMAL, ABS_RIGHTPRESSED, ABS_RIGHTDISABLED
        };
844
        int stateNum = button == DIRECTION_FORWARD ? 3 : 0;
845 846 847 848
        if (pressed)
            stateNum += 1;
        else
        {
849
            if (infoPtr->dwStyle & WS_DISABLED) stateNum += 2;
850 851 852 853 854
        }
        DrawThemeBackground (theme, hdc, SBP_ARROWBTN, states[stateNum], r, NULL);
    }
    else
    {
855
        int style = button == DIRECTION_FORWARD ? DFCS_SCROLLRIGHT : DFCS_SCROLLLEFT;
856 857 858 859
        if (pressed)
            style |= DFCS_PUSHED;
        else
        {
860
            if (infoPtr->dwStyle & WS_DISABLED) style |= DFCS_INACTIVE;
861 862 863 864 865
        }
        
        DrawFrameControl(hdc, r, DFC_SCROLL, style);
    }
}
866

867
/* paint a title with buttons and month/year string */
868
static void MONTHCAL_PaintTitle(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
869
{
870
  static const WCHAR fmt_monthW[] = { '%','s',' ','%','l','d',0 };
871
  RECT *title = &infoPtr->calendars[calIdx].title;
872
  const SYSTEMTIME *st = &infoPtr->calendars[calIdx].month;
873 874
  WCHAR buf_month[80], buf_fmt[80];
  SIZE sz;
875

876
  /* fill header box */
877
  FillRect(hdc, title, infoPtr->brushes[BrushTitle]);
878

879
  /* month/year string */
880 881
  SetBkColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
  SetTextColor(hdc, infoPtr->colors[MCSC_TITLETEXT]);
882
  SelectObject(hdc, infoPtr->hBoldFont);
883

884
  GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1 + st->wMonth - 1,
885
                 buf_month, countof(buf_month));
886

887
  wsprintfW(buf_fmt, fmt_monthW, buf_month, st->wYear);
888 889
  DrawTextW(hdc, buf_fmt, strlenW(buf_fmt), title,
                      DT_CENTER | DT_VCENTER | DT_SINGLELINE);
890

891 892
  /* update title rectangles with current month - used while testing hits */
  GetTextExtentPoint32W(hdc, buf_fmt, strlenW(buf_fmt), &sz);
893 894
  infoPtr->calendars[calIdx].titlemonth.left = title->right / 2 + title->left / 2 - sz.cx / 2;
  infoPtr->calendars[calIdx].titleyear.right = title->right / 2 + title->left / 2 + sz.cx / 2;
895

896
  GetTextExtentPoint32W(hdc, buf_month, strlenW(buf_month), &sz);
897 898
  infoPtr->calendars[calIdx].titlemonth.right = infoPtr->calendars[calIdx].titlemonth.left + sz.cx;
  infoPtr->calendars[calIdx].titleyear.left   = infoPtr->calendars[calIdx].titlemonth.right;
899 900
}

901
static void MONTHCAL_PaintWeeknumbers(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
902
{
903
  const SYSTEMTIME *date = &infoPtr->calendars[calIdx].month;
904 905 906
  static const WCHAR fmt_weekW[] = { '%','d',0 };
  INT mindays, weeknum, weeknum1, startofprescal;
  INT i, prev_month;
907 908
  SYSTEMTIME st;
  WCHAR buf[80];
909
  HPEN old_pen;
910
  RECT r;
911 912 913 914 915

  if (!(infoPtr->dwStyle & MCS_WEEKNUMBERS)) return;

  MONTHCAL_GetMinDate(infoPtr, &st);
  startofprescal = st.wDay;
916
  st = *date;
917

918
  prev_month = date->wMonth - 1;
919 920 921 922 923 924 925 926
  if(prev_month == 0) prev_month = 12;

  /*
     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
     LOCALE_IFIRSTWEEKOFYEAR == 2 (e.g. Germany):
     First week of year must contain 4 days of the new year
927
     LOCALE_IFIRSTWEEKOFYEAR == 1  (what countries?)
928 929 930 931 932
     The first week of the year must contain only days of the new year
  */
  GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, buf, countof(buf));
  weeknum = atoiW(buf);
  switch (weeknum)
933
  {
934 935 936 937 938 939 940 941 942
    case 1: mindays = 6;
	break;
    case 2: mindays = 3;
	break;
    case 0: mindays = 0;
        break;
    default:
        WARN("Unknown LOCALE_IFIRSTWEEKOFYEAR value %d, defaulting to 0\n", weeknum);
	mindays = 0;
943
  }
944

945
  if (date->wMonth == 1)
946
  {
947
    /* calculate all those exceptions for January */
948 949 950 951 952 953 954 955
    st.wDay = st.wMonth = 1;
    weeknum1 = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
    if ((infoPtr->firstDay - weeknum1) % 7 > mindays)
	weeknum = 1;
    else
    {
	weeknum = 0;
	for(i = 0; i < 11; i++)
956
	   weeknum += MONTHCAL_MonthLength(i+1, date->wYear - 1);
957

958 959 960 961 962 963 964 965
	weeknum  += startofprescal + 7;
	weeknum  /= 7;
	st.wYear -= 1;
	weeknum1  = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
	if ((infoPtr->firstDay - weeknum1) % 7 > mindays) weeknum++;
    }
  }
  else
966
  {
967 968
    weeknum = 0;
    for(i = 0; i < prev_month - 1; i++)
969
	weeknum += MONTHCAL_MonthLength(i+1, date->wYear);
970 971 972 973 974 975

    weeknum += startofprescal + 7;
    weeknum /= 7;
    st.wDay = st.wMonth = 1;
    weeknum1 = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
    if ((infoPtr->firstDay - weeknum1) % 7 > mindays) weeknum++;
976
  }
977

978
  r = infoPtr->calendars[calIdx].weeknums;
979 980

  /* erase whole week numbers area */
981
  FillRect(hdc, &r, infoPtr->brushes[BrushMonth]);
982
  SetTextColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
983 984

  /* reduce rectangle to one week number */
985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
  r.bottom = r.top + infoPtr->height_increment;

  for(i = 0; i < 6; i++) {
    if((i == 0) && (weeknum > 50))
    {
        wsprintfW(buf, fmt_weekW, weeknum);
        weeknum = 0;
    }
    else if((i == 5) && (weeknum > 47))
    {
	wsprintfW(buf, fmt_weekW, 1);
    }
    else
	wsprintfW(buf, fmt_weekW, weeknum + i);

    DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    OffsetRect(&r, 0, infoPtr->height_increment);
  }
1003

1004
  /* line separator for week numbers column */
1005
  old_pen = SelectObject(hdc, infoPtr->pens[PenText]);
1006 1007
  MoveToEx(hdc, infoPtr->calendars[calIdx].weeknums.right, infoPtr->calendars[calIdx].weeknums.top + 3 , NULL);
  LineTo(hdc,   infoPtr->calendars[calIdx].weeknums.right, infoPtr->calendars[calIdx].weeknums.bottom);
1008
  SelectObject(hdc, old_pen);
1009 1010 1011 1012 1013
}

/* bottom today date */
static void MONTHCAL_PaintTodayTitle(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
{
1014 1015 1016 1017 1018 1019 1020 1021 1022 1023
  static const WCHAR fmt_todayW[] = { '%','s',' ','%','s',0 };
  WCHAR buf_todayW[30], buf_dateW[20], buf[80];
  RECT text_rect, box_rect;
  HFONT old_font;
  INT col;

  if(infoPtr->dwStyle & MCS_NOTODAY) return;

  if (!LoadStringW(COMCTL32_hModule, IDM_TODAY, buf_todayW, countof(buf_todayW)))
  {
1024
    static const WCHAR todayW[] = { 'T','o','d','a','y',':',0 };
1025 1026 1027
    WARN("Can't load resource\n");
    strcpyW(buf_todayW, todayW);
  }
1028

1029 1030
  col = infoPtr->dwStyle & MCS_NOTODAYCIRCLE ? 0 : 1;
  if (infoPtr->dwStyle & MCS_WEEKNUMBERS) col--;
1031 1032
  /* label is located below first calendar last row */
  MONTHCAL_GetDayRectI(infoPtr, &text_rect, col, 6, infoPtr->dim.cx * infoPtr->dim.cy - infoPtr->dim.cx);
1033
  box_rect = text_rect;
1034

1035 1036 1037 1038
  GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &infoPtr->todaysDate, NULL,
                                                      buf_dateW, countof(buf_dateW));
  old_font = SelectObject(hdc, infoPtr->hBoldFont);
  SetTextColor(hdc, infoPtr->colors[MCSC_TEXT]);
1039

1040 1041 1042
  wsprintfW(buf, fmt_todayW, buf_todayW, buf_dateW);
  DrawTextW(hdc, buf, -1, &text_rect, DT_CALCRECT | DT_LEFT | DT_VCENTER | DT_SINGLELINE);
  DrawTextW(hdc, buf, -1, &text_rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
1043

1044 1045 1046
  if(!(infoPtr->dwStyle & MCS_NOTODAYCIRCLE)) {
    OffsetRect(&box_rect, -infoPtr->width_increment, 0);
    MONTHCAL_Circle(infoPtr, hdc, &box_rect);
1047
  }
1048 1049

  SelectObject(hdc, old_font);
1050 1051 1052 1053 1054
}

/* today mark + focus */
static void MONTHCAL_PaintFocusAndCircle(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
{
1055 1056
  /* circle today date if only it's in fully visible month */
  if (!(infoPtr->dwStyle & MCS_NOTODAYCIRCLE))
1057
  {
1058 1059 1060 1061 1062 1063 1064 1065
    INT i;

    for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
      if (!MONTHCAL_CompareMonths(&infoPtr->todaysDate, &infoPtr->calendars[i].month))
      {
        MONTHCAL_CircleDay(infoPtr, hdc, &infoPtr->todaysDate);
        break;
      }
1066 1067
  }

1068
  if (!MONTHCAL_IsDateEqual(&infoPtr->focusedSel, &st_null))
1069 1070
  {
    RECT r;
1071
    MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1);
1072 1073
    DrawFocusRect(hdc, &r);
  }
1074
}
1075

1076 1077 1078
/* months before first calendar month and after last calendar month */
static void MONTHCAL_PaintLeadTrailMonths(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
{
1079
  INT mask, length, index;
1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091
  SYSTEMTIME st_max, st;

  if (infoPtr->dwStyle & MCS_NOTRAILINGDATES) return;

  SetTextColor(hdc, infoPtr->colors[MCSC_TRAILINGTEXT]);

  /* draw prev month */
  MONTHCAL_GetMinDate(infoPtr, &st);
  mask = 1 << (st.wDay-1);
  /* December and January both 31 days long, so no worries if wrapped */
  length = MONTHCAL_MonthLength(infoPtr->calendars[0].month.wMonth - 1,
                                infoPtr->calendars[0].month.wYear);
1092
  index = 0;
1093 1094
  while(st.wDay <= length)
  {
1095
      MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[index] & mask, ps);
1096 1097 1098 1099 1100
      mask <<= 1;
      st.wDay++;
  }

  /* draw next month */
1101
  st = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
1102 1103 1104 1105
  st.wDay = 1;
  MONTHCAL_GetNextMonth(&st);
  MONTHCAL_GetMaxDate(infoPtr, &st_max);
  mask = 1;
1106
  index = MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)-1;
1107 1108
  while(st.wDay <= st_max.wDay)
  {
1109
      MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[index] & mask, ps);
1110 1111 1112 1113 1114
      mask <<= 1;
      st.wDay++;
  }
}

1115
/* paint a calendar area */
1116
static void MONTHCAL_PaintCalendar(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
1117
{
1118
  const SYSTEMTIME *date = &infoPtr->calendars[calIdx].month;
1119
  INT i, j, length;
1120 1121
  RECT r, fill_bk_rect;
  SYSTEMTIME st;
1122
  WCHAR buf[80];
1123
  HPEN old_pen;
1124
  int mask;
1125

1126 1127 1128 1129
  /* fill whole days area - from week days area to today note rectangle */
  fill_bk_rect = infoPtr->calendars[calIdx].wdays;
  fill_bk_rect.bottom = infoPtr->calendars[calIdx].days.bottom +
                          (infoPtr->todayrect.bottom - infoPtr->todayrect.top);
1130

1131
  FillRect(hdc, &fill_bk_rect, infoPtr->brushes[BrushMonth]);
1132 1133

  /* draw line under day abbreviations */
1134
  old_pen = SelectObject(hdc, infoPtr->pens[PenText]);
1135 1136 1137 1138
  MoveToEx(hdc, infoPtr->calendars[calIdx].days.left + 3,
                infoPtr->calendars[calIdx].title.bottom + infoPtr->textHeight + 1, NULL);
  LineTo(hdc, infoPtr->calendars[calIdx].days.right - 3,
              infoPtr->calendars[calIdx].title.bottom + infoPtr->textHeight + 1);
1139
  SelectObject(hdc, old_pen);
1140

1141 1142
  infoPtr->calendars[calIdx].wdays.left = infoPtr->calendars[calIdx].days.left =
      infoPtr->calendars[calIdx].weeknums.right;
1143

1144
  /* draw day abbreviations */
Duane Clark's avatar
Duane Clark committed
1145
  SelectObject(hdc, infoPtr->hFont);
1146
  SetBkColor(hdc, infoPtr->colors[MCSC_MONTHBK]);
1147
  SetTextColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
1148
  /* rectangle to draw a single day abbreviation within */
1149
  r = infoPtr->calendars[calIdx].wdays;
1150
  r.right = r.left + infoPtr->width_increment;
1151

1152
  i = infoPtr->firstDay;
1153 1154
  for(j = 0; j < 7; j++) {
    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1 + (i+j+6)%7, buf, countof(buf));
1155 1156
    DrawTextW(hdc, buf, strlenW(buf), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    OffsetRect(&r, infoPtr->width_increment, 0);
1157
  }
1158

1159
  /* draw current month */
1160
  SetTextColor(hdc, infoPtr->colors[MCSC_TEXT]);
1161
  st = *date;
1162
  st.wDay = 1;
1163
  mask = 1;
1164 1165
  length = MONTHCAL_MonthLength(date->wMonth, date->wYear);
  while(st.wDay <= length)
1166
  {
1167
    MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[calIdx+1] & mask, ps);
1168
    mask <<= 1;
1169
    st.wDay++;
1170
  }
1171
}
1172

1173 1174 1175 1176
static void MONTHCAL_Refresh(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
{
  COLORREF old_text_clr, old_bk_clr;
  HFONT old_font;
1177
  INT i;
1178

1179 1180 1181 1182
  old_text_clr = SetTextColor(hdc, comctl32_color.clrWindowText);
  old_bk_clr   = GetBkColor(hdc);
  old_font     = GetCurrentObject(hdc, OBJ_FONT);

1183
  for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1184 1185 1186 1187 1188 1189 1190
  {
    RECT *title = &infoPtr->calendars[i].title;
    RECT r;

    /* draw title, redraw all its elements */
    if (IntersectRect(&r, &(ps->rcPaint), title))
        MONTHCAL_PaintTitle(infoPtr, hdc, ps, i);
1191

1192 1193 1194 1195
    /* draw calendar area */
    UnionRect(&r, &infoPtr->calendars[i].wdays, &infoPtr->todayrect);
    if (IntersectRect(&r, &(ps->rcPaint), &r))
        MONTHCAL_PaintCalendar(infoPtr, hdc, ps, i);
1196

1197 1198 1199 1200
    /* week numbers */
    MONTHCAL_PaintWeeknumbers(infoPtr, hdc, ps, i);
  }

1201 1202 1203
  /* partially visible months */
  MONTHCAL_PaintLeadTrailMonths(infoPtr, hdc, ps);

1204 1205 1206 1207 1208 1209 1210
  /* focus and today rectangle */
  MONTHCAL_PaintFocusAndCircle(infoPtr, hdc, ps);

  /* today at the bottom left */
  MONTHCAL_PaintTodayTitle(infoPtr, hdc, ps);

  /* navigation buttons */
1211 1212
  MONTHCAL_PaintButton(infoPtr, hdc, DIRECTION_BACKWARD);
  MONTHCAL_PaintButton(infoPtr, hdc, DIRECTION_FORWARD);
1213

1214 1215 1216 1217
  /* restore context */
  SetBkColor(hdc, old_bk_clr);
  SelectObject(hdc, old_font);
  SetTextColor(hdc, old_text_clr);
1218 1219
}

1220
static LRESULT
1221
MONTHCAL_GetMinReqRect(const MONTHCAL_INFO *infoPtr, RECT *rect)
1222
{
1223
  TRACE("rect %p\n", rect);
1224

1225
  if(!rect) return FALSE;
1226

1227 1228 1229
  *rect = infoPtr->calendars[0].title;
  rect->bottom = infoPtr->calendars[0].days.bottom + infoPtr->todayrect.bottom -
                 infoPtr->todayrect.top;
1230

1231
  AdjustWindowRect(rect, infoPtr->dwStyle, FALSE);
1232

1233
  /* minimal rectangle is zero based */
1234
  OffsetRect(rect, -rect->left, -rect->top);
1235

1236
  TRACE("%s\n", wine_dbgstr_rect(rect));
1237

1238
  return TRUE;
1239 1240
}

1241 1242
static COLORREF
MONTHCAL_GetColor(const MONTHCAL_INFO *infoPtr, UINT index)
1243
{
1244
  TRACE("%p, %d\n", infoPtr, index);
1245

1246 1247
  if (index > MCSC_TRAILINGTEXT) return -1;
  return infoPtr->colors[index];
1248 1249
}

1250
static LRESULT
1251 1252
MONTHCAL_SetColor(MONTHCAL_INFO *infoPtr, UINT index, COLORREF color)
{
1253
  enum CachedBrush type;
1254 1255 1256 1257 1258 1259 1260 1261
  COLORREF prev;

  TRACE("%p, %d: color %08x\n", infoPtr, index, color);

  if (index > MCSC_TRAILINGTEXT) return -1;

  prev = infoPtr->colors[index];
  infoPtr->colors[index] = color;
1262

1263
  /* update cached brush */
1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279
  switch (index)
  {
  case MCSC_BACKGROUND:
    type = BrushBackground;
    break;
  case MCSC_TITLEBK:
    type = BrushTitle;
    break;
  case MCSC_MONTHBK:
    type = BrushMonth;
    break;
  default:
    type = BrushLast;
  }

  if (type != BrushLast)
1280
  {
1281 1282
    DeleteObject(infoPtr->brushes[type]);
    infoPtr->brushes[type] = CreateSolidBrush(color);
1283 1284
  }

1285 1286 1287 1288 1289 1290 1291
  /* update cached pen */
  if (index == MCSC_TEXT)
  {
    DeleteObject(infoPtr->pens[PenText]);
    infoPtr->pens[PenText] = CreatePen(PS_SOLID, 1, infoPtr->colors[index]);
  }

1292
  InvalidateRect(infoPtr->hwndSelf, NULL, index == MCSC_BACKGROUND);
1293
  return prev;
1294 1295
}

1296
static LRESULT
1297
MONTHCAL_GetMonthDelta(const MONTHCAL_INFO *infoPtr)
1298
{
1299
  TRACE("\n");
1300

1301
  if(infoPtr->delta)
1302 1303 1304
    return infoPtr->delta;
  else
    return infoPtr->visible;
1305 1306
}

1307

1308
static LRESULT
1309
MONTHCAL_SetMonthDelta(MONTHCAL_INFO *infoPtr, INT delta)
1310
{
1311
  INT prev = infoPtr->delta;
1312

1313
  TRACE("delta %d\n", delta);
1314

1315
  infoPtr->delta = delta;
1316
  return prev;
1317 1318 1319
}


1320
static inline LRESULT
1321
MONTHCAL_GetFirstDayOfWeek(const MONTHCAL_INFO *infoPtr)
1322
{
1323 1324 1325 1326 1327 1328
  int day;

  /* convert from SYSTEMTIME to locale format */
  day = (infoPtr->firstDay >= 0) ? (infoPtr->firstDay+6)%7 : infoPtr->firstDay;

  return MAKELONG(day, infoPtr->firstDaySet);
1329 1330 1331
}


1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342
/* Sets the first day of the week that will appear in the control
 *
 *
 * PARAMETERS:
 *  [I] infoPtr : valid pointer to control data
 *  [I] day : day number to set as new first day (0 == Monday,...,6 == Sunday)
 *
 *
 * RETURN VALUE:
 *  Low word contains previous first day,
 *  high word indicates was first day forced with this message before or is
1343
 *  locale defined (TRUE - was forced, FALSE - wasn't).
1344 1345 1346 1347
 *
 * FIXME: this needs to be implemented properly in MONTHCAL_Refresh()
 * FIXME: we need more error checking here
 */
1348
static LRESULT
1349
MONTHCAL_SetFirstDayOfWeek(MONTHCAL_INFO *infoPtr, INT day)
1350
{
1351 1352
  LRESULT prev = MONTHCAL_GetFirstDayOfWeek(infoPtr);
  int new_day;
1353

1354
  TRACE("%d\n", day);
1355

1356
  if(day == -1)
1357
  {
1358 1359 1360 1361 1362
    WCHAR buf[80];

    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, buf, countof(buf));
    TRACE("%s %d\n", debugstr_w(buf), strlenW(buf));

1363
    new_day = atoiW(buf);
1364

1365
    infoPtr->firstDaySet = FALSE;
1366
  }
1367
  else if(day >= 7)
1368
  {
1369 1370
    new_day = 6; /* max first day allowed */
    infoPtr->firstDaySet = TRUE;
1371
  }
1372
  else
1373
  {
1374 1375
    /* Native behaviour for that case is broken: invalid date number >31
       got displayed at (0,0) position, current month starts always from
1376 1377
       (1,0) position. Should be implemented here as well only if there's
       nothing else to do. */
1378 1379 1380 1381 1382
    if (day < -1)
      FIXME("No bug compatibility for day=%d\n", day);

    new_day = day;
    infoPtr->firstDaySet = TRUE;
1383
  }
1384

1385 1386 1387
  /* convert from locale to SYSTEMTIME format */
  infoPtr->firstDay = (new_day >= 0) ? (++new_day) % 7 : new_day;

1388 1389
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);

1390
  return prev;
1391 1392 1393
}

static LRESULT
1394
MONTHCAL_GetMaxTodayWidth(const MONTHCAL_INFO *infoPtr)
1395
{
1396
  return(infoPtr->todayrect.right - infoPtr->todayrect.left);
1397 1398 1399
}

static LRESULT
1400
MONTHCAL_SetRange(MONTHCAL_INFO *infoPtr, SHORT limits, SYSTEMTIME *range)
1401
{
1402
    FILETIME ft_min, ft_max;
1403

1404
    TRACE("%x %p\n", limits, range);
1405

1406 1407
    if ((limits & GDTR_MIN && !MONTHCAL_ValidateDate(&range[0])) ||
        (limits & GDTR_MAX && !MONTHCAL_ValidateDate(&range[1])))
1408 1409
        return FALSE;

1410
    if (limits & GDTR_MIN)
1411
    {
1412 1413 1414
        if (!MONTHCAL_ValidateTime(&range[0]))
            MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]);

1415
        infoPtr->minDate = range[0];
1416
        infoPtr->rangeValid |= GDTR_MIN;
1417
    }
1418
    if (limits & GDTR_MAX)
1419
    {
1420 1421 1422
        if (!MONTHCAL_ValidateTime(&range[1]))
            MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]);

1423
        infoPtr->maxDate = range[1];
1424
        infoPtr->rangeValid |= GDTR_MAX;
1425 1426
    }

1427 1428 1429
    /* Only one limit set - we are done */
    if ((infoPtr->rangeValid & (GDTR_MIN | GDTR_MAX)) != (GDTR_MIN | GDTR_MAX))
        return TRUE;
1430

1431 1432 1433
    SystemTimeToFileTime(&infoPtr->maxDate, &ft_max);
    SystemTimeToFileTime(&infoPtr->minDate, &ft_min);

1434
    if (CompareFileTime(&ft_min, &ft_max) >= 0)
1435
    {
1436
        if ((limits & (GDTR_MIN | GDTR_MAX)) == (GDTR_MIN | GDTR_MAX))
1437 1438 1439 1440 1441 1442 1443 1444
        {
            /* Native swaps limits only when both limits are being set. */
            SYSTEMTIME st_tmp = infoPtr->minDate;
            infoPtr->minDate  = infoPtr->maxDate;
            infoPtr->maxDate  = st_tmp;
        }
        else
        {
1445
            /* reset the other limit */
1446 1447 1448
            if (limits & GDTR_MIN) infoPtr->maxDate = st_null;
            if (limits & GDTR_MAX) infoPtr->minDate = st_null;
            infoPtr->rangeValid &= limits & GDTR_MIN ? ~GDTR_MAX : ~GDTR_MIN;
1449 1450 1451
        }
    }

1452
    return TRUE;
1453 1454
}

1455

1456
static LRESULT
1457
MONTHCAL_GetRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1458
{
1459
  TRACE("%p\n", range);
1460

1461
  if(!range) return FALSE;
1462

1463 1464
  range[1] = infoPtr->maxDate;
  range[0] = infoPtr->minDate;
1465 1466 1467 1468

  return infoPtr->rangeValid;
}

1469

1470
static LRESULT
1471
MONTHCAL_SetDayState(const MONTHCAL_INFO *infoPtr, INT months, MONTHDAYSTATE *states)
1472
{
1473
  TRACE("%p %d %p\n", infoPtr, months, states);
1474 1475 1476

  if (!(infoPtr->dwStyle & MCS_DAYSTATE)) return 0;
  if (months != MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)) return 0;
1477

1478
  memcpy(infoPtr->monthdayState, states, months*sizeof(MONTHDAYSTATE));
1479 1480 1481 1482

  return 1;
}

1483
static LRESULT
1484
MONTHCAL_GetCurSel(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel)
1485
{
1486 1487
  TRACE("%p\n", curSel);
  if(!curSel) return FALSE;
1488
  if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
1489

1490
  *curSel = infoPtr->minSel;
1491
  TRACE("%d/%d/%d\n", curSel->wYear, curSel->wMonth, curSel->wDay);
1492 1493 1494
  return TRUE;
}

1495
static LRESULT
1496
MONTHCAL_SetCurSel(MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel)
1497
{
1498
  SYSTEMTIME prev = infoPtr->minSel, selection;
1499
  INT diff;
1500
  WORD day;
1501

1502 1503
  TRACE("%p\n", curSel);
  if(!curSel) return FALSE;
1504
  if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
1505

1506
  if(!MONTHCAL_ValidateDate(curSel)) return FALSE;
1507
  /* exit earlier if selection equals current */
1508
  if (MONTHCAL_IsDateEqual(&infoPtr->minSel, curSel)) return TRUE;
1509

1510 1511 1512 1513 1514
  selection = *curSel;
  selection.wHour = selection.wMinute = selection.wSecond = selection.wMilliseconds = 0;
  MONTHCAL_CalculateDayOfWeek(&selection, TRUE);

  if(!MONTHCAL_IsDateInValidRange(infoPtr, &selection, FALSE)) return FALSE;
1515

1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531
  /* scroll calendars only if we have to */
  diff = MONTHCAL_MonthDiff(&infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month, curSel);
  if (diff <= 0)
  {
    diff = MONTHCAL_MonthDiff(&infoPtr->calendars[0].month, curSel);
    if (diff > 0) diff = 0;
  }

  if (diff != 0)
  {
    INT i;

    for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
      MONTHCAL_GetMonth(&infoPtr->calendars[i].month, diff);
  }

1532
  /* we need to store time part as it is */
1533 1534 1535
  selection = *curSel;
  MONTHCAL_CalculateDayOfWeek(&selection, TRUE);
  infoPtr->minSel = infoPtr->maxSel = selection;
1536

1537
  /* if selection is still in current month, reduce rectangle */
1538
  day = prev.wDay;
1539 1540 1541 1542
  prev.wDay = curSel->wDay;
  if (MONTHCAL_IsDateEqual(&prev, curSel))
  {
    RECT r_prev, r_new;
1543

1544
    prev.wDay = day;
1545 1546
    MONTHCAL_GetDayRect(infoPtr, &prev, &r_prev, -1);
    MONTHCAL_GetDayRect(infoPtr, curSel, &r_new, -1);
1547 1548 1549 1550 1551 1552 1553

    InvalidateRect(infoPtr->hwndSelf, &r_prev, FALSE);
    InvalidateRect(infoPtr->hwndSelf, &r_new,  FALSE);
  }
  else
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);

1554 1555 1556
  return TRUE;
}

1557

1558
static LRESULT
1559
MONTHCAL_GetMaxSelCount(const MONTHCAL_INFO *infoPtr)
1560 1561 1562 1563
{
  return infoPtr->maxSelCount;
}

1564

1565
static LRESULT
1566
MONTHCAL_SetMaxSelCount(MONTHCAL_INFO *infoPtr, INT max)
1567
{
1568
  TRACE("%d\n", max);
1569

1570 1571 1572 1573
  if(!(infoPtr->dwStyle & MCS_MULTISELECT)) return FALSE;
  if(max <= 0) return FALSE;

  infoPtr->maxSelCount = max;
1574 1575 1576 1577 1578

  return TRUE;
}


1579
static LRESULT
1580
MONTHCAL_GetSelRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1581
{
1582
  TRACE("%p\n", range);
1583

1584
  if(!range) return FALSE;
1585

1586
  if(infoPtr->dwStyle & MCS_MULTISELECT)
1587
  {
1588 1589
    range[1] = infoPtr->maxSel;
    range[0] = infoPtr->minSel;
1590
    TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
1591
    return TRUE;
1592
  }
1593

1594 1595 1596
  return FALSE;
}

1597

1598
static LRESULT
1599
MONTHCAL_SetSelRange(MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1600
{
1601
  SYSTEMTIME old_range[2];
1602
  INT diff;
1603

1604
  TRACE("%p\n", range);
1605

1606
  if(!range || !(infoPtr->dwStyle & MCS_MULTISELECT)) return FALSE;
1607

1608 1609 1610
  /* adjust timestamps */
  if(!MONTHCAL_ValidateTime(&range[0])) MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]);
  if(!MONTHCAL_ValidateTime(&range[1])) MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]);
1611

1612 1613
  /* maximum range exceeded */
  if(!MONTHCAL_IsSelRangeValid(infoPtr, &range[0], &range[1], NULL)) return FALSE;
1614

1615 1616
  old_range[0] = infoPtr->minSel;
  old_range[1] = infoPtr->maxSel;
1617

1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628
  /* swap if min > max */
  if(MONTHCAL_CompareSystemTime(&range[0], &range[1]) <= 0)
  {
    infoPtr->minSel = range[0];
    infoPtr->maxSel = range[1];
  }
  else
  {
    infoPtr->minSel = range[1];
    infoPtr->maxSel = range[0];
  }
1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643

  diff = MONTHCAL_MonthDiff(&infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month, &infoPtr->maxSel);
  if (diff < 0)
  {
    diff = MONTHCAL_MonthDiff(&infoPtr->calendars[0].month, &infoPtr->maxSel);
    if (diff > 0) diff = 0;
  }

  if (diff != 0)
  {
    INT i;

    for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
      MONTHCAL_GetMonth(&infoPtr->calendars[i].month, diff);
  }
1644

1645 1646 1647
  /* update day of week */
  MONTHCAL_CalculateDayOfWeek(&infoPtr->minSel, TRUE);
  MONTHCAL_CalculateDayOfWeek(&infoPtr->maxSel, TRUE);
1648

1649 1650 1651 1652 1653 1654
  /* redraw if bounds changed */
  /* FIXME: no actual need to redraw everything */
  if(!MONTHCAL_IsDateEqual(&old_range[0], &range[0]) ||
     !MONTHCAL_IsDateEqual(&old_range[1], &range[1]))
  {
     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1655
  }
1656

1657 1658
  TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
  return TRUE;
1659 1660 1661
}


1662
static LRESULT
1663
MONTHCAL_GetToday(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *today)
1664
{
1665
  TRACE("%p\n", today);
1666

1667
  if(!today) return FALSE;
1668
  *today = infoPtr->todaysDate;
1669 1670 1671
  return TRUE;
}

1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684
/* Internal helper for MCM_SETTODAY handler and auto update timer handler
 *
 * RETURN VALUE
 *
 *  TRUE  - today date changed
 *  FALSE - today date isn't changed
 */
static BOOL
MONTHCAL_UpdateToday(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *today)
{
  RECT new_r, old_r;

  if(MONTHCAL_IsDateEqual(today, &infoPtr->todaysDate)) return FALSE;
1685

1686 1687
  MONTHCAL_GetDayRect(infoPtr, &infoPtr->todaysDate, &old_r, -1);
  MONTHCAL_GetDayRect(infoPtr, today, &new_r, -1);
1688 1689 1690 1691 1692 1693

  infoPtr->todaysDate = *today;

  /* only two days need redrawing */
  InvalidateRect(infoPtr->hwndSelf, &old_r, FALSE);
  InvalidateRect(infoPtr->hwndSelf, &new_r, FALSE);
1694 1695
  /* and today label */
  InvalidateRect(infoPtr->hwndSelf, &infoPtr->todayrect, FALSE);
1696 1697 1698 1699
  return TRUE;
}

/* MCM_SETTODAT handler */
1700
static LRESULT
1701
MONTHCAL_SetToday(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *today)
1702
{
1703
  TRACE("%p\n", today);
1704

1705 1706 1707 1708 1709
  if (today)
  {
    /* remember if date was set successfully */
    if (MONTHCAL_UpdateToday(infoPtr, today)) infoPtr->todaySet = TRUE;
  }
1710

1711
  return 0;
1712 1713
}

1714 1715 1716 1717 1718 1719
/* returns calendar index containing specified point, or -1 if it's background */
static INT MONTHCAL_GetCalendarFromPoint(const MONTHCAL_INFO *infoPtr, const POINT *pt)
{
  RECT r;
  INT i;

1720
  for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733
  {
     /* whole bounding rectangle allows some optimization to compute */
     r.left   = infoPtr->calendars[i].title.left;
     r.top    = infoPtr->calendars[i].title.top;
     r.bottom = infoPtr->calendars[i].days.bottom;
     r.right  = infoPtr->calendars[i].days.right;

     if (PtInRect(&r, *pt)) return i;
  }

  return -1;
}

1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744
static inline UINT fill_hittest_info(const MCHITTESTINFO *src, MCHITTESTINFO *dest)
{
  dest->uHit = src->uHit;
  dest->st = src->st;

  if (dest->cbSize == sizeof(MCHITTESTINFO))
    memcpy(&dest->rc, &src->rc, sizeof(MCHITTESTINFO) - MCHITTESTINFO_V1_SIZE);

  return src->uHit;
}

1745
static LRESULT
1746
MONTHCAL_HitTest(const MONTHCAL_INFO *infoPtr, MCHITTESTINFO *lpht)
1747
{
1748
  MCHITTESTINFO htinfo;
1749
  SYSTEMTIME *ht_month;
1750
  INT day, calIdx;
1751

1752
  if(!lpht || lpht->cbSize < MCHITTESTINFO_V1_SIZE) return -1;
1753

1754 1755 1756 1757 1758
  htinfo.st = st_null;

  /* we should preserve passed fields if hit area doesn't need them */
  if (lpht->cbSize == sizeof(MCHITTESTINFO))
    memcpy(&htinfo.rc, &lpht->rc, sizeof(MCHITTESTINFO) - MCHITTESTINFO_V1_SIZE);
1759 1760 1761

  /* 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,
1762 1763 1764 1765 1766 1767 1768 1769 1770
	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);
  */
1771

1772 1773
  /* guess in what calendar we are */
  calIdx = MONTHCAL_GetCalendarFromPoint(infoPtr, &lpht->pt);
1774 1775 1776
  if (calIdx == -1)
  {
    if (PtInRect(&infoPtr->todayrect, lpht->pt))
1777 1778 1779 1780
    {
      htinfo.uHit = MCHT_TODAYLINK;
      htinfo.rc = infoPtr->todayrect;
    }
1781 1782
    else
      /* outside of calendar area? What's left must be background :-) */
1783
      htinfo.uHit = MCHT_CALENDARBK;
1784

1785
    return fill_hittest_info(&htinfo, lpht);
1786
  }
1787

1788
  /* are we in the header? */
1789
  if (PtInRect(&infoPtr->calendars[calIdx].title, lpht->pt)) {
1790 1791
    /* FIXME: buttons hittesting could be optimized cause maximum
              two calendars have buttons */
1792 1793
    if (calIdx == 0 && PtInRect(&infoPtr->titlebtnprev, lpht->pt))
    {
1794 1795
      htinfo.uHit = MCHT_TITLEBTNPREV;
      htinfo.rc = infoPtr->titlebtnprev;
1796
    }
1797 1798
    else if (PtInRect(&infoPtr->titlebtnnext, lpht->pt))
    {
1799 1800
      htinfo.uHit = MCHT_TITLEBTNNEXT;
      htinfo.rc = infoPtr->titlebtnnext;
1801
    }
1802 1803
    else if (PtInRect(&infoPtr->calendars[calIdx].titlemonth, lpht->pt))
    {
1804 1805 1806
      htinfo.uHit = MCHT_TITLEMONTH;
      htinfo.rc = infoPtr->calendars[calIdx].titlemonth;
      htinfo.iOffset = calIdx;
1807
    }
1808 1809
    else if (PtInRect(&infoPtr->calendars[calIdx].titleyear, lpht->pt))
    {
1810 1811 1812
      htinfo.uHit = MCHT_TITLEYEAR;
      htinfo.rc = infoPtr->calendars[calIdx].titleyear;
      htinfo.iOffset = calIdx;
1813
    }
1814
    else
1815 1816 1817 1818 1819
    {
      htinfo.uHit = MCHT_TITLE;
      htinfo.rc = infoPtr->calendars[calIdx].title;
      htinfo.iOffset = calIdx;
    }
1820

1821
    return fill_hittest_info(&htinfo, lpht);
1822
  }
1823

1824 1825
  ht_month = &infoPtr->calendars[calIdx].month;
  /* days area (including week days and week numbers) */
1826
  day = MONTHCAL_GetDayFromPos(infoPtr, lpht->pt, calIdx);
1827 1828
  if (PtInRect(&infoPtr->calendars[calIdx].wdays, lpht->pt))
  {
1829 1830
    htinfo.uHit = MCHT_CALENDARDAY;
    htinfo.iOffset = calIdx;
1831 1832
    htinfo.st.wYear  = ht_month->wYear;
    htinfo.st.wMonth = (day < 1) ? ht_month->wMonth -1 : ht_month->wMonth;
1833
    htinfo.st.wDay   = (day < 1) ?
1834
      MONTHCAL_MonthLength(ht_month->wMonth-1, ht_month->wYear) - day : day;
1835

1836
    MONTHCAL_GetDayPos(infoPtr, &htinfo.st, &htinfo.iCol, &htinfo.iRow, calIdx);
1837
  }
1838 1839
  else if(PtInRect(&infoPtr->calendars[calIdx].weeknums, lpht->pt))
  {
1840
    htinfo.uHit = MCHT_CALENDARWEEKNUM;
1841
    htinfo.st.wYear = ht_month->wYear;
1842
    htinfo.iOffset = calIdx;
1843

1844 1845
    if (day < 1)
    {
1846 1847
      htinfo.st.wMonth = ht_month->wMonth - 1;
      htinfo.st.wDay = MONTHCAL_MonthLength(ht_month->wMonth-1, ht_month->wYear) - day;
1848
    }
1849
    else if (day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear))
1850
    {
1851 1852
      htinfo.st.wMonth = ht_month->wMonth + 1;
      htinfo.st.wDay = day - MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear);
1853 1854
    }
    else
1855
    {
1856
      htinfo.st.wMonth = ht_month->wMonth;
1857
      htinfo.st.wDay = day;
1858
    }
1859
  }
1860
  else if(PtInRect(&infoPtr->calendars[calIdx].days, lpht->pt))
1861
  {
1862
      htinfo.iOffset = calIdx;
1863 1864 1865 1866
      htinfo.st.wYear  = ht_month->wYear;
      htinfo.st.wMonth = ht_month->wMonth;
      /* previous month only valid for first calendar */
      if (day < 1 && calIdx == 0)
1867
      {
1868 1869
	  htinfo.uHit = MCHT_CALENDARDATEPREV;
	  MONTHCAL_GetPrevMonth(&htinfo.st);
1870
	  htinfo.st.wDay = MONTHCAL_MonthLength(htinfo.st.wMonth, htinfo.st.wYear) + day;
1871
      }
1872 1873 1874
      /* next month only valid for last calendar */
      else if (day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear) &&
               calIdx == MONTHCAL_GetCalCount(infoPtr)-1)
1875
      {
1876 1877
	  htinfo.uHit = MCHT_CALENDARDATENEXT;
	  MONTHCAL_GetNextMonth(&htinfo.st);
1878 1879 1880 1881 1882 1883
	  htinfo.st.wDay = day - MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear);
      }
      /* multiple calendars case - blank areas for previous/next month */
      else if (day < 1 || day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear))
      {
          htinfo.uHit = MCHT_CALENDARBK;
1884
      }
1885 1886 1887 1888
      else
      {
	htinfo.uHit = MCHT_CALENDARDATE;
	htinfo.st.wDay = day;
1889
      }
1890

1891 1892
      MONTHCAL_GetDayPos(infoPtr, &htinfo.st, &htinfo.iCol, &htinfo.iRow, calIdx);
      MONTHCAL_GetDayRectI(infoPtr, &htinfo.rc, htinfo.iCol, htinfo.iRow, calIdx);
1893
      /* always update day of week */
1894
      MONTHCAL_CalculateDayOfWeek(&htinfo.st, TRUE);
1895
  }
1896

1897
  return fill_hittest_info(&htinfo, lpht);
1898 1899
}

1900 1901
/* MCN_GETDAYSTATE notification helper */
static void MONTHCAL_NotifyDayState(MONTHCAL_INFO *infoPtr)
1902
{
1903 1904
  MONTHDAYSTATE *state;
  NMDAYSTATE nmds;
1905

1906
  if (!(infoPtr->dwStyle & MCS_DAYSTATE)) return;
1907

1908 1909 1910 1911 1912
  nmds.nmhdr.hwndFrom = infoPtr->hwndSelf;
  nmds.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
  nmds.nmhdr.code     = MCN_GETDAYSTATE;
  nmds.cDayState      = MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0);
  nmds.prgDayState    = state = Alloc(nmds.cDayState * sizeof(MONTHDAYSTATE));
1913

1914 1915
  MONTHCAL_GetMinDate(infoPtr, &nmds.stStart);
  nmds.stStart.wDay = 1;
1916

1917 1918 1919 1920 1921
  SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmds.nmhdr.idFrom, (LPARAM)&nmds);
  memcpy(infoPtr->monthdayState, nmds.prgDayState,
      MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)*sizeof(MONTHDAYSTATE));

  Free(state);
1922 1923
}

1924 1925 1926 1927 1928
/* no valid range check performed */
static void MONTHCAL_Scroll(MONTHCAL_INFO *infoPtr, INT delta)
{
  INT i, selIdx = -1;

1929
  for(i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956
  {
    /* save selection position to shift it later */
    if (selIdx == -1 && MONTHCAL_CompareMonths(&infoPtr->minSel, &infoPtr->calendars[i].month) == 0)
      selIdx = i;

    MONTHCAL_GetMonth(&infoPtr->calendars[i].month, delta);
  }

  /* selection is always shifted to first calendar */
  if(infoPtr->dwStyle & MCS_MULTISELECT)
  {
    SYSTEMTIME range[2];

    MONTHCAL_GetSelRange(infoPtr, range);
    MONTHCAL_GetMonth(&range[0], delta - selIdx);
    MONTHCAL_GetMonth(&range[1], delta - selIdx);
    MONTHCAL_SetSelRange(infoPtr, range);
  }
  else
  {
    SYSTEMTIME st = infoPtr->minSel;

    MONTHCAL_GetMonth(&st, delta - selIdx);
    MONTHCAL_SetCurSel(infoPtr, &st);
  }
}

1957
static void MONTHCAL_GoToMonth(MONTHCAL_INFO *infoPtr, enum nav_direction direction)
1958
{
1959
  INT delta = infoPtr->delta ? infoPtr->delta : MONTHCAL_GetCalCount(infoPtr);
1960
  SYSTEMTIME st;
1961

1962
  TRACE("%s\n", direction == DIRECTION_BACKWARD ? "back" : "fwd");
1963

1964 1965 1966 1967 1968 1969 1970 1971
  /* check if change allowed by range set */
  if(direction == DIRECTION_BACKWARD)
  {
    st = infoPtr->calendars[0].month;
    MONTHCAL_GetMonth(&st, -delta);
  }
  else
  {
1972
    st = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
1973 1974
    MONTHCAL_GetMonth(&st, delta);
  }
1975

1976
  if(!MONTHCAL_IsDateInValidRange(infoPtr, &st, FALSE)) return;
1977

1978
  MONTHCAL_Scroll(infoPtr, direction == DIRECTION_BACKWARD ? -delta : delta);
1979
  MONTHCAL_NotifyDayState(infoPtr);
1980
  MONTHCAL_NotifySelectionChange(infoPtr);
1981 1982
}

1983
static LRESULT
1984
MONTHCAL_RButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
1985
{
1986
  static const WCHAR todayW[] = { 'G','o',' ','t','o',' ','T','o','d','a','y',':',0 };
1987 1988
  HMENU hMenu;
  POINT menupoint;
1989
  WCHAR buf[32];
1990

1991
  hMenu = CreatePopupMenu();
1992 1993
  if (!LoadStringW(COMCTL32_hModule, IDM_GOTODAY, buf, countof(buf)))
  {
1994
      WARN("Can't load resource\n");
1995
      strcpyW(buf, todayW);
1996 1997 1998 1999
  }
  AppendMenuW(hMenu, MF_STRING|MF_ENABLED, 1, buf);
  menupoint.x = (short)LOWORD(lParam);
  menupoint.y = (short)HIWORD(lParam);
2000
  ClientToScreen(infoPtr->hwndSelf, &menupoint);
2001
  if( TrackPopupMenu(hMenu, TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD,
2002
		     menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL))
2003
  {
2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015
      if (infoPtr->dwStyle & MCS_MULTISELECT)
      {
          SYSTEMTIME range[2];

          range[0] = range[1] = infoPtr->todaysDate;
          MONTHCAL_SetSelRange(infoPtr, range);
      }
      else
          MONTHCAL_SetCurSel(infoPtr, &infoPtr->todaysDate);

      MONTHCAL_NotifySelectionChange(infoPtr);
      MONTHCAL_NotifySelect(infoPtr);
2016 2017
  }

2018 2019
  return 0;
}
2020

2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067
/***
 * DESCRIPTION:
 * Subclassed edit control windproc function
 *
 * PARAMETER(S):
 * [I] hwnd : the edit window handle
 * [I] uMsg : the message that is to be processed
 * [I] wParam : first message parameter
 * [I] lParam : second message parameter
 *
 */
static LRESULT CALLBACK EditWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    MONTHCAL_INFO *infoPtr = (MONTHCAL_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);

    TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx)\n",
	  hwnd, uMsg, wParam, lParam);

    switch (uMsg)
    {
	case WM_GETDLGCODE:
	  return DLGC_WANTARROWS | DLGC_WANTALLKEYS;

	case WM_DESTROY:
	{
	    WNDPROC editProc = infoPtr->EditWndProc;
	    infoPtr->EditWndProc = NULL;
	    SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
	    return CallWindowProcW(editProc, hwnd, uMsg, wParam, lParam);
	}

	case WM_KILLFOCUS:
	    break;

	case WM_KEYDOWN:
	    if ((VK_ESCAPE == (INT)wParam) || (VK_RETURN == (INT)wParam))
		break;

	default:
	    return CallWindowProcW(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam);
    }

    SendMessageW(infoPtr->hWndYearUpDown, WM_CLOSE, 0, 0);
    SendMessageW(hwnd, WM_CLOSE, 0, 0);
    return 0;
}

2068
/* creates updown control and edit box */
2069
static void MONTHCAL_EditYear(MONTHCAL_INFO *infoPtr, INT calIdx)
2070
{
2071 2072 2073
    RECT *rc = &infoPtr->calendars[calIdx].titleyear;
    RECT *title = &infoPtr->calendars[calIdx].title;

2074
    infoPtr->hWndYearEdit =
2075
	CreateWindowExW(0, WC_EDITW, 0, WS_VISIBLE | WS_CHILD | ES_READONLY,
2076 2077
			rc->left + 3, (title->bottom + title->top - infoPtr->textHeight) / 2,
			rc->right - rc->left + 4,
2078 2079 2080 2081 2082 2083 2084 2085
			infoPtr->textHeight, infoPtr->hwndSelf,
			NULL, NULL, NULL);

    SendMessageW(infoPtr->hWndYearEdit, WM_SETFONT, (WPARAM)infoPtr->hBoldFont, TRUE);

    infoPtr->hWndYearUpDown =
	CreateWindowExW(0, UPDOWN_CLASSW, 0,
			WS_VISIBLE | WS_CHILD | UDS_SETBUDDYINT | UDS_NOTHOUSANDS | UDS_ARROWKEYS,
2086
			rc->right + 7, (title->bottom + title->top - infoPtr->textHeight) / 2,
2087 2088 2089 2090
			18, infoPtr->textHeight, infoPtr->hwndSelf,
			NULL, NULL, NULL);

    /* attach edit box */
2091 2092
    SendMessageW(infoPtr->hWndYearUpDown, UDM_SETRANGE, 0,
                 MAKELONG(max_allowed_date.wYear, min_allowed_date.wYear));
2093
    SendMessageW(infoPtr->hWndYearUpDown, UDM_SETBUDDY, (WPARAM)infoPtr->hWndYearEdit, 0);
2094
    SendMessageW(infoPtr->hWndYearUpDown, UDM_SETPOS, 0, infoPtr->calendars[calIdx].month.wYear);
2095 2096 2097 2098 2099 2100

    /* subclass edit box */
    infoPtr->EditWndProc = (WNDPROC)SetWindowLongPtrW(infoPtr->hWndYearEdit,
                                  GWLP_WNDPROC, (DWORD_PTR)EditWndProc);

    SetFocus(infoPtr->hWndYearEdit);
2101 2102
}

2103
static LRESULT
2104
MONTHCAL_LButtonDown(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2105
{
2106 2107
  MCHITTESTINFO ht;
  DWORD hit;
2108

2109 2110 2111
  /* Actually we don't need input focus for calendar, this is used to kill
     year updown and its buddy edit box */
  if (IsWindow(infoPtr->hWndYearUpDown))
2112
  {
2113 2114
      SetFocus(infoPtr->hwndSelf);
      return 0;
2115
  }
2116

2117 2118
  SetCapture(infoPtr->hwndSelf);

2119
  ht.cbSize = sizeof(MCHITTESTINFO);
2120 2121
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
2122

2123
  hit = MONTHCAL_HitTest(infoPtr, &ht);
2124

2125 2126
  TRACE("%x at (%d, %d)\n", hit, ht.pt.x, ht.pt.y);

2127 2128 2129
  switch(hit)
  {
  case MCHT_TITLEBTNNEXT:
2130
    MONTHCAL_GoToMonth(infoPtr, DIRECTION_FORWARD);
2131
    infoPtr->status = MC_NEXTPRESSED;
2132
    SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0);
2133
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2134
    return 0;
2135 2136

  case MCHT_TITLEBTNPREV:
2137
    MONTHCAL_GoToMonth(infoPtr, DIRECTION_BACKWARD);
2138
    infoPtr->status = MC_PREVPRESSED;
2139
    SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0);
2140
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2141
    return 0;
2142

2143 2144 2145 2146 2147 2148
  case MCHT_TITLEMONTH:
  {
    HMENU hMenu = CreatePopupMenu();
    WCHAR buf[32];
    POINT menupoint;
    INT i;
2149

2150 2151 2152 2153 2154
    for (i = 0; i < 12; i++)
    {
	GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+i, buf, countof(buf));
	AppendMenuW(hMenu, MF_STRING|MF_ENABLED, i + 1, buf);
    }
2155 2156
    menupoint.x = ht.pt.x;
    menupoint.y = ht.pt.y;
2157
    ClientToScreen(infoPtr->hwndSelf, &menupoint);
2158 2159 2160
    i = TrackPopupMenu(hMenu,TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON | TPM_RETURNCMD,
		       menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL);

2161
    if ((i > 0) && (i < 13) && infoPtr->calendars[ht.iOffset].month.wMonth != i)
2162
    {
2163 2164 2165 2166 2167
        INT delta = i - infoPtr->calendars[ht.iOffset].month.wMonth;
        SYSTEMTIME st;

        /* check if change allowed by range set */
        st = delta < 0 ? infoPtr->calendars[0].month :
2168
                         infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
2169 2170 2171 2172 2173 2174 2175 2176 2177
        MONTHCAL_GetMonth(&st, delta);

        if (MONTHCAL_IsDateInValidRange(infoPtr, &st, FALSE))
        {
            MONTHCAL_Scroll(infoPtr, delta);
            MONTHCAL_NotifyDayState(infoPtr);
            MONTHCAL_NotifySelectionChange(infoPtr);
            InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
        }
2178
    }
2179
    return 0;
2180
  }
2181 2182
  case MCHT_TITLEYEAR:
  {
2183
    MONTHCAL_EditYear(infoPtr, ht.iOffset);
2184
    return 0;
2185
  }
2186 2187
  case MCHT_TODAYLINK:
  {
2188 2189 2190 2191 2192 2193 2194 2195 2196
    if (infoPtr->dwStyle & MCS_MULTISELECT)
    {
        SYSTEMTIME range[2];

        range[0] = range[1] = infoPtr->todaysDate;
        MONTHCAL_SetSelRange(infoPtr, range);
    }
    else
        MONTHCAL_SetCurSel(infoPtr, &infoPtr->todaysDate);
2197

2198 2199
    MONTHCAL_NotifySelectionChange(infoPtr);
    MONTHCAL_NotifySelect(infoPtr);
2200
    return 0;
2201
  }
2202 2203
  case MCHT_CALENDARDATENEXT:
  case MCHT_CALENDARDATEPREV:
2204 2205
  case MCHT_CALENDARDATE:
  {
2206
    SYSTEMTIME st[2];
2207

2208
    MONTHCAL_CopyDate(&ht.st, &infoPtr->firstSel);
2209

2210 2211 2212
    st[0] = st[1] = ht.st;
    /* clear selection range */
    MONTHCAL_SetSelRange(infoPtr, st);
2213

2214
    infoPtr->status = MC_SEL_LBUTDOWN;
2215
    MONTHCAL_SetDayFocus(infoPtr, &ht.st);
2216
    return 0;
2217
  }
2218
  }
2219

2220
  return 1;
2221 2222
}

2223

2224
static LRESULT
2225
MONTHCAL_LButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2226
{
2227
  NMHDR nmhdr;
2228 2229
  MCHITTESTINFO ht;
  DWORD hit;
2230 2231

  TRACE("\n");
2232

2233
  if(infoPtr->status & (MC_PREVPRESSED | MC_NEXTPRESSED)) {
2234 2235
    RECT *r;

2236
    KillTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER);
2237
    r = infoPtr->status & MC_PREVPRESSED ? &infoPtr->titlebtnprev : &infoPtr->titlebtnnext;
2238
    infoPtr->status &= ~(MC_PREVPRESSED | MC_NEXTPRESSED);
2239 2240

    InvalidateRect(infoPtr->hwndSelf, r, FALSE);
2241
  }
2242

2243 2244
  ReleaseCapture();

2245 2246 2247 2248 2249 2250 2251 2252
  /* always send NM_RELEASEDCAPTURE notification */
  nmhdr.hwndFrom = infoPtr->hwndSelf;
  nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
  nmhdr.code     = NM_RELEASEDCAPTURE;
  TRACE("Sent notification from %p to %p\n", infoPtr->hwndSelf, infoPtr->hwndNotify);

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

2253 2254
  if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;

2255
  ht.cbSize = sizeof(MCHITTESTINFO);
2256 2257
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
2258
  hit = MONTHCAL_HitTest(infoPtr, &ht);
2259

2260
  infoPtr->status = MC_SEL_LBUTUP;
2261
  MONTHCAL_SetDayFocus(infoPtr, NULL);
2262

2263
  if((hit & MCHT_CALENDARDATE) == MCHT_CALENDARDATE)
2264
  {
2265
    SYSTEMTIME sel = infoPtr->minSel;
2266

2267 2268
    /* will be invalidated here */
    MONTHCAL_SetCurSel(infoPtr, &ht.st);
2269

2270 2271
    /* send MCN_SELCHANGE only if new date selected */
    if (!MONTHCAL_IsDateEqual(&sel, &ht.st))
2272
        MONTHCAL_NotifySelectionChange(infoPtr);
2273

2274
    MONTHCAL_NotifySelect(infoPtr);
2275
  }
2276

2277
  return 0;
2278 2279
}

2280

2281
static LRESULT
2282
MONTHCAL_Timer(MONTHCAL_INFO *infoPtr, WPARAM id)
2283
{
2284
  TRACE("%ld\n", id);
2285

2286 2287
  switch(id) {
  case MC_PREVNEXTMONTHTIMER:
2288 2289
    if(infoPtr->status & MC_NEXTPRESSED) MONTHCAL_GoToMonth(infoPtr, DIRECTION_FORWARD);
    if(infoPtr->status & MC_PREVPRESSED) MONTHCAL_GoToMonth(infoPtr, DIRECTION_BACKWARD);
2290
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2291
    break;
2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305
  case MC_TODAYUPDATETIMER:
  {
    SYSTEMTIME st;

    if(infoPtr->todaySet) return 0;

    GetLocalTime(&st);
    MONTHCAL_UpdateToday(infoPtr, &st);

    /* notification sent anyway */
    MONTHCAL_NotifySelectionChange(infoPtr);

    return 0;
  }
2306
  default:
2307
    ERR("got unknown timer %ld\n", id);
2308
    break;
2309
  }
2310

2311 2312
  return 0;
}
2313

2314 2315

static LRESULT
2316
MONTHCAL_MouseMove(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2317
{
2318
  MCHITTESTINFO ht;
2319
  SYSTEMTIME st_ht;
2320
  INT hit;
2321
  RECT r;
2322

2323
  if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
2324

2325
  ht.cbSize = sizeof(MCHITTESTINFO);
2326 2327
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
2328
  ht.iOffset = -1;
2329

2330
  hit = MONTHCAL_HitTest(infoPtr, &ht);
2331

2332
  /* not on the calendar date numbers? bail out */
2333
  TRACE("hit:%x\n",hit);
2334 2335 2336 2337 2338
  if((hit & MCHT_CALENDARDATE) != MCHT_CALENDARDATE)
  {
    MONTHCAL_SetDayFocus(infoPtr, NULL);
    return 0;
  }
2339

2340
  st_ht = ht.st;
2341 2342 2343

  /* if pointer is over focused day still there's nothing to do */
  if(!MONTHCAL_SetDayFocus(infoPtr, &ht.st)) return 0;
2344

2345
  MONTHCAL_GetDayRect(infoPtr, &ht.st, &r, ht.iOffset);
2346

2347
  if(infoPtr->dwStyle & MCS_MULTISELECT) {
2348 2349 2350 2351
    SYSTEMTIME st[2];

    MONTHCAL_GetSelRange(infoPtr, st);

2352 2353 2354 2355
    /* If we're still at the first selected date and range is empty, return.
       If range isn't empty we should change range to a single firstSel */
    if(MONTHCAL_IsDateEqual(&infoPtr->firstSel, &st_ht) &&
       MONTHCAL_IsDateEqual(&st[0], &st[1])) goto done;
2356

2357
    MONTHCAL_IsSelRangeValid(infoPtr, &st_ht, &infoPtr->firstSel, &st_ht);
2358

2359 2360 2361
    st[0] = infoPtr->firstSel;
    /* we should overwrite timestamp here */
    MONTHCAL_CopyDate(&st_ht, &st[1]);
2362

2363 2364 2365 2366
    /* bounds will be swapped here if needed */
    MONTHCAL_SetSelRange(infoPtr, st);

    return 0;
2367
  }
2368 2369 2370

done:

2371 2372 2373
  /* FIXME: this should specify a rectangle containing only the days that changed
     using InvalidateRect */
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2374

2375
  return 0;
2376 2377
}

2378

2379
static LRESULT
2380
MONTHCAL_Paint(MONTHCAL_INFO *infoPtr, HDC hdc_paint)
2381
{
2382 2383
  HDC hdc;
  PAINTSTRUCT ps;
2384

2385
  if (hdc_paint)
2386
  {
2387
    GetClientRect(infoPtr->hwndSelf, &ps.rcPaint);
2388
    hdc = hdc_paint;
2389 2390
  }
  else
2391
    hdc = BeginPaint(infoPtr->hwndSelf, &ps);
2392

2393
  MONTHCAL_Refresh(infoPtr, hdc, &ps);
2394
  if (!hdc_paint) EndPaint(infoPtr->hwndSelf, &ps);
2395
  return 0;
2396 2397
}

2398 2399 2400 2401 2402 2403 2404
static LRESULT
MONTHCAL_EraseBkgnd(const MONTHCAL_INFO *infoPtr, HDC hdc)
{
  RECT rc;

  if (!GetClipBox(hdc, &rc)) return FALSE;

2405
  FillRect(hdc, &rc, infoPtr->brushes[BrushBackground]);
2406 2407 2408 2409

  return TRUE;
}

2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426
static LRESULT
MONTHCAL_PrintClient(MONTHCAL_INFO *infoPtr, HDC hdc, DWORD options)
{
  FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);

  if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
      return 0;

  if (options & PRF_ERASEBKGND)
      MONTHCAL_EraseBkgnd(infoPtr, hdc);

  if (options & PRF_CLIENT)
      MONTHCAL_Paint(infoPtr, hdc);

  return 0;
}

2427
static LRESULT
2428
MONTHCAL_SetFocus(const MONTHCAL_INFO *infoPtr)
2429
{
2430
  TRACE("\n");
2431

2432
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2433

2434
  return 0;
2435 2436
}

2437
/* sets the size information */
2438
static void MONTHCAL_UpdateSize(MONTHCAL_INFO *infoPtr)
2439
{
2440
  static const WCHAR O0W[] = { '0','0',0 };
2441
  RECT *title=&infoPtr->calendars[0].title;
2442 2443
  RECT *prev=&infoPtr->titlebtnprev;
  RECT *next=&infoPtr->titlebtnnext;
2444 2445 2446 2447 2448
  RECT *titlemonth=&infoPtr->calendars[0].titlemonth;
  RECT *titleyear=&infoPtr->calendars[0].titleyear;
  RECT *wdays=&infoPtr->calendars[0].wdays;
  RECT *weeknumrect=&infoPtr->calendars[0].weeknums;
  RECT *days=&infoPtr->calendars[0].days;
2449
  RECT *todayrect=&infoPtr->todayrect;
2450 2451 2452

  INT xdiv, dx, dy, i, j, x, y, c_dx, c_dy;
  WCHAR buff[80];
2453
  TEXTMETRICW tm;
2454
  SIZE size, sz;
2455
  RECT client;
2456 2457
  HFONT font;
  HDC hdc;
2458

2459
  GetClientRect(infoPtr->hwndSelf, &client);
2460

2461 2462
  hdc = GetDC(infoPtr->hwndSelf);
  font = SelectObject(hdc, infoPtr->hFont);
2463 2464

  /* get the height and width of each day's text */
2465
  GetTextMetricsW(hdc, &tm);
2466
  infoPtr->textHeight = tm.tmHeight + tm.tmExternalLeading + tm.tmInternalLeading;
2467 2468 2469 2470 2471

  /* find largest abbreviated day name for current locale */
  size.cx = sz.cx = 0;
  for (i = 0; i < 7; i++)
  {
2472
      if(GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1 + i,
2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486
                        buff, countof(buff)))
      {
          GetTextExtentPoint32W(hdc, buff, lstrlenW(buff), &sz);
          if (sz.cx > size.cx) size.cx = sz.cx;
      }
      else /* locale independent fallback on failure */
      {
          static const WCHAR SunW[] = { 'S','u','n',0 };

          GetTextExtentPoint32W(hdc, SunW, lstrlenW(SunW), &size);
          break;
      }
  }

2487 2488
  infoPtr->textWidth = size.cx + 2;

2489
  /* recalculate the height and width increments and offsets */
2490
  GetTextExtentPoint32W(hdc, O0W, 2, &size);
2491

2492 2493 2494 2495
  /* restore the originally selected font */
  SelectObject(hdc, font);
  ReleaseDC(infoPtr->hwndSelf, hdc);

2496
  xdiv = (infoPtr->dwStyle & MCS_WEEKNUMBERS) ? 8 : 7;
2497

2498
  infoPtr->width_increment  = size.cx * 2 + 4;
2499
  infoPtr->height_increment = infoPtr->textHeight;
2500

2501
  /* calculate title area */
2502 2503 2504 2505
  title->top    = 0;
  title->bottom = 3 * infoPtr->height_increment / 2;
  title->left   = 0;
  title->right  = infoPtr->width_increment * xdiv;
2506 2507 2508

  /* set the dimensions of the next and previous buttons and center */
  /* the month text vertically */
2509 2510 2511
  prev->top    = next->top    = title->top + 4;
  prev->bottom = next->bottom = title->bottom - 4;
  prev->left   = title->left + 4;
2512
  prev->right  = prev->left + (title->bottom - title->top);
2513
  next->right  = title->right - 4;
2514
  next->left   = next->right - (title->bottom - title->top);
2515

2516 2517 2518
  /* 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 */
2519 2520
  titlemonth->top    = titleyear->top    = title->top    + (infoPtr->height_increment)/2;
  titlemonth->bottom = titleyear->bottom = title->bottom - (infoPtr->height_increment)/2;
2521

2522 2523 2524
  /* week numbers */
  weeknumrect->left  = 0;
  weeknumrect->right = infoPtr->dwStyle & MCS_WEEKNUMBERS ? prev->right : 0;
2525

2526
  /* days abbreviated names */
2527 2528
  wdays->left   = days->left   = weeknumrect->right;
  wdays->right  = days->right  = wdays->left + 7 * infoPtr->width_increment;
2529
  wdays->top    = title->bottom;
2530
  wdays->bottom = wdays->top + infoPtr->height_increment;
2531

2532
  days->top    = weeknumrect->top = wdays->bottom;
2533
  days->bottom = weeknumrect->bottom = days->top + 6 * infoPtr->height_increment;
2534

2535 2536
  todayrect->left   = 0;
  todayrect->right  = title->right;
2537 2538 2539
  todayrect->top    = days->bottom;
  todayrect->bottom = days->bottom + infoPtr->height_increment;

2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552
  /* compute calendar count, update all calendars */
  x = (client.right  + MC_CALENDAR_PADDING) / (title->right - title->left + MC_CALENDAR_PADDING);
  /* today label affects whole height */
  if (infoPtr->dwStyle & MCS_NOTODAY)
    y = (client.bottom + MC_CALENDAR_PADDING) / (days->bottom - title->top + MC_CALENDAR_PADDING);
  else
    y = (client.bottom - todayrect->bottom + todayrect->top + MC_CALENDAR_PADDING) /
         (days->bottom - title->top + MC_CALENDAR_PADDING);

  /* TODO: ensure that count is properly adjusted to fit 12 months constraint */
  if (x == 0) x = 1;
  if (y == 0) y = 1;

2553 2554 2555 2556 2557 2558 2559 2560 2561
  if (x*y != MONTHCAL_GetCalCount(infoPtr))
  {
      infoPtr->dim.cx = x;
      infoPtr->dim.cy = y;
      infoPtr->calendars = ReAlloc(infoPtr->calendars, MONTHCAL_GetCalCount(infoPtr)*sizeof(CALENDAR_INFO));

      infoPtr->monthdayState = ReAlloc(infoPtr->monthdayState,
          MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)*sizeof(MONTHDAYSTATE));
      MONTHCAL_NotifyDayState(infoPtr);
2562 2563 2564 2565 2566

      /* update pointers that we'll need */
      title = &infoPtr->calendars[0].title;
      wdays = &infoPtr->calendars[0].wdays;
      days  = &infoPtr->calendars[0].days;
2567
  }
2568 2569 2570 2571 2572 2573 2574 2575

  for (i = 1; i < MONTHCAL_GetCalCount(infoPtr); i++)
  {
      /* set months */
      infoPtr->calendars[i] = infoPtr->calendars[0];
      MONTHCAL_GetMonth(&infoPtr->calendars[i].month, i);
  }

2576
  /* offset all rectangles to center in client area */
2577 2578
  c_dx = (client.right  - x * title->right - MC_CALENDAR_PADDING * (x-1)) / 2;
  c_dy = (client.bottom - y * todayrect->bottom - MC_CALENDAR_PADDING * (y-1)) / 2;
2579 2580

  /* if calendar doesn't fit client area show it at left/top bounds */
2581 2582
  if (title->left + c_dx < 0) c_dx = 0;
  if (title->top  + c_dy < 0) c_dy = 0;
2583

2584
  for (i = 0; i < y; i++)
2585
  {
2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597
      for (j = 0; j < x; j++)
      {
          dx = j*(title->right - title->left + MC_CALENDAR_PADDING) + c_dx;
          dy = i*(days->bottom - title->top  + MC_CALENDAR_PADDING) + c_dy;

          OffsetRect(&infoPtr->calendars[i*x+j].title, dx, dy);
          OffsetRect(&infoPtr->calendars[i*x+j].titlemonth, dx, dy);
          OffsetRect(&infoPtr->calendars[i*x+j].titleyear, dx, dy);
          OffsetRect(&infoPtr->calendars[i*x+j].wdays, dx, dy);
          OffsetRect(&infoPtr->calendars[i*x+j].weeknums, dx, dy);
          OffsetRect(&infoPtr->calendars[i*x+j].days, dx, dy);
      }
2598 2599
  }

2600 2601 2602 2603 2604 2605 2606 2607
  OffsetRect(prev, c_dx, c_dy);
  OffsetRect(next, (x-1)*(title->right - title->left + MC_CALENDAR_PADDING) + c_dx, c_dy);

  i = infoPtr->dim.cx * infoPtr->dim.cy - infoPtr->dim.cx;
  todayrect->left   = infoPtr->calendars[i].title.left;
  todayrect->right  = infoPtr->calendars[i].title.right;
  todayrect->top    = infoPtr->calendars[i].days.bottom;
  todayrect->bottom = infoPtr->calendars[i].days.bottom + infoPtr->height_increment;
2608

2609
  TRACE("dx=%d dy=%d client[%s] title[%s] wdays[%s] days[%s] today[%s]\n",
2610
	infoPtr->width_increment,infoPtr->height_increment,
2611
        wine_dbgstr_rect(&client),
2612 2613 2614 2615
        wine_dbgstr_rect(title),
        wine_dbgstr_rect(wdays),
        wine_dbgstr_rect(days),
        wine_dbgstr_rect(todayrect));
2616 2617
}

2618
static LRESULT MONTHCAL_Size(MONTHCAL_INFO *infoPtr, int Width, int Height)
2619
{
2620
  TRACE("(width=%d, height=%d)\n", Width, Height);
2621

2622 2623
  MONTHCAL_UpdateSize(infoPtr);
  InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
2624 2625 2626

  return 0;
}
2627

2628
static LRESULT MONTHCAL_GetFont(const MONTHCAL_INFO *infoPtr)
2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646
{
    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);

2647 2648
    MONTHCAL_UpdateSize(infoPtr);

2649 2650 2651 2652 2653 2654
    if (redraw)
        InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);

    return (LRESULT)hOldFont;
}

2655
/* update theme after a WM_THEMECHANGED message */
2656
static LRESULT theme_changed (const MONTHCAL_INFO* infoPtr)
2657 2658 2659
{
    HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
    CloseThemeData (theme);
2660
    OpenThemeData (infoPtr->hwndSelf, themeClass);
2661 2662 2663
    return 0;
}

2664 2665 2666 2667 2668 2669 2670 2671 2672 2673
static INT MONTHCAL_StyleChanged(MONTHCAL_INFO *infoPtr, WPARAM wStyleType,
                                 const STYLESTRUCT *lpss)
{
    TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
          wStyleType, lpss->styleOld, lpss->styleNew);

    if (wStyleType != GWL_STYLE) return 0;

    infoPtr->dwStyle = lpss->styleNew;

2674 2675 2676 2677
    /* make room for week numbers */
    if ((lpss->styleNew ^ lpss->styleOld) & MCS_WEEKNUMBERS)
        MONTHCAL_UpdateSize(infoPtr);

2678 2679 2680
    return 0;
}

2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695
static INT MONTHCAL_StyleChanging(MONTHCAL_INFO *infoPtr, WPARAM wStyleType,
                                  STYLESTRUCT *lpss)
{
    TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
          wStyleType, lpss->styleOld, lpss->styleNew);

    /* block MCS_MULTISELECT change */
    if ((lpss->styleNew ^ lpss->styleOld) & MCS_MULTISELECT)
    {
        if (lpss->styleOld & MCS_MULTISELECT)
            lpss->styleNew |= MCS_MULTISELECT;
        else
            lpss->styleNew &= ~MCS_MULTISELECT;
    }

2696 2697 2698 2699 2700 2701 2702 2703 2704
    /* block MCS_DAYSTATE change */
    if ((lpss->styleNew ^ lpss->styleOld) & MCS_DAYSTATE)
    {
        if (lpss->styleOld & MCS_DAYSTATE)
            lpss->styleNew |= MCS_DAYSTATE;
        else
            lpss->styleNew &= ~MCS_DAYSTATE;
    }

2705 2706 2707
    return 0;
}

2708
/* FIXME: check whether dateMin/dateMax need to be adjusted. */
2709
static LRESULT
2710
MONTHCAL_Create(HWND hwnd, LPCREATESTRUCTW lpcs)
2711
{
2712
  MONTHCAL_INFO *infoPtr;
2713

2714
  /* allocate memory for info structure */
2715
  infoPtr = Alloc(sizeof(MONTHCAL_INFO));
2716
  SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2717

2718 2719
  if (infoPtr == NULL) {
    ERR("could not allocate info memory!\n");
2720 2721
    return 0;
  }
2722

2723
  infoPtr->hwndSelf = hwnd;
2724
  infoPtr->hwndNotify = lpcs->hwndParent;
2725 2726
  infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
  infoPtr->dim.cx = infoPtr->dim.cy = 1;
2727 2728
  infoPtr->calendars = Alloc(sizeof(CALENDAR_INFO));
  if (!infoPtr->calendars) goto fail;
2729 2730
  infoPtr->monthdayState = Alloc(3*sizeof(MONTHDAYSTATE));
  if (!infoPtr->monthdayState) goto fail;
2731 2732

  /* initialize info structure */
2733
  /* FIXME: calculate systemtime ->> localtime(subtract timezoneinfo) */
2734

Duane Clark's avatar
Duane Clark committed
2735
  GetLocalTime(&infoPtr->todaysDate);
2736
  MONTHCAL_SetFirstDayOfWeek(infoPtr, -1);
2737

2738
  infoPtr->maxSelCount   = (infoPtr->dwStyle & MCS_MULTISELECT) ? 7 : 1;
2739

2740 2741 2742 2743 2744 2745
  infoPtr->colors[MCSC_BACKGROUND]   = comctl32_color.clrWindow;
  infoPtr->colors[MCSC_TEXT]         = comctl32_color.clrWindowText;
  infoPtr->colors[MCSC_TITLEBK]      = comctl32_color.clrActiveCaption;
  infoPtr->colors[MCSC_TITLETEXT]    = comctl32_color.clrWindow;
  infoPtr->colors[MCSC_MONTHBK]      = comctl32_color.clrWindow;
  infoPtr->colors[MCSC_TRAILINGTEXT] = comctl32_color.clrGrayText;
2746

2747 2748 2749
  infoPtr->brushes[BrushBackground]  = CreateSolidBrush(infoPtr->colors[MCSC_BACKGROUND]);
  infoPtr->brushes[BrushTitle]       = CreateSolidBrush(infoPtr->colors[MCSC_TITLEBK]);
  infoPtr->brushes[BrushMonth]       = CreateSolidBrush(infoPtr->colors[MCSC_MONTHBK]);
2750

2751 2752 2753
  infoPtr->pens[PenRed]  = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
  infoPtr->pens[PenText] = CreatePen(PS_SOLID, 1, infoPtr->colors[MCSC_TEXT]);

2754 2755
  infoPtr->minSel = infoPtr->todaysDate;
  infoPtr->maxSel = infoPtr->todaysDate;
2756
  infoPtr->calendars[0].month = infoPtr->todaysDate;
2757
  infoPtr->isUnicode = TRUE;
2758

2759
  /* setup control layout and day state data */
2760
  MONTHCAL_UpdateSize(infoPtr);
2761 2762 2763 2764

  /* today auto update timer, to be freed only on control destruction */
  SetTimer(infoPtr->hwndSelf, MC_TODAYUPDATETIMER, MC_TODAYUPDATEDELAY, 0);

2765
  OpenThemeData (infoPtr->hwndSelf, themeClass);
2766 2767

  return 0;
2768

2769 2770 2771 2772 2773 2774
fail:
  Free(infoPtr->monthdayState);
  Free(infoPtr->calendars);
  Free(infoPtr);
  return 0;
}
2775 2776

static LRESULT
2777
MONTHCAL_Destroy(MONTHCAL_INFO *infoPtr)
2778
{
2779 2780
  INT i;

2781
  /* free month calendar info data */
2782
  Free(infoPtr->monthdayState);
2783
  Free(infoPtr->calendars);
2784
  SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
2785

2786
  CloseThemeData (GetWindowTheme (infoPtr->hwndSelf));
2787

2788 2789
  for (i = 0; i < BrushLast; i++) DeleteObject(infoPtr->brushes[i]);
  for (i = 0; i < PenLast; i++) DeleteObject(infoPtr->pens[i]);
2790

2791
  Free(infoPtr);
2792
  return 0;
2793 2794
}

2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805
/*
 * Handler for WM_NOTIFY messages
 */
static LRESULT
MONTHCAL_Notify(MONTHCAL_INFO *infoPtr, NMHDR *hdr)
{
  /* notification from year edit updown */
  if (hdr->code == UDN_DELTAPOS)
  {
    NMUPDOWN *nmud = (NMUPDOWN*)hdr;

2806
    if (hdr->hwndFrom == infoPtr->hWndYearUpDown && nmud->iDelta)
2807 2808
    {
      /* year value limits are set up explicitly after updown creation */
2809 2810 2811
      MONTHCAL_Scroll(infoPtr, 12 * nmud->iDelta);
      MONTHCAL_NotifyDayState(infoPtr);
      MONTHCAL_NotifySelectionChange(infoPtr);
2812 2813 2814 2815
    }
  }
  return 0;
}
2816

2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830
static inline BOOL
MONTHCAL_SetUnicodeFormat(MONTHCAL_INFO *infoPtr, BOOL isUnicode)
{
  BOOL prev = infoPtr->isUnicode;
  infoPtr->isUnicode = isUnicode;
  return prev;
}

static inline BOOL
MONTHCAL_GetUnicodeFormat(const MONTHCAL_INFO *infoPtr)
{
  return infoPtr->isUnicode;
}

2831
static LRESULT WINAPI
2832
MONTHCAL_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2833
{
2834
  MONTHCAL_INFO *infoPtr = (MONTHCAL_INFO *)GetWindowLongPtrW(hwnd, 0);
2835

2836
  TRACE("hwnd=%p msg=%x wparam=%lx lparam=%lx\n", hwnd, uMsg, wParam, lParam);
2837 2838

  if (!infoPtr && (uMsg != WM_CREATE))
2839
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
2840
  switch(uMsg)
2841 2842
  {
  case MCM_GETCURSEL:
2843
    return MONTHCAL_GetCurSel(infoPtr, (LPSYSTEMTIME)lParam);
2844

2845
  case MCM_SETCURSEL:
2846
    return MONTHCAL_SetCurSel(infoPtr, (LPSYSTEMTIME)lParam);
2847

2848
  case MCM_GETMAXSELCOUNT:
2849
    return MONTHCAL_GetMaxSelCount(infoPtr);
2850

2851
  case MCM_SETMAXSELCOUNT:
2852
    return MONTHCAL_SetMaxSelCount(infoPtr, wParam);
2853

2854
  case MCM_GETSELRANGE:
2855
    return MONTHCAL_GetSelRange(infoPtr, (LPSYSTEMTIME)lParam);
2856

2857
  case MCM_SETSELRANGE:
2858
    return MONTHCAL_SetSelRange(infoPtr, (LPSYSTEMTIME)lParam);
2859

2860
  case MCM_GETMONTHRANGE:
2861
    return MONTHCAL_GetMonthRange(infoPtr, wParam, (SYSTEMTIME*)lParam);
2862

2863
  case MCM_SETDAYSTATE:
2864
    return MONTHCAL_SetDayState(infoPtr, (INT)wParam, (LPMONTHDAYSTATE)lParam);
2865

2866
  case MCM_GETMINREQRECT:
2867
    return MONTHCAL_GetMinReqRect(infoPtr, (LPRECT)lParam);
2868

2869
  case MCM_GETCOLOR:
2870
    return MONTHCAL_GetColor(infoPtr, wParam);
2871

2872
  case MCM_SETCOLOR:
2873
    return MONTHCAL_SetColor(infoPtr, wParam, (COLORREF)lParam);
2874

2875
  case MCM_GETTODAY:
2876
    return MONTHCAL_GetToday(infoPtr, (LPSYSTEMTIME)lParam);
2877

2878
  case MCM_SETTODAY:
2879
    return MONTHCAL_SetToday(infoPtr, (LPSYSTEMTIME)lParam);
2880

2881
  case MCM_HITTEST:
2882
    return MONTHCAL_HitTest(infoPtr, (PMCHITTESTINFO)lParam);
2883

2884
  case MCM_GETFIRSTDAYOFWEEK:
2885
    return MONTHCAL_GetFirstDayOfWeek(infoPtr);
2886

2887
  case MCM_SETFIRSTDAYOFWEEK:
2888
    return MONTHCAL_SetFirstDayOfWeek(infoPtr, (INT)lParam);
2889

2890
  case MCM_GETRANGE:
2891
    return MONTHCAL_GetRange(infoPtr, (LPSYSTEMTIME)lParam);
2892

2893
  case MCM_SETRANGE:
2894
    return MONTHCAL_SetRange(infoPtr, (SHORT)wParam, (LPSYSTEMTIME)lParam);
2895

2896
  case MCM_GETMONTHDELTA:
2897
    return MONTHCAL_GetMonthDelta(infoPtr);
2898

2899
  case MCM_SETMONTHDELTA:
2900
    return MONTHCAL_SetMonthDelta(infoPtr, wParam);
2901

2902
  case MCM_GETMAXTODAYWIDTH:
2903
    return MONTHCAL_GetMaxTodayWidth(infoPtr);
2904

2905 2906 2907 2908 2909 2910
  case MCM_SETUNICODEFORMAT:
    return MONTHCAL_SetUnicodeFormat(infoPtr, (BOOL)wParam);

  case MCM_GETUNICODEFORMAT:
    return MONTHCAL_GetUnicodeFormat(infoPtr);

2911 2912 2913
  case MCM_GETCALENDARCOUNT:
    return MONTHCAL_GetCalCount(infoPtr);

2914 2915
  case WM_GETDLGCODE:
    return DLGC_WANTARROWS | DLGC_WANTCHARS;
2916

2917 2918
  case WM_RBUTTONUP:
    return MONTHCAL_RButtonUp(infoPtr, lParam);
2919

2920
  case WM_LBUTTONDOWN:
2921
    return MONTHCAL_LButtonDown(infoPtr, lParam);
2922

2923
  case WM_MOUSEMOVE:
2924
    return MONTHCAL_MouseMove(infoPtr, lParam);
2925

2926
  case WM_LBUTTONUP:
2927
    return MONTHCAL_LButtonUp(infoPtr, lParam);
2928

2929
  case WM_PAINT:
2930
    return MONTHCAL_Paint(infoPtr, (HDC)wParam);
2931

2932 2933 2934
  case WM_PRINTCLIENT:
    return MONTHCAL_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);

2935 2936 2937
  case WM_ERASEBKGND:
    return MONTHCAL_EraseBkgnd(infoPtr, (HDC)wParam);

2938
  case WM_SETFOCUS:
2939
    return MONTHCAL_SetFocus(infoPtr);
2940 2941

  case WM_SIZE:
2942
    return MONTHCAL_Size(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
2943

2944 2945 2946
  case WM_NOTIFY:
    return MONTHCAL_Notify(infoPtr, (NMHDR*)lParam);

2947
  case WM_CREATE:
2948
    return MONTHCAL_Create(hwnd, (LPCREATESTRUCTW)lParam);
2949

2950 2951 2952 2953 2954 2955
  case WM_SETFONT:
    return MONTHCAL_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);

  case WM_GETFONT:
    return MONTHCAL_GetFont(infoPtr);

2956
  case WM_TIMER:
2957
    return MONTHCAL_Timer(infoPtr, wParam);
2958 2959 2960
    
  case WM_THEMECHANGED:
    return theme_changed (infoPtr);
2961

2962
  case WM_DESTROY:
2963
    return MONTHCAL_Destroy(infoPtr);
2964

2965 2966 2967 2968
  case WM_SYSCOLORCHANGE:
    COMCTL32_RefreshSysColors();
    return 0;

2969 2970 2971
  case WM_STYLECHANGED:
    return MONTHCAL_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);

2972 2973 2974
  case WM_STYLECHANGING:
    return MONTHCAL_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);

2975
  default:
2976
    if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
2977
      ERR( "unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
2978
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
2979
  }
2980 2981 2982
}


2983
void
2984
MONTHCAL_Register(void)
2985
{
2986
  WNDCLASSW wndClass;
2987

2988
  ZeroMemory(&wndClass, sizeof(WNDCLASSW));
2989
  wndClass.style         = CS_GLOBALCLASS;
2990
  wndClass.lpfnWndProc   = MONTHCAL_WindowProc;
2991 2992
  wndClass.cbClsExtra    = 0;
  wndClass.cbWndExtra    = sizeof(MONTHCAL_INFO *);
2993
  wndClass.hCursor       = LoadCursorW(0, (LPWSTR)IDC_ARROW);
2994
  wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
2995
  wndClass.lpszClassName = MONTHCAL_CLASSW;
2996

2997
  RegisterClassW(&wndClass);
2998 2999 3000
}


3001
void
3002
MONTHCAL_Unregister(void)
3003
{
3004
    UnregisterClassW(MONTHCAL_CLASSW, NULL);
3005
}