4 * Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27 static const TCHAR szTrayClockWndClass
[] = TEXT("TrayClockWClass");
29 #define ID_TRAYCLOCK_TIMER 0
30 #define ID_TRAYCLOCK_TIMER_INIT 1
37 } ClockWndFormats
[] = {
38 {TRUE
, TIME_NOSECONDS
, NULL
},
39 {FALSE
, 0, TEXT("dddd")},
40 {FALSE
, DATE_SHORTDATE
, NULL
},
43 #define CLOCKWND_FORMAT_COUNT (sizeof(ClockWndFormats) / sizeof(ClockWndFormats[0]))
45 #define TRAY_CLOCK_WND_SPACING_X 0
46 #define TRAY_CLOCK_WND_SPACING_Y 0
48 typedef struct _TRAY_CLOCK_WND_DATA
60 DWORD IsTimerEnabled
: 1;
61 DWORD IsInitTimerEnabled
: 1;
62 DWORD LinesMeasured
: 1;
63 DWORD IsHorizontal
: 1;
69 SIZE LineSizes
[CLOCKWND_FORMAT_COUNT
];
70 TCHAR szLines
[CLOCKWND_FORMAT_COUNT
][48];
71 } TRAY_CLOCK_WND_DATA
, *PTRAY_CLOCK_WND_DATA
;
74 TrayClockWnd_MeasureLines(IN OUT PTRAY_CLOCK_WND_DATA This
)
81 hDC
= GetDC(This
->hWnd
);
84 hPrevFont
= SelectObject(hDC
,
88 i
!= CLOCKWND_FORMAT_COUNT
&& bRet
;
91 if (This
->szLines
[i
][0] != TEXT('\0') &&
92 !GetTextExtentPoint(hDC
,
94 _tcslen(This
->szLines
[i
]),
105 ReleaseDC(This
->hWnd
,
110 This
->LineSpacing
= 0;
112 /* calculate the line spacing */
114 i
!= CLOCKWND_FORMAT_COUNT
;
117 if (This
->LineSizes
[i
].cx
> 0)
119 This
->LineSpacing
+= This
->LineSizes
[i
].cy
;
126 /* We want a spaceing of 1/2 line */
127 This
->LineSpacing
= (This
->LineSpacing
/ c
) / 2;
138 TrayClockWnd_GetMinimumSize(IN OUT PTRAY_CLOCK_WND_DATA This
,
142 WORD iLinesVisible
= 0;
144 SIZE szMax
= { 0, 0 };
146 if (!This
->LinesMeasured
)
147 This
->LinesMeasured
= TrayClockWnd_MeasureLines(This
);
149 if (!This
->LinesMeasured
)
153 i
!= CLOCKWND_FORMAT_COUNT
;
156 if (This
->LineSizes
[i
].cx
!= 0)
158 if (iLinesVisible
> 0)
162 if (szMax
.cy
+ This
->LineSizes
[i
].cy
+ (LONG
)This
->LineSpacing
>
163 pSize
->cy
- (2 * TRAY_CLOCK_WND_SPACING_Y
))
170 if (This
->LineSizes
[i
].cx
> pSize
->cx
- (2 * TRAY_CLOCK_WND_SPACING_X
))
174 /* Add line spacing */
175 szMax
.cy
+= This
->LineSpacing
;
180 /* Increase maximum rectangle */
181 szMax
.cy
+= This
->LineSizes
[i
].cy
;
182 if (This
->LineSizes
[i
].cx
> szMax
.cx
- (2 * TRAY_CLOCK_WND_SPACING_X
))
183 szMax
.cx
= This
->LineSizes
[i
].cx
+ (2 * TRAY_CLOCK_WND_SPACING_X
);
187 szMax
.cx
+= 2 * TRAY_CLOCK_WND_SPACING_X
;
188 szMax
.cy
+= 2 * TRAY_CLOCK_WND_SPACING_Y
;
192 return iLinesVisible
;
197 TrayClockWnd_UpdateWnd(IN OUT PTRAY_CLOCK_WND_DATA This
)
200 INT BufSize
, iRet
, i
;
203 ZeroMemory(This
->LineSizes
,
204 sizeof(This
->LineSizes
));
206 szPrevCurrent
= This
->CurrentSize
;
209 i
!= CLOCKWND_FORMAT_COUNT
;
212 This
->szLines
[i
][0] = TEXT('\0');
213 BufSize
= sizeof(This
->szLines
[0]) / sizeof(This
->szLines
[0][0]);
215 if (ClockWndFormats
[i
].IsTime
)
217 iRet
= GetTimeFormat(LOCALE_USER_DEFAULT
,
218 ClockWndFormats
[i
].dwFormatFlags
,
220 ClockWndFormats
[i
].lpFormat
,
226 iRet
= GetDateFormat(LOCALE_USER_DEFAULT
,
227 ClockWndFormats
[i
].dwFormatFlags
,
229 ClockWndFormats
[i
].lpFormat
,
234 if (iRet
!= 0 && i
== 0)
236 /* Set the window text to the time only */
237 SetWindowText(This
->hWnd
,
242 This
->LinesMeasured
= TrayClockWnd_MeasureLines(This
);
244 if (This
->LinesMeasured
&&
245 GetClientRect(This
->hWnd
,
250 szWnd
.cx
= rcClient
.right
;
251 szWnd
.cy
= rcClient
.bottom
;
253 This
->VisibleLines
= TrayClockWnd_GetMinimumSize(This
,
256 This
->CurrentSize
= szWnd
;
259 if (IsWindowVisible(This
->hWnd
))
261 InvalidateRect(This
->hWnd
,
265 if (This
->hWndNotify
!= NULL
&&
266 (szPrevCurrent
.cx
!= This
->CurrentSize
.cx
||
267 szPrevCurrent
.cy
!= This
->CurrentSize
.cy
))
271 nmh
.hwndFrom
= This
->hWnd
;
272 nmh
.idFrom
= GetWindowLongPtr(This
->hWnd
,
274 nmh
.code
= NTNWM_REALIGN
;
276 SendMessage(This
->hWndNotify
,
285 TrayClockWnd_Update(IN OUT PTRAY_CLOCK_WND_DATA This
)
287 GetLocalTime(&This
->LocalTime
);
288 TrayClockWnd_UpdateWnd(This
);
292 TrayClockWnd_CalculateDueTime(IN OUT PTRAY_CLOCK_WND_DATA This
)
296 /* Calculate the due time */
297 GetLocalTime(&This
->LocalTime
);
298 uiDueTime
= 1000 - (UINT
)This
->LocalTime
.wMilliseconds
;
299 uiDueTime
+= (59 - (UINT
)This
->LocalTime
.wSecond
) * 1000;
301 if (uiDueTime
< USER_TIMER_MINIMUM
|| uiDueTime
> USER_TIMER_MAXIMUM
)
305 /* Add an artificial delay of 0.05 seconds to make sure the timer
306 doesn't fire too early*/
314 TrayClockWnd_ResetTime(IN OUT PTRAY_CLOCK_WND_DATA This
)
319 /* Disable all timers */
320 if (This
->IsTimerEnabled
)
322 KillTimer(This
->hWnd
,
324 This
->IsTimerEnabled
= FALSE
;
327 if (This
->IsInitTimerEnabled
)
329 KillTimer(This
->hWnd
,
330 ID_TRAYCLOCK_TIMER_INIT
);
333 uiDueTime
= TrayClockWnd_CalculateDueTime(This
);
335 /* Set the new timer */
336 Ret
= SetTimer(This
->hWnd
,
337 ID_TRAYCLOCK_TIMER_INIT
,
340 This
->IsInitTimerEnabled
= Ret
;
342 /* Update the time */
343 TrayClockWnd_Update(This
);
349 TrayClockWnd_CalibrateTimer(IN OUT PTRAY_CLOCK_WND_DATA This
)
354 /* Kill the initialization timer */
355 KillTimer(This
->hWnd
,
356 ID_TRAYCLOCK_TIMER_INIT
);
357 This
->IsInitTimerEnabled
= FALSE
;
359 uiDueTime
= TrayClockWnd_CalculateDueTime(This
);
361 if (uiDueTime
> (60 * 1000) - 200)
363 /* The update of the clock will be up to 200 ms late, but that's
364 acceptable. We're going to setup a timer that fires every
366 Ret
= SetTimer(This
->hWnd
,
370 This
->IsTimerEnabled
= Ret
;
372 /* Update the time */
373 TrayClockWnd_Update(This
);
377 /* Recalibrate the timer and recalculate again when the current
379 TrayClockWnd_ResetTime(This
);
384 TrayClockWnd_NCDestroy(IN OUT PTRAY_CLOCK_WND_DATA This
)
386 /* Disable all timers */
387 if (This
->IsTimerEnabled
)
389 KillTimer(This
->hWnd
,
393 if (This
->IsInitTimerEnabled
)
395 KillTimer(This
->hWnd
,
396 ID_TRAYCLOCK_TIMER_INIT
);
399 /* Free allocated resources */
400 SetWindowLongPtr(This
->hWnd
,
403 HeapFree(hProcessHeap
,
409 TrayClockWnd_Paint(IN OUT PTRAY_CLOCK_WND_DATA This
,
414 int iPrevBkMode
, i
, line
;
416 if (This
->LinesMeasured
&&
417 GetClientRect(This
->hWnd
,
420 iPrevBkMode
= SetBkMode(hDC
,
423 hPrevFont
= SelectObject(hDC
,
426 rcClient
.left
= (rcClient
.right
/ 2) - (This
->CurrentSize
.cx
/ 2);
427 rcClient
.top
= (rcClient
.bottom
/ 2) - (This
->CurrentSize
.cy
/ 2);
428 rcClient
.right
= rcClient
.left
+ This
->CurrentSize
.cx
;
429 rcClient
.bottom
= rcClient
.top
+ This
->CurrentSize
.cy
;
431 for (i
= 0, line
= 0;
432 i
!= CLOCKWND_FORMAT_COUNT
&& line
< This
->VisibleLines
;
435 if (This
->LineSizes
[i
].cx
!= 0)
438 rcClient
.left
+ (This
->CurrentSize
.cx
/ 2) - (This
->LineSizes
[i
].cx
/ 2) +
439 TRAY_CLOCK_WND_SPACING_X
,
440 rcClient
.top
+ TRAY_CLOCK_WND_SPACING_Y
,
442 _tcslen(This
->szLines
[i
]));
444 rcClient
.top
+= This
->LineSizes
[i
].cy
+ This
->LineSpacing
;
458 TrayClockWnd_SetFont(IN OUT PTRAY_CLOCK_WND_DATA This
,
462 This
->hFont
= hNewFont
;
463 This
->LinesMeasured
= TrayClockWnd_MeasureLines(This
);
466 InvalidateRect(This
->hWnd
,
472 static LRESULT CALLBACK
473 TrayClockWndProc(IN HWND hwnd
,
478 PTRAY_CLOCK_WND_DATA This
= NULL
;
481 if (uMsg
!= WM_NCCREATE
)
483 This
= (PTRAY_CLOCK_WND_DATA
)GetWindowLongPtr(hwnd
,
487 if (This
!= NULL
|| uMsg
== WM_NCCREATE
)
495 HDC hDC
= (HDC
)wParam
;
499 hDC
= BeginPaint(This
->hWnd
,
505 TrayClockWnd_Paint(This
,
520 case ID_TRAYCLOCK_TIMER
:
521 TrayClockWnd_Update(This
);
524 case ID_TRAYCLOCK_TIMER_INIT
:
525 TrayClockWnd_CalibrateTimer(This
);
531 /* We want the user to be able to drag the task bar when clicking the clock */
535 case TCWM_GETMINIMUMSIZE
:
537 This
->IsHorizontal
= (BOOL
)wParam
;
539 Ret
= (LRESULT
)TrayClockWnd_GetMinimumSize(This
,
545 case TCWM_UPDATETIME
:
547 Ret
= (LRESULT
)TrayClockWnd_ResetTime(This
);
553 LPCREATESTRUCT CreateStruct
= (LPCREATESTRUCT
)lParam
;
554 This
= (PTRAY_CLOCK_WND_DATA
)CreateStruct
->lpCreateParams
;
556 This
->hWndNotify
= CreateStruct
->hwndParent
;
558 SetWindowLongPtr(hwnd
,
567 TrayClockWnd_SetFont(This
,
569 (BOOL
)LOWORD(lParam
));
574 TrayClockWnd_ResetTime(This
);
578 TrayClockWnd_NCDestroy(This
);
585 szClient
.cx
= LOWORD(lParam
);
586 szClient
.cy
= HIWORD(lParam
);
587 This
->VisibleLines
= TrayClockWnd_GetMinimumSize(This
,
590 This
->CurrentSize
= szClient
;
592 InvalidateRect(This
->hWnd
,
599 Ret
= DefWindowProc(hwnd
,
611 CreateTrayClockWnd(IN HWND hWndParent
,
614 PTRAY_CLOCK_WND_DATA TcData
;
618 TcData
= HeapAlloc(hProcessHeap
,
626 TcData
->IsHorizontal
= TRUE
;
627 /* Create the window. The tray window is going to move it to the correct
628 position and resize it as needed. */
629 dwStyle
= WS_CHILD
| WS_CLIPSIBLINGS
;
631 dwStyle
|= WS_VISIBLE
;
633 hWnd
= CreateWindowEx(0,
648 HeapFree(hProcessHeap
,
659 RegisterTrayClockWndClass(VOID
)
661 WNDCLASS wcTrayClock
;
663 wcTrayClock
.style
= CS_DBLCLKS
;
664 wcTrayClock
.lpfnWndProc
= TrayClockWndProc
;
665 wcTrayClock
.cbClsExtra
= 0;
666 wcTrayClock
.cbWndExtra
= sizeof(PTRAY_CLOCK_WND_DATA
);
667 wcTrayClock
.hInstance
= hExplorerInstance
;
668 wcTrayClock
.hIcon
= NULL
;
669 wcTrayClock
.hCursor
= LoadCursor(NULL
,
671 wcTrayClock
.hbrBackground
= (HBRUSH
)(COLOR_3DFACE
+ 1);
672 wcTrayClock
.lpszMenuName
= NULL
;
673 wcTrayClock
.lpszClassName
= szTrayClockWndClass
;
675 return RegisterClass(&wcTrayClock
) != 0;
679 UnregisterTrayClockWndClass(VOID
)
681 UnregisterClass(szTrayClockWndClass
,
689 static const TCHAR szTrayNotifyWndClass
[] = TEXT("TrayNotifyWnd");
691 #define TRAY_NOTIFY_WND_SPACING_X 2
692 #define TRAY_NOTIFY_WND_SPACING_Y 2
694 typedef struct _TRAY_NOTIFY_WND_DATA
701 ITrayWindow
*TrayWindow
;
708 DWORD IsHorizontal
: 1;
711 } TRAY_NOTIFY_WND_DATA
, *PTRAY_NOTIFY_WND_DATA
;
714 TrayNotifyWnd_UpdateStyle(IN OUT PTRAY_NOTIFY_WND_DATA This
)
716 RECT rcClient
= { 0, 0, 0, 0 };
718 if (AdjustWindowRectEx(&rcClient
,
719 GetWindowLongPtr(This
->hWnd
,
722 GetWindowLongPtr(This
->hWnd
,
725 This
->szNonClient
.cx
= rcClient
.right
- rcClient
.left
;
726 This
->szNonClient
.cy
= rcClient
.bottom
- rcClient
.top
;
729 This
->szNonClient
.cx
= This
->szNonClient
.cy
= 0;
733 TrayNotifyWnd_Create(IN OUT PTRAY_NOTIFY_WND_DATA This
)
735 This
->hWndTrayClock
= CreateTrayClockWnd(This
->hWnd
,
738 TrayNotifyWnd_UpdateStyle(This
);
742 TrayNotifyWnd_NCDestroy(IN OUT PTRAY_NOTIFY_WND_DATA This
)
744 SetWindowLongPtr(This
->hWnd
,
747 HeapFree(hProcessHeap
,
753 TrayNotifyWnd_GetMinimumSize(IN OUT PTRAY_NOTIFY_WND_DATA This
,
757 This
->IsHorizontal
= Horizontal
;
759 if (!This
->HideClock
)
761 SIZE szClock
= { 0, 0 };
765 szClock
.cy
= pSize
->cy
- This
->szNonClient
.cy
- (2 * TRAY_NOTIFY_WND_SPACING_Y
);
771 szClock
.cx
= pSize
->cx
- This
->szNonClient
.cx
- (2 * TRAY_NOTIFY_WND_SPACING_X
);
776 SendMessage(This
->hWndTrayClock
,
781 This
->szTrayClockMin
= szClock
;
785 This
->szTrayClockMin
= This
->szNonClient
;
789 pSize
->cx
= This
->szNonClient
.cx
+ (2 * TRAY_NOTIFY_WND_SPACING_X
);
791 if (!This
->HideClock
)
792 pSize
->cx
+= TRAY_NOTIFY_WND_SPACING_X
+ This
->szTrayClockMin
.cx
;
796 pSize
->cy
= This
->szNonClient
.cy
+ (2 * TRAY_NOTIFY_WND_SPACING_Y
);
798 if (!This
->HideClock
)
799 pSize
->cy
+= TRAY_NOTIFY_WND_SPACING_Y
+ This
->szTrayClockMin
.cy
;
806 TrayNotifyWnd_Size(IN OUT PTRAY_NOTIFY_WND_DATA This
,
807 IN
const SIZE
*pszClient
)
809 if (!This
->HideClock
)
814 if (This
->IsHorizontal
)
816 ptClock
.x
= pszClient
->cx
- TRAY_NOTIFY_WND_SPACING_X
- This
->szTrayClockMin
.cx
;
817 ptClock
.y
= TRAY_NOTIFY_WND_SPACING_Y
;
818 szClock
.cx
= This
->szTrayClockMin
.cx
;
819 szClock
.cy
= pszClient
->cy
- (2 * TRAY_NOTIFY_WND_SPACING_Y
);
823 ptClock
.x
= TRAY_NOTIFY_WND_SPACING_X
;
824 ptClock
.y
= pszClient
->cy
- TRAY_NOTIFY_WND_SPACING_Y
- This
->szTrayClockMin
.cy
;
825 szClock
.cx
= pszClient
->cx
- (2 * TRAY_NOTIFY_WND_SPACING_X
);
826 szClock
.cy
= This
->szTrayClockMin
.cy
;
829 SetWindowPos(This
->hWndTrayClock
,
839 static LRESULT CALLBACK
840 TrayNotifyWndProc(IN HWND hwnd
,
845 PTRAY_NOTIFY_WND_DATA This
= NULL
;
848 if (uMsg
!= WM_NCCREATE
)
850 This
= (PTRAY_NOTIFY_WND_DATA
)GetWindowLongPtr(hwnd
,
854 if (This
!= NULL
|| uMsg
== WM_NCCREATE
)
858 case TNWM_GETMINIMUMSIZE
:
860 Ret
= (LRESULT
)TrayNotifyWnd_GetMinimumSize(This
,
866 case TNWM_UPDATETIME
:
868 if (This
->hWndTrayClock
!= NULL
)
870 /* Forward the message to the tray clock window procedure */
871 Ret
= TrayClockWndProc(This
->hWndTrayClock
,
883 szClient
.cx
= LOWORD(lParam
);
884 szClient
.cy
= HIWORD(lParam
);
886 TrayNotifyWnd_Size(This
,
892 /* We want the user to be able to drag the task bar when clicking the
893 tray notification window */
899 BOOL PrevHidden
= This
->HideClock
;
900 This
->HideClock
= (wParam
== 0);
902 if (This
->hWndTrayClock
!= NULL
&& PrevHidden
!= This
->HideClock
)
904 ShowWindow(This
->hWndTrayClock
,
905 This
->HideClock
? SW_HIDE
: SW_SHOW
);
908 Ret
= (LRESULT
)(!PrevHidden
);
914 const NMHDR
*nmh
= (const NMHDR
*)lParam
;
916 if (nmh
->hwndFrom
== This
->hWndTrayClock
)
918 /* Pass down notifications */
919 Ret
= SendMessage(This
->hWndNotify
,
929 if (This
->hWndTrayClock
!= NULL
)
931 SendMessage(This
->hWndTrayClock
,
936 goto HandleDefaultMessage
;
941 LPCREATESTRUCT CreateStruct
= (LPCREATESTRUCT
)lParam
;
942 This
= (PTRAY_NOTIFY_WND_DATA
)CreateStruct
->lpCreateParams
;
944 This
->hWndNotify
= CreateStruct
->hwndParent
;
946 SetWindowLongPtr(hwnd
,
954 TrayNotifyWnd_Create(This
);
958 TrayNotifyWnd_NCDestroy(This
);
962 HandleDefaultMessage
:
963 Ret
= DefWindowProc(hwnd
,
975 CreateTrayNotifyWnd(IN OUT ITrayWindow
*TrayWindow
,
978 PTRAY_NOTIFY_WND_DATA TnData
;
982 hWndTrayWindow
= ITrayWindow_GetHWND(TrayWindow
);
983 if (hWndTrayWindow
== NULL
)
986 TnData
= HeapAlloc(hProcessHeap
,
994 TnData
->TrayWindow
= TrayWindow
;
995 TnData
->HideClock
= bHideClock
;
997 /* Create the window. The tray window is going to move it to the correct
998 position and resize it as needed. */
999 hWnd
= CreateWindowEx(WS_EX_STATICEDGE
,
1000 szTrayNotifyWndClass
,
1002 WS_CHILD
| WS_VISIBLE
| WS_CLIPSIBLINGS
| WS_CLIPCHILDREN
,
1014 HeapFree(hProcessHeap
,
1024 RegisterTrayNotifyWndClass(VOID
)
1029 wcTrayWnd
.style
= CS_DBLCLKS
;
1030 wcTrayWnd
.lpfnWndProc
= TrayNotifyWndProc
;
1031 wcTrayWnd
.cbClsExtra
= 0;
1032 wcTrayWnd
.cbWndExtra
= sizeof(PTRAY_NOTIFY_WND_DATA
);
1033 wcTrayWnd
.hInstance
= hExplorerInstance
;
1034 wcTrayWnd
.hIcon
= NULL
;
1035 wcTrayWnd
.hCursor
= LoadCursor(NULL
,
1037 wcTrayWnd
.hbrBackground
= (HBRUSH
)(COLOR_3DFACE
+ 1);
1038 wcTrayWnd
.lpszMenuName
= NULL
;
1039 wcTrayWnd
.lpszClassName
= szTrayNotifyWndClass
;
1041 Ret
= RegisterClass(&wcTrayWnd
) != 0;
1045 Ret
= RegisterTrayClockWndClass();
1048 UnregisterClass(szTrayNotifyWndClass
,
1057 UnregisterTrayNotifyWndClass(VOID
)
1059 UnregisterTrayClockWndClass();
1061 UnregisterClass(szTrayNotifyWndClass
,