monthcal.c 86.9 KB
Newer Older
1 2
/*
 * Month calendar control
3
 *
4
 * Copyright 1998, 1999 Eric Kohl (ekohl@abo.rhein-zeitung.de)
5 6 7
 * Copyright 1999 Alex Priem (alexp@sci.kun.nl)
 * Copyright 1999 Chris Morgan <cmorgan@wpi.edu> and
 *		  James Abbatiello <abbeyj@wpi.edu>
8
 * Copyright 2000 Uwe Bonnes <bon@elektron.ikp.physik.tu-darmstadt.de>
9
 * Copyright 2009-2011 Nikolay Sivov
10
 *
11 12 13 14 15 16 17 18 19 20 21 22
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
23
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24
 *
25
 * 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/debug.h"
49
#include "wine/heap.h"
50

51
WINE_DEFAULT_DEBUG_CHANNEL(monthcal);
52

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

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

65 66
#define MC_CALENDAR_PADDING     6

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

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

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

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

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

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

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

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

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

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

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

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

145
static const WCHAR themeClass[] = L"Scrollbar";
146

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

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

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

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

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

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

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

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

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

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

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

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

254
    return TRUE;
255 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
/* 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;
}

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

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

  return CompareFileTime(&ft_first, &ft_second);
}

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

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

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

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

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

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

377
  return !fix_st;
378 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
/* 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);

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

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

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

     return FALSE;
  }
  else return TRUE;
}

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

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

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

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

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

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

478
  return st.wDayOfWeek;
479 480
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  return TRUE;
667 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
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;
}

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

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

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

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

    infoPtr->focusedSel = *st;
  }

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

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

751
  return TRUE;
752
}
753

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

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

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

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

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

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

805
    selection = 1;
806
  }
807 808
  else
    selection = 0;
809

810
  SelectObject(hdc, bold ? infoPtr->hBoldFont : infoPtr->hFont);
811

812
  old_bkmode = SetBkMode(hdc, TRANSPARENT);
813
  wsprintfW(buf, L"%d", st->wDay);
814
  DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
815
  SetBkMode(hdc, old_bkmode);
816

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

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

861
/* paint a title with buttons and month/year string */
862
static void MONTHCAL_PaintTitle(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
863
{
864
  RECT *title = &infoPtr->calendars[calIdx].title;
865
  const SYSTEMTIME *st = &infoPtr->calendars[calIdx].month;
866 867
  WCHAR monthW[80], strW[80], fmtW[80], yearW[6] /* valid year range is 1601-30827 */;
  int yearoffset, monthoffset, shiftX;
868
  SIZE sz;
869

870
  /* fill header box */
871
  FillRect(hdc, title, infoPtr->brushes[BrushTitle]);
872

873
  /* month/year string */
874 875
  SetBkColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
  SetTextColor(hdc, infoPtr->colors[MCSC_TITLETEXT]);
876
  SelectObject(hdc, infoPtr->hBoldFont);
877

878
  /* draw formatted date string */
879
  GetDateFormatW(LOCALE_USER_DEFAULT, DATE_YEARMONTH, st, NULL, strW, ARRAY_SIZE(strW));
880
  DrawTextW(hdc, strW, lstrlenW(strW), title, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
881

882
  GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SYEARMONTH, fmtW, ARRAY_SIZE(fmtW));
883
  wsprintfW(yearW, L"%ld", st->wYear);
884 885 886

  /* month is trickier as it's possible to have different format pictures, we'll
     test for M, MM, MMM, and MMMM */
887
  if (wcsstr(fmtW, L"MMMM"))
888
    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+st->wMonth-1, monthW, ARRAY_SIZE(monthW));
889
  else if (wcsstr(fmtW, L"MMM"))
890
    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVMONTHNAME1+st->wMonth-1, monthW, ARRAY_SIZE(monthW));
891 892
  else if (wcsstr(fmtW, L"MM"))
    wsprintfW(monthW, L"%02d", st->wMonth);
893
  else
894
    wsprintfW(monthW, L"%d", st->wMonth);
895

896 897 898 899
  /* update hit boxes */
  yearoffset = 0;
  while (strW[yearoffset])
  {
900
    if (!wcsncmp(&strW[yearoffset], yearW, lstrlenW(yearW)))
901 902 903
        break;
    yearoffset++;
  }
904

905 906 907
  monthoffset = 0;
  while (strW[monthoffset])
  {
908
    if (!wcsncmp(&strW[monthoffset], monthW, lstrlenW(monthW)))
909 910 911
        break;
    monthoffset++;
  }
912

913 914 915 916 917 918 919 920 921 922 923 924
  /* 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 */
925
  GetTextExtentPoint32W(hdc, &strW[yearoffset], lstrlenW(yearW), &sz);
926 927
  infoPtr->calendars[calIdx].titleyear.right = infoPtr->calendars[calIdx].titleyear.left + sz.cx;

928
  GetTextExtentPoint32W(hdc, monthW, lstrlenW(monthW), &sz);
929
  infoPtr->calendars[calIdx].titlemonth.right = infoPtr->calendars[calIdx].titlemonth.left + sz.cx;
930 931 932

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

939
static void MONTHCAL_PaintWeeknumbers(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
940
{
941
  const SYSTEMTIME *date = &infoPtr->calendars[calIdx].month;
942 943
  INT mindays, weeknum, weeknum1, startofprescal;
  INT i, prev_month;
944 945
  SYSTEMTIME st;
  WCHAR buf[80];
946
  HPEN old_pen;
947
  RECT r;
948 949 950 951 952

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

  MONTHCAL_GetMinDate(infoPtr, &st);
  startofprescal = st.wDay;
953
  st = *date;
954

955
  prev_month = date->wMonth - 1;
956 957 958 959 960 961 962 963
  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
964
     LOCALE_IFIRSTWEEKOFYEAR == 1  (what countries?)
965 966
     The first week of the year must contain only days of the new year
  */
967
  GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, buf, ARRAY_SIZE(buf));
968
  weeknum = wcstol(buf, NULL, 10);
969
  switch (weeknum)
970
  {
971 972 973 974 975 976 977 978 979
    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;
980
  }
981

982
  if (date->wMonth == 1)
983
  {
984
    /* calculate all those exceptions for January */
985 986 987 988 989 990 991 992
    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++)
993
	   weeknum += MONTHCAL_MonthLength(i+1, date->wYear - 1);
994

995 996 997 998 999 1000 1001 1002
	weeknum  += startofprescal + 7;
	weeknum  /= 7;
	st.wYear -= 1;
	weeknum1  = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
	if ((infoPtr->firstDay - weeknum1) % 7 > mindays) weeknum++;
    }
  }
  else
1003
  {
1004 1005
    weeknum = 0;
    for(i = 0; i < prev_month - 1; i++)
1006
	weeknum += MONTHCAL_MonthLength(i+1, date->wYear);
1007 1008 1009 1010 1011 1012

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

1015
  r = infoPtr->calendars[calIdx].weeknums;
1016 1017

  /* erase whole week numbers area */
1018
  FillRect(hdc, &r, infoPtr->brushes[BrushMonth]);
1019
  SetTextColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
1020 1021

  /* reduce rectangle to one week number */
1022 1023 1024 1025 1026
  r.bottom = r.top + infoPtr->height_increment;

  for(i = 0; i < 6; i++) {
    if((i == 0) && (weeknum > 50))
    {
1027
        wsprintfW(buf, L"%d", weeknum);
1028 1029 1030 1031
        weeknum = 0;
    }
    else if((i == 5) && (weeknum > 47))
    {
1032
        wsprintfW(buf, L"%d", 1);
1033 1034
    }
    else
1035
        wsprintfW(buf, L"%d", weeknum + i);
1036 1037 1038 1039

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

1041
  /* line separator for week numbers column */
1042
  old_pen = SelectObject(hdc, infoPtr->pens[PenText]);
1043 1044
  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);
1045
  SelectObject(hdc, old_pen);
1046 1047 1048 1049 1050
}

/* bottom today date */
static void MONTHCAL_PaintTodayTitle(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
{
1051 1052 1053 1054 1055 1056 1057
  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;

1058
  LoadStringW(COMCTL32_hModule, IDM_TODAY, buf_todayW, ARRAY_SIZE(buf_todayW));
1059 1060
  col = infoPtr->dwStyle & MCS_NOTODAYCIRCLE ? 0 : 1;
  if (infoPtr->dwStyle & MCS_WEEKNUMBERS) col--;
1061 1062
  /* label is located below first calendar last row */
  MONTHCAL_GetDayRectI(infoPtr, &text_rect, col, 6, infoPtr->dim.cx * infoPtr->dim.cy - infoPtr->dim.cx);
1063
  box_rect = text_rect;
1064

1065
  GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &infoPtr->todaysDate, NULL, buf_dateW, ARRAY_SIZE(buf_dateW));
1066 1067
  old_font = SelectObject(hdc, infoPtr->hBoldFont);
  SetTextColor(hdc, infoPtr->colors[MCSC_TEXT]);
1068

1069
  wsprintfW(buf, L"%s %s", buf_todayW, buf_dateW);
1070 1071
  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);
1072

1073 1074 1075
  if(!(infoPtr->dwStyle & MCS_NOTODAYCIRCLE)) {
    OffsetRect(&box_rect, -infoPtr->width_increment, 0);
    MONTHCAL_Circle(infoPtr, hdc, &box_rect);
1076
  }
1077 1078

  SelectObject(hdc, old_font);
1079 1080 1081 1082 1083
}

/* today mark + focus */
static void MONTHCAL_PaintFocusAndCircle(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
{
1084 1085
  /* circle today date if only it's in fully visible month */
  if (!(infoPtr->dwStyle & MCS_NOTODAYCIRCLE))
1086
  {
1087 1088 1089 1090 1091 1092 1093 1094
    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;
      }
1095 1096
  }

1097
  if (!MONTHCAL_IsDateEqual(&infoPtr->focusedSel, &st_null))
1098 1099
  {
    RECT r;
1100
    MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1);
1101 1102
    DrawFocusRect(hdc, &r);
  }
1103
}
1104

1105 1106 1107
/* months before first calendar month and after last calendar month */
static void MONTHCAL_PaintLeadTrailMonths(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
{
1108 1109
  INT mask, index;
  UINT length;
1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
  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);
1122
  index = 0;
1123 1124
  while(st.wDay <= length)
  {
1125
      MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[index] & mask, ps);
1126 1127 1128 1129 1130
      mask <<= 1;
      st.wDay++;
  }

  /* draw next month */
1131
  st = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
1132 1133 1134 1135
  st.wDay = 1;
  MONTHCAL_GetNextMonth(&st);
  MONTHCAL_GetMaxDate(infoPtr, &st_max);
  mask = 1;
1136
  index = MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)-1;
1137 1138
  while(st.wDay <= st_max.wDay)
  {
1139
      MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[index] & mask, ps);
1140 1141 1142 1143 1144
      mask <<= 1;
      st.wDay++;
  }
}

1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156
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);
}

1157
/* paint a calendar area */
1158
static void MONTHCAL_PaintCalendar(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
1159
{
1160
  const SYSTEMTIME *date = &infoPtr->calendars[calIdx].month;
1161 1162
  INT i, j;
  UINT length;
1163 1164
  RECT r, fill_bk_rect;
  SYSTEMTIME st;
1165
  WCHAR buf[80];
1166
  HPEN old_pen;
1167
  int mask;
1168

1169 1170 1171 1172
  /* 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);
1173

1174
  FillRect(hdc, &fill_bk_rect, infoPtr->brushes[BrushMonth]);
1175 1176

  /* draw line under day abbreviations */
1177
  old_pen = SelectObject(hdc, infoPtr->pens[PenText]);
1178 1179 1180 1181
  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);
1182
  SelectObject(hdc, old_pen);
1183

1184 1185
  infoPtr->calendars[calIdx].wdays.left = infoPtr->calendars[calIdx].days.left =
      infoPtr->calendars[calIdx].weeknums.right;
1186

1187
  /* draw day abbreviations */
Duane Clark's avatar
Duane Clark committed
1188
  SelectObject(hdc, infoPtr->hFont);
1189
  SetBkColor(hdc, infoPtr->colors[MCSC_MONTHBK]);
1190
  SetTextColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
1191
  /* rectangle to draw a single day abbreviation within */
1192
  r = infoPtr->calendars[calIdx].wdays;
1193
  r.right = r.left + infoPtr->width_increment;
1194

1195
  i = infoPtr->firstDay;
1196
  for(j = 0; j < 7; j++) {
1197
    get_localized_dayname(infoPtr, (i + j + 6) % 7, buf, ARRAY_SIZE(buf));
1198
    DrawTextW(hdc, buf, lstrlenW(buf), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
1199
    OffsetRect(&r, infoPtr->width_increment, 0);
1200
  }
1201

1202
  /* draw current month */
1203
  SetTextColor(hdc, infoPtr->colors[MCSC_TEXT]);
1204
  st = *date;
1205
  st.wDay = 1;
1206
  mask = 1;
1207 1208
  length = MONTHCAL_MonthLength(date->wMonth, date->wYear);
  while(st.wDay <= length)
1209
  {
1210
    MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[calIdx+1] & mask, ps);
1211
    mask <<= 1;
1212
    st.wDay++;
1213
  }
1214
}
1215

1216 1217 1218 1219
static void MONTHCAL_Refresh(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
{
  COLORREF old_text_clr, old_bk_clr;
  HFONT old_font;
1220
  INT i;
1221

1222 1223 1224 1225
  old_text_clr = SetTextColor(hdc, comctl32_color.clrWindowText);
  old_bk_clr   = GetBkColor(hdc);
  old_font     = GetCurrentObject(hdc, OBJ_FONT);

1226
  for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1227 1228 1229 1230 1231 1232 1233
  {
    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);
1234

1235 1236 1237 1238
    /* draw calendar area */
    UnionRect(&r, &infoPtr->calendars[i].wdays, &infoPtr->todayrect);
    if (IntersectRect(&r, &(ps->rcPaint), &r))
        MONTHCAL_PaintCalendar(infoPtr, hdc, ps, i);
1239

1240 1241 1242 1243
    /* week numbers */
    MONTHCAL_PaintWeeknumbers(infoPtr, hdc, ps, i);
  }

1244 1245 1246
  /* partially visible months */
  MONTHCAL_PaintLeadTrailMonths(infoPtr, hdc, ps);

1247 1248 1249 1250 1251 1252 1253
  /* focus and today rectangle */
  MONTHCAL_PaintFocusAndCircle(infoPtr, hdc, ps);

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

  /* navigation buttons */
1254 1255
  MONTHCAL_PaintButton(infoPtr, hdc, DIRECTION_BACKWARD);
  MONTHCAL_PaintButton(infoPtr, hdc, DIRECTION_FORWARD);
1256

1257 1258 1259 1260
  /* restore context */
  SetBkColor(hdc, old_bk_clr);
  SelectObject(hdc, old_font);
  SetTextColor(hdc, old_text_clr);
1261 1262
}

1263
static LRESULT
1264
MONTHCAL_GetMinReqRect(const MONTHCAL_INFO *infoPtr, RECT *rect)
1265
{
1266
  TRACE("rect %p\n", rect);
1267

1268
  if(!rect) return FALSE;
1269

1270 1271 1272
  *rect = infoPtr->calendars[0].title;
  rect->bottom = infoPtr->calendars[0].days.bottom + infoPtr->todayrect.bottom -
                 infoPtr->todayrect.top;
1273

1274
  AdjustWindowRect(rect, infoPtr->dwStyle, FALSE);
1275

1276
  /* minimal rectangle is zero based */
1277
  OffsetRect(rect, -rect->left, -rect->top);
1278

1279
  TRACE("%s\n", wine_dbgstr_rect(rect));
1280

1281
  return TRUE;
1282 1283
}

1284 1285
static COLORREF
MONTHCAL_GetColor(const MONTHCAL_INFO *infoPtr, UINT index)
1286
{
1287
  TRACE("%p, %d\n", infoPtr, index);
1288

1289 1290
  if (index > MCSC_TRAILINGTEXT) return -1;
  return infoPtr->colors[index];
1291 1292
}

1293
static LRESULT
1294 1295
MONTHCAL_SetColor(MONTHCAL_INFO *infoPtr, UINT index, COLORREF color)
{
1296
  enum CachedBrush type;
1297 1298 1299 1300 1301 1302 1303 1304
  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;
1305

1306
  /* update cached brush */
1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322
  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)
1323
  {
1324 1325
    DeleteObject(infoPtr->brushes[type]);
    infoPtr->brushes[type] = CreateSolidBrush(color);
1326 1327
  }

1328 1329 1330 1331 1332 1333 1334
  /* update cached pen */
  if (index == MCSC_TEXT)
  {
    DeleteObject(infoPtr->pens[PenText]);
    infoPtr->pens[PenText] = CreatePen(PS_SOLID, 1, infoPtr->colors[index]);
  }

1335
  InvalidateRect(infoPtr->hwndSelf, NULL, index == MCSC_BACKGROUND);
1336
  return prev;
1337 1338
}

1339
static LRESULT
1340
MONTHCAL_GetMonthDelta(const MONTHCAL_INFO *infoPtr)
1341
{
1342
  TRACE("\n");
1343

1344
  if(infoPtr->delta)
1345
    return infoPtr->delta;
1346 1347

  return MONTHCAL_GetMonthRange(infoPtr, GMR_VISIBLE, NULL);
1348 1349
}

1350

1351
static LRESULT
1352
MONTHCAL_SetMonthDelta(MONTHCAL_INFO *infoPtr, INT delta)
1353
{
1354
  INT prev = infoPtr->delta;
1355

1356
  TRACE("delta %d\n", delta);
1357

1358
  infoPtr->delta = delta;
1359
  return prev;
1360 1361 1362
}


1363
static inline LRESULT
1364
MONTHCAL_GetFirstDayOfWeek(const MONTHCAL_INFO *infoPtr)
1365
{
1366 1367 1368 1369 1370 1371
  int day;

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

  return MAKELONG(day, infoPtr->firstDaySet);
1372 1373 1374
}


1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385
/* 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
1386
 *  locale defined (TRUE - was forced, FALSE - wasn't).
1387 1388 1389 1390
 *
 * FIXME: this needs to be implemented properly in MONTHCAL_Refresh()
 * FIXME: we need more error checking here
 */
1391
static LRESULT
1392
MONTHCAL_SetFirstDayOfWeek(MONTHCAL_INFO *infoPtr, INT day)
1393
{
1394 1395
  LRESULT prev = MONTHCAL_GetFirstDayOfWeek(infoPtr);
  int new_day;
1396

1397
  TRACE("%d\n", day);
1398

1399
  if(day == -1)
1400
  {
1401 1402
    WCHAR buf[80];

1403
    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, buf, ARRAY_SIZE(buf));
1404
    TRACE("%s %d\n", debugstr_w(buf), lstrlenW(buf));
1405

1406
    new_day = wcstol(buf, NULL, 10);
1407

1408
    infoPtr->firstDaySet = FALSE;
1409
  }
1410
  else if(day >= 7)
1411
  {
1412 1413
    new_day = 6; /* max first day allowed */
    infoPtr->firstDaySet = TRUE;
1414
  }
1415
  else
1416
  {
1417 1418
    /* Native behaviour for that case is broken: invalid date number >31
       got displayed at (0,0) position, current month starts always from
1419 1420
       (1,0) position. Should be implemented here as well only if there's
       nothing else to do. */
1421 1422 1423 1424 1425
    if (day < -1)
      FIXME("No bug compatibility for day=%d\n", day);

    new_day = day;
    infoPtr->firstDaySet = TRUE;
1426
  }
1427

1428 1429 1430
  /* convert from locale to SYSTEMTIME format */
  infoPtr->firstDay = (new_day >= 0) ? (++new_day) % 7 : new_day;

1431 1432
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);

1433
  return prev;
1434 1435 1436
}

static LRESULT
1437
MONTHCAL_GetMaxTodayWidth(const MONTHCAL_INFO *infoPtr)
1438
{
1439
  return(infoPtr->todayrect.right - infoPtr->todayrect.left);
1440 1441 1442
}

static LRESULT
1443
MONTHCAL_SetRange(MONTHCAL_INFO *infoPtr, SHORT limits, SYSTEMTIME *range)
1444
{
1445
    FILETIME ft_min, ft_max;
1446

1447
    TRACE("%x %p\n", limits, range);
1448

1449 1450
    if ((limits & GDTR_MIN && !MONTHCAL_ValidateDate(&range[0])) ||
        (limits & GDTR_MAX && !MONTHCAL_ValidateDate(&range[1])))
1451 1452
        return FALSE;

1453 1454 1455
    infoPtr->rangeValid = 0;
    infoPtr->minDate = infoPtr->maxDate = st_null;

1456
    if (limits & GDTR_MIN)
1457
    {
1458 1459 1460
        if (!MONTHCAL_ValidateTime(&range[0]))
            MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]);

1461
        infoPtr->minDate = range[0];
1462
        infoPtr->rangeValid |= GDTR_MIN;
1463
    }
1464
    if (limits & GDTR_MAX)
1465
    {
1466 1467 1468
        if (!MONTHCAL_ValidateTime(&range[1]))
            MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]);

1469
        infoPtr->maxDate = range[1];
1470
        infoPtr->rangeValid |= GDTR_MAX;
1471 1472
    }

1473 1474 1475
    /* Only one limit set - we are done */
    if ((infoPtr->rangeValid & (GDTR_MIN | GDTR_MAX)) != (GDTR_MIN | GDTR_MAX))
        return TRUE;
1476

1477 1478 1479
    SystemTimeToFileTime(&infoPtr->maxDate, &ft_max);
    SystemTimeToFileTime(&infoPtr->minDate, &ft_min);

1480
    if (CompareFileTime(&ft_min, &ft_max) >= 0)
1481
    {
1482
        if ((limits & (GDTR_MIN | GDTR_MAX)) == (GDTR_MIN | GDTR_MAX))
1483 1484 1485 1486 1487 1488 1489 1490
        {
            /* Native swaps limits only when both limits are being set. */
            SYSTEMTIME st_tmp = infoPtr->minDate;
            infoPtr->minDate  = infoPtr->maxDate;
            infoPtr->maxDate  = st_tmp;
        }
        else
        {
1491
            /* reset the other limit */
1492 1493 1494
            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;
1495 1496 1497
        }
    }

1498
    return TRUE;
1499 1500
}

1501

1502
static LRESULT
1503
MONTHCAL_GetRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1504
{
1505
  TRACE("%p\n", range);
1506

1507
  if (!range) return 0;
1508

1509 1510
  range[1] = infoPtr->maxDate;
  range[0] = infoPtr->minDate;
1511 1512 1513 1514

  return infoPtr->rangeValid;
}

1515

1516
static LRESULT
1517
MONTHCAL_SetDayState(const MONTHCAL_INFO *infoPtr, INT months, MONTHDAYSTATE *states)
1518
{
1519
  TRACE("%p %d %p\n", infoPtr, months, states);
1520 1521 1522

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

1524
  memcpy(infoPtr->monthdayState, states, months*sizeof(MONTHDAYSTATE));
1525 1526 1527 1528

  return 1;
}

1529
static LRESULT
1530
MONTHCAL_GetCurSel(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel)
1531
{
1532 1533
  TRACE("%p\n", curSel);
  if(!curSel) return FALSE;
1534
  if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
1535

1536
  *curSel = infoPtr->minSel;
1537
  TRACE("%d/%d/%d\n", curSel->wYear, curSel->wMonth, curSel->wDay);
1538 1539 1540
  return TRUE;
}

1541
static LRESULT
1542
MONTHCAL_SetCurSel(MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel)
1543
{
1544
  SYSTEMTIME prev = infoPtr->minSel, selection;
1545
  INT diff;
1546
  WORD day;
1547

1548 1549
  TRACE("%p\n", curSel);
  if(!curSel) return FALSE;
1550
  if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
1551

1552
  if(!MONTHCAL_ValidateDate(curSel)) return FALSE;
1553
  /* exit earlier if selection equals current */
1554
  if (MONTHCAL_IsDateEqual(&infoPtr->minSel, curSel)) return TRUE;
1555

1556 1557 1558 1559 1560
  selection = *curSel;
  selection.wHour = selection.wMinute = selection.wSecond = selection.wMilliseconds = 0;
  MONTHCAL_CalculateDayOfWeek(&selection, TRUE);

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

1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577
  /* 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);
  }

1578
  /* we need to store time part as it is */
1579 1580 1581
  selection = *curSel;
  MONTHCAL_CalculateDayOfWeek(&selection, TRUE);
  infoPtr->minSel = infoPtr->maxSel = selection;
1582

1583
  /* if selection is still in current month, reduce rectangle */
1584
  day = prev.wDay;
1585 1586 1587 1588
  prev.wDay = curSel->wDay;
  if (MONTHCAL_IsDateEqual(&prev, curSel))
  {
    RECT r_prev, r_new;
1589

1590
    prev.wDay = day;
1591 1592
    MONTHCAL_GetDayRect(infoPtr, &prev, &r_prev, -1);
    MONTHCAL_GetDayRect(infoPtr, curSel, &r_new, -1);
1593 1594 1595 1596 1597 1598 1599

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

1600 1601 1602
  return TRUE;
}

1603

1604
static LRESULT
1605
MONTHCAL_GetMaxSelCount(const MONTHCAL_INFO *infoPtr)
1606 1607 1608 1609
{
  return infoPtr->maxSelCount;
}

1610

1611
static LRESULT
1612
MONTHCAL_SetMaxSelCount(MONTHCAL_INFO *infoPtr, INT max)
1613
{
1614
  TRACE("%d\n", max);
1615

1616 1617 1618 1619
  if(!(infoPtr->dwStyle & MCS_MULTISELECT)) return FALSE;
  if(max <= 0) return FALSE;

  infoPtr->maxSelCount = max;
1620 1621 1622 1623 1624

  return TRUE;
}


1625
static LRESULT
1626
MONTHCAL_GetSelRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1627
{
1628
  TRACE("%p\n", range);
1629

1630
  if(!range) return FALSE;
1631

1632
  if(infoPtr->dwStyle & MCS_MULTISELECT)
1633
  {
1634 1635
    range[1] = infoPtr->maxSel;
    range[0] = infoPtr->minSel;
1636
    TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
1637
    return TRUE;
1638
  }
1639

1640 1641 1642
  return FALSE;
}

1643

1644
static LRESULT
1645
MONTHCAL_SetSelRange(MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
1646
{
1647
  SYSTEMTIME old_range[2];
1648
  INT diff;
1649

1650
  TRACE("%p\n", range);
1651

1652
  if(!range || !(infoPtr->dwStyle & MCS_MULTISELECT)) return FALSE;
1653

1654 1655 1656
  /* adjust timestamps */
  if(!MONTHCAL_ValidateTime(&range[0])) MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]);
  if(!MONTHCAL_ValidateTime(&range[1])) MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]);
1657

1658 1659
  /* maximum range exceeded */
  if(!MONTHCAL_IsSelRangeValid(infoPtr, &range[0], &range[1], NULL)) return FALSE;
1660

1661 1662
  old_range[0] = infoPtr->minSel;
  old_range[1] = infoPtr->maxSel;
1663

1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674
  /* 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];
  }
1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689

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

1691 1692 1693
  /* update day of week */
  MONTHCAL_CalculateDayOfWeek(&infoPtr->minSel, TRUE);
  MONTHCAL_CalculateDayOfWeek(&infoPtr->maxSel, TRUE);
1694

1695 1696 1697 1698 1699 1700
  /* 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);
1701
  }
1702

1703 1704
  TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
  return TRUE;
1705 1706 1707
}


1708
static LRESULT
1709
MONTHCAL_GetToday(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *today)
1710
{
1711
  TRACE("%p\n", today);
1712

1713
  if(!today) return FALSE;
1714
  *today = infoPtr->todaysDate;
1715 1716 1717
  return TRUE;
}

1718 1719 1720 1721 1722 1723 1724 1725 1726 1727
/* 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)
{
1728 1729 1730 1731
    RECT rect;

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

1733
    /* Invalidate old and new today day rectangle, and today label. */
1734
    if (MONTHCAL_GetDayRect(infoPtr, &infoPtr->todaysDate, &rect, -1))
1735
        InvalidateRect(infoPtr->hwndSelf, &rect, FALSE);
1736

1737
    if (MONTHCAL_GetDayRect(infoPtr, today, &rect, -1))
1738
        InvalidateRect(infoPtr->hwndSelf, &rect, FALSE);
1739

1740
    infoPtr->todaysDate = *today;
1741

1742 1743
    InvalidateRect(infoPtr->hwndSelf, &infoPtr->todayrect, FALSE);
    return TRUE;
1744 1745 1746
}

/* MCM_SETTODAT handler */
1747
static LRESULT
1748
MONTHCAL_SetToday(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *today)
1749
{
1750
  TRACE("%p\n", today);
1751

1752 1753 1754 1755 1756
  if (today)
  {
    /* remember if date was set successfully */
    if (MONTHCAL_UpdateToday(infoPtr, today)) infoPtr->todaySet = TRUE;
  }
1757

1758
  return 0;
1759 1760
}

1761 1762 1763 1764 1765 1766
/* 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;

1767
  for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780
  {
     /* 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;
}

1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791
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;
}

1792
static LRESULT
1793
MONTHCAL_HitTest(const MONTHCAL_INFO *infoPtr, MCHITTESTINFO *lpht)
1794
{
1795
  MCHITTESTINFO htinfo;
1796
  SYSTEMTIME *ht_month;
1797
  INT day, calIdx;
1798

1799
  if(!lpht || lpht->cbSize < MCHITTESTINFO_V1_SIZE) return -1;
1800

1801
  htinfo.st = st_null;
1802
  htinfo.uHit = 0;
1803 1804 1805 1806

  /* 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);
1807

1808 1809
  /* guess in what calendar we are */
  calIdx = MONTHCAL_GetCalendarFromPoint(infoPtr, &lpht->pt);
1810 1811 1812
  if (calIdx == -1)
  {
    if (PtInRect(&infoPtr->todayrect, lpht->pt))
1813 1814 1815 1816
    {
      htinfo.uHit = MCHT_TODAYLINK;
      htinfo.rc = infoPtr->todayrect;
    }
1817 1818
    else
      /* outside of calendar area? What's left must be background :-) */
1819
      htinfo.uHit = MCHT_CALENDARBK;
1820

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

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

1857
    return fill_hittest_info(&htinfo, lpht);
1858
  }
1859

1860 1861
  ht_month = &infoPtr->calendars[calIdx].month;
  /* days area (including week days and week numbers) */
1862
  day = MONTHCAL_GetDayFromPos(infoPtr, lpht->pt, calIdx);
1863 1864
  if (PtInRect(&infoPtr->calendars[calIdx].wdays, lpht->pt))
  {
1865 1866
    htinfo.uHit = MCHT_CALENDARDAY;
    htinfo.iOffset = calIdx;
1867 1868
    htinfo.st.wYear  = ht_month->wYear;
    htinfo.st.wMonth = (day < 1) ? ht_month->wMonth -1 : ht_month->wMonth;
1869
    htinfo.st.wDay   = (day < 1) ?
1870
      MONTHCAL_MonthLength(ht_month->wMonth-1, ht_month->wYear) - day : day;
1871

1872
    MONTHCAL_GetDayPos(infoPtr, &htinfo.st, &htinfo.iCol, &htinfo.iRow, calIdx);
1873
  }
1874 1875
  else if(PtInRect(&infoPtr->calendars[calIdx].weeknums, lpht->pt))
  {
1876
    htinfo.uHit = MCHT_CALENDARWEEKNUM;
1877
    htinfo.st.wYear = ht_month->wYear;
1878
    htinfo.iOffset = calIdx;
1879

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

1928 1929
      MONTHCAL_GetDayPos(infoPtr, &htinfo.st, &htinfo.iCol, &htinfo.iRow, calIdx);
      MONTHCAL_GetDayRectI(infoPtr, &htinfo.rc, htinfo.iCol, htinfo.iRow, calIdx);
1930
      /* always update day of week */
1931
      MONTHCAL_CalculateDayOfWeek(&htinfo.st, TRUE);
1932
  }
1933

1934
  return fill_hittest_info(&htinfo, lpht);
1935 1936
}

1937 1938
/* MCN_GETDAYSTATE notification helper */
static void MONTHCAL_NotifyDayState(MONTHCAL_INFO *infoPtr)
1939
{
1940 1941
  MONTHDAYSTATE *state;
  NMDAYSTATE nmds;
1942

1943
  if (!(infoPtr->dwStyle & MCS_DAYSTATE)) return;
1944

1945 1946 1947 1948
  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);
1949
  nmds.prgDayState    = state = heap_alloc_zero(nmds.cDayState * sizeof(MONTHDAYSTATE));
1950

1951 1952
  MONTHCAL_GetMinDate(infoPtr, &nmds.stStart);
  nmds.stStart.wDay = 1;
1953

1954 1955 1956 1957
  SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmds.nmhdr.idFrom, (LPARAM)&nmds);
  memcpy(infoPtr->monthdayState, nmds.prgDayState,
      MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)*sizeof(MONTHDAYSTATE));

1958
  heap_free(state);
1959 1960
}

1961
/* no valid range check performed */
1962
static void MONTHCAL_Scroll(MONTHCAL_INFO *infoPtr, INT delta, BOOL keep_selection)
1963 1964 1965
{
  INT i, selIdx = -1;

1966
  for(i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
1967 1968 1969 1970 1971 1972 1973 1974
  {
    /* 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);
  }

1975 1976 1977
  if (keep_selection)
    return;

1978
  /* selection is always shifted to first calendar */
1979
  if (infoPtr->dwStyle & MCS_MULTISELECT)
1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996
  {
    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);
  }
}

1997
static void MONTHCAL_GoToMonth(MONTHCAL_INFO *infoPtr, enum nav_direction direction)
1998
{
1999
  INT delta = infoPtr->delta ? infoPtr->delta : MONTHCAL_GetCalCount(infoPtr);
2000
  BOOL keep_selection;
2001
  SYSTEMTIME st;
2002

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

2005 2006 2007 2008 2009 2010 2011 2012
  /* check if change allowed by range set */
  if(direction == DIRECTION_BACKWARD)
  {
    st = infoPtr->calendars[0].month;
    MONTHCAL_GetMonth(&st, -delta);
  }
  else
  {
2013
    st = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
2014 2015
    MONTHCAL_GetMonth(&st, delta);
  }
2016

2017
  if(!MONTHCAL_IsDateInValidRange(infoPtr, &st, FALSE)) return;
2018

2019 2020
  keep_selection = infoPtr->dwStyle & MCS_NOSELCHANGEONNAV;
  MONTHCAL_Scroll(infoPtr, direction == DIRECTION_BACKWARD ? -delta : delta, keep_selection);
2021
  MONTHCAL_NotifyDayState(infoPtr);
2022 2023
  if (!keep_selection)
    MONTHCAL_NotifySelectionChange(infoPtr);
2024 2025
}

2026
static LRESULT
2027
MONTHCAL_RButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2028 2029 2030
{
  HMENU hMenu;
  POINT menupoint;
2031
  WCHAR buf[32];
2032

2033
  hMenu = CreatePopupMenu();
2034
  LoadStringW(COMCTL32_hModule, IDM_GOTODAY, buf, ARRAY_SIZE(buf));
2035 2036 2037
  AppendMenuW(hMenu, MF_STRING|MF_ENABLED, 1, buf);
  menupoint.x = (short)LOWORD(lParam);
  menupoint.y = (short)HIWORD(lParam);
2038
  ClientToScreen(infoPtr->hwndSelf, &menupoint);
2039
  if( TrackPopupMenu(hMenu, TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD,
2040
		     menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL))
2041
  {
2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053
      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);
2054 2055
  }

2056 2057
  return 0;
}
2058

2059 2060 2061 2062 2063 2064 2065 2066 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
/***
 * 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;
}

2106
/* creates updown control and edit box */
2107
static void MONTHCAL_EditYear(MONTHCAL_INFO *infoPtr, INT calIdx)
2108
{
2109 2110 2111
    RECT *rc = &infoPtr->calendars[calIdx].titleyear;
    RECT *title = &infoPtr->calendars[calIdx].title;

2112
    infoPtr->hWndYearEdit =
2113
	CreateWindowExW(0, WC_EDITW, 0, WS_VISIBLE | WS_CHILD | ES_READONLY,
2114 2115
			rc->left + 3, (title->bottom + title->top - infoPtr->textHeight) / 2,
			rc->right - rc->left + 4,
2116 2117 2118 2119 2120 2121 2122 2123
			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,
2124
			rc->right + 7, (title->bottom + title->top - infoPtr->textHeight) / 2,
2125 2126 2127 2128
			18, infoPtr->textHeight, infoPtr->hwndSelf,
			NULL, NULL, NULL);

    /* attach edit box */
2129 2130
    SendMessageW(infoPtr->hWndYearUpDown, UDM_SETRANGE, 0,
                 MAKELONG(max_allowed_date.wYear, min_allowed_date.wYear));
2131
    SendMessageW(infoPtr->hWndYearUpDown, UDM_SETBUDDY, (WPARAM)infoPtr->hWndYearEdit, 0);
2132
    SendMessageW(infoPtr->hWndYearUpDown, UDM_SETPOS, 0, infoPtr->calendars[calIdx].month.wYear);
2133 2134 2135 2136 2137 2138

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

    SetFocus(infoPtr->hWndYearEdit);
2139 2140
}

2141
static LRESULT
2142
MONTHCAL_LButtonDown(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2143
{
2144 2145
  MCHITTESTINFO ht;
  DWORD hit;
2146

2147 2148 2149
  /* 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))
2150
  {
2151 2152
      SetFocus(infoPtr->hwndSelf);
      return 0;
2153
  }
2154

2155 2156
  SetCapture(infoPtr->hwndSelf);

2157
  ht.cbSize = sizeof(MCHITTESTINFO);
2158 2159
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
2160

2161
  hit = MONTHCAL_HitTest(infoPtr, &ht);
2162

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

2165 2166 2167
  switch(hit)
  {
  case MCHT_TITLEBTNNEXT:
2168
    MONTHCAL_GoToMonth(infoPtr, DIRECTION_FORWARD);
2169
    infoPtr->status = MC_NEXTPRESSED;
2170
    SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0);
2171
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2172
    return 0;
2173 2174

  case MCHT_TITLEBTNPREV:
2175
    MONTHCAL_GoToMonth(infoPtr, DIRECTION_BACKWARD);
2176
    infoPtr->status = MC_PREVPRESSED;
2177
    SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0);
2178
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2179
    return 0;
2180

2181 2182 2183 2184 2185 2186
  case MCHT_TITLEMONTH:
  {
    HMENU hMenu = CreatePopupMenu();
    WCHAR buf[32];
    POINT menupoint;
    INT i;
2187

2188 2189
    for (i = 0; i < 12; i++)
    {
2190 2191
        GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+i, buf, ARRAY_SIZE(buf));
        AppendMenuW(hMenu, MF_STRING|MF_ENABLED, i + 1, buf);
2192
    }
2193 2194
    menupoint.x = ht.pt.x;
    menupoint.y = ht.pt.y;
2195
    ClientToScreen(infoPtr->hwndSelf, &menupoint);
2196 2197 2198
    i = TrackPopupMenu(hMenu,TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON | TPM_RETURNCMD,
		       menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL);

2199
    if ((i > 0) && (i < 13) && infoPtr->calendars[ht.iOffset].month.wMonth != i)
2200
    {
2201 2202 2203 2204 2205
        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 :
2206
                         infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
2207 2208 2209 2210
        MONTHCAL_GetMonth(&st, delta);

        if (MONTHCAL_IsDateInValidRange(infoPtr, &st, FALSE))
        {
2211
            MONTHCAL_Scroll(infoPtr, delta, FALSE);
2212 2213 2214 2215
            MONTHCAL_NotifyDayState(infoPtr);
            MONTHCAL_NotifySelectionChange(infoPtr);
            InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
        }
2216
    }
2217
    return 0;
2218
  }
2219 2220
  case MCHT_TITLEYEAR:
  {
2221
    MONTHCAL_EditYear(infoPtr, ht.iOffset);
2222
    return 0;
2223
  }
2224 2225
  case MCHT_TODAYLINK:
  {
2226 2227 2228 2229 2230 2231 2232 2233 2234
    if (infoPtr->dwStyle & MCS_MULTISELECT)
    {
        SYSTEMTIME range[2];

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

2236 2237
    MONTHCAL_NotifySelectionChange(infoPtr);
    MONTHCAL_NotifySelect(infoPtr);
2238
    return 0;
2239
  }
2240 2241
  case MCHT_CALENDARDATENEXT:
  case MCHT_CALENDARDATEPREV:
2242 2243
  case MCHT_CALENDARDATE:
  {
2244
    SYSTEMTIME st[2];
2245

2246
    MONTHCAL_CopyDate(&ht.st, &infoPtr->firstSel);
2247

2248 2249 2250
    st[0] = st[1] = ht.st;
    /* clear selection range */
    MONTHCAL_SetSelRange(infoPtr, st);
2251

2252
    infoPtr->status = MC_SEL_LBUTDOWN;
2253 2254
    if (MONTHCAL_SetDayFocus(infoPtr, &ht.st) && (infoPtr->dwStyle & MCS_MULTISELECT))
        MONTHCAL_NotifySelectionChange(infoPtr);
2255
    return 0;
2256
  }
2257
  }
2258

2259
  return 1;
2260 2261
}

2262

2263
static LRESULT
2264
MONTHCAL_LButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2265
{
2266
  NMHDR nmhdr;
2267 2268
  MCHITTESTINFO ht;
  DWORD hit;
2269 2270

  TRACE("\n");
2271

2272
  if(infoPtr->status & (MC_PREVPRESSED | MC_NEXTPRESSED)) {
2273 2274
    RECT *r;

2275
    KillTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER);
2276
    r = infoPtr->status & MC_PREVPRESSED ? &infoPtr->titlebtnprev : &infoPtr->titlebtnnext;
2277
    infoPtr->status &= ~(MC_PREVPRESSED | MC_NEXTPRESSED);
2278 2279

    InvalidateRect(infoPtr->hwndSelf, r, FALSE);
2280
  }
2281

2282 2283
  ReleaseCapture();

2284 2285 2286 2287 2288 2289 2290 2291
  /* 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);

2292 2293
  if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;

2294
  ht.cbSize = sizeof(MCHITTESTINFO);
2295 2296
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
2297
  hit = MONTHCAL_HitTest(infoPtr, &ht);
2298

2299
  infoPtr->status = MC_SEL_LBUTUP;
2300
  MONTHCAL_SetDayFocus(infoPtr, NULL);
2301

2302
  if((hit & MCHT_CALENDARDATE) == MCHT_CALENDARDATE)
2303
  {
2304
    SYSTEMTIME sel = infoPtr->minSel;
2305

2306 2307
    /* will be invalidated here */
    MONTHCAL_SetCurSel(infoPtr, &ht.st);
2308

2309 2310
    /* send MCN_SELCHANGE only if new date selected */
    if (!MONTHCAL_IsDateEqual(&sel, &ht.st))
2311
        MONTHCAL_NotifySelectionChange(infoPtr);
2312

2313
    MONTHCAL_NotifySelect(infoPtr);
2314
  }
2315

2316
  return 0;
2317 2318
}

2319

2320
static LRESULT
2321
MONTHCAL_Timer(MONTHCAL_INFO *infoPtr, WPARAM id)
2322
{
2323
  TRACE("%ld\n", id);
2324

2325 2326
  switch(id) {
  case MC_PREVNEXTMONTHTIMER:
2327 2328
    if(infoPtr->status & MC_NEXTPRESSED) MONTHCAL_GoToMonth(infoPtr, DIRECTION_FORWARD);
    if(infoPtr->status & MC_PREVPRESSED) MONTHCAL_GoToMonth(infoPtr, DIRECTION_BACKWARD);
2329
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2330
    break;
2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344
  case MC_TODAYUPDATETIMER:
  {
    SYSTEMTIME st;

    if(infoPtr->todaySet) return 0;

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

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

    return 0;
  }
2345
  default:
2346
    ERR("got unknown timer %ld\n", id);
2347
    break;
2348
  }
2349

2350 2351
  return 0;
}
2352

2353 2354

static LRESULT
2355
MONTHCAL_MouseMove(MONTHCAL_INFO *infoPtr, LPARAM lParam)
2356
{
2357
  MCHITTESTINFO ht;
2358
  SYSTEMTIME st_ht;
2359
  INT hit;
2360
  RECT r;
2361

2362
  if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
2363

2364
  ht.cbSize = sizeof(MCHITTESTINFO);
2365 2366
  ht.pt.x = (short)LOWORD(lParam);
  ht.pt.y = (short)HIWORD(lParam);
2367
  ht.iOffset = -1;
2368

2369
  hit = MONTHCAL_HitTest(infoPtr, &ht);
2370

2371
  /* not on the calendar date numbers? bail out */
2372
  TRACE("hit:%x\n",hit);
2373 2374 2375 2376 2377
  if((hit & MCHT_CALENDARDATE) != MCHT_CALENDARDATE)
  {
    MONTHCAL_SetDayFocus(infoPtr, NULL);
    return 0;
  }
2378

2379
  st_ht = ht.st;
2380 2381 2382

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

2384
  MONTHCAL_GetDayRect(infoPtr, &ht.st, &r, ht.iOffset);
2385

2386
  if(infoPtr->dwStyle & MCS_MULTISELECT) {
2387 2388 2389 2390
    SYSTEMTIME st[2];

    MONTHCAL_GetSelRange(infoPtr, st);

2391 2392 2393 2394
    /* 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;
2395

2396
    MONTHCAL_IsSelRangeValid(infoPtr, &st_ht, &infoPtr->firstSel, &st_ht);
2397

2398 2399 2400
    st[0] = infoPtr->firstSel;
    /* we should overwrite timestamp here */
    MONTHCAL_CopyDate(&st_ht, &st[1]);
2401

2402 2403 2404 2405
    /* bounds will be swapped here if needed */
    MONTHCAL_SetSelRange(infoPtr, st);

    return 0;
2406
  }
2407 2408 2409

done:

2410 2411 2412
  /* FIXME: this should specify a rectangle containing only the days that changed
     using InvalidateRect */
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2413

2414
  return 0;
2415 2416
}

2417

2418
static LRESULT
2419
MONTHCAL_Paint(MONTHCAL_INFO *infoPtr, HDC hdc_paint)
2420
{
2421 2422
  HDC hdc;
  PAINTSTRUCT ps;
2423

2424
  if (hdc_paint)
2425
  {
2426
    GetClientRect(infoPtr->hwndSelf, &ps.rcPaint);
2427
    hdc = hdc_paint;
2428 2429
  }
  else
2430
    hdc = BeginPaint(infoPtr->hwndSelf, &ps);
2431

2432
  MONTHCAL_Refresh(infoPtr, hdc, &ps);
2433
  if (!hdc_paint) EndPaint(infoPtr->hwndSelf, &ps);
2434
  return 0;
2435 2436
}

2437 2438 2439 2440 2441 2442 2443
static LRESULT
MONTHCAL_EraseBkgnd(const MONTHCAL_INFO *infoPtr, HDC hdc)
{
  RECT rc;

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

2444
  FillRect(hdc, &rc, infoPtr->brushes[BrushBackground]);
2445 2446 2447 2448

  return TRUE;
}

2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465
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;
}

2466
static LRESULT
2467
MONTHCAL_SetFocus(const MONTHCAL_INFO *infoPtr)
2468
{
2469
  TRACE("\n");
2470

2471
  InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
2472

2473
  return 0;
2474 2475
}

2476
/* sets the size information */
2477
static void MONTHCAL_UpdateSize(MONTHCAL_INFO *infoPtr)
2478
{
2479
  RECT *title=&infoPtr->calendars[0].title;
2480 2481
  RECT *prev=&infoPtr->titlebtnprev;
  RECT *next=&infoPtr->titlebtnnext;
2482 2483 2484 2485 2486
  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;
2487
  RECT *todayrect=&infoPtr->todayrect;
2488 2489 2490

  INT xdiv, dx, dy, i, j, x, y, c_dx, c_dy;
  WCHAR buff[80];
2491
  TEXTMETRICW tm;
2492
  INT day_width;
2493
  RECT client;
2494
  HFONT font;
2495
  SIZE size;
2496
  HDC hdc;
2497

2498
  GetClientRect(infoPtr->hwndSelf, &client);
2499

2500 2501
  hdc = GetDC(infoPtr->hwndSelf);
  font = SelectObject(hdc, infoPtr->hFont);
2502 2503

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

2507 2508
  /* find widest day name for current locale and font */
  day_width = 0;
2509 2510
  for (i = 0; i < 7; i++)
  {
2511 2512
      SIZE sz;

2513
      if (get_localized_dayname(infoPtr, i, buff, ARRAY_SIZE(buff)))
2514 2515
      {
          GetTextExtentPoint32W(hdc, buff, lstrlenW(buff), &sz);
2516
          if (sz.cx > day_width) day_width = sz.cx;
2517 2518 2519
      }
      else /* locale independent fallback on failure */
      {
2520
          GetTextExtentPoint32W(hdc, L"Sun", 3, &sz);
2521
          day_width = sz.cx;
2522 2523 2524 2525
          break;
      }
  }

2526
  day_width += 2;
2527

2528
  /* recalculate the height and width increments and offsets */
2529
  size.cx = 0;
2530
  GetTextExtentPoint32W(hdc, L"00", 2, &size);
2531

2532 2533 2534 2535
  /* restore the originally selected font */
  SelectObject(hdc, font);
  ReleaseDC(infoPtr->hwndSelf, hdc);

2536
  xdiv = (infoPtr->dwStyle & MCS_WEEKNUMBERS) ? 8 : 7;
2537

2538
  infoPtr->width_increment  = max(day_width, size.cx * 2 + 4);
2539
  infoPtr->height_increment = infoPtr->textHeight;
2540

2541
  /* calculate title area */
2542 2543 2544 2545
  title->top    = 0;
  title->bottom = 3 * infoPtr->height_increment / 2;
  title->left   = 0;
  title->right  = infoPtr->width_increment * xdiv;
2546 2547 2548

  /* set the dimensions of the next and previous buttons and center */
  /* the month text vertically */
2549 2550 2551
  prev->top    = next->top    = title->top + 4;
  prev->bottom = next->bottom = title->bottom - 4;
  prev->left   = title->left + 4;
2552
  prev->right  = prev->left + (title->bottom - title->top);
2553
  next->right  = title->right - 4;
2554
  next->left   = next->right - (title->bottom - title->top);
2555

2556 2557 2558
  /* 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 */
2559 2560
  titlemonth->top    = titleyear->top    = title->top    + (infoPtr->height_increment)/2;
  titlemonth->bottom = titleyear->bottom = title->bottom - (infoPtr->height_increment)/2;
2561

2562 2563 2564
  /* week numbers */
  weeknumrect->left  = 0;
  weeknumrect->right = infoPtr->dwStyle & MCS_WEEKNUMBERS ? prev->right : 0;
2565

2566
  /* days abbreviated names */
2567 2568
  wdays->left   = days->left   = weeknumrect->right;
  wdays->right  = days->right  = wdays->left + 7 * infoPtr->width_increment;
2569
  wdays->top    = title->bottom;
2570
  wdays->bottom = wdays->top + infoPtr->height_increment;
2571

2572
  days->top    = weeknumrect->top = wdays->bottom;
2573
  days->bottom = weeknumrect->bottom = days->top + 6 * infoPtr->height_increment;
2574

2575 2576
  todayrect->left   = 0;
  todayrect->right  = title->right;
2577 2578 2579
  todayrect->top    = days->bottom;
  todayrect->bottom = days->bottom + infoPtr->height_increment;

2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592
  /* 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;

2593 2594 2595 2596
  if (x*y != MONTHCAL_GetCalCount(infoPtr))
  {
      infoPtr->dim.cx = x;
      infoPtr->dim.cy = y;
2597
      infoPtr->calendars = heap_realloc(infoPtr->calendars, MONTHCAL_GetCalCount(infoPtr)*sizeof(CALENDAR_INFO));
2598

2599
      infoPtr->monthdayState = heap_realloc(infoPtr->monthdayState,
2600 2601
          MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)*sizeof(MONTHDAYSTATE));
      MONTHCAL_NotifyDayState(infoPtr);
2602 2603 2604 2605 2606

      /* update pointers that we'll need */
      title = &infoPtr->calendars[0].title;
      wdays = &infoPtr->calendars[0].wdays;
      days  = &infoPtr->calendars[0].days;
2607
  }
2608 2609 2610 2611 2612 2613 2614 2615

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

2616
  /* offset all rectangles to center in client area */
2617 2618
  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;
2619 2620

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

2624
  for (i = 0; i < y; i++)
2625
  {
2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637
      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);
      }
2638 2639
  }

2640 2641 2642 2643 2644 2645 2646 2647
  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;
2648

2649
  TRACE("dx=%d dy=%d client[%s] title[%s] wdays[%s] days[%s] today[%s]\n",
2650
	infoPtr->width_increment,infoPtr->height_increment,
2651
        wine_dbgstr_rect(&client),
2652 2653 2654 2655
        wine_dbgstr_rect(title),
        wine_dbgstr_rect(wdays),
        wine_dbgstr_rect(days),
        wine_dbgstr_rect(todayrect));
2656 2657
}

2658
static LRESULT MONTHCAL_Size(MONTHCAL_INFO *infoPtr, int Width, int Height)
2659
{
2660
  TRACE("(width=%d, height=%d)\n", Width, Height);
2661

2662 2663
  MONTHCAL_UpdateSize(infoPtr);
  InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
2664 2665 2666

  return 0;
}
2667

2668
static LRESULT MONTHCAL_GetFont(const MONTHCAL_INFO *infoPtr)
2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686
{
    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);

2687 2688
    MONTHCAL_UpdateSize(infoPtr);

2689 2690 2691 2692 2693 2694
    if (redraw)
        InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);

    return (LRESULT)hOldFont;
}

2695
/* update theme after a WM_THEMECHANGED message */
2696
static LRESULT theme_changed (const MONTHCAL_INFO* infoPtr)
2697 2698 2699
{
    HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
    CloseThemeData (theme);
2700
    OpenThemeData (infoPtr->hwndSelf, themeClass);
2701
    InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
2702 2703 2704
    return 0;
}

2705 2706 2707 2708 2709 2710 2711 2712 2713 2714
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;

2715
    /* make room for week numbers */
2716
    if ((lpss->styleNew ^ lpss->styleOld) & (MCS_WEEKNUMBERS | MCS_SHORTDAYSOFWEEK))
2717 2718
        MONTHCAL_UpdateSize(infoPtr);

2719 2720 2721
    return 0;
}

2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736
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;
    }

2737 2738 2739 2740 2741 2742 2743 2744 2745
    /* 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;
    }

2746 2747 2748
    return 0;
}

2749
/* FIXME: check whether dateMin/dateMax need to be adjusted. */
2750
static LRESULT
2751
MONTHCAL_Create(HWND hwnd, LPCREATESTRUCTW lpcs)
2752
{
2753
  MONTHCAL_INFO *infoPtr;
2754

2755
  /* allocate memory for info structure */
2756
  infoPtr = heap_alloc_zero(sizeof(*infoPtr));
2757
  SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
2758

2759 2760
  if (infoPtr == NULL) {
    ERR("could not allocate info memory!\n");
2761 2762
    return 0;
  }
2763

2764
  infoPtr->hwndSelf = hwnd;
2765
  infoPtr->hwndNotify = lpcs->hwndParent;
2766 2767
  infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
  infoPtr->dim.cx = infoPtr->dim.cy = 1;
2768
  infoPtr->calendars = heap_alloc_zero(sizeof(CALENDAR_INFO));
2769
  if (!infoPtr->calendars) goto fail;
2770
  infoPtr->monthdayState = heap_alloc_zero(3 * sizeof(MONTHDAYSTATE));
2771
  if (!infoPtr->monthdayState) goto fail;
2772 2773

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

Duane Clark's avatar
Duane Clark committed
2776
  GetLocalTime(&infoPtr->todaysDate);
2777
  MONTHCAL_SetFirstDayOfWeek(infoPtr, -1);
2778

2779
  infoPtr->maxSelCount   = (infoPtr->dwStyle & MCS_MULTISELECT) ? 7 : 1;
2780

2781 2782 2783 2784 2785 2786
  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;
2787

2788 2789 2790
  infoPtr->brushes[BrushBackground]  = CreateSolidBrush(infoPtr->colors[MCSC_BACKGROUND]);
  infoPtr->brushes[BrushTitle]       = CreateSolidBrush(infoPtr->colors[MCSC_TITLEBK]);
  infoPtr->brushes[BrushMonth]       = CreateSolidBrush(infoPtr->colors[MCSC_MONTHBK]);
2791

2792 2793 2794
  infoPtr->pens[PenRed]  = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
  infoPtr->pens[PenText] = CreatePen(PS_SOLID, 1, infoPtr->colors[MCSC_TEXT]);

2795 2796
  infoPtr->minSel = infoPtr->todaysDate;
  infoPtr->maxSel = infoPtr->todaysDate;
2797
  infoPtr->calendars[0].month = infoPtr->todaysDate;
2798
  infoPtr->isUnicode = TRUE;
2799

2800
  /* setup control layout and day state data */
2801
  MONTHCAL_UpdateSize(infoPtr);
2802 2803 2804 2805

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

2806
  OpenThemeData (infoPtr->hwndSelf, themeClass);
2807 2808

  return 0;
2809

2810
fail:
2811 2812 2813
  heap_free(infoPtr->monthdayState);
  heap_free(infoPtr->calendars);
  heap_free(infoPtr);
2814 2815
  return 0;
}
2816 2817

static LRESULT
2818
MONTHCAL_Destroy(MONTHCAL_INFO *infoPtr)
2819
{
2820 2821
  INT i;

2822
  /* free month calendar info data */
2823 2824
  heap_free(infoPtr->monthdayState);
  heap_free(infoPtr->calendars);
2825
  SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
2826

2827
  CloseThemeData (GetWindowTheme (infoPtr->hwndSelf));
2828

2829 2830
  for (i = 0; i < BrushLast; i++) DeleteObject(infoPtr->brushes[i]);
  for (i = 0; i < PenLast; i++) DeleteObject(infoPtr->pens[i]);
2831

2832
  heap_free(infoPtr);
2833
  return 0;
2834 2835
}

2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846
/*
 * 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;

2847
    if (hdr->hwndFrom == infoPtr->hWndYearUpDown && nmud->iDelta)
2848 2849
    {
      /* year value limits are set up explicitly after updown creation */
2850
      MONTHCAL_Scroll(infoPtr, 12 * nmud->iDelta, FALSE);
2851 2852
      MONTHCAL_NotifyDayState(infoPtr);
      MONTHCAL_NotifySelectionChange(infoPtr);
2853 2854 2855 2856
    }
  }
  return 0;
}
2857

2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871
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;
}

2872
static LRESULT WINAPI
2873
MONTHCAL_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2874
{
2875
  MONTHCAL_INFO *infoPtr = (MONTHCAL_INFO *)GetWindowLongPtrW(hwnd, 0);
2876

2877
  TRACE("hwnd=%p msg=%x wparam=%lx lparam=%lx\n", hwnd, uMsg, wParam, lParam);
2878 2879

  if (!infoPtr && (uMsg != WM_CREATE))
2880
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
2881
  switch(uMsg)
2882 2883
  {
  case MCM_GETCURSEL:
2884
    return MONTHCAL_GetCurSel(infoPtr, (LPSYSTEMTIME)lParam);
2885

2886
  case MCM_SETCURSEL:
2887
    return MONTHCAL_SetCurSel(infoPtr, (LPSYSTEMTIME)lParam);
2888

2889
  case MCM_GETMAXSELCOUNT:
2890
    return MONTHCAL_GetMaxSelCount(infoPtr);
2891

2892
  case MCM_SETMAXSELCOUNT:
2893
    return MONTHCAL_SetMaxSelCount(infoPtr, wParam);
2894

2895
  case MCM_GETSELRANGE:
2896
    return MONTHCAL_GetSelRange(infoPtr, (LPSYSTEMTIME)lParam);
2897

2898
  case MCM_SETSELRANGE:
2899
    return MONTHCAL_SetSelRange(infoPtr, (LPSYSTEMTIME)lParam);
2900

2901
  case MCM_GETMONTHRANGE:
2902
    return MONTHCAL_GetMonthRange(infoPtr, wParam, (SYSTEMTIME*)lParam);
2903

2904
  case MCM_SETDAYSTATE:
2905
    return MONTHCAL_SetDayState(infoPtr, (INT)wParam, (LPMONTHDAYSTATE)lParam);
2906

2907
  case MCM_GETMINREQRECT:
2908
    return MONTHCAL_GetMinReqRect(infoPtr, (LPRECT)lParam);
2909

2910
  case MCM_GETCOLOR:
2911
    return MONTHCAL_GetColor(infoPtr, wParam);
2912

2913
  case MCM_SETCOLOR:
2914
    return MONTHCAL_SetColor(infoPtr, wParam, (COLORREF)lParam);
2915

2916
  case MCM_GETTODAY:
2917
    return MONTHCAL_GetToday(infoPtr, (LPSYSTEMTIME)lParam);
2918

2919
  case MCM_SETTODAY:
2920
    return MONTHCAL_SetToday(infoPtr, (LPSYSTEMTIME)lParam);
2921

2922
  case MCM_HITTEST:
2923
    return MONTHCAL_HitTest(infoPtr, (PMCHITTESTINFO)lParam);
2924

2925
  case MCM_GETFIRSTDAYOFWEEK:
2926
    return MONTHCAL_GetFirstDayOfWeek(infoPtr);
2927

2928
  case MCM_SETFIRSTDAYOFWEEK:
2929
    return MONTHCAL_SetFirstDayOfWeek(infoPtr, (INT)lParam);
2930

2931
  case MCM_GETRANGE:
2932
    return MONTHCAL_GetRange(infoPtr, (LPSYSTEMTIME)lParam);
2933

2934
  case MCM_SETRANGE:
2935
    return MONTHCAL_SetRange(infoPtr, (SHORT)wParam, (LPSYSTEMTIME)lParam);
2936

2937
  case MCM_GETMONTHDELTA:
2938
    return MONTHCAL_GetMonthDelta(infoPtr);
2939

2940
  case MCM_SETMONTHDELTA:
2941
    return MONTHCAL_SetMonthDelta(infoPtr, wParam);
2942

2943
  case MCM_GETMAXTODAYWIDTH:
2944
    return MONTHCAL_GetMaxTodayWidth(infoPtr);
2945

2946 2947 2948 2949 2950 2951
  case MCM_SETUNICODEFORMAT:
    return MONTHCAL_SetUnicodeFormat(infoPtr, (BOOL)wParam);

  case MCM_GETUNICODEFORMAT:
    return MONTHCAL_GetUnicodeFormat(infoPtr);

2952 2953 2954
  case MCM_GETCALENDARCOUNT:
    return MONTHCAL_GetCalCount(infoPtr);

2955 2956
  case WM_GETDLGCODE:
    return DLGC_WANTARROWS | DLGC_WANTCHARS;
2957

2958 2959
  case WM_RBUTTONUP:
    return MONTHCAL_RButtonUp(infoPtr, lParam);
2960

2961
  case WM_LBUTTONDOWN:
2962
    return MONTHCAL_LButtonDown(infoPtr, lParam);
2963

2964
  case WM_MOUSEMOVE:
2965
    return MONTHCAL_MouseMove(infoPtr, lParam);
2966

2967
  case WM_LBUTTONUP:
2968
    return MONTHCAL_LButtonUp(infoPtr, lParam);
2969

2970
  case WM_PAINT:
2971
    return MONTHCAL_Paint(infoPtr, (HDC)wParam);
2972

2973 2974 2975
  case WM_PRINTCLIENT:
    return MONTHCAL_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);

2976 2977 2978
  case WM_ERASEBKGND:
    return MONTHCAL_EraseBkgnd(infoPtr, (HDC)wParam);

2979
  case WM_SETFOCUS:
2980
    return MONTHCAL_SetFocus(infoPtr);
2981 2982

  case WM_SIZE:
2983
    return MONTHCAL_Size(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
2984

2985 2986 2987
  case WM_NOTIFY:
    return MONTHCAL_Notify(infoPtr, (NMHDR*)lParam);

2988
  case WM_CREATE:
2989
    return MONTHCAL_Create(hwnd, (LPCREATESTRUCTW)lParam);
2990

2991 2992 2993 2994 2995 2996
  case WM_SETFONT:
    return MONTHCAL_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);

  case WM_GETFONT:
    return MONTHCAL_GetFont(infoPtr);

2997
  case WM_TIMER:
2998
    return MONTHCAL_Timer(infoPtr, wParam);
2999 3000 3001
    
  case WM_THEMECHANGED:
    return theme_changed (infoPtr);
3002

3003
  case WM_DESTROY:
3004
    return MONTHCAL_Destroy(infoPtr);
3005

3006 3007 3008 3009
  case WM_SYSCOLORCHANGE:
    COMCTL32_RefreshSysColors();
    return 0;

3010 3011 3012
  case WM_STYLECHANGED:
    return MONTHCAL_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);

3013 3014 3015
  case WM_STYLECHANGING:
    return MONTHCAL_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);

3016
  default:
3017
    if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
3018
      ERR( "unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
3019
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
3020
  }
3021 3022 3023
}


3024
void
3025
MONTHCAL_Register(void)
3026
{
3027
  WNDCLASSW wndClass;
3028

3029
  ZeroMemory(&wndClass, sizeof(WNDCLASSW));
3030
  wndClass.style         = CS_GLOBALCLASS;
3031
  wndClass.lpfnWndProc   = MONTHCAL_WindowProc;
3032 3033
  wndClass.cbClsExtra    = 0;
  wndClass.cbWndExtra    = sizeof(MONTHCAL_INFO *);
3034
  wndClass.hCursor       = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3035
  wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
3036
  wndClass.lpszClassName = MONTHCAL_CLASSW;
3037

3038
  RegisterClassW(&wndClass);
3039 3040 3041
}


3042
void
3043
MONTHCAL_Unregister(void)
3044
{
3045
    UnregisterClassW(MONTHCAL_CLASSW, NULL);
3046
}