4 * Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org>
5 * Copyright 2018 Ged Murphy <gedmurphy@reactos.org>
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
33 } ClockWndFormats
[] = {
35 { FALSE
, 0, L
"dddd" },
36 { FALSE
, DATE_SHORTDATE
, NULL
}
38 const UINT ClockWndFormatsCount
= _ARRAYSIZE(ClockWndFormats
);
40 #define CLOCKWND_FORMAT_COUNT ClockWndFormatsCount
42 extern const WCHAR szTrayClockWndClass
[];
45 public CComCoClass
<CTrayClockWnd
>,
46 public CComObjectRootEx
<CComMultiThreadModelNoCS
>,
47 public CWindowImpl
< CTrayClockWnd
, CWindow
, CControlWinTraits
>,
60 DWORD IsTimerEnabled
: 1;
61 DWORD IsInitTimerEnabled
: 1;
62 DWORD LinesMeasured
: 1;
63 DWORD IsHorizontal
: 1;
69 SIZE LineSizes
[CLOCKWND_FORMAT_COUNT
];
70 WCHAR szLines
[CLOCKWND_FORMAT_COUNT
][48];
74 virtual ~CTrayClockWnd();
77 LRESULT
OnThemeChanged();
78 LRESULT
OnThemeChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
81 WORD
GetMinimumSize(IN BOOL Horizontal
, IN OUT PSIZE pSize
);
84 UINT
CalculateDueTime();
86 VOID
CalibrateTimer();
87 LRESULT
OnDestroy(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
88 LRESULT
OnPaint(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
89 VOID
SetFont(IN HFONT hNewFont
, IN BOOL bRedraw
);
90 LRESULT
DrawBackground(HDC hdc
);
91 LRESULT
OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
92 LRESULT
OnTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
93 LRESULT
OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
94 LRESULT
OnNcHitTest(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
95 LRESULT
OnSetFont(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
96 LRESULT
OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
97 LRESULT
OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
98 LRESULT
OnTaskbarSettingsChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
99 LRESULT
OnNcLButtonDblClick(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
103 HRESULT WINAPI
GetWindow(HWND
* phwnd
)
111 HRESULT WINAPI
ContextSensitiveHelp(BOOL fEnterMode
)
116 DECLARE_NOT_AGGREGATABLE(CTrayClockWnd
)
118 DECLARE_PROTECT_FINAL_CONSTRUCT()
119 BEGIN_COM_MAP(CTrayClockWnd
)
120 COM_INTERFACE_ENTRY_IID(IID_IOleWindow
, IOleWindow
)
123 DECLARE_WND_CLASS_EX(szTrayClockWndClass
, CS_DBLCLKS
, COLOR_3DFACE
)
125 BEGIN_MSG_MAP(CTrayClockWnd
)
126 MESSAGE_HANDLER(WM_CREATE
, OnCreate
)
127 MESSAGE_HANDLER(WM_DESTROY
, OnDestroy
)
128 MESSAGE_HANDLER(WM_ERASEBKGND
, OnEraseBackground
)
129 MESSAGE_HANDLER(WM_SIZE
, OnSize
)
130 MESSAGE_HANDLER(WM_PAINT
, OnPaint
)
131 MESSAGE_HANDLER(WM_PRINTCLIENT
, OnPaint
)
132 MESSAGE_HANDLER(WM_THEMECHANGED
, OnThemeChanged
)
133 MESSAGE_HANDLER(WM_TIMER
, OnTimer
)
134 MESSAGE_HANDLER(WM_NCHITTEST
, OnNcHitTest
)
135 MESSAGE_HANDLER(WM_SETFONT
, OnSetFont
)
136 MESSAGE_HANDLER(TNWM_GETMINIMUMSIZE
, OnGetMinimumSize
)
137 MESSAGE_HANDLER(TWM_SETTINGSCHANGED
, OnTaskbarSettingsChanged
)
138 MESSAGE_HANDLER(WM_NCLBUTTONDBLCLK
, OnNcLButtonDblClick
)
141 HRESULT
Initialize(IN HWND hWndParent
);
144 const WCHAR szTrayClockWndClass
[] = L
"TrayClockWClass";
146 #define ID_TRAYCLOCK_TIMER 0
147 #define ID_TRAYCLOCK_TIMER_INIT 1
149 #define TRAY_CLOCK_WND_SPACING_X 0
150 #define TRAY_CLOCK_WND_SPACING_Y 0
152 CTrayClockWnd::CTrayClockWnd() :
158 ZeroMemory(&textColor
, sizeof(textColor
));
159 ZeroMemory(&rcText
, sizeof(rcText
));
160 ZeroMemory(&LocalTime
, sizeof(LocalTime
));
161 ZeroMemory(&CurrentSize
, sizeof(CurrentSize
));
162 ZeroMemory(LineSizes
, sizeof(LineSizes
));
163 ZeroMemory(szLines
, sizeof(szLines
));
165 CTrayClockWnd::~CTrayClockWnd() { }
167 LRESULT
CTrayClockWnd::OnThemeChanged()
173 clockTheme
= OpenThemeData(m_hWnd
, L
"Clock");
177 GetThemeFont(clockTheme
,
184 hFont
= CreateFontIndirectW(&clockFont
);
186 GetThemeColor(clockTheme
,
192 if (this->hFont
!= NULL
)
193 DeleteObject(this->hFont
);
195 SetFont(hFont
, FALSE
);
199 /* We don't need to set a font here, our parent will use
200 * WM_SETFONT to set the right one when themes are not enabled. */
201 textColor
= RGB(0, 0, 0);
204 CloseThemeData(clockTheme
);
209 LRESULT
CTrayClockWnd::OnThemeChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
211 return OnThemeChanged();
214 BOOL
CTrayClockWnd::MeasureLines()
225 hPrevFont
= (HFONT
) SelectObject(hDC
, hFont
);
227 for (i
= 0; i
< CLOCKWND_FORMAT_COUNT
&& bRet
; i
++)
229 if (szLines
[i
][0] != L
'\0' &&
230 !GetTextExtentPointW(hDC
, szLines
[i
], wcslen(szLines
[i
]),
239 SelectObject(hDC
, hPrevFont
);
247 /* calculate the line spacing */
248 for (i
= 0, c
= 0; i
< CLOCKWND_FORMAT_COUNT
; i
++)
250 if (LineSizes
[i
].cx
> 0)
252 LineSpacing
+= LineSizes
[i
].cy
;
259 /* We want a spacing of 1/2 line */
260 LineSpacing
= (LineSpacing
/ c
) / 2;
270 WORD
CTrayClockWnd::GetMinimumSize(IN BOOL Horizontal
, IN OUT PSIZE pSize
)
272 WORD iLinesVisible
= 0;
274 SIZE szMax
= { 0, 0 };
277 LinesMeasured
= MeasureLines();
282 for (i
= 0; i
< CLOCKWND_FORMAT_COUNT
; i
++)
284 if (LineSizes
[i
].cx
!= 0)
286 if (iLinesVisible
> 0)
290 if (szMax
.cy
+ LineSizes
[i
].cy
+ (LONG
) LineSpacing
>
291 pSize
->cy
- (2 * TRAY_CLOCK_WND_SPACING_Y
))
298 if (LineSizes
[i
].cx
> pSize
->cx
- (2 * TRAY_CLOCK_WND_SPACING_X
))
302 /* Add line spacing */
303 szMax
.cy
+= LineSpacing
;
308 /* Increase maximum rectangle */
309 szMax
.cy
+= LineSizes
[i
].cy
;
310 if (LineSizes
[i
].cx
> szMax
.cx
- (2 * TRAY_CLOCK_WND_SPACING_X
))
311 szMax
.cx
= LineSizes
[i
].cx
+ (2 * TRAY_CLOCK_WND_SPACING_X
);
315 szMax
.cx
+= 2 * TRAY_CLOCK_WND_SPACING_X
;
316 szMax
.cy
+= 2 * TRAY_CLOCK_WND_SPACING_Y
;
320 return iLinesVisible
;
323 VOID
CTrayClockWnd::UpdateWnd()
330 ZeroMemory(LineSizes
, sizeof(LineSizes
));
332 szPrevCurrent
= CurrentSize
;
334 for (i
= 0; i
< CLOCKWND_FORMAT_COUNT
; i
++)
336 szLines
[i
][0] = L
'\0';
337 BufSize
= _countof(szLines
[0]);
339 if (ClockWndFormats
[i
].IsTime
)
341 iRet
= GetTimeFormat(LOCALE_USER_DEFAULT
,
342 g_TaskbarSettings
.bShowSeconds
? ClockWndFormats
[i
].dwFormatFlags
: TIME_NOSECONDS
,
344 ClockWndFormats
[i
].lpFormat
,
350 iRet
= GetDateFormat(LOCALE_USER_DEFAULT
,
351 ClockWndFormats
[i
].dwFormatFlags
,
353 ClockWndFormats
[i
].lpFormat
,
358 if (iRet
!= 0 && i
== 0)
360 /* Set the window text to the time only */
361 SetWindowText(szLines
[i
]);
365 LinesMeasured
= MeasureLines();
368 GetClientRect(&rcClient
))
372 szWnd
.cx
= rcClient
.right
;
373 szWnd
.cy
= rcClient
.bottom
;
375 VisibleLines
= GetMinimumSize(IsHorizontal
, &szWnd
);
379 if (IsWindowVisible())
381 InvalidateRect(NULL
, TRUE
);
383 if (szPrevCurrent
.cx
!= CurrentSize
.cx
||
384 szPrevCurrent
.cy
!= CurrentSize
.cy
)
386 /* Ask the parent to resize */
387 NMHDR nmh
= {GetParent(), 0, NTNWM_REALIGN
};
388 GetParent().SendMessage(WM_NOTIFY
, 0, (LPARAM
) &nmh
);
393 VOID
CTrayClockWnd::Update()
395 GetLocalTime(&LocalTime
);
399 UINT
CTrayClockWnd::CalculateDueTime()
403 /* Calculate the due time */
404 GetLocalTime(&LocalTime
);
405 uiDueTime
= 1000 - (UINT
) LocalTime
.wMilliseconds
;
406 if (g_TaskbarSettings
.bShowSeconds
)
407 uiDueTime
+= (UINT
) LocalTime
.wSecond
* 100;
409 uiDueTime
+= (59 - (UINT
) LocalTime
.wSecond
) * 1000;
411 if (uiDueTime
< USER_TIMER_MINIMUM
|| uiDueTime
> USER_TIMER_MAXIMUM
)
415 /* Add an artificial delay of 0.05 seconds to make sure the timer
416 doesn't fire too early*/
423 BOOL
CTrayClockWnd::ResetTime()
428 /* Disable all timers */
431 KillTimer(ID_TRAYCLOCK_TIMER
);
432 IsTimerEnabled
= FALSE
;
435 if (IsInitTimerEnabled
)
437 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
440 uiDueTime
= CalculateDueTime();
442 /* Set the new timer */
443 Ret
= SetTimer(ID_TRAYCLOCK_TIMER_INIT
, uiDueTime
, NULL
) != 0;
444 IsInitTimerEnabled
= Ret
;
446 /* Update the time */
452 VOID
CTrayClockWnd::CalibrateTimer()
456 UINT uiWait1
, uiWait2
;
458 /* Kill the initialization timer */
459 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
460 IsInitTimerEnabled
= FALSE
;
462 uiDueTime
= CalculateDueTime();
464 if (g_TaskbarSettings
.bShowSeconds
)
466 uiWait1
= 1000 - 200;
471 uiWait1
= 60 * 1000 - 200;
475 if (uiDueTime
> uiWait1
)
477 /* The update of the clock will be up to 200 ms late, but that's
478 acceptable. We're going to setup a timer that fires depending
480 Ret
= SetTimer(ID_TRAYCLOCK_TIMER
, uiWait2
, NULL
) != 0;
481 IsTimerEnabled
= Ret
;
483 /* Update the time */
488 /* Recalibrate the timer and recalculate again when the current
489 minute/second ends. */
494 LRESULT
CTrayClockWnd::OnDestroy(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
496 /* Disable all timers */
499 KillTimer(ID_TRAYCLOCK_TIMER
);
502 if (IsInitTimerEnabled
)
504 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
510 LRESULT
CTrayClockWnd::OnPaint(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
518 HDC hDC
= (HDC
) wParam
;
522 hDC
= BeginPaint(&ps
);
529 GetClientRect(&rcClient
))
531 iPrevBkMode
= SetBkMode(hDC
, TRANSPARENT
);
533 SetTextColor(hDC
, textColor
);
535 hPrevFont
= (HFONT
) SelectObject(hDC
, hFont
);
537 rcClient
.left
= (rcClient
.right
/ 2) - (CurrentSize
.cx
/ 2);
538 rcClient
.top
= (rcClient
.bottom
/ 2) - (CurrentSize
.cy
/ 2);
539 rcClient
.right
= rcClient
.left
+ CurrentSize
.cx
;
540 rcClient
.bottom
= rcClient
.top
+ CurrentSize
.cy
;
542 for (i
= 0, line
= 0;
543 i
< CLOCKWND_FORMAT_COUNT
&& line
< VisibleLines
;
546 if (LineSizes
[i
].cx
!= 0)
549 rcClient
.left
+ (CurrentSize
.cx
/ 2) - (LineSizes
[i
].cx
/ 2) +
550 TRAY_CLOCK_WND_SPACING_X
,
551 rcClient
.top
+ TRAY_CLOCK_WND_SPACING_Y
,
555 rcClient
.top
+= LineSizes
[i
].cy
+ LineSpacing
;
560 SelectObject(hDC
, hPrevFont
);
562 SetBkMode(hDC
, iPrevBkMode
);
573 VOID
CTrayClockWnd::SetFont(IN HFONT hNewFont
, IN BOOL bRedraw
)
576 LinesMeasured
= MeasureLines();
579 InvalidateRect(NULL
, TRUE
);
583 LRESULT
CTrayClockWnd::DrawBackground(HDC hdc
)
587 GetClientRect(&rect
);
588 DrawThemeParentBackground(m_hWnd
, hdc
, &rect
);
593 LRESULT
CTrayClockWnd::OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
595 HDC hdc
= (HDC
) wParam
;
603 return DrawBackground(hdc
);
606 LRESULT
CTrayClockWnd::OnTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
610 case ID_TRAYCLOCK_TIMER
:
614 case ID_TRAYCLOCK_TIMER_INIT
:
621 LRESULT
CTrayClockWnd::OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
623 IsHorizontal
= (BOOL
) wParam
;
625 return (LRESULT
) GetMinimumSize((BOOL
) wParam
, (PSIZE
) lParam
) != 0;
628 LRESULT
CTrayClockWnd::OnNcHitTest(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
630 return HTTRANSPARENT
;
633 LRESULT
CTrayClockWnd::OnSetFont(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
635 SetFont((HFONT
) wParam
, (BOOL
) LOWORD(lParam
));
639 LRESULT
CTrayClockWnd::OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
645 LRESULT
CTrayClockWnd::OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
649 szClient
.cx
= LOWORD(lParam
);
650 szClient
.cy
= HIWORD(lParam
);
652 VisibleLines
= GetMinimumSize(IsHorizontal
, &szClient
);
653 CurrentSize
= szClient
;
655 InvalidateRect(NULL
, TRUE
);
659 LRESULT
CTrayClockWnd::OnTaskbarSettingsChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
661 BOOL bRealign
= FALSE
;
663 TaskbarSettings
* newSettings
= (TaskbarSettings
*)lParam
;
664 if (newSettings
->bShowSeconds
!= g_TaskbarSettings
.bShowSeconds
)
666 g_TaskbarSettings
.bShowSeconds
= newSettings
->bShowSeconds
;
670 if (newSettings
->sr
.HideClock
!= g_TaskbarSettings
.sr
.HideClock
)
672 g_TaskbarSettings
.sr
.HideClock
= newSettings
->sr
.HideClock
;
673 ShowWindow(g_TaskbarSettings
.sr
.HideClock
? SW_HIDE
: SW_SHOW
);
679 /* Ask the parent to resize */
680 NMHDR nmh
= {GetParent(), 0, NTNWM_REALIGN
};
681 GetParent().SendMessage(WM_NOTIFY
, 0, (LPARAM
) &nmh
);
687 LRESULT
CTrayClockWnd::OnNcLButtonDblClick(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
689 if (IsWindowVisible())
691 /* We get all WM_NCLBUTTONDBLCLK for the taskbar so we need to check if it is on the clock*/
693 if (GetWindowRect(&rcClock
))
696 ptClick
.x
= MAKEPOINTS(lParam
).x
;
697 ptClick
.y
= MAKEPOINTS(lParam
).y
;
698 if (PtInRect(&rcClock
, ptClick
))
700 //FIXME: use SHRunControlPanel
701 ShellExecuteW(m_hWnd
, NULL
, L
"timedate.cpl", NULL
, NULL
, SW_NORMAL
);
708 HRESULT
CTrayClockWnd::Initialize(IN HWND hWndParent
)
712 /* Create the window. The tray window is going to move it to the correct
713 position and resize it as needed. */
714 DWORD dwStyle
= WS_CHILD
| WS_CLIPSIBLINGS
;
715 if (!g_TaskbarSettings
.sr
.HideClock
)
716 dwStyle
|= WS_VISIBLE
;
718 Create(hWndParent
, 0, NULL
, dwStyle
);
722 SetWindowTheme(m_hWnd
, L
"TrayNotify", NULL
);
728 HRESULT
CTrayClockWnd_CreateInstance(HWND hwndParent
, REFIID riid
, void **ppv
)
730 return ShellObjectCreatorInit
<CTrayClockWnd
>(hwndParent
, riid
, ppv
);