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
OnNcHitTest(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_NCHITTEST
, OnNcHitTest
)
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 /* Calculate the due time */
428 GetLocalTime(&LocalTime
);
429 uiDueTime
= 1000 - (UINT
) LocalTime
.wMilliseconds
;
430 if (g_TaskbarSettings
.bShowSeconds
)
431 uiDueTime
+= (UINT
) LocalTime
.wSecond
* 100;
433 uiDueTime
+= (59 - (UINT
) LocalTime
.wSecond
) * 1000;
435 if (uiDueTime
< USER_TIMER_MINIMUM
|| uiDueTime
> USER_TIMER_MAXIMUM
)
439 /* Add an artificial delay of 0.05 seconds to make sure the timer
440 doesn't fire too early*/
447 BOOL
CTrayClockWnd::ResetTime()
452 /* Disable all timers */
455 KillTimer(ID_TRAYCLOCK_TIMER
);
456 IsTimerEnabled
= FALSE
;
459 if (IsInitTimerEnabled
)
461 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
464 uiDueTime
= CalculateDueTime();
466 /* Set the new timer */
467 Ret
= SetTimer(ID_TRAYCLOCK_TIMER_INIT
, uiDueTime
, NULL
) != 0;
468 IsInitTimerEnabled
= Ret
;
470 /* Update the time */
476 VOID
CTrayClockWnd::CalibrateTimer()
480 UINT uiWait1
, uiWait2
;
482 /* Kill the initialization timer */
483 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
484 IsInitTimerEnabled
= FALSE
;
486 uiDueTime
= CalculateDueTime();
488 if (g_TaskbarSettings
.bShowSeconds
)
490 uiWait1
= 1000 - 200;
495 uiWait1
= 60 * 1000 - 200;
499 if (uiDueTime
> uiWait1
)
501 /* The update of the clock will be up to 200 ms late, but that's
502 acceptable. We're going to setup a timer that fires depending
504 Ret
= SetTimer(ID_TRAYCLOCK_TIMER
, uiWait2
, NULL
) != 0;
505 IsTimerEnabled
= Ret
;
507 /* Update the time */
512 /* Recalibrate the timer and recalculate again when the current
513 minute/second ends. */
518 LRESULT
CTrayClockWnd::OnDestroy(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
520 /* Disable all timers */
523 KillTimer(ID_TRAYCLOCK_TIMER
);
526 if (IsInitTimerEnabled
)
528 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
534 LRESULT
CTrayClockWnd::OnPaint(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
542 HDC hDC
= (HDC
) wParam
;
546 hDC
= BeginPaint(&ps
);
553 GetClientRect(&rcClient
))
555 iPrevBkMode
= SetBkMode(hDC
, TRANSPARENT
);
557 SetTextColor(hDC
, textColor
);
559 hPrevFont
= (HFONT
) SelectObject(hDC
, hFont
);
561 rcClient
.top
= (rcClient
.bottom
- CurrentSize
.cy
) / 2;
562 rcClient
.bottom
= rcClient
.top
+ CurrentSize
.cy
;
564 for (i
= 0, line
= 0;
565 i
< CLOCKWND_FORMAT_COUNT
&& line
< VisibleLines
;
568 if (LineSizes
[i
].cx
!= 0)
571 (rcClient
.right
- LineSizes
[i
].cx
) / 2,
572 rcClient
.top
+ TRAY_CLOCK_WND_SPACING_Y
,
576 rcClient
.top
+= LineSizes
[i
].cy
+ LineSpacing
;
581 SelectObject(hDC
, hPrevFont
);
583 SetBkMode(hDC
, iPrevBkMode
);
594 VOID
CTrayClockWnd::SetFont(IN HFONT hNewFont
, IN BOOL bRedraw
)
597 LinesMeasured
= MeasureLines();
600 InvalidateRect(NULL
, TRUE
);
604 LRESULT
CTrayClockWnd::DrawBackground(HDC hdc
)
608 GetClientRect(&rect
);
609 DrawThemeParentBackground(m_hWnd
, hdc
, &rect
);
614 LRESULT
CTrayClockWnd::OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
616 HDC hdc
= (HDC
) wParam
;
624 return DrawBackground(hdc
);
627 LRESULT
CTrayClockWnd::OnTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
631 case ID_TRAYCLOCK_TIMER
:
635 case ID_TRAYCLOCK_TIMER_INIT
:
642 LRESULT
CTrayClockWnd::OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
644 IsHorizontal
= (BOOL
) wParam
;
646 return (LRESULT
) GetMinimumSize((BOOL
) wParam
, (PSIZE
) lParam
) != 0;
649 LRESULT
CTrayClockWnd::OnNcHitTest(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
651 // HTCLIENT is returned to receive WM_MOUSEMOVE messages for the tooltip
655 LRESULT
CTrayClockWnd::OnSetFont(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
657 SetFont((HFONT
) wParam
, (BOOL
) LOWORD(lParam
));
661 LRESULT
CTrayClockWnd::OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
663 m_tooltip
.Create(m_hWnd
, WS_POPUP
| TTS_NOPREFIX
| TTS_ALWAYSTIP
);
665 TOOLINFOW ti
= { 0 };
666 ti
.cbSize
= TTTOOLINFOW_V1_SIZE
;
667 ti
.uFlags
= TTF_IDISHWND
| TTF_SUBCLASS
;
669 ti
.uId
= reinterpret_cast<UINT_PTR
>(m_hWnd
);
673 m_tooltip
.AddTool(&ti
);
679 LRESULT
CTrayClockWnd::OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
683 szClient
.cx
= LOWORD(lParam
);
684 szClient
.cy
= HIWORD(lParam
);
686 VisibleLines
= GetMinimumSize(IsHorizontal
, &szClient
);
687 CurrentSize
= szClient
;
689 InvalidateRect(NULL
, TRUE
);
693 LRESULT
CTrayClockWnd::OnTaskbarSettingsChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
695 BOOL bRealign
= FALSE
;
697 TaskbarSettings
* newSettings
= (TaskbarSettings
*)lParam
;
698 if (newSettings
->bShowSeconds
!= g_TaskbarSettings
.bShowSeconds
)
700 g_TaskbarSettings
.bShowSeconds
= newSettings
->bShowSeconds
;
704 if (newSettings
->sr
.HideClock
!= g_TaskbarSettings
.sr
.HideClock
)
706 g_TaskbarSettings
.sr
.HideClock
= newSettings
->sr
.HideClock
;
707 ShowWindow(g_TaskbarSettings
.sr
.HideClock
? SW_HIDE
: SW_SHOW
);
713 /* Ask the parent to resize */
714 NMHDR nmh
= {GetParent(), 0, NTNWM_REALIGN
};
715 GetParent().SendMessage(WM_NOTIFY
, 0, (LPARAM
) &nmh
);
721 LRESULT
CTrayClockWnd::OnLButtonDblClick(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
723 if (IsWindowVisible())
725 //FIXME: use SHRunControlPanel
726 ShellExecuteW(m_hWnd
, NULL
, L
"timedate.cpl", NULL
, NULL
, SW_NORMAL
);
731 HRESULT
CTrayClockWnd::Initialize(IN HWND hWndParent
)
735 /* Create the window. The tray window is going to move it to the correct
736 position and resize it as needed. */
737 DWORD dwStyle
= WS_CHILD
| WS_CLIPSIBLINGS
;
738 if (!g_TaskbarSettings
.sr
.HideClock
)
739 dwStyle
|= WS_VISIBLE
;
741 Create(hWndParent
, 0, NULL
, dwStyle
);
745 SetWindowTheme(m_hWnd
, L
"TrayNotify", NULL
);
751 HRESULT
CTrayClockWnd_CreateInstance(HWND hwndParent
, REFIID riid
, void **ppv
)
753 return ShellObjectCreatorInit
<CTrayClockWnd
>(hwndParent
, riid
, ppv
);