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