monthcal.c 87.3 KB
Newer Older
1 2
/*
 * Month calendar control
3
 *
4
 * Copyright 1998, 1999 Eric Kohl (ekohl@abo.rhein-zeitung.de)
5 6 7
 * Copyright 1999 Alex Priem (alexp@sci.kun.nl)
 * Copyright 1999 Chris Morgan <cmorgan@wpi.edu> and
 *		  James Abbatiello <abbeyj@wpi.edu>
8
 * Copyright 2000 Uwe Bonnes <bon@elektron.ikp.physik.tu-darmstadt.de>
9
 * 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
 * TODO:
26 27 28 29 30
 *    -- MCM_[GS]ETUNICODEFORMAT
 *    -- handle resources better (doesn't work now); 
 *    -- take care of internationalization.
 *    -- keyboard handling.
 *    -- search for FIXME
31 32
 */

33
#include <math.h>
34
#include <stdarg.h>
35
#include <stdio.h>
36
#include <stdlib.h>
37
#include <string.h>
38

39
#include "windef.h"
40
#include "winbase.h"
41
#include "wingdi.h"
42 43
#include "winuser.h"
#include "winnls.h"
44
#include "commctrl.h"
45
#include "comctl32.h"
46
#include "uxtheme.h"
47
#include "vssym32.h"
48
#include "wine/unicode.h"
49
#include "wine/debug.h"
50
#include "wine/heap.h"
51

52
WINE_DEFAULT_DEBUG_CHANNEL(monthcal);
53

54 55 56 57
#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 */
58
#define MC_PREVNEXTMONTHDELAY   350	/* when continuously pressing `next/prev
59
					   month', wait 350 ms before going
60 61 62
					   to the next/prev month */
#define MC_TODAYUPDATEDELAY 120000 /* time between today check for update (2 min) */

63
#define MC_PREVNEXTMONTHTIMER   1	/* Timer IDs */
64
#define MC_TODAYUPDATETIMER     2
65

66 67
#define MC_CALENDAR_PADDING     6

68 69 70
/* convert from days to 100 nanoseconds unit - used as FILETIME unit */
#define DAYSTO100NSECS(days) (((ULONGLONG)(days))*24*60*60*10000000)

71 72 73 74 75 76 77
enum CachedPen
{
    PenRed = 0,
    PenText,
    PenLast
};

78 79 80 81 82 83 84 85
enum CachedBrush
{
    BrushTitle = 0,
    BrushMonth,
    BrushBackground,
    BrushLast
};

86 87 88 89 90 91 92 93 94
/* 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 */
95 96

    SYSTEMTIME month;/* contains calendar main month/year */
97 98
} CALENDAR_INFO;

99 100
typedef struct
{
101 102
    HWND	hwndSelf;
    DWORD	dwStyle; /* cached GWL_STYLE */
103 104

    COLORREF    colors[MCSC_TRAILINGTEXT+1];
105
    HBRUSH      brushes[BrushLast];
106
    HPEN        pens[PenLast];
107

108 109 110 111 112
    HFONT	hFont;
    HFONT	hBoldFont;
    int		textHeight;
    int		height_increment;
    int		width_increment;
113
    INT		delta;	/* scroll rate; # of months that the */
114
                        /* control moves when user clicks a scroll button */
115 116 117 118
    int		firstDay;	/* Start month calendar with firstDay's day,
				   stored in SYSTEMTIME format */
    BOOL	firstDaySet;    /* first week day differs from locale defined */

119 120
    BOOL	isUnicode;      /* value set with MCM_SETUNICODE format */

121 122
    MONTHDAYSTATE *monthdayState;
    SYSTEMTIME	todaysDate;
123
    BOOL	todaySet;       /* Today was forced with MCM_SETTODAY */
124
    int		status;		/* See MC_SEL flags */
125
    SYSTEMTIME	firstSel;	/* first selected day */
126
    INT		maxSelCount;
127
    SYSTEMTIME	minSel;         /* contains single selection when used without MCS_MULTISELECT */
128
    SYSTEMTIME	maxSel;
129
    SYSTEMTIME  focusedSel;     /* date currently focused with mouse movement */
130 131 132
    DWORD	rangeValid;
    SYSTEMTIME	minDate;
    SYSTEMTIME	maxDate;
133

134
    RECT titlebtnnext;	/* the `next month' button in the header */
135
    RECT titlebtnprev;  /* the `prev month' button in the header */
136
    RECT todayrect;	/* `today: xx/xx/xx' text rect */
137
    HWND hwndNotify;    /* Window to receive the notifications */
138 139
    HWND hWndYearEdit;  /* Window Handle of edit box to handle years */
    HWND hWndYearUpDown;/* Window Handle of updown box to handle years */
140
    WNDPROC EditWndProc;  /* original Edit window procedure */
141 142

    CALENDAR_INFO *calendars;
143
    SIZE dim;           /* [cx,cy] - dimensions of calendars matrix, row/column count */
144 145
} MONTHCAL_INFO, *LPMONTHCAL_INFO;

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

148 149
/* empty SYSTEMTIME const */
static const SYSTEMTIME st_null;
150
/* valid date limits */
151 152
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 };
153

154 155 156 157 158 159
/* Prev/Next buttons */
enum nav_direction
{
    DIRECTION_BACKWARD,
    DIRECTION_FORWARD
};
160

161
/* helper functions  */
162 163 164 165
static inline INT MONTHCAL_GetCalCount(const MONTHCAL_INFO *infoPtr)
{
   return infoPtr->dim.cx * infoPtr->dim.cy;
}
166

167 168 169 170 171 172 173 174 175
/* 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;
176 177 178 179 180 181 182 183
    nmsc.stSelStart.wDayOfWeek = 0;
    if(infoPtr->dwStyle & MCS_MULTISELECT){
        nmsc.stSelEnd = infoPtr->maxSel;
        nmsc.stSelEnd.wDayOfWeek = 0;
    }
    else
        nmsc.stSelEnd = st_null;

184 185 186 187 188 189 190 191 192 193 194 195
    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;
196 197 198 199 200 201 202
    nmsc.stSelStart.wDayOfWeek = 0;
    if(infoPtr->dwStyle & MCS_MULTISELECT){
        nmsc.stSelEnd = infoPtr->maxSel;
        nmsc.stSelEnd.wDayOfWeek = 0;
    }
    else
        nmsc.stSelEnd = st_null;
203 204 205 206

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

207 208 209 210 211
static inline int MONTHCAL_MonthDiff(const SYSTEMTIME *left, const SYSTEMTIME *right)
{
    return (right->wYear - left->wYear)*12 + right->wMonth - left->wMonth;
}

212
/* returns the number of days in any given month, checking for leap days */
213
/* January is 1, December is 12 */
214
int MONTHCAL_MonthLength(int month, int year)
215
{
216
  const int mdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
217 218 219
  /* 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 */
220 221
  if(month == 0)
    month = 12;
222
  else if(month == 13)
223 224
    month = 1;

225 226 227
  /* special case for calendar transition year */
  if(month == min_allowed_date.wMonth && year == min_allowed_date.wYear) return 19;

228 229 230 231 232
  /* 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) &&
233
     (year%4 == 0)) ? 1 : 0);
234 235 236 237 238
  }
  else {
    return mdays[month - 1];
  }
}
239

240 241 242 243 244 245
/* 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);
}
246

247 248
/* make sure that date fields are valid */
static BOOL MONTHCAL_ValidateDate(const SYSTEMTIME *time)
249
{
250 251 252 253
    if (time->wMonth < 1 || time->wMonth > 12 )
        return FALSE;
    if (time->wDay == 0 || time->wDay > MONTHCAL_MonthLength(time->wMonth, time->wYear))
        return FALSE;
254

255
    return TRUE;
256 257
}

258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
/* 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;
}

287 288 289 290 291 292 293 294 295 296 297 298 299
/* 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
 *
300
 *  Note that no date validation performed, already validated values expected.
301
 */
302
LONG MONTHCAL_CompareSystemTime(const SYSTEMTIME *first, const SYSTEMTIME *second)
303 304 305 306 307 308 309 310 311
{
  FILETIME ft_first, ft_second;

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

  return CompareFileTime(&ft_first, &ft_second);
}

312 313 314 315 316 317 318 319 320 321 322 323
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);
}

324 325 326 327 328 329 330 331 332 333 334
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);
}

335 336 337 338 339 340
/* 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
341
 *  [I] fix     : make date fit valid range
342 343 344
 *
 * RETURN VALUE
 *
345
 *  TRUE  - date within largest and configured range
346 347
 *  FALSE - date is outside largest or configured range
 */
348 349
static BOOL MONTHCAL_IsDateInValidRange(const MONTHCAL_INFO *infoPtr,
                                        SYSTEMTIME *date, BOOL fix)
350
{
351
  const SYSTEMTIME *fix_st = NULL;
352

353 354 355 356 357 358
  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;
  }
359 360 361 362 363
  else {
     if(infoPtr->rangeValid & GDTR_MAX) {
        if((MONTHCAL_CompareSystemTime(date, &infoPtr->maxDate) == 1)) {
           fix_st = &infoPtr->maxDate;
        }
364
     }
365 366 367 368 369

     if(infoPtr->rangeValid & GDTR_MIN) {
        if((MONTHCAL_CompareSystemTime(date, &infoPtr->minDate) == -1)) {
           fix_st = &infoPtr->minDate;
        }
370
     }
371 372
  }

373 374 375
  if (fix && fix_st) {
    date->wYear  = fix_st->wYear;
    date->wMonth = fix_st->wMonth;
376 377
  }

378
  return !fix_st;
379 380
}

381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
/* 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);

411 412 413 414
  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;
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430

  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);

431 432
       ft_range0.dwLowDateTime  = ul_range0.u.LowPart;
       ft_range0.dwHighDateTime = ul_range0.u.HighPart;
433 434 435 436 437 438 439 440
       FileTimeToSystemTime(&ft_range0, adjust);
     }

     return FALSE;
  }
  else return TRUE;
}

441
/* Used in MCM_SETRANGE/MCM_SETSELRANGE to determine resulting time part.
442
   Milliseconds are intentionally not validated. */
443 444 445 446 447 448 449 450
static BOOL MONTHCAL_ValidateTime(const SYSTEMTIME *time)
{
  if((time->wHour > 24) || (time->wMinute > 59) || (time->wSecond > 59))
    return FALSE;
  else
    return TRUE;
}

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

458 459 460
/* Returns the day in the week
 *
 * PARAMETERS
461 462
 *  [i] date    : input date
 *  [I] inplace : set calculated value back to date structure
463 464 465 466
 *
 * RETURN VALUE
 *   day of week in SYSTEMTIME format: (0 == sunday,..., 6 == saturday)
 */
467
int MONTHCAL_CalculateDayOfWeek(SYSTEMTIME *date, BOOL inplace)
468
{
469
  SYSTEMTIME st = st_null;
470 471
  FILETIME ft;

472
  MONTHCAL_CopyDate(date, &st);
473 474 475

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

477 478
  if (inplace) date->wDayOfWeek = st.wDayOfWeek;

479
  return st.wDayOfWeek;
480 481
}

482
/* add/subtract 'months' from date */
483
static inline void MONTHCAL_GetMonth(SYSTEMTIME *date, INT months)
484
{
485
  INT length, m = date->wMonth + months;
486

487 488
  date->wYear += m > 0 ? (m - 1) / 12 : m / 12 - 1;
  date->wMonth = m > 0 ? (m - 1) % 12 + 1 : 12 + m % 12;
489 490 491
  /* fix moving from last day in a month */
  length = MONTHCAL_MonthLength(date->wMonth, date->wYear);
  if(date->wDay > length) date->wDay = length;
492
  MONTHCAL_CalculateDayOfWeek(date, TRUE);
493 494
}

495 496 497
/* properly updates date to point on next month */
static inline void MONTHCAL_GetNextMonth(SYSTEMTIME *date)
{
498
  MONTHCAL_GetMonth(date, 1);
499 500
}

501
/* properly updates date to point on prev month */
502
static inline void MONTHCAL_GetPrevMonth(SYSTEMTIME *date)
503
{
504
  MONTHCAL_GetMonth(date, -1);
505 506
}

507 508 509
/* Returns full date for a first currently visible day */
static void MONTHCAL_GetMinDate(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *date)
{
510 511 512
  /* zero indexed calendar has the earliest date */
  SYSTEMTIME st_first = infoPtr->calendars[0].month;
  INT firstDay;
513

514 515
  st_first.wDay = 1;
  firstDay = MONTHCAL_CalculateDayOfWeek(&st_first, FALSE);
516

517
  *date = infoPtr->calendars[0].month;
518 519 520 521 522 523 524 525 526
  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 */
527
  MONTHCAL_CalculateDayOfWeek(date, TRUE);
528 529 530 531 532
}

/* Returns full date for a last currently visible day */
static void MONTHCAL_GetMaxDate(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *date)
{
533
  /* the latest date is in latest calendar */
534 535 536 537 538 539 540 541 542
  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);
543 544

  MONTHCAL_GetNextMonth(date);
545 546 547 548 549 550
  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;
551 552 553

  /* 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) -
554
                     MONTHCAL_MonthLength(lt_month->wMonth, lt_month->wYear);
555 556

  /* fix day of week */
557
  MONTHCAL_CalculateDayOfWeek(date, TRUE);
558 559
}

560 561 562
/* 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)
563
{
564 565 566
  SYSTEMTIME st = infoPtr->calendars[calIdx].month;
  int firstDay, col, row;
  RECT client;
567

568
  GetClientRect(infoPtr->hwndSelf, &client);
569

570 571
  /* 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;
572

573 574
  col = (pt.x - infoPtr->calendars[calIdx].days.left ) / infoPtr->width_increment;
  row = (pt.y - infoPtr->calendars[calIdx].days.top  ) / infoPtr->height_increment;
575

576 577
  st.wDay = 1;
  firstDay = (MONTHCAL_CalculateDayOfWeek(&st, FALSE) + 6 - infoPtr->firstDay) % 7;
578
  return col + 7 * row - firstDay;
579 580
}

581
/* Get day position for given date and calendar
582 583 584 585 586
 *
 * PARAMETERS
 *
 *  [I] infoPtr : pointer to control data
 *  [I] date : date value
587 588 589
 *  [O] col : day column (zero based)
 *  [O] row : week column (zero based)
 *  [I] calIdx : calendar index
590
 */
591 592
static void MONTHCAL_GetDayPos(const MONTHCAL_INFO *infoPtr, const SYSTEMTIME *date,
    INT *col, INT *row, INT calIdx)
593
{
594 595
  SYSTEMTIME st = infoPtr->calendars[calIdx].month;
  INT first;
596

597 598
  st.wDay = 1;
  first = (MONTHCAL_CalculateDayOfWeek(&st, FALSE) + 6 - infoPtr->firstDay) % 7;
599

600
  if (calIdx == 0 || calIdx == MONTHCAL_GetCalCount(infoPtr)-1) {
601 602
      const SYSTEMTIME *cal = &infoPtr->calendars[calIdx].month;
      LONG cmp = MONTHCAL_CompareMonths(date, &st);
603

604 605 606 607 608 609
      /* previous month */
      if (cmp == -1) {
        *col = (first - MONTHCAL_MonthLength(date->wMonth, cal->wYear) + date->wDay) % 7;
        *row = 0;
        return;
      }
610

611 612 613
      /* next month calculation is same as for current, just add current month length */
      if (cmp == 1)
          first += MONTHCAL_MonthLength(cal->wMonth, cal->wYear);
614 615
  }

616 617
  *col = (date->wDay + first) % 7;
  *row = (date->wDay + first - *col) / 7;
618 619
}

620 621 622
/* 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)
623
{
624 625 626
  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;
627 628 629
  r->bottom = r->top + infoPtr->textHeight;
}

630 631 632 633
/* Returns bounding box for given date
 *
 * NOTE: when calendar index is unknown pass -1
 */
634
static BOOL MONTHCAL_GetDayRect(const MONTHCAL_INFO *infoPtr, const SYSTEMTIME *date, RECT *r, INT calIdx)
635
{
636 637
  INT col, row;

638 639 640 641 642 643
  if (!MONTHCAL_ValidateDate(date))
  {
      SetRectEmpty(r);
      return FALSE;
  }

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

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

  MONTHCAL_GetDayPos(infoPtr, date, &col, &row, calIdx);
  MONTHCAL_GetDayRectI(infoPtr, r, col, row, calIdx);
666 667

  return TRUE;
668 669
}

670 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
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;
}

720 721 722 723 724
/* 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.
725 726

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

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

    /* invalidate old focused day */
739 740
    if (MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1))
      InvalidateRect(infoPtr->hwndSelf, &r, FALSE);
741 742 743 744

    infoPtr->focusedSel = *st;
  }

745 746 747
  /* On set invalidates new day, on reset clears previous focused day. */
  if (MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1))
    InvalidateRect(infoPtr->hwndSelf, &r, FALSE);
748

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

752
  return TRUE;
753
}
754

755 756 757
/* draw today boundary box for specified rectangle */
static void MONTHCAL_Circle(const MONTHCAL_INFO *infoPtr, HDC hdc, const RECT *r)
{
758
  HPEN old_pen = SelectObject(hdc, infoPtr->pens[PenRed]);
759 760 761 762 763 764 765 766 767
  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);
}

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

  MONTHCAL_GetDayRect(infoPtr, date, &r, -1);
  MONTHCAL_Circle(infoPtr, hdc, &r);
781
}
782

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

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

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

807
    selection = 1;
808
  }
809 810
  else
    selection = 0;
811

812
  SelectObject(hdc, bold ? infoPtr->hBoldFont : infoPtr->hFont);
813

814
  old_bkmode = SetBkMode(hdc, TRANSPARENT);
815
  wsprintfW(buf, fmtW, st->wDay);
816
  DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
817
  SetBkMode(hdc, old_bkmode);
818

819 820
  if (selection)
  {
821 822 823
    SetTextColor(hdc, oldCol);
    SetBkColor(hdc, oldBk);
  }
824 825
}

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

863
/* paint a title with buttons and month/year string */
864
static void MONTHCAL_PaintTitle(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
865
{
866 867 868 869 870 871
  static const WCHAR mmmmW[] = {'M','M','M','M',0};
  static const WCHAR mmmW[] = {'M','M','M',0};
  static const WCHAR mmW[] = {'M','M',0};
  static const WCHAR fmtyearW[] = {'%','l','d',0};
  static const WCHAR fmtmmW[] = {'%','0','2','d',0};
  static const WCHAR fmtmW[] = {'%','d',0};
872
  RECT *title = &infoPtr->calendars[calIdx].title;
873
  const SYSTEMTIME *st = &infoPtr->calendars[calIdx].month;
874 875
  WCHAR monthW[80], strW[80], fmtW[80], yearW[6] /* valid year range is 1601-30827 */;
  int yearoffset, monthoffset, shiftX;
876
  SIZE sz;
877

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

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

886
  /* draw formatted date string */
887
  GetDateFormatW(LOCALE_USER_DEFAULT, DATE_YEARMONTH, st, NULL, strW, ARRAY_SIZE(strW));
888 889
  DrawTextW(hdc, strW, strlenW(strW), title, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

890
  GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SYEARMONTH, fmtW, ARRAY_SIZE(fmtW));
891 892 893 894 895
  wsprintfW(yearW, fmtyearW, st->wYear);

  /* month is trickier as it's possible to have different format pictures, we'll
     test for M, MM, MMM, and MMMM */
  if (strstrW(fmtW, mmmmW))
896
    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+st->wMonth-1, monthW, ARRAY_SIZE(monthW));
897
  else if (strstrW(fmtW, mmmW))
898
    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVMONTHNAME1+st->wMonth-1, monthW, ARRAY_SIZE(monthW));
899 900 901 902
  else if (strstrW(fmtW, mmW))
    wsprintfW(monthW, fmtmmW, st->wMonth);
  else
    wsprintfW(monthW, fmtmW, st->wMonth);
903

904 905 906 907 908 909 910 911
  /* update hit boxes */
  yearoffset = 0;
  while (strW[yearoffset])
  {
    if (!strncmpW(&strW[yearoffset], yearW, strlenW(yearW)))
        break;
    yearoffset++;
  }
912

913 914 915 916 917 918 919
  monthoffset = 0;
  while (strW[monthoffset])
  {
    if (!strncmpW(&strW[monthoffset], monthW, strlenW(monthW)))
        break;
    monthoffset++;
  }
920

921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936
  /* for left limits use offsets */
  sz.cx = 0;
  if (yearoffset)
    GetTextExtentPoint32W(hdc, strW, yearoffset, &sz);
  infoPtr->calendars[calIdx].titleyear.left = sz.cx;

  sz.cx = 0;
  if (monthoffset)
    GetTextExtentPoint32W(hdc, strW, monthoffset, &sz);
  infoPtr->calendars[calIdx].titlemonth.left = sz.cx;

  /* for right limits use actual string parts lengths */
  GetTextExtentPoint32W(hdc, &strW[yearoffset], strlenW(yearW), &sz);
  infoPtr->calendars[calIdx].titleyear.right = infoPtr->calendars[calIdx].titleyear.left + sz.cx;

  GetTextExtentPoint32W(hdc, monthW, strlenW(monthW), &sz);
937
  infoPtr->calendars[calIdx].titlemonth.right = infoPtr->calendars[calIdx].titlemonth.left + sz.cx;
938 939 940 941 942 943 944

  /* Finally translate rectangles to match center aligned string,
     hit rectangles are relative to title rectangle before translation. */
  GetTextExtentPoint32W(hdc, strW, strlenW(strW), &sz);
  shiftX = (title->right - title->left - sz.cx) / 2 + title->left;
  OffsetRect(&infoPtr->calendars[calIdx].titleyear, shiftX, 0);
  OffsetRect(&infoPtr->calendars[calIdx].titlemonth, shiftX, 0);
945 946
}

947
static void MONTHCAL_PaintWeeknumbers(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
948
{
949
  const SYSTEMTIME *date = &infoPtr->calendars[calIdx].month;
950 951 952
  static const WCHAR fmt_weekW[] = { '%','d',0 };
  INT mindays, weeknum, weeknum1, startofprescal;
  INT i, prev_month;
953 954
  SYSTEMTIME st;
  WCHAR buf[80];
955
  HPEN old_pen;
956
  RECT r;
957 958 959 960 961

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

  MONTHCAL_GetMinDate(infoPtr, &st);
  startofprescal = st.wDay;
962
  st = *date;
963

964
  prev_month = date->wMonth - 1;
965 966 967 968 969 970 971 972
  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
973
     LOCALE_IFIRSTWEEKOFYEAR == 1  (what countries?)
974 975
     The first week of the year must contain only days of the new year
  */
976
  GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, buf, ARRAY_SIZE(buf));
977 978
  weeknum = atoiW(buf);
  switch (weeknum)
979
  {
980 981 982 983 984 985 986 987 988
    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;
989
  }
990

991
  if (date->wMonth == 1)
992
  {
993
    /* calculate all those exceptions for January */
994 995 996 997 998 999 1000 1001
    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++)
1002
	   weeknum += MONTHCAL_MonthLength(i+1, date->wYear - 1);
1003

1004 1005 1006 1007 1008 1009 1010 1011
	weeknum  += startofprescal + 7;
	weeknum  /= 7;
	st.wYear -= 1;
	weeknum1  = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
	if ((infoPtr->firstDay - weeknum1) % 7 > mindays) weeknum++;
    }
  }
  else
1012
  {
1013 1014
    weeknum = 0;
    for(i = 0; i < prev_month - 1; i++)
1015
	weeknum += MONTHCAL_MonthLength(i+1, date->wYear);
1016 1017 1018 1019 1020 1021

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

1024
  r = infoPtr->calendars[calIdx].weeknums;
1025 1026

  /* erase whole week numbers area */
1027
  FillRect(hdc, &r, infoPtr->brushes[BrushMonth]);
1028
  SetTextColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
1029 1030

  /* reduce rectangle to one week number */
1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
  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);
  }
1049

1050
  /* line separator for week numbers column */
1051
  old_pen = SelectObject(hdc, infoPtr->pens[PenText]);
1052 1053
  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);
1054
  SelectObject(hdc, old_pen);
1055 1056 1057 1058 1059
}

/* bottom today date */
static void MONTHCAL_PaintTodayTitle(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
{
1060 1061 1062 1063 1064 1065 1066 1067
  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;

1068
  LoadStringW(COMCTL32_hModule, IDM_TODAY, buf_todayW, ARRAY_SIZE(buf_todayW));
1069 1070
  col = infoPtr->dwStyle & MCS_NOTODAYCIRCLE ? 0 : 1;
  if (infoPtr->dwStyle & MCS_WEEKNUMBERS) col--;
1071 1072
  /* label is located below first calendar last row */
  MONTHCAL_GetDayRectI(infoPtr, &text_rect, col, 6, infoPtr->dim.cx * infoPtr->dim.cy - infoPtr->dim.cx);
1073
  box_rect = text_rect;
1074

1075
  GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &infoPtr->todaysDate, NULL, buf_dateW, ARRAY_SIZE(buf_dateW));
1076 1077
  old_font = SelectObject(hdc, infoPtr->hBoldFont);
  SetTextColor(hdc, infoPtr->colors[MCSC_TEXT]);
1078

1079 1080 1081
  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);
1082

1083 1084 1085
  if(!(infoPtr->dwStyle & MCS_NOTODAYCIRCLE)) {
    OffsetRect(&box_rect, -infoPtr->width_increment, 0);
    MONTHCAL_Circle(infoPtr, hdc, &box_rect);
1086
  }
1087 1088

  SelectObject(hdc, old_font);
1089 1090 1091 1092 1093
}

/* today mark + focus */
static void MONTHCAL_PaintFocusAndCircle(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
{
1094 1095
  /* circle today date if only it's in fully visible month */
  if (!(infoPtr->dwStyle & MCS_NOTODAYCIRCLE))
1096
  {
1097 1098 1099 1100 1101 1102 1103 1104
    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;
      }
1105 1106
  }

1107
  if (!MONTHCAL_IsDateEqual(&infoPtr->focusedSel, &st_null))
1108 1109
  {
    RECT r;
1110
    MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1);
1111 1112
    DrawFocusRect(hdc, &r);
  }
1113
}
1114

1115 1116 1117
/* months before first calendar month and after last calendar month */
static void MONTHCAL_PaintLeadTrailMonths(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
{
1118 1119
  INT mask, index;
  UINT length;
1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131
  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);
1132
  index = 0;
1133 1134
  while(st.wDay <= length)
  {
1135
      MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[index] & mask, ps);
1136 1137 1138 1139 1140
      mask <<= 1;
      st.wDay++;
  }

  /* draw next month */
1141
  st = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
1142 1143 1144 1145
  st.wDay = 1;
  MONTHCAL_GetNextMonth(&st);
  MONTHCAL_GetMaxDate(infoPtr, &st_max);
  mask = 1;
1146
  index = MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)-1;
1147 1148
  while(st.wDay <= st_max.wDay)
  {
1149
      MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[index] & mask, ps);
1150 1151 1152 1153 1154
      mask <<= 1;
      st.wDay++;
  }
}

1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166
static int get_localized_dayname(const MONTHCAL_INFO *infoPtr, unsigned int day, WCHAR *buff, unsigned int count)
{
  LCTYPE lctype;

  if (infoPtr->dwStyle & MCS_SHORTDAYSOFWEEK)
      lctype = LOCALE_SSHORTESTDAYNAME1 + day;
  else
      lctype = LOCALE_SABBREVDAYNAME1 + day;

  return GetLocaleInfoW(LOCALE_USER_DEFAULT, lctype, buff, count);
}

1167
/* paint a calendar area */
1168
static void MONTHCAL_PaintCalendar(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
1169
{
1170
  const SYSTEMTIME *date = &infoPtr->calendars[calIdx].month;
1171 1172
  INT i, j;
  UINT length;
1173 1174
  RECT r, fill_bk_rect;
  SYSTEMTIME st;
1175
  WCHAR buf[80];
1176
  HPEN old_pen;
1177
  int mask;
1178

1179 1180 1181 1182
  /* 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);
1183

1184
  FillRect(hdc, &fill_bk_rect, infoPtr->brushes[BrushMonth]);
1185 1186

  /* draw line under day abbreviations */
1187
  old_pen = SelectObject(hdc, infoPtr->pens[PenText]);
1188 1189 1190 1191
  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);
1192
  SelectObject(hdc, old_pen);
1193

1194 1195
  infoPtr->calendars[calIdx].wdays.left = infoPtr->calendars[calIdx].days.left =
      infoPtr->calendars[calIdx].weeknums.right;
1196

1197
  /* draw day abbreviations */
Duane Clark's avatar
Duane Clark committed
1198
  SelectObject(hdc, infoPtr->hFont);
1199
  SetBkColor(hdc, infoPtr->colors[MCSC_MONTHBK]);
1200
  SetTextColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
1201
  /* rectangle to draw a single day abbreviation within */
1202
  r = infoPtr->calendars[calIdx].wdays;
1203
  r.right = r.left + infoPtr->width_increment;
1204

1205
  i = infoPtr->firstDay;
1206
  for(j = 0; j < 7; j++) {
1207
    get_localized_dayname(infoPtr, (i + j + 6) % 7, buf, ARRAY_SIZE(buf));
1208 1209
    DrawTextW(hdc, buf, strlenW(buf), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    OffsetRect(&r, infoPtr->width_increment, 0);
1210
  }
1211

1212
  /* draw current month */
1213
  SetTextColor(hdc, infoPtr->colors[MCSC_TEXT]);
1214
  st = *date;
1215
  st.wDay = 1;
1216
  mask = 1;
1217 1218
  length = MONTHCAL_MonthLength(date->wMonth, date->wYear);
  while(st.wDay <= length)
1219
  {
1220
    MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[calIdx+1] & mask, ps);
1221
    mask <<= 1;
1222
    st.wDay++;
1223
  }
1224
}
1225

1226 1227 1228 1229
static void MONTHCAL_Refresh(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
{
  COLORREF old_text_clr, old_bk_clr;
  HFONT old_font;
1230
  INT i;
1231

1232 1233 1234 1235
  old_text_clr = SetTextColor(hdc, comctl32_color.clrWindowText);
  old_bk_clr   = GetBkColor(hdc);
  old_font     = GetCurrentObject(hdc, OBJ_FONT);

1236
  for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1237 1238 1239 1240 1241 1242 1243
  {
    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);
1244

1245 1246 1247 1248
    /* draw calendar area */
    UnionRect(&r, &infoPtr->calendars[i].wdays, &infoPtr->todayrect);
    if (IntersectRect(&r, &(ps->rcPaint), &r))
        MONTHCAL_PaintCalendar(infoPtr, hdc, ps, i);
1249

1250 1251 1252 1253
    /* week numbers */
    MONTHCAL_PaintWeeknumbers(infoPtr, hdc, ps, i);
  }

1254 1255 1256
  /* partially visible months */
  MONTHCAL_PaintLeadTrailMonths(infoPtr, hdc, ps);

1257 1258 1259 1260 1261 1262 1263
  /* focus and today rectangle */
  MONTHCAL_PaintFocusAndCircle(infoPtr, hdc, ps);

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

  /* navigation buttons */
1264 1265
  MONTHCAL_PaintButton(infoPtr, hdc, DIRECTION_BACKWARD);
  MONTHCAL_PaintButton(infoPtr, hdc, DIRECTION_FORWARD);
1266

1267 1268 1269 1270
  /* restore context */
  SetBkColor(hdc, old_bk_clr);
  SelectObject(hdc, old_font);
  SetTextColor(hdc, old_text_clr);
1271 1272
}

1273
static LRESULT
1274
MONTHCAL_GetMinReqRect(const MONTHCAL_INFO *infoPtr, RECT *rect)
1275
{
1276
  TRACE("rect %p\n", rect);
1277

1278
  if(!rect) return FALSE;
1279

1280 1281 1282
  *rect = infoPtr->calendars[0].title;
  rect->bottom = infoPtr->calendars[0].days.bottom + infoPtr->todayrect.bottom -
                 infoPtr->todayrect.top;
1283

1284
  AdjustWindowRect(rect, infoPtr->dwStyle, FALSE);
1285

1286
  /* minimal rectangle is zero based */
1287
  OffsetRect(rect, -rect->left, -rect->top);
1288

1289
  TRACE("%s\n", wine_dbgstr_rect(rect));
1290

1291
  return TRUE;
1292 1293
}

1294 1295
static COLORREF
MONTHCAL_GetColor(const MONTHCAL_INFO *infoPtr, UINT index)
1296
{
1297
  TRACE("%p, %d\n", infoPtr, index);
1298

1299 1300
  if (index > MCSC_TRAILINGTEXT) return -1;
  return infoPtr->colors[index];
1301 1302
}

1303
static LRESULT
1304 1305
MONTHCAL_SetColor(MONTHCAL_INFO *infoPtr, UINT index, COLORREF color)
{
1306
  enum CachedBrush type;
1307 1308 1309 1310 1311 1312 1313 1314
  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;
1315

1316
  /* update cached brush */
1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332
  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)
1333
  {
1334 1335
    DeleteObject(infoPtr->brushes[type]);
    infoPtr->brushes[type] = CreateSolidBrush(color);
1336 1337
  }

1338 1339 1340 1341 1342 1343 1344
  /* update cached pen */
  if (index == MCSC_TEXT)
  {
    DeleteObject(infoPtr->pens[PenText]);
    infoPtr->pens[PenText] = CreatePen(PS_SOLID, 1, infoPtr->colors[index]);
  }

1345
  InvalidateRect(infoPtr->hwndSelf, NULL, index == MCSC_BACKGROUND);
1346
  return prev;
1347 1348
}

1349
static LRESULT
1350
MONTHCAL_GetMonthDelta(const MONTHCAL_INFO *infoPtr)
1351
{
1352
  TRACE("\n");
1353

1354
  if(infoPtr->delta)
1355
    return infoPtr->delta;
1356 1357

  return MONTHCAL_GetMonthRange(infoPtr, GMR_VISIBLE, NULL);
1358 1359
}

1360

1361
static LRESULT
1362
MONTHCAL_SetMonthDelta(MONTHCAL_INFO *infoPtr, INT delta)
1363
{
1364
  INT prev = infoPtr->delta;
1365

1366
  TRACE("delta %d\n", delta);
1367

1368
  infoPtr->delta = delta;
1369
  return prev;
1370 1371 1372
}


1373
static inline LRESULT
1374
MONTHCAL_GetFirstDayOfWeek(const MONTHCAL_INFO *infoPtr)
1375
{
1376 1377 1378 1379 1380 1381
  int day;

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

  return MAKELONG(day, infoPtr->firstDaySet);
1382 1383 1384
}


1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395
/* 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
1396
 *  locale defined (TRUE - was forced, FALSE - wasn't).
1397 1398 1399 1400
 *
 * FIXME: this needs to be implemented properly in MONTHCAL_Refresh()
 * FIXME: we need more error checking here
 */
1401
static LRESULT
1402
MONTHCAL_SetFirstDayOfWeek(MONTHCAL_INFO *infoPtr, INT day)
1403
{
1404 1405
  LRESULT prev = MONTHCAL_GetFirstDayOfWeek(infoPtr);
  int new_day;
1406

1407
  TRACE("%d\n", day);
1408

1409
  if(day == -1)
1410
  {
1411 1412
    WCHAR buf[80];

1413
    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, buf, ARRAY_SIZE(buf));
1414 1415
    TRACE("%s %d\n", debugstr_w(buf), strlenW(buf));

1416
    new_day = atoiW(buf);
1417

1418
    infoPtr->firstDaySet = FALSE;
1419
  }
1420
  else if(day >= 7)
1421
  {
1422 1423
    new_day = 6; /* max first day allowed */
    infoPtr->firstDaySet = TRUE;
1424
  }
1425
  else
1426
  {
1427 1428
    /* Native behaviour for that case is broken: invalid date number >31
       got displayed at (0,0) position, current month starts always from
1429 1430
       (1,0) position. Should be implemented here as well only if there's
       nothing else to do. */
1431 1432 1433 1434 1435
    if (day < -1)
      FIXME("No bug compatibility for day=%d\n", day);

    new_day = day;
    infoPtr->firstDaySet = TRUE;
1436
  }
1437

1438 1439 1440
  /* convert from locale to SYSTEMTIME format */
  infoPtr->firstDay = (new_day >= 0) ? (++new_day) % 7 : new_day;

1441 1442
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);

1443
  return prev;
1444 1445 1446
}

static LRESULT
1447
MONTHCAL_GetMaxTodayWidth(const MONTHCAL_INFO *infoPtr)
1448
{
1449
  return(infoPtr->todayrect.right - infoPtr->todayrect.left);
1450 1451 1452
}

static LRESULT
1453
MONTHCAL_SetRange(MONTHCAL_INFO *infoPtr, SHORT limits, SYSTEMTIME *range)
1454
{
1455
    FILETIME ft_min, ft_max;
1456

1457
    TRACE("%x %p\n", limits, range);
1458

1459 1460
    if ((limits & GDTR_MIN && !MONTHCAL_ValidateDate(&range[0])) ||
        (limits & GDTR_MAX && !MONTHCAL_ValidateDate(&range[1])))
1461 1462
        return FALSE;

1463 1464 1465
    infoPtr->rangeValid = 0;
    infoPtr->minDate = infoPtr->maxDate = st_null;

1466
    if (limits & GDTR_MIN)
1467
    {
1468 1469 1470
        if (!MONTHCAL_ValidateTime(&range[0]))
            MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]);

1471
        infoPtr->minDate = range[0];
1472
        infoPtr->rangeValid |= GDTR_MIN;
1473
    }
1474
    if (limits & GDTR_MAX)
1475
    {
1476 1477 1478
        if (!MONTHCAL_ValidateTime(&range[1]))
            MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]);

1479
        infoPtr->maxDate = range[1];
1480
        infoPtr->rangeValid |= GDTR_MAX;
1481 1482
    }

1483 1484 1485
    /* Only one limit set - we are done */
    if ((infoPtr->rangeValid & (GDTR_MIN | GDTR_MAX)) != (GDTR_MIN | GDTR_MAX))
        return TRUE;
1486

1487 1488 1489
    SystemTimeToFileTime(&infoPtr->maxDate, &ft_max);
    SystemTimeToFileTime(&infoPtr->minDate, &ft_min);

1490
    if (CompareFileTime(&ft_min, &ft_max) >= 0)
1491
    {
1492
        if ((limits & (GDTR_MIN | GDTR_MAX)) == (GDTR_MIN | GDTR_MAX))
1493 1494 1495 1496 1497 1498 1499 1500
        {
            /* Native swaps limits only when both limits are being set. */
            SYSTEMTIME st_tmp = infoPtr->minDate;
            infoPtr->minDate  = infoPtr->maxDate;
            infoPtr->maxDate  = st_tmp;
        }
        else
        {
1501
            /* reset the other limit */
1502 1503 1504
            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;
1505 1506 1507
        }
    }

1508
    return TRUE;
1509 1510
}

1511

1512
static LRESULT
1513
MONTHCAL_GetRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1514
{
1515
  TRACE("%p\n", range);
1516

1517
  if (!range) return 0;
1518

1519 1520
  range[1] = infoPtr->maxDate;
  range[0] = infoPtr->minDate;
1521 1522 1523 1524

  return infoPtr->rangeValid;
}

1525

1526
static LRESULT
1527
MONTHCAL_SetDayState(const MONTHCAL_INFO *infoPtr, INT months, MONTHDAYSTATE *states)
1528
{
1529
  TRACE("%p %d %p\n", infoPtr, months, states);
1530 1531 1532

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

1534
  memcpy(infoPtr->monthdayState, states, months*sizeof(MONTHDAYSTATE));
1535 1536 1537 1538

  return 1;
}

1539
static LRESULT
1540
MONTHCAL_GetCurSel(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel)
1541
{
1542 1543
  TRACE("%p\n", curSel);
  if(!curSel) return FALSE;
1544
  if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
1545

1546
  *curSel = infoPtr->minSel;
1547
  TRACE("%d/%d/%d\n", curSel->wYear, curSel->wMonth, curSel->wDay);
1548 1549 1550
  return TRUE;
}

1551
static LRESULT
1552
MONTHCAL_SetCurSel(MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel)
1553
{
1554
  SYSTEMTIME prev = infoPtr->minSel, selection;
1555
  INT diff;
1556
  WORD day;
1557

1558 1559
  TRACE("%p\n", curSel);
  if(!curSel) return FALSE;
1560
  if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
1561

1562
  if(!MONTHCAL_ValidateDate(curSel)) return FALSE;
1563
  /* exit earlier if selection equals current */
1564
  if (MONTHCAL_IsDateEqual(&infoPtr->minSel, curSel)) return TRUE;
1565

1566 1567 1568 1569 1570
  selection = *curSel;
  selection.wHour = selection.wMinute = selection.wSecond = selection.wMilliseconds = 0;
  MONTHCAL_CalculateDayOfWeek(&selection, TRUE);

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

1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587
  /* 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);
  }

1588
  /* we need to store time part as it is */
1589 1590 1591
  selection = *curSel;
  MONTHCAL_CalculateDayOfWeek(&selection, TRUE);
  infoPtr->minSel = infoPtr->maxSel = selection;
1592

1593
  /* if selection is still in current month, reduce rectangle */
1594
  day = prev.wDay;
1595 1596 1597 1598
  prev.wDay = curSel->wDay;
  if (MONTHCAL_IsDateEqual(&prev, curSel))
  {
    RECT r_prev, r_new;
1599

1600
    prev.wDay = day;
1601 1602
    MONTHCAL_GetDayRect(infoPtr, &prev, &r_prev, -1);
    MONTHCAL_GetDayRect(infoPtr, curSel, &r_new, -1);
1603 1604 1605 1606 1607 1608 1609

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

1610 1611 1612
  return TRUE;
}

1613

1614
static LRESULT
1615
MONTHCAL_GetMaxSelCount(const MONTHCAL_INFO *infoPtr)
1616 1617 1618 1619
{
  return infoPtr->maxSelCount;
}

1620

1621
static LRESULT
1622
MONTHCAL_SetMaxSelCount(MONTHCAL_INFO *infoPtr, INT max)
1623
{
1624
  TRACE("%d\n", max);
1625

1626 1627 1628 1629
  if(!(infoPtr->dwStyle & MCS_MULTISELECT)) return FALSE;
  if(max <= 0) return FALSE;

  infoPtr->maxSelCount = max;
1630 1631 1632 1633 1634

  return TRUE;
}


1635
static LRESULT
1636
MONTHCAL_GetSelRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1637
{
1638
  TRACE("%p\n", range);
1639

1640
  if(!range) return FALSE;
1641

1642
  if(infoPtr->dwStyle & MCS_MULTISELECT)
1643
  {
1644 1645
    range[1] = infoPtr->maxSel;
    range[0] = infoPtr->minSel;
1646
    TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
1647
    return TRUE;
1648
  }
1649

1650 1651 1652
  return FALSE;
}

1653

1654
static LRESULT
1655
MONTHCAL_SetSelRange(MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1656
{
1657
  SYSTEMTIME old_range[2];
1658
  INT diff;
1659

1660
  TRACE("%p\n", range);
1661

1662
  if(!range || !(infoPtr->dwStyle & MCS_MULTISELECT)) return FALSE;
1663

1664 1665 1666
  /* adjust timestamps */
  if(!MONTHCAL_ValidateTime(&range[0])) MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]);
  if(!MONTHCAL_ValidateTime(&range[1])) MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]);
1667

1668 1669
  /* maximum range exceeded */
  if(!MONTHCAL_IsSelRangeValid(infoPtr, &range[0], &range[1], NULL)) return FALSE;
1670

1671 1672
  old_range[0] = infoPtr->minSel;
  old_range[1] = infoPtr->maxSel;
1673

1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684
  /* 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];
  }
1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699

  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);
  }
1700

1701 1702 1703
  /* update day of week */
  MONTHCAL_CalculateDayOfWeek(&infoPtr->minSel, TRUE);
  MONTHCAL_CalculateDayOfWeek(&infoPtr->maxSel, TRUE);
1704

1705 1706 1707 1708 1709 1710
  /* 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);
1711
  }
1712

1713 1714
  TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
  return TRUE;
1715 1716 1717
}


1718
static LRESULT
1719
MONTHCAL_GetToday(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *today)
1720
{
1721
  TRACE("%p\n", today);
1722

1723
  if(!today) return FALSE;
1724
  *today = infoPtr->todaysDate;
1725 1726 1727
  return TRUE;
}

1728 1729 1730 1731 1732 1733 1734 1735 1736 1737
/* 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)
{
1738 1739 1740 1741
    RECT rect;

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

1743
    /* Invalidate old and new today day rectangle, and today label. */
1744
    if (MONTHCAL_GetDayRect(infoPtr, &infoPtr->todaysDate, &rect, -1))
1745
        InvalidateRect(infoPtr->hwndSelf, &rect, FALSE);
1746

1747
    if (MONTHCAL_GetDayRect(infoPtr, today, &rect, -1))
1748
        InvalidateRect(infoPtr->hwndSelf, &rect, FALSE);
1749

1750
    infoPtr->todaysDate = *today;
1751

1752 1753
    InvalidateRect(infoPtr->hwndSelf, &infoPtr->todayrect, FALSE);
    return TRUE;
1754 1755 1756
}

/* MCM_SETTODAT handler */
1757
static LRESULT
1758
MONTHCAL_SetToday(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *today)
1759
{
1760
  TRACE("%p\n", today);
1761

1762 1763 1764 1765 1766
  if (today)
  {
    /* remember if date was set successfully */
    if (MONTHCAL_UpdateToday(infoPtr, today)) infoPtr->todaySet = TRUE;
  }
1767

1768
  return 0;
1769 1770
}

1771 1772 1773 1774 1775 1776
/* 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;

1777
  for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790
  {
     /* 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;
}

1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801
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;
}

1802
static LRESULT
1803
MONTHCAL_HitTest(const MONTHCAL_INFO *infoPtr, MCHITTESTINFO *lpht)
1804
{
1805
  MCHITTESTINFO htinfo;
1806
  SYSTEMTIME *ht_month;
1807
  INT day, calIdx;
1808

1809
  if(!lpht || lpht->cbSize < MCHITTESTINFO_V1_SIZE) return -1;
1810

1811 1812 1813 1814 1815
  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);
1816

1817 1818
  /* guess in what calendar we are */
  calIdx = MONTHCAL_GetCalendarFromPoint(infoPtr, &lpht->pt);
1819 1820 1821
  if (calIdx == -1)
  {
    if (PtInRect(&infoPtr->todayrect, lpht->pt))
1822 1823 1824 1825
    {
      htinfo.uHit = MCHT_TODAYLINK;
      htinfo.rc = infoPtr->todayrect;
    }
1826 1827
    else
      /* outside of calendar area? What's left must be background :-) */
1828
      htinfo.uHit = MCHT_CALENDARBK;
1829

1830
    return fill_hittest_info(&htinfo, lpht);
1831
  }
1832

1833
  /* are we in the header? */
1834
  if (PtInRect(&infoPtr->calendars[calIdx].title, lpht->pt)) {
1835 1836
    /* FIXME: buttons hittesting could be optimized cause maximum
              two calendars have buttons */
1837 1838
    if (calIdx == 0 && PtInRect(&infoPtr->titlebtnprev, lpht->pt))
    {
1839 1840
      htinfo.uHit = MCHT_TITLEBTNPREV;
      htinfo.rc = infoPtr->titlebtnprev;
1841
    }
1842 1843
    else if (PtInRect(&infoPtr->titlebtnnext, lpht->pt))
    {
1844 1845
      htinfo.uHit = MCHT_TITLEBTNNEXT;
      htinfo.rc = infoPtr->titlebtnnext;
1846
    }
1847 1848
    else if (PtInRect(&infoPtr->calendars[calIdx].titlemonth, lpht->pt))
    {
1849 1850 1851
      htinfo.uHit = MCHT_TITLEMONTH;
      htinfo.rc = infoPtr->calendars[calIdx].titlemonth;
      htinfo.iOffset = calIdx;
1852
    }
1853 1854
    else if (PtInRect(&infoPtr->calendars[calIdx].titleyear, lpht->pt))
    {
1855 1856 1857
      htinfo.uHit = MCHT_TITLEYEAR;
      htinfo.rc = infoPtr->calendars[calIdx].titleyear;
      htinfo.iOffset = calIdx;
1858
    }
1859
    else
1860 1861 1862 1863 1864
    {
      htinfo.uHit = MCHT_TITLE;
      htinfo.rc = infoPtr->calendars[calIdx].title;
      htinfo.iOffset = calIdx;
    }
1865

1866
    return fill_hittest_info(&htinfo, lpht);
1867
  }
1868

1869 1870
  ht_month = &infoPtr->calendars[calIdx].month;
  /* days area (including week days and week numbers) */
1871
  day = MONTHCAL_GetDayFromPos(infoPtr, lpht->pt, calIdx);
1872 1873
  if (PtInRect(&infoPtr->calendars[calIdx].wdays, lpht->pt))
  {
1874 1875
    htinfo.uHit = MCHT_CALENDARDAY;
    htinfo.iOffset = calIdx;
1876 1877
    htinfo.st.wYear  = ht_month->wYear;
    htinfo.st.wMonth = (day < 1) ? ht_month->wMonth -1 : ht_month->wMonth;
1878
    htinfo.st.wDay   = (day < 1) ?
1879
      MONTHCAL_MonthLength(ht_month->wMonth-1, ht_month->wYear) - day : day;
1880

1881
    MONTHCAL_GetDayPos(infoPtr, &htinfo.st, &htinfo.iCol, &htinfo.iRow, calIdx);
1882
  }
1883 1884
  else if(PtInRect(&infoPtr->calendars[calIdx].weeknums, lpht->pt))
  {
1885
    htinfo.uHit = MCHT_CALENDARWEEKNUM;
1886
    htinfo.st.wYear = ht_month->wYear;
1887
    htinfo.iOffset = calIdx;
1888

1889 1890
    if (day < 1)
    {
1891 1892
      htinfo.st.wMonth = ht_month->wMonth - 1;
      htinfo.st.wDay = MONTHCAL_MonthLength(ht_month->wMonth-1, ht_month->wYear) - day;
1893
    }
1894
    else if (day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear))
1895
    {
1896 1897
      htinfo.st.wMonth = ht_month->wMonth + 1;
      htinfo.st.wDay = day - MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear);
1898 1899
    }
    else
1900
    {
1901
      htinfo.st.wMonth = ht_month->wMonth;
1902
      htinfo.st.wDay = day;
1903
    }
1904
  }
1905
  else if(PtInRect(&infoPtr->calendars[calIdx].days, lpht->pt))
1906
  {
1907
      htinfo.iOffset = calIdx;
1908
      htinfo.st.wDay = ht_month->wDay;
1909 1910 1911 1912
      htinfo.st.wYear  = ht_month->wYear;
      htinfo.st.wMonth = ht_month->wMonth;
      /* previous month only valid for first calendar */
      if (day < 1 && calIdx == 0)
1913
      {
1914 1915
	  htinfo.uHit = MCHT_CALENDARDATEPREV;
	  MONTHCAL_GetPrevMonth(&htinfo.st);
1916
	  htinfo.st.wDay = MONTHCAL_MonthLength(htinfo.st.wMonth, htinfo.st.wYear) + day;
1917
      }
1918 1919 1920
      /* next month only valid for last calendar */
      else if (day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear) &&
               calIdx == MONTHCAL_GetCalCount(infoPtr)-1)
1921
      {
1922 1923
	  htinfo.uHit = MCHT_CALENDARDATENEXT;
	  MONTHCAL_GetNextMonth(&htinfo.st);
1924 1925 1926 1927 1928 1929
	  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;
1930
      }
1931 1932 1933 1934
      else
      {
	htinfo.uHit = MCHT_CALENDARDATE;
	htinfo.st.wDay = day;
1935
      }
1936

1937 1938
      MONTHCAL_GetDayPos(infoPtr, &htinfo.st, &htinfo.iCol, &htinfo.iRow, calIdx);
      MONTHCAL_GetDayRectI(infoPtr, &htinfo.rc, htinfo.iCol, htinfo.iRow, calIdx);
1939
      /* always update day of week */
1940
      MONTHCAL_CalculateDayOfWeek(&htinfo.st, TRUE);
1941
  }
1942

1943
  return fill_hittest_info(&htinfo, lpht);
1944 1945
}

1946 1947
/* MCN_GETDAYSTATE notification helper */
static void MONTHCAL_NotifyDayState(MONTHCAL_INFO *infoPtr)
1948
{
1949 1950
  MONTHDAYSTATE *state;
  NMDAYSTATE nmds;
1951

1952
  if (!(infoPtr->dwStyle & MCS_DAYSTATE)) return;
1953

1954 1955 1956 1957
  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);
1958
  nmds.prgDayState    = state = heap_alloc_zero(nmds.cDayState * sizeof(MONTHDAYSTATE));
1959

1960 1961
  MONTHCAL_GetMinDate(infoPtr, &nmds.stStart);
  nmds.stStart.wDay = 1;
1962

1963 1964 1965 1966
  SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmds.nmhdr.idFrom, (LPARAM)&nmds);
  memcpy(infoPtr->monthdayState, nmds.prgDayState,
      MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)*sizeof(MONTHDAYSTATE));

1967
  heap_free(state);
1968 1969
}

1970
/* no valid range check performed */
1971
static void MONTHCAL_Scroll(MONTHCAL_INFO *infoPtr, INT delta, BOOL keep_selection)
1972 1973 1974
{
  INT i, selIdx = -1;

1975
  for(i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1976 1977 1978 1979 1980 1981 1982 1983
  {
    /* 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);
  }

1984 1985 1986
  if (keep_selection)
    return;

1987
  /* selection is always shifted to first calendar */
1988
  if (infoPtr->dwStyle & MCS_MULTISELECT)
1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005
  {
    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);
  }
}

2006
static void MONTHCAL_GoToMonth(MONTHCAL_INFO *infoPtr, enum nav_direction direction)
2007
{
2008
  INT delta = infoPtr->delta ? infoPtr->delta : MONTHCAL_GetCalCount(infoPtr);
2009
  BOOL keep_selection;
2010
  SYSTEMTIME st;
2011

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

2014 2015 2016 2017 2018 2019 2020 2021
  /* check if change allowed by range set */
  if(direction == DIRECTION_BACKWARD)
  {
    st = infoPtr->calendars[0].month;
    MONTHCAL_GetMonth(&st, -delta);
  }
  else
  {
2022
    st = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
2023 2024
    MONTHCAL_GetMonth(&st, delta);
  }
2025

2026
  if(!MONTHCAL_IsDateInValidRange(infoPtr, &st, FALSE)) return;
2027

2028 2029
  keep_selection = infoPtr->dwStyle & MCS_NOSELCHANGEONNAV;
  MONTHCAL_Scroll(infoPtr, direction == DIRECTION_BACKWARD ? -delta : delta, keep_selection);
2030
  MONTHCAL_NotifyDayState(infoPtr);
2031 2032
  if (!keep_selection)
    MONTHCAL_NotifySelectionChange(infoPtr);
2033 2034
}

2035
static LRESULT
2036
MONTHCAL_RButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2037 2038 2039
{
  HMENU hMenu;
  POINT menupoint;
2040
  WCHAR buf[32];
2041

2042
  hMenu = CreatePopupMenu();
2043
  LoadStringW(COMCTL32_hModule, IDM_GOTODAY, buf, ARRAY_SIZE(buf));
2044 2045 2046
  AppendMenuW(hMenu, MF_STRING|MF_ENABLED, 1, buf);
  menupoint.x = (short)LOWORD(lParam);
  menupoint.y = (short)HIWORD(lParam);
2047
  ClientToScreen(infoPtr->hwndSelf, &menupoint);
2048
  if( TrackPopupMenu(hMenu, TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD,
2049
		     menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL))
2050
  {
2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062
      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);
2063 2064
  }

2065 2066
  return 0;
}
2067

2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114
/***
 * 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;
}

2115
/* creates updown control and edit box */
2116
static void MONTHCAL_EditYear(MONTHCAL_INFO *infoPtr, INT calIdx)
2117
{
2118 2119 2120
    RECT *rc = &infoPtr->calendars[calIdx].titleyear;
    RECT *title = &infoPtr->calendars[calIdx].title;

2121
    infoPtr->hWndYearEdit =
2122
	CreateWindowExW(0, WC_EDITW, 0, WS_VISIBLE | WS_CHILD | ES_READONLY,
2123 2124
			rc->left + 3, (title->bottom + title->top - infoPtr->textHeight) / 2,
			rc->right - rc->left + 4,
2125 2126 2127 2128 2129 2130 2131 2132
			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,
2133
			rc->right + 7, (title->bottom + title->top - infoPtr->textHeight) / 2,
2134 2135 2136 2137
			18, infoPtr->textHeight, infoPtr->hwndSelf,
			NULL, NULL, NULL);

    /* attach edit box */
2138 2139
    SendMessageW(infoPtr->hWndYearUpDown, UDM_SETRANGE, 0,
                 MAKELONG(max_allowed_date.wYear, min_allowed_date.wYear));
2140
    SendMessageW(infoPtr->hWndYearUpDown, UDM_SETBUDDY, (WPARAM)infoPtr->hWndYearEdit, 0);
2141
    SendMessageW(infoPtr->hWndYearUpDown, UDM_SETPOS, 0, infoPtr->calendars[calIdx].month.wYear);
2142 2143 2144 2145 2146 2147

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

    SetFocus(infoPtr->hWndYearEdit);
2148 2149
}

2150
static LRESULT
2151
MONTHCAL_LButtonDown(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2152
{
2153 2154
  MCHITTESTINFO ht;
  DWORD hit;
2155

2156 2157 2158
  /* 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))
2159
  {
2160 2161
      SetFocus(infoPtr->hwndSelf);
      return 0;
2162
  }
2163

2164 2165
  SetCapture(infoPtr->hwndSelf);

2166
  ht.cbSize = sizeof(MCHITTESTINFO);
2167 2168
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
2169

2170
  hit = MONTHCAL_HitTest(infoPtr, &ht);
2171

2172
  TRACE("%x at %s\n", hit, wine_dbgstr_point(&ht.pt));
2173

2174 2175 2176
  switch(hit)
  {
  case MCHT_TITLEBTNNEXT:
2177
    MONTHCAL_GoToMonth(infoPtr, DIRECTION_FORWARD);
2178
    infoPtr->status = MC_NEXTPRESSED;
2179
    SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0);
2180
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2181
    return 0;
2182 2183

  case MCHT_TITLEBTNPREV:
2184
    MONTHCAL_GoToMonth(infoPtr, DIRECTION_BACKWARD);
2185
    infoPtr->status = MC_PREVPRESSED;
2186
    SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0);
2187
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2188
    return 0;
2189

2190 2191 2192 2193 2194 2195
  case MCHT_TITLEMONTH:
  {
    HMENU hMenu = CreatePopupMenu();
    WCHAR buf[32];
    POINT menupoint;
    INT i;
2196

2197 2198
    for (i = 0; i < 12; i++)
    {
2199 2200
        GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+i, buf, ARRAY_SIZE(buf));
        AppendMenuW(hMenu, MF_STRING|MF_ENABLED, i + 1, buf);
2201
    }
2202 2203
    menupoint.x = ht.pt.x;
    menupoint.y = ht.pt.y;
2204
    ClientToScreen(infoPtr->hwndSelf, &menupoint);
2205 2206 2207
    i = TrackPopupMenu(hMenu,TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON | TPM_RETURNCMD,
		       menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL);

2208
    if ((i > 0) && (i < 13) && infoPtr->calendars[ht.iOffset].month.wMonth != i)
2209
    {
2210 2211 2212 2213 2214
        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 :
2215
                         infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
2216 2217 2218 2219
        MONTHCAL_GetMonth(&st, delta);

        if (MONTHCAL_IsDateInValidRange(infoPtr, &st, FALSE))
        {
2220
            MONTHCAL_Scroll(infoPtr, delta, FALSE);
2221 2222 2223 2224
            MONTHCAL_NotifyDayState(infoPtr);
            MONTHCAL_NotifySelectionChange(infoPtr);
            InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
        }
2225
    }
2226
    return 0;
2227
  }
2228 2229
  case MCHT_TITLEYEAR:
  {
2230
    MONTHCAL_EditYear(infoPtr, ht.iOffset);
2231
    return 0;
2232
  }
2233 2234
  case MCHT_TODAYLINK:
  {
2235 2236 2237 2238 2239 2240 2241 2242 2243
    if (infoPtr->dwStyle & MCS_MULTISELECT)
    {
        SYSTEMTIME range[2];

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

2245 2246
    MONTHCAL_NotifySelectionChange(infoPtr);
    MONTHCAL_NotifySelect(infoPtr);
2247
    return 0;
2248
  }
2249 2250
  case MCHT_CALENDARDATENEXT:
  case MCHT_CALENDARDATEPREV:
2251 2252
  case MCHT_CALENDARDATE:
  {
2253
    SYSTEMTIME st[2];
2254

2255
    MONTHCAL_CopyDate(&ht.st, &infoPtr->firstSel);
2256

2257 2258 2259
    st[0] = st[1] = ht.st;
    /* clear selection range */
    MONTHCAL_SetSelRange(infoPtr, st);
2260

2261
    infoPtr->status = MC_SEL_LBUTDOWN;
2262
    MONTHCAL_SetDayFocus(infoPtr, &ht.st);
2263
    return 0;
2264
  }
2265
  }
2266

2267
  return 1;
2268 2269
}

2270

2271
static LRESULT
2272
MONTHCAL_LButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2273
{
2274
  NMHDR nmhdr;
2275 2276
  MCHITTESTINFO ht;
  DWORD hit;
2277 2278

  TRACE("\n");
2279

2280
  if(infoPtr->status & (MC_PREVPRESSED | MC_NEXTPRESSED)) {
2281 2282
    RECT *r;

2283
    KillTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER);
2284
    r = infoPtr->status & MC_PREVPRESSED ? &infoPtr->titlebtnprev : &infoPtr->titlebtnnext;
2285
    infoPtr->status &= ~(MC_PREVPRESSED | MC_NEXTPRESSED);
2286 2287

    InvalidateRect(infoPtr->hwndSelf, r, FALSE);
2288
  }
2289

2290 2291
  ReleaseCapture();

2292 2293 2294 2295 2296 2297 2298 2299
  /* 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);

2300 2301
  if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;

2302
  ht.cbSize = sizeof(MCHITTESTINFO);
2303 2304
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
2305
  hit = MONTHCAL_HitTest(infoPtr, &ht);
2306

2307
  infoPtr->status = MC_SEL_LBUTUP;
2308
  MONTHCAL_SetDayFocus(infoPtr, NULL);
2309

2310
  if((hit & MCHT_CALENDARDATE) == MCHT_CALENDARDATE)
2311
  {
2312
    SYSTEMTIME sel = infoPtr->minSel;
2313

2314 2315
    /* will be invalidated here */
    MONTHCAL_SetCurSel(infoPtr, &ht.st);
2316

2317 2318
    /* send MCN_SELCHANGE only if new date selected */
    if (!MONTHCAL_IsDateEqual(&sel, &ht.st))
2319
        MONTHCAL_NotifySelectionChange(infoPtr);
2320

2321
    MONTHCAL_NotifySelect(infoPtr);
2322
  }
2323

2324
  return 0;
2325 2326
}

2327

2328
static LRESULT
2329
MONTHCAL_Timer(MONTHCAL_INFO *infoPtr, WPARAM id)
2330
{
2331
  TRACE("%ld\n", id);
2332

2333 2334
  switch(id) {
  case MC_PREVNEXTMONTHTIMER:
2335 2336
    if(infoPtr->status & MC_NEXTPRESSED) MONTHCAL_GoToMonth(infoPtr, DIRECTION_FORWARD);
    if(infoPtr->status & MC_PREVPRESSED) MONTHCAL_GoToMonth(infoPtr, DIRECTION_BACKWARD);
2337
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2338
    break;
2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352
  case MC_TODAYUPDATETIMER:
  {
    SYSTEMTIME st;

    if(infoPtr->todaySet) return 0;

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

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

    return 0;
  }
2353
  default:
2354
    ERR("got unknown timer %ld\n", id);
2355
    break;
2356
  }
2357

2358 2359
  return 0;
}
2360

2361 2362

static LRESULT
2363
MONTHCAL_MouseMove(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2364
{
2365
  MCHITTESTINFO ht;
2366
  SYSTEMTIME st_ht;
2367
  INT hit;
2368
  RECT r;
2369

2370
  if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
2371

2372
  ht.cbSize = sizeof(MCHITTESTINFO);
2373 2374
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
2375
  ht.iOffset = -1;
2376

2377
  hit = MONTHCAL_HitTest(infoPtr, &ht);
2378

2379
  /* not on the calendar date numbers? bail out */
2380
  TRACE("hit:%x\n",hit);
2381 2382 2383 2384 2385
  if((hit & MCHT_CALENDARDATE) != MCHT_CALENDARDATE)
  {
    MONTHCAL_SetDayFocus(infoPtr, NULL);
    return 0;
  }
2386

2387
  st_ht = ht.st;
2388 2389 2390

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

2392
  MONTHCAL_GetDayRect(infoPtr, &ht.st, &r, ht.iOffset);
2393

2394
  if(infoPtr->dwStyle & MCS_MULTISELECT) {
2395 2396 2397 2398
    SYSTEMTIME st[2];

    MONTHCAL_GetSelRange(infoPtr, st);

2399 2400 2401 2402
    /* 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;
2403

2404
    MONTHCAL_IsSelRangeValid(infoPtr, &st_ht, &infoPtr->firstSel, &st_ht);
2405

2406 2407 2408
    st[0] = infoPtr->firstSel;
    /* we should overwrite timestamp here */
    MONTHCAL_CopyDate(&st_ht, &st[1]);
2409

2410 2411 2412 2413
    /* bounds will be swapped here if needed */
    MONTHCAL_SetSelRange(infoPtr, st);

    return 0;
2414
  }
2415 2416 2417

done:

2418 2419 2420
  /* FIXME: this should specify a rectangle containing only the days that changed
     using InvalidateRect */
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2421

2422
  return 0;
2423 2424
}

2425

2426
static LRESULT
2427
MONTHCAL_Paint(MONTHCAL_INFO *infoPtr, HDC hdc_paint)
2428
{
2429 2430
  HDC hdc;
  PAINTSTRUCT ps;
2431

2432
  if (hdc_paint)
2433
  {
2434
    GetClientRect(infoPtr->hwndSelf, &ps.rcPaint);
2435
    hdc = hdc_paint;
2436 2437
  }
  else
2438
    hdc = BeginPaint(infoPtr->hwndSelf, &ps);
2439

2440
  MONTHCAL_Refresh(infoPtr, hdc, &ps);
2441
  if (!hdc_paint) EndPaint(infoPtr->hwndSelf, &ps);
2442
  return 0;
2443 2444
}

2445 2446 2447 2448 2449 2450 2451
static LRESULT
MONTHCAL_EraseBkgnd(const MONTHCAL_INFO *infoPtr, HDC hdc)
{
  RECT rc;

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

2452
  FillRect(hdc, &rc, infoPtr->brushes[BrushBackground]);
2453 2454 2455 2456

  return TRUE;
}

2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473
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;
}

2474
static LRESULT
2475
MONTHCAL_SetFocus(const MONTHCAL_INFO *infoPtr)
2476
{
2477
  TRACE("\n");
2478

2479
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2480

2481
  return 0;
2482 2483
}

2484
/* sets the size information */
2485
static void MONTHCAL_UpdateSize(MONTHCAL_INFO *infoPtr)
2486
{
2487
  static const WCHAR O0W[] = { '0','0',0 };
2488
  RECT *title=&infoPtr->calendars[0].title;
2489 2490
  RECT *prev=&infoPtr->titlebtnprev;
  RECT *next=&infoPtr->titlebtnnext;
2491 2492 2493 2494 2495
  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;
2496
  RECT *todayrect=&infoPtr->todayrect;
2497 2498 2499

  INT xdiv, dx, dy, i, j, x, y, c_dx, c_dy;
  WCHAR buff[80];
2500
  TEXTMETRICW tm;
2501
  INT day_width;
2502
  RECT client;
2503
  HFONT font;
2504
  SIZE size;
2505
  HDC hdc;
2506

2507
  GetClientRect(infoPtr->hwndSelf, &client);
2508

2509 2510
  hdc = GetDC(infoPtr->hwndSelf);
  font = SelectObject(hdc, infoPtr->hFont);
2511 2512

  /* get the height and width of each day's text */
2513
  GetTextMetricsW(hdc, &tm);
2514
  infoPtr->textHeight = tm.tmHeight + tm.tmExternalLeading + tm.tmInternalLeading;
2515

2516 2517
  /* find widest day name for current locale and font */
  day_width = 0;
2518 2519
  for (i = 0; i < 7; i++)
  {
2520 2521
      SIZE sz;

2522
      if (get_localized_dayname(infoPtr, i, buff, ARRAY_SIZE(buff)))
2523 2524
      {
          GetTextExtentPoint32W(hdc, buff, lstrlenW(buff), &sz);
2525
          if (sz.cx > day_width) day_width = sz.cx;
2526 2527 2528
      }
      else /* locale independent fallback on failure */
      {
2529
          static const WCHAR sunW[] = { 'S','u','n' };
2530
          GetTextExtentPoint32W(hdc, sunW, ARRAY_SIZE(sunW), &sz);
2531
          day_width = sz.cx;
2532 2533 2534 2535
          break;
      }
  }

2536
  day_width += 2;
2537

2538
  /* recalculate the height and width increments and offsets */
2539
  size.cx = 0;
2540
  GetTextExtentPoint32W(hdc, O0W, 2, &size);
2541

2542 2543 2544 2545
  /* restore the originally selected font */
  SelectObject(hdc, font);
  ReleaseDC(infoPtr->hwndSelf, hdc);

2546
  xdiv = (infoPtr->dwStyle & MCS_WEEKNUMBERS) ? 8 : 7;
2547

2548
  infoPtr->width_increment  = max(day_width, size.cx * 2 + 4);
2549
  infoPtr->height_increment = infoPtr->textHeight;
2550

2551
  /* calculate title area */
2552 2553 2554 2555
  title->top    = 0;
  title->bottom = 3 * infoPtr->height_increment / 2;
  title->left   = 0;
  title->right  = infoPtr->width_increment * xdiv;
2556 2557 2558

  /* set the dimensions of the next and previous buttons and center */
  /* the month text vertically */
2559 2560 2561
  prev->top    = next->top    = title->top + 4;
  prev->bottom = next->bottom = title->bottom - 4;
  prev->left   = title->left + 4;
2562
  prev->right  = prev->left + (title->bottom - title->top);
2563
  next->right  = title->right - 4;
2564
  next->left   = next->right - (title->bottom - title->top);
2565

2566 2567 2568
  /* 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 */
2569 2570
  titlemonth->top    = titleyear->top    = title->top    + (infoPtr->height_increment)/2;
  titlemonth->bottom = titleyear->bottom = title->bottom - (infoPtr->height_increment)/2;
2571

2572 2573 2574
  /* week numbers */
  weeknumrect->left  = 0;
  weeknumrect->right = infoPtr->dwStyle & MCS_WEEKNUMBERS ? prev->right : 0;
2575

2576
  /* days abbreviated names */
2577 2578
  wdays->left   = days->left   = weeknumrect->right;
  wdays->right  = days->right  = wdays->left + 7 * infoPtr->width_increment;
2579
  wdays->top    = title->bottom;
2580
  wdays->bottom = wdays->top + infoPtr->height_increment;
2581

2582
  days->top    = weeknumrect->top = wdays->bottom;
2583
  days->bottom = weeknumrect->bottom = days->top + 6 * infoPtr->height_increment;
2584

2585 2586
  todayrect->left   = 0;
  todayrect->right  = title->right;
2587 2588 2589
  todayrect->top    = days->bottom;
  todayrect->bottom = days->bottom + infoPtr->height_increment;

2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602
  /* 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;

2603 2604 2605 2606
  if (x*y != MONTHCAL_GetCalCount(infoPtr))
  {
      infoPtr->dim.cx = x;
      infoPtr->dim.cy = y;
2607
      infoPtr->calendars = heap_realloc(infoPtr->calendars, MONTHCAL_GetCalCount(infoPtr)*sizeof(CALENDAR_INFO));
2608

2609
      infoPtr->monthdayState = heap_realloc(infoPtr->monthdayState,
2610 2611
          MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)*sizeof(MONTHDAYSTATE));
      MONTHCAL_NotifyDayState(infoPtr);
2612 2613 2614 2615 2616

      /* update pointers that we'll need */
      title = &infoPtr->calendars[0].title;
      wdays = &infoPtr->calendars[0].wdays;
      days  = &infoPtr->calendars[0].days;
2617
  }
2618 2619 2620 2621 2622 2623 2624 2625

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

2626
  /* offset all rectangles to center in client area */
2627 2628
  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;
2629 2630

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

2634
  for (i = 0; i < y; i++)
2635
  {
2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647
      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);
      }
2648 2649
  }

2650 2651 2652 2653 2654 2655 2656 2657
  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;
2658

2659
  TRACE("dx=%d dy=%d client[%s] title[%s] wdays[%s] days[%s] today[%s]\n",
2660
	infoPtr->width_increment,infoPtr->height_increment,
2661
        wine_dbgstr_rect(&client),
2662 2663 2664 2665
        wine_dbgstr_rect(title),
        wine_dbgstr_rect(wdays),
        wine_dbgstr_rect(days),
        wine_dbgstr_rect(todayrect));
2666 2667
}

2668
static LRESULT MONTHCAL_Size(MONTHCAL_INFO *infoPtr, int Width, int Height)
2669
{
2670
  TRACE("(width=%d, height=%d)\n", Width, Height);
2671

2672 2673
  MONTHCAL_UpdateSize(infoPtr);
  InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
2674 2675 2676

  return 0;
}
2677

2678
static LRESULT MONTHCAL_GetFont(const MONTHCAL_INFO *infoPtr)
2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696
{
    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);

2697 2698
    MONTHCAL_UpdateSize(infoPtr);

2699 2700 2701 2702 2703 2704
    if (redraw)
        InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);

    return (LRESULT)hOldFont;
}

2705
/* update theme after a WM_THEMECHANGED message */
2706
static LRESULT theme_changed (const MONTHCAL_INFO* infoPtr)
2707 2708 2709
{
    HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
    CloseThemeData (theme);
2710
    OpenThemeData (infoPtr->hwndSelf, themeClass);
2711 2712 2713
    return 0;
}

2714 2715 2716 2717 2718 2719 2720 2721 2722 2723
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;

2724
    /* make room for week numbers */
2725
    if ((lpss->styleNew ^ lpss->styleOld) & (MCS_WEEKNUMBERS | MCS_SHORTDAYSOFWEEK))
2726 2727
        MONTHCAL_UpdateSize(infoPtr);

2728 2729 2730
    return 0;
}

2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745
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;
    }

2746 2747 2748 2749 2750 2751 2752 2753 2754
    /* 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;
    }

2755 2756 2757
    return 0;
}

2758
/* FIXME: check whether dateMin/dateMax need to be adjusted. */
2759
static LRESULT
2760
MONTHCAL_Create(HWND hwnd, LPCREATESTRUCTW lpcs)
2761
{
2762
  MONTHCAL_INFO *infoPtr;
2763

2764
  /* allocate memory for info structure */
2765
  infoPtr = heap_alloc_zero(sizeof(*infoPtr));
2766
  SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2767

2768 2769
  if (infoPtr == NULL) {
    ERR("could not allocate info memory!\n");
2770 2771
    return 0;
  }
2772

2773
  infoPtr->hwndSelf = hwnd;
2774
  infoPtr->hwndNotify = lpcs->hwndParent;
2775 2776
  infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
  infoPtr->dim.cx = infoPtr->dim.cy = 1;
2777
  infoPtr->calendars = heap_alloc_zero(sizeof(CALENDAR_INFO));
2778
  if (!infoPtr->calendars) goto fail;
2779
  infoPtr->monthdayState = heap_alloc_zero(3 * sizeof(MONTHDAYSTATE));
2780
  if (!infoPtr->monthdayState) goto fail;
2781 2782

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

Duane Clark's avatar
Duane Clark committed
2785
  GetLocalTime(&infoPtr->todaysDate);
2786
  MONTHCAL_SetFirstDayOfWeek(infoPtr, -1);
2787

2788
  infoPtr->maxSelCount   = (infoPtr->dwStyle & MCS_MULTISELECT) ? 7 : 1;
2789

2790 2791 2792 2793 2794 2795
  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;
2796

2797 2798 2799
  infoPtr->brushes[BrushBackground]  = CreateSolidBrush(infoPtr->colors[MCSC_BACKGROUND]);
  infoPtr->brushes[BrushTitle]       = CreateSolidBrush(infoPtr->colors[MCSC_TITLEBK]);
  infoPtr->brushes[BrushMonth]       = CreateSolidBrush(infoPtr->colors[MCSC_MONTHBK]);
2800

2801 2802 2803
  infoPtr->pens[PenRed]  = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
  infoPtr->pens[PenText] = CreatePen(PS_SOLID, 1, infoPtr->colors[MCSC_TEXT]);

2804 2805
  infoPtr->minSel = infoPtr->todaysDate;
  infoPtr->maxSel = infoPtr->todaysDate;
2806
  infoPtr->calendars[0].month = infoPtr->todaysDate;
2807
  infoPtr->isUnicode = TRUE;
2808

2809
  /* setup control layout and day state data */
2810
  MONTHCAL_UpdateSize(infoPtr);
2811 2812 2813 2814

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

2815
  OpenThemeData (infoPtr->hwndSelf, themeClass);
2816 2817

  return 0;
2818

2819
fail:
2820 2821 2822
  heap_free(infoPtr->monthdayState);
  heap_free(infoPtr->calendars);
  heap_free(infoPtr);
2823 2824
  return 0;
}
2825 2826

static LRESULT
2827
MONTHCAL_Destroy(MONTHCAL_INFO *infoPtr)
2828
{
2829 2830
  INT i;

2831
  /* free month calendar info data */
2832 2833
  heap_free(infoPtr->monthdayState);
  heap_free(infoPtr->calendars);
2834
  SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
2835

2836
  CloseThemeData (GetWindowTheme (infoPtr->hwndSelf));
2837

2838 2839
  for (i = 0; i < BrushLast; i++) DeleteObject(infoPtr->brushes[i]);
  for (i = 0; i < PenLast; i++) DeleteObject(infoPtr->pens[i]);
2840

2841
  heap_free(infoPtr);
2842
  return 0;
2843 2844
}

2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855
/*
 * 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;

2856
    if (hdr->hwndFrom == infoPtr->hWndYearUpDown && nmud->iDelta)
2857 2858
    {
      /* year value limits are set up explicitly after updown creation */
2859
      MONTHCAL_Scroll(infoPtr, 12 * nmud->iDelta, FALSE);
2860 2861
      MONTHCAL_NotifyDayState(infoPtr);
      MONTHCAL_NotifySelectionChange(infoPtr);
2862 2863 2864 2865
    }
  }
  return 0;
}
2866

2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880
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;
}

2881
static LRESULT WINAPI
2882
MONTHCAL_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2883
{
2884
  MONTHCAL_INFO *infoPtr = (MONTHCAL_INFO *)GetWindowLongPtrW(hwnd, 0);
2885

2886
  TRACE("hwnd=%p msg=%x wparam=%lx lparam=%lx\n", hwnd, uMsg, wParam, lParam);
2887 2888

  if (!infoPtr && (uMsg != WM_CREATE))
2889
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
2890
  switch(uMsg)
2891 2892
  {
  case MCM_GETCURSEL:
2893
    return MONTHCAL_GetCurSel(infoPtr, (LPSYSTEMTIME)lParam);
2894

2895
  case MCM_SETCURSEL:
2896
    return MONTHCAL_SetCurSel(infoPtr, (LPSYSTEMTIME)lParam);
2897

2898
  case MCM_GETMAXSELCOUNT:
2899
    return MONTHCAL_GetMaxSelCount(infoPtr);
2900

2901
  case MCM_SETMAXSELCOUNT:
2902
    return MONTHCAL_SetMaxSelCount(infoPtr, wParam);
2903

2904
  case MCM_GETSELRANGE:
2905
    return MONTHCAL_GetSelRange(infoPtr, (LPSYSTEMTIME)lParam);
2906

2907
  case MCM_SETSELRANGE:
2908
    return MONTHCAL_SetSelRange(infoPtr, (LPSYSTEMTIME)lParam);
2909

2910
  case MCM_GETMONTHRANGE:
2911
    return MONTHCAL_GetMonthRange(infoPtr, wParam, (SYSTEMTIME*)lParam);
2912

2913
  case MCM_SETDAYSTATE:
2914
    return MONTHCAL_SetDayState(infoPtr, (INT)wParam, (LPMONTHDAYSTATE)lParam);
2915

2916
  case MCM_GETMINREQRECT:
2917
    return MONTHCAL_GetMinReqRect(infoPtr, (LPRECT)lParam);
2918

2919
  case MCM_GETCOLOR:
2920
    return MONTHCAL_GetColor(infoPtr, wParam);
2921

2922
  case MCM_SETCOLOR:
2923
    return MONTHCAL_SetColor(infoPtr, wParam, (COLORREF)lParam);
2924

2925
  case MCM_GETTODAY:
2926
    return MONTHCAL_GetToday(infoPtr, (LPSYSTEMTIME)lParam);
2927

2928
  case MCM_SETTODAY:
2929
    return MONTHCAL_SetToday(infoPtr, (LPSYSTEMTIME)lParam);
2930

2931
  case MCM_HITTEST:
2932
    return MONTHCAL_HitTest(infoPtr, (PMCHITTESTINFO)lParam);
2933

2934
  case MCM_GETFIRSTDAYOFWEEK:
2935
    return MONTHCAL_GetFirstDayOfWeek(infoPtr);
2936

2937
  case MCM_SETFIRSTDAYOFWEEK:
2938
    return MONTHCAL_SetFirstDayOfWeek(infoPtr, (INT)lParam);
2939

2940
  case MCM_GETRANGE:
2941
    return MONTHCAL_GetRange(infoPtr, (LPSYSTEMTIME)lParam);
2942

2943
  case MCM_SETRANGE:
2944
    return MONTHCAL_SetRange(infoPtr, (SHORT)wParam, (LPSYSTEMTIME)lParam);
2945

2946
  case MCM_GETMONTHDELTA:
2947
    return MONTHCAL_GetMonthDelta(infoPtr);
2948

2949
  case MCM_SETMONTHDELTA:
2950
    return MONTHCAL_SetMonthDelta(infoPtr, wParam);
2951

2952
  case MCM_GETMAXTODAYWIDTH:
2953
    return MONTHCAL_GetMaxTodayWidth(infoPtr);
2954

2955 2956 2957 2958 2959 2960
  case MCM_SETUNICODEFORMAT:
    return MONTHCAL_SetUnicodeFormat(infoPtr, (BOOL)wParam);

  case MCM_GETUNICODEFORMAT:
    return MONTHCAL_GetUnicodeFormat(infoPtr);

2961 2962 2963
  case MCM_GETCALENDARCOUNT:
    return MONTHCAL_GetCalCount(infoPtr);

2964 2965
  case WM_GETDLGCODE:
    return DLGC_WANTARROWS | DLGC_WANTCHARS;
2966

2967 2968
  case WM_RBUTTONUP:
    return MONTHCAL_RButtonUp(infoPtr, lParam);
2969

2970
  case WM_LBUTTONDOWN:
2971
    return MONTHCAL_LButtonDown(infoPtr, lParam);
2972

2973
  case WM_MOUSEMOVE:
2974
    return MONTHCAL_MouseMove(infoPtr, lParam);
2975

2976
  case WM_LBUTTONUP:
2977
    return MONTHCAL_LButtonUp(infoPtr, lParam);
2978

2979
  case WM_PAINT:
2980
    return MONTHCAL_Paint(infoPtr, (HDC)wParam);
2981

2982 2983 2984
  case WM_PRINTCLIENT:
    return MONTHCAL_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);

2985 2986 2987
  case WM_ERASEBKGND:
    return MONTHCAL_EraseBkgnd(infoPtr, (HDC)wParam);

2988
  case WM_SETFOCUS:
2989
    return MONTHCAL_SetFocus(infoPtr);
2990 2991

  case WM_SIZE:
2992
    return MONTHCAL_Size(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
2993

2994 2995 2996
  case WM_NOTIFY:
    return MONTHCAL_Notify(infoPtr, (NMHDR*)lParam);

2997
  case WM_CREATE:
2998
    return MONTHCAL_Create(hwnd, (LPCREATESTRUCTW)lParam);
2999

3000 3001 3002 3003 3004 3005
  case WM_SETFONT:
    return MONTHCAL_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);

  case WM_GETFONT:
    return MONTHCAL_GetFont(infoPtr);

3006
  case WM_TIMER:
3007
    return MONTHCAL_Timer(infoPtr, wParam);
3008 3009 3010
    
  case WM_THEMECHANGED:
    return theme_changed (infoPtr);
3011

3012
  case WM_DESTROY:
3013
    return MONTHCAL_Destroy(infoPtr);
3014

3015 3016 3017 3018
  case WM_SYSCOLORCHANGE:
    COMCTL32_RefreshSysColors();
    return 0;

3019 3020 3021
  case WM_STYLECHANGED:
    return MONTHCAL_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);

3022 3023 3024
  case WM_STYLECHANGING:
    return MONTHCAL_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);

3025
  default:
3026
    if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
3027
      ERR( "unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
3028
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3029
  }
3030 3031 3032
}


3033
void
3034
MONTHCAL_Register(void)
3035
{
3036
  WNDCLASSW wndClass;
3037

3038
  ZeroMemory(&wndClass, sizeof(WNDCLASSW));
3039
  wndClass.style         = CS_GLOBALCLASS;
3040
  wndClass.lpfnWndProc   = MONTHCAL_WindowProc;
3041 3042
  wndClass.cbClsExtra    = 0;
  wndClass.cbWndExtra    = sizeof(MONTHCAL_INFO *);
3043
  wndClass.hCursor       = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3044
  wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
3045
  wndClass.lpszClassName = MONTHCAL_CLASSW;
3046

3047
  RegisterClassW(&wndClass);
3048 3049 3050
}


3051
void
3052
MONTHCAL_Unregister(void)
3053
{
3054
    UnregisterClassW(MONTHCAL_CLASSW, NULL);
3055
}