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
>,
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
OnNcLButtonDblClick(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_NCLBUTTONDBLCLK
, OnNcLButtonDblClick
)
142 HRESULT
Initialize(IN HWND hWndParent
);
145 const WCHAR szTrayClockWndClass
[] = L
"TrayClockWClass";
147 #define ID_TRAYCLOCK_TIMER 0
148 #define ID_TRAYCLOCK_TIMER_INIT 1
150 #define TRAY_CLOCK_WND_SPACING_X 5
151 #define TRAY_CLOCK_WND_SPACING_Y 0
153 CTrayClockWnd::CTrayClockWnd() :
159 ZeroMemory(&textColor
, sizeof(textColor
));
160 ZeroMemory(&rcText
, sizeof(rcText
));
161 ZeroMemory(&LocalTime
, sizeof(LocalTime
));
162 ZeroMemory(&CurrentSize
, sizeof(CurrentSize
));
163 ZeroMemory(LineSizes
, sizeof(LineSizes
));
164 ZeroMemory(szLines
, sizeof(szLines
));
166 CTrayClockWnd::~CTrayClockWnd() { }
168 LRESULT
CTrayClockWnd::OnThemeChanged()
174 clockTheme
= OpenThemeData(m_hWnd
, L
"Clock");
178 GetThemeFont(clockTheme
,
185 hFont
= CreateFontIndirectW(&clockFont
);
187 GetThemeColor(clockTheme
,
193 if (this->hFont
!= NULL
)
194 DeleteObject(this->hFont
);
196 SetFont(hFont
, FALSE
);
200 /* We don't need to set a font here, our parent will use
201 * WM_SETFONT to set the right one when themes are not enabled. */
202 textColor
= RGB(0, 0, 0);
205 CloseThemeData(clockTheme
);
210 LRESULT
CTrayClockWnd::OnThemeChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
212 return OnThemeChanged();
215 BOOL
CTrayClockWnd::MeasureLines()
226 hPrevFont
= (HFONT
) SelectObject(hDC
, hFont
);
228 for (i
= 0; i
< CLOCKWND_FORMAT_COUNT
&& bRet
; i
++)
230 if (szLines
[i
][0] != L
'\0' &&
231 !GetTextExtentPointW(hDC
, szLines
[i
], wcslen(szLines
[i
]),
240 SelectObject(hDC
, hPrevFont
);
248 /* calculate the line spacing */
249 for (i
= 0, c
= 0; i
< CLOCKWND_FORMAT_COUNT
; i
++)
251 if (LineSizes
[i
].cx
> 0)
253 LineSpacing
+= LineSizes
[i
].cy
;
260 /* We want a spacing of 1/2 line */
261 LineSpacing
= (LineSpacing
/ c
) / 2;
271 WORD
CTrayClockWnd::GetMinimumSize(IN BOOL Horizontal
, IN OUT PSIZE pSize
)
273 WORD iLinesVisible
= 0;
275 SIZE szMax
= { 0, 0 };
278 LinesMeasured
= MeasureLines();
283 for (i
= 0; i
< CLOCKWND_FORMAT_COUNT
; i
++)
285 if (LineSizes
[i
].cx
!= 0)
287 if (iLinesVisible
> 0)
291 if (szMax
.cy
+ LineSizes
[i
].cy
+ (LONG
) LineSpacing
>
292 pSize
->cy
- (2 * TRAY_CLOCK_WND_SPACING_Y
))
299 if (LineSizes
[i
].cx
> pSize
->cx
- (2 * TRAY_CLOCK_WND_SPACING_X
))
303 /* Add line spacing */
304 szMax
.cy
+= LineSpacing
;
309 /* Increase maximum rectangle */
310 szMax
.cy
+= LineSizes
[i
].cy
;
311 if (LineSizes
[i
].cx
> szMax
.cx
)
312 szMax
.cx
= LineSizes
[i
].cx
;
316 szMax
.cx
+= 2 * TRAY_CLOCK_WND_SPACING_X
;
317 szMax
.cy
+= 2 * TRAY_CLOCK_WND_SPACING_Y
;
321 return iLinesVisible
;
324 VOID
CTrayClockWnd::UpdateWnd()
331 ZeroMemory(LineSizes
, sizeof(LineSizes
));
333 szPrevCurrent
= CurrentSize
;
335 for (i
= 0; i
< CLOCKWND_FORMAT_COUNT
; i
++)
337 szLines
[i
][0] = L
'\0';
338 BufSize
= _countof(szLines
[0]);
340 if (ClockWndFormats
[i
].IsTime
)
342 iRet
= GetTimeFormat(LOCALE_USER_DEFAULT
,
343 g_TaskbarSettings
.bShowSeconds
? ClockWndFormats
[i
].dwFormatFlags
: TIME_NOSECONDS
,
345 ClockWndFormats
[i
].lpFormat
,
351 iRet
= GetDateFormat(LOCALE_USER_DEFAULT
,
352 ClockWndFormats
[i
].dwFormatFlags
,
354 ClockWndFormats
[i
].lpFormat
,
359 if (iRet
!= 0 && i
== 0)
361 /* Set the window text to the time only */
362 SetWindowText(szLines
[i
]);
366 LinesMeasured
= MeasureLines();
369 GetClientRect(&rcClient
))
373 szWnd
.cx
= rcClient
.right
;
374 szWnd
.cy
= rcClient
.bottom
;
376 VisibleLines
= GetMinimumSize(IsHorizontal
, &szWnd
);
380 if (IsWindowVisible())
382 InvalidateRect(NULL
, TRUE
);
384 if (szPrevCurrent
.cx
!= CurrentSize
.cx
||
385 szPrevCurrent
.cy
!= CurrentSize
.cy
)
387 /* Ask the parent to resize */
388 NMHDR nmh
= {GetParent(), 0, NTNWM_REALIGN
};
389 GetParent().SendMessage(WM_NOTIFY
, 0, (LPARAM
) &nmh
);
393 int iDateLength
= GetDateFormat(LOCALE_USER_DEFAULT
,
399 if (iDateLength
<= 0)
404 WCHAR
* szDate
= new WCHAR
[iDateLength
];
405 if (GetDateFormat(LOCALE_USER_DEFAULT
,
412 m_tooltip
.UpdateTipText(m_hWnd
,
413 reinterpret_cast<UINT_PTR
>(m_hWnd
),
419 VOID
CTrayClockWnd::Update()
421 GetLocalTime(&LocalTime
);
425 UINT
CTrayClockWnd::CalculateDueTime()
429 /* Calculate the due time */
430 GetLocalTime(&LocalTime
);
431 uiDueTime
= 1000 - (UINT
) LocalTime
.wMilliseconds
;
432 if (g_TaskbarSettings
.bShowSeconds
)
433 uiDueTime
+= (UINT
) LocalTime
.wSecond
* 100;
435 uiDueTime
+= (59 - (UINT
) LocalTime
.wSecond
) * 1000;
437 if (uiDueTime
< USER_TIMER_MINIMUM
|| uiDueTime
> USER_TIMER_MAXIMUM
)
441 /* Add an artificial delay of 0.05 seconds to make sure the timer
442 doesn't fire too early*/
449 BOOL
CTrayClockWnd::ResetTime()
454 /* Disable all timers */
457 KillTimer(ID_TRAYCLOCK_TIMER
);
458 IsTimerEnabled
= FALSE
;
461 if (IsInitTimerEnabled
)
463 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
466 uiDueTime
= CalculateDueTime();
468 /* Set the new timer */
469 Ret
= SetTimer(ID_TRAYCLOCK_TIMER_INIT
, uiDueTime
, NULL
) != 0;
470 IsInitTimerEnabled
= Ret
;
472 /* Update the time */
478 VOID
CTrayClockWnd::CalibrateTimer()
482 UINT uiWait1
, uiWait2
;
484 /* Kill the initialization timer */
485 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
486 IsInitTimerEnabled
= FALSE
;
488 uiDueTime
= CalculateDueTime();
490 if (g_TaskbarSettings
.bShowSeconds
)
492 uiWait1
= 1000 - 200;
497 uiWait1
= 60 * 1000 - 200;
501 if (uiDueTime
> uiWait1
)
503 /* The update of the clock will be up to 200 ms late, but that's
504 acceptable. We're going to setup a timer that fires depending
506 Ret
= SetTimer(ID_TRAYCLOCK_TIMER
, uiWait2
, NULL
) != 0;
507 IsTimerEnabled
= Ret
;
509 /* Update the time */
514 /* Recalibrate the timer and recalculate again when the current
515 minute/second ends. */
520 LRESULT
CTrayClockWnd::OnDestroy(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
522 /* Disable all timers */
525 KillTimer(ID_TRAYCLOCK_TIMER
);
528 if (IsInitTimerEnabled
)
530 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
536 LRESULT
CTrayClockWnd::OnPaint(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
544 HDC hDC
= (HDC
) wParam
;
548 hDC
= BeginPaint(&ps
);
555 GetClientRect(&rcClient
))
557 iPrevBkMode
= SetBkMode(hDC
, TRANSPARENT
);
559 SetTextColor(hDC
, textColor
);
561 hPrevFont
= (HFONT
) SelectObject(hDC
, hFont
);
563 rcClient
.top
= (rcClient
.bottom
- CurrentSize
.cy
) / 2;
564 rcClient
.bottom
= rcClient
.top
+ CurrentSize
.cy
;
566 for (i
= 0, line
= 0;
567 i
< CLOCKWND_FORMAT_COUNT
&& line
< VisibleLines
;
570 if (LineSizes
[i
].cx
!= 0)
573 (rcClient
.right
- LineSizes
[i
].cx
) / 2,
574 rcClient
.top
+ TRAY_CLOCK_WND_SPACING_Y
,
578 rcClient
.top
+= LineSizes
[i
].cy
+ LineSpacing
;
583 SelectObject(hDC
, hPrevFont
);
585 SetBkMode(hDC
, iPrevBkMode
);
596 VOID
CTrayClockWnd::SetFont(IN HFONT hNewFont
, IN BOOL bRedraw
)
599 LinesMeasured
= MeasureLines();
602 InvalidateRect(NULL
, TRUE
);
606 LRESULT
CTrayClockWnd::DrawBackground(HDC hdc
)
610 GetClientRect(&rect
);
611 DrawThemeParentBackground(m_hWnd
, hdc
, &rect
);
616 LRESULT
CTrayClockWnd::OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
618 HDC hdc
= (HDC
) wParam
;
626 return DrawBackground(hdc
);
629 LRESULT
CTrayClockWnd::OnTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
633 case ID_TRAYCLOCK_TIMER
:
637 case ID_TRAYCLOCK_TIMER_INIT
:
644 LRESULT
CTrayClockWnd::OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
646 IsHorizontal
= (BOOL
) wParam
;
648 return (LRESULT
) GetMinimumSize((BOOL
) wParam
, (PSIZE
) lParam
) != 0;
651 LRESULT
CTrayClockWnd::OnNcHitTest(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
653 // HTCLIENT is returned to receive WM_MOUSEMOVE messages for the tooltip
657 LRESULT
CTrayClockWnd::OnSetFont(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
659 SetFont((HFONT
) wParam
, (BOOL
) LOWORD(lParam
));
663 LRESULT
CTrayClockWnd::OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
665 m_tooltip
.Create(m_hWnd
, WS_POPUP
| TTS_NOPREFIX
| TTS_ALWAYSTIP
);
667 TOOLINFOW ti
= { 0 };
668 ti
.cbSize
= TTTOOLINFOW_V1_SIZE
;
669 ti
.uFlags
= TTF_IDISHWND
| TTF_SUBCLASS
;
671 ti
.uId
= reinterpret_cast<UINT_PTR
>(m_hWnd
);
675 m_tooltip
.AddTool(&ti
);
681 LRESULT
CTrayClockWnd::OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
685 szClient
.cx
= LOWORD(lParam
);
686 szClient
.cy
= HIWORD(lParam
);
688 VisibleLines
= GetMinimumSize(IsHorizontal
, &szClient
);
689 CurrentSize
= szClient
;
691 InvalidateRect(NULL
, TRUE
);
695 LRESULT
CTrayClockWnd::OnTaskbarSettingsChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
697 BOOL bRealign
= FALSE
;
699 TaskbarSettings
* newSettings
= (TaskbarSettings
*)lParam
;
700 if (newSettings
->bShowSeconds
!= g_TaskbarSettings
.bShowSeconds
)
702 g_TaskbarSettings
.bShowSeconds
= newSettings
->bShowSeconds
;
706 if (newSettings
->sr
.HideClock
!= g_TaskbarSettings
.sr
.HideClock
)
708 g_TaskbarSettings
.sr
.HideClock
= newSettings
->sr
.HideClock
;
709 ShowWindow(g_TaskbarSettings
.sr
.HideClock
? SW_HIDE
: SW_SHOW
);
715 /* Ask the parent to resize */
716 NMHDR nmh
= {GetParent(), 0, NTNWM_REALIGN
};
717 GetParent().SendMessage(WM_NOTIFY
, 0, (LPARAM
) &nmh
);
723 LRESULT
CTrayClockWnd::OnNcLButtonDblClick(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
725 if (IsWindowVisible())
727 /* We get all WM_NCLBUTTONDBLCLK for the taskbar so we need to check if it is on the clock*/
729 if (GetWindowRect(&rcClock
))
732 ptClick
.x
= MAKEPOINTS(lParam
).x
;
733 ptClick
.y
= MAKEPOINTS(lParam
).y
;
734 if (PtInRect(&rcClock
, ptClick
))
736 //FIXME: use SHRunControlPanel
737 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
);