datetime.c 45.3 KB
Newer Older
1 2 3
/*
 * Date and time picker control
 *
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
 *
8 9 10 11 12 13 14 15 16 17 18 19
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21
 *
22 23 24 25 26 27 28 29 30
 * NOTE
 * 
 * This code was audited for completeness against the documented features
 * of Comctl32.dll version 6.0 on Oct. 20, 2004, by Dimitrie O. Paun.
 * 
 * Unless otherwise noted, we believe this code to be complete, as per
 * the specification mentioned above.
 * If you discover missing features, or bugs, please note them below.
 * 
31
 * TODO:
32 33 34 35 36 37 38
 *    -- DTS_APPCANPARSE
 *    -- DTS_SHORTDATECENTURYFORMAT
 *    -- DTN_FORMAT
 *    -- DTN_FORMATQUERY
 *    -- DTN_USERSTRING
 *    -- DTN_WMKEYDOWN
 *    -- FORMATCALLBACK
39 40
 */

41 42
#include <math.h>
#include <string.h>
43
#include <stdarg.h>
44
#include <stdio.h>
45
#include <limits.h>
46

47
#include "windef.h"
48
#include "winbase.h"
49
#include "wingdi.h"
50 51
#include "winuser.h"
#include "winnls.h"
52
#include "commctrl.h"
53
#include "comctl32.h"
54
#include "wine/debug.h"
55
#include "wine/unicode.h"
56

57
WINE_DEFAULT_DEBUG_CHANNEL(datetime);
58

59 60
typedef struct
{
61 62 63 64
    HWND hwndSelf;
    HWND hMonthCal;
    HWND hwndNotify;
    HWND hUpdown;
65
    DWORD dwStyle;
66 67 68 69 70 71 72 73
    SYSTEMTIME date;
    BOOL dateValid;
    HWND hwndCheckbut;
    RECT rcClient; /* rect around the edge of the window */
    RECT rcDraw; /* rect inside of the border */
    RECT checkbox;  /* checkbox allowing the control to be enabled/disabled */
    RECT calbutton; /* button that toggles the dropdown of the monthcal control */
    BOOL bCalDepressed; /* TRUE = cal button is depressed */
74
    int  bDropdownEnabled;
75
    int  select;
76 77
    WCHAR charsEntered[4];
    int nCharsEntered;
78 79 80 81 82 83 84 85 86
    HFONT hFont;
    int nrFieldsAllocated;
    int nrFields;
    int haveFocus;
    int *fieldspec;
    RECT *fieldRect;
    int  *buflen;
    WCHAR textbuf[256];
    POINT monthcal_pos;
Duane Clark's avatar
Duane Clark committed
87
    int pendingUpdown;
88 89 90
} DATETIME_INFO, *LPDATETIME_INFO;

/* in monthcal.c */
91
extern int MONTHCAL_MonthLength(int month, int year);
92
extern int MONTHCAL_CalculateDayOfWeek(SYSTEMTIME *date, BOOL inplace);
93 94 95

/* this list of defines is closely related to `allowedformatchars' defined
 * in datetime.c; the high nibble indicates the `base type' of the format
96
 * specifier.
97
 * Do not change without first reading DATETIME_UseFormat.
98
 *
99 100
 */

101
#define DT_END_FORMAT      0
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
#define ONEDIGITDAY   	0x01
#define TWODIGITDAY   	0x02
#define THREECHARDAY  	0x03
#define FULLDAY         0x04
#define ONEDIGIT12HOUR  0x11
#define TWODIGIT12HOUR  0x12
#define ONEDIGIT24HOUR  0x21
#define TWODIGIT24HOUR  0x22
#define ONEDIGITMINUTE  0x31
#define TWODIGITMINUTE  0x32
#define ONEDIGITMONTH   0x41
#define TWODIGITMONTH   0x42
#define THREECHARMONTH  0x43
#define FULLMONTH       0x44
#define ONEDIGITSECOND  0x51
#define TWODIGITSECOND  0x52
#define ONELETTERAMPM   0x61
#define TWOLETTERAMPM   0x62
#define ONEDIGITYEAR    0x71
#define TWODIGITYEAR    0x72
Huw Davies's avatar
Huw Davies committed
122 123
#define INVALIDFULLYEAR 0x73      /* FIXME - yyy is not valid - we'll treat it as yyyy */
#define FULLYEAR        0x74
124 125 126 127 128 129
#define FORMATCALLBACK  0x81      /* -> maximum of 0x80 callbacks possible */
#define FORMATCALLMASK  0x80
#define DT_STRING 	0x0100

#define DTHT_DATEFIELD  0xff      /* for hit-testing */

130 131 132 133 134
#define DTHT_NONE       0x1000
#define DTHT_CHECKBOX   0x2000  /* these should end at '00' , to make */
#define DTHT_MCPOPUP    0x3000  /* & DTHT_DATEFIELD 0 when DATETIME_KeyDown */
#define DTHT_GOTFOCUS   0x4000  /* tests for date-fields */
#define DTHT_NODATEMASK 0xf000  /* to mask check and drop down from others */
135

136 137
static BOOL DATETIME_SendSimpleNotify (const DATETIME_INFO *infoPtr, UINT code);
static BOOL DATETIME_SendDateTimeChangeNotify (const DATETIME_INFO *infoPtr);
138 139
static const WCHAR allowedformatchars[] = {'d', 'h', 'H', 'm', 'M', 's', 't', 'y', 'X', 0};
static const int maxrepetition [] = {4,2,2,2,4,2,2,4,-1};
140

141

142
static DWORD
143
DATETIME_GetSystemTime (const DATETIME_INFO *infoPtr, SYSTEMTIME *systime)
144
{
145
    if (!systime) return GDT_NONE;
146

147 148
    if ((infoPtr->dwStyle & DTS_SHOWNONE) &&
        (SendMessageW (infoPtr->hwndCheckbut, BM_GETCHECK, 0, 0) == BST_UNCHECKED))
149 150
        return GDT_NONE;

151
    *systime = infoPtr->date;
152

153
    return GDT_VALID;
154 155 156
}


157
static BOOL
158
DATETIME_SetSystemTime (DATETIME_INFO *infoPtr, DWORD flag, const SYSTEMTIME *systime)
159
{
160
    if (!systime) return 0;
161

162
    TRACE("%04d/%02d/%02d %02d:%02d:%02d\n",
163 164
          systime->wYear, systime->wMonth, systime->wDay,
          systime->wHour, systime->wMinute, systime->wSecond);
165 166

    if (flag == GDT_VALID) {
167 168
      if (systime->wYear < 1601 || systime->wYear > 30827 ||
          systime->wMonth < 1 || systime->wMonth > 12 ||
169 170
          systime->wDay < 1 ||
          systime->wDay > MONTHCAL_MonthLength(systime->wMonth, systime->wYear) ||
171 172 173 174
          systime->wHour > 23 ||
          systime->wMinute > 59 ||
          systime->wSecond > 59 ||
          systime->wMilliseconds > 999
175
          )
176
        return FALSE;
177

178
        infoPtr->dateValid = TRUE;
179
        infoPtr->date = *systime;
180
        /* always store a valid day of week */
181
        MONTHCAL_CalculateDayOfWeek(&infoPtr->date, TRUE);
182

183 184
        SendMessageW (infoPtr->hMonthCal, MCM_SETCURSEL, 0, (LPARAM)(&infoPtr->date));
        SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_CHECKED, 0);
185
    } else if ((infoPtr->dwStyle & DTS_SHOWNONE) && (flag == GDT_NONE)) {
186 187 188
        infoPtr->dateValid = FALSE;
        SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_UNCHECKED, 0);
    }
189
    else
190
        return FALSE;
191

192 193
    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
    return TRUE;
194 195
}

196

197 198 199 200 201 202 203 204 205 206
/***
 * Split up a formattxt in actions.
 * See ms documentation for the meaning of the letter codes/'specifiers'.
 *
 * Notes:
 * *'dddddd' is handled as 'dddd' plus 'dd'.
 * *unrecognized formats are strings (here given the type DT_STRING;
 * start of the string is encoded in lower bits of DT_STRING.
 * Therefore, 'string' ends finally up as '<show seconds>tring'.
 *
207
 */
208
static void
209
DATETIME_UseFormat (DATETIME_INFO *infoPtr, LPCWSTR formattxt)
210
{
211 212
    unsigned int i;
    int j, k, len;
213
    BOOL inside_literal = FALSE; /* inside '...' */
214 215 216 217 218 219 220 221 222
    int *nrFields = &infoPtr->nrFields;

    *nrFields = 0;
    infoPtr->fieldspec[*nrFields] = 0;
    len = strlenW(allowedformatchars);
    k = 0;

    for (i = 0; formattxt[i]; i++)  {
	TRACE ("\n%d %c:", i, formattxt[i]);
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
	if (!inside_literal) {
	    for (j = 0; j < len; j++) {
	        if (allowedformatchars[j]==formattxt[i]) {
                    TRACE ("%c[%d,%x]", allowedformatchars[j], *nrFields, infoPtr->fieldspec[*nrFields]);
                    if ((*nrFields==0) && (infoPtr->fieldspec[*nrFields]==0)) {
                        infoPtr->fieldspec[*nrFields] = (j<<4) + 1;
                        break;
                    }
                    if (infoPtr->fieldspec[*nrFields] >> 4 != j) {
                        (*nrFields)++;
                        infoPtr->fieldspec[*nrFields] = (j<<4) + 1;
                        break;
                    }
                    if ((infoPtr->fieldspec[*nrFields] & 0x0f) == maxrepetition[j]) {
                        (*nrFields)++;
                        infoPtr->fieldspec[*nrFields] = (j<<4) + 1;
                        break;
		    }
                    infoPtr->fieldspec[*nrFields]++;
                    break;
                }   /* if allowedformatchar */
            } /* for j */
        }
        else
            j = len;

        if (formattxt[i] == '\'')
        {
            inside_literal = !inside_literal;
            continue;
        }
254 255 256 257 258 259 260 261 262 263 264 265 266 267

	/* char is not a specifier: handle char like a string */
	if (j == len) {
	    if ((*nrFields==0) && (infoPtr->fieldspec[*nrFields]==0)) {
		infoPtr->fieldspec[*nrFields] = DT_STRING + k;
		infoPtr->buflen[*nrFields] = 0;
            } else if ((infoPtr->fieldspec[*nrFields] & DT_STRING) != DT_STRING)  {
		(*nrFields)++;
		infoPtr->fieldspec[*nrFields] = DT_STRING + k;
		infoPtr->buflen[*nrFields] = 0;
	    }
	    infoPtr->textbuf[k] = formattxt[i];
	    k++;
	    infoPtr->buflen[*nrFields]++;
268 269
	}   /* if j=len */

270 271
	if (*nrFields == infoPtr->nrFieldsAllocated) {
	    FIXME ("out of memory; should reallocate. crash ahead.\n");
272
	}
273
    } /* for i */
274

275
    TRACE("\n");
276

277
    if (infoPtr->fieldspec[*nrFields] != 0) (*nrFields)++;
278 279
}

280

281
static BOOL
282
DATETIME_SetFormatW (DATETIME_INFO *infoPtr, LPCWSTR format)
283
{
284 285 286
    WCHAR format_buf[80];

    if (!format) {
287
	DWORD format_item;
288

289
	if (infoPtr->dwStyle & DTS_LONGDATEFORMAT)
290
	    format_item = LOCALE_SLONGDATE;
Duane Clark's avatar
Duane Clark committed
291
	else if ((infoPtr->dwStyle & DTS_TIMEFORMAT) == DTS_TIMEFORMAT)
292
	    format_item = LOCALE_STIMEFORMAT;
293
        else /* DTS_SHORTDATEFORMAT */
294
	    format_item = LOCALE_SSHORTDATE;
295
	GetLocaleInfoW(LOCALE_USER_DEFAULT, format_item, format_buf, sizeof(format_buf)/sizeof(format_buf[0]));
296
	format = format_buf;
297
    }
298

299
    DATETIME_UseFormat (infoPtr, format);
300
    InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
301

302
    return TRUE;
303
}
304

305

306 307
static BOOL
DATETIME_SetFormatA (DATETIME_INFO *infoPtr, LPCSTR lpszFormat)
308
{
309 310 311 312 313 314 315
    if (lpszFormat) {
	BOOL retval;
	INT len = MultiByteToWideChar(CP_ACP, 0, lpszFormat, -1, NULL, 0);
	LPWSTR wstr = Alloc(len * sizeof(WCHAR));
	if (wstr) MultiByteToWideChar(CP_ACP, 0, lpszFormat, -1, wstr, len);
	retval = DATETIME_SetFormatW (infoPtr, wstr);
	Free (wstr);
316
	return retval;
317 318 319
    }
    else
	return DATETIME_SetFormatW (infoPtr, 0);
320 321 322

}

323

324
static void
325
DATETIME_ReturnTxt (const DATETIME_INFO *infoPtr, int count, LPWSTR result, int resultSize)
326
{
327 328 329 330 331 332 333 334 335 336
    static const WCHAR fmt_dW[] = { '%', 'd', 0 };
    static const WCHAR fmt__2dW[] = { '%', '.', '2', 'd', 0 };
    static const WCHAR fmt__3sW[] = { '%', '.', '3', 's', 0 };
    SYSTEMTIME date = infoPtr->date;
    int spec;
    WCHAR buffer[80];

    *result=0;
    TRACE ("%d,%d\n", infoPtr->nrFields, count);
    if (count>infoPtr->nrFields || count < 0) {
337 338
	WARN ("buffer overrun, have %d want %d\n", infoPtr->nrFields, count);
	return;
339
    }
340

341
    if (!infoPtr->fieldspec) return;
342

343 344 345
    spec = infoPtr->fieldspec[count];
    if (spec & DT_STRING) {
	int txtlen = infoPtr->buflen[count];
346

347 348
        if (txtlen > resultSize)
            txtlen = resultSize - 1;
349 350 351
	memcpy (result, infoPtr->textbuf + (spec &~ DT_STRING), txtlen * sizeof(WCHAR));
	result[txtlen] = 0;
	TRACE ("arg%d=%x->[%s]\n", count, infoPtr->fieldspec[count], debugstr_w(result));
352
	return;
353
    }
354

355

356
    switch (spec) {
357
	case DT_END_FORMAT:
358 359
	    *result = 0;
	    break;
360
	case ONEDIGITDAY:
361 362
	    wsprintfW (result, fmt_dW, date.wDay);
	    break;
363
	case TWODIGITDAY:
364 365
	    wsprintfW (result, fmt__2dW, date.wDay);
	    break;
366
	case THREECHARDAY:
367 368 369
	    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1+(date.wDayOfWeek+6)%7, result, 4);
	    /*wsprintfW (result,"%.3s",days[date.wDayOfWeek]);*/
	    break;
370
	case FULLDAY:
371 372
	    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDAYNAME1+(date.wDayOfWeek+6)%7, result, resultSize);
	    break;
373
	case ONEDIGIT12HOUR:
374 375 376 377 378 379 380
	    if (date.wHour == 0) {
	        result[0] = '1';
	        result[1] = '2';
	        result[2] = 0;
	    }
	    else
	        wsprintfW (result, fmt_dW, date.wHour - (date.wHour > 12 ? 12 : 0));
381
	    break;
382
	case TWODIGIT12HOUR:
383 384 385 386 387 388 389
	    if (date.wHour == 0) {
	        result[0] = '1';
	        result[1] = '2';
	        result[2] = 0;
	    }
	    else
	        wsprintfW (result, fmt__2dW, date.wHour - (date.wHour > 12 ? 12 : 0));
390
	    break;
391
	case ONEDIGIT24HOUR:
392 393
	    wsprintfW (result, fmt_dW, date.wHour);
	    break;
394
	case TWODIGIT24HOUR:
395 396
	    wsprintfW (result, fmt__2dW, date.wHour);
	    break;
397
	case ONEDIGITSECOND:
398 399
	    wsprintfW (result, fmt_dW, date.wSecond);
	    break;
400
	case TWODIGITSECOND:
401 402
	    wsprintfW (result, fmt__2dW, date.wSecond);
	    break;
403
	case ONEDIGITMINUTE:
404 405
	    wsprintfW (result, fmt_dW, date.wMinute);
	    break;
406
	case TWODIGITMINUTE:
407 408
	    wsprintfW (result, fmt__2dW, date.wMinute);
	    break;
409
	case ONEDIGITMONTH:
410 411
	    wsprintfW (result, fmt_dW, date.wMonth);
	    break;
412
	case TWODIGITMONTH:
413 414
	    wsprintfW (result, fmt__2dW, date.wMonth);
	    break;
415
	case THREECHARMONTH:
416
	    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+date.wMonth -1,
417 418 419
			   buffer, sizeof(buffer)/sizeof(buffer[0]));
	    wsprintfW (result, fmt__3sW, buffer);
	    break;
420
	case FULLMONTH:
421
	    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+date.wMonth -1,
422 423
                           result, resultSize);
	    break;
424
	case ONELETTERAMPM:
425 426 427
	    result[0] = (date.wHour < 12 ? 'A' : 'P');
	    result[1] = 0;
	    break;
428
	case TWOLETTERAMPM:
429 430 431 432
	    result[0] = (date.wHour < 12 ? 'A' : 'P');
	    result[1] = 'M';
	    result[2] = 0;
	    break;
433
	case FORMATCALLBACK:
434 435 436 437
	    FIXME ("Not implemented\n");
	    result[0] = 'x';
	    result[1] = 0;
	    break;
438
	case ONEDIGITYEAR:
439 440
	    wsprintfW (result, fmt_dW, date.wYear-10* (int) floor(date.wYear/10));
	    break;
441
	case TWODIGITYEAR:
442 443
	    wsprintfW (result, fmt__2dW, date.wYear-100* (int) floor(date.wYear/100));
	    break;
Huw Davies's avatar
Huw Davies committed
444
        case INVALIDFULLYEAR:
445
	case FULLYEAR:
446 447
	    wsprintfW (result, fmt_dW, date.wYear);
	    break;
448
    }
449

450
    TRACE ("arg%d=%x->[%s]\n", count, infoPtr->fieldspec[count], debugstr_w(result));
451 452
}

453
static int wrap(int val, int delta, int minVal, int maxVal)
454
{
455 456 457 458
    val += delta;
    if (delta == INT_MIN || val < minVal) return maxVal;
    if (delta == INT_MAX || val > maxVal) return minVal;
    return val;
459 460
}

461
static void
462
DATETIME_IncreaseField (DATETIME_INFO *infoPtr, int number, int delta)
463
{
464
    SYSTEMTIME *date = &infoPtr->date;
465

466 467
    TRACE ("%d\n", number);
    if ((number > infoPtr->nrFields) || (number < 0)) return;
468

469
    if ((infoPtr->fieldspec[number] & DTHT_DATEFIELD) == 0) return;
470

471
    switch (infoPtr->fieldspec[number]) {
472 473 474
	case ONEDIGITYEAR:
	case TWODIGITYEAR:
	case FULLYEAR:
475
	    date->wYear = wrap(date->wYear, delta, 1752, 9999);
476 477 478
	    if (date->wDay > MONTHCAL_MonthLength(date->wMonth, date->wYear))
	        /* This can happen when moving away from a leap year. */
	        date->wDay = MONTHCAL_MonthLength(date->wMonth, date->wYear);
479
	    MONTHCAL_CalculateDayOfWeek(date, TRUE);
480
	    break;
481 482 483 484
	case ONEDIGITMONTH:
	case TWODIGITMONTH:
	case THREECHARMONTH:
	case FULLMONTH:
485
	    date->wMonth = wrap(date->wMonth, delta, 1, 12);
486
	    MONTHCAL_CalculateDayOfWeek(date, TRUE);
487 488
	    delta = 0;
	    /* fall through */
489 490 491
	case ONEDIGITDAY:
	case TWODIGITDAY:
	case THREECHARDAY:
492
	case FULLDAY:
493
	    date->wDay = wrap(date->wDay, delta, 1, MONTHCAL_MonthLength(date->wMonth, date->wYear));
494
	    MONTHCAL_CalculateDayOfWeek(date, TRUE);
495 496 497 498 499
	    break;
	case ONELETTERAMPM:
	case TWOLETTERAMPM:
	    delta *= 12;
	    /* fall through */
500 501 502 503
	case ONEDIGIT12HOUR:
	case TWODIGIT12HOUR:
	case ONEDIGIT24HOUR:
	case TWODIGIT24HOUR:
504 505
	    date->wHour = wrap(date->wHour, delta, 0, 23);
	    break;
506 507
	case ONEDIGITMINUTE:
	case TWODIGITMINUTE:
508 509 510 511 512 513
	    date->wMinute = wrap(date->wMinute, delta, 0, 59);
	    break;
	case ONEDIGITSECOND:
	case TWODIGITSECOND:
	    date->wSecond = wrap(date->wSecond, delta, 0, 59);
	    break;
514
	case FORMATCALLBACK:
515 516 517
	    FIXME ("Not implemented\n");
	    break;
    }
518

519 520 521 522 523 524 525 526 527 528 529 530 531
    /* FYI: On 1752/9/14 the calendar changed and England and the
     * American colonies changed to the Gregorian calendar. This change
     * involved having September 14th follow September 2nd. So no date
     * algorithm works before that date.
     */
    if (10000 * date->wYear + 100 * date->wMonth + date->wDay < 17520914) {
	date->wYear = 1752;
    	date->wMonth = 9;
	date->wDay = 14;
	date->wSecond = 0;
	date->wMinute = 0;
	date->wHour = 0;
    }
532 533 534
}


Duane Clark's avatar
Duane Clark committed
535
static void
536
DATETIME_ReturnFieldWidth (const DATETIME_INFO *infoPtr, HDC hdc, int count, SHORT *width)
Duane Clark's avatar
Duane Clark committed
537 538 539 540 541 542 543 544 545
{
    /* fields are a fixed width, determined by the largest possible string */
    /* presumably, these widths should be language dependent */
    static const WCHAR fld_d1W[] = { '2', 0 };
    static const WCHAR fld_d2W[] = { '2', '2', 0 };
    static const WCHAR fld_d4W[] = { '2', '2', '2', '2', 0 };
    static const WCHAR fld_am1[] = { 'A', 0 };
    static const WCHAR fld_am2[] = { 'A', 'M', 0 };
    int spec;
546 547
    WCHAR buffer[80];
    LPCWSTR bufptr;
Duane Clark's avatar
Duane Clark committed
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584
    SIZE size;

    TRACE ("%d,%d\n", infoPtr->nrFields, count);
    if (count>infoPtr->nrFields || count < 0) {
	WARN ("buffer overrun, have %d want %d\n", infoPtr->nrFields, count);
	return;
    }

    if (!infoPtr->fieldspec) return;

    spec = infoPtr->fieldspec[count];
    if (spec & DT_STRING) {
	int txtlen = infoPtr->buflen[count];

        if (txtlen > 79)
            txtlen = 79;
	memcpy (buffer, infoPtr->textbuf + (spec &~ DT_STRING), txtlen * sizeof(WCHAR));
	buffer[txtlen] = 0;
	bufptr = buffer;
    }
    else {
        switch (spec) {
	    case ONEDIGITDAY:
	    case ONEDIGIT12HOUR:
	    case ONEDIGIT24HOUR:
	    case ONEDIGITSECOND:
	    case ONEDIGITMINUTE:
	    case ONEDIGITMONTH:
	    case ONEDIGITYEAR:
	        /* these seem to use a two byte field */
	    case TWODIGITDAY:
	    case TWODIGIT12HOUR:
	    case TWODIGIT24HOUR:
	    case TWODIGITSECOND:
	    case TWODIGITMINUTE:
	    case TWODIGITMONTH:
	    case TWODIGITYEAR:
585
	        bufptr = fld_d2W;
Duane Clark's avatar
Duane Clark committed
586 587 588
	        break;
            case INVALIDFULLYEAR:
	    case FULLYEAR:
589
	        bufptr = fld_d4W;
Duane Clark's avatar
Duane Clark committed
590 591 592
	        break;
	    case THREECHARMONTH:
	    case FULLMONTH:
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
	    case THREECHARDAY:
	    case FULLDAY:
	    {
		static const WCHAR fld_day[] = {'W','e','d','n','e','s','d','a','y',0};
		static const WCHAR fld_abbrday[] = {'W','e','d',0};
		static const WCHAR fld_mon[] = {'S','e','p','t','e','m','b','e','r',0};
		static const WCHAR fld_abbrmon[] = {'D','e','c',0};

		const WCHAR *fall;
		LCTYPE lctype;
		INT i, max_count;
		LONG cx;

		/* choose locale data type and fallback string */
		switch (spec) {
		case THREECHARDAY:
		    fall   = fld_abbrday;
		    lctype = LOCALE_SABBREVDAYNAME1;
		    max_count = 7;
		    break;
		case FULLDAY:
		    fall   = fld_day;
		    lctype = LOCALE_SDAYNAME1;
		    max_count = 7;
		    break;
		case THREECHARMONTH:
		    fall   = fld_abbrmon;
		    lctype = LOCALE_SABBREVMONTHNAME1;
		    max_count = 12;
		    break;
623
		default: /* FULLMONTH */
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
		    fall   = fld_mon;
		    lctype = LOCALE_SMONTHNAME1;
		    max_count = 12;
		    break;
		}

		cx = 0;
		for (i = 0; i < max_count; i++)
		{
		    if(GetLocaleInfoW(LOCALE_USER_DEFAULT, lctype + i,
			buffer, lstrlenW(buffer)))
		    {
			GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &size);
			if (size.cx > cx) cx = size.cx;
		    }
		    else /* locale independent fallback on failure */
		    {
		        GetTextExtentPoint32W(hdc, fall, lstrlenW(fall), &size);
			cx = size.cx;
		        break;
		    }
		}
		*width = cx;
		return;
	    }
Duane Clark's avatar
Duane Clark committed
649
	    case ONELETTERAMPM:
650
	        bufptr = fld_am1;
Duane Clark's avatar
Duane Clark committed
651 652
	        break;
	    case TWOLETTERAMPM:
653
	        bufptr = fld_am2;
Duane Clark's avatar
Duane Clark committed
654 655
	        break;
	    default:
656
	        bufptr = fld_d1W;
Duane Clark's avatar
Duane Clark committed
657 658 659 660
	        break;
        }
    }
    GetTextExtentPoint32W (hdc, bufptr, strlenW(bufptr), &size);
661
    *width = size.cx;
Duane Clark's avatar
Duane Clark committed
662 663
}

664 665
static void 
DATETIME_Refresh (DATETIME_INFO *infoPtr, HDC hdc)
666
{
667 668 669
    TRACE("\n");

    if (infoPtr->dateValid) {
670 671 672 673 674 675
        int i, prevright;
        RECT *field;
        RECT *rcDraw = &infoPtr->rcDraw;
        SIZE size;
        COLORREF oldTextColor;
        SHORT fieldWidth = 0;
676
        HFONT oldFont = SelectObject (hdc, infoPtr->hFont);
677
        INT oldBkMode = SetBkMode (hdc, TRANSPARENT);
678 679 680 681 682 683
        WCHAR txt[80];

        DATETIME_ReturnTxt (infoPtr, 0, txt, sizeof(txt)/sizeof(txt[0]));
        GetTextExtentPoint32W (hdc, txt, strlenW(txt), &size);
        rcDraw->bottom = size.cy + 2;

684
        prevright = infoPtr->checkbox.right = ((infoPtr->dwStyle & DTS_SHOWNONE) ? 18 : 2);
685 686 687 688

        for (i = 0; i < infoPtr->nrFields; i++) {
            DATETIME_ReturnTxt (infoPtr, i, txt, sizeof(txt)/sizeof(txt[0]));
            GetTextExtentPoint32W (hdc, txt, strlenW(txt), &size);
Duane Clark's avatar
Duane Clark committed
689
            DATETIME_ReturnFieldWidth (infoPtr, hdc, i, &fieldWidth);
690
            field = &infoPtr->fieldRect[i];
691 692 693
            field->left   = prevright;
            field->right  = prevright + fieldWidth;
            field->top    = rcDraw->top;
694 695 696
            field->bottom = rcDraw->bottom;
            prevright = field->right;

697 698 699
            if (infoPtr->dwStyle & WS_DISABLED)
                oldTextColor = SetTextColor (hdc, comctl32_color.clrGrayText);
            else if ((infoPtr->haveFocus) && (i == infoPtr->select)) {
700 701
                RECT selection;

702
                /* fill if focused */
703
                HBRUSH hbr = CreateSolidBrush (comctl32_color.clrActiveCaption);
704

705 706 707 708 709 710 711
                if (infoPtr->nCharsEntered)
                {
                    memcpy(txt, infoPtr->charsEntered, infoPtr->nCharsEntered * sizeof(WCHAR));
                    txt[infoPtr->nCharsEntered] = 0;
                    GetTextExtentPoint32W (hdc, txt, strlenW(txt), &size);
                }

712
                selection.left   = 0;
713
                selection.top    = 0;
714
                selection.right  = size.cx;
715 716
                selection.bottom = size.cy;
                /* center rectangle */
717 718
                OffsetRect(&selection, (field->right  + field->left - size.cx)/2,
                                       (field->bottom - size.cy)/2);
719 720

                FillRect(hdc, &selection, hbr);
721
                DeleteObject (hbr);
722 723 724 725 726 727
                oldTextColor = SetTextColor (hdc, comctl32_color.clrWindow);
            }
            else
                oldTextColor = SetTextColor (hdc, comctl32_color.clrWindowText);

            /* draw the date text using the colour set above */
728
            DrawTextW (hdc, txt, strlenW(txt), field, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
729
            SetTextColor (hdc, oldTextColor);
730
        }
731
        SetBkMode (hdc, oldBkMode);
732
        SelectObject (hdc, oldFont);
733
    }
734

735
    if (!(infoPtr->dwStyle & DTS_UPDOWN)) {
736
        DrawFrameControl(hdc, &infoPtr->calbutton, DFC_SCROLL,
737 738 739
                         DFCS_SCROLLDOWN | (infoPtr->bCalDepressed ? DFCS_PUSHED : 0) |
                         (infoPtr->dwStyle & WS_DISABLED ? DFCS_INACTIVE : 0) );
    }
740 741
}

742

743
static INT
744
DATETIME_HitTest (const DATETIME_INFO *infoPtr, POINT pt)
745
{
746
    int i;
747

748
    TRACE ("%d, %d\n", pt.x, pt.y);
749

750 751
    if (PtInRect (&infoPtr->calbutton, pt)) return DTHT_MCPOPUP;
    if (PtInRect (&infoPtr->checkbox, pt)) return DTHT_CHECKBOX;
752

753
    for (i = 0; i < infoPtr->nrFields; i++) {
754
        if (PtInRect (&infoPtr->fieldRect[i], pt)) return i;
755
    }
756

757
    return DTHT_NONE;
758 759
}

760 761 762 763 764 765 766 767 768 769
/* Returns index of a closest date field from given counting to left
   or -1 if there's no such fields at left */
static int DATETIME_GetPrevDateField(const DATETIME_INFO *infoPtr, int i)
{
    for(--i; i >= 0; i--)
    {
        if (infoPtr->fieldspec[i] & DTHT_DATEFIELD) return i;
    }
    return -1;
}
770

771 772 773 774
static void
DATETIME_ApplySelectedField (DATETIME_INFO *infoPtr)
{
    int fieldNum = infoPtr->select & DTHT_DATEFIELD;
775
    int i, val=0, clamp_day=0;
776 777 778 779 780 781 782 783 784 785 786 787 788 789
    SYSTEMTIME date = infoPtr->date;

    if (infoPtr->select == -1 || infoPtr->nCharsEntered == 0)
        return;

    for (i=0; i<infoPtr->nCharsEntered; i++)
        val = val * 10 + infoPtr->charsEntered[i] - '0';

    infoPtr->nCharsEntered = 0;

    switch (infoPtr->fieldspec[fieldNum]) {
        case ONEDIGITYEAR:
        case TWODIGITYEAR:
            date.wYear = date.wYear - (date.wYear%100) + val;
790
            clamp_day = 1;
791 792 793 794
            break;
        case INVALIDFULLYEAR:
        case FULLYEAR:
            date.wYear = val;
795
            clamp_day = 1;
796 797 798 799
            break;
        case ONEDIGITMONTH:
        case TWODIGITMONTH:
            date.wMonth = val;
800
            clamp_day = 1;
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822
            break;
        case ONEDIGITDAY:
        case TWODIGITDAY:
            date.wDay = val;
            break;
        case ONEDIGIT12HOUR:
        case TWODIGIT12HOUR:
        case ONEDIGIT24HOUR:
        case TWODIGIT24HOUR:
            /* FIXME: Preserve AM/PM for 12HOUR? */
            date.wHour = val;
            break;
        case ONEDIGITMINUTE:
        case TWODIGITMINUTE:
            date.wMinute = val;
            break;
        case ONEDIGITSECOND:
        case TWODIGITSECOND:
            date.wSecond = val;
            break;
    }

823 824 825
    if (clamp_day && date.wDay > MONTHCAL_MonthLength(date.wMonth, date.wYear))
        date.wDay = MONTHCAL_MonthLength(date.wMonth, date.wYear);

826 827 828 829 830 831 832 833 834 835 836 837 838
    if (DATETIME_SetSystemTime(infoPtr, GDT_VALID, &date))
        DATETIME_SendDateTimeChangeNotify (infoPtr);
}

static void
DATETIME_SetSelectedField (DATETIME_INFO *infoPtr, int select)
{
    DATETIME_ApplySelectedField(infoPtr);

    infoPtr->select = select;
    infoPtr->nCharsEntered = 0;
}

839
static LRESULT
840
DATETIME_LButtonDown (DATETIME_INFO *infoPtr, INT x, INT y)
841
{
842
    POINT pt;
843
    int new;
844 845 846 847

    pt.x = x;
    pt.y = y;
    new = DATETIME_HitTest (infoPtr, pt);
848

849
    SetFocus(infoPtr->hwndSelf);
850

851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866
    if (!(new & DTHT_NODATEMASK) || (new == DTHT_NONE))
    {
        if (new == DTHT_NONE)
            new = infoPtr->nrFields - 1;
        else
        {
            /* hitting string part moves selection to next date field to left */
            if (infoPtr->fieldspec[new] & DT_STRING)
            {
                new = DATETIME_GetPrevDateField(infoPtr, new);
                if (new == -1) return 0;
            }
            /* never select full day of week */
            if (infoPtr->fieldspec[new] == FULLDAY) return 0;
        }
    }
867 868

    DATETIME_SetSelectedField(infoPtr, new);
869

870
    if (infoPtr->select == DTHT_MCPOPUP) {
871
        RECT rcMonthCal;
872
        POINT pos;
873 874
        SendMessageW(infoPtr->hMonthCal, MCM_GETMINREQRECT, 0, (LPARAM)&rcMonthCal);

875 876 877 878 879 880
        /* FIXME: button actually is only depressed during dropdown of the */
        /* calendar control and when the mouse is over the button window */
        infoPtr->bCalDepressed = TRUE;

        /* recalculate the position of the monthcal popup */
        if(infoPtr->dwStyle & DTS_RIGHTALIGN)
881
            pos.x = infoPtr->calbutton.left - (rcMonthCal.right - rcMonthCal.left);
882
        else
883
            /* FIXME: this should be after the area reserved for the checkbox */
884
            pos.x = infoPtr->rcDraw.left;
885

886 887 888 889 890
        pos.y = infoPtr->rcClient.bottom;
        OffsetRect( &rcMonthCal, pos.x, pos.y );
        MapWindowPoints( infoPtr->hwndSelf, 0, (POINT *)&rcMonthCal, 2 );
        SetWindowPos(infoPtr->hMonthCal, 0, rcMonthCal.left, rcMonthCal.top,
                     rcMonthCal.right - rcMonthCal.left, rcMonthCal.bottom - rcMonthCal.top, 0);
891 892 893

        if(IsWindowVisible(infoPtr->hMonthCal)) {
            ShowWindow(infoPtr->hMonthCal, SW_HIDE);
894 895
            infoPtr->bDropdownEnabled = FALSE;
            DATETIME_SendSimpleNotify (infoPtr, DTN_CLOSEUP);
896
        } else {
897
            const SYSTEMTIME *lprgSysTimeArray = &infoPtr->date;
898 899 900
            TRACE("update calendar %04d/%02d/%02d\n", 
            lprgSysTimeArray->wYear, lprgSysTimeArray->wMonth, lprgSysTimeArray->wDay);
            SendMessageW(infoPtr->hMonthCal, MCM_SETCURSEL, 0, (LPARAM)(&infoPtr->date));
901

902
            if (infoPtr->bDropdownEnabled) {
903
                ShowWindow(infoPtr->hMonthCal, SW_SHOW);
904 905
                DATETIME_SendSimpleNotify (infoPtr, DTN_DROPDOWN);
            }
906
            infoPtr->bDropdownEnabled = TRUE;
907 908 909 910
        }

        TRACE ("dt:%p mc:%p mc parent:%p, desktop:%p\n",
               infoPtr->hwndSelf, infoPtr->hMonthCal, infoPtr->hwndNotify, GetDesktopWindow ());
911
    }
912

913
    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
914

915
    return 0;
916 917 918 919
}


static LRESULT
920
DATETIME_LButtonUp (DATETIME_INFO *infoPtr)
921
{
922
    if(infoPtr->bCalDepressed) {
923 924 925
        infoPtr->bCalDepressed = FALSE;
        InvalidateRect(infoPtr->hwndSelf, &(infoPtr->calbutton), TRUE);
    }
926

927
    return 0;
928 929 930 931
}


static LRESULT
932
DATETIME_Paint (DATETIME_INFO *infoPtr, HDC hdc)
933
{
934 935 936 937 938 939 940 941
    if (!hdc) {
	PAINTSTRUCT ps;
        hdc = BeginPaint (infoPtr->hwndSelf, &ps);
        DATETIME_Refresh (infoPtr, hdc);
        EndPaint (infoPtr->hwndSelf, &ps);
    } else {
        DATETIME_Refresh (infoPtr, hdc);
    }
942 943 944 945

    /* Not a click on the dropdown box, enabled it */
    infoPtr->bDropdownEnabled = TRUE;

946 947 948
    return 0;
}

949

Huw Davies's avatar
Huw Davies committed
950
static LRESULT
951
DATETIME_Button_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
Huw Davies's avatar
Huw Davies committed
952
{
953 954 955 956
    if( HIWORD(wParam) == BN_CLICKED) {
        DWORD state = SendMessageW((HWND)lParam, BM_GETCHECK, 0, 0);
        infoPtr->dateValid = (state == BST_CHECKED);
        InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
Huw Davies's avatar
Huw Davies committed
957
    }
958
    return 0;
Huw Davies's avatar
Huw Davies committed
959 960 961 962 963
}
          
        
        
static LRESULT
964
DATETIME_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
Huw Davies's avatar
Huw Davies committed
965 966 967
{
    TRACE("hwndbutton = %p\n", infoPtr->hwndCheckbut);
    if(infoPtr->hwndCheckbut == (HWND)lParam)
968
        return DATETIME_Button_Command(infoPtr, wParam, lParam);
Huw Davies's avatar
Huw Davies committed
969 970 971
    return 0;
}

972

973 974 975 976 977 978 979 980
static LRESULT
DATETIME_Enable (DATETIME_INFO *infoPtr, BOOL bEnable)
{
    TRACE("%p %s\n", infoPtr, bEnable ? "TRUE" : "FALSE");
    if (bEnable)
        infoPtr->dwStyle &= ~WS_DISABLED;
    else
        infoPtr->dwStyle |= WS_DISABLED;
981 982 983

    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);

984 985 986 987 988
    return 0;
}


static LRESULT
989
DATETIME_EraseBackground (const DATETIME_INFO *infoPtr, HDC hdc)
990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
{
    HBRUSH hBrush, hSolidBrush = NULL;
    RECT   rc;

    if (infoPtr->dwStyle & WS_DISABLED)
        hBrush = hSolidBrush = CreateSolidBrush(comctl32_color.clrBtnFace);
    else
    {
        hBrush = (HBRUSH)SendMessageW(infoPtr->hwndNotify, WM_CTLCOLOREDIT,
                                      (WPARAM)hdc, (LPARAM)infoPtr->hwndSelf);
        if (!hBrush)
            hBrush = hSolidBrush = CreateSolidBrush(comctl32_color.clrWindow);
    }

    GetClientRect (infoPtr->hwndSelf, &rc);

    FillRect (hdc, &rc, hBrush);

    if (hSolidBrush)
        DeleteObject(hSolidBrush);

    return -1;
}


1015
static LRESULT
1016
DATETIME_Notify (DATETIME_INFO *infoPtr, const NMHDR *lpnmh)
1017
{
1018 1019 1020 1021 1022 1023 1024
    TRACE ("Got notification %x from %p\n", lpnmh->code, lpnmh->hwndFrom);
    TRACE ("info: %p %p %p\n", infoPtr->hwndSelf, infoPtr->hMonthCal, infoPtr->hUpdown);

    if (lpnmh->code == MCN_SELECT) {
        ShowWindow(infoPtr->hMonthCal, SW_HIDE);
        infoPtr->dateValid = TRUE;
        SendMessageW (infoPtr->hMonthCal, MCM_GETCURSEL, 0, (LPARAM)&infoPtr->date);
Duane Clark's avatar
Duane Clark committed
1025 1026
        TRACE("got from calendar %04d/%02d/%02d day of week %d\n", 
        infoPtr->date.wYear, infoPtr->date.wMonth, infoPtr->date.wDay, infoPtr->date.wDayOfWeek);
1027 1028 1029
        SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_CHECKED, 0);
        InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
        DATETIME_SendDateTimeChangeNotify (infoPtr);
1030
        DATETIME_SendSimpleNotify(infoPtr, DTN_CLOSEUP);
1031
    }
Duane Clark's avatar
Duane Clark committed
1032
    if ((lpnmh->hwndFrom == infoPtr->hUpdown) && (lpnmh->code == UDN_DELTAPOS)) {
1033
        const NM_UPDOWN *lpnmud = (const NM_UPDOWN*)lpnmh;
Duane Clark's avatar
Duane Clark committed
1034 1035 1036
        TRACE("Delta pos %d\n", lpnmud->iDelta);
        infoPtr->pendingUpdown = lpnmud->iDelta;
    }
1037
    return 0;
1038 1039
}

1040

1041
static LRESULT
1042
DATETIME_KeyDown (DATETIME_INFO *infoPtr, DWORD vkCode)
1043
{
1044 1045
    int fieldNum = infoPtr->select & DTHT_DATEFIELD;
    int wrap = 0;
1046
    int new;
1047

1048 1049
    if (!(infoPtr->haveFocus)) return 0;
    if ((fieldNum==0) && (infoPtr->select)) return 0;
1050

1051
    if (infoPtr->select & FORMATCALLMASK) {
1052
	FIXME ("Callbacks not implemented yet\n");
1053
    }
1054

1055
    switch (vkCode) {
1056
	case VK_ADD:
1057
    	case VK_UP:
1058
	    infoPtr->nCharsEntered = 0;
1059 1060 1061
	    DATETIME_IncreaseField (infoPtr, fieldNum, 1);
	    DATETIME_SendDateTimeChangeNotify (infoPtr);
	    break;
1062 1063
	case VK_SUBTRACT:
	case VK_DOWN:
1064
	    infoPtr->nCharsEntered = 0;
1065 1066 1067
	    DATETIME_IncreaseField (infoPtr, fieldNum, -1);
	    DATETIME_SendDateTimeChangeNotify (infoPtr);
	    break;
1068
	case VK_HOME:
1069
	    infoPtr->nCharsEntered = 0;
1070 1071 1072
	    DATETIME_IncreaseField (infoPtr, fieldNum, INT_MIN);
	    DATETIME_SendDateTimeChangeNotify (infoPtr);
	    break;
1073
	case VK_END:
1074
	    infoPtr->nCharsEntered = 0;
1075 1076 1077
	    DATETIME_IncreaseField (infoPtr, fieldNum, INT_MAX);
	    DATETIME_SendDateTimeChangeNotify (infoPtr);
	    break;
1078
	case VK_LEFT:
1079
	    new = infoPtr->select;
1080
	    do {
1081 1082
		if (new == 0) {
		    new = new - 1;
1083 1084
		    wrap++;
		} else {
1085
		    new--;
1086
		}
1087 1088 1089
	    } while ((infoPtr->fieldspec[new] & DT_STRING) && (wrap<2));
	    if (new != infoPtr->select)
	        DATETIME_SetSelectedField(infoPtr, new);
1090
	    break;
1091
	case VK_RIGHT:
1092
	    new = infoPtr->select;
1093
	    do {
1094 1095 1096
		new++;
		if (new==infoPtr->nrFields) {
		    new = 0;
1097 1098
		    wrap++;
		}
1099 1100 1101
	    } while ((infoPtr->fieldspec[new] & DT_STRING) && (wrap<2));
	    if (new != infoPtr->select)
	        DATETIME_SetSelectedField(infoPtr, new);
1102 1103
	    break;
    }
1104

1105
    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1106

1107
    return 0;
1108 1109 1110
}


1111
static LRESULT
1112
DATETIME_Char (DATETIME_INFO *infoPtr, WPARAM vkCode)
1113 1114 1115 1116
{
    int fieldNum = infoPtr->select & DTHT_DATEFIELD;

    if (vkCode >= '0' && vkCode <= '9') {
1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130
        int maxChars;
        int fieldSpec;

        infoPtr->charsEntered[infoPtr->nCharsEntered++] = vkCode;

        fieldSpec = infoPtr->fieldspec[fieldNum];

        if (fieldSpec == INVALIDFULLYEAR || fieldSpec == FULLYEAR)
            maxChars = 4;
        else
            maxChars = 2;

        if (maxChars == infoPtr->nCharsEntered)
            DATETIME_ApplySelectedField(infoPtr);
1131 1132 1133 1134 1135
    }
    return 0;
}


Duane Clark's avatar
Duane Clark committed
1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153
static LRESULT
DATETIME_VScroll (DATETIME_INFO *infoPtr, WORD wScroll)
{
    int fieldNum = infoPtr->select & DTHT_DATEFIELD;

    if ((SHORT)LOWORD(wScroll) != SB_THUMBPOSITION) return 0;
    if (!(infoPtr->haveFocus)) return 0;
    if ((fieldNum==0) && (infoPtr->select)) return 0;

    if (infoPtr->pendingUpdown >= 0) {
	DATETIME_IncreaseField (infoPtr, fieldNum, 1);
	DATETIME_SendDateTimeChangeNotify (infoPtr);
    }
    else {
	DATETIME_IncreaseField (infoPtr, fieldNum, -1);
	DATETIME_SendDateTimeChangeNotify (infoPtr);
    }

1154
    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
Duane Clark's avatar
Duane Clark committed
1155 1156 1157 1158 1159

    return 0;
}


1160
static LRESULT
1161
DATETIME_KillFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
1162
{
1163 1164
    TRACE("lost focus to %p\n", lostFocus);

1165
    if (infoPtr->haveFocus) {
1166
	DATETIME_SendSimpleNotify (infoPtr, NM_KILLFOCUS);
1167
	infoPtr->haveFocus = 0;
1168
        DATETIME_SetSelectedField (infoPtr, -1);
1169
    }
1170

1171
    InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
1172 1173 1174 1175 1176

    return 0;
}


1177
static LRESULT
1178
DATETIME_NCCreate (HWND hwnd, const CREATESTRUCTW *lpcs)
1179 1180 1181 1182 1183 1184 1185 1186 1187 1188
{
    DWORD dwExStyle = GetWindowLongW(hwnd, GWL_EXSTYLE);
    /* force control to have client edge */
    dwExStyle |= WS_EX_CLIENTEDGE;
    SetWindowLongW(hwnd, GWL_EXSTYLE, dwExStyle);

    return DefWindowProcW(hwnd, WM_NCCREATE, 0, (LPARAM)lpcs);
}


1189
static LRESULT
1190
DATETIME_SetFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
1191
{
1192 1193
    TRACE("got focus from %p\n", lostFocus);

1194
    /* if monthcal is open and it loses focus, close monthcal */
1195
    if (infoPtr->hMonthCal && (lostFocus == infoPtr->hMonthCal) &&
1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206
        IsWindowVisible(infoPtr->hMonthCal))
    {
        ShowWindow(infoPtr->hMonthCal, SW_HIDE);
        DATETIME_SendSimpleNotify(infoPtr, DTN_CLOSEUP);
        /* note: this get triggered even if monthcal loses focus to a dropdown
         * box click, which occurs without an intermediate WM_PAINT call
         */
        infoPtr->bDropdownEnabled = FALSE;
        return 0;
    }

1207 1208
    if (infoPtr->haveFocus == 0) {
	DATETIME_SendSimpleNotify (infoPtr, NM_SETFOCUS);
1209
	infoPtr->haveFocus = DTHT_GOTFOCUS;
1210
    }
1211

1212
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1213 1214 1215 1216 1217

    return 0;
}


1218
static BOOL
1219
DATETIME_SendDateTimeChangeNotify (const DATETIME_INFO *infoPtr)
1220
{
1221 1222 1223 1224 1225 1226
    NMDATETIMECHANGE dtdtc;

    dtdtc.nmhdr.hwndFrom = infoPtr->hwndSelf;
    dtdtc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
    dtdtc.nmhdr.code     = DTN_DATETIMECHANGE;

1227
    dtdtc.dwFlags = infoPtr->dateValid ? GDT_VALID : GDT_NONE;
1228

1229
    dtdtc.st = infoPtr->date;
1230
    return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
1231
                                dtdtc.nmhdr.idFrom, (LPARAM)&dtdtc);
1232 1233 1234
}


1235
static BOOL
1236
DATETIME_SendSimpleNotify (const DATETIME_INFO *infoPtr, UINT code)
1237 1238 1239
{
    NMHDR nmhdr;

1240 1241 1242
    TRACE("%x\n", code);
    nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1243 1244
    nmhdr.code     = code;

1245
    return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
1246
                                nmhdr.idFrom, (LPARAM)&nmhdr);
1247
}
1248

1249
static LRESULT
1250
DATETIME_Size (DATETIME_INFO *infoPtr, INT width, INT height)
1251
{
1252 1253 1254 1255
    /* set size */
    infoPtr->rcClient.bottom = height;
    infoPtr->rcClient.right = width;

1256
    TRACE("Height=%d, Width=%d\n", infoPtr->rcClient.bottom, infoPtr->rcClient.right);
1257

1258
    infoPtr->rcDraw = infoPtr->rcClient;
Duane Clark's avatar
Duane Clark committed
1259 1260
    
    if (infoPtr->dwStyle & DTS_UPDOWN) {
1261 1262 1263 1264
        SetWindowPos(infoPtr->hUpdown, NULL,
            infoPtr->rcClient.right-14, 0,
            15, infoPtr->rcClient.bottom - infoPtr->rcClient.top,
            SWP_NOACTIVATE | SWP_NOZORDER);
Duane Clark's avatar
Duane Clark committed
1265 1266 1267 1268 1269 1270 1271 1272 1273
    }
    else {
        /* set the size of the button that drops the calendar down */
        /* FIXME: account for style that allows button on left side */
        infoPtr->calbutton.top   = infoPtr->rcDraw.top;
        infoPtr->calbutton.bottom= infoPtr->rcDraw.bottom;
        infoPtr->calbutton.left  = infoPtr->rcDraw.right-15;
        infoPtr->calbutton.right = infoPtr->rcDraw.right;
    }
1274 1275 1276 1277 1278 1279 1280 1281 1282

    /* set enable/disable button size for show none style being enabled */
    /* FIXME: these dimensions are completely incorrect */
    infoPtr->checkbox.top = infoPtr->rcDraw.top;
    infoPtr->checkbox.bottom = infoPtr->rcDraw.bottom;
    infoPtr->checkbox.left = infoPtr->rcDraw.left;
    infoPtr->checkbox.right = infoPtr->rcDraw.left + 10;

    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1283

1284 1285
    return 0;
}
1286

1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303
static LRESULT
DATETIME_StyleChanging(DATETIME_INFO *infoPtr, WPARAM wStyleType, STYLESTRUCT *lpss)
{
    TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
          wStyleType, lpss->styleOld, lpss->styleNew);

    /* block DTS_SHOWNONE change */
    if ((lpss->styleNew ^ lpss->styleOld) & DTS_SHOWNONE)
    {
        if (lpss->styleOld & DTS_SHOWNONE)
            lpss->styleNew |= DTS_SHOWNONE;
        else
            lpss->styleNew &= ~DTS_SHOWNONE;
    }

    return 0;
}
1304

1305
static LRESULT 
1306
DATETIME_StyleChanged(DATETIME_INFO *infoPtr, WPARAM wStyleType, const STYLESTRUCT *lpss)
1307
{
1308
    TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
1309
          wStyleType, lpss->styleOld, lpss->styleNew);
1310

1311 1312 1313
    if (wStyleType != GWL_STYLE) return 0;
  
    infoPtr->dwStyle = lpss->styleNew;
1314

1315
    if ( !(lpss->styleOld & DTS_SHOWNONE) && (lpss->styleNew & DTS_SHOWNONE) ) {
1316
        infoPtr->hwndCheckbut = CreateWindowExW (0, WC_BUTTONW, 0, WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332
         					 2, 2, 13, 13, infoPtr->hwndSelf, 0, 
						(HINSTANCE)GetWindowLongPtrW (infoPtr->hwndSelf, GWLP_HINSTANCE), 0);
        SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, 1, 0);
    }
    if ( (lpss->styleOld & DTS_SHOWNONE) && !(lpss->styleNew & DTS_SHOWNONE) ) {
        DestroyWindow(infoPtr->hwndCheckbut);
        infoPtr->hwndCheckbut = 0;
    }
    if ( !(lpss->styleOld & DTS_UPDOWN) && (lpss->styleNew & DTS_UPDOWN) ) {
	infoPtr->hUpdown = CreateUpDownControl (WS_CHILD | WS_BORDER | WS_VISIBLE, 120, 1, 20, 20, 
						infoPtr->hwndSelf, 1, 0, 0, UD_MAXVAL, UD_MINVAL, 0);
    }
    if ( (lpss->styleOld & DTS_UPDOWN) && !(lpss->styleNew & DTS_UPDOWN) ) {
	DestroyWindow(infoPtr->hUpdown);
	infoPtr->hUpdown = 0;
    }
1333

1334 1335
    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
    return 0;
1336
}
1337

1338

1339 1340 1341 1342 1343 1344 1345 1346 1347
static LRESULT
DATETIME_SetFont (DATETIME_INFO *infoPtr, HFONT font, BOOL repaint)
{
    infoPtr->hFont = font;
    if (repaint) InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
    return 0;
}


1348
static LRESULT
1349
DATETIME_Create (HWND hwnd, const CREATESTRUCTW *lpcs)
1350
{
1351
    DATETIME_INFO *infoPtr = Alloc (sizeof(DATETIME_INFO));
1352 1353 1354 1355 1356 1357 1358 1359
    STYLESTRUCT ss = { 0, lpcs->style };

    if (!infoPtr) return -1;

    infoPtr->hwndSelf = hwnd;
    infoPtr->dwStyle = lpcs->style;

    infoPtr->nrFieldsAllocated = 32;
1360 1361 1362
    infoPtr->fieldspec = Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
    infoPtr->fieldRect = Alloc (infoPtr->nrFieldsAllocated * sizeof(RECT));
    infoPtr->buflen = Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
1363
    infoPtr->hwndNotify = lpcs->hwndParent;
1364
    infoPtr->select = -1; /* initially, nothing is selected */
1365
    infoPtr->bDropdownEnabled = TRUE;
1366 1367 1368 1369 1370

    DATETIME_StyleChanged(infoPtr, GWL_STYLE, &ss);
    DATETIME_SetFormatW (infoPtr, 0);

    /* create the monthcal control */
1371
    infoPtr->hMonthCal = CreateWindowExW (0, MONTHCAL_CLASSW, 0, WS_BORDER | WS_POPUP | WS_CLIPSIBLINGS,
1372 1373 1374
					  0, 0, 0, 0, infoPtr->hwndSelf, 0, 0, 0);

    /* initialize info structure */
1375
    GetLocalTime (&infoPtr->date);
1376 1377 1378 1379 1380 1381
    infoPtr->dateValid = TRUE;
    infoPtr->hFont = GetStockObject(DEFAULT_GUI_FONT);

    SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);

    return 0;
1382 1383 1384
}


1385

1386
static LRESULT
1387
DATETIME_Destroy (DATETIME_INFO *infoPtr)
1388
{
1389 1390 1391 1392
    if (infoPtr->hwndCheckbut)
	DestroyWindow(infoPtr->hwndCheckbut);
    if (infoPtr->hUpdown)
	DestroyWindow(infoPtr->hUpdown);
1393
    if (infoPtr->hMonthCal) 
1394
        DestroyWindow(infoPtr->hMonthCal);
1395
    SetWindowLongPtrW( infoPtr->hwndSelf, 0, 0 ); /* clear infoPtr */
1396 1397 1398
    Free (infoPtr->buflen);
    Free (infoPtr->fieldRect);
    Free (infoPtr->fieldspec);
1399
    Free (infoPtr);
1400 1401 1402 1403
    return 0;
}


1404
static INT
1405
DATETIME_GetText (const DATETIME_INFO *infoPtr, INT count, LPWSTR dst)
1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423
{
    WCHAR buf[80];
    int i;

    if (!dst || (count <= 0)) return 0;

    dst[0] = 0;
    for (i = 0; i < infoPtr->nrFields; i++)
    {
        DATETIME_ReturnTxt(infoPtr, i, buf, sizeof(buf)/sizeof(buf[0]));
        if ((strlenW(dst) + strlenW(buf)) < count)
            strcatW(dst, buf);
        else break;
    }
    return strlenW(dst);
}


1424
static LRESULT WINAPI
1425
DATETIME_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1426
{
1427
    DATETIME_INFO *infoPtr = ((DATETIME_INFO *)GetWindowLongPtrW (hwnd, 0));
1428
    LRESULT ret;
1429

1430
    TRACE ("%x, %lx, %lx\n", uMsg, wParam, lParam);
1431

1432
    if (!infoPtr && (uMsg != WM_CREATE) && (uMsg != WM_NCCREATE))
1433
	return DefWindowProcW( hwnd, uMsg, wParam, lParam );
1434

1435
    switch (uMsg) {
1436

1437
    case DTM_GETSYSTEMTIME:
1438
        return DATETIME_GetSystemTime (infoPtr, (SYSTEMTIME *) lParam);
1439 1440

    case DTM_SETSYSTEMTIME:
1441
	return DATETIME_SetSystemTime (infoPtr, wParam, (SYSTEMTIME *) lParam);
1442 1443

    case DTM_GETRANGE:
1444 1445
	ret = SendMessageW (infoPtr->hMonthCal, MCM_GETRANGE, wParam, lParam);
	return ret ? ret : 1; /* bug emulation */
1446 1447

    case DTM_SETRANGE:
1448
	return SendMessageW (infoPtr->hMonthCal, MCM_SETRANGE, wParam, lParam);
1449

1450
    case DTM_SETFORMATA:
1451
        return DATETIME_SetFormatA (infoPtr, (LPCSTR)lParam);
1452

1453
    case DTM_SETFORMATW:
1454
        return DATETIME_SetFormatW (infoPtr, (LPCWSTR)lParam);
1455

1456 1457 1458
    case DTM_GETMONTHCAL:
	return (LRESULT)infoPtr->hMonthCal;

1459
    case DTM_SETMCCOLOR:
1460
	return SendMessageW (infoPtr->hMonthCal, MCM_SETCOLOR, wParam, lParam);
1461 1462

    case DTM_GETMCCOLOR:
1463
        return SendMessageW (infoPtr->hMonthCal, MCM_GETCOLOR, wParam, 0);
1464 1465

    case DTM_SETMCFONT:
1466
	return SendMessageW (infoPtr->hMonthCal, WM_SETFONT, wParam, lParam);
1467 1468

    case DTM_GETMCFONT:
1469
	return SendMessageW (infoPtr->hMonthCal, WM_GETFONT, wParam, lParam);
1470 1471

    case WM_NOTIFY:
1472
	return DATETIME_Notify (infoPtr, (LPNMHDR)lParam);
1473

1474 1475 1476 1477 1478 1479
    case WM_ENABLE:
        return DATETIME_Enable (infoPtr, (BOOL)wParam);

    case WM_ERASEBKGND:
        return DATETIME_EraseBackground (infoPtr, (HDC)wParam);

1480 1481
    case WM_GETDLGCODE:
        return DLGC_WANTARROWS | DLGC_WANTCHARS;
1482

1483
    case WM_PRINTCLIENT:
1484
    case WM_PAINT:
1485
        return DATETIME_Paint (infoPtr, (HDC)wParam);
1486

1487
    case WM_KEYDOWN:
1488
        return DATETIME_KeyDown (infoPtr, wParam);
1489

1490
    case WM_CHAR:
1491
        return DATETIME_Char (infoPtr, wParam);
1492

1493
    case WM_KILLFOCUS:
1494
        return DATETIME_KillFocus (infoPtr, (HWND)wParam);
1495

1496 1497 1498
    case WM_NCCREATE:
        return DATETIME_NCCreate (hwnd, (LPCREATESTRUCTW)lParam);

1499
    case WM_SETFOCUS:
1500
        return DATETIME_SetFocus (infoPtr, (HWND)wParam);
1501

1502
    case WM_SIZE:
1503
        return DATETIME_Size (infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1504

1505
    case WM_LBUTTONDOWN:
1506
        return DATETIME_LButtonDown (infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1507

1508
    case WM_LBUTTONUP:
1509
        return DATETIME_LButtonUp (infoPtr);
1510

Duane Clark's avatar
Duane Clark committed
1511 1512 1513
    case WM_VSCROLL:
        return DATETIME_VScroll (infoPtr, (WORD)wParam);

1514
    case WM_CREATE:
1515
	return DATETIME_Create (hwnd, (LPCREATESTRUCTW)lParam);
1516

1517
    case WM_DESTROY:
1518
	return DATETIME_Destroy (infoPtr);
1519

Huw Davies's avatar
Huw Davies committed
1520
    case WM_COMMAND:
1521
        return DATETIME_Command (infoPtr, wParam, lParam);
Huw Davies's avatar
Huw Davies committed
1522

1523 1524 1525
    case WM_STYLECHANGING:
        return DATETIME_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);

1526 1527 1528
    case WM_STYLECHANGED:
        return DATETIME_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);

1529 1530 1531 1532 1533 1534
    case WM_SETFONT:
        return DATETIME_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);

    case WM_GETFONT:
        return (LRESULT) infoPtr->hFont;

1535 1536 1537
    case WM_GETTEXT:
        return (LRESULT) DATETIME_GetText(infoPtr, wParam, (LPWSTR)lParam);

1538 1539 1540
    case WM_SETTEXT:
        return CB_ERR;

1541
    default:
1542
	if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
1543
		ERR("unknown msg %04x wp=%08lx lp=%08lx\n",
1544
		     uMsg, wParam, lParam);
1545
	return DefWindowProcW (hwnd, uMsg, wParam, lParam);
1546 1547 1548 1549
    }
}


1550
void
1551
DATETIME_Register (void)
1552
{
1553
    WNDCLASSW wndClass;
1554

1555
    ZeroMemory (&wndClass, sizeof(WNDCLASSW));
1556
    wndClass.style         = CS_GLOBALCLASS;
1557
    wndClass.lpfnWndProc   = DATETIME_WindowProc;
1558 1559
    wndClass.cbClsExtra    = 0;
    wndClass.cbWndExtra    = sizeof(DATETIME_INFO *);
1560
    wndClass.hCursor       = LoadCursorW (0, (LPCWSTR)IDC_ARROW);
1561
    wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
1562
    wndClass.lpszClassName = DATETIMEPICK_CLASSW;
1563

1564
    RegisterClassW (&wndClass);
1565 1566 1567
}


1568
void
1569
DATETIME_Unregister (void)
1570
{
1571
    UnregisterClassW (DATETIMEPICK_CLASSW, NULL);
1572
}