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