xref: /reactos/dll/cpl/timedate/clock.c (revision 9393fc32)
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
RotatePoint(POINT pt[],INT iNum,INT iAngle)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
DrawClock(HDC hdc,PCLOCKDATA pClockData)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
DrawHands(HDC hdc,SYSTEMTIME * pst,BOOL fChange,INT Radius)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
ClockWndProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)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             GetLocalTime(&pClockData->stCurrent);
156             pClockData->stPrevious = pClockData->stCurrent;
157 
158             pClockData->bTimer = (SetTimer(hwnd, ID_TIMER, 1000 - pClockData->stCurrent.wMilliseconds, NULL) != 0);
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 
172             // Reset timeout.
173             if (pClockData->bTimer)
174             {
175                 SetTimer(hwnd, ID_TIMER, 1000 - pClockData->stCurrent.wMilliseconds, NULL);
176             }
177             break;
178 
179         case WM_PAINT:
180             hdc = BeginPaint(hwnd, &ps);
181 
182             hdcMem = CreateCompatibleDC(hdc);
183             if (hdcMem)
184             {
185                 HBITMAP hBmp, hBmpOld;
186 
187                 hBmp = CreateCompatibleBitmap(hdc,
188                                               pClockData->cxClient,
189                                               pClockData->cyClient);
190                 if (hBmp)
191                 {
192                     RECT rcParent;
193                     HWND hParentWnd = GetParent(hwnd);
194                     INT oldMap, Radius;
195                     POINT oldOrg;
196 
197                     hBmpOld = SelectObject(hdcMem, hBmp);
198 
199                     SetRect(&rcParent, 0, 0, pClockData->cxClient, pClockData->cyClient);
200                     MapWindowPoints(hwnd, hParentWnd, (POINT*)&rcParent, 2);
201                     OffsetViewportOrgEx(hdcMem, -rcParent.left, -rcParent.top, &oldOrg);
202                     SendMessage(hParentWnd, WM_PRINT, (WPARAM)hdcMem, PRF_ERASEBKGND | PRF_CLIENT);
203                     SetViewportOrgEx(hdcMem, oldOrg.x, oldOrg.y, NULL);
204 
205                     oldMap = SetMapMode(hdcMem, MM_ISOTROPIC);
206                     SetWindowExtEx(hdcMem, 3600, 2700, NULL);
207                     SetViewportExtEx(hdcMem, 800, -600, NULL);
208                     SetViewportOrgEx(hdcMem,
209                                      pClockData->cxClient / 2,
210                                      pClockData->cyClient / 2,
211                                      &oldOrg);
212 
213                     Radius = DrawClock(hdcMem, pClockData);
214                     DrawHands(hdcMem, &pClockData->stPrevious, TRUE, Radius);
215 
216                     SetMapMode(hdcMem, oldMap);
217                     SetViewportOrgEx(hdcMem, oldOrg.x, oldOrg.y, NULL);
218 
219                     BitBlt(hdc,
220                            0,
221                            0,
222                            pClockData->cxClient,
223                            pClockData->cyClient,
224                            hdcMem,
225                            0,
226                            0,
227                            SRCCOPY);
228 
229                     SelectObject(hdcMem, hBmpOld);
230                     DeleteObject(hBmp);
231                 }
232 
233                 DeleteDC(hdcMem);
234             }
235 
236             EndPaint(hwnd, &ps);
237             break;
238 
239         /* No need to erase background, handled during paint */
240         case WM_ERASEBKGND:
241             return 1;
242 
243         case WM_DESTROY:
244             DeleteObject(pClockData->hGreyPen);
245             DeleteObject(pClockData->hGreyBrush);
246 
247             if (pClockData->bTimer)
248                 KillTimer(hwnd, ID_TIMER);
249 
250             HeapFree(GetProcessHeap(), 0, pClockData);
251             break;
252 
253         case CLM_STOPCLOCK:
254             if (pClockData->bTimer)
255             {
256                 KillTimer(hwnd, ID_TIMER);
257                 pClockData->bTimer = FALSE;
258             }
259             break;
260 
261         case CLM_STARTCLOCK:
262             if (!pClockData->bTimer)
263             {
264                 SYSTEMTIME LocalTime;
265 
266                 GetLocalTime(&LocalTime);
267                 pClockData->bTimer = (SetTimer(hwnd, ID_TIMER, 1000 - LocalTime.wMilliseconds, NULL) != 0);
268             }
269             break;
270 
271         default:
272             DefWindowProcW(hwnd,
273                            uMsg,
274                            wParam,
275                            lParam);
276     }
277 
278     return TRUE;
279 }
280 
281 
282 BOOL
RegisterClockControl(VOID)283 RegisterClockControl(VOID)
284 {
285     WNDCLASSEXW wc = {0};
286 
287     wc.cbSize = sizeof(WNDCLASSEXW);
288     wc.lpfnWndProc = ClockWndProc;
289     wc.hInstance = hApplet;
290     wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
291     wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
292     wc.lpszClassName = szClockWndClass;
293 
294     return RegisterClassExW(&wc) != (ATOM)0;
295 }
296 
297 
298 VOID
UnregisterClockControl(VOID)299 UnregisterClockControl(VOID)
300 {
301     UnregisterClassW(szClockWndClass,
302                      hApplet);
303 }
304