datetime.c 42.5 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 39
 *    -- DTS_APPCANPARSE
 *    -- DTS_SHORTDATECENTURYFORMAT
 *    -- DTN_CLOSEUP
 *    -- DTN_FORMAT
 *    -- DTN_FORMATQUERY
 *    -- DTN_USERSTRING
 *    -- DTN_WMKEYDOWN
 *    -- FORMATCALLBACK
40 41
 */

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

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

58
WINE_DEFAULT_DEBUG_CHANNEL(datetime);
59

60 61
typedef struct
{
62 63 64 65
    HWND hwndSelf;
    HWND hMonthCal;
    HWND hwndNotify;
    HWND hUpdown;
66
    DWORD dwStyle;
67 68 69 70 71 72 73 74
    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 */
75
    int  bDropdownEnabled;
76 77 78 79 80 81 82 83 84 85
    int  select;
    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
86
    int pendingUpdown;
87 88 89
} DATETIME_INFO, *LPDATETIME_INFO;

/* in monthcal.c */
90
extern int MONTHCAL_MonthLength(int month, int year);
91 92 93

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

99
#define DT_END_FORMAT      0
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
#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
120 121
#define INVALIDFULLYEAR 0x73      /* FIXME - yyy is not valid - we'll treat it as yyyy */
#define FULLYEAR        0x74
122 123 124 125 126 127 128 129 130 131
#define FORMATCALLBACK  0x81      /* -> maximum of 0x80 callbacks possible */
#define FORMATCALLMASK  0x80
#define DT_STRING 	0x0100

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

#define DTHT_NONE     0
#define DTHT_CHECKBOX 0x200	/* these should end at '00' , to make */
#define DTHT_MCPOPUP  0x300     /* & DTHT_DATEFIELD 0 when DATETIME_KeyDown */
#define DTHT_GOTFOCUS 0x400     /* tests for date-fields */
132

133 134
static BOOL DATETIME_SendSimpleNotify (const DATETIME_INFO *infoPtr, UINT code);
static BOOL DATETIME_SendDateTimeChangeNotify (const DATETIME_INFO *infoPtr);
135
extern void MONTHCAL_CopyTime(const SYSTEMTIME *from, SYSTEMTIME *to);
136 137
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};
138

139

140
static DWORD
141
DATETIME_GetSystemTime (const DATETIME_INFO *infoPtr, SYSTEMTIME *lprgSysTimeArray)
142
{
143
    if (!lprgSysTimeArray) return GDT_NONE;
144

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

149
    MONTHCAL_CopyTime (&infoPtr->date, lprgSysTimeArray);
150

151
    return GDT_VALID;
152 153 154
}


155
static BOOL
156
DATETIME_SetSystemTime (DATETIME_INFO *infoPtr, DWORD flag, const SYSTEMTIME *lprgSysTimeArray)
157
{
158
    if (!lprgSysTimeArray) return 0;
159

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

    if (flag == GDT_VALID) {
165 166 167 168 169 170 171 172 173 174 175
      if (lprgSysTimeArray->wYear < 1601 || lprgSysTimeArray->wYear > 30827 ||
          lprgSysTimeArray->wMonth < 1 || lprgSysTimeArray->wMonth > 12 ||
          lprgSysTimeArray->wDayOfWeek > 6 ||
          lprgSysTimeArray->wDay < 1 || lprgSysTimeArray->wDay > 31 ||
          lprgSysTimeArray->wHour > 23 ||
          lprgSysTimeArray->wMinute > 59 ||
          lprgSysTimeArray->wSecond > 59 ||
          lprgSysTimeArray->wMilliseconds > 999
          )
        return 0;

176 177 178 179
        infoPtr->dateValid = TRUE;
        MONTHCAL_CopyTime (lprgSysTimeArray, &infoPtr->date);
        SendMessageW (infoPtr->hMonthCal, MCM_SETCURSEL, 0, (LPARAM)(&infoPtr->date));
        SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_CHECKED, 0);
180
    } else if ((infoPtr->dwStyle & DTS_SHOWNONE) && (flag == GDT_NONE)) {
181 182 183
        infoPtr->dateValid = FALSE;
        SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_UNCHECKED, 0);
    }
184 185
    else
        return 0;
186

187 188
    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
    return TRUE;
189 190
}

191

192 193 194 195 196 197 198 199 200 201
/***
 * 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'.
 *
202
 */
203
static void
204
DATETIME_UseFormat (DATETIME_INFO *infoPtr, LPCWSTR formattxt)
205
{
206 207
    unsigned int i;
    int j, k, len;
208
    BOOL inside_literal = FALSE; /* inside '...' */
209 210 211 212 213 214 215 216 217
    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]);
218 219 220 221 222 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
	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;
        }
249 250 251 252 253 254 255 256 257 258 259 260 261 262

	/* 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]++;
263 264
	}   /* if j=len */

265 266
	if (*nrFields == infoPtr->nrFieldsAllocated) {
	    FIXME ("out of memory; should reallocate. crash ahead.\n");
267
	}
268
    } /* for i */
269

270
    TRACE("\n");
271

272
    if (infoPtr->fieldspec[*nrFields] != 0) (*nrFields)++;
273 274
}

275

276 277
static BOOL
DATETIME_SetFormatW (DATETIME_INFO *infoPtr, LPCWSTR lpszFormat)
278
{
279 280 281
    if (!lpszFormat) {
	WCHAR format_buf[80];
	DWORD format_item;
282

283
	if (infoPtr->dwStyle & DTS_LONGDATEFORMAT)
284
	    format_item = LOCALE_SLONGDATE;
Duane Clark's avatar
Duane Clark committed
285
	else if ((infoPtr->dwStyle & DTS_TIMEFORMAT) == DTS_TIMEFORMAT)
286
	    format_item = LOCALE_STIMEFORMAT;
287
        else /* DTS_SHORTDATEFORMAT */
288 289 290 291
	    format_item = LOCALE_SSHORTDATE;
	GetLocaleInfoW( GetSystemDefaultLCID(), format_item, format_buf, sizeof(format_buf)/sizeof(format_buf[0]));
	lpszFormat = format_buf;
    }
292

293
    DATETIME_UseFormat (infoPtr, lpszFormat);
294
    InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
295

296
    return 1;
297
}
298

299

300 301
static BOOL
DATETIME_SetFormatA (DATETIME_INFO *infoPtr, LPCSTR lpszFormat)
302
{
303 304 305 306 307 308 309
    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);
310
	return retval;
311 312 313
    }
    else
	return DATETIME_SetFormatW (infoPtr, 0);
314 315 316

}

317

318
static void
319
DATETIME_ReturnTxt (const DATETIME_INFO *infoPtr, int count, LPWSTR result, int resultSize)
320
{
321 322 323 324 325 326 327 328 329 330
    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) {
331 332
	WARN ("buffer overrun, have %d want %d\n", infoPtr->nrFields, count);
	return;
333
    }
334

335
    if (!infoPtr->fieldspec) return;
336

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

341 342
        if (txtlen > resultSize)
            txtlen = resultSize - 1;
343 344 345
	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));
346
	return;
347
    }
348

349

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

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

Duane Clark's avatar
Duane Clark committed
447 448 449 450 451 452 453 454 455 456 457 458 459
/* Offsets of days in the week to the weekday of january 1 in a leap year. */
static const int DayOfWeekTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};

/* returns the day in the week(0 == sunday, 6 == saturday) */
/* day(1 == 1st, 2 == 2nd... etc), year is the  year value */
static int DATETIME_CalculateDayOfWeek(DWORD day, DWORD month, DWORD year)
{
    year-=(month < 3);
    
    return((year + year/4 - year/100 + year/400 +
         DayOfWeekTable[month-1] + day ) % 7);
}

460
static int wrap(int val, int delta, int minVal, int maxVal)
461
{
462 463 464 465
    val += delta;
    if (delta == INT_MIN || val < minVal) return maxVal;
    if (delta == INT_MAX || val > maxVal) return minVal;
    return val;
466 467
}

468
static void
469
DATETIME_IncreaseField (DATETIME_INFO *infoPtr, int number, int delta)
470
{
471
    SYSTEMTIME *date = &infoPtr->date;
472

473 474
    TRACE ("%d\n", number);
    if ((number > infoPtr->nrFields) || (number < 0)) return;
475

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

478
    switch (infoPtr->fieldspec[number]) {
479 480 481
	case ONEDIGITYEAR:
	case TWODIGITYEAR:
	case FULLYEAR:
482
	    date->wYear = wrap(date->wYear, delta, 1752, 9999);
Duane Clark's avatar
Duane Clark committed
483
	    date->wDayOfWeek = DATETIME_CalculateDayOfWeek(date->wDay,date->wMonth,date->wYear);
484
	    break;
485 486 487 488
	case ONEDIGITMONTH:
	case TWODIGITMONTH:
	case THREECHARMONTH:
	case FULLMONTH:
489
	    date->wMonth = wrap(date->wMonth, delta, 1, 12);
Duane Clark's avatar
Duane Clark committed
490
	    date->wDayOfWeek = DATETIME_CalculateDayOfWeek(date->wDay,date->wMonth,date->wYear);
491 492
	    delta = 0;
	    /* fall through */
493 494 495
	case ONEDIGITDAY:
	case TWODIGITDAY:
	case THREECHARDAY:
496
	case FULLDAY:
497
	    date->wDay = wrap(date->wDay, delta, 1, MONTHCAL_MonthLength(date->wMonth, date->wYear));
Duane Clark's avatar
Duane Clark committed
498
	    date->wDayOfWeek = DATETIME_CalculateDayOfWeek(date->wDay,date->wMonth,date->wYear);
499 500 501 502 503
	    break;
	case ONELETTERAMPM:
	case TWOLETTERAMPM:
	    delta *= 12;
	    /* fall through */
504 505 506 507
	case ONEDIGIT12HOUR:
	case TWODIGIT12HOUR:
	case ONEDIGIT24HOUR:
	case TWODIGIT24HOUR:
508 509
	    date->wHour = wrap(date->wHour, delta, 0, 23);
	    break;
510 511
	case ONEDIGITMINUTE:
	case TWODIGITMINUTE:
512 513 514 515 516 517
	    date->wMinute = wrap(date->wMinute, delta, 0, 59);
	    break;
	case ONEDIGITSECOND:
	case TWODIGITSECOND:
	    date->wSecond = wrap(date->wSecond, delta, 0, 59);
	    break;
518
	case FORMATCALLBACK:
519 520 521
	    FIXME ("Not implemented\n");
	    break;
    }
522

523 524 525 526 527 528 529 530 531 532 533 534 535
    /* 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;
    }
536 537 538
}


Duane Clark's avatar
Duane Clark committed
539
static void
540
DATETIME_ReturnFieldWidth (const DATETIME_INFO *infoPtr, HDC hdc, int count, SHORT *fieldWidthPtr)
Duane Clark's avatar
Duane Clark committed
541 542 543 544 545 546 547 548 549 550 551 552 553
{
    /* 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 };
    static const WCHAR fld_day[] = { 'W', 'e', 'd', 'n', 'e', 's', 'd', 'a', 'y', 0 };
    static const WCHAR fld_day3[] = { 'W', 'e', 'd', 0 };
    static const WCHAR fld_mon[] = { 'S', 'e', 'p', 't', 'e', 'm', 'b', 'e', 'r', 0 };
    static const WCHAR fld_mon3[] = { 'D', 'e', 'c', 0 };
    int spec;
554 555
    WCHAR buffer[80];
    LPCWSTR bufptr;
Duane Clark's avatar
Duane Clark committed
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 585 586 587 588 589 590 591 592
    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:
593
	        bufptr = fld_d2W;
Duane Clark's avatar
Duane Clark committed
594 595 596
	        break;
            case INVALIDFULLYEAR:
	    case FULLYEAR:
597
	        bufptr = fld_d4W;
Duane Clark's avatar
Duane Clark committed
598 599
	        break;
	    case THREECHARDAY:
600
	        bufptr = fld_day3;
Duane Clark's avatar
Duane Clark committed
601 602
	        break;
	    case FULLDAY:
603
	        bufptr = fld_day;
Duane Clark's avatar
Duane Clark committed
604 605
	        break;
	    case THREECHARMONTH:
606
	        bufptr = fld_mon3;
Duane Clark's avatar
Duane Clark committed
607 608
	        break;
	    case FULLMONTH:
609
	        bufptr = fld_mon;
Duane Clark's avatar
Duane Clark committed
610 611
	        break;
	    case ONELETTERAMPM:
612
	        bufptr = fld_am1;
Duane Clark's avatar
Duane Clark committed
613 614
	        break;
	    case TWOLETTERAMPM:
615
	        bufptr = fld_am2;
Duane Clark's avatar
Duane Clark committed
616 617
	        break;
	    default:
618
	        bufptr = fld_d1W;
Duane Clark's avatar
Duane Clark committed
619 620 621 622 623 624 625
	        break;
        }
    }
    GetTextExtentPoint32W (hdc, bufptr, strlenW(bufptr), &size);
    *fieldWidthPtr = size.cx;
}

626 627
static void 
DATETIME_Refresh (DATETIME_INFO *infoPtr, HDC hdc)
628
{
629 630 631 632 633 634
    int i,prevright;
    RECT *field;
    RECT *rcDraw = &infoPtr->rcDraw;
    RECT *calbutton = &infoPtr->calbutton;
    RECT *checkbox = &infoPtr->checkbox;
    SIZE size;
635
    COLORREF oldTextColor;
636
    SHORT fieldWidth = 0;
637 638 639 640 641 642

    /* draw control edge */
    TRACE("\n");

    if (infoPtr->dateValid) {
        HFONT oldFont = SelectObject (hdc, infoPtr->hFont);
643
        INT oldBkMode = SetBkMode (hdc, TRANSPARENT);
644 645 646 647 648 649 650 651 652 653 654
        WCHAR txt[80];

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

        prevright = checkbox->right = ((infoPtr->dwStyle & DTS_SHOWNONE) ? 18 : 2);

        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
655
            DATETIME_ReturnFieldWidth (infoPtr, hdc, i, &fieldWidth);
656 657
            field = &infoPtr->fieldRect[i];
            field->left  = prevright;
Duane Clark's avatar
Duane Clark committed
658
            field->right = prevright + fieldWidth;
659 660 661 662
            field->top   = rcDraw->top;
            field->bottom = rcDraw->bottom;
            prevright = field->right;

663 664 665 666 667
            if (infoPtr->dwStyle & WS_DISABLED)
                oldTextColor = SetTextColor (hdc, comctl32_color.clrGrayText);
            else if ((infoPtr->haveFocus) && (i == infoPtr->select)) {
                /* fill if focussed */
                HBRUSH hbr = CreateSolidBrush (comctl32_color.clrActiveCaption);
668 669
                FillRect(hdc, field, hbr);
                DeleteObject (hbr);
670 671 672 673 674 675 676 677
                oldTextColor = SetTextColor (hdc, comctl32_color.clrWindow);
            }
            else
                oldTextColor = SetTextColor (hdc, comctl32_color.clrWindowText);

            /* draw the date text using the colour set above */
            DrawTextW (hdc, txt, strlenW(txt), field, DT_RIGHT | DT_VCENTER | DT_SINGLELINE);
            SetTextColor (hdc, oldTextColor);
678
        }
679
        SetBkMode (hdc, oldBkMode);
680
        SelectObject (hdc, oldFont);
681
    }
682

683 684 685 686 687
    if (!(infoPtr->dwStyle & DTS_UPDOWN)) {
        DrawFrameControl(hdc, calbutton, DFC_SCROLL,
                         DFCS_SCROLLDOWN | (infoPtr->bCalDepressed ? DFCS_PUSHED : 0) |
                         (infoPtr->dwStyle & WS_DISABLED ? DFCS_INACTIVE : 0) );
    }
688 689
}

690

691
static INT
692
DATETIME_HitTest (const DATETIME_INFO *infoPtr, POINT pt)
693
{
694
    int i;
695

696
    TRACE ("%d, %d\n", pt.x, pt.y);
697

698 699
    if (PtInRect (&infoPtr->calbutton, pt)) return DTHT_MCPOPUP;
    if (PtInRect (&infoPtr->checkbox, pt)) return DTHT_CHECKBOX;
700

701 702
    for (i=0; i < infoPtr->nrFields; i++) {
        if (PtInRect (&infoPtr->fieldRect[i], pt)) return i;
703
    }
704

705
    return DTHT_NONE;
706 707
}

708

709
static LRESULT
710
DATETIME_LButtonDown (DATETIME_INFO *infoPtr, WORD wKey, INT x, INT y)
711
{
712 713 714 715 716 717 718
    POINT pt;
    int old, new;

    pt.x = x;
    pt.y = y;
    old = infoPtr->select;
    new = DATETIME_HitTest (infoPtr, pt);
719 720 721 722

    /* FIXME: might be conditions where we don't want to update infoPtr->select */
    infoPtr->select = new;

723
    SetFocus(infoPtr->hwndSelf);
724 725

    if (infoPtr->select == DTHT_MCPOPUP) {
726 727 728
        RECT rcMonthCal;
        SendMessageW(infoPtr->hMonthCal, MCM_GETMINREQRECT, 0, (LPARAM)&rcMonthCal);

729 730 731 732 733 734
        /* 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)
735 736
            infoPtr->monthcal_pos.x = infoPtr->calbutton.left - 
                (rcMonthCal.right - rcMonthCal.left);
737
        else
738 739
            /* FIXME: this should be after the area reserved for the checkbox */
            infoPtr->monthcal_pos.x = infoPtr->rcDraw.left;
740 741 742

        infoPtr->monthcal_pos.y = infoPtr->rcClient.bottom;
        ClientToScreen (infoPtr->hwndSelf, &(infoPtr->monthcal_pos));
743 744 745
        SetWindowPos(infoPtr->hMonthCal, 0, infoPtr->monthcal_pos.x,
            infoPtr->monthcal_pos.y, rcMonthCal.right - rcMonthCal.left,
            rcMonthCal.bottom - rcMonthCal.top, 0);
746 747 748 749

        if(IsWindowVisible(infoPtr->hMonthCal)) {
            ShowWindow(infoPtr->hMonthCal, SW_HIDE);
        } else {
750
            const SYSTEMTIME *lprgSysTimeArray = &infoPtr->date;
751 752 753
            TRACE("update calendar %04d/%02d/%02d\n", 
            lprgSysTimeArray->wYear, lprgSysTimeArray->wMonth, lprgSysTimeArray->wDay);
            SendMessageW(infoPtr->hMonthCal, MCM_SETCURSEL, 0, (LPARAM)(&infoPtr->date));
754 755 756 757

            if (infoPtr->bDropdownEnabled)
                ShowWindow(infoPtr->hMonthCal, SW_SHOW);
            infoPtr->bDropdownEnabled = TRUE;
758 759 760 761 762
        }

        TRACE ("dt:%p mc:%p mc parent:%p, desktop:%p\n",
               infoPtr->hwndSelf, infoPtr->hMonthCal, infoPtr->hwndNotify, GetDesktopWindow ());
        DATETIME_SendSimpleNotify (infoPtr, DTN_DROPDOWN);
763
    }
764

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

767
    return 0;
768 769 770 771
}


static LRESULT
772
DATETIME_LButtonUp (DATETIME_INFO *infoPtr, WORD wKey)
773
{
774
    if(infoPtr->bCalDepressed) {
775 776 777
        infoPtr->bCalDepressed = FALSE;
        InvalidateRect(infoPtr->hwndSelf, &(infoPtr->calbutton), TRUE);
    }
778

779
    return 0;
780 781 782 783
}


static LRESULT
784
DATETIME_Paint (DATETIME_INFO *infoPtr, HDC hdc)
785
{
786 787 788 789 790 791 792 793
    if (!hdc) {
	PAINTSTRUCT ps;
        hdc = BeginPaint (infoPtr->hwndSelf, &ps);
        DATETIME_Refresh (infoPtr, hdc);
        EndPaint (infoPtr->hwndSelf, &ps);
    } else {
        DATETIME_Refresh (infoPtr, hdc);
    }
794 795 796 797

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

798 799 800
    return 0;
}

801

Huw Davies's avatar
Huw Davies committed
802
static LRESULT
803
DATETIME_Button_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
Huw Davies's avatar
Huw Davies committed
804
{
805 806 807 808
    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
809
    }
810
    return 0;
Huw Davies's avatar
Huw Davies committed
811 812 813 814 815
}
          
        
        
static LRESULT
816
DATETIME_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
Huw Davies's avatar
Huw Davies committed
817 818 819
{
    TRACE("hwndbutton = %p\n", infoPtr->hwndCheckbut);
    if(infoPtr->hwndCheckbut == (HWND)lParam)
820
        return DATETIME_Button_Command(infoPtr, wParam, lParam);
Huw Davies's avatar
Huw Davies committed
821 822 823
    return 0;
}

824

825 826 827 828 829 830 831 832
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;
833 834 835

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

836 837 838 839 840
    return 0;
}


static LRESULT
841
DATETIME_EraseBackground (const DATETIME_INFO *infoPtr, HDC hdc)
842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866
{
    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;
}


867
static LRESULT
868
DATETIME_Notify (DATETIME_INFO *infoPtr, int idCtrl, LPNMHDR lpnmh)
869
{
870 871 872 873 874 875 876
    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
877 878
        TRACE("got from calendar %04d/%02d/%02d day of week %d\n", 
        infoPtr->date.wYear, infoPtr->date.wMonth, infoPtr->date.wDay, infoPtr->date.wDayOfWeek);
879 880 881 882
        SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_CHECKED, 0);
        InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
        DATETIME_SendDateTimeChangeNotify (infoPtr);
    }
Duane Clark's avatar
Duane Clark committed
883 884 885 886 887
    if ((lpnmh->hwndFrom == infoPtr->hUpdown) && (lpnmh->code == UDN_DELTAPOS)) {
        LPNMUPDOWN lpnmud = (LPNMUPDOWN)lpnmh;
        TRACE("Delta pos %d\n", lpnmud->iDelta);
        infoPtr->pendingUpdown = lpnmud->iDelta;
    }
888
    return 0;
889 890
}

891

892
static LRESULT
893
DATETIME_KeyDown (DATETIME_INFO *infoPtr, DWORD vkCode, LPARAM flags)
894
{
895 896
    int fieldNum = infoPtr->select & DTHT_DATEFIELD;
    int wrap = 0;
897

898 899
    if (!(infoPtr->haveFocus)) return 0;
    if ((fieldNum==0) && (infoPtr->select)) return 0;
900

901
    if (infoPtr->select & FORMATCALLMASK) {
902
	FIXME ("Callbacks not implemented yet\n");
903
    }
904

905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981
    if (vkCode >= '0' && vkCode <= '9') {
        /* this is a somewhat simplified version of what Windows does */
	SYSTEMTIME *date = &infoPtr->date;
	switch (infoPtr->fieldspec[fieldNum]) {
	    case ONEDIGITYEAR:
	    case TWODIGITYEAR:
	        date->wYear = date->wYear - (date->wYear%100) +
	                (date->wYear%10)*10 + (vkCode-'0');
	        date->wDayOfWeek = DATETIME_CalculateDayOfWeek(
	                date->wDay,date->wMonth,date->wYear);
	        DATETIME_SendDateTimeChangeNotify (infoPtr);
	        break;
            case INVALIDFULLYEAR:
	    case FULLYEAR:
	        date->wYear = (date->wYear%1000)*10 + (vkCode-'0');
	        date->wDayOfWeek = DATETIME_CalculateDayOfWeek(
	                date->wDay,date->wMonth,date->wYear);
	        DATETIME_SendDateTimeChangeNotify (infoPtr);
	        break;
	    case ONEDIGITMONTH:
	    case TWODIGITMONTH:
	        if ((date->wMonth%10) > 1 || (vkCode-'0') > 2)
	            date->wMonth = vkCode-'0';
	        else
	            date->wMonth = (date->wMonth%10)*10+vkCode-'0';
	        date->wDayOfWeek = DATETIME_CalculateDayOfWeek(
	                date->wDay,date->wMonth,date->wYear);
	        DATETIME_SendDateTimeChangeNotify (infoPtr);
	        break;
	    case ONEDIGITDAY:
	    case TWODIGITDAY:
	        /* probably better checking here would help */
	        if ((date->wDay%10) >= 3 && (vkCode-'0') > 1)
	            date->wDay = vkCode-'0';
	        else
	            date->wDay = (date->wDay%10)*10+vkCode-'0';
	        date->wDayOfWeek = DATETIME_CalculateDayOfWeek(
	                date->wDay,date->wMonth,date->wYear);
	        DATETIME_SendDateTimeChangeNotify (infoPtr);
	        break;
	    case ONEDIGIT12HOUR:
	    case TWODIGIT12HOUR:
	        if ((date->wHour%10) > 1 || (vkCode-'0') > 2)
	            date->wHour = vkCode-'0';
	        else
	            date->wHour = (date->wHour%10)*10+vkCode-'0';
	        DATETIME_SendDateTimeChangeNotify (infoPtr);
	        break;
	    case ONEDIGIT24HOUR:
	    case TWODIGIT24HOUR:
	        if ((date->wHour%10) > 2)
	            date->wHour = vkCode-'0';
	        else if ((date->wHour%10) == 2 && (vkCode-'0') > 3)
	            date->wHour = vkCode-'0';
	        else
	            date->wHour = (date->wHour%10)*10+vkCode-'0';
	        DATETIME_SendDateTimeChangeNotify (infoPtr);
	        break;
	    case ONEDIGITMINUTE:
	    case TWODIGITMINUTE:
	        if ((date->wMinute%10) > 5)
	            date->wMinute = vkCode-'0';
	        else
	            date->wMinute = (date->wMinute%10)*10+vkCode-'0';
	        DATETIME_SendDateTimeChangeNotify (infoPtr);
	        break;
	    case ONEDIGITSECOND:
	    case TWODIGITSECOND:
	        if ((date->wSecond%10) > 5)
	            date->wSecond = vkCode-'0';
	        else
	            date->wSecond = (date->wSecond%10)*10+vkCode-'0';
	        DATETIME_SendDateTimeChangeNotify (infoPtr);
	        break;
	}
    }
    
982
    switch (vkCode) {
983
	case VK_ADD:
984
    	case VK_UP:
985 986 987
	    DATETIME_IncreaseField (infoPtr, fieldNum, 1);
	    DATETIME_SendDateTimeChangeNotify (infoPtr);
	    break;
988 989
	case VK_SUBTRACT:
	case VK_DOWN:
990 991 992
	    DATETIME_IncreaseField (infoPtr, fieldNum, -1);
	    DATETIME_SendDateTimeChangeNotify (infoPtr);
	    break;
993
	case VK_HOME:
994 995 996
	    DATETIME_IncreaseField (infoPtr, fieldNum, INT_MIN);
	    DATETIME_SendDateTimeChangeNotify (infoPtr);
	    break;
997
	case VK_END:
998 999 1000
	    DATETIME_IncreaseField (infoPtr, fieldNum, INT_MAX);
	    DATETIME_SendDateTimeChangeNotify (infoPtr);
	    break;
1001
	case VK_LEFT:
1002 1003 1004 1005 1006 1007
	    do {
		if (infoPtr->select == 0) {
		    infoPtr->select = infoPtr->nrFields - 1;
		    wrap++;
		} else {
		    infoPtr->select--;
1008
		}
1009 1010
	    } while ((infoPtr->fieldspec[infoPtr->select] & DT_STRING) && (wrap<2));
	    break;
1011
	case VK_RIGHT:
1012 1013 1014 1015 1016 1017 1018 1019 1020
	    do {
		infoPtr->select++;
		if (infoPtr->select==infoPtr->nrFields) {
		    infoPtr->select = 0;
		    wrap++;
		}
	    } while ((infoPtr->fieldspec[infoPtr->select] & DT_STRING) && (wrap<2));
	    break;
    }
1021

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

1024
    return 0;
1025 1026 1027
}


Duane Clark's avatar
Duane Clark committed
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
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);
    }

1046
    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
Duane Clark's avatar
Duane Clark committed
1047 1048 1049 1050 1051

    return 0;
}


1052
static LRESULT
1053
DATETIME_KillFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
1054
{
1055 1056
    TRACE("lost focus to %p\n", lostFocus);

1057
    if (infoPtr->haveFocus) {
1058
	DATETIME_SendSimpleNotify (infoPtr, NM_KILLFOCUS);
1059 1060
	infoPtr->haveFocus = 0;
    }
1061

1062
    InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
1063 1064 1065 1066 1067

    return 0;
}


1068
static LRESULT
1069
DATETIME_NCCreate (HWND hwnd, const CREATESTRUCTW *lpcs)
1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
{
    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);
}


1080
static LRESULT
1081
DATETIME_SetFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
1082
{
1083 1084
    TRACE("got focus from %p\n", lostFocus);

1085
    /* if monthcal is open and it loses focus, close monthcal */
1086
    if (infoPtr->hMonthCal && (lostFocus == infoPtr->hMonthCal) &&
1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097
        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;
    }

1098 1099
    if (infoPtr->haveFocus == 0) {
	DATETIME_SendSimpleNotify (infoPtr, NM_SETFOCUS);
1100
	infoPtr->haveFocus = DTHT_GOTFOCUS;
1101
    }
1102

1103
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1104 1105 1106 1107 1108

    return 0;
}


1109
static BOOL
1110
DATETIME_SendDateTimeChangeNotify (const DATETIME_INFO *infoPtr)
1111
{
1112 1113 1114 1115 1116 1117
    NMDATETIMECHANGE dtdtc;

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

1118
    dtdtc.dwFlags = (infoPtr->dwStyle & DTS_SHOWNONE) ? GDT_NONE : GDT_VALID;
1119 1120 1121

    MONTHCAL_CopyTime (&infoPtr->date, &dtdtc.st);
    return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
1122
                                dtdtc.nmhdr.idFrom, (LPARAM)&dtdtc);
1123 1124 1125
}


1126
static BOOL
1127
DATETIME_SendSimpleNotify (const DATETIME_INFO *infoPtr, UINT code)
1128 1129 1130
{
    NMHDR nmhdr;

1131 1132 1133
    TRACE("%x\n", code);
    nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1134 1135
    nmhdr.code     = code;

1136
    return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
1137
                                nmhdr.idFrom, (LPARAM)&nmhdr);
1138
}
1139

1140
static LRESULT
1141
DATETIME_Size (DATETIME_INFO *infoPtr, WORD flags, INT width, INT height)
1142
{
1143 1144 1145 1146
    /* set size */
    infoPtr->rcClient.bottom = height;
    infoPtr->rcClient.right = width;

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

1149
    infoPtr->rcDraw = infoPtr->rcClient;
Duane Clark's avatar
Duane Clark committed
1150 1151
    
    if (infoPtr->dwStyle & DTS_UPDOWN) {
1152 1153 1154 1155
        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
1156 1157 1158 1159 1160 1161 1162 1163 1164
    }
    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;
    }
1165 1166 1167 1168 1169 1170 1171 1172 1173

    /* 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);
1174

1175 1176
    return 0;
}
1177 1178


1179
static LRESULT 
1180
DATETIME_StyleChanged(DATETIME_INFO *infoPtr, WPARAM wStyleType, const STYLESTRUCT *lpss)
1181 1182
{
    static const WCHAR buttonW[] = { 'b', 'u', 't', 't', 'o', 'n', 0 };
1183

1184
    TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
1185
          wStyleType, lpss->styleOld, lpss->styleNew);
1186

1187 1188 1189
    if (wStyleType != GWL_STYLE) return 0;
  
    infoPtr->dwStyle = lpss->styleNew;
1190

1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208
    if ( !(lpss->styleOld & DTS_SHOWNONE) && (lpss->styleNew & DTS_SHOWNONE) ) {
        infoPtr->hwndCheckbut = CreateWindowExW (0, buttonW, 0, WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
         					 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;
    }
1209

1210 1211
    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
    return 0;
1212
}
1213

1214

1215 1216 1217 1218 1219 1220 1221 1222 1223
static LRESULT
DATETIME_SetFont (DATETIME_INFO *infoPtr, HFONT font, BOOL repaint)
{
    infoPtr->hFont = font;
    if (repaint) InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
    return 0;
}


1224
static LRESULT
1225
DATETIME_Create (HWND hwnd, const CREATESTRUCTW *lpcs)
1226
{
1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240
    static const WCHAR SysMonthCal32W[] = { 'S', 'y', 's', 'M', 'o', 'n', 't', 'h', 'C', 'a', 'l', '3', '2', 0 };
    DATETIME_INFO *infoPtr = (DATETIME_INFO *)Alloc (sizeof(DATETIME_INFO));
    STYLESTRUCT ss = { 0, lpcs->style };

    if (!infoPtr) return -1;

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

    infoPtr->nrFieldsAllocated = 32;
    infoPtr->fieldspec = (int *) Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
    infoPtr->fieldRect = (RECT *) Alloc (infoPtr->nrFieldsAllocated * sizeof(RECT));
    infoPtr->buflen = (int *) Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
    infoPtr->hwndNotify = lpcs->hwndParent;
1241
    infoPtr->select = -1; /* initially, nothing is selected */
1242
    infoPtr->bDropdownEnabled = TRUE;
1243 1244 1245 1246 1247 1248 1249 1250 1251

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

    /* create the monthcal control */
    infoPtr->hMonthCal = CreateWindowExW (0, SysMonthCal32W, 0, WS_BORDER | WS_POPUP | WS_CLIPSIBLINGS, 
					  0, 0, 0, 0, infoPtr->hwndSelf, 0, 0, 0);

    /* initialize info structure */
1252
    GetLocalTime (&infoPtr->date);
1253 1254 1255 1256 1257 1258
    infoPtr->dateValid = TRUE;
    infoPtr->hFont = GetStockObject(DEFAULT_GUI_FONT);

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

    return 0;
1259 1260 1261
}


1262

1263
static LRESULT
1264
DATETIME_Destroy (DATETIME_INFO *infoPtr)
1265
{
1266 1267 1268 1269
    if (infoPtr->hwndCheckbut)
	DestroyWindow(infoPtr->hwndCheckbut);
    if (infoPtr->hUpdown)
	DestroyWindow(infoPtr->hUpdown);
1270
    if (infoPtr->hMonthCal) 
1271
        DestroyWindow(infoPtr->hMonthCal);
1272
    SetWindowLongPtrW( infoPtr->hwndSelf, 0, 0 ); /* clear infoPtr */
1273
    Free (infoPtr);
1274 1275 1276 1277
    return 0;
}


1278
static LRESULT WINAPI
1279
DATETIME_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1280
{
1281
    DATETIME_INFO *infoPtr = ((DATETIME_INFO *)GetWindowLongPtrW (hwnd, 0));
1282
    LRESULT ret;
1283

1284
    TRACE ("%x, %lx, %lx\n", uMsg, wParam, lParam);
1285

1286
    if (!infoPtr && (uMsg != WM_CREATE) && (uMsg != WM_NCCREATE))
1287
	return DefWindowProcW( hwnd, uMsg, wParam, lParam );
1288

1289
    switch (uMsg) {
1290

1291
    case DTM_GETSYSTEMTIME:
1292
        return DATETIME_GetSystemTime (infoPtr, (SYSTEMTIME *) lParam);
1293 1294

    case DTM_SETSYSTEMTIME:
1295
	return DATETIME_SetSystemTime (infoPtr, wParam, (SYSTEMTIME *) lParam);
1296 1297

    case DTM_GETRANGE:
1298 1299
	ret = SendMessageW (infoPtr->hMonthCal, MCM_GETRANGE, wParam, lParam);
	return ret ? ret : 1; /* bug emulation */
1300 1301

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

1304
    case DTM_SETFORMATA:
1305
        return DATETIME_SetFormatA (infoPtr, (LPCSTR)lParam);
1306

1307
    case DTM_SETFORMATW:
1308
        return DATETIME_SetFormatW (infoPtr, (LPCWSTR)lParam);
1309

1310 1311 1312
    case DTM_GETMONTHCAL:
	return (LRESULT)infoPtr->hMonthCal;

1313
    case DTM_SETMCCOLOR:
1314
	return SendMessageW (infoPtr->hMonthCal, MCM_SETCOLOR, wParam, lParam);
1315 1316

    case DTM_GETMCCOLOR:
1317
        return SendMessageW (infoPtr->hMonthCal, MCM_GETCOLOR, wParam, 0);
1318 1319

    case DTM_SETMCFONT:
1320
	return SendMessageW (infoPtr->hMonthCal, WM_SETFONT, wParam, lParam);
1321 1322

    case DTM_GETMCFONT:
1323
	return SendMessageW (infoPtr->hMonthCal, WM_GETFONT, wParam, lParam);
1324 1325

    case WM_NOTIFY:
1326
	return DATETIME_Notify (infoPtr, (int)wParam, (LPNMHDR)lParam);
1327

1328 1329 1330 1331 1332 1333
    case WM_ENABLE:
        return DATETIME_Enable (infoPtr, (BOOL)wParam);

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

1334 1335
    case WM_GETDLGCODE:
        return DLGC_WANTARROWS | DLGC_WANTCHARS;
1336

1337
    case WM_PRINTCLIENT:
1338
    case WM_PAINT:
1339
        return DATETIME_Paint (infoPtr, (HDC)wParam);
1340

1341
    case WM_KEYDOWN:
1342
        return DATETIME_KeyDown (infoPtr, wParam, lParam);
1343

1344
    case WM_KILLFOCUS:
1345
        return DATETIME_KillFocus (infoPtr, (HWND)wParam);
1346

1347 1348 1349
    case WM_NCCREATE:
        return DATETIME_NCCreate (hwnd, (LPCREATESTRUCTW)lParam);

1350
    case WM_SETFOCUS:
1351
        return DATETIME_SetFocus (infoPtr, (HWND)wParam);
1352

1353
    case WM_SIZE:
1354
        return DATETIME_Size (infoPtr, wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1355

1356
    case WM_LBUTTONDOWN:
1357
        return DATETIME_LButtonDown (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1358

1359
    case WM_LBUTTONUP:
1360
        return DATETIME_LButtonUp (infoPtr, (WORD)wParam);
1361

Duane Clark's avatar
Duane Clark committed
1362 1363 1364
    case WM_VSCROLL:
        return DATETIME_VScroll (infoPtr, (WORD)wParam);

1365
    case WM_CREATE:
1366
	return DATETIME_Create (hwnd, (LPCREATESTRUCTW)lParam);
1367

1368
    case WM_DESTROY:
1369
	return DATETIME_Destroy (infoPtr);
1370

Huw Davies's avatar
Huw Davies committed
1371
    case WM_COMMAND:
1372
        return DATETIME_Command (infoPtr, wParam, lParam);
Huw Davies's avatar
Huw Davies committed
1373

1374 1375 1376
    case WM_STYLECHANGED:
        return DATETIME_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);

1377 1378 1379 1380 1381 1382
    case WM_SETFONT:
        return DATETIME_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);

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

1383
    default:
1384
	if ((uMsg >= WM_USER) && (uMsg < WM_APP))
1385
		ERR("unknown msg %04x wp=%08lx lp=%08lx\n",
1386
		     uMsg, wParam, lParam);
1387
	return DefWindowProcW (hwnd, uMsg, wParam, lParam);
1388 1389 1390 1391
    }
}


1392
void
1393
DATETIME_Register (void)
1394
{
1395
    WNDCLASSW wndClass;
1396

1397
    ZeroMemory (&wndClass, sizeof(WNDCLASSW));
1398
    wndClass.style         = CS_GLOBALCLASS;
1399
    wndClass.lpfnWndProc   = DATETIME_WindowProc;
1400 1401
    wndClass.cbClsExtra    = 0;
    wndClass.cbWndExtra    = sizeof(DATETIME_INFO *);
1402
    wndClass.hCursor       = LoadCursorW (0, (LPCWSTR)IDC_ARROW);
1403
    wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
1404
    wndClass.lpszClassName = DATETIMEPICK_CLASSW;
1405

1406
    RegisterClassW (&wndClass);
1407 1408 1409
}


1410
void
1411
DATETIME_Unregister (void)
1412
{
1413
    UnregisterClassW (DATETIMEPICK_CLASSW, NULL);
1414
}