datetime.c 43 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, 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)
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, 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)
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
    switch (vkCode) {
906
	case VK_ADD:
907
    	case VK_UP:
908 909 910
	    DATETIME_IncreaseField (infoPtr, fieldNum, 1);
	    DATETIME_SendDateTimeChangeNotify (infoPtr);
	    break;
911 912
	case VK_SUBTRACT:
	case VK_DOWN:
913 914 915
	    DATETIME_IncreaseField (infoPtr, fieldNum, -1);
	    DATETIME_SendDateTimeChangeNotify (infoPtr);
	    break;
916
	case VK_HOME:
917 918 919
	    DATETIME_IncreaseField (infoPtr, fieldNum, INT_MIN);
	    DATETIME_SendDateTimeChangeNotify (infoPtr);
	    break;
920
	case VK_END:
921 922 923
	    DATETIME_IncreaseField (infoPtr, fieldNum, INT_MAX);
	    DATETIME_SendDateTimeChangeNotify (infoPtr);
	    break;
924
	case VK_LEFT:
925 926 927 928 929 930
	    do {
		if (infoPtr->select == 0) {
		    infoPtr->select = infoPtr->nrFields - 1;
		    wrap++;
		} else {
		    infoPtr->select--;
931
		}
932 933
	    } while ((infoPtr->fieldspec[infoPtr->select] & DT_STRING) && (wrap<2));
	    break;
934
	case VK_RIGHT:
935 936 937 938 939 940 941 942 943
	    do {
		infoPtr->select++;
		if (infoPtr->select==infoPtr->nrFields) {
		    infoPtr->select = 0;
		    wrap++;
		}
	    } while ((infoPtr->fieldspec[infoPtr->select] & DT_STRING) && (wrap<2));
	    break;
    }
944

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

947
    return 0;
948 949 950
}


951
static LRESULT
952
DATETIME_Char (DATETIME_INFO *infoPtr, WPARAM vkCode)
953 954 955 956
{
    int fieldNum = infoPtr->select & DTHT_DATEFIELD;

    if (vkCode >= '0' && vkCode <= '9') {
957
        int num = vkCode-'0';
958
        int newDays;
959

960 961 962 963 964 965
        /* 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) +
966
                        (date->wYear%10)*10 + num;
967 968 969 970 971 972
                date->wDayOfWeek = DATETIME_CalculateDayOfWeek(
                        date->wDay,date->wMonth,date->wYear);
                DATETIME_SendDateTimeChangeNotify (infoPtr);
                break;
            case INVALIDFULLYEAR:
            case FULLYEAR:
973
                date->wYear = (date->wYear%1000)*10 + num;
974 975 976 977 978 979
                date->wDayOfWeek = DATETIME_CalculateDayOfWeek(
                        date->wDay,date->wMonth,date->wYear);
                DATETIME_SendDateTimeChangeNotify (infoPtr);
                break;
            case ONEDIGITMONTH:
            case TWODIGITMONTH:
980 981
                if ((date->wMonth%10) > 1 || num > 2)
                    date->wMonth = num;
982
                else
983
                    date->wMonth = (date->wMonth%10)*10+num;
984 985 986 987 988 989
                date->wDayOfWeek = DATETIME_CalculateDayOfWeek(
                        date->wDay,date->wMonth,date->wYear);
                DATETIME_SendDateTimeChangeNotify (infoPtr);
                break;
            case ONEDIGITDAY:
            case TWODIGITDAY:
990 991
                newDays = (date->wDay%10)*10+num;
                if (newDays > MONTHCAL_MonthLength(date->wMonth, date->wYear))
992
                    date->wDay = num;
993
                else
994
                    date->wDay = newDays;
995 996 997 998 999 1000
                date->wDayOfWeek = DATETIME_CalculateDayOfWeek(
                        date->wDay,date->wMonth,date->wYear);
                DATETIME_SendDateTimeChangeNotify (infoPtr);
                break;
            case ONEDIGIT12HOUR:
            case TWODIGIT12HOUR:
1001 1002
                if ((date->wHour%10) > 1 || num > 2)
                    date->wHour = num;
1003
                else
1004
                    date->wHour = (date->wHour%10)*10+num;
1005 1006 1007 1008 1009
                DATETIME_SendDateTimeChangeNotify (infoPtr);
                break;
            case ONEDIGIT24HOUR:
            case TWODIGIT24HOUR:
                if ((date->wHour%10) > 2)
1010 1011 1012
                    date->wHour = num;
                else if ((date->wHour%10) == 2 && num > 3)
                    date->wHour = num;
1013
                else
1014
                    date->wHour = (date->wHour%10)*10+num;
1015 1016 1017 1018 1019
                DATETIME_SendDateTimeChangeNotify (infoPtr);
                break;
            case ONEDIGITMINUTE:
            case TWODIGITMINUTE:
                if ((date->wMinute%10) > 5)
1020
                    date->wMinute = num;
1021
                else
1022
                    date->wMinute = (date->wMinute%10)*10+num;
1023 1024 1025 1026 1027
                DATETIME_SendDateTimeChangeNotify (infoPtr);
                break;
            case ONEDIGITSECOND:
            case TWODIGITSECOND:
                if ((date->wSecond%10) > 5)
1028
                    date->wSecond = num;
1029
                else
1030
                    date->wSecond = (date->wSecond%10)*10+num;
1031 1032 1033 1034 1035 1036 1037 1038
                DATETIME_SendDateTimeChangeNotify (infoPtr);
                break;
        }
    }
    return 0;
}


Duane Clark's avatar
Duane Clark committed
1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056
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);
    }

1057
    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
Duane Clark's avatar
Duane Clark committed
1058 1059 1060 1061 1062

    return 0;
}


1063
static LRESULT
1064
DATETIME_KillFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
1065
{
1066 1067
    TRACE("lost focus to %p\n", lostFocus);

1068
    if (infoPtr->haveFocus) {
1069
	DATETIME_SendSimpleNotify (infoPtr, NM_KILLFOCUS);
1070 1071
	infoPtr->haveFocus = 0;
    }
1072

1073
    InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
1074 1075 1076 1077 1078

    return 0;
}


1079
static LRESULT
1080
DATETIME_NCCreate (HWND hwnd, const CREATESTRUCTW *lpcs)
1081 1082 1083 1084 1085 1086 1087 1088 1089 1090
{
    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);
}


1091
static LRESULT
1092
DATETIME_SetFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
1093
{
1094 1095
    TRACE("got focus from %p\n", lostFocus);

1096
    /* if monthcal is open and it loses focus, close monthcal */
1097
    if (infoPtr->hMonthCal && (lostFocus == infoPtr->hMonthCal) &&
1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
        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;
    }

1109 1110
    if (infoPtr->haveFocus == 0) {
	DATETIME_SendSimpleNotify (infoPtr, NM_SETFOCUS);
1111
	infoPtr->haveFocus = DTHT_GOTFOCUS;
1112
    }
1113

1114
    InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1115 1116 1117 1118 1119

    return 0;
}


1120
static BOOL
1121
DATETIME_SendDateTimeChangeNotify (const DATETIME_INFO *infoPtr)
1122
{
1123 1124 1125 1126 1127 1128
    NMDATETIMECHANGE dtdtc;

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

1129
    dtdtc.dwFlags = (infoPtr->dwStyle & DTS_SHOWNONE) ? GDT_NONE : GDT_VALID;
1130 1131 1132

    MONTHCAL_CopyTime (&infoPtr->date, &dtdtc.st);
    return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
1133
                                dtdtc.nmhdr.idFrom, (LPARAM)&dtdtc);
1134 1135 1136
}


1137
static BOOL
1138
DATETIME_SendSimpleNotify (const DATETIME_INFO *infoPtr, UINT code)
1139 1140 1141
{
    NMHDR nmhdr;

1142 1143 1144
    TRACE("%x\n", code);
    nmhdr.hwndFrom = infoPtr->hwndSelf;
    nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1145 1146
    nmhdr.code     = code;

1147
    return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
1148
                                nmhdr.idFrom, (LPARAM)&nmhdr);
1149
}
1150

1151
static LRESULT
1152
DATETIME_Size (DATETIME_INFO *infoPtr, INT width, INT height)
1153
{
1154 1155 1156 1157
    /* set size */
    infoPtr->rcClient.bottom = height;
    infoPtr->rcClient.right = width;

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

1160
    infoPtr->rcDraw = infoPtr->rcClient;
Duane Clark's avatar
Duane Clark committed
1161 1162
    
    if (infoPtr->dwStyle & DTS_UPDOWN) {
1163 1164 1165 1166
        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
1167 1168 1169 1170 1171 1172 1173 1174 1175
    }
    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;
    }
1176 1177 1178 1179 1180 1181 1182 1183 1184

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

1186 1187
    return 0;
}
1188 1189


1190
static LRESULT 
1191
DATETIME_StyleChanged(DATETIME_INFO *infoPtr, WPARAM wStyleType, const STYLESTRUCT *lpss)
1192 1193
{
    static const WCHAR buttonW[] = { 'b', 'u', 't', 't', 'o', 'n', 0 };
1194

1195
    TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
1196
          wStyleType, lpss->styleOld, lpss->styleNew);
1197

1198 1199 1200
    if (wStyleType != GWL_STYLE) return 0;
  
    infoPtr->dwStyle = lpss->styleNew;
1201

1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219
    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;
    }
1220

1221 1222
    InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
    return 0;
1223
}
1224

1225

1226 1227 1228 1229 1230 1231 1232 1233 1234
static LRESULT
DATETIME_SetFont (DATETIME_INFO *infoPtr, HFONT font, BOOL repaint)
{
    infoPtr->hFont = font;
    if (repaint) InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
    return 0;
}


1235
static LRESULT
1236
DATETIME_Create (HWND hwnd, const CREATESTRUCTW *lpcs)
1237
{
1238
    static const WCHAR SysMonthCal32W[] = { 'S', 'y', 's', 'M', 'o', 'n', 't', 'h', 'C', 'a', 'l', '3', '2', 0 };
1239
    DATETIME_INFO *infoPtr = Alloc (sizeof(DATETIME_INFO));
1240 1241 1242 1243 1244 1245 1246 1247
    STYLESTRUCT ss = { 0, lpcs->style };

    if (!infoPtr) return -1;

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

    infoPtr->nrFieldsAllocated = 32;
1248 1249 1250
    infoPtr->fieldspec = Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
    infoPtr->fieldRect = Alloc (infoPtr->nrFieldsAllocated * sizeof(RECT));
    infoPtr->buflen = Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
1251
    infoPtr->hwndNotify = lpcs->hwndParent;
1252
    infoPtr->select = -1; /* initially, nothing is selected */
1253
    infoPtr->bDropdownEnabled = TRUE;
1254 1255 1256 1257 1258 1259 1260 1261 1262

    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 */
1263
    GetLocalTime (&infoPtr->date);
1264 1265 1266 1267 1268 1269
    infoPtr->dateValid = TRUE;
    infoPtr->hFont = GetStockObject(DEFAULT_GUI_FONT);

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

    return 0;
1270 1271 1272
}


1273

1274
static LRESULT
1275
DATETIME_Destroy (DATETIME_INFO *infoPtr)
1276
{
1277 1278 1279 1280
    if (infoPtr->hwndCheckbut)
	DestroyWindow(infoPtr->hwndCheckbut);
    if (infoPtr->hUpdown)
	DestroyWindow(infoPtr->hUpdown);
1281
    if (infoPtr->hMonthCal) 
1282
        DestroyWindow(infoPtr->hMonthCal);
1283
    SetWindowLongPtrW( infoPtr->hwndSelf, 0, 0 ); /* clear infoPtr */
1284
    Free (infoPtr);
1285 1286 1287 1288
    return 0;
}


1289
static LRESULT WINAPI
1290
DATETIME_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1291
{
1292
    DATETIME_INFO *infoPtr = ((DATETIME_INFO *)GetWindowLongPtrW (hwnd, 0));
1293
    LRESULT ret;
1294

1295
    TRACE ("%x, %lx, %lx\n", uMsg, wParam, lParam);
1296

1297
    if (!infoPtr && (uMsg != WM_CREATE) && (uMsg != WM_NCCREATE))
1298
	return DefWindowProcW( hwnd, uMsg, wParam, lParam );
1299

1300
    switch (uMsg) {
1301

1302
    case DTM_GETSYSTEMTIME:
1303
        return DATETIME_GetSystemTime (infoPtr, (SYSTEMTIME *) lParam);
1304 1305

    case DTM_SETSYSTEMTIME:
1306
	return DATETIME_SetSystemTime (infoPtr, wParam, (SYSTEMTIME *) lParam);
1307 1308

    case DTM_GETRANGE:
1309 1310
	ret = SendMessageW (infoPtr->hMonthCal, MCM_GETRANGE, wParam, lParam);
	return ret ? ret : 1; /* bug emulation */
1311 1312

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

1315
    case DTM_SETFORMATA:
1316
        return DATETIME_SetFormatA (infoPtr, (LPCSTR)lParam);
1317

1318
    case DTM_SETFORMATW:
1319
        return DATETIME_SetFormatW (infoPtr, (LPCWSTR)lParam);
1320

1321 1322 1323
    case DTM_GETMONTHCAL:
	return (LRESULT)infoPtr->hMonthCal;

1324
    case DTM_SETMCCOLOR:
1325
	return SendMessageW (infoPtr->hMonthCal, MCM_SETCOLOR, wParam, lParam);
1326 1327

    case DTM_GETMCCOLOR:
1328
        return SendMessageW (infoPtr->hMonthCal, MCM_GETCOLOR, wParam, 0);
1329 1330

    case DTM_SETMCFONT:
1331
	return SendMessageW (infoPtr->hMonthCal, WM_SETFONT, wParam, lParam);
1332 1333

    case DTM_GETMCFONT:
1334
	return SendMessageW (infoPtr->hMonthCal, WM_GETFONT, wParam, lParam);
1335 1336

    case WM_NOTIFY:
1337
	return DATETIME_Notify (infoPtr, (LPNMHDR)lParam);
1338

1339 1340 1341 1342 1343 1344
    case WM_ENABLE:
        return DATETIME_Enable (infoPtr, (BOOL)wParam);

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

1345 1346
    case WM_GETDLGCODE:
        return DLGC_WANTARROWS | DLGC_WANTCHARS;
1347

1348
    case WM_PRINTCLIENT:
1349
    case WM_PAINT:
1350
        return DATETIME_Paint (infoPtr, (HDC)wParam);
1351

1352
    case WM_KEYDOWN:
1353
        return DATETIME_KeyDown (infoPtr, wParam);
1354

1355
    case WM_CHAR:
1356
        return DATETIME_Char (infoPtr, wParam);
1357

1358
    case WM_KILLFOCUS:
1359
        return DATETIME_KillFocus (infoPtr, (HWND)wParam);
1360

1361 1362 1363
    case WM_NCCREATE:
        return DATETIME_NCCreate (hwnd, (LPCREATESTRUCTW)lParam);

1364
    case WM_SETFOCUS:
1365
        return DATETIME_SetFocus (infoPtr, (HWND)wParam);
1366

1367
    case WM_SIZE:
1368
        return DATETIME_Size (infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1369

1370
    case WM_LBUTTONDOWN:
1371
        return DATETIME_LButtonDown (infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1372

1373
    case WM_LBUTTONUP:
1374
        return DATETIME_LButtonUp (infoPtr);
1375

Duane Clark's avatar
Duane Clark committed
1376 1377 1378
    case WM_VSCROLL:
        return DATETIME_VScroll (infoPtr, (WORD)wParam);

1379
    case WM_CREATE:
1380
	return DATETIME_Create (hwnd, (LPCREATESTRUCTW)lParam);
1381

1382
    case WM_DESTROY:
1383
	return DATETIME_Destroy (infoPtr);
1384

Huw Davies's avatar
Huw Davies committed
1385
    case WM_COMMAND:
1386
        return DATETIME_Command (infoPtr, wParam, lParam);
Huw Davies's avatar
Huw Davies committed
1387

1388 1389 1390
    case WM_STYLECHANGED:
        return DATETIME_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);

1391 1392 1393 1394 1395 1396
    case WM_SETFONT:
        return DATETIME_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);

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

1397
    default:
1398
	if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
1399
		ERR("unknown msg %04x wp=%08lx lp=%08lx\n",
1400
		     uMsg, wParam, lParam);
1401
	return DefWindowProcW (hwnd, uMsg, wParam, lParam);
1402 1403 1404 1405
    }
}


1406
void
1407
DATETIME_Register (void)
1408
{
1409
    WNDCLASSW wndClass;
1410

1411
    ZeroMemory (&wndClass, sizeof(WNDCLASSW));
1412
    wndClass.style         = CS_GLOBALCLASS;
1413
    wndClass.lpfnWndProc   = DATETIME_WindowProc;
1414 1415
    wndClass.cbClsExtra    = 0;
    wndClass.cbWndExtra    = sizeof(DATETIME_INFO *);
1416
    wndClass.hCursor       = LoadCursorW (0, (LPCWSTR)IDC_ARROW);
1417
    wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
1418
    wndClass.lpszClassName = DATETIMEPICK_CLASSW;
1419

1420
    RegisterClassW (&wndClass);
1421 1422 1423
}


1424
void
1425
DATETIME_Unregister (void)
1426
{
1427
    UnregisterClassW (DATETIMEPICK_CLASSW, NULL);
1428
}