00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 #include <windows.h>
00013 #include <commctrl.h>
00014 #include "celutil/basictypes.h"
00015 #include "celengine/astro.h"
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039 static char* Months[12] =
00040 {
00041 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
00042 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
00043 };
00044
00045 enum DatePickerField
00046 {
00047 InvalidField = -1,
00048 DayField = 0,
00049 MonthField = 1,
00050 YearField = 2,
00051 NumFields = 3,
00052 };
00053
00054 class DatePicker
00055 {
00056 public:
00057 DatePicker(HWND _hwnd, CREATESTRUCT& cs);
00058 ~DatePicker();
00059
00060 LRESULT paint(HDC hdc);
00061 void redraw(HDC hdc);
00062 LRESULT keyDown(DWORD vkcode, LPARAM lParam);
00063 LRESULT killFocus(HWND lostFocus);
00064 LRESULT setFocus(HWND lostFocus);
00065 LRESULT enable(bool);
00066 LRESULT leftButtonDown(WORD key, int x, int y);
00067 LRESULT notify(int id, const NMHDR& nmhdr);
00068 LRESULT command(WPARAM wParam, LPARAM lParam);
00069 LRESULT resize(WORD flags, int width, int height);
00070
00071 bool sendNotify(UINT code);
00072 bool notifyDateChanged();
00073
00074 LRESULT setSystemTime(DWORD flag, SYSTEMTIME* sysTime);
00075 LRESULT getSystemTime(SYSTEMTIME* sysTime);
00076
00077 LRESULT destroy();
00078
00079 private:
00080 int getFieldWidth(DatePickerField field, HDC hdc);
00081 void incrementField();
00082 void decrementField();
00083
00084 private:
00085 HWND hwnd;
00086 HWND parent;
00087 astro::Date date;
00088 DatePickerField selectedField;
00089 char textBuffer[64];
00090 HFONT hFont;
00091 DWORD style;
00092
00093 bool haveFocus;
00094 bool firstDigit;
00095
00096 RECT fieldRects[NumFields];
00097 RECT clientRect;
00098 };
00099
00100
00101 DatePicker::DatePicker(HWND _hwnd, CREATESTRUCT& cs) :
00102 hwnd(_hwnd),
00103 parent(cs.hwndParent),
00104 date(1970, 10, 25),
00105 selectedField(YearField),
00106 haveFocus(false),
00107 firstDigit(true)
00108 {
00109 textBuffer[0] = '\0';
00110
00111 hFont = reinterpret_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
00112
00113 clientRect.left = 0;
00114 clientRect.right = 0;
00115 clientRect.top = 0;
00116 clientRect.bottom = 0;
00117 }
00118
00119
00120 DatePicker::~DatePicker()
00121 {
00122 }
00123
00124
00125 LRESULT
00126 DatePicker::paint(HDC hdc)
00127 {
00128 PAINTSTRUCT ps;
00129
00130 if (!hdc)
00131 {
00132 hdc = BeginPaint(hwnd, &ps);
00133 redraw(hdc);
00134 EndPaint(hwnd, &ps);
00135 }
00136 else
00137 {
00138 redraw(hdc);
00139 }
00140
00141 return 0;
00142 }
00143
00144
00145 void
00146 DatePicker::redraw(HDC hdc)
00147 {
00148 RECT rect;
00149 GetClientRect(hwnd, &rect);
00150
00151 SelectObject(hdc, hFont);
00152 SetTextColor(hdc, RGB(0, 0, 0));
00153 SetBkMode(hdc, TRANSPARENT);
00154
00155 char dayBuf[32];
00156 char monthBuf[32];
00157 char yearBuf[32];
00158
00159 sprintf(dayBuf, "%02d", date.day);
00160 sprintf(monthBuf, "%s", Months[date.month - 1]);
00161 sprintf(yearBuf, "%5d", date.year);
00162
00163 char* fieldText[NumFields];
00164 fieldText[DayField] = dayBuf;
00165 fieldText[MonthField] = monthBuf;
00166 fieldText[YearField] = yearBuf;
00167
00168 int right = 2;
00169 for (unsigned int i = 0; i < NumFields; i++)
00170 {
00171 SIZE size;
00172 GetTextExtentPoint(hdc, fieldText[i], strlen(fieldText[i]), &size);
00173 int fieldWidth = getFieldWidth(DatePickerField(i), hdc);
00174 fieldRects[i].left = right;
00175 fieldRects[i].right = right + fieldWidth;
00176 fieldRects[i].top = rect.top;
00177 fieldRects[i].bottom = rect.bottom;
00178
00179 right = fieldRects[i].right;
00180
00181 if (i == selectedField && haveFocus)
00182 {
00183 RECT r = fieldRects[i];
00184 r.top = (clientRect.bottom - size.cy) / 2;
00185 r.bottom = r.top + size.cy + 1;
00186
00187 HBRUSH hbrush = CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT));
00188 FillRect(hdc, &r, hbrush);
00189 DeleteObject(hbrush);
00190
00191 SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
00192 }
00193 else
00194 {
00195 SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
00196 }
00197
00198 DrawText(hdc, fieldText[i], strlen(fieldText[i]), &fieldRects[i], DT_RIGHT | DT_VCENTER | DT_SINGLELINE);
00199 }
00200 }
00201
00202
00203 static bool isLeapYear(unsigned int year)
00204 {
00205 if (year > 1582)
00206 {
00207 return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
00208 }
00209 else
00210 {
00211 return year % 4 == 0;
00212 }
00213 }
00214
00215
00216 static unsigned int daysInMonth(unsigned int month, unsigned int year)
00217 {
00218 static unsigned int daysPerMonth[12] =
00219 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
00220
00221 if (month == 2)
00222 return isLeapYear(year) ? 29 : 28;
00223 else
00224 return daysPerMonth[month - 1];
00225 }
00226
00227
00228 static void clampToValidDate(astro::Date& date)
00229 {
00230 int days = (int) daysInMonth(date.month, date.year);
00231 if (date.day > days)
00232 date.day = days;
00233
00234
00235 if (date.year == 1582 && date.month == 10 && date.day > 4 && date.day < 15)
00236 {
00237 if (date.day < 10)
00238 date.day = 4;
00239 else
00240 date.day = 15;
00241 }
00242 }
00243
00244
00245 LRESULT
00246 DatePicker::keyDown(DWORD vkcode, LPARAM flags)
00247 {
00248 if (!haveFocus)
00249 return 0;
00250
00251 if (vkcode >= '0' && vkcode <= '9')
00252 {
00253 unsigned int digit = vkcode - '0';
00254
00255 if (firstDigit)
00256 {
00257 switch (selectedField)
00258 {
00259 case DayField:
00260 if (digit != 0)
00261 date.day = digit;
00262 break;
00263 case MonthField:
00264 if (digit != 0)
00265 date.month = digit;
00266 break;
00267 case YearField:
00268 if (digit != 0)
00269 date.year = digit;
00270 break;
00271 }
00272 firstDigit = false;
00273 }
00274 else
00275 {
00276 switch (selectedField)
00277 {
00278 case DayField:
00279 {
00280 unsigned int day = date.day * 10 + digit;
00281 if (day >= 10)
00282 firstDigit = true;
00283 if (day > daysInMonth(date.month, date.year))
00284 day = 1;
00285 date.day = day;
00286 }
00287 break;
00288
00289 case MonthField:
00290 {
00291 unsigned int month = date.month * 10 + digit;
00292 if (month > 1)
00293 firstDigit = true;
00294 if (month > 12)
00295 month = 1;
00296 date.month = month;
00297 }
00298 break;
00299
00300 case YearField:
00301 {
00302 unsigned int year = date.year * 10 + digit;
00303 if (year >= 1000)
00304 firstDigit = true;
00305 if (year <= 9999)
00306 date.year = year;
00307 }
00308 break;
00309 }
00310 }
00311 clampToValidDate(date);
00312 notifyDateChanged();
00313 }
00314 else if (vkcode == VK_SUBTRACT || vkcode == VK_OEM_MINUS)
00315 {
00316 if (selectedField == YearField)
00317 {
00318 date.year = -date.year;
00319 clampToValidDate(date);
00320 notifyDateChanged();
00321 }
00322 }
00323 else
00324 {
00325 firstDigit = true;
00326
00327 switch (vkcode)
00328 {
00329 case VK_LEFT:
00330 if ((int) selectedField == 0)
00331 selectedField = DatePickerField((int) NumFields - 1);
00332 else
00333 selectedField = DatePickerField((int) selectedField - 1);
00334 break;
00335
00336 case VK_RIGHT:
00337 if ((int) selectedField == (int) NumFields - 1)
00338 selectedField = DatePickerField(0);
00339 else
00340 selectedField = DatePickerField((int) selectedField + 1);
00341 break;
00342
00343 case VK_UP:
00344 incrementField();
00345 notifyDateChanged();
00346 break;
00347
00348 case VK_DOWN:
00349 decrementField();
00350 notifyDateChanged();
00351 break;
00352
00353 default:
00354 break;
00355 }
00356 }
00357
00358 InvalidateRect(hwnd, NULL, TRUE);
00359
00360 return 0;
00361 }
00362
00363
00364 LRESULT
00365 DatePicker::leftButtonDown(WORD key, int x, int y)
00366 {
00367 POINT pt;
00368 pt.x = x;
00369 pt.y = y;
00370
00371 if (PtInRect(&fieldRects[DayField], pt))
00372 selectedField = DayField;
00373 else if (PtInRect(&fieldRects[MonthField], pt))
00374 selectedField = MonthField;
00375 else if (PtInRect(&fieldRects[YearField], pt))
00376 selectedField = YearField;
00377
00378 InvalidateRect(hwnd, NULL, TRUE);
00379
00380 ::SetFocus(hwnd);
00381
00382 return 0;
00383 }
00384
00385
00386 LRESULT
00387 DatePicker::setFocus(HWND lostFocus)
00388 {
00389 if (!haveFocus)
00390 {
00391 sendNotify(NM_SETFOCUS);
00392 haveFocus = true;
00393 }
00394
00395 firstDigit = true;
00396
00397 InvalidateRect(hwnd, NULL, TRUE);
00398
00399 return 0;
00400 }
00401
00402
00403 LRESULT
00404 DatePicker::killFocus(HWND lostFocus)
00405 {
00406 if (haveFocus)
00407 {
00408 sendNotify(NM_KILLFOCUS);
00409 haveFocus = false;
00410 }
00411
00412 InvalidateRect(hwnd, NULL, TRUE);
00413
00414 return 0;
00415 }
00416
00417
00418 LRESULT
00419 DatePicker::enable(bool b)
00420 {
00421 if (!b)
00422 style &= ~WS_DISABLED;
00423 else
00424 style |= WS_DISABLED;
00425
00426 return 0;
00427 }
00428
00429
00430 LRESULT
00431 DatePicker::notify(int id, const NMHDR& nmhdr)
00432 {
00433 return 0;
00434 }
00435
00436
00437 LRESULT
00438 DatePicker::command(WPARAM, LPARAM)
00439 {
00440 return 0;
00441 }
00442
00443
00444 bool
00445 DatePicker::sendNotify(UINT code)
00446 {
00447 NMHDR nmhdr;
00448
00449 ZeroMemory(&nmhdr, sizeof(nmhdr));
00450 nmhdr.hwndFrom = hwnd;
00451 nmhdr.idFrom = GetWindowLongPtr(hwnd, GWLP_ID);
00452 nmhdr.code = code;
00453
00454 return SendMessage(parent, WM_NOTIFY,
00455 nmhdr.idFrom,
00456 reinterpret_cast<LPARAM>(&nmhdr)) ? true : false;
00457 }
00458
00459
00460 bool
00461 DatePicker::notifyDateChanged()
00462 {
00463 NMDATETIMECHANGE change;
00464
00465 ZeroMemory(&change, sizeof(change));
00466 change.nmhdr.hwndFrom = hwnd;
00467 change.nmhdr.idFrom = GetWindowLongPtr(hwnd, GWLP_ID);
00468 change.nmhdr.code = DTN_DATETIMECHANGE;
00469 change.st.wYear = date.year;
00470 change.st.wMonth = date.month;
00471 change.st.wDay = date.day;
00472
00473 return SendMessage(parent, WM_NOTIFY,
00474 change.nmhdr.idFrom,
00475 reinterpret_cast<LPARAM>(&change)) ? true : false;
00476 }
00477
00478
00479 int
00480 DatePicker::getFieldWidth(DatePickerField field, HDC hdc)
00481 {
00482 char* maxWidthText = "\0";
00483
00484 switch (field)
00485 {
00486 case YearField:
00487 maxWidthText = "-2222 ";
00488 break;
00489
00490 case MonthField:
00491 maxWidthText = " Oct ";
00492 break;
00493
00494 case DayField:
00495 maxWidthText = "22 ";
00496 break;
00497 }
00498
00499 SIZE size;
00500 GetTextExtentPoint32(hdc, maxWidthText, strlen(maxWidthText), &size);
00501
00502 return size.cx;
00503 }
00504
00505
00506 void
00507 DatePicker::incrementField()
00508 {
00509 switch (selectedField)
00510 {
00511 case YearField:
00512 date.year++;
00513 clampToValidDate(date);
00514 break;
00515 case MonthField:
00516 date.month++;
00517 if (date.month > 12)
00518 date.month = 1;
00519 clampToValidDate(date);
00520 break;
00521 case DayField:
00522 date.day++;
00523 if (date.day > (int) daysInMonth(date.month, date.year))
00524 date.day = 1;
00525
00526 if (date.year == 1582 && date.month == 10 && date.day == 5)
00527 date.day = 15;
00528 break;
00529 }
00530 }
00531
00532
00533 void
00534 DatePicker::decrementField()
00535 {
00536 switch (selectedField)
00537 {
00538 case YearField:
00539 date.year--;
00540 clampToValidDate(date);
00541 break;
00542 case MonthField:
00543 date.month--;
00544 if (date.month < 1)
00545 date.month = 12;
00546 clampToValidDate(date);
00547 break;
00548 case DayField:
00549 date.day--;
00550 if (date.day < 1)
00551 date.day = daysInMonth(date.month, date.year);
00552
00553 if (date.year == 1582 && date.month == 10 && date.day == 14)
00554 date.day = 4;
00555 break;
00556 }
00557 }
00558
00559
00560 LRESULT
00561 DatePicker::destroy()
00562 {
00563 return 0;
00564 }
00565
00566
00567 LRESULT
00568 DatePicker::resize(WORD flags, int width, int height)
00569 {
00570 clientRect.bottom = height;
00571 clientRect.right = width;
00572
00573 InvalidateRect(hwnd, NULL, FALSE);
00574
00575 return 0;
00576 }
00577
00578
00579 LRESULT
00580 DatePicker::setSystemTime(DWORD flag, SYSTEMTIME* sysTime)
00581 {
00582 date.year = (int16) sysTime->wYear;
00583 date.month = sysTime->wMonth;
00584 date.day = sysTime->wDay;
00585
00586 InvalidateRect(hwnd, NULL, TRUE);
00587
00588 return 0;
00589 }
00590
00591
00592 LRESULT
00593 DatePicker::getSystemTime(SYSTEMTIME* sysTime)
00594 {
00595 if (sysTime != NULL)
00596 {
00597 sysTime->wYear = date.year;
00598 sysTime->wMonth = date.month;
00599 sysTime->wDay = date.day;
00600 }
00601
00602 return GDT_VALID;
00603 }
00604
00605
00606 static LRESULT
00607 DatePickerNCCreate(HWND hwnd, CREATESTRUCT& cs)
00608 {
00609 DWORD exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
00610
00611 exStyle |= WS_EX_CLIENTEDGE;
00612 SetWindowLong(hwnd, GWL_EXSTYLE, exStyle);
00613
00614 return DefWindowProc(hwnd, WM_NCCREATE, 0, reinterpret_cast<LPARAM>(&cs));
00615 }
00616
00617
00618 static LRESULT
00619 DatePickerCreate(HWND hwnd, CREATESTRUCT& cs)
00620 {
00621 DatePicker* dp = new DatePicker(hwnd, cs);
00622 if (dp == NULL)
00623 return -1;
00624
00625 SetWindowLongPtr(hwnd, 0, reinterpret_cast<DWORD_PTR>(dp));
00626
00627 return 0;
00628 }
00629
00630
00631 static LRESULT WINAPI
00632 DatePickerProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
00633 {
00634 DatePicker* dp = reinterpret_cast<DatePicker*>(GetWindowLongPtr(hwnd, 0));
00635
00636 if (!dp && (uMsg != WM_CREATE) && (uMsg != WM_NCCREATE))
00637 return DefWindowProc(hwnd, uMsg, wParam, lParam);
00638
00639
00640 switch (uMsg)
00641 {
00642 case DTM_SETSYSTEMTIME:
00643 return dp->setSystemTime(wParam, reinterpret_cast<SYSTEMTIME*>(lParam));
00644 break;
00645
00646 case DTM_GETSYSTEMTIME:
00647 return dp->getSystemTime(reinterpret_cast<SYSTEMTIME*>(lParam));
00648 break;
00649
00650 case WM_NOTIFY:
00651 return dp->notify((int) wParam, *reinterpret_cast<NMHDR*>(lParam));
00652 break;
00653
00654 case WM_ENABLE:
00655 return dp->enable(wParam != 0 ? true : false);
00656 break;
00657
00658
00659
00660
00661 case WM_PAINT:
00662 return dp->paint(reinterpret_cast<HDC>(wParam));
00663 break;
00664
00665 case WM_GETDLGCODE:
00666 return DLGC_WANTARROWS | DLGC_WANTCHARS;
00667
00668 case WM_KEYDOWN:
00669 return dp->keyDown(wParam, lParam);
00670 break;
00671
00672 case WM_KILLFOCUS:
00673 return dp->killFocus(reinterpret_cast<HWND>(wParam));
00674 break;
00675
00676 case WM_SETFOCUS:
00677 return dp->setFocus(reinterpret_cast<HWND>(wParam));
00678 break;
00679
00680 case WM_NCCREATE:
00681 return DatePickerNCCreate(hwnd, *reinterpret_cast<CREATESTRUCT*>(lParam));
00682 break;
00683
00684 case WM_SIZE:
00685 return dp->resize(wParam, LOWORD(lParam), HIWORD(lParam));
00686 break;
00687
00688 case WM_LBUTTONDOWN:
00689 return dp->leftButtonDown((WORD) wParam, LOWORD(lParam), HIWORD(lParam));
00690 break;
00691
00692 case WM_LBUTTONUP:
00693 return 0;
00694 break;
00695
00696 case WM_CREATE:
00697 return DatePickerCreate(hwnd, *reinterpret_cast<CREATESTRUCT*>(lParam));
00698 break;
00699
00700 case WM_DESTROY:
00701 return dp->destroy();
00702 break;
00703
00704 case WM_COMMAND:
00705 return dp->command(wParam, lParam);
00706 break;
00707
00708 default:
00709 return DefWindowProc(hwnd, uMsg, wParam, lParam);
00710 }
00711 }
00712
00713
00714 void
00715 RegisterDatePicker()
00716 {
00717 WNDCLASS wc;
00718
00719 ZeroMemory(&wc, sizeof(wc));
00720 wc.style = CS_GLOBALCLASS;
00721 wc.lpfnWndProc = DatePickerProc;
00722 wc.cbClsExtra = 0;
00723 wc.cbWndExtra = sizeof(DatePicker*);
00724 wc.hCursor = LoadCursor(0, IDC_ARROW);
00725 wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
00726 wc.lpszClassName = "CelestiaDatePicker";
00727
00728 RegisterClass(&wc);
00729 }