1 /* 2 * Month calendar control 3 * 4 * Copyright 1998, 1999 Eric Kohl (ekohl@abo.rhein-zeitung.de) 5 * Copyright 1999 Alex Priem (alexp@sci.kun.nl) 6 * Copyright 1999 Chris Morgan <cmorgan@wpi.edu> and 7 * 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 * This library is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU Lesser General Public 13 * License as published by the Free Software Foundation; either 14 * version 2.1 of the License, or (at your option) any later version. 15 * 16 * This library is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 * Lesser General Public License for more details. 20 * 21 * You should have received a copy of the GNU Lesser General Public 22 * 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 * NOTE 26 * 27 * This code was audited for completeness against the documented features 28 * of Comctl32.dll version 6.0 on Oct. 20, 2004, by Dimitrie O. Paun. 29 * 30 * Unless otherwise noted, we believe this code to be complete, as per 31 * the specification mentioned above. 32 * If you discover missing features, or bugs, please note them below. 33 * 34 * TODO: 35 * -- MCM_[GS]ETUNICODEFORMAT 36 * -- handle resources better (doesn't work now); 37 * -- take care of internationalization. 38 * -- keyboard handling. 39 * -- search for FIXME 40 */ 41 42 #include "comctl32.h" 43 44 WINE_DEFAULT_DEBUG_CHANNEL(monthcal); 45 46 /* FIXME: Inspect */ 47 #define MCS_NOSELCHANGEONNAV 0x0100 48 49 #define MC_SEL_LBUTUP 1 /* Left button released */ 50 #define MC_SEL_LBUTDOWN 2 /* Left button pressed in calendar */ 51 #define MC_PREVPRESSED 4 /* Prev month button pressed */ 52 #define MC_NEXTPRESSED 8 /* Next month button pressed */ 53 #define MC_PREVNEXTMONTHDELAY 350 /* when continuously pressing `next/prev 54 month', wait 350 ms before going 55 to the next/prev month */ 56 #define MC_TODAYUPDATEDELAY 120000 /* time between today check for update (2 min) */ 57 58 #define MC_PREVNEXTMONTHTIMER 1 /* Timer IDs */ 59 #define MC_TODAYUPDATETIMER 2 60 61 #define MC_CALENDAR_PADDING 6 62 63 #define countof(arr) (sizeof(arr)/sizeof(arr[0])) 64 65 /* convert from days to 100 nanoseconds unit - used as FILETIME unit */ 66 #define DAYSTO100NSECS(days) (((ULONGLONG)(days))*24*60*60*10000000) 67 68 enum CachedPen 69 { 70 PenRed = 0, 71 PenText, 72 PenLast 73 }; 74 75 enum CachedBrush 76 { 77 BrushTitle = 0, 78 BrushMonth, 79 BrushBackground, 80 BrushLast 81 }; 82 83 /* single calendar data */ 84 typedef struct _CALENDAR_INFO 85 { 86 RECT title; /* rect for the header above the calendar */ 87 RECT titlemonth; /* the 'month name' text in the header */ 88 RECT titleyear; /* the 'year number' text in the header */ 89 RECT wdays; /* week days at top */ 90 RECT days; /* calendar area */ 91 RECT weeknums; /* week numbers at left side */ 92 93 SYSTEMTIME month;/* contains calendar main month/year */ 94 } CALENDAR_INFO; 95 96 typedef struct 97 { 98 HWND hwndSelf; 99 DWORD dwStyle; /* cached GWL_STYLE */ 100 101 COLORREF colors[MCSC_TRAILINGTEXT+1]; 102 HBRUSH brushes[BrushLast]; 103 HPEN pens[PenLast]; 104 105 HFONT hFont; 106 HFONT hBoldFont; 107 int textHeight; 108 int height_increment; 109 int width_increment; 110 INT delta; /* scroll rate; # of months that the */ 111 /* control moves when user clicks a scroll button */ 112 int firstDay; /* Start month calendar with firstDay's day, 113 stored in SYSTEMTIME format */ 114 BOOL firstDaySet; /* first week day differs from locale defined */ 115 116 BOOL isUnicode; /* value set with MCM_SETUNICODE format */ 117 118 MONTHDAYSTATE *monthdayState; 119 SYSTEMTIME todaysDate; 120 BOOL todaySet; /* Today was forced with MCM_SETTODAY */ 121 int status; /* See MC_SEL flags */ 122 SYSTEMTIME firstSel; /* first selected day */ 123 INT maxSelCount; 124 SYSTEMTIME minSel; /* contains single selection when used without MCS_MULTISELECT */ 125 SYSTEMTIME maxSel; 126 SYSTEMTIME focusedSel; /* date currently focused with mouse movement */ 127 DWORD rangeValid; 128 SYSTEMTIME minDate; 129 SYSTEMTIME maxDate; 130 131 RECT titlebtnnext; /* the `next month' button in the header */ 132 RECT titlebtnprev; /* the `prev month' button in the header */ 133 RECT todayrect; /* `today: xx/xx/xx' text rect */ 134 HWND hwndNotify; /* Window to receive the notifications */ 135 HWND hWndYearEdit; /* Window Handle of edit box to handle years */ 136 HWND hWndYearUpDown;/* Window Handle of updown box to handle years */ 137 WNDPROC EditWndProc; /* original Edit window procedure */ 138 139 CALENDAR_INFO *calendars; 140 SIZE dim; /* [cx,cy] - dimensions of calendars matrix, row/column count */ 141 } MONTHCAL_INFO, *LPMONTHCAL_INFO; 142 143 static const WCHAR themeClass[] = { 'S','c','r','o','l','l','b','a','r',0 }; 144 145 /* empty SYSTEMTIME const */ 146 static const SYSTEMTIME st_null; 147 /* valid date limits */ 148 static const SYSTEMTIME max_allowed_date = { /* wYear */ 9999, /* wMonth */ 12, /* wDayOfWeek */ 0, /* wDay */ 31 }; 149 static const SYSTEMTIME min_allowed_date = { /* wYear */ 1752, /* wMonth */ 9, /* wDayOfWeek */ 0, /* wDay */ 14 }; 150 151 /* Prev/Next buttons */ 152 enum nav_direction 153 { 154 DIRECTION_BACKWARD, 155 DIRECTION_FORWARD 156 }; 157 158 /* helper functions */ 159 static inline INT MONTHCAL_GetCalCount(const MONTHCAL_INFO *infoPtr) 160 { 161 return infoPtr->dim.cx * infoPtr->dim.cy; 162 } 163 164 /* send a single MCN_SELCHANGE notification */ 165 static inline void MONTHCAL_NotifySelectionChange(const MONTHCAL_INFO *infoPtr) 166 { 167 NMSELCHANGE nmsc; 168 169 nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf; 170 nmsc.nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID); 171 nmsc.nmhdr.code = MCN_SELCHANGE; 172 nmsc.stSelStart = infoPtr->minSel; 173 nmsc.stSelStart.wDayOfWeek = 0; 174 if(infoPtr->dwStyle & MCS_MULTISELECT){ 175 nmsc.stSelEnd = infoPtr->maxSel; 176 nmsc.stSelEnd.wDayOfWeek = 0; 177 } 178 else 179 nmsc.stSelEnd = st_null; 180 181 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc); 182 } 183 184 /* send a single MCN_SELECT notification */ 185 static inline void MONTHCAL_NotifySelect(const MONTHCAL_INFO *infoPtr) 186 { 187 NMSELCHANGE nmsc; 188 189 nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf; 190 nmsc.nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID); 191 nmsc.nmhdr.code = MCN_SELECT; 192 nmsc.stSelStart = infoPtr->minSel; 193 nmsc.stSelStart.wDayOfWeek = 0; 194 if(infoPtr->dwStyle & MCS_MULTISELECT){ 195 nmsc.stSelEnd = infoPtr->maxSel; 196 nmsc.stSelEnd.wDayOfWeek = 0; 197 } 198 else 199 nmsc.stSelEnd = st_null; 200 201 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc); 202 } 203 204 static inline int MONTHCAL_MonthDiff(const SYSTEMTIME *left, const SYSTEMTIME *right) 205 { 206 return (right->wYear - left->wYear)*12 + right->wMonth - left->wMonth; 207 } 208 209 /* returns the number of days in any given month, checking for leap days */ 210 /* January is 1, December is 12 */ 211 int MONTHCAL_MonthLength(int month, int year) 212 { 213 const int mdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 214 /* Wrap around, this eases handling. Getting length only we shouldn't care 215 about year change here cause January and December have 216 the same day quantity */ 217 if(month == 0) 218 month = 12; 219 else if(month == 13) 220 month = 1; 221 222 /* special case for calendar transition year */ 223 if(month == min_allowed_date.wMonth && year == min_allowed_date.wYear) return 19; 224 225 /* if we have a leap year add 1 day to February */ 226 /* a leap year is a year either divisible by 400 */ 227 /* or divisible by 4 and not by 100 */ 228 if(month == 2) { /* February */ 229 return mdays[month - 1] + ((year%400 == 0) ? 1 : ((year%100 != 0) && 230 (year%4 == 0)) ? 1 : 0); 231 } 232 else { 233 return mdays[month - 1]; 234 } 235 } 236 237 /* compares timestamps using date part only */ 238 static inline BOOL MONTHCAL_IsDateEqual(const SYSTEMTIME *first, const SYSTEMTIME *second) 239 { 240 return (first->wYear == second->wYear) && (first->wMonth == second->wMonth) && 241 (first->wDay == second->wDay); 242 } 243 244 /* make sure that date fields are valid */ 245 static BOOL MONTHCAL_ValidateDate(const SYSTEMTIME *time) 246 { 247 if (time->wMonth < 1 || time->wMonth > 12 ) 248 return FALSE; 249 if (time->wDay == 0 || time->wDay > MONTHCAL_MonthLength(time->wMonth, time->wYear)) 250 return FALSE; 251 252 return TRUE; 253 } 254 255 /* Copies timestamp part only. 256 * 257 * PARAMETERS 258 * 259 * [I] from : source date 260 * [O] to : dest date 261 */ 262 static void MONTHCAL_CopyTime(const SYSTEMTIME *from, SYSTEMTIME *to) 263 { 264 to->wHour = from->wHour; 265 to->wMinute = from->wMinute; 266 to->wSecond = from->wSecond; 267 } 268 269 /* Copies date part only. 270 * 271 * PARAMETERS 272 * 273 * [I] from : source date 274 * [O] to : dest date 275 */ 276 static void MONTHCAL_CopyDate(const SYSTEMTIME *from, SYSTEMTIME *to) 277 { 278 to->wYear = from->wYear; 279 to->wMonth = from->wMonth; 280 to->wDay = from->wDay; 281 to->wDayOfWeek = from->wDayOfWeek; 282 } 283 284 /* Compares two dates in SYSTEMTIME format 285 * 286 * PARAMETERS 287 * 288 * [I] first : pointer to valid first date data to compare 289 * [I] second : pointer to valid second date data to compare 290 * 291 * RETURN VALUE 292 * 293 * -1 : first < second 294 * 0 : first == second 295 * 1 : first > second 296 * 297 * Note that no date validation performed, already validated values expected. 298 */ 299 LONG MONTHCAL_CompareSystemTime(const SYSTEMTIME *first, const SYSTEMTIME *second) 300 { 301 FILETIME ft_first, ft_second; 302 303 SystemTimeToFileTime(first, &ft_first); 304 SystemTimeToFileTime(second, &ft_second); 305 306 return CompareFileTime(&ft_first, &ft_second); 307 } 308 309 static LONG MONTHCAL_CompareMonths(const SYSTEMTIME *first, const SYSTEMTIME *second) 310 { 311 SYSTEMTIME st_first, st_second; 312 313 st_first = st_second = st_null; 314 MONTHCAL_CopyDate(first, &st_first); 315 MONTHCAL_CopyDate(second, &st_second); 316 st_first.wDay = st_second.wDay = 1; 317 318 return MONTHCAL_CompareSystemTime(&st_first, &st_second); 319 } 320 321 static LONG MONTHCAL_CompareDate(const SYSTEMTIME *first, const SYSTEMTIME *second) 322 { 323 SYSTEMTIME st_first, st_second; 324 325 st_first = st_second = st_null; 326 MONTHCAL_CopyDate(first, &st_first); 327 MONTHCAL_CopyDate(second, &st_second); 328 329 return MONTHCAL_CompareSystemTime(&st_first, &st_second); 330 } 331 332 /* Checks largest possible date range and configured one 333 * 334 * PARAMETERS 335 * 336 * [I] infoPtr : valid pointer to control data 337 * [I] date : pointer to valid date data to check 338 * [I] fix : make date fit valid range 339 * 340 * RETURN VALUE 341 * 342 * TRUE - date within largest and configured range 343 * FALSE - date is outside largest or configured range 344 */ 345 static BOOL MONTHCAL_IsDateInValidRange(const MONTHCAL_INFO *infoPtr, 346 SYSTEMTIME *date, BOOL fix) 347 { 348 const SYSTEMTIME *fix_st = NULL; 349 350 if(MONTHCAL_CompareSystemTime(date, &max_allowed_date) == 1) { 351 fix_st = &max_allowed_date; 352 } 353 else if(MONTHCAL_CompareSystemTime(date, &min_allowed_date) == -1) { 354 fix_st = &min_allowed_date; 355 } 356 else { 357 if(infoPtr->rangeValid & GDTR_MAX) { 358 if((MONTHCAL_CompareSystemTime(date, &infoPtr->maxDate) == 1)) { 359 fix_st = &infoPtr->maxDate; 360 } 361 } 362 363 if(infoPtr->rangeValid & GDTR_MIN) { 364 if((MONTHCAL_CompareSystemTime(date, &infoPtr->minDate) == -1)) { 365 fix_st = &infoPtr->minDate; 366 } 367 } 368 } 369 370 if (fix && fix_st) { 371 date->wYear = fix_st->wYear; 372 date->wMonth = fix_st->wMonth; 373 } 374 375 return !fix_st; 376 } 377 378 /* Checks passed range width with configured maximum selection count 379 * 380 * PARAMETERS 381 * 382 * [I] infoPtr : valid pointer to control data 383 * [I] range0 : pointer to valid date data (requested bound) 384 * [I] range1 : pointer to valid date data (primary bound) 385 * [O] adjust : returns adjusted range bound to fit maximum range (optional) 386 * 387 * Adjust value computed basing on primary bound and current maximum selection 388 * count. For simple range check (without adjusted value required) (range0, range1) 389 * relation means nothing. 390 * 391 * RETURN VALUE 392 * 393 * TRUE - range is shorter or equal to maximum 394 * FALSE - range is larger than maximum 395 */ 396 static BOOL MONTHCAL_IsSelRangeValid(const MONTHCAL_INFO *infoPtr, 397 const SYSTEMTIME *range0, 398 const SYSTEMTIME *range1, 399 SYSTEMTIME *adjust) 400 { 401 ULARGE_INTEGER ul_range0, ul_range1, ul_diff; 402 FILETIME ft_range0, ft_range1; 403 LONG cmp; 404 405 SystemTimeToFileTime(range0, &ft_range0); 406 SystemTimeToFileTime(range1, &ft_range1); 407 408 ul_range0.u.LowPart = ft_range0.dwLowDateTime; 409 ul_range0.u.HighPart = ft_range0.dwHighDateTime; 410 ul_range1.u.LowPart = ft_range1.dwLowDateTime; 411 ul_range1.u.HighPart = ft_range1.dwHighDateTime; 412 413 cmp = CompareFileTime(&ft_range0, &ft_range1); 414 415 if(cmp == 1) 416 ul_diff.QuadPart = ul_range0.QuadPart - ul_range1.QuadPart; 417 else 418 ul_diff.QuadPart = -ul_range0.QuadPart + ul_range1.QuadPart; 419 420 if(ul_diff.QuadPart >= DAYSTO100NSECS(infoPtr->maxSelCount)) { 421 422 if(adjust) { 423 if(cmp == 1) 424 ul_range0.QuadPart = ul_range1.QuadPart + DAYSTO100NSECS(infoPtr->maxSelCount - 1); 425 else 426 ul_range0.QuadPart = ul_range1.QuadPart - DAYSTO100NSECS(infoPtr->maxSelCount - 1); 427 428 ft_range0.dwLowDateTime = ul_range0.u.LowPart; 429 ft_range0.dwHighDateTime = ul_range0.u.HighPart; 430 FileTimeToSystemTime(&ft_range0, adjust); 431 } 432 433 return FALSE; 434 } 435 else return TRUE; 436 } 437 438 /* Used in MCM_SETRANGE/MCM_SETSELRANGE to determine resulting time part. 439 Milliseconds are intentionally not validated. */ 440 static BOOL MONTHCAL_ValidateTime(const SYSTEMTIME *time) 441 { 442 if((time->wHour > 24) || (time->wMinute > 59) || (time->wSecond > 59)) 443 return FALSE; 444 else 445 return TRUE; 446 } 447 448 /* Note:Depending on DST, this may be offset by a day. 449 Need to find out if we're on a DST place & adjust the clock accordingly. 450 Above function assumes we have a valid data. 451 Valid for year>1752; 1 <= d <= 31, 1 <= m <= 12. 452 0 = Sunday. 453 */ 454 455 /* Returns the day in the week 456 * 457 * PARAMETERS 458 * [i] date : input date 459 * [I] inplace : set calculated value back to date structure 460 * 461 * RETURN VALUE 462 * day of week in SYSTEMTIME format: (0 == sunday,..., 6 == saturday) 463 */ 464 int MONTHCAL_CalculateDayOfWeek(SYSTEMTIME *date, BOOL inplace) 465 { 466 SYSTEMTIME st = st_null; 467 FILETIME ft; 468 469 MONTHCAL_CopyDate(date, &st); 470 471 SystemTimeToFileTime(&st, &ft); 472 FileTimeToSystemTime(&ft, &st); 473 474 if (inplace) date->wDayOfWeek = st.wDayOfWeek; 475 476 return st.wDayOfWeek; 477 } 478 479 /* add/subtract 'months' from date */ 480 static inline void MONTHCAL_GetMonth(SYSTEMTIME *date, INT months) 481 { 482 INT length, m = date->wMonth + months; 483 484 date->wYear += m > 0 ? (m - 1) / 12 : m / 12 - 1; 485 date->wMonth = m > 0 ? (m - 1) % 12 + 1 : 12 + m % 12; 486 /* fix moving from last day in a month */ 487 length = MONTHCAL_MonthLength(date->wMonth, date->wYear); 488 if(date->wDay > length) date->wDay = length; 489 MONTHCAL_CalculateDayOfWeek(date, TRUE); 490 } 491 492 /* properly updates date to point on next month */ 493 static inline void MONTHCAL_GetNextMonth(SYSTEMTIME *date) 494 { 495 MONTHCAL_GetMonth(date, 1); 496 } 497 498 /* properly updates date to point on prev month */ 499 static inline void MONTHCAL_GetPrevMonth(SYSTEMTIME *date) 500 { 501 MONTHCAL_GetMonth(date, -1); 502 } 503 504 /* Returns full date for a first currently visible day */ 505 static void MONTHCAL_GetMinDate(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *date) 506 { 507 /* zero indexed calendar has the earliest date */ 508 SYSTEMTIME st_first = infoPtr->calendars[0].month; 509 INT firstDay; 510 511 st_first.wDay = 1; 512 firstDay = MONTHCAL_CalculateDayOfWeek(&st_first, FALSE); 513 514 *date = infoPtr->calendars[0].month; 515 MONTHCAL_GetPrevMonth(date); 516 517 date->wDay = MONTHCAL_MonthLength(date->wMonth, date->wYear) + 518 (infoPtr->firstDay - firstDay) % 7 + 1; 519 520 if(date->wDay > MONTHCAL_MonthLength(date->wMonth, date->wYear)) 521 date->wDay -= 7; 522 523 /* fix day of week */ 524 MONTHCAL_CalculateDayOfWeek(date, TRUE); 525 } 526 527 /* Returns full date for a last currently visible day */ 528 static void MONTHCAL_GetMaxDate(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *date) 529 { 530 /* the latest date is in latest calendar */ 531 SYSTEMTIME st, *lt_month = &infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month; 532 INT first_day; 533 534 *date = *lt_month; 535 st = *lt_month; 536 537 /* day of week of first day of current month */ 538 st.wDay = 1; 539 first_day = MONTHCAL_CalculateDayOfWeek(&st, FALSE); 540 541 MONTHCAL_GetNextMonth(date); 542 MONTHCAL_GetPrevMonth(&st); 543 544 /* last calendar starts with some date from previous month that not displayed */ 545 st.wDay = MONTHCAL_MonthLength(st.wMonth, st.wYear) + 546 (infoPtr->firstDay - first_day) % 7 + 1; 547 if (st.wDay > MONTHCAL_MonthLength(st.wMonth, st.wYear)) st.wDay -= 7; 548 549 /* Use month length to get max day. 42 means max day count in calendar area */ 550 date->wDay = 42 - (MONTHCAL_MonthLength(st.wMonth, st.wYear) - st.wDay + 1) - 551 MONTHCAL_MonthLength(lt_month->wMonth, lt_month->wYear); 552 553 /* fix day of week */ 554 MONTHCAL_CalculateDayOfWeek(date, TRUE); 555 } 556 557 /* From a given point calculate the row, column and day in the calendar, 558 'day == 0' means the last day of the last month. */ 559 static int MONTHCAL_GetDayFromPos(const MONTHCAL_INFO *infoPtr, POINT pt, INT calIdx) 560 { 561 SYSTEMTIME st = infoPtr->calendars[calIdx].month; 562 int firstDay, col, row; 563 RECT client; 564 565 GetClientRect(infoPtr->hwndSelf, &client); 566 567 /* if the point is outside the x bounds of the window put it at the boundary */ 568 if (pt.x > client.right) pt.x = client.right; 569 570 col = (pt.x - infoPtr->calendars[calIdx].days.left ) / infoPtr->width_increment; 571 row = (pt.y - infoPtr->calendars[calIdx].days.top ) / infoPtr->height_increment; 572 573 st.wDay = 1; 574 firstDay = (MONTHCAL_CalculateDayOfWeek(&st, FALSE) + 6 - infoPtr->firstDay) % 7; 575 return col + 7 * row - firstDay; 576 } 577 578 /* Get day position for given date and calendar 579 * 580 * PARAMETERS 581 * 582 * [I] infoPtr : pointer to control data 583 * [I] date : date value 584 * [O] col : day column (zero based) 585 * [O] row : week column (zero based) 586 * [I] calIdx : calendar index 587 */ 588 static void MONTHCAL_GetDayPos(const MONTHCAL_INFO *infoPtr, const SYSTEMTIME *date, 589 INT *col, INT *row, INT calIdx) 590 { 591 SYSTEMTIME st = infoPtr->calendars[calIdx].month; 592 INT first; 593 594 st.wDay = 1; 595 first = (MONTHCAL_CalculateDayOfWeek(&st, FALSE) + 6 - infoPtr->firstDay) % 7; 596 597 if (calIdx == 0 || calIdx == MONTHCAL_GetCalCount(infoPtr)-1) { 598 const SYSTEMTIME *cal = &infoPtr->calendars[calIdx].month; 599 LONG cmp = MONTHCAL_CompareMonths(date, &st); 600 601 /* previous month */ 602 if (cmp == -1) { 603 *col = (first - MONTHCAL_MonthLength(date->wMonth, cal->wYear) + date->wDay) % 7; 604 *row = 0; 605 return; 606 } 607 608 /* next month calculation is same as for current, just add current month length */ 609 if (cmp == 1) 610 first += MONTHCAL_MonthLength(cal->wMonth, cal->wYear); 611 } 612 613 *col = (date->wDay + first) % 7; 614 *row = (date->wDay + first - *col) / 7; 615 } 616 617 /* returns bounding box for day in given position in given calendar */ 618 static inline void MONTHCAL_GetDayRectI(const MONTHCAL_INFO *infoPtr, RECT *r, 619 INT col, INT row, INT calIdx) 620 { 621 r->left = infoPtr->calendars[calIdx].days.left + col * infoPtr->width_increment; 622 r->right = r->left + infoPtr->width_increment; 623 r->top = infoPtr->calendars[calIdx].days.top + row * infoPtr->height_increment; 624 r->bottom = r->top + infoPtr->textHeight; 625 } 626 627 /* Returns bounding box for given date 628 * 629 * NOTE: when calendar index is unknown pass -1 630 */ 631 static BOOL MONTHCAL_GetDayRect(const MONTHCAL_INFO *infoPtr, const SYSTEMTIME *date, RECT *r, INT calIdx) 632 { 633 INT col, row; 634 635 if (!MONTHCAL_ValidateDate(date)) 636 { 637 SetRectEmpty(r); 638 return FALSE; 639 } 640 641 if (calIdx == -1) 642 { 643 INT cmp = MONTHCAL_CompareMonths(date, &infoPtr->calendars[0].month); 644 645 if (cmp <= 0) 646 calIdx = 0; 647 else 648 { 649 cmp = MONTHCAL_CompareMonths(date, &infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month); 650 if (cmp >= 0) 651 calIdx = MONTHCAL_GetCalCount(infoPtr)-1; 652 else 653 { 654 for (calIdx = 1; calIdx < MONTHCAL_GetCalCount(infoPtr)-1; calIdx++) 655 if (MONTHCAL_CompareMonths(date, &infoPtr->calendars[calIdx].month) == 0) 656 break; 657 } 658 } 659 } 660 661 MONTHCAL_GetDayPos(infoPtr, date, &col, &row, calIdx); 662 MONTHCAL_GetDayRectI(infoPtr, r, col, row, calIdx); 663 664 return TRUE; 665 } 666 667 static LRESULT 668 MONTHCAL_GetMonthRange(const MONTHCAL_INFO *infoPtr, DWORD flag, SYSTEMTIME *st) 669 { 670 INT range; 671 672 TRACE("flag=%d, st=%p\n", flag, st); 673 674 switch (flag) { 675 case GMR_VISIBLE: 676 { 677 if (st) 678 { 679 st[0] = infoPtr->calendars[0].month; 680 st[1] = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month; 681 682 if (st[0].wMonth == min_allowed_date.wMonth && 683 st[0].wYear == min_allowed_date.wYear) 684 { 685 st[0].wDay = min_allowed_date.wDay; 686 } 687 else 688 st[0].wDay = 1; 689 MONTHCAL_CalculateDayOfWeek(&st[0], TRUE); 690 691 st[1].wDay = MONTHCAL_MonthLength(st[1].wMonth, st[1].wYear); 692 MONTHCAL_CalculateDayOfWeek(&st[1], TRUE); 693 } 694 695 range = MONTHCAL_GetCalCount(infoPtr); 696 break; 697 } 698 case GMR_DAYSTATE: 699 { 700 if (st) 701 { 702 MONTHCAL_GetMinDate(infoPtr, &st[0]); 703 MONTHCAL_GetMaxDate(infoPtr, &st[1]); 704 } 705 /* include two partially visible months */ 706 range = MONTHCAL_GetCalCount(infoPtr) + 2; 707 break; 708 } 709 default: 710 WARN("Unknown flag value, got %d\n", flag); 711 range = 0; 712 } 713 714 return range; 715 } 716 717 /* Focused day helper: 718 719 - set focused date to given value; 720 - reset to zero value if NULL passed; 721 - invalidate previous and new day rectangle only if needed. 722 723 Returns TRUE if focused day changed, FALSE otherwise. 724 */ 725 static BOOL MONTHCAL_SetDayFocus(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *st) 726 { 727 RECT r; 728 729 if(st) 730 { 731 /* there's nothing to do if it's the same date, 732 mouse move within same date rectangle case */ 733 if(MONTHCAL_IsDateEqual(&infoPtr->focusedSel, st)) return FALSE; 734 735 /* invalidate old focused day */ 736 if (MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1)) 737 InvalidateRect(infoPtr->hwndSelf, &r, FALSE); 738 739 infoPtr->focusedSel = *st; 740 } 741 742 /* On set invalidates new day, on reset clears previous focused day. */ 743 if (MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1)) 744 InvalidateRect(infoPtr->hwndSelf, &r, FALSE); 745 746 if(!st && MONTHCAL_ValidateDate(&infoPtr->focusedSel)) 747 infoPtr->focusedSel = st_null; 748 749 return TRUE; 750 } 751 752 /* draw today boundary box for specified rectangle */ 753 static void MONTHCAL_Circle(const MONTHCAL_INFO *infoPtr, HDC hdc, const RECT *r) 754 { 755 HPEN old_pen = SelectObject(hdc, infoPtr->pens[PenRed]); 756 HBRUSH old_brush; 757 758 old_brush = SelectObject(hdc, GetStockObject(NULL_BRUSH)); 759 Rectangle(hdc, r->left, r->top, r->right, r->bottom); 760 761 SelectObject(hdc, old_brush); 762 SelectObject(hdc, old_pen); 763 } 764 765 /* Draw today day mark rectangle 766 * 767 * [I] hdc : context to draw in 768 * [I] date : day to mark with rectangle 769 * 770 */ 771 static void MONTHCAL_CircleDay(const MONTHCAL_INFO *infoPtr, HDC hdc, 772 const SYSTEMTIME *date) 773 { 774 RECT r; 775 776 MONTHCAL_GetDayRect(infoPtr, date, &r, -1); 777 MONTHCAL_Circle(infoPtr, hdc, &r); 778 } 779 780 static void MONTHCAL_DrawDay(const MONTHCAL_INFO *infoPtr, HDC hdc, const SYSTEMTIME *st, 781 int bold, const PAINTSTRUCT *ps) 782 { 783 static const WCHAR fmtW[] = { '%','d',0 }; 784 WCHAR buf[10]; 785 RECT r, r_temp; 786 COLORREF oldCol = 0; 787 COLORREF oldBk = 0; 788 INT old_bkmode, selection; 789 790 /* no need to check styles: when selection is not valid, it is set to zero. 791 1 < day < 31, so everything is OK */ 792 MONTHCAL_GetDayRect(infoPtr, st, &r, -1); 793 if(!IntersectRect(&r_temp, &(ps->rcPaint), &r)) return; 794 795 if ((MONTHCAL_CompareDate(st, &infoPtr->minSel) >= 0) && 796 (MONTHCAL_CompareDate(st, &infoPtr->maxSel) <= 0)) 797 { 798 TRACE("%d %d %d\n", st->wDay, infoPtr->minSel.wDay, infoPtr->maxSel.wDay); 799 TRACE("%s\n", wine_dbgstr_rect(&r)); 800 oldCol = SetTextColor(hdc, infoPtr->colors[MCSC_MONTHBK]); 801 oldBk = SetBkColor(hdc, infoPtr->colors[MCSC_TRAILINGTEXT]); 802 FillRect(hdc, &r, infoPtr->brushes[BrushTitle]); 803 804 selection = 1; 805 } 806 else 807 selection = 0; 808 809 SelectObject(hdc, bold ? infoPtr->hBoldFont : infoPtr->hFont); 810 811 old_bkmode = SetBkMode(hdc, TRANSPARENT); 812 wsprintfW(buf, fmtW, st->wDay); 813 DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE ); 814 SetBkMode(hdc, old_bkmode); 815 816 if (selection) 817 { 818 SetTextColor(hdc, oldCol); 819 SetBkColor(hdc, oldBk); 820 } 821 } 822 823 static void MONTHCAL_PaintButton(MONTHCAL_INFO *infoPtr, HDC hdc, enum nav_direction button) 824 { 825 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf); 826 RECT *r = button == DIRECTION_FORWARD ? &infoPtr->titlebtnnext : &infoPtr->titlebtnprev; 827 BOOL pressed = button == DIRECTION_FORWARD ? infoPtr->status & MC_NEXTPRESSED : 828 infoPtr->status & MC_PREVPRESSED; 829 if (theme) 830 { 831 static const int states[] = { 832 /* Prev button */ 833 ABS_LEFTNORMAL, ABS_LEFTPRESSED, ABS_LEFTDISABLED, 834 /* Next button */ 835 ABS_RIGHTNORMAL, ABS_RIGHTPRESSED, ABS_RIGHTDISABLED 836 }; 837 int stateNum = button == DIRECTION_FORWARD ? 3 : 0; 838 if (pressed) 839 stateNum += 1; 840 else 841 { 842 if (infoPtr->dwStyle & WS_DISABLED) stateNum += 2; 843 } 844 DrawThemeBackground (theme, hdc, SBP_ARROWBTN, states[stateNum], r, NULL); 845 } 846 else 847 { 848 int style = button == DIRECTION_FORWARD ? DFCS_SCROLLRIGHT : DFCS_SCROLLLEFT; 849 if (pressed) 850 style |= DFCS_PUSHED; 851 else 852 { 853 if (infoPtr->dwStyle & WS_DISABLED) style |= DFCS_INACTIVE; 854 } 855 856 DrawFrameControl(hdc, r, DFC_SCROLL, style); 857 } 858 } 859 860 /* paint a title with buttons and month/year string */ 861 static void MONTHCAL_PaintTitle(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx) 862 { 863 static const WCHAR mmmmW[] = {'M','M','M','M',0}; 864 static const WCHAR mmmW[] = {'M','M','M',0}; 865 static const WCHAR mmW[] = {'M','M',0}; 866 static const WCHAR fmtyearW[] = {'%','l','d',0}; 867 static const WCHAR fmtmmW[] = {'%','0','2','d',0}; 868 static const WCHAR fmtmW[] = {'%','d',0}; 869 RECT *title = &infoPtr->calendars[calIdx].title; 870 const SYSTEMTIME *st = &infoPtr->calendars[calIdx].month; 871 WCHAR monthW[80], strW[80], fmtW[80], yearW[6] /* valid year range is 1601-30827 */; 872 int yearoffset, monthoffset, shiftX; 873 SIZE sz; 874 875 /* fill header box */ 876 FillRect(hdc, title, infoPtr->brushes[BrushTitle]); 877 878 /* month/year string */ 879 SetBkColor(hdc, infoPtr->colors[MCSC_TITLEBK]); 880 SetTextColor(hdc, infoPtr->colors[MCSC_TITLETEXT]); 881 SelectObject(hdc, infoPtr->hBoldFont); 882 883 /* draw formatted date string */ 884 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_YEARMONTH, st, NULL, strW, countof(strW)); 885 DrawTextW(hdc, strW, strlenW(strW), title, DT_CENTER | DT_VCENTER | DT_SINGLELINE); 886 887 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SYEARMONTH, fmtW, countof(fmtW)); 888 wsprintfW(yearW, fmtyearW, st->wYear); 889 890 /* month is trickier as it's possible to have different format pictures, we'll 891 test for M, MM, MMM, and MMMM */ 892 if (strstrW(fmtW, mmmmW)) 893 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+st->wMonth-1, monthW, countof(monthW)); 894 else if (strstrW(fmtW, mmmW)) 895 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVMONTHNAME1+st->wMonth-1, monthW, countof(monthW)); 896 else if (strstrW(fmtW, mmW)) 897 wsprintfW(monthW, fmtmmW, st->wMonth); 898 else 899 wsprintfW(monthW, fmtmW, st->wMonth); 900 901 /* update hit boxes */ 902 yearoffset = 0; 903 while (strW[yearoffset]) 904 { 905 if (!strncmpW(&strW[yearoffset], yearW, strlenW(yearW))) 906 break; 907 yearoffset++; 908 } 909 910 monthoffset = 0; 911 while (strW[monthoffset]) 912 { 913 if (!strncmpW(&strW[monthoffset], monthW, strlenW(monthW))) 914 break; 915 monthoffset++; 916 } 917 918 /* for left limits use offsets */ 919 sz.cx = 0; 920 if (yearoffset) 921 GetTextExtentPoint32W(hdc, strW, yearoffset, &sz); 922 infoPtr->calendars[calIdx].titleyear.left = sz.cx; 923 924 sz.cx = 0; 925 if (monthoffset) 926 GetTextExtentPoint32W(hdc, strW, monthoffset, &sz); 927 infoPtr->calendars[calIdx].titlemonth.left = sz.cx; 928 929 /* for right limits use actual string parts lengths */ 930 GetTextExtentPoint32W(hdc, &strW[yearoffset], strlenW(yearW), &sz); 931 infoPtr->calendars[calIdx].titleyear.right = infoPtr->calendars[calIdx].titleyear.left + sz.cx; 932 933 GetTextExtentPoint32W(hdc, monthW, strlenW(monthW), &sz); 934 infoPtr->calendars[calIdx].titlemonth.right = infoPtr->calendars[calIdx].titlemonth.left + sz.cx; 935 936 /* Finally translate rectangles to match center aligned string, 937 hit rectangles are relative to title rectangle before translation. */ 938 GetTextExtentPoint32W(hdc, strW, strlenW(strW), &sz); 939 shiftX = (title->right - title->left - sz.cx) / 2 + title->left; 940 OffsetRect(&infoPtr->calendars[calIdx].titleyear, shiftX, 0); 941 OffsetRect(&infoPtr->calendars[calIdx].titlemonth, shiftX, 0); 942 } 943 944 static void MONTHCAL_PaintWeeknumbers(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx) 945 { 946 const SYSTEMTIME *date = &infoPtr->calendars[calIdx].month; 947 static const WCHAR fmt_weekW[] = { '%','d',0 }; 948 INT mindays, weeknum, weeknum1, startofprescal; 949 INT i, prev_month; 950 SYSTEMTIME st; 951 WCHAR buf[80]; 952 HPEN old_pen; 953 RECT r; 954 955 if (!(infoPtr->dwStyle & MCS_WEEKNUMBERS)) return; 956 957 MONTHCAL_GetMinDate(infoPtr, &st); 958 startofprescal = st.wDay; 959 st = *date; 960 961 prev_month = date->wMonth - 1; 962 if(prev_month == 0) prev_month = 12; 963 964 /* 965 Rules what week to call the first week of a new year: 966 LOCALE_IFIRSTWEEKOFYEAR == 0 (e.g US?): 967 The week containing Jan 1 is the first week of year 968 LOCALE_IFIRSTWEEKOFYEAR == 2 (e.g. Germany): 969 First week of year must contain 4 days of the new year 970 LOCALE_IFIRSTWEEKOFYEAR == 1 (what countries?) 971 The first week of the year must contain only days of the new year 972 */ 973 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, buf, countof(buf)); 974 weeknum = atoiW(buf); 975 switch (weeknum) 976 { 977 case 1: mindays = 6; 978 break; 979 case 2: mindays = 3; 980 break; 981 case 0: mindays = 0; 982 break; 983 default: 984 WARN("Unknown LOCALE_IFIRSTWEEKOFYEAR value %d, defaulting to 0\n", weeknum); 985 mindays = 0; 986 } 987 988 if (date->wMonth == 1) 989 { 990 /* calculate all those exceptions for January */ 991 st.wDay = st.wMonth = 1; 992 weeknum1 = MONTHCAL_CalculateDayOfWeek(&st, FALSE); 993 if ((infoPtr->firstDay - weeknum1) % 7 > mindays) 994 weeknum = 1; 995 else 996 { 997 weeknum = 0; 998 for(i = 0; i < 11; i++) 999 weeknum += MONTHCAL_MonthLength(i+1, date->wYear - 1); 1000 1001 weeknum += startofprescal + 7; 1002 weeknum /= 7; 1003 st.wYear -= 1; 1004 weeknum1 = MONTHCAL_CalculateDayOfWeek(&st, FALSE); 1005 if ((infoPtr->firstDay - weeknum1) % 7 > mindays) weeknum++; 1006 } 1007 } 1008 else 1009 { 1010 weeknum = 0; 1011 for(i = 0; i < prev_month - 1; i++) 1012 weeknum += MONTHCAL_MonthLength(i+1, date->wYear); 1013 1014 weeknum += startofprescal + 7; 1015 weeknum /= 7; 1016 st.wDay = st.wMonth = 1; 1017 weeknum1 = MONTHCAL_CalculateDayOfWeek(&st, FALSE); 1018 if ((infoPtr->firstDay - weeknum1) % 7 > mindays) weeknum++; 1019 } 1020 1021 r = infoPtr->calendars[calIdx].weeknums; 1022 1023 /* erase whole week numbers area */ 1024 FillRect(hdc, &r, infoPtr->brushes[BrushMonth]); 1025 SetTextColor(hdc, infoPtr->colors[MCSC_TITLEBK]); 1026 1027 /* reduce rectangle to one week number */ 1028 r.bottom = r.top + infoPtr->height_increment; 1029 1030 for(i = 0; i < 6; i++) { 1031 if((i == 0) && (weeknum > 50)) 1032 { 1033 wsprintfW(buf, fmt_weekW, weeknum); 1034 weeknum = 0; 1035 } 1036 else if((i == 5) && (weeknum > 47)) 1037 { 1038 wsprintfW(buf, fmt_weekW, 1); 1039 } 1040 else 1041 wsprintfW(buf, fmt_weekW, weeknum + i); 1042 1043 DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE); 1044 OffsetRect(&r, 0, infoPtr->height_increment); 1045 } 1046 1047 /* line separator for week numbers column */ 1048 old_pen = SelectObject(hdc, infoPtr->pens[PenText]); 1049 MoveToEx(hdc, infoPtr->calendars[calIdx].weeknums.right, infoPtr->calendars[calIdx].weeknums.top + 3 , NULL); 1050 LineTo(hdc, infoPtr->calendars[calIdx].weeknums.right, infoPtr->calendars[calIdx].weeknums.bottom); 1051 SelectObject(hdc, old_pen); 1052 } 1053 1054 /* bottom today date */ 1055 static void MONTHCAL_PaintTodayTitle(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps) 1056 { 1057 static const WCHAR fmt_todayW[] = { '%','s',' ','%','s',0 }; 1058 WCHAR buf_todayW[30], buf_dateW[20], buf[80]; 1059 RECT text_rect, box_rect; 1060 HFONT old_font; 1061 INT col; 1062 1063 if(infoPtr->dwStyle & MCS_NOTODAY) return; 1064 1065 LoadStringW(COMCTL32_hModule, IDM_TODAY, buf_todayW, countof(buf_todayW)); 1066 col = infoPtr->dwStyle & MCS_NOTODAYCIRCLE ? 0 : 1; 1067 if (infoPtr->dwStyle & MCS_WEEKNUMBERS) col--; 1068 /* label is located below first calendar last row */ 1069 MONTHCAL_GetDayRectI(infoPtr, &text_rect, col, 6, infoPtr->dim.cx * infoPtr->dim.cy - infoPtr->dim.cx); 1070 box_rect = text_rect; 1071 1072 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &infoPtr->todaysDate, NULL, 1073 buf_dateW, countof(buf_dateW)); 1074 old_font = SelectObject(hdc, infoPtr->hBoldFont); 1075 SetTextColor(hdc, infoPtr->colors[MCSC_TEXT]); 1076 1077 wsprintfW(buf, fmt_todayW, buf_todayW, buf_dateW); 1078 DrawTextW(hdc, buf, -1, &text_rect, DT_CALCRECT | DT_LEFT | DT_VCENTER | DT_SINGLELINE); 1079 DrawTextW(hdc, buf, -1, &text_rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); 1080 1081 if(!(infoPtr->dwStyle & MCS_NOTODAYCIRCLE)) { 1082 OffsetRect(&box_rect, -infoPtr->width_increment, 0); 1083 MONTHCAL_Circle(infoPtr, hdc, &box_rect); 1084 } 1085 1086 SelectObject(hdc, old_font); 1087 } 1088 1089 /* today mark + focus */ 1090 static void MONTHCAL_PaintFocusAndCircle(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps) 1091 { 1092 /* circle today date if only it's in fully visible month */ 1093 if (!(infoPtr->dwStyle & MCS_NOTODAYCIRCLE)) 1094 { 1095 INT i; 1096 1097 for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++) 1098 if (!MONTHCAL_CompareMonths(&infoPtr->todaysDate, &infoPtr->calendars[i].month)) 1099 { 1100 MONTHCAL_CircleDay(infoPtr, hdc, &infoPtr->todaysDate); 1101 break; 1102 } 1103 } 1104 1105 if (!MONTHCAL_IsDateEqual(&infoPtr->focusedSel, &st_null)) 1106 { 1107 RECT r; 1108 MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1); 1109 DrawFocusRect(hdc, &r); 1110 } 1111 } 1112 1113 /* months before first calendar month and after last calendar month */ 1114 static void MONTHCAL_PaintLeadTrailMonths(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps) 1115 { 1116 INT mask, index; 1117 UINT length; 1118 SYSTEMTIME st_max, st; 1119 1120 if (infoPtr->dwStyle & MCS_NOTRAILINGDATES) return; 1121 1122 SetTextColor(hdc, infoPtr->colors[MCSC_TRAILINGTEXT]); 1123 1124 /* draw prev month */ 1125 MONTHCAL_GetMinDate(infoPtr, &st); 1126 mask = 1 << (st.wDay-1); 1127 /* December and January both 31 days long, so no worries if wrapped */ 1128 length = MONTHCAL_MonthLength(infoPtr->calendars[0].month.wMonth - 1, 1129 infoPtr->calendars[0].month.wYear); 1130 index = 0; 1131 while(st.wDay <= length) 1132 { 1133 MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[index] & mask, ps); 1134 mask <<= 1; 1135 st.wDay++; 1136 } 1137 1138 /* draw next month */ 1139 st = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month; 1140 st.wDay = 1; 1141 MONTHCAL_GetNextMonth(&st); 1142 MONTHCAL_GetMaxDate(infoPtr, &st_max); 1143 mask = 1; 1144 index = MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)-1; 1145 while(st.wDay <= st_max.wDay) 1146 { 1147 MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[index] & mask, ps); 1148 mask <<= 1; 1149 st.wDay++; 1150 } 1151 } 1152 1153 static int get_localized_dayname(const MONTHCAL_INFO *infoPtr, unsigned int day, WCHAR *buff, unsigned int count) 1154 { 1155 LCTYPE lctype; 1156 1157 if (infoPtr->dwStyle & MCS_SHORTDAYSOFWEEK) 1158 lctype = LOCALE_SSHORTESTDAYNAME1 + day; 1159 else 1160 lctype = LOCALE_SABBREVDAYNAME1 + day; 1161 1162 return GetLocaleInfoW(LOCALE_USER_DEFAULT, lctype, buff, count); 1163 } 1164 1165 /* paint a calendar area */ 1166 static void MONTHCAL_PaintCalendar(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx) 1167 { 1168 const SYSTEMTIME *date = &infoPtr->calendars[calIdx].month; 1169 INT i, j; 1170 UINT length; 1171 RECT r, fill_bk_rect; 1172 SYSTEMTIME st; 1173 WCHAR buf[80]; 1174 HPEN old_pen; 1175 int mask; 1176 1177 /* fill whole days area - from week days area to today note rectangle */ 1178 fill_bk_rect = infoPtr->calendars[calIdx].wdays; 1179 fill_bk_rect.bottom = infoPtr->calendars[calIdx].days.bottom + 1180 (infoPtr->todayrect.bottom - infoPtr->todayrect.top); 1181 1182 FillRect(hdc, &fill_bk_rect, infoPtr->brushes[BrushMonth]); 1183 1184 /* draw line under day abbreviations */ 1185 old_pen = SelectObject(hdc, infoPtr->pens[PenText]); 1186 MoveToEx(hdc, infoPtr->calendars[calIdx].days.left + 3, 1187 infoPtr->calendars[calIdx].title.bottom + infoPtr->textHeight + 1, NULL); 1188 LineTo(hdc, infoPtr->calendars[calIdx].days.right - 3, 1189 infoPtr->calendars[calIdx].title.bottom + infoPtr->textHeight + 1); 1190 SelectObject(hdc, old_pen); 1191 1192 infoPtr->calendars[calIdx].wdays.left = infoPtr->calendars[calIdx].days.left = 1193 infoPtr->calendars[calIdx].weeknums.right; 1194 1195 /* draw day abbreviations */ 1196 SelectObject(hdc, infoPtr->hFont); 1197 SetBkColor(hdc, infoPtr->colors[MCSC_MONTHBK]); 1198 SetTextColor(hdc, infoPtr->colors[MCSC_TITLEBK]); 1199 /* rectangle to draw a single day abbreviation within */ 1200 r = infoPtr->calendars[calIdx].wdays; 1201 r.right = r.left + infoPtr->width_increment; 1202 1203 i = infoPtr->firstDay; 1204 for(j = 0; j < 7; j++) { 1205 get_localized_dayname(infoPtr, (i + j + 6) % 7, buf, countof(buf)); 1206 DrawTextW(hdc, buf, strlenW(buf), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE); 1207 OffsetRect(&r, infoPtr->width_increment, 0); 1208 } 1209 1210 /* draw current month */ 1211 SetTextColor(hdc, infoPtr->colors[MCSC_TEXT]); 1212 st = *date; 1213 st.wDay = 1; 1214 mask = 1; 1215 length = MONTHCAL_MonthLength(date->wMonth, date->wYear); 1216 while(st.wDay <= length) 1217 { 1218 MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[calIdx+1] & mask, ps); 1219 mask <<= 1; 1220 st.wDay++; 1221 } 1222 } 1223 1224 static void MONTHCAL_Refresh(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps) 1225 { 1226 COLORREF old_text_clr, old_bk_clr; 1227 HFONT old_font; 1228 INT i; 1229 1230 old_text_clr = SetTextColor(hdc, comctl32_color.clrWindowText); 1231 old_bk_clr = GetBkColor(hdc); 1232 old_font = GetCurrentObject(hdc, OBJ_FONT); 1233 1234 for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++) 1235 { 1236 RECT *title = &infoPtr->calendars[i].title; 1237 RECT r; 1238 1239 /* draw title, redraw all its elements */ 1240 if (IntersectRect(&r, &(ps->rcPaint), title)) 1241 MONTHCAL_PaintTitle(infoPtr, hdc, ps, i); 1242 1243 /* draw calendar area */ 1244 UnionRect(&r, &infoPtr->calendars[i].wdays, &infoPtr->todayrect); 1245 if (IntersectRect(&r, &(ps->rcPaint), &r)) 1246 MONTHCAL_PaintCalendar(infoPtr, hdc, ps, i); 1247 1248 /* week numbers */ 1249 MONTHCAL_PaintWeeknumbers(infoPtr, hdc, ps, i); 1250 } 1251 1252 /* partially visible months */ 1253 MONTHCAL_PaintLeadTrailMonths(infoPtr, hdc, ps); 1254 1255 /* focus and today rectangle */ 1256 MONTHCAL_PaintFocusAndCircle(infoPtr, hdc, ps); 1257 1258 /* today at the bottom left */ 1259 MONTHCAL_PaintTodayTitle(infoPtr, hdc, ps); 1260 1261 /* navigation buttons */ 1262 MONTHCAL_PaintButton(infoPtr, hdc, DIRECTION_BACKWARD); 1263 MONTHCAL_PaintButton(infoPtr, hdc, DIRECTION_FORWARD); 1264 1265 /* restore context */ 1266 SetBkColor(hdc, old_bk_clr); 1267 SelectObject(hdc, old_font); 1268 SetTextColor(hdc, old_text_clr); 1269 } 1270 1271 static LRESULT 1272 MONTHCAL_GetMinReqRect(const MONTHCAL_INFO *infoPtr, RECT *rect) 1273 { 1274 TRACE("rect %p\n", rect); 1275 1276 if(!rect) return FALSE; 1277 1278 *rect = infoPtr->calendars[0].title; 1279 rect->bottom = infoPtr->calendars[0].days.bottom + infoPtr->todayrect.bottom - 1280 infoPtr->todayrect.top; 1281 1282 AdjustWindowRect(rect, infoPtr->dwStyle, FALSE); 1283 1284 /* minimal rectangle is zero based */ 1285 OffsetRect(rect, -rect->left, -rect->top); 1286 1287 TRACE("%s\n", wine_dbgstr_rect(rect)); 1288 1289 return TRUE; 1290 } 1291 1292 static COLORREF 1293 MONTHCAL_GetColor(const MONTHCAL_INFO *infoPtr, UINT index) 1294 { 1295 TRACE("%p, %d\n", infoPtr, index); 1296 1297 if (index > MCSC_TRAILINGTEXT) return -1; 1298 return infoPtr->colors[index]; 1299 } 1300 1301 static LRESULT 1302 MONTHCAL_SetColor(MONTHCAL_INFO *infoPtr, UINT index, COLORREF color) 1303 { 1304 enum CachedBrush type; 1305 COLORREF prev; 1306 1307 TRACE("%p, %d: color %08x\n", infoPtr, index, color); 1308 1309 if (index > MCSC_TRAILINGTEXT) return -1; 1310 1311 prev = infoPtr->colors[index]; 1312 infoPtr->colors[index] = color; 1313 1314 /* update cached brush */ 1315 switch (index) 1316 { 1317 case MCSC_BACKGROUND: 1318 type = BrushBackground; 1319 break; 1320 case MCSC_TITLEBK: 1321 type = BrushTitle; 1322 break; 1323 case MCSC_MONTHBK: 1324 type = BrushMonth; 1325 break; 1326 default: 1327 type = BrushLast; 1328 } 1329 1330 if (type != BrushLast) 1331 { 1332 DeleteObject(infoPtr->brushes[type]); 1333 infoPtr->brushes[type] = CreateSolidBrush(color); 1334 } 1335 1336 /* update cached pen */ 1337 if (index == MCSC_TEXT) 1338 { 1339 DeleteObject(infoPtr->pens[PenText]); 1340 infoPtr->pens[PenText] = CreatePen(PS_SOLID, 1, infoPtr->colors[index]); 1341 } 1342 1343 InvalidateRect(infoPtr->hwndSelf, NULL, index == MCSC_BACKGROUND); 1344 return prev; 1345 } 1346 1347 static LRESULT 1348 MONTHCAL_GetMonthDelta(const MONTHCAL_INFO *infoPtr) 1349 { 1350 TRACE("\n"); 1351 1352 if(infoPtr->delta) 1353 return infoPtr->delta; 1354 1355 return MONTHCAL_GetMonthRange(infoPtr, GMR_VISIBLE, NULL); 1356 } 1357 1358 1359 static LRESULT 1360 MONTHCAL_SetMonthDelta(MONTHCAL_INFO *infoPtr, INT delta) 1361 { 1362 INT prev = infoPtr->delta; 1363 1364 TRACE("delta %d\n", delta); 1365 1366 infoPtr->delta = delta; 1367 return prev; 1368 } 1369 1370 1371 static inline LRESULT 1372 MONTHCAL_GetFirstDayOfWeek(const MONTHCAL_INFO *infoPtr) 1373 { 1374 int day; 1375 1376 /* convert from SYSTEMTIME to locale format */ 1377 day = (infoPtr->firstDay >= 0) ? (infoPtr->firstDay+6)%7 : infoPtr->firstDay; 1378 1379 return MAKELONG(day, infoPtr->firstDaySet); 1380 } 1381 1382 1383 /* Sets the first day of the week that will appear in the control 1384 * 1385 * 1386 * PARAMETERS: 1387 * [I] infoPtr : valid pointer to control data 1388 * [I] day : day number to set as new first day (0 == Monday,...,6 == Sunday) 1389 * 1390 * 1391 * RETURN VALUE: 1392 * Low word contains previous first day, 1393 * high word indicates was first day forced with this message before or is 1394 * locale defined (TRUE - was forced, FALSE - wasn't). 1395 * 1396 * FIXME: this needs to be implemented properly in MONTHCAL_Refresh() 1397 * FIXME: we need more error checking here 1398 */ 1399 static LRESULT 1400 MONTHCAL_SetFirstDayOfWeek(MONTHCAL_INFO *infoPtr, INT day) 1401 { 1402 LRESULT prev = MONTHCAL_GetFirstDayOfWeek(infoPtr); 1403 int new_day; 1404 1405 TRACE("%d\n", day); 1406 1407 if(day == -1) 1408 { 1409 WCHAR buf[80]; 1410 1411 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, buf, countof(buf)); 1412 TRACE("%s %d\n", debugstr_w(buf), strlenW(buf)); 1413 1414 new_day = atoiW(buf); 1415 1416 infoPtr->firstDaySet = FALSE; 1417 } 1418 else if(day >= 7) 1419 { 1420 new_day = 6; /* max first day allowed */ 1421 infoPtr->firstDaySet = TRUE; 1422 } 1423 else 1424 { 1425 /* Native behaviour for that case is broken: invalid date number >31 1426 got displayed at (0,0) position, current month starts always from 1427 (1,0) position. Should be implemented here as well only if there's 1428 nothing else to do. */ 1429 if (day < -1) 1430 FIXME("No bug compatibility for day=%d\n", day); 1431 1432 new_day = day; 1433 infoPtr->firstDaySet = TRUE; 1434 } 1435 1436 /* convert from locale to SYSTEMTIME format */ 1437 infoPtr->firstDay = (new_day >= 0) ? (++new_day) % 7 : new_day; 1438 1439 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE); 1440 1441 return prev; 1442 } 1443 1444 static LRESULT 1445 MONTHCAL_GetMaxTodayWidth(const MONTHCAL_INFO *infoPtr) 1446 { 1447 return(infoPtr->todayrect.right - infoPtr->todayrect.left); 1448 } 1449 1450 static LRESULT 1451 MONTHCAL_SetRange(MONTHCAL_INFO *infoPtr, SHORT limits, SYSTEMTIME *range) 1452 { 1453 FILETIME ft_min, ft_max; 1454 1455 TRACE("%x %p\n", limits, range); 1456 1457 if ((limits & GDTR_MIN && !MONTHCAL_ValidateDate(&range[0])) || 1458 (limits & GDTR_MAX && !MONTHCAL_ValidateDate(&range[1]))) 1459 return FALSE; 1460 1461 infoPtr->rangeValid = 0; 1462 infoPtr->minDate = infoPtr->maxDate = st_null; 1463 1464 if (limits & GDTR_MIN) 1465 { 1466 if (!MONTHCAL_ValidateTime(&range[0])) 1467 MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]); 1468 1469 infoPtr->minDate = range[0]; 1470 infoPtr->rangeValid |= GDTR_MIN; 1471 } 1472 if (limits & GDTR_MAX) 1473 { 1474 if (!MONTHCAL_ValidateTime(&range[1])) 1475 MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]); 1476 1477 infoPtr->maxDate = range[1]; 1478 infoPtr->rangeValid |= GDTR_MAX; 1479 } 1480 1481 /* Only one limit set - we are done */ 1482 if ((infoPtr->rangeValid & (GDTR_MIN | GDTR_MAX)) != (GDTR_MIN | GDTR_MAX)) 1483 return TRUE; 1484 1485 SystemTimeToFileTime(&infoPtr->maxDate, &ft_max); 1486 SystemTimeToFileTime(&infoPtr->minDate, &ft_min); 1487 1488 if (CompareFileTime(&ft_min, &ft_max) >= 0) 1489 { 1490 if ((limits & (GDTR_MIN | GDTR_MAX)) == (GDTR_MIN | GDTR_MAX)) 1491 { 1492 /* Native swaps limits only when both limits are being set. */ 1493 SYSTEMTIME st_tmp = infoPtr->minDate; 1494 infoPtr->minDate = infoPtr->maxDate; 1495 infoPtr->maxDate = st_tmp; 1496 } 1497 else 1498 { 1499 /* reset the other limit */ 1500 if (limits & GDTR_MIN) infoPtr->maxDate = st_null; 1501 if (limits & GDTR_MAX) infoPtr->minDate = st_null; 1502 infoPtr->rangeValid &= limits & GDTR_MIN ? ~GDTR_MAX : ~GDTR_MIN; 1503 } 1504 } 1505 1506 return TRUE; 1507 } 1508 1509 1510 static LRESULT 1511 MONTHCAL_GetRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range) 1512 { 1513 TRACE("%p\n", range); 1514 1515 if (!range) return 0; 1516 1517 range[1] = infoPtr->maxDate; 1518 range[0] = infoPtr->minDate; 1519 1520 return infoPtr->rangeValid; 1521 } 1522 1523 1524 static LRESULT 1525 MONTHCAL_SetDayState(const MONTHCAL_INFO *infoPtr, INT months, MONTHDAYSTATE *states) 1526 { 1527 TRACE("%p %d %p\n", infoPtr, months, states); 1528 1529 if (!(infoPtr->dwStyle & MCS_DAYSTATE)) return 0; 1530 if (months != MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)) return 0; 1531 1532 memcpy(infoPtr->monthdayState, states, months*sizeof(MONTHDAYSTATE)); 1533 1534 return 1; 1535 } 1536 1537 static LRESULT 1538 MONTHCAL_GetCurSel(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel) 1539 { 1540 TRACE("%p\n", curSel); 1541 if(!curSel) return FALSE; 1542 if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE; 1543 1544 *curSel = infoPtr->minSel; 1545 TRACE("%d/%d/%d\n", curSel->wYear, curSel->wMonth, curSel->wDay); 1546 return TRUE; 1547 } 1548 1549 static LRESULT 1550 MONTHCAL_SetCurSel(MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel) 1551 { 1552 SYSTEMTIME prev = infoPtr->minSel, selection; 1553 INT diff; 1554 WORD day; 1555 1556 TRACE("%p\n", curSel); 1557 if(!curSel) return FALSE; 1558 if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE; 1559 1560 if(!MONTHCAL_ValidateDate(curSel)) return FALSE; 1561 /* exit earlier if selection equals current */ 1562 if (MONTHCAL_IsDateEqual(&infoPtr->minSel, curSel)) return TRUE; 1563 1564 selection = *curSel; 1565 selection.wHour = selection.wMinute = selection.wSecond = selection.wMilliseconds = 0; 1566 MONTHCAL_CalculateDayOfWeek(&selection, TRUE); 1567 1568 if(!MONTHCAL_IsDateInValidRange(infoPtr, &selection, FALSE)) return FALSE; 1569 1570 /* scroll calendars only if we have to */ 1571 diff = MONTHCAL_MonthDiff(&infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month, curSel); 1572 if (diff <= 0) 1573 { 1574 diff = MONTHCAL_MonthDiff(&infoPtr->calendars[0].month, curSel); 1575 if (diff > 0) diff = 0; 1576 } 1577 1578 if (diff != 0) 1579 { 1580 INT i; 1581 1582 for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++) 1583 MONTHCAL_GetMonth(&infoPtr->calendars[i].month, diff); 1584 } 1585 1586 /* we need to store time part as it is */ 1587 selection = *curSel; 1588 MONTHCAL_CalculateDayOfWeek(&selection, TRUE); 1589 infoPtr->minSel = infoPtr->maxSel = selection; 1590 1591 /* if selection is still in current month, reduce rectangle */ 1592 day = prev.wDay; 1593 prev.wDay = curSel->wDay; 1594 if (MONTHCAL_IsDateEqual(&prev, curSel)) 1595 { 1596 RECT r_prev, r_new; 1597 1598 prev.wDay = day; 1599 MONTHCAL_GetDayRect(infoPtr, &prev, &r_prev, -1); 1600 MONTHCAL_GetDayRect(infoPtr, curSel, &r_new, -1); 1601 1602 InvalidateRect(infoPtr->hwndSelf, &r_prev, FALSE); 1603 InvalidateRect(infoPtr->hwndSelf, &r_new, FALSE); 1604 } 1605 else 1606 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE); 1607 1608 return TRUE; 1609 } 1610 1611 1612 static LRESULT 1613 MONTHCAL_GetMaxSelCount(const MONTHCAL_INFO *infoPtr) 1614 { 1615 return infoPtr->maxSelCount; 1616 } 1617 1618 1619 static LRESULT 1620 MONTHCAL_SetMaxSelCount(MONTHCAL_INFO *infoPtr, INT max) 1621 { 1622 TRACE("%d\n", max); 1623 1624 if(!(infoPtr->dwStyle & MCS_MULTISELECT)) return FALSE; 1625 if(max <= 0) return FALSE; 1626 1627 infoPtr->maxSelCount = max; 1628 1629 return TRUE; 1630 } 1631 1632 1633 static LRESULT 1634 MONTHCAL_GetSelRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range) 1635 { 1636 TRACE("%p\n", range); 1637 1638 if(!range) return FALSE; 1639 1640 if(infoPtr->dwStyle & MCS_MULTISELECT) 1641 { 1642 range[1] = infoPtr->maxSel; 1643 range[0] = infoPtr->minSel; 1644 TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay); 1645 return TRUE; 1646 } 1647 1648 return FALSE; 1649 } 1650 1651 1652 static LRESULT 1653 MONTHCAL_SetSelRange(MONTHCAL_INFO *infoPtr, SYSTEMTIME *range) 1654 { 1655 SYSTEMTIME old_range[2]; 1656 INT diff; 1657 1658 TRACE("%p\n", range); 1659 1660 if(!range || !(infoPtr->dwStyle & MCS_MULTISELECT)) return FALSE; 1661 1662 /* adjust timestamps */ 1663 if(!MONTHCAL_ValidateTime(&range[0])) MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]); 1664 if(!MONTHCAL_ValidateTime(&range[1])) MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]); 1665 1666 /* maximum range exceeded */ 1667 if(!MONTHCAL_IsSelRangeValid(infoPtr, &range[0], &range[1], NULL)) return FALSE; 1668 1669 old_range[0] = infoPtr->minSel; 1670 old_range[1] = infoPtr->maxSel; 1671 1672 /* swap if min > max */ 1673 if(MONTHCAL_CompareSystemTime(&range[0], &range[1]) <= 0) 1674 { 1675 infoPtr->minSel = range[0]; 1676 infoPtr->maxSel = range[1]; 1677 } 1678 else 1679 { 1680 infoPtr->minSel = range[1]; 1681 infoPtr->maxSel = range[0]; 1682 } 1683 1684 diff = MONTHCAL_MonthDiff(&infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month, &infoPtr->maxSel); 1685 if (diff < 0) 1686 { 1687 diff = MONTHCAL_MonthDiff(&infoPtr->calendars[0].month, &infoPtr->maxSel); 1688 if (diff > 0) diff = 0; 1689 } 1690 1691 if (diff != 0) 1692 { 1693 INT i; 1694 1695 for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++) 1696 MONTHCAL_GetMonth(&infoPtr->calendars[i].month, diff); 1697 } 1698 1699 /* update day of week */ 1700 MONTHCAL_CalculateDayOfWeek(&infoPtr->minSel, TRUE); 1701 MONTHCAL_CalculateDayOfWeek(&infoPtr->maxSel, TRUE); 1702 1703 /* redraw if bounds changed */ 1704 /* FIXME: no actual need to redraw everything */ 1705 if(!MONTHCAL_IsDateEqual(&old_range[0], &range[0]) || 1706 !MONTHCAL_IsDateEqual(&old_range[1], &range[1])) 1707 { 1708 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE); 1709 } 1710 1711 TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay); 1712 return TRUE; 1713 } 1714 1715 1716 static LRESULT 1717 MONTHCAL_GetToday(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *today) 1718 { 1719 TRACE("%p\n", today); 1720 1721 if(!today) return FALSE; 1722 *today = infoPtr->todaysDate; 1723 return TRUE; 1724 } 1725 1726 /* Internal helper for MCM_SETTODAY handler and auto update timer handler 1727 * 1728 * RETURN VALUE 1729 * 1730 * TRUE - today date changed 1731 * FALSE - today date isn't changed 1732 */ 1733 static BOOL 1734 MONTHCAL_UpdateToday(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *today) 1735 { 1736 RECT rect; 1737 1738 if (MONTHCAL_IsDateEqual(today, &infoPtr->todaysDate)) 1739 return FALSE; 1740 1741 /* Invalidate old and new today day rectangle, and today label. */ 1742 if (MONTHCAL_GetDayRect(infoPtr, &infoPtr->todaysDate, &rect, -1)) 1743 InvalidateRect(infoPtr->hwndSelf, &rect, FALSE); 1744 1745 if (MONTHCAL_GetDayRect(infoPtr, today, &rect, -1)) 1746 InvalidateRect(infoPtr->hwndSelf, &rect, FALSE); 1747 1748 infoPtr->todaysDate = *today; 1749 1750 InvalidateRect(infoPtr->hwndSelf, &infoPtr->todayrect, FALSE); 1751 return TRUE; 1752 } 1753 1754 /* MCM_SETTODAT handler */ 1755 static LRESULT 1756 MONTHCAL_SetToday(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *today) 1757 { 1758 TRACE("%p\n", today); 1759 1760 if (today) 1761 { 1762 /* remember if date was set successfully */ 1763 if (MONTHCAL_UpdateToday(infoPtr, today)) infoPtr->todaySet = TRUE; 1764 } 1765 1766 return 0; 1767 } 1768 1769 /* returns calendar index containing specified point, or -1 if it's background */ 1770 static INT MONTHCAL_GetCalendarFromPoint(const MONTHCAL_INFO *infoPtr, const POINT *pt) 1771 { 1772 RECT r; 1773 INT i; 1774 1775 for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++) 1776 { 1777 /* whole bounding rectangle allows some optimization to compute */ 1778 r.left = infoPtr->calendars[i].title.left; 1779 r.top = infoPtr->calendars[i].title.top; 1780 r.bottom = infoPtr->calendars[i].days.bottom; 1781 r.right = infoPtr->calendars[i].days.right; 1782 1783 if (PtInRect(&r, *pt)) return i; 1784 } 1785 1786 return -1; 1787 } 1788 1789 static inline UINT fill_hittest_info(const MCHITTESTINFO *src, MCHITTESTINFO *dest) 1790 { 1791 dest->uHit = src->uHit; 1792 dest->st = src->st; 1793 1794 if (dest->cbSize == sizeof(MCHITTESTINFO)) 1795 memcpy(&dest->rc, &src->rc, sizeof(MCHITTESTINFO) - MCHITTESTINFO_V1_SIZE); 1796 1797 return src->uHit; 1798 } 1799 1800 static LRESULT 1801 MONTHCAL_HitTest(const MONTHCAL_INFO *infoPtr, MCHITTESTINFO *lpht) 1802 { 1803 MCHITTESTINFO htinfo; 1804 SYSTEMTIME *ht_month; 1805 INT day, calIdx; 1806 1807 if(!lpht || lpht->cbSize < MCHITTESTINFO_V1_SIZE) return -1; 1808 1809 htinfo.st = st_null; 1810 1811 /* we should preserve passed fields if hit area doesn't need them */ 1812 if (lpht->cbSize == sizeof(MCHITTESTINFO)) 1813 memcpy(&htinfo.rc, &lpht->rc, sizeof(MCHITTESTINFO) - MCHITTESTINFO_V1_SIZE); 1814 1815 /* guess in what calendar we are */ 1816 calIdx = MONTHCAL_GetCalendarFromPoint(infoPtr, &lpht->pt); 1817 if (calIdx == -1) 1818 { 1819 if (PtInRect(&infoPtr->todayrect, lpht->pt)) 1820 { 1821 htinfo.uHit = MCHT_TODAYLINK; 1822 htinfo.rc = infoPtr->todayrect; 1823 } 1824 else 1825 /* outside of calendar area? What's left must be background :-) */ 1826 htinfo.uHit = MCHT_CALENDARBK; 1827 1828 return fill_hittest_info(&htinfo, lpht); 1829 } 1830 1831 /* are we in the header? */ 1832 if (PtInRect(&infoPtr->calendars[calIdx].title, lpht->pt)) { 1833 /* FIXME: buttons hittesting could be optimized cause maximum 1834 two calendars have buttons */ 1835 if (calIdx == 0 && PtInRect(&infoPtr->titlebtnprev, lpht->pt)) 1836 { 1837 htinfo.uHit = MCHT_TITLEBTNPREV; 1838 htinfo.rc = infoPtr->titlebtnprev; 1839 } 1840 else if (PtInRect(&infoPtr->titlebtnnext, lpht->pt)) 1841 { 1842 htinfo.uHit = MCHT_TITLEBTNNEXT; 1843 htinfo.rc = infoPtr->titlebtnnext; 1844 } 1845 else if (PtInRect(&infoPtr->calendars[calIdx].titlemonth, lpht->pt)) 1846 { 1847 htinfo.uHit = MCHT_TITLEMONTH; 1848 htinfo.rc = infoPtr->calendars[calIdx].titlemonth; 1849 htinfo.iOffset = calIdx; 1850 } 1851 else if (PtInRect(&infoPtr->calendars[calIdx].titleyear, lpht->pt)) 1852 { 1853 htinfo.uHit = MCHT_TITLEYEAR; 1854 htinfo.rc = infoPtr->calendars[calIdx].titleyear; 1855 htinfo.iOffset = calIdx; 1856 } 1857 else 1858 { 1859 htinfo.uHit = MCHT_TITLE; 1860 htinfo.rc = infoPtr->calendars[calIdx].title; 1861 htinfo.iOffset = calIdx; 1862 } 1863 1864 return fill_hittest_info(&htinfo, lpht); 1865 } 1866 1867 ht_month = &infoPtr->calendars[calIdx].month; 1868 /* days area (including week days and week numbers) */ 1869 day = MONTHCAL_GetDayFromPos(infoPtr, lpht->pt, calIdx); 1870 if (PtInRect(&infoPtr->calendars[calIdx].wdays, lpht->pt)) 1871 { 1872 htinfo.uHit = MCHT_CALENDARDAY; 1873 htinfo.iOffset = calIdx; 1874 htinfo.st.wYear = ht_month->wYear; 1875 htinfo.st.wMonth = (day < 1) ? ht_month->wMonth -1 : ht_month->wMonth; 1876 htinfo.st.wDay = (day < 1) ? 1877 MONTHCAL_MonthLength(ht_month->wMonth-1, ht_month->wYear) - day : day; 1878 1879 MONTHCAL_GetDayPos(infoPtr, &htinfo.st, &htinfo.iCol, &htinfo.iRow, calIdx); 1880 } 1881 else if(PtInRect(&infoPtr->calendars[calIdx].weeknums, lpht->pt)) 1882 { 1883 htinfo.uHit = MCHT_CALENDARWEEKNUM; 1884 htinfo.st.wYear = ht_month->wYear; 1885 htinfo.iOffset = calIdx; 1886 1887 if (day < 1) 1888 { 1889 htinfo.st.wMonth = ht_month->wMonth - 1; 1890 htinfo.st.wDay = MONTHCAL_MonthLength(ht_month->wMonth-1, ht_month->wYear) - day; 1891 } 1892 else if (day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear)) 1893 { 1894 htinfo.st.wMonth = ht_month->wMonth + 1; 1895 htinfo.st.wDay = day - MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear); 1896 } 1897 else 1898 { 1899 htinfo.st.wMonth = ht_month->wMonth; 1900 htinfo.st.wDay = day; 1901 } 1902 } 1903 else if(PtInRect(&infoPtr->calendars[calIdx].days, lpht->pt)) 1904 { 1905 htinfo.iOffset = calIdx; 1906 htinfo.st.wDay = ht_month->wDay; 1907 htinfo.st.wYear = ht_month->wYear; 1908 htinfo.st.wMonth = ht_month->wMonth; 1909 /* previous month only valid for first calendar */ 1910 if (day < 1 && calIdx == 0) 1911 { 1912 htinfo.uHit = MCHT_CALENDARDATEPREV; 1913 MONTHCAL_GetPrevMonth(&htinfo.st); 1914 htinfo.st.wDay = MONTHCAL_MonthLength(htinfo.st.wMonth, htinfo.st.wYear) + day; 1915 } 1916 /* next month only valid for last calendar */ 1917 else if (day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear) && 1918 calIdx == MONTHCAL_GetCalCount(infoPtr)-1) 1919 { 1920 htinfo.uHit = MCHT_CALENDARDATENEXT; 1921 MONTHCAL_GetNextMonth(&htinfo.st); 1922 htinfo.st.wDay = day - MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear); 1923 } 1924 /* multiple calendars case - blank areas for previous/next month */ 1925 else if (day < 1 || day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear)) 1926 { 1927 htinfo.uHit = MCHT_CALENDARBK; 1928 } 1929 else 1930 { 1931 htinfo.uHit = MCHT_CALENDARDATE; 1932 htinfo.st.wDay = day; 1933 } 1934 1935 MONTHCAL_GetDayPos(infoPtr, &htinfo.st, &htinfo.iCol, &htinfo.iRow, calIdx); 1936 MONTHCAL_GetDayRectI(infoPtr, &htinfo.rc, htinfo.iCol, htinfo.iRow, calIdx); 1937 /* always update day of week */ 1938 MONTHCAL_CalculateDayOfWeek(&htinfo.st, TRUE); 1939 } 1940 1941 return fill_hittest_info(&htinfo, lpht); 1942 } 1943 1944 /* MCN_GETDAYSTATE notification helper */ 1945 static void MONTHCAL_NotifyDayState(MONTHCAL_INFO *infoPtr) 1946 { 1947 MONTHDAYSTATE *state; 1948 NMDAYSTATE nmds; 1949 1950 if (!(infoPtr->dwStyle & MCS_DAYSTATE)) return; 1951 1952 nmds.nmhdr.hwndFrom = infoPtr->hwndSelf; 1953 nmds.nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID); 1954 nmds.nmhdr.code = MCN_GETDAYSTATE; 1955 nmds.cDayState = MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0); 1956 nmds.prgDayState = state = Alloc(nmds.cDayState * sizeof(MONTHDAYSTATE)); 1957 1958 MONTHCAL_GetMinDate(infoPtr, &nmds.stStart); 1959 nmds.stStart.wDay = 1; 1960 1961 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmds.nmhdr.idFrom, (LPARAM)&nmds); 1962 memcpy(infoPtr->monthdayState, nmds.prgDayState, 1963 MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)*sizeof(MONTHDAYSTATE)); 1964 1965 Free(state); 1966 } 1967 1968 /* no valid range check performed */ 1969 static void MONTHCAL_Scroll(MONTHCAL_INFO *infoPtr, INT delta, BOOL keep_selection) 1970 { 1971 INT i, selIdx = -1; 1972 1973 for(i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++) 1974 { 1975 /* save selection position to shift it later */ 1976 if (selIdx == -1 && MONTHCAL_CompareMonths(&infoPtr->minSel, &infoPtr->calendars[i].month) == 0) 1977 selIdx = i; 1978 1979 MONTHCAL_GetMonth(&infoPtr->calendars[i].month, delta); 1980 } 1981 1982 if (keep_selection) 1983 return; 1984 1985 /* selection is always shifted to first calendar */ 1986 if (infoPtr->dwStyle & MCS_MULTISELECT) 1987 { 1988 SYSTEMTIME range[2]; 1989 1990 MONTHCAL_GetSelRange(infoPtr, range); 1991 MONTHCAL_GetMonth(&range[0], delta - selIdx); 1992 MONTHCAL_GetMonth(&range[1], delta - selIdx); 1993 MONTHCAL_SetSelRange(infoPtr, range); 1994 } 1995 else 1996 { 1997 SYSTEMTIME st = infoPtr->minSel; 1998 1999 MONTHCAL_GetMonth(&st, delta - selIdx); 2000 MONTHCAL_SetCurSel(infoPtr, &st); 2001 } 2002 } 2003 2004 static void MONTHCAL_GoToMonth(MONTHCAL_INFO *infoPtr, enum nav_direction direction) 2005 { 2006 INT delta = infoPtr->delta ? infoPtr->delta : MONTHCAL_GetCalCount(infoPtr); 2007 BOOL keep_selection; 2008 SYSTEMTIME st; 2009 2010 TRACE("%s\n", direction == DIRECTION_BACKWARD ? "back" : "fwd"); 2011 2012 /* check if change allowed by range set */ 2013 if(direction == DIRECTION_BACKWARD) 2014 { 2015 st = infoPtr->calendars[0].month; 2016 MONTHCAL_GetMonth(&st, -delta); 2017 } 2018 else 2019 { 2020 st = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month; 2021 MONTHCAL_GetMonth(&st, delta); 2022 } 2023 2024 if(!MONTHCAL_IsDateInValidRange(infoPtr, &st, FALSE)) return; 2025 2026 keep_selection = infoPtr->dwStyle & MCS_NOSELCHANGEONNAV; 2027 MONTHCAL_Scroll(infoPtr, direction == DIRECTION_BACKWARD ? -delta : delta, keep_selection); 2028 MONTHCAL_NotifyDayState(infoPtr); 2029 if (!keep_selection) 2030 MONTHCAL_NotifySelectionChange(infoPtr); 2031 } 2032 2033 static LRESULT 2034 MONTHCAL_RButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam) 2035 { 2036 HMENU hMenu; 2037 POINT menupoint; 2038 WCHAR buf[32]; 2039 2040 hMenu = CreatePopupMenu(); 2041 LoadStringW(COMCTL32_hModule, IDM_GOTODAY, buf, countof(buf)); 2042 AppendMenuW(hMenu, MF_STRING|MF_ENABLED, 1, buf); 2043 menupoint.x = (short)LOWORD(lParam); 2044 menupoint.y = (short)HIWORD(lParam); 2045 ClientToScreen(infoPtr->hwndSelf, &menupoint); 2046 if( TrackPopupMenu(hMenu, TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, 2047 menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL)) 2048 { 2049 if (infoPtr->dwStyle & MCS_MULTISELECT) 2050 { 2051 SYSTEMTIME range[2]; 2052 2053 range[0] = range[1] = infoPtr->todaysDate; 2054 MONTHCAL_SetSelRange(infoPtr, range); 2055 } 2056 else 2057 MONTHCAL_SetCurSel(infoPtr, &infoPtr->todaysDate); 2058 2059 MONTHCAL_NotifySelectionChange(infoPtr); 2060 MONTHCAL_NotifySelect(infoPtr); 2061 } 2062 2063 return 0; 2064 } 2065 2066 /*** 2067 * DESCRIPTION: 2068 * Subclassed edit control windproc function 2069 * 2070 * PARAMETER(S): 2071 * [I] hwnd : the edit window handle 2072 * [I] uMsg : the message that is to be processed 2073 * [I] wParam : first message parameter 2074 * [I] lParam : second message parameter 2075 * 2076 */ 2077 static LRESULT CALLBACK EditWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 2078 { 2079 MONTHCAL_INFO *infoPtr = (MONTHCAL_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0); 2080 2081 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx)\n", 2082 hwnd, uMsg, wParam, lParam); 2083 2084 switch (uMsg) 2085 { 2086 case WM_GETDLGCODE: 2087 return DLGC_WANTARROWS | DLGC_WANTALLKEYS; 2088 2089 case WM_DESTROY: 2090 { 2091 WNDPROC editProc = infoPtr->EditWndProc; 2092 infoPtr->EditWndProc = NULL; 2093 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc); 2094 return CallWindowProcW(editProc, hwnd, uMsg, wParam, lParam); 2095 } 2096 2097 case WM_KILLFOCUS: 2098 break; 2099 2100 case WM_KEYDOWN: 2101 if ((VK_ESCAPE == (INT)wParam) || (VK_RETURN == (INT)wParam)) 2102 break; 2103 2104 default: 2105 return CallWindowProcW(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam); 2106 } 2107 2108 SendMessageW(infoPtr->hWndYearUpDown, WM_CLOSE, 0, 0); 2109 SendMessageW(hwnd, WM_CLOSE, 0, 0); 2110 return 0; 2111 } 2112 2113 /* creates updown control and edit box */ 2114 static void MONTHCAL_EditYear(MONTHCAL_INFO *infoPtr, INT calIdx) 2115 { 2116 RECT *rc = &infoPtr->calendars[calIdx].titleyear; 2117 RECT *title = &infoPtr->calendars[calIdx].title; 2118 2119 infoPtr->hWndYearEdit = 2120 CreateWindowExW(0, WC_EDITW, 0, WS_VISIBLE | WS_CHILD | ES_READONLY, 2121 rc->left + 3, (title->bottom + title->top - infoPtr->textHeight) / 2, 2122 rc->right - rc->left + 4, 2123 infoPtr->textHeight, infoPtr->hwndSelf, 2124 NULL, NULL, NULL); 2125 2126 SendMessageW(infoPtr->hWndYearEdit, WM_SETFONT, (WPARAM)infoPtr->hBoldFont, TRUE); 2127 2128 infoPtr->hWndYearUpDown = 2129 CreateWindowExW(0, UPDOWN_CLASSW, 0, 2130 WS_VISIBLE | WS_CHILD | UDS_SETBUDDYINT | UDS_NOTHOUSANDS | UDS_ARROWKEYS, 2131 rc->right + 7, (title->bottom + title->top - infoPtr->textHeight) / 2, 2132 18, infoPtr->textHeight, infoPtr->hwndSelf, 2133 NULL, NULL, NULL); 2134 2135 /* attach edit box */ 2136 SendMessageW(infoPtr->hWndYearUpDown, UDM_SETRANGE, 0, 2137 MAKELONG(max_allowed_date.wYear, min_allowed_date.wYear)); 2138 SendMessageW(infoPtr->hWndYearUpDown, UDM_SETBUDDY, (WPARAM)infoPtr->hWndYearEdit, 0); 2139 SendMessageW(infoPtr->hWndYearUpDown, UDM_SETPOS, 0, infoPtr->calendars[calIdx].month.wYear); 2140 2141 /* subclass edit box */ 2142 infoPtr->EditWndProc = (WNDPROC)SetWindowLongPtrW(infoPtr->hWndYearEdit, 2143 GWLP_WNDPROC, (DWORD_PTR)EditWndProc); 2144 2145 SetFocus(infoPtr->hWndYearEdit); 2146 } 2147 2148 static LRESULT 2149 MONTHCAL_LButtonDown(MONTHCAL_INFO *infoPtr, LPARAM lParam) 2150 { 2151 MCHITTESTINFO ht; 2152 DWORD hit; 2153 2154 /* Actually we don't need input focus for calendar, this is used to kill 2155 year updown and its buddy edit box */ 2156 if (IsWindow(infoPtr->hWndYearUpDown)) 2157 { 2158 SetFocus(infoPtr->hwndSelf); 2159 return 0; 2160 } 2161 2162 SetCapture(infoPtr->hwndSelf); 2163 2164 ht.cbSize = sizeof(MCHITTESTINFO); 2165 ht.pt.x = (short)LOWORD(lParam); 2166 ht.pt.y = (short)HIWORD(lParam); 2167 2168 hit = MONTHCAL_HitTest(infoPtr, &ht); 2169 2170 TRACE("%x at %s\n", hit, wine_dbgstr_point(&ht.pt)); 2171 2172 switch(hit) 2173 { 2174 case MCHT_TITLEBTNNEXT: 2175 MONTHCAL_GoToMonth(infoPtr, DIRECTION_FORWARD); 2176 infoPtr->status = MC_NEXTPRESSED; 2177 SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0); 2178 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE); 2179 return 0; 2180 2181 case MCHT_TITLEBTNPREV: 2182 MONTHCAL_GoToMonth(infoPtr, DIRECTION_BACKWARD); 2183 infoPtr->status = MC_PREVPRESSED; 2184 SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0); 2185 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE); 2186 return 0; 2187 2188 case MCHT_TITLEMONTH: 2189 { 2190 HMENU hMenu = CreatePopupMenu(); 2191 WCHAR buf[32]; 2192 POINT menupoint; 2193 INT i; 2194 2195 for (i = 0; i < 12; i++) 2196 { 2197 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+i, buf, countof(buf)); 2198 AppendMenuW(hMenu, MF_STRING|MF_ENABLED, i + 1, buf); 2199 } 2200 menupoint.x = ht.pt.x; 2201 menupoint.y = ht.pt.y; 2202 ClientToScreen(infoPtr->hwndSelf, &menupoint); 2203 i = TrackPopupMenu(hMenu,TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON | TPM_RETURNCMD, 2204 menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL); 2205 2206 if ((i > 0) && (i < 13) && infoPtr->calendars[ht.iOffset].month.wMonth != i) 2207 { 2208 INT delta = i - infoPtr->calendars[ht.iOffset].month.wMonth; 2209 SYSTEMTIME st; 2210 2211 /* check if change allowed by range set */ 2212 st = delta < 0 ? infoPtr->calendars[0].month : 2213 infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month; 2214 MONTHCAL_GetMonth(&st, delta); 2215 2216 if (MONTHCAL_IsDateInValidRange(infoPtr, &st, FALSE)) 2217 { 2218 MONTHCAL_Scroll(infoPtr, delta, FALSE); 2219 MONTHCAL_NotifyDayState(infoPtr); 2220 MONTHCAL_NotifySelectionChange(infoPtr); 2221 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE); 2222 } 2223 } 2224 return 0; 2225 } 2226 case MCHT_TITLEYEAR: 2227 { 2228 MONTHCAL_EditYear(infoPtr, ht.iOffset); 2229 return 0; 2230 } 2231 case MCHT_TODAYLINK: 2232 { 2233 if (infoPtr->dwStyle & MCS_MULTISELECT) 2234 { 2235 SYSTEMTIME range[2]; 2236 2237 range[0] = range[1] = infoPtr->todaysDate; 2238 MONTHCAL_SetSelRange(infoPtr, range); 2239 } 2240 else 2241 MONTHCAL_SetCurSel(infoPtr, &infoPtr->todaysDate); 2242 2243 MONTHCAL_NotifySelectionChange(infoPtr); 2244 MONTHCAL_NotifySelect(infoPtr); 2245 return 0; 2246 } 2247 case MCHT_CALENDARDATENEXT: 2248 case MCHT_CALENDARDATEPREV: 2249 case MCHT_CALENDARDATE: 2250 { 2251 SYSTEMTIME st[2]; 2252 2253 MONTHCAL_CopyDate(&ht.st, &infoPtr->firstSel); 2254 2255 st[0] = st[1] = ht.st; 2256 /* clear selection range */ 2257 MONTHCAL_SetSelRange(infoPtr, st); 2258 2259 infoPtr->status = MC_SEL_LBUTDOWN; 2260 MONTHCAL_SetDayFocus(infoPtr, &ht.st); 2261 return 0; 2262 } 2263 } 2264 2265 return 1; 2266 } 2267 2268 2269 static LRESULT 2270 MONTHCAL_LButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam) 2271 { 2272 NMHDR nmhdr; 2273 MCHITTESTINFO ht; 2274 DWORD hit; 2275 2276 TRACE("\n"); 2277 2278 if(infoPtr->status & (MC_PREVPRESSED | MC_NEXTPRESSED)) { 2279 RECT *r; 2280 2281 KillTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER); 2282 r = infoPtr->status & MC_PREVPRESSED ? &infoPtr->titlebtnprev : &infoPtr->titlebtnnext; 2283 infoPtr->status &= ~(MC_PREVPRESSED | MC_NEXTPRESSED); 2284 2285 InvalidateRect(infoPtr->hwndSelf, r, FALSE); 2286 } 2287 2288 ReleaseCapture(); 2289 2290 /* always send NM_RELEASEDCAPTURE notification */ 2291 nmhdr.hwndFrom = infoPtr->hwndSelf; 2292 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID); 2293 nmhdr.code = NM_RELEASEDCAPTURE; 2294 TRACE("Sent notification from %p to %p\n", infoPtr->hwndSelf, infoPtr->hwndNotify); 2295 2296 SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmhdr.idFrom, (LPARAM)&nmhdr); 2297 2298 if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0; 2299 2300 ht.cbSize = sizeof(MCHITTESTINFO); 2301 ht.pt.x = (short)LOWORD(lParam); 2302 ht.pt.y = (short)HIWORD(lParam); 2303 hit = MONTHCAL_HitTest(infoPtr, &ht); 2304 2305 infoPtr->status = MC_SEL_LBUTUP; 2306 MONTHCAL_SetDayFocus(infoPtr, NULL); 2307 2308 if((hit & MCHT_CALENDARDATE) == MCHT_CALENDARDATE) 2309 { 2310 SYSTEMTIME sel = infoPtr->minSel; 2311 2312 /* will be invalidated here */ 2313 MONTHCAL_SetCurSel(infoPtr, &ht.st); 2314 2315 /* send MCN_SELCHANGE only if new date selected */ 2316 if (!MONTHCAL_IsDateEqual(&sel, &ht.st)) 2317 MONTHCAL_NotifySelectionChange(infoPtr); 2318 2319 MONTHCAL_NotifySelect(infoPtr); 2320 } 2321 2322 return 0; 2323 } 2324 2325 2326 static LRESULT 2327 MONTHCAL_Timer(MONTHCAL_INFO *infoPtr, WPARAM id) 2328 { 2329 TRACE("%ld\n", id); 2330 2331 switch(id) { 2332 case MC_PREVNEXTMONTHTIMER: 2333 if(infoPtr->status & MC_NEXTPRESSED) MONTHCAL_GoToMonth(infoPtr, DIRECTION_FORWARD); 2334 if(infoPtr->status & MC_PREVPRESSED) MONTHCAL_GoToMonth(infoPtr, DIRECTION_BACKWARD); 2335 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE); 2336 break; 2337 case MC_TODAYUPDATETIMER: 2338 { 2339 SYSTEMTIME st; 2340 2341 if(infoPtr->todaySet) return 0; 2342 2343 GetLocalTime(&st); 2344 MONTHCAL_UpdateToday(infoPtr, &st); 2345 2346 /* notification sent anyway */ 2347 MONTHCAL_NotifySelectionChange(infoPtr); 2348 2349 return 0; 2350 } 2351 default: 2352 ERR("got unknown timer %ld\n", id); 2353 break; 2354 } 2355 2356 return 0; 2357 } 2358 2359 2360 static LRESULT 2361 MONTHCAL_MouseMove(MONTHCAL_INFO *infoPtr, LPARAM lParam) 2362 { 2363 MCHITTESTINFO ht; 2364 SYSTEMTIME st_ht; 2365 INT hit; 2366 RECT r; 2367 2368 if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0; 2369 2370 ht.cbSize = sizeof(MCHITTESTINFO); 2371 ht.pt.x = (short)LOWORD(lParam); 2372 ht.pt.y = (short)HIWORD(lParam); 2373 ht.iOffset = -1; 2374 2375 hit = MONTHCAL_HitTest(infoPtr, &ht); 2376 2377 /* not on the calendar date numbers? bail out */ 2378 TRACE("hit:%x\n",hit); 2379 if((hit & MCHT_CALENDARDATE) != MCHT_CALENDARDATE) 2380 { 2381 MONTHCAL_SetDayFocus(infoPtr, NULL); 2382 return 0; 2383 } 2384 2385 st_ht = ht.st; 2386 2387 /* if pointer is over focused day still there's nothing to do */ 2388 if(!MONTHCAL_SetDayFocus(infoPtr, &ht.st)) return 0; 2389 2390 MONTHCAL_GetDayRect(infoPtr, &ht.st, &r, ht.iOffset); 2391 2392 if(infoPtr->dwStyle & MCS_MULTISELECT) { 2393 SYSTEMTIME st[2]; 2394 2395 MONTHCAL_GetSelRange(infoPtr, st); 2396 2397 /* If we're still at the first selected date and range is empty, return. 2398 If range isn't empty we should change range to a single firstSel */ 2399 if(MONTHCAL_IsDateEqual(&infoPtr->firstSel, &st_ht) && 2400 MONTHCAL_IsDateEqual(&st[0], &st[1])) goto done; 2401 2402 MONTHCAL_IsSelRangeValid(infoPtr, &st_ht, &infoPtr->firstSel, &st_ht); 2403 2404 st[0] = infoPtr->firstSel; 2405 /* we should overwrite timestamp here */ 2406 MONTHCAL_CopyDate(&st_ht, &st[1]); 2407 2408 /* bounds will be swapped here if needed */ 2409 MONTHCAL_SetSelRange(infoPtr, st); 2410 2411 return 0; 2412 } 2413 2414 done: 2415 2416 /* FIXME: this should specify a rectangle containing only the days that changed 2417 using InvalidateRect */ 2418 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE); 2419 2420 return 0; 2421 } 2422 2423 2424 static LRESULT 2425 MONTHCAL_Paint(MONTHCAL_INFO *infoPtr, HDC hdc_paint) 2426 { 2427 HDC hdc; 2428 PAINTSTRUCT ps; 2429 2430 if (hdc_paint) 2431 { 2432 GetClientRect(infoPtr->hwndSelf, &ps.rcPaint); 2433 hdc = hdc_paint; 2434 } 2435 else 2436 hdc = BeginPaint(infoPtr->hwndSelf, &ps); 2437 2438 MONTHCAL_Refresh(infoPtr, hdc, &ps); 2439 if (!hdc_paint) EndPaint(infoPtr->hwndSelf, &ps); 2440 return 0; 2441 } 2442 2443 static LRESULT 2444 MONTHCAL_EraseBkgnd(const MONTHCAL_INFO *infoPtr, HDC hdc) 2445 { 2446 RECT rc; 2447 2448 if (!GetClipBox(hdc, &rc)) return FALSE; 2449 2450 FillRect(hdc, &rc, infoPtr->brushes[BrushBackground]); 2451 2452 return TRUE; 2453 } 2454 2455 static LRESULT 2456 MONTHCAL_PrintClient(MONTHCAL_INFO *infoPtr, HDC hdc, DWORD options) 2457 { 2458 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options); 2459 2460 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf)) 2461 return 0; 2462 2463 if (options & PRF_ERASEBKGND) 2464 MONTHCAL_EraseBkgnd(infoPtr, hdc); 2465 2466 if (options & PRF_CLIENT) 2467 MONTHCAL_Paint(infoPtr, hdc); 2468 2469 return 0; 2470 } 2471 2472 static LRESULT 2473 MONTHCAL_SetFocus(const MONTHCAL_INFO *infoPtr) 2474 { 2475 TRACE("\n"); 2476 2477 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE); 2478 2479 return 0; 2480 } 2481 2482 /* sets the size information */ 2483 static void MONTHCAL_UpdateSize(MONTHCAL_INFO *infoPtr) 2484 { 2485 static const WCHAR O0W[] = { '0','0',0 }; 2486 RECT *title=&infoPtr->calendars[0].title; 2487 RECT *prev=&infoPtr->titlebtnprev; 2488 RECT *next=&infoPtr->titlebtnnext; 2489 RECT *titlemonth=&infoPtr->calendars[0].titlemonth; 2490 RECT *titleyear=&infoPtr->calendars[0].titleyear; 2491 RECT *wdays=&infoPtr->calendars[0].wdays; 2492 RECT *weeknumrect=&infoPtr->calendars[0].weeknums; 2493 RECT *days=&infoPtr->calendars[0].days; 2494 RECT *todayrect=&infoPtr->todayrect; 2495 2496 INT xdiv, dx, dy, i, j, x, y, c_dx, c_dy; 2497 WCHAR buff[80]; 2498 TEXTMETRICW tm; 2499 INT day_width; 2500 RECT client; 2501 HFONT font; 2502 SIZE size; 2503 HDC hdc; 2504 2505 GetClientRect(infoPtr->hwndSelf, &client); 2506 2507 hdc = GetDC(infoPtr->hwndSelf); 2508 font = SelectObject(hdc, infoPtr->hFont); 2509 2510 /* get the height and width of each day's text */ 2511 GetTextMetricsW(hdc, &tm); 2512 infoPtr->textHeight = tm.tmHeight + tm.tmExternalLeading + tm.tmInternalLeading; 2513 2514 /* find widest day name for current locale and font */ 2515 day_width = 0; 2516 for (i = 0; i < 7; i++) 2517 { 2518 SIZE sz; 2519 2520 if (get_localized_dayname(infoPtr, i, buff, countof(buff))) 2521 { 2522 GetTextExtentPoint32W(hdc, buff, lstrlenW(buff), &sz); 2523 if (sz.cx > day_width) day_width = sz.cx; 2524 } 2525 else /* locale independent fallback on failure */ 2526 { 2527 static const WCHAR sunW[] = { 'S','u','n' }; 2528 GetTextExtentPoint32W(hdc, sunW, countof(sunW), &sz); 2529 day_width = sz.cx; 2530 break; 2531 } 2532 } 2533 2534 day_width += 2; 2535 2536 /* recalculate the height and width increments and offsets */ 2537 size.cx = 0; 2538 GetTextExtentPoint32W(hdc, O0W, 2, &size); 2539 2540 /* restore the originally selected font */ 2541 SelectObject(hdc, font); 2542 ReleaseDC(infoPtr->hwndSelf, hdc); 2543 2544 xdiv = (infoPtr->dwStyle & MCS_WEEKNUMBERS) ? 8 : 7; 2545 2546 infoPtr->width_increment = max(day_width, size.cx * 2 + 4); 2547 infoPtr->height_increment = infoPtr->textHeight; 2548 2549 /* calculate title area */ 2550 title->top = 0; 2551 title->bottom = 3 * infoPtr->height_increment / 2; 2552 title->left = 0; 2553 title->right = infoPtr->width_increment * xdiv; 2554 2555 /* set the dimensions of the next and previous buttons and center */ 2556 /* the month text vertically */ 2557 prev->top = next->top = title->top + 4; 2558 prev->bottom = next->bottom = title->bottom - 4; 2559 prev->left = title->left + 4; 2560 prev->right = prev->left + (title->bottom - title->top); 2561 next->right = title->right - 4; 2562 next->left = next->right - (title->bottom - title->top); 2563 2564 /* titlemonth->left and right change based upon the current month 2565 and are recalculated in refresh as the current month may change 2566 without the control being resized */ 2567 titlemonth->top = titleyear->top = title->top + (infoPtr->height_increment)/2; 2568 titlemonth->bottom = titleyear->bottom = title->bottom - (infoPtr->height_increment)/2; 2569 2570 /* week numbers */ 2571 weeknumrect->left = 0; 2572 weeknumrect->right = infoPtr->dwStyle & MCS_WEEKNUMBERS ? prev->right : 0; 2573 2574 /* days abbreviated names */ 2575 wdays->left = days->left = weeknumrect->right; 2576 wdays->right = days->right = wdays->left + 7 * infoPtr->width_increment; 2577 wdays->top = title->bottom; 2578 wdays->bottom = wdays->top + infoPtr->height_increment; 2579 2580 days->top = weeknumrect->top = wdays->bottom; 2581 days->bottom = weeknumrect->bottom = days->top + 6 * infoPtr->height_increment; 2582 2583 todayrect->left = 0; 2584 todayrect->right = title->right; 2585 todayrect->top = days->bottom; 2586 todayrect->bottom = days->bottom + infoPtr->height_increment; 2587 2588 /* compute calendar count, update all calendars */ 2589 x = (client.right + MC_CALENDAR_PADDING) / (title->right - title->left + MC_CALENDAR_PADDING); 2590 /* today label affects whole height */ 2591 if (infoPtr->dwStyle & MCS_NOTODAY) 2592 y = (client.bottom + MC_CALENDAR_PADDING) / (days->bottom - title->top + MC_CALENDAR_PADDING); 2593 else 2594 y = (client.bottom - todayrect->bottom + todayrect->top + MC_CALENDAR_PADDING) / 2595 (days->bottom - title->top + MC_CALENDAR_PADDING); 2596 2597 /* TODO: ensure that count is properly adjusted to fit 12 months constraint */ 2598 if (x == 0) x = 1; 2599 if (y == 0) y = 1; 2600 2601 if (x*y != MONTHCAL_GetCalCount(infoPtr)) 2602 { 2603 infoPtr->dim.cx = x; 2604 infoPtr->dim.cy = y; 2605 infoPtr->calendars = ReAlloc(infoPtr->calendars, MONTHCAL_GetCalCount(infoPtr)*sizeof(CALENDAR_INFO)); 2606 2607 infoPtr->monthdayState = ReAlloc(infoPtr->monthdayState, 2608 MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)*sizeof(MONTHDAYSTATE)); 2609 MONTHCAL_NotifyDayState(infoPtr); 2610 2611 /* update pointers that we'll need */ 2612 title = &infoPtr->calendars[0].title; 2613 wdays = &infoPtr->calendars[0].wdays; 2614 days = &infoPtr->calendars[0].days; 2615 } 2616 2617 for (i = 1; i < MONTHCAL_GetCalCount(infoPtr); i++) 2618 { 2619 /* set months */ 2620 infoPtr->calendars[i] = infoPtr->calendars[0]; 2621 MONTHCAL_GetMonth(&infoPtr->calendars[i].month, i); 2622 } 2623 2624 /* offset all rectangles to center in client area */ 2625 c_dx = (client.right - x * title->right - MC_CALENDAR_PADDING * (x-1)) / 2; 2626 c_dy = (client.bottom - y * todayrect->bottom - MC_CALENDAR_PADDING * (y-1)) / 2; 2627 2628 /* if calendar doesn't fit client area show it at left/top bounds */ 2629 if (title->left + c_dx < 0) c_dx = 0; 2630 if (title->top + c_dy < 0) c_dy = 0; 2631 2632 for (i = 0; i < y; i++) 2633 { 2634 for (j = 0; j < x; j++) 2635 { 2636 dx = j*(title->right - title->left + MC_CALENDAR_PADDING) + c_dx; 2637 dy = i*(days->bottom - title->top + MC_CALENDAR_PADDING) + c_dy; 2638 2639 OffsetRect(&infoPtr->calendars[i*x+j].title, dx, dy); 2640 OffsetRect(&infoPtr->calendars[i*x+j].titlemonth, dx, dy); 2641 OffsetRect(&infoPtr->calendars[i*x+j].titleyear, dx, dy); 2642 OffsetRect(&infoPtr->calendars[i*x+j].wdays, dx, dy); 2643 OffsetRect(&infoPtr->calendars[i*x+j].weeknums, dx, dy); 2644 OffsetRect(&infoPtr->calendars[i*x+j].days, dx, dy); 2645 } 2646 } 2647 2648 OffsetRect(prev, c_dx, c_dy); 2649 OffsetRect(next, (x-1)*(title->right - title->left + MC_CALENDAR_PADDING) + c_dx, c_dy); 2650 2651 i = infoPtr->dim.cx * infoPtr->dim.cy - infoPtr->dim.cx; 2652 todayrect->left = infoPtr->calendars[i].title.left; 2653 todayrect->right = infoPtr->calendars[i].title.right; 2654 todayrect->top = infoPtr->calendars[i].days.bottom; 2655 todayrect->bottom = infoPtr->calendars[i].days.bottom + infoPtr->height_increment; 2656 2657 TRACE("dx=%d dy=%d client[%s] title[%s] wdays[%s] days[%s] today[%s]\n", 2658 infoPtr->width_increment,infoPtr->height_increment, 2659 wine_dbgstr_rect(&client), 2660 wine_dbgstr_rect(title), 2661 wine_dbgstr_rect(wdays), 2662 wine_dbgstr_rect(days), 2663 wine_dbgstr_rect(todayrect)); 2664 } 2665 2666 static LRESULT MONTHCAL_Size(MONTHCAL_INFO *infoPtr, int Width, int Height) 2667 { 2668 TRACE("(width=%d, height=%d)\n", Width, Height); 2669 2670 MONTHCAL_UpdateSize(infoPtr); 2671 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE); 2672 2673 return 0; 2674 } 2675 2676 static LRESULT MONTHCAL_GetFont(const MONTHCAL_INFO *infoPtr) 2677 { 2678 return (LRESULT)infoPtr->hFont; 2679 } 2680 2681 static LRESULT MONTHCAL_SetFont(MONTHCAL_INFO *infoPtr, HFONT hFont, BOOL redraw) 2682 { 2683 HFONT hOldFont; 2684 LOGFONTW lf; 2685 2686 if (!hFont) return 0; 2687 2688 hOldFont = infoPtr->hFont; 2689 infoPtr->hFont = hFont; 2690 2691 GetObjectW(infoPtr->hFont, sizeof(lf), &lf); 2692 lf.lfWeight = FW_BOLD; 2693 infoPtr->hBoldFont = CreateFontIndirectW(&lf); 2694 2695 MONTHCAL_UpdateSize(infoPtr); 2696 2697 if (redraw) 2698 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE); 2699 2700 return (LRESULT)hOldFont; 2701 } 2702 2703 /* update theme after a WM_THEMECHANGED message */ 2704 static LRESULT theme_changed (const MONTHCAL_INFO* infoPtr) 2705 { 2706 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf); 2707 CloseThemeData (theme); 2708 OpenThemeData (infoPtr->hwndSelf, themeClass); 2709 return 0; 2710 } 2711 2712 static INT MONTHCAL_StyleChanged(MONTHCAL_INFO *infoPtr, WPARAM wStyleType, 2713 const STYLESTRUCT *lpss) 2714 { 2715 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n", 2716 wStyleType, lpss->styleOld, lpss->styleNew); 2717 2718 if (wStyleType != GWL_STYLE) return 0; 2719 2720 infoPtr->dwStyle = lpss->styleNew; 2721 2722 /* make room for week numbers */ 2723 if ((lpss->styleNew ^ lpss->styleOld) & (MCS_WEEKNUMBERS | MCS_SHORTDAYSOFWEEK)) 2724 MONTHCAL_UpdateSize(infoPtr); 2725 2726 return 0; 2727 } 2728 2729 static INT MONTHCAL_StyleChanging(MONTHCAL_INFO *infoPtr, WPARAM wStyleType, 2730 STYLESTRUCT *lpss) 2731 { 2732 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n", 2733 wStyleType, lpss->styleOld, lpss->styleNew); 2734 2735 /* block MCS_MULTISELECT change */ 2736 if ((lpss->styleNew ^ lpss->styleOld) & MCS_MULTISELECT) 2737 { 2738 if (lpss->styleOld & MCS_MULTISELECT) 2739 lpss->styleNew |= MCS_MULTISELECT; 2740 else 2741 lpss->styleNew &= ~MCS_MULTISELECT; 2742 } 2743 2744 /* block MCS_DAYSTATE change */ 2745 if ((lpss->styleNew ^ lpss->styleOld) & MCS_DAYSTATE) 2746 { 2747 if (lpss->styleOld & MCS_DAYSTATE) 2748 lpss->styleNew |= MCS_DAYSTATE; 2749 else 2750 lpss->styleNew &= ~MCS_DAYSTATE; 2751 } 2752 2753 return 0; 2754 } 2755 2756 /* FIXME: check whether dateMin/dateMax need to be adjusted. */ 2757 static LRESULT 2758 MONTHCAL_Create(HWND hwnd, LPCREATESTRUCTW lpcs) 2759 { 2760 MONTHCAL_INFO *infoPtr; 2761 2762 /* allocate memory for info structure */ 2763 infoPtr = Alloc(sizeof(MONTHCAL_INFO)); 2764 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr); 2765 2766 if (infoPtr == NULL) { 2767 ERR("could not allocate info memory!\n"); 2768 return 0; 2769 } 2770 2771 infoPtr->hwndSelf = hwnd; 2772 infoPtr->hwndNotify = lpcs->hwndParent; 2773 infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE); 2774 infoPtr->dim.cx = infoPtr->dim.cy = 1; 2775 infoPtr->calendars = Alloc(sizeof(CALENDAR_INFO)); 2776 if (!infoPtr->calendars) goto fail; 2777 infoPtr->monthdayState = Alloc(3*sizeof(MONTHDAYSTATE)); 2778 if (!infoPtr->monthdayState) goto fail; 2779 2780 /* initialize info structure */ 2781 /* FIXME: calculate systemtime ->> localtime(subtract timezoneinfo) */ 2782 2783 GetLocalTime(&infoPtr->todaysDate); 2784 MONTHCAL_SetFirstDayOfWeek(infoPtr, -1); 2785 2786 infoPtr->maxSelCount = (infoPtr->dwStyle & MCS_MULTISELECT) ? 7 : 1; 2787 2788 infoPtr->colors[MCSC_BACKGROUND] = comctl32_color.clrWindow; 2789 infoPtr->colors[MCSC_TEXT] = comctl32_color.clrWindowText; 2790 infoPtr->colors[MCSC_TITLEBK] = comctl32_color.clrActiveCaption; 2791 infoPtr->colors[MCSC_TITLETEXT] = comctl32_color.clrWindow; 2792 infoPtr->colors[MCSC_MONTHBK] = comctl32_color.clrWindow; 2793 infoPtr->colors[MCSC_TRAILINGTEXT] = comctl32_color.clrGrayText; 2794 2795 infoPtr->brushes[BrushBackground] = CreateSolidBrush(infoPtr->colors[MCSC_BACKGROUND]); 2796 infoPtr->brushes[BrushTitle] = CreateSolidBrush(infoPtr->colors[MCSC_TITLEBK]); 2797 infoPtr->brushes[BrushMonth] = CreateSolidBrush(infoPtr->colors[MCSC_MONTHBK]); 2798 2799 infoPtr->pens[PenRed] = CreatePen(PS_SOLID, 1, RGB(255, 0, 0)); 2800 infoPtr->pens[PenText] = CreatePen(PS_SOLID, 1, infoPtr->colors[MCSC_TEXT]); 2801 2802 infoPtr->minSel = infoPtr->todaysDate; 2803 infoPtr->maxSel = infoPtr->todaysDate; 2804 infoPtr->calendars[0].month = infoPtr->todaysDate; 2805 infoPtr->isUnicode = TRUE; 2806 2807 /* setup control layout and day state data */ 2808 MONTHCAL_UpdateSize(infoPtr); 2809 2810 /* today auto update timer, to be freed only on control destruction */ 2811 SetTimer(infoPtr->hwndSelf, MC_TODAYUPDATETIMER, MC_TODAYUPDATEDELAY, 0); 2812 2813 OpenThemeData (infoPtr->hwndSelf, themeClass); 2814 2815 return 0; 2816 2817 fail: 2818 Free(infoPtr->monthdayState); 2819 Free(infoPtr->calendars); 2820 Free(infoPtr); 2821 return 0; 2822 } 2823 2824 static LRESULT 2825 MONTHCAL_Destroy(MONTHCAL_INFO *infoPtr) 2826 { 2827 INT i; 2828 2829 /* free month calendar info data */ 2830 Free(infoPtr->monthdayState); 2831 Free(infoPtr->calendars); 2832 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0); 2833 2834 CloseThemeData (GetWindowTheme (infoPtr->hwndSelf)); 2835 2836 for (i = 0; i < BrushLast; i++) DeleteObject(infoPtr->brushes[i]); 2837 for (i = 0; i < PenLast; i++) DeleteObject(infoPtr->pens[i]); 2838 2839 Free(infoPtr); 2840 return 0; 2841 } 2842 2843 /* 2844 * Handler for WM_NOTIFY messages 2845 */ 2846 static LRESULT 2847 MONTHCAL_Notify(MONTHCAL_INFO *infoPtr, NMHDR *hdr) 2848 { 2849 /* notification from year edit updown */ 2850 if (hdr->code == UDN_DELTAPOS) 2851 { 2852 NMUPDOWN *nmud = (NMUPDOWN*)hdr; 2853 2854 if (hdr->hwndFrom == infoPtr->hWndYearUpDown && nmud->iDelta) 2855 { 2856 /* year value limits are set up explicitly after updown creation */ 2857 MONTHCAL_Scroll(infoPtr, 12 * nmud->iDelta, FALSE); 2858 MONTHCAL_NotifyDayState(infoPtr); 2859 MONTHCAL_NotifySelectionChange(infoPtr); 2860 } 2861 } 2862 return 0; 2863 } 2864 2865 static inline BOOL 2866 MONTHCAL_SetUnicodeFormat(MONTHCAL_INFO *infoPtr, BOOL isUnicode) 2867 { 2868 BOOL prev = infoPtr->isUnicode; 2869 infoPtr->isUnicode = isUnicode; 2870 return prev; 2871 } 2872 2873 static inline BOOL 2874 MONTHCAL_GetUnicodeFormat(const MONTHCAL_INFO *infoPtr) 2875 { 2876 return infoPtr->isUnicode; 2877 } 2878 2879 static LRESULT WINAPI 2880 MONTHCAL_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 2881 { 2882 MONTHCAL_INFO *infoPtr = (MONTHCAL_INFO *)GetWindowLongPtrW(hwnd, 0); 2883 2884 TRACE("hwnd=%p msg=%x wparam=%lx lparam=%lx\n", hwnd, uMsg, wParam, lParam); 2885 2886 if (!infoPtr && (uMsg != WM_CREATE)) 2887 return DefWindowProcW(hwnd, uMsg, wParam, lParam); 2888 switch(uMsg) 2889 { 2890 case MCM_GETCURSEL: 2891 return MONTHCAL_GetCurSel(infoPtr, (LPSYSTEMTIME)lParam); 2892 2893 case MCM_SETCURSEL: 2894 return MONTHCAL_SetCurSel(infoPtr, (LPSYSTEMTIME)lParam); 2895 2896 case MCM_GETMAXSELCOUNT: 2897 return MONTHCAL_GetMaxSelCount(infoPtr); 2898 2899 case MCM_SETMAXSELCOUNT: 2900 return MONTHCAL_SetMaxSelCount(infoPtr, wParam); 2901 2902 case MCM_GETSELRANGE: 2903 return MONTHCAL_GetSelRange(infoPtr, (LPSYSTEMTIME)lParam); 2904 2905 case MCM_SETSELRANGE: 2906 return MONTHCAL_SetSelRange(infoPtr, (LPSYSTEMTIME)lParam); 2907 2908 case MCM_GETMONTHRANGE: 2909 return MONTHCAL_GetMonthRange(infoPtr, wParam, (SYSTEMTIME*)lParam); 2910 2911 case MCM_SETDAYSTATE: 2912 return MONTHCAL_SetDayState(infoPtr, (INT)wParam, (LPMONTHDAYSTATE)lParam); 2913 2914 case MCM_GETMINREQRECT: 2915 return MONTHCAL_GetMinReqRect(infoPtr, (LPRECT)lParam); 2916 2917 case MCM_GETCOLOR: 2918 return MONTHCAL_GetColor(infoPtr, wParam); 2919 2920 case MCM_SETCOLOR: 2921 return MONTHCAL_SetColor(infoPtr, wParam, (COLORREF)lParam); 2922 2923 case MCM_GETTODAY: 2924 return MONTHCAL_GetToday(infoPtr, (LPSYSTEMTIME)lParam); 2925 2926 case MCM_SETTODAY: 2927 return MONTHCAL_SetToday(infoPtr, (LPSYSTEMTIME)lParam); 2928 2929 case MCM_HITTEST: 2930 return MONTHCAL_HitTest(infoPtr, (PMCHITTESTINFO)lParam); 2931 2932 case MCM_GETFIRSTDAYOFWEEK: 2933 return MONTHCAL_GetFirstDayOfWeek(infoPtr); 2934 2935 case MCM_SETFIRSTDAYOFWEEK: 2936 return MONTHCAL_SetFirstDayOfWeek(infoPtr, (INT)lParam); 2937 2938 case MCM_GETRANGE: 2939 return MONTHCAL_GetRange(infoPtr, (LPSYSTEMTIME)lParam); 2940 2941 case MCM_SETRANGE: 2942 return MONTHCAL_SetRange(infoPtr, (SHORT)wParam, (LPSYSTEMTIME)lParam); 2943 2944 case MCM_GETMONTHDELTA: 2945 return MONTHCAL_GetMonthDelta(infoPtr); 2946 2947 case MCM_SETMONTHDELTA: 2948 return MONTHCAL_SetMonthDelta(infoPtr, wParam); 2949 2950 case MCM_GETMAXTODAYWIDTH: 2951 return MONTHCAL_GetMaxTodayWidth(infoPtr); 2952 2953 case MCM_SETUNICODEFORMAT: 2954 return MONTHCAL_SetUnicodeFormat(infoPtr, (BOOL)wParam); 2955 2956 case MCM_GETUNICODEFORMAT: 2957 return MONTHCAL_GetUnicodeFormat(infoPtr); 2958 2959 case MCM_GETCALENDARCOUNT: 2960 return MONTHCAL_GetCalCount(infoPtr); 2961 2962 case WM_GETDLGCODE: 2963 return DLGC_WANTARROWS | DLGC_WANTCHARS; 2964 2965 case WM_RBUTTONUP: 2966 return MONTHCAL_RButtonUp(infoPtr, lParam); 2967 2968 case WM_LBUTTONDOWN: 2969 return MONTHCAL_LButtonDown(infoPtr, lParam); 2970 2971 case WM_MOUSEMOVE: 2972 return MONTHCAL_MouseMove(infoPtr, lParam); 2973 2974 case WM_LBUTTONUP: 2975 return MONTHCAL_LButtonUp(infoPtr, lParam); 2976 2977 case WM_PAINT: 2978 return MONTHCAL_Paint(infoPtr, (HDC)wParam); 2979 2980 case WM_PRINTCLIENT: 2981 return MONTHCAL_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam); 2982 2983 case WM_ERASEBKGND: 2984 return MONTHCAL_EraseBkgnd(infoPtr, (HDC)wParam); 2985 2986 case WM_SETFOCUS: 2987 return MONTHCAL_SetFocus(infoPtr); 2988 2989 case WM_SIZE: 2990 return MONTHCAL_Size(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam)); 2991 2992 case WM_NOTIFY: 2993 return MONTHCAL_Notify(infoPtr, (NMHDR*)lParam); 2994 2995 case WM_CREATE: 2996 return MONTHCAL_Create(hwnd, (LPCREATESTRUCTW)lParam); 2997 2998 case WM_SETFONT: 2999 return MONTHCAL_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam); 3000 3001 case WM_GETFONT: 3002 return MONTHCAL_GetFont(infoPtr); 3003 3004 case WM_TIMER: 3005 return MONTHCAL_Timer(infoPtr, wParam); 3006 3007 case WM_THEMECHANGED: 3008 return theme_changed (infoPtr); 3009 3010 case WM_DESTROY: 3011 return MONTHCAL_Destroy(infoPtr); 3012 3013 case WM_SYSCOLORCHANGE: 3014 COMCTL32_RefreshSysColors(); 3015 return 0; 3016 3017 case WM_STYLECHANGED: 3018 return MONTHCAL_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam); 3019 3020 case WM_STYLECHANGING: 3021 return MONTHCAL_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam); 3022 3023 default: 3024 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg)) 3025 ERR( "unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam); 3026 return DefWindowProcW(hwnd, uMsg, wParam, lParam); 3027 } 3028 } 3029 3030 3031 void 3032 MONTHCAL_Register(void) 3033 { 3034 WNDCLASSW wndClass; 3035 3036 ZeroMemory(&wndClass, sizeof(WNDCLASSW)); 3037 wndClass.style = CS_GLOBALCLASS; 3038 wndClass.lpfnWndProc = MONTHCAL_WindowProc; 3039 wndClass.cbClsExtra = 0; 3040 wndClass.cbWndExtra = sizeof(MONTHCAL_INFO *); 3041 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW); 3042 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 3043 wndClass.lpszClassName = MONTHCAL_CLASSW; 3044 3045 RegisterClassW(&wndClass); 3046 } 3047 3048 3049 void 3050 MONTHCAL_Unregister(void) 3051 { 3052 UnregisterClassW(MONTHCAL_CLASSW, NULL); 3053 } 3054