1 /* 2 * Date and time picker control 3 * 4 * Copyright 1998, 1999 Eric Kohl 5 * Copyright 1999, 2000 Alex Priem <alexp@sci.kun.nl> 6 * Copyright 2000 Chris Morgan <cmorgan@wpi.edu> 7 * Copyright 2012 Owen Rudge for CodeWeavers 8 * 9 * This library is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU Lesser General Public 11 * License as published by the Free Software Foundation; either 12 * version 2.1 of the License, or (at your option) any later version. 13 * 14 * This library is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Lesser General Public License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public 20 * License along with this library; if not, write to the Free Software 21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 22 * 23 * TODO: 24 * -- DTS_APPCANPARSE 25 * -- DTS_SHORTDATECENTURYFORMAT 26 * -- DTN_FORMAT 27 * -- DTN_FORMATQUERY 28 * -- DTN_USERSTRING 29 * -- DTN_WMKEYDOWN 30 * -- FORMATCALLBACK 31 */ 32 33 #include <math.h> 34 #include <string.h> 35 #include <stdarg.h> 36 #include <stdio.h> 37 #include <limits.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 "wine/debug.h" 47 48 WINE_DEFAULT_DEBUG_CHANNEL(datetime); 49 50 typedef struct 51 { 52 HWND hwndSelf; 53 HWND hMonthCal; 54 HWND hwndNotify; 55 HWND hUpdown; 56 DWORD dwStyle; 57 SYSTEMTIME date; 58 BOOL dateValid; 59 HWND hwndCheckbut; 60 RECT rcClient; /* rect around the edge of the window */ 61 RECT rcDraw; /* rect inside of the border */ 62 RECT checkbox; /* checkbox allowing the control to be enabled/disabled */ 63 RECT calbutton; /* button that toggles the dropdown of the monthcal control */ 64 BOOL bCalDepressed; /* TRUE = cal button is depressed */ 65 BOOL bDropdownEnabled; 66 int select; 67 WCHAR charsEntered[4]; 68 int nCharsEntered; 69 HFONT hFont; 70 int nrFieldsAllocated; 71 int nrFields; 72 int haveFocus; 73 int *fieldspec; 74 RECT *fieldRect; 75 int *buflen; 76 WCHAR textbuf[256]; 77 POINT monthcal_pos; 78 int pendingUpdown; 79 } DATETIME_INFO, *LPDATETIME_INFO; 80 81 /* in monthcal.c */ 82 extern int MONTHCAL_MonthLength(int month, int year); 83 extern int MONTHCAL_CalculateDayOfWeek(SYSTEMTIME *date, BOOL inplace); 84 85 /* this list of defines is closely related to `allowedformatchars' defined 86 * in datetime.c; the high nibble indicates the `base type' of the format 87 * specifier. 88 * Do not change without first reading DATETIME_UseFormat. 89 * 90 */ 91 92 #define DT_END_FORMAT 0 93 #define ONEDIGITDAY 0x01 94 #define TWODIGITDAY 0x02 95 #define THREECHARDAY 0x03 96 #define FULLDAY 0x04 97 #define ONEDIGIT12HOUR 0x11 98 #define TWODIGIT12HOUR 0x12 99 #define ONEDIGIT24HOUR 0x21 100 #define TWODIGIT24HOUR 0x22 101 #define ONEDIGITMINUTE 0x31 102 #define TWODIGITMINUTE 0x32 103 #define ONEDIGITMONTH 0x41 104 #define TWODIGITMONTH 0x42 105 #define THREECHARMONTH 0x43 106 #define FULLMONTH 0x44 107 #define ONEDIGITSECOND 0x51 108 #define TWODIGITSECOND 0x52 109 #define ONELETTERAMPM 0x61 110 #define TWOLETTERAMPM 0x62 111 #define ONEDIGITYEAR 0x71 112 #define TWODIGITYEAR 0x72 113 #define INVALIDFULLYEAR 0x73 /* FIXME - yyy is not valid - we'll treat it as yyyy */ 114 #define FULLYEAR 0x74 115 #define FORMATCALLBACK 0x81 /* -> maximum of 0x80 callbacks possible */ 116 #define FORMATCALLMASK 0x80 117 #define DT_STRING 0x0100 118 119 #define DTHT_DATEFIELD 0xff /* for hit-testing */ 120 121 #define DTHT_NONE 0x1000 122 #define DTHT_CHECKBOX 0x2000 /* these should end at '00' , to make */ 123 #define DTHT_MCPOPUP 0x3000 /* & DTHT_DATEFIELD 0 when DATETIME_KeyDown */ 124 #define DTHT_GOTFOCUS 0x4000 /* tests for date-fields */ 125 #define DTHT_NODATEMASK 0xf000 /* to mask check and drop down from others */ 126 127 static BOOL DATETIME_SendSimpleNotify (const DATETIME_INFO *infoPtr, UINT code); 128 static BOOL DATETIME_SendDateTimeChangeNotify (const DATETIME_INFO *infoPtr); 129 static const WCHAR allowedformatchars[] = L"dhHmMstyX"; 130 static const int maxrepetition [] = {4,2,2,2,4,2,2,4,-1}; 131 132 /* valid date limits */ 133 #ifndef __REACTOS__ 134 static const SYSTEMTIME max_allowed_date = { .wYear = 9999, .wMonth = 12, .wDayOfWeek = 0, .wDay = 31 }; 135 static const SYSTEMTIME min_allowed_date = { .wYear = 1752, .wMonth = 9, .wDayOfWeek = 0, .wDay = 14 }; 136 #else 137 static const SYSTEMTIME max_allowed_date = { /*.wYear =*/ 9999, /*.wMonth =*/ 12, /*.wDayOfWeek =*/ 0, /*.wDay =*/ 31 }; 138 static const SYSTEMTIME min_allowed_date = { /*.wYear =*/ 1752, /*.wMonth =*/ 9, /*.wDayOfWeek =*/ 0, /*.wDay =*/ 14 }; 139 #endif 140 141 static DWORD 142 DATETIME_GetSystemTime (const DATETIME_INFO *infoPtr, SYSTEMTIME *systime) 143 { 144 if (!systime) return GDT_NONE; 145 146 if ((infoPtr->dwStyle & DTS_SHOWNONE) && 147 (SendMessageW (infoPtr->hwndCheckbut, BM_GETCHECK, 0, 0) == BST_UNCHECKED)) 148 return GDT_NONE; 149 150 *systime = infoPtr->date; 151 152 return GDT_VALID; 153 } 154 155 /* Checks value is within configured date range 156 * 157 * PARAMETERS 158 * 159 * [I] infoPtr : valid pointer to control data 160 * [I] date : pointer to valid date data to check 161 * 162 * RETURN VALUE 163 * 164 * TRUE - date within configured range 165 * FALSE - date is outside configured range 166 */ 167 static BOOL DATETIME_IsDateInValidRange(const DATETIME_INFO *infoPtr, const SYSTEMTIME *date) 168 { 169 SYSTEMTIME range[2]; 170 DWORD limits; 171 172 if ((MONTHCAL_CompareSystemTime(date, &max_allowed_date) == 1) || 173 (MONTHCAL_CompareSystemTime(date, &min_allowed_date) == -1)) 174 return FALSE; 175 176 limits = SendMessageW (infoPtr->hMonthCal, MCM_GETRANGE, 0, (LPARAM)range); 177 178 if (limits & GDTR_MAX) 179 { 180 if (MONTHCAL_CompareSystemTime(date, &range[1]) == 1) 181 return FALSE; 182 } 183 184 if (limits & GDTR_MIN) 185 { 186 if (MONTHCAL_CompareSystemTime(date, &range[0]) == -1) 187 return FALSE; 188 } 189 190 return TRUE; 191 } 192 193 static BOOL 194 DATETIME_SetSystemTime (DATETIME_INFO *infoPtr, DWORD flag, const SYSTEMTIME *systime) 195 { 196 if (!systime) return FALSE; 197 198 TRACE("%04d/%02d/%02d %02d:%02d:%02d\n", 199 systime->wYear, systime->wMonth, systime->wDay, 200 systime->wHour, systime->wMinute, systime->wSecond); 201 202 if (flag == GDT_VALID) { 203 if (systime->wYear == 0 || 204 systime->wMonth < 1 || systime->wMonth > 12 || 205 systime->wDay < 1 || 206 systime->wDay > MONTHCAL_MonthLength(systime->wMonth, systime->wYear) || 207 systime->wHour > 23 || 208 systime->wMinute > 59 || 209 systime->wSecond > 59 || 210 systime->wMilliseconds > 999 211 ) 212 return FALSE; 213 214 /* Windows returns true if the date is valid but outside the limits set */ 215 if (!DATETIME_IsDateInValidRange(infoPtr, systime)) 216 return TRUE; 217 218 infoPtr->dateValid = TRUE; 219 infoPtr->date = *systime; 220 /* always store a valid day of week */ 221 MONTHCAL_CalculateDayOfWeek(&infoPtr->date, TRUE); 222 223 SendMessageW (infoPtr->hMonthCal, MCM_SETCURSEL, 0, (LPARAM)(&infoPtr->date)); 224 SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_CHECKED, 0); 225 } else if ((infoPtr->dwStyle & DTS_SHOWNONE) && (flag == GDT_NONE)) { 226 infoPtr->dateValid = FALSE; 227 SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_UNCHECKED, 0); 228 } 229 else 230 return FALSE; 231 232 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE); 233 return TRUE; 234 } 235 236 237 /*** 238 * Split up a formattxt in actions. 239 * See ms documentation for the meaning of the letter codes/'specifiers'. 240 * 241 * Notes: 242 * *'dddddd' is handled as 'dddd' plus 'dd'. 243 * *unrecognized formats are strings (here given the type DT_STRING; 244 * start of the string is encoded in lower bits of DT_STRING. 245 * Therefore, 'string' ends up as '<show seconds>tring'. 246 * 247 */ 248 static void 249 DATETIME_UseFormat (DATETIME_INFO *infoPtr, LPCWSTR formattxt) 250 { 251 unsigned int i; 252 int j, k, len; 253 BOOL inside_literal = FALSE; /* inside '...' */ 254 int *nrFields = &infoPtr->nrFields; 255 256 *nrFields = 0; 257 infoPtr->fieldspec[*nrFields] = 0; 258 len = lstrlenW(allowedformatchars); 259 k = 0; 260 261 for (i = 0; formattxt[i]; i++) { 262 TRACE ("\n%d %c:", i, formattxt[i]); 263 if (!inside_literal) { 264 for (j = 0; j < len; j++) { 265 if (allowedformatchars[j]==formattxt[i]) { 266 TRACE ("%c[%d,%x]", allowedformatchars[j], *nrFields, infoPtr->fieldspec[*nrFields]); 267 if ((*nrFields==0) && (infoPtr->fieldspec[*nrFields]==0)) { 268 infoPtr->fieldspec[*nrFields] = (j<<4) + 1; 269 break; 270 } 271 if (infoPtr->fieldspec[*nrFields] >> 4 != j) { 272 (*nrFields)++; 273 infoPtr->fieldspec[*nrFields] = (j<<4) + 1; 274 break; 275 } 276 if ((infoPtr->fieldspec[*nrFields] & 0x0f) == maxrepetition[j]) { 277 (*nrFields)++; 278 infoPtr->fieldspec[*nrFields] = (j<<4) + 1; 279 break; 280 } 281 infoPtr->fieldspec[*nrFields]++; 282 break; 283 } /* if allowedformatchar */ 284 } /* for j */ 285 } 286 else 287 j = len; 288 289 if (formattxt[i] == '\'') 290 { 291 inside_literal = !inside_literal; 292 continue; 293 } 294 295 /* char is not a specifier: handle char like a string */ 296 if (j == len) { 297 if ((*nrFields==0) && (infoPtr->fieldspec[*nrFields]==0)) { 298 infoPtr->fieldspec[*nrFields] = DT_STRING + k; 299 infoPtr->buflen[*nrFields] = 0; 300 } else if ((infoPtr->fieldspec[*nrFields] & DT_STRING) != DT_STRING) { 301 (*nrFields)++; 302 infoPtr->fieldspec[*nrFields] = DT_STRING + k; 303 infoPtr->buflen[*nrFields] = 0; 304 } 305 infoPtr->textbuf[k] = formattxt[i]; 306 k++; 307 infoPtr->buflen[*nrFields]++; 308 } /* if j=len */ 309 310 if (*nrFields == infoPtr->nrFieldsAllocated) { 311 FIXME ("out of memory; should reallocate. crash ahead.\n"); 312 } 313 } /* for i */ 314 315 TRACE("\n"); 316 317 if (infoPtr->fieldspec[*nrFields] != 0) (*nrFields)++; 318 } 319 320 321 static BOOL 322 DATETIME_SetFormatW (DATETIME_INFO *infoPtr, LPCWSTR format) 323 { 324 WCHAR format_buf[80]; 325 326 if (!format) { 327 DWORD format_item; 328 329 if ((infoPtr->dwStyle & DTS_SHORTDATECENTURYFORMAT) == DTS_SHORTDATECENTURYFORMAT) 330 format_item = LOCALE_SSHORTDATE; 331 else if ((infoPtr->dwStyle & DTS_LONGDATEFORMAT) == DTS_LONGDATEFORMAT) 332 format_item = LOCALE_SLONGDATE; 333 else if ((infoPtr->dwStyle & DTS_TIMEFORMAT) == DTS_TIMEFORMAT) 334 format_item = LOCALE_STIMEFORMAT; 335 else /* DTS_SHORTDATEFORMAT */ 336 format_item = LOCALE_SSHORTDATE; 337 GetLocaleInfoW(LOCALE_USER_DEFAULT, format_item, format_buf, ARRAY_SIZE(format_buf)); 338 format = format_buf; 339 } 340 341 DATETIME_UseFormat (infoPtr, format); 342 InvalidateRect (infoPtr->hwndSelf, NULL, TRUE); 343 344 return TRUE; 345 } 346 347 348 static BOOL 349 DATETIME_SetFormatA (DATETIME_INFO *infoPtr, LPCSTR lpszFormat) 350 { 351 if (lpszFormat) { 352 BOOL retval; 353 INT len = MultiByteToWideChar(CP_ACP, 0, lpszFormat, -1, NULL, 0); 354 LPWSTR wstr = Alloc(len * sizeof(WCHAR)); 355 if (wstr) MultiByteToWideChar(CP_ACP, 0, lpszFormat, -1, wstr, len); 356 retval = DATETIME_SetFormatW (infoPtr, wstr); 357 Free (wstr); 358 return retval; 359 } 360 else 361 return DATETIME_SetFormatW (infoPtr, 0); 362 363 } 364 365 366 static void 367 DATETIME_ReturnTxt (const DATETIME_INFO *infoPtr, int count, LPWSTR result, int resultSize) 368 { 369 SYSTEMTIME date = infoPtr->date; 370 int spec; 371 WCHAR buffer[80]; 372 373 *result=0; 374 TRACE ("%d,%d\n", infoPtr->nrFields, count); 375 if (count>infoPtr->nrFields || count < 0) { 376 WARN ("buffer overrun, have %d want %d\n", infoPtr->nrFields, count); 377 return; 378 } 379 380 if (!infoPtr->fieldspec) return; 381 382 spec = infoPtr->fieldspec[count]; 383 if (spec & DT_STRING) { 384 int txtlen = infoPtr->buflen[count]; 385 386 if (txtlen > resultSize) 387 txtlen = resultSize - 1; 388 memcpy (result, infoPtr->textbuf + (spec &~ DT_STRING), txtlen * sizeof(WCHAR)); 389 result[txtlen] = 0; 390 TRACE ("arg%d=%x->[%s]\n", count, infoPtr->fieldspec[count], debugstr_w(result)); 391 return; 392 } 393 394 395 switch (spec) { 396 case DT_END_FORMAT: 397 *result = 0; 398 break; 399 case ONEDIGITDAY: 400 wsprintfW (result, L"%d", date.wDay); 401 break; 402 case TWODIGITDAY: 403 wsprintfW (result, L"%.2d", date.wDay); 404 break; 405 case THREECHARDAY: 406 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1+(date.wDayOfWeek+6)%7, result, 4); 407 /*wsprintfW (result,"%.3s",days[date.wDayOfWeek]);*/ 408 break; 409 case FULLDAY: 410 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDAYNAME1+(date.wDayOfWeek+6)%7, result, resultSize); 411 break; 412 case ONEDIGIT12HOUR: 413 if (date.wHour == 0) { 414 result[0] = '1'; 415 result[1] = '2'; 416 result[2] = 0; 417 } 418 else 419 wsprintfW (result, L"%d", date.wHour - (date.wHour > 12 ? 12 : 0)); 420 break; 421 case TWODIGIT12HOUR: 422 if (date.wHour == 0) { 423 result[0] = '1'; 424 result[1] = '2'; 425 result[2] = 0; 426 } 427 else 428 wsprintfW (result, L"%.2d", date.wHour - (date.wHour > 12 ? 12 : 0)); 429 break; 430 case ONEDIGIT24HOUR: 431 wsprintfW (result, L"%d", date.wHour); 432 break; 433 case TWODIGIT24HOUR: 434 wsprintfW (result, L"%.2d", date.wHour); 435 break; 436 case ONEDIGITSECOND: 437 wsprintfW (result, L"%d", date.wSecond); 438 break; 439 case TWODIGITSECOND: 440 wsprintfW (result, L"%.2d", date.wSecond); 441 break; 442 case ONEDIGITMINUTE: 443 wsprintfW (result, L"%d", date.wMinute); 444 break; 445 case TWODIGITMINUTE: 446 wsprintfW (result, L"%.2d", date.wMinute); 447 break; 448 case ONEDIGITMONTH: 449 wsprintfW (result, L"%d", date.wMonth); 450 break; 451 case TWODIGITMONTH: 452 wsprintfW (result, L"%.2d", date.wMonth); 453 break; 454 case THREECHARMONTH: 455 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+date.wMonth -1, buffer, ARRAY_SIZE(buffer)); 456 wsprintfW (result, L"%s.3s", buffer); 457 break; 458 case FULLMONTH: 459 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+date.wMonth -1, 460 result, resultSize); 461 break; 462 case ONELETTERAMPM: 463 result[0] = (date.wHour < 12 ? 'A' : 'P'); 464 result[1] = 0; 465 break; 466 case TWOLETTERAMPM: 467 result[0] = (date.wHour < 12 ? 'A' : 'P'); 468 result[1] = 'M'; 469 result[2] = 0; 470 break; 471 case FORMATCALLBACK: 472 FIXME ("Not implemented\n"); 473 result[0] = 'x'; 474 result[1] = 0; 475 break; 476 case ONEDIGITYEAR: 477 wsprintfW (result, L"%d", date.wYear % 10); 478 break; 479 case TWODIGITYEAR: 480 wsprintfW (result, L"%.2d", date.wYear % 100); 481 break; 482 case INVALIDFULLYEAR: 483 case FULLYEAR: 484 wsprintfW (result, L"%d", date.wYear); 485 break; 486 } 487 488 TRACE ("arg%d=%x->[%s]\n", count, infoPtr->fieldspec[count], debugstr_w(result)); 489 } 490 491 static int wrap(int val, int delta, int minVal, int maxVal) 492 { 493 val += delta; 494 if (delta == INT_MIN || val < minVal) return maxVal; 495 if (delta == INT_MAX || val > maxVal) return minVal; 496 return val; 497 } 498 499 static void 500 DATETIME_IncreaseField (DATETIME_INFO *infoPtr, int number, int delta) 501 { 502 SYSTEMTIME *date = &infoPtr->date; 503 SYSTEMTIME range[2]; 504 DWORD limits; 505 BOOL min; 506 507 TRACE ("%d\n", number); 508 if ((number > infoPtr->nrFields) || (number < 0)) return; 509 510 if ((infoPtr->fieldspec[number] & DTHT_DATEFIELD) == 0) return; 511 512 switch (infoPtr->fieldspec[number]) { 513 case ONEDIGITYEAR: 514 case TWODIGITYEAR: 515 case FULLYEAR: 516 if (delta == INT_MIN) 517 date->wYear = 1752; 518 else if (delta == INT_MAX) 519 date->wYear = 9999; 520 else 521 date->wYear = max(min(date->wYear + delta, 9999), 1752); 522 523 if (date->wDay > MONTHCAL_MonthLength(date->wMonth, date->wYear)) 524 /* This can happen when moving away from a leap year. */ 525 date->wDay = MONTHCAL_MonthLength(date->wMonth, date->wYear); 526 MONTHCAL_CalculateDayOfWeek(date, TRUE); 527 break; 528 case ONEDIGITMONTH: 529 case TWODIGITMONTH: 530 case THREECHARMONTH: 531 case FULLMONTH: 532 date->wMonth = wrap(date->wMonth, delta, 1, 12); 533 MONTHCAL_CalculateDayOfWeek(date, TRUE); 534 delta = 0; 535 /* fall through */ 536 case ONEDIGITDAY: 537 case TWODIGITDAY: 538 case THREECHARDAY: 539 case FULLDAY: 540 date->wDay = wrap(date->wDay, delta, 1, MONTHCAL_MonthLength(date->wMonth, date->wYear)); 541 MONTHCAL_CalculateDayOfWeek(date, TRUE); 542 break; 543 case ONELETTERAMPM: 544 case TWOLETTERAMPM: 545 delta *= 12; 546 /* fall through */ 547 case ONEDIGIT12HOUR: 548 case TWODIGIT12HOUR: 549 case ONEDIGIT24HOUR: 550 case TWODIGIT24HOUR: 551 date->wHour = wrap(date->wHour, delta, 0, 23); 552 break; 553 case ONEDIGITMINUTE: 554 case TWODIGITMINUTE: 555 date->wMinute = wrap(date->wMinute, delta, 0, 59); 556 break; 557 case ONEDIGITSECOND: 558 case TWODIGITSECOND: 559 date->wSecond = wrap(date->wSecond, delta, 0, 59); 560 break; 561 case FORMATCALLBACK: 562 FIXME ("Not implemented\n"); 563 break; 564 } 565 566 /* FYI: On 1752/9/14 the calendar changed and England and the 567 * American colonies changed to the Gregorian calendar. This change 568 * involved having September 14th follow September 2nd. So no date 569 * algorithm works before that date. 570 */ 571 if (10000 * date->wYear + 100 * date->wMonth + date->wDay < 17520914) { 572 date->wYear = 1752; 573 date->wMonth = 9; 574 date->wDay = 14; 575 date->wSecond = 0; 576 date->wMinute = 0; 577 date->wHour = 0; 578 } 579 580 /* Ensure time is within bounds */ 581 limits = SendMessageW (infoPtr->hMonthCal, MCM_GETRANGE, 0, (LPARAM)range); 582 min = delta < 0; 583 584 if (limits & (min ? GDTR_MIN : GDTR_MAX)) 585 { 586 int i = (min ? 0 : 1); 587 588 if (MONTHCAL_CompareSystemTime(date, &range[i]) == (min ? -1 : 1)) 589 { 590 date->wYear = range[i].wYear; 591 date->wMonth = range[i].wMonth; 592 date->wDayOfWeek = range[i].wDayOfWeek; 593 date->wDay = range[i].wDay; 594 date->wHour = range[i].wHour; 595 date->wMinute = range[i].wMinute; 596 date->wSecond = range[i].wSecond; 597 date->wMilliseconds = range[i].wMilliseconds; 598 } 599 } 600 } 601 602 static int DATETIME_GetFieldWidth (const DATETIME_INFO *infoPtr, HDC hdc, int count) 603 { 604 /* fields are a fixed width, determined by the largest possible string */ 605 /* presumably, these widths should be language dependent */ 606 int spec; 607 WCHAR buffer[80]; 608 LPCWSTR bufptr; 609 SIZE size; 610 611 if (!infoPtr->fieldspec) return 0; 612 613 spec = infoPtr->fieldspec[count]; 614 if (spec & DT_STRING) { 615 int txtlen = infoPtr->buflen[count]; 616 617 if (txtlen > 79) 618 txtlen = 79; 619 memcpy (buffer, infoPtr->textbuf + (spec &~ DT_STRING), txtlen * sizeof(WCHAR)); 620 buffer[txtlen] = 0; 621 bufptr = buffer; 622 } 623 else { 624 switch (spec) { 625 case ONEDIGITDAY: 626 case ONEDIGIT12HOUR: 627 case ONEDIGIT24HOUR: 628 case ONEDIGITSECOND: 629 case ONEDIGITMINUTE: 630 case ONEDIGITMONTH: 631 case ONEDIGITYEAR: 632 /* these seem to use a two byte field */ 633 case TWODIGITDAY: 634 case TWODIGIT12HOUR: 635 case TWODIGIT24HOUR: 636 case TWODIGITSECOND: 637 case TWODIGITMINUTE: 638 case TWODIGITMONTH: 639 case TWODIGITYEAR: 640 bufptr = L"22"; 641 break; 642 case INVALIDFULLYEAR: 643 case FULLYEAR: 644 bufptr = L"2222"; 645 break; 646 case THREECHARMONTH: 647 case FULLMONTH: 648 case THREECHARDAY: 649 case FULLDAY: 650 { 651 const WCHAR *fall; 652 LCTYPE lctype; 653 INT i, max_count; 654 LONG cx; 655 656 /* choose locale data type and fallback string */ 657 switch (spec) { 658 case THREECHARDAY: 659 fall = L"Wed"; 660 lctype = LOCALE_SABBREVDAYNAME1; 661 max_count = 7; 662 break; 663 case FULLDAY: 664 fall = L"Wednesday"; 665 lctype = LOCALE_SDAYNAME1; 666 max_count = 7; 667 break; 668 case THREECHARMONTH: 669 fall = L"Dec"; 670 lctype = LOCALE_SABBREVMONTHNAME1; 671 max_count = 12; 672 break; 673 default: /* FULLMONTH */ 674 fall = L"September"; 675 lctype = LOCALE_SMONTHNAME1; 676 max_count = 12; 677 break; 678 } 679 680 cx = 0; 681 for (i = 0; i < max_count; i++) 682 { 683 if(GetLocaleInfoW(LOCALE_USER_DEFAULT, lctype + i, 684 buffer, ARRAY_SIZE(buffer))) 685 { 686 GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &size); 687 if (size.cx > cx) cx = size.cx; 688 } 689 else /* locale independent fallback on failure */ 690 { 691 GetTextExtentPoint32W(hdc, fall, lstrlenW(fall), &size); 692 cx = size.cx; 693 break; 694 } 695 } 696 return cx; 697 } 698 case ONELETTERAMPM: 699 bufptr = L"A"; 700 break; 701 case TWOLETTERAMPM: 702 bufptr = L"AM"; 703 break; 704 default: 705 bufptr = L"2"; 706 break; 707 } 708 } 709 GetTextExtentPoint32W (hdc, bufptr, lstrlenW(bufptr), &size); 710 return size.cx; 711 } 712 713 static void 714 DATETIME_Refresh (DATETIME_INFO *infoPtr, HDC hdc) 715 { 716 TRACE("\n"); 717 718 if (infoPtr->dateValid) { 719 int i, prevright; 720 RECT *field; 721 RECT *rcDraw = &infoPtr->rcDraw; 722 SIZE size; 723 COLORREF oldTextColor; 724 HFONT oldFont = SelectObject (hdc, infoPtr->hFont); 725 INT oldBkMode = SetBkMode (hdc, TRANSPARENT); 726 WCHAR txt[80]; 727 728 DATETIME_ReturnTxt (infoPtr, 0, txt, ARRAY_SIZE(txt)); 729 GetTextExtentPoint32W (hdc, txt, lstrlenW(txt), &size); 730 rcDraw->bottom = size.cy + 2; 731 732 prevright = infoPtr->checkbox.right = ((infoPtr->dwStyle & DTS_SHOWNONE) ? 18 : 2); 733 734 for (i = 0; i < infoPtr->nrFields; i++) { 735 DATETIME_ReturnTxt (infoPtr, i, txt, ARRAY_SIZE(txt)); 736 GetTextExtentPoint32W (hdc, txt, lstrlenW(txt), &size); 737 field = &infoPtr->fieldRect[i]; 738 field->left = prevright; 739 field->right = prevright + DATETIME_GetFieldWidth (infoPtr, hdc, i); 740 field->top = rcDraw->top; 741 field->bottom = rcDraw->bottom; 742 prevright = field->right; 743 744 if (infoPtr->dwStyle & WS_DISABLED) 745 oldTextColor = SetTextColor (hdc, comctl32_color.clrGrayText); 746 else if ((infoPtr->haveFocus) && (i == infoPtr->select)) { 747 RECT selection; 748 749 /* fill if focused */ 750 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrActiveCaption); 751 752 if (infoPtr->nCharsEntered) 753 { 754 memcpy(txt, infoPtr->charsEntered, infoPtr->nCharsEntered * sizeof(WCHAR)); 755 txt[infoPtr->nCharsEntered] = 0; 756 GetTextExtentPoint32W (hdc, txt, lstrlenW(txt), &size); 757 } 758 759 SetRect(&selection, 0, 0, size.cx, size.cy); 760 /* center rectangle */ 761 OffsetRect(&selection, (field->right + field->left - size.cx)/2, 762 (field->bottom - size.cy)/2); 763 764 FillRect(hdc, &selection, hbr); 765 DeleteObject (hbr); 766 oldTextColor = SetTextColor (hdc, comctl32_color.clrWindow); 767 } 768 else 769 oldTextColor = SetTextColor (hdc, comctl32_color.clrWindowText); 770 771 /* draw the date text using the colour set above */ 772 DrawTextW (hdc, txt, lstrlenW(txt), field, DT_CENTER | DT_VCENTER | DT_SINGLELINE); 773 SetTextColor (hdc, oldTextColor); 774 } 775 SetBkMode (hdc, oldBkMode); 776 SelectObject (hdc, oldFont); 777 } 778 779 if (!(infoPtr->dwStyle & DTS_UPDOWN)) { 780 DrawFrameControl(hdc, &infoPtr->calbutton, DFC_SCROLL, 781 DFCS_SCROLLDOWN | (infoPtr->bCalDepressed ? DFCS_PUSHED : 0) | 782 (infoPtr->dwStyle & WS_DISABLED ? DFCS_INACTIVE : 0) ); 783 } 784 } 785 786 787 static INT 788 DATETIME_HitTest (const DATETIME_INFO *infoPtr, POINT pt) 789 { 790 int i; 791 792 TRACE ("%s\n", wine_dbgstr_point(&pt)); 793 794 if (PtInRect (&infoPtr->calbutton, pt)) return DTHT_MCPOPUP; 795 if (PtInRect (&infoPtr->checkbox, pt)) return DTHT_CHECKBOX; 796 797 for (i = 0; i < infoPtr->nrFields; i++) { 798 if (PtInRect (&infoPtr->fieldRect[i], pt)) return i; 799 } 800 801 return DTHT_NONE; 802 } 803 804 /* Returns index of the nearest preceding date field from given, 805 or -1 if none was found */ 806 static int DATETIME_GetPrevDateField(const DATETIME_INFO *infoPtr, int i) 807 { 808 for(--i; i >= 0; i--) 809 { 810 if (infoPtr->fieldspec[i] & DTHT_DATEFIELD) return i; 811 } 812 return -1; 813 } 814 815 static void 816 DATETIME_ApplySelectedField (DATETIME_INFO *infoPtr) 817 { 818 int fieldNum = infoPtr->select & DTHT_DATEFIELD; 819 int i, val = 0; 820 BOOL clamp_day = FALSE; 821 SYSTEMTIME date = infoPtr->date; 822 int oldyear; 823 824 if (infoPtr->select == -1 || infoPtr->nCharsEntered == 0) 825 return; 826 827 if ((infoPtr->fieldspec[fieldNum] == ONELETTERAMPM) || 828 (infoPtr->fieldspec[fieldNum] == TWOLETTERAMPM)) 829 val = infoPtr->charsEntered[0]; 830 else { 831 for (i=0; i<infoPtr->nCharsEntered; i++) 832 val = val * 10 + infoPtr->charsEntered[i] - '0'; 833 } 834 835 infoPtr->nCharsEntered = 0; 836 837 switch (infoPtr->fieldspec[fieldNum]) { 838 case ONEDIGITYEAR: 839 case TWODIGITYEAR: 840 oldyear = date.wYear; 841 date.wYear = date.wYear - (date.wYear%100) + val; 842 843 if (DATETIME_IsDateInValidRange(infoPtr, &date)) 844 clamp_day = TRUE; 845 else 846 date.wYear = oldyear; 847 848 break; 849 case INVALIDFULLYEAR: 850 case FULLYEAR: 851 oldyear = date.wYear; 852 date.wYear = val; 853 854 if (DATETIME_IsDateInValidRange(infoPtr, &date)) 855 clamp_day = TRUE; 856 else 857 date.wYear = oldyear; 858 859 break; 860 case ONEDIGITMONTH: 861 case TWODIGITMONTH: 862 date.wMonth = val; 863 clamp_day = TRUE; 864 break; 865 case ONEDIGITDAY: 866 case TWODIGITDAY: 867 date.wDay = val; 868 break; 869 case ONEDIGIT12HOUR: 870 case TWODIGIT12HOUR: 871 if (val >= 24) 872 val -= 20; 873 874 if (val >= 13) 875 date.wHour = val; 876 else if (val != 0) { 877 if (date.wHour >= 12) /* preserve current AM/PM state */ 878 date.wHour = (val == 12 ? 12 : val + 12); 879 else 880 date.wHour = (val == 12 ? 0 : val); 881 } 882 break; 883 case ONEDIGIT24HOUR: 884 case TWODIGIT24HOUR: 885 date.wHour = val; 886 break; 887 case ONEDIGITMINUTE: 888 case TWODIGITMINUTE: 889 date.wMinute = val; 890 break; 891 case ONEDIGITSECOND: 892 case TWODIGITSECOND: 893 date.wSecond = val; 894 break; 895 case ONELETTERAMPM: 896 case TWOLETTERAMPM: 897 if (val == 'a' || val == 'A') { 898 if (date.wHour >= 12) 899 date.wHour -= 12; 900 } else if (val == 'p' || val == 'P') { 901 if (date.wHour < 12) 902 date.wHour += 12; 903 } 904 break; 905 } 906 907 if (clamp_day && date.wDay > MONTHCAL_MonthLength(date.wMonth, date.wYear)) 908 date.wDay = MONTHCAL_MonthLength(date.wMonth, date.wYear); 909 910 if (DATETIME_SetSystemTime(infoPtr, GDT_VALID, &date)) 911 DATETIME_SendDateTimeChangeNotify (infoPtr); 912 } 913 914 static void 915 DATETIME_SetSelectedField (DATETIME_INFO *infoPtr, int select) 916 { 917 DATETIME_ApplySelectedField(infoPtr); 918 919 infoPtr->select = select; 920 infoPtr->nCharsEntered = 0; 921 } 922 923 static LRESULT 924 DATETIME_LButtonDown (DATETIME_INFO *infoPtr, INT x, INT y) 925 { 926 POINT pt; 927 int new; 928 929 pt.x = x; 930 pt.y = y; 931 new = DATETIME_HitTest (infoPtr, pt); 932 933 SetFocus(infoPtr->hwndSelf); 934 935 if (!(new & DTHT_NODATEMASK) || (new == DTHT_NONE)) 936 { 937 if (new == DTHT_NONE) 938 new = infoPtr->nrFields - 1; 939 else 940 { 941 /* hitting string part moves selection to next date field to left */ 942 if (infoPtr->fieldspec[new] & DT_STRING) 943 { 944 new = DATETIME_GetPrevDateField(infoPtr, new); 945 if (new == -1) return 0; 946 } 947 /* never select full day of week */ 948 if (infoPtr->fieldspec[new] == FULLDAY) return 0; 949 } 950 } 951 952 DATETIME_SetSelectedField(infoPtr, new); 953 954 if (infoPtr->select == DTHT_MCPOPUP) { 955 RECT rcMonthCal; 956 POINT pos; 957 SendMessageW(infoPtr->hMonthCal, MCM_GETMINREQRECT, 0, (LPARAM)&rcMonthCal); 958 959 /* FIXME: button actually is only depressed during dropdown of the */ 960 /* calendar control and when the mouse is over the button window */ 961 infoPtr->bCalDepressed = TRUE; 962 963 /* recalculate the position of the monthcal popup */ 964 if(infoPtr->dwStyle & DTS_RIGHTALIGN) 965 pos.x = infoPtr->calbutton.left - (rcMonthCal.right - rcMonthCal.left); 966 else 967 /* FIXME: this should be after the area reserved for the checkbox */ 968 pos.x = infoPtr->rcDraw.left; 969 970 pos.y = infoPtr->rcClient.bottom; 971 OffsetRect( &rcMonthCal, pos.x, pos.y ); 972 MapWindowPoints( infoPtr->hwndSelf, 0, (POINT *)&rcMonthCal, 2 ); 973 SetWindowPos(infoPtr->hMonthCal, 0, rcMonthCal.left, rcMonthCal.top, 974 rcMonthCal.right - rcMonthCal.left, rcMonthCal.bottom - rcMonthCal.top, 0); 975 976 if(IsWindowVisible(infoPtr->hMonthCal)) { 977 ShowWindow(infoPtr->hMonthCal, SW_HIDE); 978 infoPtr->bDropdownEnabled = FALSE; 979 DATETIME_SendSimpleNotify (infoPtr, DTN_CLOSEUP); 980 } else { 981 const SYSTEMTIME *lprgSysTimeArray = &infoPtr->date; 982 TRACE("update calendar %04d/%02d/%02d\n", 983 lprgSysTimeArray->wYear, lprgSysTimeArray->wMonth, lprgSysTimeArray->wDay); 984 SendMessageW(infoPtr->hMonthCal, MCM_SETCURSEL, 0, (LPARAM)(&infoPtr->date)); 985 986 if (infoPtr->bDropdownEnabled) { 987 ShowWindow(infoPtr->hMonthCal, SW_SHOW); 988 DATETIME_SendSimpleNotify (infoPtr, DTN_DROPDOWN); 989 } 990 infoPtr->bDropdownEnabled = TRUE; 991 } 992 993 TRACE ("dt:%p mc:%p mc parent:%p, desktop:%p\n", 994 infoPtr->hwndSelf, infoPtr->hMonthCal, infoPtr->hwndNotify, GetDesktopWindow ()); 995 } 996 997 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE); 998 999 return 0; 1000 } 1001 1002 1003 static LRESULT 1004 DATETIME_LButtonUp (DATETIME_INFO *infoPtr) 1005 { 1006 if(infoPtr->bCalDepressed) { 1007 infoPtr->bCalDepressed = FALSE; 1008 InvalidateRect(infoPtr->hwndSelf, &(infoPtr->calbutton), TRUE); 1009 } 1010 1011 return 0; 1012 } 1013 1014 1015 static LRESULT 1016 DATETIME_Paint (DATETIME_INFO *infoPtr, HDC hdc) 1017 { 1018 if (!hdc) { 1019 PAINTSTRUCT ps; 1020 hdc = BeginPaint (infoPtr->hwndSelf, &ps); 1021 DATETIME_Refresh (infoPtr, hdc); 1022 EndPaint (infoPtr->hwndSelf, &ps); 1023 } else { 1024 DATETIME_Refresh (infoPtr, hdc); 1025 } 1026 1027 /* Not a click on the dropdown box, enabled it */ 1028 infoPtr->bDropdownEnabled = TRUE; 1029 1030 return 0; 1031 } 1032 1033 1034 static LRESULT 1035 DATETIME_Button_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam) 1036 { 1037 if( HIWORD(wParam) == BN_CLICKED) { 1038 DWORD state = SendMessageW((HWND)lParam, BM_GETCHECK, 0, 0); 1039 infoPtr->dateValid = (state == BST_CHECKED); 1040 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE); 1041 } 1042 return 0; 1043 } 1044 1045 1046 1047 static LRESULT 1048 DATETIME_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam) 1049 { 1050 TRACE("hwndbutton = %p\n", infoPtr->hwndCheckbut); 1051 if(infoPtr->hwndCheckbut == (HWND)lParam) 1052 return DATETIME_Button_Command(infoPtr, wParam, lParam); 1053 return 0; 1054 } 1055 1056 1057 static LRESULT 1058 DATETIME_Enable (DATETIME_INFO *infoPtr, BOOL bEnable) 1059 { 1060 TRACE("%p %s\n", infoPtr, bEnable ? "TRUE" : "FALSE"); 1061 if (bEnable) 1062 infoPtr->dwStyle &= ~WS_DISABLED; 1063 else 1064 infoPtr->dwStyle |= WS_DISABLED; 1065 1066 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE); 1067 1068 return 0; 1069 } 1070 1071 1072 static LRESULT 1073 DATETIME_EraseBackground (const DATETIME_INFO *infoPtr, HDC hdc) 1074 { 1075 HBRUSH hBrush, hSolidBrush = NULL; 1076 RECT rc; 1077 1078 if (infoPtr->dwStyle & WS_DISABLED) 1079 hBrush = hSolidBrush = CreateSolidBrush(comctl32_color.clrBtnFace); 1080 else 1081 { 1082 hBrush = (HBRUSH)SendMessageW(infoPtr->hwndNotify, WM_CTLCOLOREDIT, 1083 (WPARAM)hdc, (LPARAM)infoPtr->hwndSelf); 1084 if (!hBrush) 1085 hBrush = hSolidBrush = CreateSolidBrush(comctl32_color.clrWindow); 1086 } 1087 1088 GetClientRect (infoPtr->hwndSelf, &rc); 1089 1090 FillRect (hdc, &rc, hBrush); 1091 1092 if (hSolidBrush) 1093 DeleteObject(hSolidBrush); 1094 1095 return -1; 1096 } 1097 1098 1099 static LRESULT 1100 DATETIME_Notify (DATETIME_INFO *infoPtr, const NMHDR *lpnmh) 1101 { 1102 TRACE ("Got notification %x from %p\n", lpnmh->code, lpnmh->hwndFrom); 1103 TRACE ("info: %p %p %p\n", infoPtr->hwndSelf, infoPtr->hMonthCal, infoPtr->hUpdown); 1104 1105 if (lpnmh->code == MCN_SELECT) { 1106 ShowWindow(infoPtr->hMonthCal, SW_HIDE); 1107 infoPtr->dateValid = TRUE; 1108 SendMessageW (infoPtr->hMonthCal, MCM_GETCURSEL, 0, (LPARAM)&infoPtr->date); 1109 TRACE("got from calendar %04d/%02d/%02d day of week %d\n", 1110 infoPtr->date.wYear, infoPtr->date.wMonth, infoPtr->date.wDay, infoPtr->date.wDayOfWeek); 1111 SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_CHECKED, 0); 1112 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE); 1113 DATETIME_SendDateTimeChangeNotify (infoPtr); 1114 DATETIME_SendSimpleNotify(infoPtr, DTN_CLOSEUP); 1115 } 1116 if ((lpnmh->hwndFrom == infoPtr->hUpdown) && (lpnmh->code == UDN_DELTAPOS)) { 1117 const NM_UPDOWN *lpnmud = (const NM_UPDOWN*)lpnmh; 1118 TRACE("Delta pos %d\n", lpnmud->iDelta); 1119 infoPtr->pendingUpdown = lpnmud->iDelta; 1120 } 1121 return 0; 1122 } 1123 1124 1125 static LRESULT 1126 DATETIME_KeyDown (DATETIME_INFO *infoPtr, DWORD vkCode) 1127 { 1128 int fieldNum = infoPtr->select & DTHT_DATEFIELD; 1129 int wrap = 0; 1130 int new; 1131 1132 if (!(infoPtr->haveFocus)) return 0; 1133 if ((fieldNum==0) && (infoPtr->select)) return 0; 1134 1135 if (infoPtr->select & FORMATCALLMASK) { 1136 FIXME ("Callbacks not implemented yet\n"); 1137 } 1138 1139 switch (vkCode) { 1140 case VK_ADD: 1141 case VK_UP: 1142 infoPtr->nCharsEntered = 0; 1143 DATETIME_IncreaseField (infoPtr, fieldNum, 1); 1144 DATETIME_SendDateTimeChangeNotify (infoPtr); 1145 break; 1146 case VK_SUBTRACT: 1147 case VK_DOWN: 1148 infoPtr->nCharsEntered = 0; 1149 DATETIME_IncreaseField (infoPtr, fieldNum, -1); 1150 DATETIME_SendDateTimeChangeNotify (infoPtr); 1151 break; 1152 case VK_HOME: 1153 infoPtr->nCharsEntered = 0; 1154 DATETIME_IncreaseField (infoPtr, fieldNum, INT_MIN); 1155 DATETIME_SendDateTimeChangeNotify (infoPtr); 1156 break; 1157 case VK_END: 1158 infoPtr->nCharsEntered = 0; 1159 DATETIME_IncreaseField (infoPtr, fieldNum, INT_MAX); 1160 DATETIME_SendDateTimeChangeNotify (infoPtr); 1161 break; 1162 case VK_LEFT: 1163 new = infoPtr->select; 1164 do { 1165 if (new == 0) { 1166 new = new - 1; 1167 wrap++; 1168 } else { 1169 new--; 1170 } 1171 } while ((infoPtr->fieldspec[new] & DT_STRING) && (wrap<2)); 1172 if (new != infoPtr->select) 1173 DATETIME_SetSelectedField(infoPtr, new); 1174 break; 1175 case VK_RIGHT: 1176 new = infoPtr->select; 1177 do { 1178 new++; 1179 if (new==infoPtr->nrFields) { 1180 new = 0; 1181 wrap++; 1182 } 1183 } while ((infoPtr->fieldspec[new] & DT_STRING) && (wrap<2)); 1184 if (new != infoPtr->select) 1185 DATETIME_SetSelectedField(infoPtr, new); 1186 break; 1187 } 1188 1189 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE); 1190 1191 return 0; 1192 } 1193 1194 1195 static LRESULT 1196 DATETIME_Char (DATETIME_INFO *infoPtr, WPARAM vkCode) 1197 { 1198 int fieldNum, fieldSpec; 1199 1200 fieldNum = infoPtr->select & DTHT_DATEFIELD; 1201 fieldSpec = infoPtr->fieldspec[fieldNum]; 1202 1203 if (fieldSpec == ONELETTERAMPM || fieldSpec == TWOLETTERAMPM) { 1204 infoPtr->charsEntered[0] = vkCode; 1205 infoPtr->nCharsEntered = 1; 1206 1207 DATETIME_ApplySelectedField(infoPtr); 1208 } else if (vkCode >= '0' && vkCode <= '9') { 1209 int maxChars; 1210 1211 infoPtr->charsEntered[infoPtr->nCharsEntered++] = vkCode; 1212 1213 if (fieldSpec == INVALIDFULLYEAR || fieldSpec == FULLYEAR) 1214 maxChars = 4; 1215 else 1216 maxChars = 2; 1217 1218 if ((fieldSpec == ONEDIGIT12HOUR || 1219 fieldSpec == TWODIGIT12HOUR || 1220 fieldSpec == ONEDIGIT24HOUR || 1221 fieldSpec == TWODIGIT24HOUR) && 1222 (infoPtr->nCharsEntered == 1)) 1223 { 1224 if (vkCode >= '3') 1225 maxChars = 1; 1226 } 1227 1228 if (maxChars == infoPtr->nCharsEntered) 1229 DATETIME_ApplySelectedField(infoPtr); 1230 } 1231 1232 return 0; 1233 } 1234 1235 1236 static LRESULT 1237 DATETIME_VScroll (DATETIME_INFO *infoPtr, WORD wScroll) 1238 { 1239 int fieldNum = infoPtr->select & DTHT_DATEFIELD; 1240 1241 if ((SHORT)LOWORD(wScroll) != SB_THUMBPOSITION) return 0; 1242 if (!(infoPtr->haveFocus)) return 0; 1243 if ((fieldNum==0) && (infoPtr->select)) return 0; 1244 1245 if (infoPtr->pendingUpdown >= 0) { 1246 DATETIME_IncreaseField (infoPtr, fieldNum, 1); 1247 DATETIME_SendDateTimeChangeNotify (infoPtr); 1248 } 1249 else { 1250 DATETIME_IncreaseField (infoPtr, fieldNum, -1); 1251 DATETIME_SendDateTimeChangeNotify (infoPtr); 1252 } 1253 1254 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE); 1255 1256 return 0; 1257 } 1258 1259 1260 static LRESULT 1261 DATETIME_KillFocus (DATETIME_INFO *infoPtr, HWND lostFocus) 1262 { 1263 TRACE("lost focus to %p\n", lostFocus); 1264 1265 if (infoPtr->haveFocus) { 1266 DATETIME_SendSimpleNotify (infoPtr, NM_KILLFOCUS); 1267 infoPtr->haveFocus = 0; 1268 DATETIME_SetSelectedField (infoPtr, -1); 1269 } 1270 1271 InvalidateRect (infoPtr->hwndSelf, NULL, TRUE); 1272 1273 return 0; 1274 } 1275 1276 1277 static LRESULT 1278 DATETIME_NCCreate (HWND hwnd, const CREATESTRUCTW *lpcs) 1279 { 1280 DWORD dwExStyle = GetWindowLongW(hwnd, GWL_EXSTYLE); 1281 /* force control to have client edge */ 1282 dwExStyle |= WS_EX_CLIENTEDGE; 1283 SetWindowLongW(hwnd, GWL_EXSTYLE, dwExStyle); 1284 1285 return 1; 1286 } 1287 1288 1289 static LRESULT 1290 DATETIME_SetFocus (DATETIME_INFO *infoPtr, HWND lostFocus) 1291 { 1292 TRACE("got focus from %p\n", lostFocus); 1293 1294 /* if monthcal is open and it loses focus, close monthcal */ 1295 if (infoPtr->hMonthCal && (lostFocus == infoPtr->hMonthCal) && 1296 IsWindowVisible(infoPtr->hMonthCal)) 1297 { 1298 ShowWindow(infoPtr->hMonthCal, SW_HIDE); 1299 DATETIME_SendSimpleNotify(infoPtr, DTN_CLOSEUP); 1300 /* note: this get triggered even if monthcal loses focus to a dropdown 1301 * box click, which occurs without an intermediate WM_PAINT call 1302 */ 1303 infoPtr->bDropdownEnabled = FALSE; 1304 return 0; 1305 } 1306 1307 if (infoPtr->haveFocus == 0) { 1308 DATETIME_SendSimpleNotify (infoPtr, NM_SETFOCUS); 1309 infoPtr->haveFocus = DTHT_GOTFOCUS; 1310 } 1311 1312 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE); 1313 1314 return 0; 1315 } 1316 1317 1318 static BOOL 1319 DATETIME_SendDateTimeChangeNotify (const DATETIME_INFO *infoPtr) 1320 { 1321 NMDATETIMECHANGE dtdtc; 1322 1323 dtdtc.nmhdr.hwndFrom = infoPtr->hwndSelf; 1324 dtdtc.nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID); 1325 dtdtc.nmhdr.code = DTN_DATETIMECHANGE; 1326 1327 dtdtc.dwFlags = infoPtr->dateValid ? GDT_VALID : GDT_NONE; 1328 1329 dtdtc.st = infoPtr->date; 1330 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY, 1331 dtdtc.nmhdr.idFrom, (LPARAM)&dtdtc); 1332 } 1333 1334 1335 static BOOL 1336 DATETIME_SendSimpleNotify (const DATETIME_INFO *infoPtr, UINT code) 1337 { 1338 NMHDR nmhdr; 1339 1340 TRACE("%x\n", code); 1341 nmhdr.hwndFrom = infoPtr->hwndSelf; 1342 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID); 1343 nmhdr.code = code; 1344 1345 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY, 1346 nmhdr.idFrom, (LPARAM)&nmhdr); 1347 } 1348 1349 static LRESULT 1350 DATETIME_Size (DATETIME_INFO *infoPtr, INT width, INT height) 1351 { 1352 /* set size */ 1353 infoPtr->rcClient.bottom = height; 1354 infoPtr->rcClient.right = width; 1355 1356 TRACE("Height=%d, Width=%d\n", infoPtr->rcClient.bottom, infoPtr->rcClient.right); 1357 1358 infoPtr->rcDraw = infoPtr->rcClient; 1359 1360 if (infoPtr->dwStyle & DTS_UPDOWN) { 1361 SetWindowPos(infoPtr->hUpdown, NULL, 1362 infoPtr->rcClient.right-14, 0, 1363 15, infoPtr->rcClient.bottom - infoPtr->rcClient.top, 1364 SWP_NOACTIVATE | SWP_NOZORDER); 1365 } 1366 else { 1367 /* set the size of the button that drops the calendar down */ 1368 /* FIXME: account for style that allows button on left side */ 1369 infoPtr->calbutton.top = infoPtr->rcDraw.top; 1370 infoPtr->calbutton.bottom= infoPtr->rcDraw.bottom; 1371 infoPtr->calbutton.left = infoPtr->rcDraw.right-15; 1372 infoPtr->calbutton.right = infoPtr->rcDraw.right; 1373 } 1374 1375 /* set enable/disable button size for show none style being enabled */ 1376 /* FIXME: these dimensions are completely incorrect */ 1377 infoPtr->checkbox.top = infoPtr->rcDraw.top; 1378 infoPtr->checkbox.bottom = infoPtr->rcDraw.bottom; 1379 infoPtr->checkbox.left = infoPtr->rcDraw.left; 1380 infoPtr->checkbox.right = infoPtr->rcDraw.left + 10; 1381 1382 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE); 1383 1384 return 0; 1385 } 1386 1387 static LRESULT 1388 DATETIME_StyleChanging(DATETIME_INFO *infoPtr, WPARAM wStyleType, STYLESTRUCT *lpss) 1389 { 1390 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n", 1391 wStyleType, lpss->styleOld, lpss->styleNew); 1392 1393 /* block DTS_SHOWNONE change */ 1394 if ((lpss->styleNew ^ lpss->styleOld) & DTS_SHOWNONE) 1395 { 1396 if (lpss->styleOld & DTS_SHOWNONE) 1397 lpss->styleNew |= DTS_SHOWNONE; 1398 else 1399 lpss->styleNew &= ~DTS_SHOWNONE; 1400 } 1401 1402 return 0; 1403 } 1404 1405 static LRESULT 1406 DATETIME_StyleChanged(DATETIME_INFO *infoPtr, WPARAM wStyleType, const STYLESTRUCT *lpss) 1407 { 1408 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n", 1409 wStyleType, lpss->styleOld, lpss->styleNew); 1410 1411 if (wStyleType != GWL_STYLE) return 0; 1412 1413 infoPtr->dwStyle = lpss->styleNew; 1414 1415 if ( !(lpss->styleOld & DTS_SHOWNONE) && (lpss->styleNew & DTS_SHOWNONE) ) { 1416 infoPtr->hwndCheckbut = CreateWindowExW (0, WC_BUTTONW, 0, WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, 1417 2, 2, 13, 13, infoPtr->hwndSelf, 0, 1418 (HINSTANCE)GetWindowLongPtrW (infoPtr->hwndSelf, GWLP_HINSTANCE), 0); 1419 SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, infoPtr->dateValid ? 1 : 0, 0); 1420 } 1421 if ( (lpss->styleOld & DTS_SHOWNONE) && !(lpss->styleNew & DTS_SHOWNONE) ) { 1422 DestroyWindow(infoPtr->hwndCheckbut); 1423 infoPtr->hwndCheckbut = 0; 1424 } 1425 if ( !(lpss->styleOld & DTS_UPDOWN) && (lpss->styleNew & DTS_UPDOWN) ) { 1426 infoPtr->hUpdown = CreateUpDownControl (WS_CHILD | WS_BORDER | WS_VISIBLE, 120, 1, 20, 20, 1427 infoPtr->hwndSelf, 1, 0, 0, UD_MAXVAL, UD_MINVAL, 0); 1428 } 1429 if ( (lpss->styleOld & DTS_UPDOWN) && !(lpss->styleNew & DTS_UPDOWN) ) { 1430 DestroyWindow(infoPtr->hUpdown); 1431 infoPtr->hUpdown = 0; 1432 } 1433 1434 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE); 1435 return 0; 1436 } 1437 1438 static BOOL DATETIME_GetIdealSize(DATETIME_INFO *infoPtr, SIZE *size) 1439 { 1440 SIZE field_size; 1441 RECT rect; 1442 WCHAR txt[80]; 1443 HDC hdc; 1444 HFONT oldFont; 1445 int i; 1446 1447 size->cx = size->cy = 0; 1448 1449 hdc = GetDC(infoPtr->hwndSelf); 1450 oldFont = SelectObject(hdc, infoPtr->hFont); 1451 1452 /* Get text font height */ 1453 DATETIME_ReturnTxt(infoPtr, 0, txt, ARRAY_SIZE(txt)); 1454 GetTextExtentPoint32W(hdc, txt, lstrlenW(txt), &field_size); 1455 size->cy = field_size.cy; 1456 1457 /* Get text font width */ 1458 for (i = 0; i < infoPtr->nrFields; i++) 1459 { 1460 size->cx += DATETIME_GetFieldWidth(infoPtr, hdc, i); 1461 } 1462 1463 SelectObject(hdc, oldFont); 1464 ReleaseDC(infoPtr->hwndSelf, hdc); 1465 1466 if (infoPtr->dwStyle & DTS_UPDOWN) 1467 { 1468 GetWindowRect(infoPtr->hUpdown, &rect); 1469 size->cx += rect.right - rect.left; 1470 } 1471 else 1472 { 1473 size->cx += infoPtr->calbutton.right - infoPtr->calbutton.left; 1474 } 1475 1476 if (infoPtr->dwStyle & DTS_SHOWNONE) 1477 { 1478 size->cx += infoPtr->checkbox.right - infoPtr->checkbox.left; 1479 } 1480 1481 /* Add space between controls for them not to get too close */ 1482 size->cx += 12; 1483 size->cy += 4; 1484 1485 TRACE("cx=%d cy=%d\n", size->cx, size->cy); 1486 return TRUE; 1487 } 1488 1489 static LRESULT 1490 DATETIME_SetFont (DATETIME_INFO *infoPtr, HFONT font, BOOL repaint) 1491 { 1492 infoPtr->hFont = font; 1493 if (repaint) InvalidateRect(infoPtr->hwndSelf, NULL, TRUE); 1494 return 0; 1495 } 1496 1497 1498 static LRESULT 1499 DATETIME_Create (HWND hwnd, const CREATESTRUCTW *lpcs) 1500 { 1501 DATETIME_INFO *infoPtr = Alloc (sizeof(DATETIME_INFO)); 1502 STYLESTRUCT ss = { 0, lpcs->style }; 1503 1504 if (!infoPtr) return -1; 1505 1506 infoPtr->hwndSelf = hwnd; 1507 infoPtr->dwStyle = lpcs->style; 1508 1509 infoPtr->nrFieldsAllocated = 32; 1510 infoPtr->fieldspec = Alloc (infoPtr->nrFieldsAllocated * sizeof(int)); 1511 infoPtr->fieldRect = Alloc (infoPtr->nrFieldsAllocated * sizeof(RECT)); 1512 infoPtr->buflen = Alloc (infoPtr->nrFieldsAllocated * sizeof(int)); 1513 infoPtr->hwndNotify = lpcs->hwndParent; 1514 infoPtr->select = -1; /* initially, nothing is selected */ 1515 infoPtr->bDropdownEnabled = TRUE; 1516 1517 DATETIME_StyleChanged(infoPtr, GWL_STYLE, &ss); 1518 DATETIME_SetFormatW (infoPtr, 0); 1519 1520 /* create the monthcal control */ 1521 infoPtr->hMonthCal = CreateWindowExW (0, MONTHCAL_CLASSW, 0, WS_BORDER | WS_POPUP | WS_CLIPSIBLINGS, 1522 0, 0, 0, 0, infoPtr->hwndSelf, 0, 0, 0); 1523 1524 /* initialize info structure */ 1525 GetLocalTime (&infoPtr->date); 1526 infoPtr->dateValid = TRUE; 1527 infoPtr->hFont = GetStockObject(DEFAULT_GUI_FONT); 1528 1529 SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr); 1530 1531 return 0; 1532 } 1533 1534 1535 1536 static LRESULT 1537 DATETIME_Destroy (DATETIME_INFO *infoPtr) 1538 { 1539 if (infoPtr->hwndCheckbut) 1540 DestroyWindow(infoPtr->hwndCheckbut); 1541 if (infoPtr->hUpdown) 1542 DestroyWindow(infoPtr->hUpdown); 1543 if (infoPtr->hMonthCal) 1544 DestroyWindow(infoPtr->hMonthCal); 1545 SetWindowLongPtrW( infoPtr->hwndSelf, 0, 0 ); /* clear infoPtr */ 1546 Free (infoPtr->buflen); 1547 Free (infoPtr->fieldRect); 1548 Free (infoPtr->fieldspec); 1549 Free (infoPtr); 1550 return 0; 1551 } 1552 1553 1554 static INT 1555 DATETIME_GetText (const DATETIME_INFO *infoPtr, INT count, LPWSTR dst) 1556 { 1557 WCHAR buf[80]; 1558 int i; 1559 1560 if (!dst || (count <= 0)) return 0; 1561 1562 dst[0] = 0; 1563 for (i = 0; i < infoPtr->nrFields; i++) 1564 { 1565 DATETIME_ReturnTxt(infoPtr, i, buf, ARRAY_SIZE(buf)); 1566 if ((lstrlenW(dst) + lstrlenW(buf)) < count) 1567 lstrcatW(dst, buf); 1568 else break; 1569 } 1570 return lstrlenW(dst); 1571 } 1572 1573 static int DATETIME_GetTextLength(const DATETIME_INFO *info) 1574 { 1575 int i, length = 0; 1576 WCHAR buffer[80]; 1577 1578 TRACE("%p.\n", info); 1579 1580 for (i = 0; i < info->nrFields; i++) 1581 { 1582 DATETIME_ReturnTxt(info, i, buffer, ARRAY_SIZE(buffer)); 1583 length += lstrlenW(buffer); 1584 } 1585 return length; 1586 } 1587 1588 static LRESULT WINAPI 1589 DATETIME_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 1590 { 1591 DATETIME_INFO *infoPtr = ((DATETIME_INFO *)GetWindowLongPtrW (hwnd, 0)); 1592 1593 TRACE ("%x, %lx, %lx\n", uMsg, wParam, lParam); 1594 1595 if (!infoPtr && (uMsg != WM_CREATE) && (uMsg != WM_NCCREATE)) 1596 return DefWindowProcW( hwnd, uMsg, wParam, lParam ); 1597 1598 switch (uMsg) { 1599 1600 case DTM_GETSYSTEMTIME: 1601 return DATETIME_GetSystemTime (infoPtr, (SYSTEMTIME *) lParam); 1602 1603 case DTM_SETSYSTEMTIME: 1604 return DATETIME_SetSystemTime (infoPtr, wParam, (SYSTEMTIME *) lParam); 1605 1606 case DTM_GETRANGE: 1607 return SendMessageW (infoPtr->hMonthCal, MCM_GETRANGE, wParam, lParam); 1608 1609 case DTM_SETRANGE: 1610 return SendMessageW (infoPtr->hMonthCal, MCM_SETRANGE, wParam, lParam); 1611 1612 case DTM_SETFORMATA: 1613 return DATETIME_SetFormatA (infoPtr, (LPCSTR)lParam); 1614 1615 case DTM_SETFORMATW: 1616 return DATETIME_SetFormatW (infoPtr, (LPCWSTR)lParam); 1617 1618 case DTM_GETMONTHCAL: 1619 return (LRESULT)infoPtr->hMonthCal; 1620 1621 case DTM_SETMCCOLOR: 1622 return SendMessageW (infoPtr->hMonthCal, MCM_SETCOLOR, wParam, lParam); 1623 1624 case DTM_GETMCCOLOR: 1625 return SendMessageW (infoPtr->hMonthCal, MCM_GETCOLOR, wParam, 0); 1626 1627 case DTM_SETMCFONT: 1628 return SendMessageW (infoPtr->hMonthCal, WM_SETFONT, wParam, lParam); 1629 1630 case DTM_GETMCFONT: 1631 return SendMessageW (infoPtr->hMonthCal, WM_GETFONT, wParam, lParam); 1632 1633 case DTM_GETIDEALSIZE: 1634 return DATETIME_GetIdealSize(infoPtr, (SIZE *)lParam); 1635 1636 case WM_NOTIFY: 1637 return DATETIME_Notify (infoPtr, (LPNMHDR)lParam); 1638 1639 case WM_ENABLE: 1640 return DATETIME_Enable (infoPtr, (BOOL)wParam); 1641 1642 case WM_ERASEBKGND: 1643 return DATETIME_EraseBackground (infoPtr, (HDC)wParam); 1644 1645 case WM_GETDLGCODE: 1646 return DLGC_WANTARROWS | DLGC_WANTCHARS; 1647 1648 case WM_PRINTCLIENT: 1649 case WM_PAINT: 1650 return DATETIME_Paint (infoPtr, (HDC)wParam); 1651 1652 case WM_KEYDOWN: 1653 return DATETIME_KeyDown (infoPtr, wParam); 1654 1655 case WM_CHAR: 1656 return DATETIME_Char (infoPtr, wParam); 1657 1658 case WM_KILLFOCUS: 1659 return DATETIME_KillFocus (infoPtr, (HWND)wParam); 1660 1661 case WM_NCCREATE: 1662 return DATETIME_NCCreate (hwnd, (LPCREATESTRUCTW)lParam); 1663 1664 case WM_SETFOCUS: 1665 return DATETIME_SetFocus (infoPtr, (HWND)wParam); 1666 1667 case WM_SIZE: 1668 return DATETIME_Size (infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam)); 1669 1670 case WM_LBUTTONDOWN: 1671 return DATETIME_LButtonDown (infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam)); 1672 1673 case WM_LBUTTONUP: 1674 return DATETIME_LButtonUp (infoPtr); 1675 1676 case WM_VSCROLL: 1677 return DATETIME_VScroll (infoPtr, (WORD)wParam); 1678 1679 case WM_CREATE: 1680 return DATETIME_Create (hwnd, (LPCREATESTRUCTW)lParam); 1681 1682 case WM_DESTROY: 1683 return DATETIME_Destroy (infoPtr); 1684 1685 case WM_COMMAND: 1686 return DATETIME_Command (infoPtr, wParam, lParam); 1687 1688 case WM_STYLECHANGING: 1689 return DATETIME_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam); 1690 1691 case WM_STYLECHANGED: 1692 return DATETIME_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam); 1693 1694 case WM_SETFONT: 1695 return DATETIME_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam); 1696 1697 case WM_GETFONT: 1698 return (LRESULT) infoPtr->hFont; 1699 1700 case WM_GETTEXT: 1701 return (LRESULT) DATETIME_GetText(infoPtr, wParam, (LPWSTR)lParam); 1702 1703 case WM_GETTEXTLENGTH: 1704 return (LRESULT)DATETIME_GetTextLength(infoPtr); 1705 1706 case WM_SETTEXT: 1707 return CB_ERR; 1708 1709 default: 1710 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg)) 1711 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", 1712 uMsg, wParam, lParam); 1713 return DefWindowProcW (hwnd, uMsg, wParam, lParam); 1714 } 1715 } 1716 1717 1718 void 1719 DATETIME_Register (void) 1720 { 1721 WNDCLASSW wndClass; 1722 1723 ZeroMemory (&wndClass, sizeof(WNDCLASSW)); 1724 wndClass.style = CS_GLOBALCLASS; 1725 wndClass.lpfnWndProc = DATETIME_WindowProc; 1726 wndClass.cbClsExtra = 0; 1727 wndClass.cbWndExtra = sizeof(DATETIME_INFO *); 1728 wndClass.hCursor = LoadCursorW (0, (LPCWSTR)IDC_ARROW); 1729 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 1730 wndClass.lpszClassName = DATETIMEPICK_CLASSW; 1731 1732 RegisterClassW (&wndClass); 1733 } 1734 1735 1736 void 1737 DATETIME_Unregister (void) 1738 { 1739 UnregisterClassW (DATETIMEPICK_CLASSW, NULL); 1740 } 1741