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 static const WCHAR szTrayClockWndClass
[] = L
"TrayClockWClass";
45 public CComCoClass
<CTrayClockWnd
>,
46 public CComObjectRootEx
<CComMultiThreadModelNoCS
>,
47 public CWindowImpl
< CTrayClockWnd
, CWindow
, CControlWinTraits
>,
61 DWORD IsTimerEnabled
: 1;
62 DWORD IsInitTimerEnabled
: 1;
63 DWORD LinesMeasured
: 1;
64 DWORD IsHorizontal
: 1;
70 SIZE LineSizes
[CLOCKWND_FORMAT_COUNT
];
71 WCHAR szLines
[CLOCKWND_FORMAT_COUNT
][48];
75 virtual ~CTrayClockWnd();
78 LRESULT
OnThemeChanged();
79 LRESULT
OnThemeChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
82 WORD
GetMinimumSize(IN BOOL Horizontal
, IN OUT PSIZE pSize
);
85 UINT
CalculateDueTime();
87 VOID
CalibrateTimer();
88 LRESULT
OnDestroy(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
89 LRESULT
OnPaint(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
90 VOID
SetFont(IN HFONT hNewFont
, IN BOOL bRedraw
);
91 LRESULT
DrawBackground(HDC hdc
);
92 LRESULT
OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
93 LRESULT
OnTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
94 LRESULT
OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
95 LRESULT
OnContextMenu(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
96 LRESULT
OnSetFont(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
97 LRESULT
OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
98 LRESULT
OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
99 LRESULT
OnTaskbarSettingsChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
100 LRESULT
OnLButtonDblClick(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
104 HRESULT WINAPI
GetWindow(HWND
* phwnd
)
112 HRESULT WINAPI
ContextSensitiveHelp(BOOL fEnterMode
)
117 DECLARE_NOT_AGGREGATABLE(CTrayClockWnd
)
119 DECLARE_PROTECT_FINAL_CONSTRUCT()
120 BEGIN_COM_MAP(CTrayClockWnd
)
121 COM_INTERFACE_ENTRY_IID(IID_IOleWindow
, IOleWindow
)
124 DECLARE_WND_CLASS_EX(szTrayClockWndClass
, CS_DBLCLKS
, COLOR_3DFACE
)
126 BEGIN_MSG_MAP(CTrayClockWnd
)
127 MESSAGE_HANDLER(WM_CREATE
, OnCreate
)
128 MESSAGE_HANDLER(WM_DESTROY
, OnDestroy
)
129 MESSAGE_HANDLER(WM_ERASEBKGND
, OnEraseBackground
)
130 MESSAGE_HANDLER(WM_SIZE
, OnSize
)
131 MESSAGE_HANDLER(WM_PAINT
, OnPaint
)
132 MESSAGE_HANDLER(WM_PRINTCLIENT
, OnPaint
)
133 MESSAGE_HANDLER(WM_THEMECHANGED
, OnThemeChanged
)
134 MESSAGE_HANDLER(WM_TIMER
, OnTimer
)
135 MESSAGE_HANDLER(WM_CONTEXTMENU
, OnContextMenu
)
136 MESSAGE_HANDLER(WM_SETFONT
, OnSetFont
)
137 MESSAGE_HANDLER(TNWM_GETMINIMUMSIZE
, OnGetMinimumSize
)
138 MESSAGE_HANDLER(TWM_SETTINGSCHANGED
, OnTaskbarSettingsChanged
)
139 MESSAGE_HANDLER(WM_LBUTTONDBLCLK
, OnLButtonDblClick
)
142 HRESULT
Initialize(IN HWND hWndParent
);
145 #define ID_TRAYCLOCK_TIMER 0
146 #define ID_TRAYCLOCK_TIMER_INIT 1
148 #define TRAY_CLOCK_WND_SPACING_X 5
149 #define TRAY_CLOCK_WND_SPACING_Y 0
151 CTrayClockWnd::CTrayClockWnd() :
157 ZeroMemory(&textColor
, sizeof(textColor
));
158 ZeroMemory(&rcText
, sizeof(rcText
));
159 ZeroMemory(&LocalTime
, sizeof(LocalTime
));
160 ZeroMemory(&CurrentSize
, sizeof(CurrentSize
));
161 ZeroMemory(LineSizes
, sizeof(LineSizes
));
162 ZeroMemory(szLines
, sizeof(szLines
));
164 CTrayClockWnd::~CTrayClockWnd() { }
166 LRESULT
CTrayClockWnd::OnThemeChanged()
172 clockTheme
= OpenThemeData(m_hWnd
, L
"Clock");
176 GetThemeFont(clockTheme
,
183 hFont
= CreateFontIndirectW(&clockFont
);
185 GetThemeColor(clockTheme
,
191 if (this->hFont
!= NULL
)
192 DeleteObject(this->hFont
);
194 SetFont(hFont
, FALSE
);
198 /* We don't need to set a font here, our parent will use
199 * WM_SETFONT to set the right one when themes are not enabled. */
200 textColor
= RGB(0, 0, 0);
203 CloseThemeData(clockTheme
);
208 LRESULT
CTrayClockWnd::OnThemeChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
210 return OnThemeChanged();
213 BOOL
CTrayClockWnd::MeasureLines()
224 hPrevFont
= (HFONT
) SelectObject(hDC
, hFont
);
226 for (i
= 0; i
< CLOCKWND_FORMAT_COUNT
&& bRet
; i
++)
228 if (szLines
[i
][0] != L
'\0' &&
229 !GetTextExtentPointW(hDC
, szLines
[i
], wcslen(szLines
[i
]),
238 SelectObject(hDC
, hPrevFont
);
246 /* calculate the line spacing */
247 for (i
= 0, c
= 0; i
< CLOCKWND_FORMAT_COUNT
; i
++)
249 if (LineSizes
[i
].cx
> 0)
251 LineSpacing
+= LineSizes
[i
].cy
;
258 /* We want a spacing of 1/2 line */
259 LineSpacing
= (LineSpacing
/ c
) / 2;
269 WORD
CTrayClockWnd::GetMinimumSize(IN BOOL Horizontal
, IN OUT PSIZE pSize
)
271 WORD iLinesVisible
= 0;
273 SIZE szMax
= { 0, 0 };
276 LinesMeasured
= MeasureLines();
281 for (i
= 0; i
< CLOCKWND_FORMAT_COUNT
; i
++)
283 if (LineSizes
[i
].cx
!= 0)
285 if (iLinesVisible
> 0)
289 if (szMax
.cy
+ LineSizes
[i
].cy
+ (LONG
) LineSpacing
>
290 pSize
->cy
- (2 * TRAY_CLOCK_WND_SPACING_Y
))
297 if (LineSizes
[i
].cx
> pSize
->cx
- (2 * TRAY_CLOCK_WND_SPACING_X
))
301 /* Add line spacing */
302 szMax
.cy
+= LineSpacing
;
307 /* Increase maximum rectangle */
308 szMax
.cy
+= LineSizes
[i
].cy
;
309 if (LineSizes
[i
].cx
> szMax
.cx
)
310 szMax
.cx
= LineSizes
[i
].cx
;
314 szMax
.cx
+= 2 * TRAY_CLOCK_WND_SPACING_X
;
315 szMax
.cy
+= 2 * TRAY_CLOCK_WND_SPACING_Y
;
319 return iLinesVisible
;
322 VOID
CTrayClockWnd::UpdateWnd()
329 ZeroMemory(LineSizes
, sizeof(LineSizes
));
331 szPrevCurrent
= CurrentSize
;
333 for (i
= 0; i
< CLOCKWND_FORMAT_COUNT
; i
++)
335 szLines
[i
][0] = L
'\0';
336 BufSize
= _countof(szLines
[0]);
338 if (ClockWndFormats
[i
].IsTime
)
340 iRet
= GetTimeFormat(LOCALE_USER_DEFAULT
,
341 g_TaskbarSettings
.bShowSeconds
? ClockWndFormats
[i
].dwFormatFlags
: TIME_NOSECONDS
,
343 ClockWndFormats
[i
].lpFormat
,
349 iRet
= GetDateFormat(LOCALE_USER_DEFAULT
,
350 ClockWndFormats
[i
].dwFormatFlags
,
352 ClockWndFormats
[i
].lpFormat
,
357 if (iRet
!= 0 && i
== 0)
359 /* Set the window text to the time only */
360 SetWindowText(szLines
[i
]);
364 LinesMeasured
= MeasureLines();
367 GetClientRect(&rcClient
))
371 szWnd
.cx
= rcClient
.right
;
372 szWnd
.cy
= rcClient
.bottom
;
374 VisibleLines
= GetMinimumSize(IsHorizontal
, &szWnd
);
378 if (IsWindowVisible())
380 InvalidateRect(NULL
, TRUE
);
382 if (szPrevCurrent
.cx
!= CurrentSize
.cx
||
383 szPrevCurrent
.cy
!= CurrentSize
.cy
)
385 /* Ask the parent to resize */
386 NMHDR nmh
= {GetParent(), 0, NTNWM_REALIGN
};
387 GetParent().SendMessage(WM_NOTIFY
, 0, (LPARAM
) &nmh
);
391 int iDateLength
= GetDateFormat(LOCALE_USER_DEFAULT
,
397 if (iDateLength
<= 0)
402 WCHAR
* szDate
= new WCHAR
[iDateLength
];
403 if (GetDateFormat(LOCALE_USER_DEFAULT
,
410 m_tooltip
.UpdateTipText(m_hWnd
,
411 reinterpret_cast<UINT_PTR
>(m_hWnd
),
417 VOID
CTrayClockWnd::Update()
419 GetLocalTime(&LocalTime
);
423 UINT
CTrayClockWnd::CalculateDueTime()
427 GetLocalTime(&LocalTime
);
428 uiDueTime
= 1000 - (UINT
) LocalTime
.wMilliseconds
;
429 if (!g_TaskbarSettings
.bShowSeconds
)
430 uiDueTime
+= (59 - (UINT
) LocalTime
.wSecond
) * 1000;
435 BOOL
CTrayClockWnd::ResetTime()
440 /* Disable all timers */
443 KillTimer(ID_TRAYCLOCK_TIMER
);
444 IsTimerEnabled
= FALSE
;
446 else if (IsInitTimerEnabled
)
448 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
451 uiDueTime
= CalculateDueTime();
453 /* Set the new timer */
454 Ret
= SetTimer(ID_TRAYCLOCK_TIMER_INIT
, uiDueTime
, NULL
) != 0;
455 IsInitTimerEnabled
= Ret
;
460 VOID
CTrayClockWnd::CalibrateTimer()
464 UINT uiWait1
, uiWait2
;
466 /* Kill the initialization timer */
467 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
468 IsInitTimerEnabled
= FALSE
;
470 uiDueTime
= CalculateDueTime();
472 if (g_TaskbarSettings
.bShowSeconds
)
474 uiWait1
= 1000 - 200;
479 uiWait1
= 60 * 1000 - 200;
483 if (uiDueTime
> uiWait1
)
485 /* The update of the clock will be up to 200 ms late, but that's
486 acceptable. We're going to setup a timer that fires depending
488 Ret
= SetTimer(ID_TRAYCLOCK_TIMER
, uiWait2
, NULL
) != 0;
489 IsTimerEnabled
= Ret
;
493 /* Recalibrate the timer and recalculate again when the current
494 minute/second ends. */
498 /* Update the time */
502 LRESULT
CTrayClockWnd::OnDestroy(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
504 /* Disable all timers */
507 KillTimer(ID_TRAYCLOCK_TIMER
);
509 else if (IsInitTimerEnabled
)
511 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
517 LRESULT
CTrayClockWnd::OnPaint(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
525 HDC hDC
= (HDC
) wParam
;
529 hDC
= BeginPaint(&ps
);
536 GetClientRect(&rcClient
))
538 iPrevBkMode
= SetBkMode(hDC
, TRANSPARENT
);
540 SetTextColor(hDC
, textColor
);
542 hPrevFont
= (HFONT
) SelectObject(hDC
, hFont
);
544 rcClient
.top
= (rcClient
.bottom
- CurrentSize
.cy
) / 2;
545 rcClient
.bottom
= rcClient
.top
+ CurrentSize
.cy
;
547 for (i
= 0, line
= 0;
548 i
< CLOCKWND_FORMAT_COUNT
&& line
< VisibleLines
;
551 if (LineSizes
[i
].cx
!= 0)
554 (rcClient
.right
- LineSizes
[i
].cx
) / 2,
555 rcClient
.top
+ TRAY_CLOCK_WND_SPACING_Y
,
559 rcClient
.top
+= LineSizes
[i
].cy
+ LineSpacing
;
564 SelectObject(hDC
, hPrevFont
);
566 SetBkMode(hDC
, iPrevBkMode
);
577 VOID
CTrayClockWnd::SetFont(IN HFONT hNewFont
, IN BOOL bRedraw
)
580 LinesMeasured
= MeasureLines();
583 InvalidateRect(NULL
, TRUE
);
587 LRESULT
CTrayClockWnd::DrawBackground(HDC hdc
)
591 GetClientRect(&rect
);
592 DrawThemeParentBackground(m_hWnd
, hdc
, &rect
);
597 LRESULT
CTrayClockWnd::OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
599 HDC hdc
= (HDC
) wParam
;
607 return DrawBackground(hdc
);
610 LRESULT
CTrayClockWnd::OnTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
614 case ID_TRAYCLOCK_TIMER
:
618 case ID_TRAYCLOCK_TIMER_INIT
:
625 LRESULT
CTrayClockWnd::OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
627 IsHorizontal
= (BOOL
) wParam
;
629 return (LRESULT
) GetMinimumSize((BOOL
) wParam
, (PSIZE
) lParam
) != 0;
632 LRESULT
CTrayClockWnd::OnContextMenu(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
634 return GetParent().SendMessage(uMsg
, wParam
, lParam
);
637 LRESULT
CTrayClockWnd::OnSetFont(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
639 SetFont((HFONT
) wParam
, (BOOL
) LOWORD(lParam
));
643 LRESULT
CTrayClockWnd::OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
645 m_tooltip
.Create(m_hWnd
, WS_POPUP
| TTS_NOPREFIX
| TTS_ALWAYSTIP
);
647 TOOLINFOW ti
= { 0 };
648 ti
.cbSize
= TTTOOLINFOW_V1_SIZE
;
649 ti
.uFlags
= TTF_IDISHWND
| TTF_SUBCLASS
;
651 ti
.uId
= reinterpret_cast<UINT_PTR
>(m_hWnd
);
655 m_tooltip
.AddTool(&ti
);
657 if (!g_TaskbarSettings
.sr
.HideClock
)
662 /* Update the time */
668 LRESULT
CTrayClockWnd::OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
672 szClient
.cx
= LOWORD(lParam
);
673 szClient
.cy
= HIWORD(lParam
);
675 VisibleLines
= GetMinimumSize(IsHorizontal
, &szClient
);
676 CurrentSize
= szClient
;
678 InvalidateRect(NULL
, TRUE
);
682 LRESULT
CTrayClockWnd::OnTaskbarSettingsChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
684 BOOL bRealign
= FALSE
;
686 TaskbarSettings
* newSettings
= (TaskbarSettings
*)lParam
;
687 if (newSettings
->bShowSeconds
!= g_TaskbarSettings
.bShowSeconds
)
689 g_TaskbarSettings
.bShowSeconds
= newSettings
->bShowSeconds
;
690 if (!g_TaskbarSettings
.sr
.HideClock
)
698 if (newSettings
->sr
.HideClock
!= g_TaskbarSettings
.sr
.HideClock
)
700 g_TaskbarSettings
.sr
.HideClock
= newSettings
->sr
.HideClock
;
701 ShowWindow(g_TaskbarSettings
.sr
.HideClock
? SW_HIDE
: SW_SHOW
);
704 if (g_TaskbarSettings
.sr
.HideClock
)
706 /* Disable all timers */
709 KillTimer(ID_TRAYCLOCK_TIMER
);
710 IsTimerEnabled
= FALSE
;
712 else if (IsInitTimerEnabled
)
714 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
715 IsInitTimerEnabled
= FALSE
;
726 /* Ask the parent to resize */
727 NMHDR nmh
= {GetParent(), 0, NTNWM_REALIGN
};
728 GetParent().SendMessage(WM_NOTIFY
, 0, (LPARAM
) &nmh
);
734 LRESULT
CTrayClockWnd::OnLButtonDblClick(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
736 if (IsWindowVisible())
738 //FIXME: use SHRunControlPanel
739 ShellExecuteW(m_hWnd
, NULL
, L
"timedate.cpl", NULL
, NULL
, SW_NORMAL
);
744 HRESULT
CTrayClockWnd::Initialize(IN HWND hWndParent
)
748 /* Create the window. The tray window is going to move it to the correct
749 position and resize it as needed. */
750 DWORD dwStyle
= WS_CHILD
| WS_CLIPSIBLINGS
;
751 if (!g_TaskbarSettings
.sr
.HideClock
)
752 dwStyle
|= WS_VISIBLE
;
754 Create(hWndParent
, 0, NULL
, dwStyle
);
758 SetWindowTheme(m_hWnd
, L
"TrayNotify", NULL
);
764 HRESULT
CTrayClockWnd_CreateInstance(HWND hwndParent
, REFIID riid
, void **ppv
)
766 return ShellObjectCreatorInit
<CTrayClockWnd
>(hwndParent
, riid
, ppv
);