Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members

windatepicker.cpp

Go to the documentation of this file.
00001 // windatepicker.cpp
00002 //
00003 // Copyright (C) 2005, Chris Laurel <claurel@shatters.net>
00004 //
00005 // Windows front end for Celestia.
00006 //
00007 // This program is free software; you can redistribute it and/or
00008 // modify it under the terms of the GNU General Public License
00009 // as published by the Free Software Foundation; either version 2
00010 // of the License, or (at your option) any later version.
00011 
00012 #include <windows.h>
00013 #include <commctrl.h>
00014 #include "celutil/basictypes.h"
00015 #include "celengine/astro.h"
00016 
00017 
00018 // DatePicker is a Win32 control for setting the date. It replaces the
00019 // date picker from commctl, adding a number of features appropriate
00020 // for astronomical applications:
00021 //
00022 // - The standard Windows date picker does not permit setting years
00023 //   prior to 1752, the point that the US and UK switched to the 
00024 //   Gregorian calendar. Celestia's date picker allows setting any
00025 //   year from -9999 to 9999.
00026 //
00027 // - Astronomical year conventions are used for dates before the
00028 //   year 1. This means that the year 0 is not omitted, and the year
00029 //   2 BCE is entered as -1.
00030 //
00031 // - The first adoption of the Gregorian calendar was in 1582, when
00032 //   days 5-14 were skipped in the month of October. All dates are
00033 //   based on the initial 1582 reform, even though most countries
00034 //   didn't adopt the Gregorian calendar until many years later.
00035 //
00036 // - No invalid date is permitted, including the skipped days in
00037 //   October 1582.
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     // 10 days skipped in Gregorian calendar reform
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); // note that this is the Win32 API function, not the class method
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         // Skip 10 days deleted in Gregorian calendar reform
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         // Skip 10 days deleted in Gregorian calendar reform
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         //case WM_ERASEBKGND:
00659         //break;
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 }

Generated on Sat Jan 14 22:30:31 2006 for Celestia by  doxygen 1.4.1