xref: /reactos/dll/cpl/timedate/clock.c (revision 8a978a17)
1 /*
2  * PROJECT:     ReactOS Timedate Control Panel
3  * LICENSE:     GPL - See COPYING in the top level directory
4  * FILE:        dll/cpl/timedate/clock.c
5  * PURPOSE:     Draws the analog clock
6  * COPYRIGHT:   Copyright 2006 Ged Murphy <gedmurphy@gmail.com>
7  *              Copyright 2007 Eric Kohl
8  */
9 
10 /* Code based on clock.c from Programming Windows, Charles Petzold */
11 
12 #include "timedate.h"
13 
14 #include <math.h>
15 
16 typedef struct _CLOCKDATA
17 {
18     HBRUSH hGreyBrush;
19     HPEN hGreyPen;
20     INT cxClient;
21     INT cyClient;
22     SYSTEMTIME stCurrent;
23     SYSTEMTIME stPrevious;
24     BOOL bTimer;
25 } CLOCKDATA, *PCLOCKDATA;
26 
27 
28 #define TWOPI (2 * 3.14159)
29 
30 static const WCHAR szClockWndClass[] = L"ClockWndClass";
31 
32 static VOID
33 RotatePoint(POINT pt[], INT iNum, INT iAngle)
34 {
35      INT i;
36      POINT ptTemp;
37 
38      for (i = 0 ; i < iNum ; i++)
39      {
40           ptTemp.x = (INT) (pt[i].x * cos (TWOPI * iAngle / 360) +
41                pt[i].y * sin (TWOPI * iAngle / 360));
42 
43           ptTemp.y = (INT) (pt[i].y * cos (TWOPI * iAngle / 360) -
44                pt[i].x * sin (TWOPI * iAngle / 360));
45 
46           pt[i] = ptTemp;
47      }
48 }
49 
50 
51 static INT
52 DrawClock(HDC hdc, PCLOCKDATA pClockData)
53 {
54      INT iAngle,Radius;
55      POINT pt[3];
56      HBRUSH hBrushOld;
57      HPEN hPenOld = NULL;
58 
59      /* Grey brush to fill the dots */
60      hBrushOld = SelectObject(hdc, pClockData->hGreyBrush);
61 
62      hPenOld = GetCurrentObject(hdc, OBJ_PEN);
63 
64      // TODO: Check if this conversion is correct resp. usable
65      Radius = min(pClockData->cxClient,pClockData->cyClient) * 2;
66 
67      for (iAngle = 0; iAngle < 360; iAngle += 6)
68      {
69           /* Starting coords */
70           pt[0].x = 0;
71           pt[0].y = Radius;
72 
73           /* Rotate start coords */
74           RotatePoint(pt, 1, iAngle);
75 
76           /* Determine whether it's a big dot or a little dot
77            * i.e. 1-4 or 5, 6-9 or 10, 11-14 or 15 */
78           if (iAngle % 5)
79           {
80                 pt[2].x = pt[2].y = 7;
81                 SelectObject(hdc, pClockData->hGreyPen);
82           }
83           else
84           {
85               pt[2].x = pt[2].y = 16;
86               SelectObject(hdc, GetStockObject(BLACK_PEN));
87           }
88 
89           pt[0].x -= pt[2].x / 2;
90           pt[0].y -= pt[2].y / 2;
91 
92           pt[1].x  = pt[0].x + pt[2].x;
93           pt[1].y  = pt[0].y + pt[2].y;
94 
95           Ellipse(hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y);
96      }
97 
98      SelectObject(hdc, hBrushOld);
99      SelectObject(hdc, hPenOld);
100      return Radius;
101 }
102 
103 
104 static VOID
105 DrawHands(HDC hdc, SYSTEMTIME * pst, BOOL fChange, INT Radius)
106 {
107      POINT pt[3][5] = { {{0, (INT)-Radius/6}, {(INT)Radius/9, 0},
108 	     {0, (INT)Radius/1.8}, {(INT)-Radius/9, 0}, {0, (INT)-Radius/6}},
109      {{0, (INT)-Radius/4.5}, {(INT)Radius/18, 0}, {0, (INT) Radius*0.89},
110 	     {(INT)-Radius/18, 0}, {0, (INT)-Radius/4.5}},
111      {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, (INT) Radius*0.89}} };
112      INT i, iAngle[3];
113      POINT ptTemp[3][5];
114 
115      /* Black pen for outline, white brush for fill */
116      SelectObject(hdc, GetStockObject(BLACK_PEN));
117      SelectObject(hdc, GetStockObject(WHITE_BRUSH));
118 
119      iAngle[0] = (pst->wHour * 30) % 360 + pst->wMinute / 2;
120      iAngle[1] = pst->wMinute * 6;
121      iAngle[2] = pst->wSecond * 6;
122 
123      CopyMemory(ptTemp, pt, sizeof(pt));
124 
125      for (i = fChange ? 0 : 2; i < 3; i++)
126      {
127           RotatePoint(ptTemp[i], 5, iAngle[i]);
128 
129           Polygon(hdc, ptTemp[i], 5);
130      }
131 }
132 
133 
134 static LRESULT CALLBACK
135 ClockWndProc(HWND hwnd,
136              UINT uMsg,
137              WPARAM wParam,
138              LPARAM lParam)
139 {
140     PCLOCKDATA pClockData;
141     HDC hdc, hdcMem;
142     PAINTSTRUCT ps;
143 
144     pClockData = (PCLOCKDATA)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
145 
146     switch (uMsg)
147     {
148         case WM_CREATE:
149             pClockData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(CLOCKDATA));
150             SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)pClockData);
151 
152             pClockData->hGreyPen = CreatePen(PS_SOLID, 1, RGB(128, 128, 128));
153             pClockData->hGreyBrush = CreateSolidBrush(RGB(128, 128, 128));
154 
155             SetTimer(hwnd, ID_TIMER, 1000, NULL);
156             pClockData->bTimer = TRUE;
157             GetLocalTime(&pClockData->stCurrent);
158             pClockData->stPrevious = pClockData->stCurrent;
159             break;
160 
161         case WM_SIZE:
162             pClockData->cxClient = LOWORD(lParam);
163             pClockData->cyClient = HIWORD(lParam);
164             break;
165 
166         case WM_TIMECHANGE:
167         case WM_TIMER:
168             GetLocalTime(&pClockData->stCurrent);
169             InvalidateRect(hwnd, NULL, FALSE);
170             pClockData->stPrevious = pClockData->stCurrent;
171             break;
172 
173         case WM_PAINT:
174             hdc = BeginPaint(hwnd, &ps);
175 
176             hdcMem = CreateCompatibleDC(hdc);
177             if (hdcMem)
178             {
179                 HBITMAP hBmp, hBmpOld;
180 
181                 hBmp = CreateCompatibleBitmap(hdc,
182                                               pClockData->cxClient,
183                                               pClockData->cyClient);
184                 if (hBmp)
185                 {
186                     RECT rcParent;
187                     HWND hParentWnd = GetParent(hwnd);
188                     INT oldMap, Radius;
189                     POINT oldOrg;
190 
191                     hBmpOld = SelectObject(hdcMem, hBmp);
192 
193                     SetRect(&rcParent, 0, 0, pClockData->cxClient, pClockData->cyClient);
194                     MapWindowPoints(hwnd, hParentWnd, (POINT*)&rcParent, 2);
195                     OffsetViewportOrgEx(hdcMem, -rcParent.left, -rcParent.top, &oldOrg);
196                     SendMessage(hParentWnd, WM_PRINT, (WPARAM)hdcMem, PRF_ERASEBKGND | PRF_CLIENT);
197                     SetViewportOrgEx(hdcMem, oldOrg.x, oldOrg.y, NULL);
198 
199                     oldMap = SetMapMode(hdcMem, MM_ISOTROPIC);
200                     SetWindowExtEx(hdcMem, 3600, 2700, NULL);
201                     SetViewportExtEx(hdcMem, 800, -600, NULL);
202                     SetViewportOrgEx(hdcMem,
203                                      pClockData->cxClient / 2,
204                                      pClockData->cyClient / 2,
205                                      &oldOrg);
206 
207                     Radius = DrawClock(hdcMem, pClockData);
208                     DrawHands(hdcMem, &pClockData->stPrevious, TRUE, Radius);
209 
210                     SetMapMode(hdcMem, oldMap);
211                     SetViewportOrgEx(hdcMem, oldOrg.x, oldOrg.y, NULL);
212 
213                     BitBlt(hdc,
214                            0,
215                            0,
216                            pClockData->cxClient,
217                            pClockData->cyClient,
218                            hdcMem,
219                            0,
220                            0,
221                            SRCCOPY);
222 
223                     SelectObject(hdcMem, hBmpOld);
224                     DeleteObject(hBmp);
225                 }
226 
227                 DeleteDC(hdcMem);
228             }
229 
230             EndPaint(hwnd, &ps);
231             break;
232 
233         /* No need to erase background, handled during paint */
234         case WM_ERASEBKGND:
235             return 1;
236 
237         case WM_DESTROY:
238             DeleteObject(pClockData->hGreyPen);
239             DeleteObject(pClockData->hGreyBrush);
240 
241             if (pClockData->bTimer)
242                 KillTimer(hwnd, ID_TIMER);
243 
244             HeapFree(GetProcessHeap(), 0, pClockData);
245             break;
246 
247         case CLM_STOPCLOCK:
248             if (pClockData->bTimer)
249             {
250                 KillTimer(hwnd, ID_TIMER);
251                 pClockData->bTimer = FALSE;
252             }
253             break;
254 
255         case CLM_STARTCLOCK:
256             if (!pClockData->bTimer)
257             {
258                 SetTimer(hwnd, ID_TIMER, 1000, NULL);
259                 pClockData->bTimer = TRUE;
260             }
261             break;
262 
263         default:
264             DefWindowProcW(hwnd,
265                            uMsg,
266                            wParam,
267                            lParam);
268     }
269 
270     return TRUE;
271 }
272 
273 
274 BOOL
275 RegisterClockControl(VOID)
276 {
277     WNDCLASSEXW wc = {0};
278 
279     wc.cbSize = sizeof(WNDCLASSEXW);
280     wc.lpfnWndProc = ClockWndProc;
281     wc.hInstance = hApplet;
282     wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
283     wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
284     wc.lpszClassName = szClockWndClass;
285 
286     return RegisterClassExW(&wc) != (ATOM)0;
287 }
288 
289 
290 VOID
291 UnregisterClockControl(VOID)
292 {
293     UnregisterClassW(szClockWndClass,
294                      hApplet);
295 }
296