monthcal.c 87.3 KB
Newer Older
1 2
/*
 * Month calendar control
3
 *
4
 * Copyright 1998, 1999 Eric Kohl (ekohl@abo.rhein-zeitung.de)
5 6 7
 * Copyright 1999 Alex Priem (alexp@sci.kun.nl)
 * Copyright 1999 Chris Morgan <cmorgan@wpi.edu> and
 *		  James Abbatiello <abbeyj@wpi.edu>
8
 * Copyright 2000 Uwe Bonnes <bon@elektron.ikp.physik.tu-darmstadt.de>
9
 * Copyright 2009-2011 Nikolay Sivov
10
 *
11 12 13 14 15 16 17 18 19 20 21 22
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
23
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24
 *
25
 * TODO:
26 27 28 29 30
 *    -- MCM_[GS]ETUNICODEFORMAT
 *    -- handle resources better (doesn't work now); 
 *    -- take care of internationalization.
 *    -- keyboard handling.
 *    -- search for FIXME
31 32
 */

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

39
#include "windef.h"
40
#include "winbase.h"
41
#include "wingdi.h"
42 43
#include "winuser.h"
#include "winnls.h"
44
#include "commctrl.h"
45
#include "comctl32.h"
46
#include "uxtheme.h"
47
#include "vssym32.h"
48
#include "wine/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[] = { 'S','c','r','o','l','l','b','a','r',0 };
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 786
  static const WCHAR fmtW[] = { '%','d',0 };
  WCHAR buf[10];
787
  RECT r, r_temp;
788
  COLORREF oldCol = 0;
789
  COLORREF oldBk  = 0;
790
  INT old_bkmode, selection;
791

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

920 921 922 923 924 925 926 927 928 929 930 931
  /* 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 */
932
  GetTextExtentPoint32W(hdc, &strW[yearoffset], lstrlenW(yearW), &sz);
933 934
  infoPtr->calendars[calIdx].titleyear.right = infoPtr->calendars[calIdx].titleyear.left + sz.cx;

935
  GetTextExtentPoint32W(hdc, monthW, lstrlenW(monthW), &sz);
936
  infoPtr->calendars[calIdx].titlemonth.right = infoPtr->calendars[calIdx].titlemonth.left + sz.cx;
937 938 939

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

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

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

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

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

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

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

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

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

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

  /* reduce rectangle to one week number */
1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047
  r.bottom = r.top + infoPtr->height_increment;

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

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

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

/* bottom today date */
static void MONTHCAL_PaintTodayTitle(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
{
1059 1060 1061 1062 1063 1064 1065 1066
  static const WCHAR fmt_todayW[] = { '%','s',' ','%','s',0 };
  WCHAR buf_todayW[30], buf_dateW[20], buf[80];
  RECT text_rect, box_rect;
  HFONT old_font;
  INT col;

  if(infoPtr->dwStyle & MCS_NOTODAY) return;

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

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

1078 1079 1080
  wsprintfW(buf, fmt_todayW, buf_todayW, buf_dateW);
  DrawTextW(hdc, buf, -1, &text_rect, DT_CALCRECT | DT_LEFT | DT_VCENTER | DT_SINGLELINE);
  DrawTextW(hdc, buf, -1, &text_rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
1081

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

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

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

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

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

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

1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165
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);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1277
  if(!rect) return FALSE;
1278

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

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

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

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

1290
  return TRUE;
1291 1292
}

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

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

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

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

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

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

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

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

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

1359

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

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

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


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

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

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


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

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

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

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

1415
    new_day = wcstol(buf, NULL, 10);
1416

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

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

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

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

1442
  return prev;
1443 1444 1445
}

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

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

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

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

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

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

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

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

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

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

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

1507
    return TRUE;
1508 1509
}

1510

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

1516
  if (!range) return 0;
1517

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

  return infoPtr->rangeValid;
}

1524

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

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

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

  return 1;
}

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

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

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

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

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

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

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

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

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

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

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

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

1609 1610 1611
  return TRUE;
}

1612

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

1619

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

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

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

  return TRUE;
}


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

1639
  if(!range) return FALSE;
1640

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

1649 1650 1651
  return FALSE;
}

1652

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

1749
    infoPtr->todaysDate = *today;
1750

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

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

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

1767
  return 0;
1768 1769
}

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

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

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

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

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

1810 1811 1812 1813 1814
  htinfo.st = st_null;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1966
  heap_free(state);
1967 1968
}

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

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

1983 1984 1985
  if (keep_selection)
    return;

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

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

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

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

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

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

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

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

2064 2065
  return 0;
}
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 2106 2107 2108 2109 2110 2111 2112 2113
/***
 * 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;
}

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

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

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

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

    SetFocus(infoPtr->hWndYearEdit);
2147 2148
}

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

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

2163 2164
  SetCapture(infoPtr->hwndSelf);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2266
  return 1;
2267 2268
}

2269

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

  TRACE("\n");
2278

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

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

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

2289 2290
  ReleaseCapture();

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

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

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

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

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

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

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

2320
    MONTHCAL_NotifySelect(infoPtr);
2321
  }
2322

2323
  return 0;
2324 2325
}

2326

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

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

    if(infoPtr->todaySet) return 0;

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

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

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

2357 2358
  return 0;
}
2359

2360 2361

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

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

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

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

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

2386
  st_ht = ht.st;
2387 2388 2389

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

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

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

    MONTHCAL_GetSelRange(infoPtr, st);

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

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

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

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

    return 0;
2413
  }
2414 2415 2416

done:

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

2421
  return 0;
2422 2423
}

2424

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

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

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

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

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

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

  return TRUE;
}

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

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

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

2480
  return 0;
2481 2482
}

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

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

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

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

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

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

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

2535
  day_width += 2;
2536

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  return 0;
}
2676

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

2696 2697
    MONTHCAL_UpdateSize(infoPtr);

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

    return (LRESULT)hOldFont;
}

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

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

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

2727 2728 2729
    return 0;
}

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

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

2754 2755 2756
    return 0;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  return 0;
2817

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  case MCM_GETUNICODEFORMAT:
    return MONTHCAL_GetUnicodeFormat(infoPtr);

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

2963 2964
  case WM_GETDLGCODE:
    return DLGC_WANTARROWS | DLGC_WANTCHARS;
2965

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

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

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

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

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

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

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

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

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

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

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

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

  case WM_GETFONT:
    return MONTHCAL_GetFont(infoPtr);

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

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

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

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

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

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


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

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

3046
  RegisterClassW(&wndClass);
3047 3048 3049
}


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