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
26 static const WCHAR szSysPagerWndClass
[] = L
"SysPager";
28 // Data comes from shell32/systray.cpp -> TrayNotifyCDS_Dummy
29 typedef struct _SYS_PAGER_COPY_DATA
33 NOTIFYICONDATA nicon_data
;
34 } SYS_PAGER_COPY_DATA
, *PSYS_PAGER_COPY_DATA
;
36 class CNotifyToolbar
:
37 public CWindowImplBaseT
< CToolbar
<NOTIFYICONDATA
>, CControlWinTraits
>
39 static const int ICON_SIZE
= 16;
41 HIMAGELIST m_ImageList
;
42 int m_VisibleButtonCount
;
47 m_VisibleButtonCount(0)
55 int GetVisibleButtonCount()
57 return m_VisibleButtonCount
;
60 int FindItemByIconData(IN CONST NOTIFYICONDATA
*iconData
, NOTIFYICONDATA
** pdata
)
62 int count
= GetButtonCount();
64 for (int i
= 0; i
< count
; i
++)
66 NOTIFYICONDATA
* data
;
68 data
= GetItemData(i
);
70 if (data
->hWnd
== iconData
->hWnd
&&
71 data
->uID
== iconData
->uID
)
82 BOOL
AddButton(IN CONST NOTIFYICONDATA
*iconData
)
85 NOTIFYICONDATA
* notifyItem
;
88 int index
= FindItemByIconData(iconData
, ¬ifyItem
);
91 return UpdateButton(iconData
);
94 notifyItem
= new NOTIFYICONDATA();
95 ZeroMemory(notifyItem
, sizeof(*notifyItem
));
97 notifyItem
->hWnd
= iconData
->hWnd
;
98 notifyItem
->uID
= iconData
->uID
;
100 tbBtn
.fsState
= TBSTATE_ENABLED
;
101 tbBtn
.fsStyle
= BTNS_NOPREFIX
;
102 tbBtn
.dwData
= (DWORD_PTR
)notifyItem
;
103 tbBtn
.iString
= (INT_PTR
) text
;
104 tbBtn
.idCommand
= GetButtonCount();
106 if (iconData
->uFlags
& NIF_MESSAGE
)
108 notifyItem
->uCallbackMessage
= iconData
->uCallbackMessage
;
111 if (iconData
->uFlags
& NIF_ICON
)
113 tbBtn
.iBitmap
= ImageList_AddIcon(m_ImageList
, iconData
->hIcon
);
116 if (iconData
->uFlags
& NIF_TIP
)
118 StringCchCopy(notifyItem
->szTip
, _countof(notifyItem
->szTip
), iconData
->szTip
);
121 m_VisibleButtonCount
++;
122 if (iconData
->uFlags
& NIF_STATE
)
124 notifyItem
->dwState
&= ~iconData
->dwStateMask
;
125 notifyItem
->dwState
|= (iconData
->dwState
& iconData
->dwStateMask
);
126 if (notifyItem
->dwState
& NIS_HIDDEN
)
128 tbBtn
.fsState
|= TBSTATE_HIDDEN
;
129 m_VisibleButtonCount
--;
133 /* TODO: support NIF_INFO, NIF_GUID, NIF_REALTIME, NIF_SHOWTIP */
135 CToolbar::AddButton(&tbBtn
);
136 SetButtonSize(ICON_SIZE
, ICON_SIZE
);
141 BOOL
UpdateButton(IN CONST NOTIFYICONDATA
*iconData
)
143 NOTIFYICONDATA
* notifyItem
;
144 TBBUTTONINFO tbbi
= { 0 };
146 int index
= FindItemByIconData(iconData
, ¬ifyItem
);
149 return AddButton(iconData
);
152 tbbi
.cbSize
= sizeof(tbbi
);
153 tbbi
.dwMask
= TBIF_BYINDEX
| TBIF_COMMAND
;
154 tbbi
.idCommand
= index
;
156 if (iconData
->uFlags
& NIF_MESSAGE
)
158 notifyItem
->uCallbackMessage
= iconData
->uCallbackMessage
;
161 if (iconData
->uFlags
& NIF_ICON
)
163 tbbi
.dwMask
|= TBIF_IMAGE
;
164 tbbi
.iImage
= ImageList_ReplaceIcon(m_ImageList
, index
, iconData
->hIcon
);
167 if (iconData
->uFlags
& NIF_TIP
)
169 StringCchCopy(notifyItem
->szTip
, _countof(notifyItem
->szTip
), iconData
->szTip
);
172 if (iconData
->uFlags
& NIF_STATE
)
174 if (iconData
->dwStateMask
& NIS_HIDDEN
&&
175 (notifyItem
->dwState
& NIS_HIDDEN
) != (iconData
->dwState
& NIS_HIDDEN
))
177 tbbi
.dwMask
|= TBIF_STATE
;
178 if (iconData
->dwState
& NIS_HIDDEN
)
180 tbbi
.fsState
|= TBSTATE_HIDDEN
;
181 m_VisibleButtonCount
--;
185 tbbi
.fsState
&= ~TBSTATE_HIDDEN
;
186 m_VisibleButtonCount
++;
190 notifyItem
->dwState
&= ~iconData
->dwStateMask
;
191 notifyItem
->dwState
|= (iconData
->dwState
& iconData
->dwStateMask
);
194 /* TODO: support NIF_INFO, NIF_GUID, NIF_REALTIME, NIF_SHOWTIP */
196 SetButtonInfo(index
, &tbbi
);
201 BOOL
RemoveButton(IN CONST NOTIFYICONDATA
*iconData
)
203 NOTIFYICONDATA
* notifyItem
;
205 int index
= FindItemByIconData(iconData
, ¬ifyItem
);
209 if (!(notifyItem
->dwState
& NIS_HIDDEN
))
211 m_VisibleButtonCount
--;
216 ImageList_Remove(m_ImageList
, index
);
218 int count
= GetButtonCount();
220 /* shift all buttons one index to the left -- starting one index right
221 from item to delete -- to preserve their correct icon and tip */
222 for (int i
= index
; i
< count
- 1; i
++)
224 notifyItem
= GetItemData(i
+ 1);
225 SetItemData(i
, notifyItem
);
226 UpdateButton(notifyItem
);
229 /* Delete the right-most, now obsolete button */
230 DeleteButton(count
- 1);
235 VOID
GetTooltipText(int index
, LPTSTR szTip
, DWORD cchTip
)
237 NOTIFYICONDATA
* notifyItem
;
238 notifyItem
= GetItemData(index
);
242 StringCchCopy(szTip
, cchTip
, notifyItem
->szTip
);
248 VOID
SendMouseEvent(IN WORD wIndex
, IN UINT uMsg
, IN WPARAM wParam
)
250 static LPCWSTR eventNames
[] = {
267 NOTIFYICONDATA
* notifyItem
= GetItemData(wIndex
);
269 if (!::IsWindow(notifyItem
->hWnd
))
271 // We detect and destroy icons with invalid handles only on mouse move over systray, same as MS does.
272 // Alternatively we could search for them periodically (would waste more resources).
273 TRACE("destroying icon with invalid handle\n");
275 HWND parentHWND
= GetParent();
276 parentHWND
= ::GetParent(parentHWND
);
279 ::GetClientRect(parentHWND
, &windowRect
);
281 RemoveButton(notifyItem
);
283 SendMessage(parentHWND
,
286 MAKELONG(windowRect
.right
- windowRect
.left
,
287 windowRect
.bottom
- windowRect
.top
));
292 if (uMsg
>= WM_MOUSEFIRST
&& uMsg
<= WM_MOUSELAST
)
294 TRACE("Sending message %S from button %d to %p (msg=%x, w=%x, l=%x)...\n",
295 eventNames
[uMsg
- WM_MOUSEFIRST
], wIndex
,
296 notifyItem
->hWnd
, notifyItem
->uCallbackMessage
, notifyItem
->uID
, uMsg
);
300 GetWindowThreadProcessId(notifyItem
->hWnd
, &pid
);
302 if (pid
== GetCurrentProcessId() ||
303 (uMsg
>= WM_MOUSEFIRST
&& uMsg
<= WM_MOUSELAST
))
305 ::PostMessage(notifyItem
->hWnd
,
306 notifyItem
->uCallbackMessage
,
312 SendMessage(notifyItem
->hWnd
,
313 notifyItem
->uCallbackMessage
,
319 LRESULT
OnMouseEvent(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
321 POINT pt
= { GET_X_LPARAM(lParam
), GET_Y_LPARAM(lParam
) };
323 INT iBtn
= HitTest(&pt
);
327 SendMouseEvent(iBtn
, uMsg
, wParam
);
334 LRESULT
OnTooltipShow(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
337 ::GetWindowRect(hdr
->hwndFrom
, &rcTip
);
339 SIZE szTip
= { rcTip
.right
- rcTip
.left
, rcTip
.bottom
- rcTip
.top
};
341 INT iBtn
= GetHotItem();
345 MONITORINFO monInfo
= { 0 };
346 HMONITOR hMon
= MonitorFromWindow(m_hWnd
, MONITOR_DEFAULTTONEAREST
);
348 monInfo
.cbSize
= sizeof(monInfo
);
351 GetMonitorInfo(hMon
, &monInfo
);
353 ::GetWindowRect(GetDesktopWindow(), &monInfo
.rcMonitor
);
355 GetItemRect(iBtn
, &rcItem
);
357 POINT ptItem
= { rcItem
.left
, rcItem
.top
};
358 SIZE szItem
= { rcItem
.right
- rcItem
.left
, rcItem
.bottom
- rcItem
.top
};
359 ClientToScreen(&ptItem
);
361 ptItem
.x
+= szItem
.cx
/ 2;
362 ptItem
.y
-= szTip
.cy
;
364 if (ptItem
.x
+ szTip
.cx
> monInfo
.rcMonitor
.right
)
365 ptItem
.x
= monInfo
.rcMonitor
.right
- szTip
.cx
;
367 if (ptItem
.y
+ szTip
.cy
> monInfo
.rcMonitor
.bottom
)
368 ptItem
.y
= monInfo
.rcMonitor
.bottom
- szTip
.cy
;
370 if (ptItem
.x
< monInfo
.rcMonitor
.left
)
371 ptItem
.x
= monInfo
.rcMonitor
.left
;
373 if (ptItem
.y
< monInfo
.rcMonitor
.top
)
374 ptItem
.y
= monInfo
.rcMonitor
.top
;
376 TRACE("ptItem { %d, %d }\n", ptItem
.x
, ptItem
.y
);
378 ::SetWindowPos(hdr
->hwndFrom
, NULL
, ptItem
.x
, ptItem
.y
, 0, 0, SWP_NOSIZE
| SWP_NOZORDER
| SWP_NOACTIVATE
);
389 BEGIN_MSG_MAP(CNotifyToolbar
)
390 MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST
, WM_MOUSELAST
, OnMouseEvent
)
391 NOTIFY_CODE_HANDLER(TTN_SHOW
, OnTooltipShow
)
394 void Initialize(HWND hWndParent
)
397 WS_CHILD
| WS_VISIBLE
| WS_CLIPCHILDREN
|
398 TBSTYLE_FLAT
| TBSTYLE_TOOLTIPS
| TBSTYLE_WRAPABLE
| TBSTYLE_TRANSPARENT
|
399 CCS_TOP
| CCS_NORESIZE
| CCS_NOPARENTALIGN
| CCS_NODIVIDER
;
401 SubclassWindow(CToolbar::Create(hWndParent
, styles
));
403 SetWindowTheme(m_hWnd
, L
"TrayNotify", NULL
);
405 m_ImageList
= ImageList_Create(16, 16, ILC_COLOR32
| ILC_MASK
, 0, 1000);
406 SetImageList(m_ImageList
);
408 SetButtonSize(ICON_SIZE
, ICON_SIZE
);
413 public CComObjectRootEx
<CComMultiThreadModelNoCS
>,
414 public CWindowImpl
< CSysPagerWnd
, CWindow
, CControlWinTraits
>
416 CNotifyToolbar Toolbar
;
420 virtual ~CSysPagerWnd() {}
422 LRESULT
DrawBackground(HDC hdc
)
426 GetClientRect(&rect
);
427 DrawThemeParentBackground(m_hWnd
, hdc
, &rect
);
432 LRESULT
OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
434 HDC hdc
= (HDC
) wParam
;
442 return DrawBackground(hdc
);
445 LRESULT
OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
447 Toolbar
.Initialize(m_hWnd
);
449 // Explicitly request running applications to re-register their systray icons
450 ::SendNotifyMessageW(HWND_BROADCAST
,
451 RegisterWindowMessageW(L
"TaskbarCreated"),
457 BOOL
NotifyIconCmd(WPARAM wParam
, LPARAM lParam
)
459 PCOPYDATASTRUCT cpData
= (PCOPYDATASTRUCT
) lParam
;
460 if (cpData
->dwData
== 1)
462 SYS_PAGER_COPY_DATA
* data
;
463 NOTIFYICONDATA
*iconData
;
467 parentHWND
= GetParent();
468 parentHWND
= ::GetParent(parentHWND
);
469 ::GetClientRect(parentHWND
, &windowRect
);
471 data
= (PSYS_PAGER_COPY_DATA
) cpData
->lpData
;
472 iconData
= &data
->nicon_data
;
474 TRACE("NotifyIconCmd received. Code=%d\n", data
->notify_code
);
475 switch (data
->notify_code
)
478 ret
= Toolbar
.AddButton(iconData
);
481 ret
= Toolbar
.UpdateButton(iconData
);
484 ret
= Toolbar
.RemoveButton(iconData
);
487 TRACE("NotifyIconCmd received with unknown code %d.\n", data
->notify_code
);
491 SendMessage(parentHWND
,
494 MAKELONG(windowRect
.right
- windowRect
.left
,
495 windowRect
.bottom
- windowRect
.top
));
503 void GetSize(IN WPARAM wParam
, IN PSIZE size
)
506 int VisibleButtonCount
= Toolbar
.GetVisibleButtonCount();
508 if (wParam
) /* horizontal */
510 rows
= size
->cy
/ 24;
513 size
->cx
= (VisibleButtonCount
+ rows
- 1) / rows
* 24;
517 rows
= size
->cx
/ 24;
520 size
->cy
= (VisibleButtonCount
+ rows
- 1) / rows
* 24;
524 LRESULT
OnGetInfoTip(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
526 NMTBGETINFOTIPW
* nmtip
= (NMTBGETINFOTIPW
*) hdr
;
527 Toolbar
.GetTooltipText(nmtip
->iItem
, nmtip
->pszText
, nmtip
->cchTextMax
);
531 LRESULT
OnCustomDraw(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
533 NMCUSTOMDRAW
* cdraw
= (NMCUSTOMDRAW
*) hdr
;
534 switch (cdraw
->dwDrawStage
)
537 return CDRF_NOTIFYITEMDRAW
;
539 case CDDS_ITEMPREPAINT
:
540 return TBCDRF_NOBACKGROUND
| TBCDRF_NOEDGES
| TBCDRF_NOOFFSET
| TBCDRF_NOMARK
| TBCDRF_NOETCHEDEFFECT
;
545 LRESULT
OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
549 szClient
.cx
= LOWORD(lParam
);
550 szClient
.cy
= HIWORD(lParam
);
552 Ret
= DefWindowProc(uMsg
, wParam
, lParam
);
557 tbm
.cbSize
= sizeof(tbm
);
558 tbm
.dwMask
= TBMF_BARPAD
| TBMF_BUTTONSPACING
;
559 tbm
.cxBarPad
= tbm
.cyBarPad
= 0;
560 tbm
.cxButtonSpacing
= 0;
561 tbm
.cyButtonSpacing
= 0;
563 Toolbar
.SetMetrics(&tbm
);
565 Toolbar
.SetWindowPos(NULL
, 0, 0, szClient
.cx
, szClient
.cy
, SWP_NOZORDER
);
569 Toolbar
.GetClientRect(&rc
);
571 SIZE szBar
= { rc
.right
- rc
.left
, rc
.bottom
- rc
.top
};
573 INT xOff
= (szClient
.cx
- szBar
.cx
) / 2;
574 INT yOff
= (szClient
.cy
- szBar
.cy
) / 2;
576 Toolbar
.SetWindowPos(NULL
, xOff
, yOff
, szBar
.cx
, szBar
.cy
, SWP_NOZORDER
);
581 LRESULT
OnCtxMenu(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
587 DECLARE_WND_CLASS_EX(szSysPagerWndClass
, CS_DBLCLKS
, COLOR_3DFACE
)
589 BEGIN_MSG_MAP(CSysPagerWnd
)
590 MESSAGE_HANDLER(WM_CREATE
, OnCreate
)
591 MESSAGE_HANDLER(WM_ERASEBKGND
, OnEraseBackground
)
592 MESSAGE_HANDLER(WM_SIZE
, OnSize
)
593 MESSAGE_HANDLER(WM_CONTEXTMENU
, OnCtxMenu
)
594 NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW
, OnGetInfoTip
)
595 NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW
, OnCustomDraw
)
598 HWND
_Init(IN HWND hWndParent
, IN BOOL bVisible
)
602 /* Create the window. The tray window is going to move it to the correct
603 position and resize it as needed. */
604 dwStyle
= WS_CHILD
| WS_CLIPSIBLINGS
;
606 dwStyle
|= WS_VISIBLE
;
608 Create(hWndParent
, 0, NULL
, dwStyle
);
615 SetWindowTheme(m_hWnd
, L
"TrayNotify", NULL
);
625 static const WCHAR szTrayClockWndClass
[] = L
"TrayClockWClass";
627 #define ID_TRAYCLOCK_TIMER 0
628 #define ID_TRAYCLOCK_TIMER_INIT 1
635 } ClockWndFormats
[] = {
637 { FALSE
, 0, L
"dddd" },
638 { FALSE
, DATE_SHORTDATE
, NULL
}
641 #define CLOCKWND_FORMAT_COUNT (_ARRAYSIZE(ClockWndFormats))
643 #define TRAY_CLOCK_WND_SPACING_X 0
644 #define TRAY_CLOCK_WND_SPACING_Y 0
646 class CTrayClockWnd
:
647 public CComObjectRootEx
<CComMultiThreadModelNoCS
>,
648 public CWindowImpl
< CTrayClockWnd
, CWindow
, CControlWinTraits
>
654 SYSTEMTIME LocalTime
;
661 DWORD IsTimerEnabled
: 1;
662 DWORD IsInitTimerEnabled
: 1;
663 DWORD LinesMeasured
: 1;
664 DWORD IsHorizontal
: 1;
670 SIZE LineSizes
[CLOCKWND_FORMAT_COUNT
];
671 WCHAR szLines
[CLOCKWND_FORMAT_COUNT
][48];
681 ZeroMemory(&textColor
, sizeof(textColor
));
682 ZeroMemory(&rcText
, sizeof(rcText
));
683 ZeroMemory(&LocalTime
, sizeof(LocalTime
));
684 ZeroMemory(&CurrentSize
, sizeof(CurrentSize
));
685 ZeroMemory(LineSizes
, sizeof(LineSizes
));
686 ZeroMemory(szLines
, sizeof(szLines
));
688 virtual ~CTrayClockWnd() { }
690 LRESULT
OnThemeChanged()
696 clockTheme
= OpenThemeData(m_hWnd
, L
"Clock");
700 GetThemeFont(clockTheme
,
707 hFont
= CreateFontIndirectW(&clockFont
);
709 GetThemeColor(clockTheme
,
715 if (this->hFont
!= NULL
)
716 DeleteObject(this->hFont
);
718 SetFont(hFont
, FALSE
);
722 /* We don't need to set a font here, our parent will use
723 * WM_SETFONT to set the right one when themes are not enabled. */
724 textColor
= RGB(0, 0, 0);
727 CloseThemeData(clockTheme
);
732 LRESULT
OnThemeChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
734 return OnThemeChanged();
748 hPrevFont
= (HFONT
) SelectObject(hDC
, hFont
);
750 for (i
= 0; i
< CLOCKWND_FORMAT_COUNT
&& bRet
; i
++)
752 if (szLines
[i
][0] != L
'\0' &&
753 !GetTextExtentPointW(hDC
, szLines
[i
], wcslen(szLines
[i
]),
762 SelectObject(hDC
, hPrevFont
);
770 /* calculate the line spacing */
771 for (i
= 0, c
= 0; i
< CLOCKWND_FORMAT_COUNT
; i
++)
773 if (LineSizes
[i
].cx
> 0)
775 LineSpacing
+= LineSizes
[i
].cy
;
782 /* We want a spacing of 1/2 line */
783 LineSpacing
= (LineSpacing
/ c
) / 2;
793 WORD
GetMinimumSize(IN BOOL Horizontal
, IN OUT PSIZE pSize
)
795 WORD iLinesVisible
= 0;
797 SIZE szMax
= { 0, 0 };
800 LinesMeasured
= MeasureLines();
805 for (i
= 0; i
< CLOCKWND_FORMAT_COUNT
; i
++)
807 if (LineSizes
[i
].cx
!= 0)
809 if (iLinesVisible
> 0)
813 if (szMax
.cy
+ LineSizes
[i
].cy
+ (LONG
) LineSpacing
>
814 pSize
->cy
- (2 * TRAY_CLOCK_WND_SPACING_Y
))
821 if (LineSizes
[i
].cx
> pSize
->cx
- (2 * TRAY_CLOCK_WND_SPACING_X
))
825 /* Add line spacing */
826 szMax
.cy
+= LineSpacing
;
831 /* Increase maximum rectangle */
832 szMax
.cy
+= LineSizes
[i
].cy
;
833 if (LineSizes
[i
].cx
> szMax
.cx
- (2 * TRAY_CLOCK_WND_SPACING_X
))
834 szMax
.cx
= LineSizes
[i
].cx
+ (2 * TRAY_CLOCK_WND_SPACING_X
);
838 szMax
.cx
+= 2 * TRAY_CLOCK_WND_SPACING_X
;
839 szMax
.cy
+= 2 * TRAY_CLOCK_WND_SPACING_Y
;
843 return iLinesVisible
;
854 ZeroMemory(LineSizes
, sizeof(LineSizes
));
856 szPrevCurrent
= CurrentSize
;
858 for (i
= 0; i
< CLOCKWND_FORMAT_COUNT
; i
++)
860 szLines
[i
][0] = L
'\0';
861 BufSize
= _countof(szLines
[0]);
863 if (ClockWndFormats
[i
].IsTime
)
865 iRet
= GetTimeFormat(LOCALE_USER_DEFAULT
,
866 TaskBarSettings
.bShowSeconds
? ClockWndFormats
[i
].dwFormatFlags
: TIME_NOSECONDS
,
868 ClockWndFormats
[i
].lpFormat
,
874 iRet
= GetDateFormat(LOCALE_USER_DEFAULT
,
875 ClockWndFormats
[i
].dwFormatFlags
,
877 ClockWndFormats
[i
].lpFormat
,
882 if (iRet
!= 0 && i
== 0)
884 /* Set the window text to the time only */
885 SetWindowText(szLines
[i
]);
889 LinesMeasured
= MeasureLines();
892 GetClientRect(&rcClient
))
896 szWnd
.cx
= rcClient
.right
;
897 szWnd
.cy
= rcClient
.bottom
;
899 VisibleLines
= GetMinimumSize(IsHorizontal
, &szWnd
);
903 if (IsWindowVisible())
905 InvalidateRect(NULL
, TRUE
);
907 if (hWndNotify
!= NULL
&&
908 (szPrevCurrent
.cx
!= CurrentSize
.cx
||
909 szPrevCurrent
.cy
!= CurrentSize
.cy
))
913 nmh
.hwndFrom
= m_hWnd
;
914 nmh
.idFrom
= GetWindowLongPtr(GWLP_ID
);
915 nmh
.code
= NTNWM_REALIGN
;
917 SendMessage(hWndNotify
,
927 GetLocalTime(&LocalTime
);
931 UINT
CalculateDueTime()
935 /* Calculate the due time */
936 GetLocalTime(&LocalTime
);
937 uiDueTime
= 1000 - (UINT
) LocalTime
.wMilliseconds
;
938 if (TaskBarSettings
.bShowSeconds
)
939 uiDueTime
+= (UINT
) LocalTime
.wSecond
* 100;
941 uiDueTime
+= (59 - (UINT
) LocalTime
.wSecond
) * 1000;
943 if (uiDueTime
< USER_TIMER_MINIMUM
|| uiDueTime
> USER_TIMER_MAXIMUM
)
947 /* Add an artificial delay of 0.05 seconds to make sure the timer
948 doesn't fire too early*/
960 /* Disable all timers */
963 KillTimer(ID_TRAYCLOCK_TIMER
);
964 IsTimerEnabled
= FALSE
;
967 if (IsInitTimerEnabled
)
969 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
972 uiDueTime
= CalculateDueTime();
974 /* Set the new timer */
975 Ret
= SetTimer(ID_TRAYCLOCK_TIMER_INIT
, uiDueTime
, NULL
) != 0;
976 IsInitTimerEnabled
= Ret
;
978 /* Update the time */
984 VOID
CalibrateTimer()
988 UINT uiWait1
, uiWait2
;
990 /* Kill the initialization timer */
991 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
992 IsInitTimerEnabled
= FALSE
;
994 uiDueTime
= CalculateDueTime();
996 if (TaskBarSettings
.bShowSeconds
)
998 uiWait1
= 1000 - 200;
1003 uiWait1
= 60 * 1000 - 200;
1004 uiWait2
= 60 * 1000;
1007 if (uiDueTime
> uiWait1
)
1009 /* The update of the clock will be up to 200 ms late, but that's
1010 acceptable. We're going to setup a timer that fires depending
1012 Ret
= SetTimer(ID_TRAYCLOCK_TIMER
, uiWait2
, NULL
) != 0;
1013 IsTimerEnabled
= Ret
;
1015 /* Update the time */
1020 /* Recalibrate the timer and recalculate again when the current
1021 minute/second ends. */
1026 LRESULT
OnDestroy(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1028 /* Disable all timers */
1031 KillTimer(ID_TRAYCLOCK_TIMER
);
1034 if (IsInitTimerEnabled
)
1036 KillTimer(ID_TRAYCLOCK_TIMER_INIT
);
1042 LRESULT
OnPaint(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1050 HDC hDC
= (HDC
) wParam
;
1054 hDC
= BeginPaint(&ps
);
1060 if (LinesMeasured
&&
1061 GetClientRect(&rcClient
))
1063 iPrevBkMode
= SetBkMode(hDC
, TRANSPARENT
);
1065 SetTextColor(hDC
, textColor
);
1067 hPrevFont
= (HFONT
) SelectObject(hDC
, hFont
);
1069 rcClient
.left
= (rcClient
.right
/ 2) - (CurrentSize
.cx
/ 2);
1070 rcClient
.top
= (rcClient
.bottom
/ 2) - (CurrentSize
.cy
/ 2);
1071 rcClient
.right
= rcClient
.left
+ CurrentSize
.cx
;
1072 rcClient
.bottom
= rcClient
.top
+ CurrentSize
.cy
;
1074 for (i
= 0, line
= 0;
1075 i
< CLOCKWND_FORMAT_COUNT
&& line
< VisibleLines
;
1078 if (LineSizes
[i
].cx
!= 0)
1081 rcClient
.left
+ (CurrentSize
.cx
/ 2) - (LineSizes
[i
].cx
/ 2) +
1082 TRAY_CLOCK_WND_SPACING_X
,
1083 rcClient
.top
+ TRAY_CLOCK_WND_SPACING_Y
,
1085 wcslen(szLines
[i
]));
1087 rcClient
.top
+= LineSizes
[i
].cy
+ LineSpacing
;
1092 SelectObject(hDC
, hPrevFont
);
1094 SetBkMode(hDC
, iPrevBkMode
);
1105 VOID
SetFont(IN HFONT hNewFont
, IN BOOL bRedraw
)
1108 LinesMeasured
= MeasureLines();
1111 InvalidateRect(NULL
, TRUE
);
1115 LRESULT
DrawBackground(HDC hdc
)
1119 GetClientRect(&rect
);
1120 DrawThemeParentBackground(m_hWnd
, hdc
, &rect
);
1125 LRESULT
OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1127 HDC hdc
= (HDC
) wParam
;
1135 return DrawBackground(hdc
);
1138 LRESULT
OnTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1142 case ID_TRAYCLOCK_TIMER
:
1146 case ID_TRAYCLOCK_TIMER_INIT
:
1153 LRESULT
OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1155 IsHorizontal
= (BOOL
) wParam
;
1157 return (LRESULT
) GetMinimumSize((BOOL
) wParam
, (PSIZE
) lParam
) != 0;
1160 LRESULT
OnUpdateTime(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1162 return (LRESULT
) ResetTime();
1165 LRESULT
OnNcHitTest(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1167 return HTTRANSPARENT
;
1170 LRESULT
OnSetFont(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1172 SetFont((HFONT
) wParam
, (BOOL
) LOWORD(lParam
));
1176 LRESULT
OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1182 LRESULT
OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1186 szClient
.cx
= LOWORD(lParam
);
1187 szClient
.cy
= HIWORD(lParam
);
1189 VisibleLines
= GetMinimumSize(IsHorizontal
, &szClient
);
1190 CurrentSize
= szClient
;
1192 InvalidateRect(NULL
, TRUE
);
1196 DECLARE_WND_CLASS_EX(szTrayClockWndClass
, CS_DBLCLKS
, COLOR_3DFACE
)
1198 BEGIN_MSG_MAP(CTrayClockWnd
)
1199 MESSAGE_HANDLER(WM_CREATE
, OnCreate
)
1200 MESSAGE_HANDLER(WM_DESTROY
, OnDestroy
)
1201 MESSAGE_HANDLER(WM_ERASEBKGND
, OnEraseBackground
)
1202 MESSAGE_HANDLER(WM_SIZE
, OnSize
)
1203 MESSAGE_HANDLER(WM_PAINT
, OnPaint
)
1204 MESSAGE_HANDLER(WM_PRINTCLIENT
, OnPaint
)
1205 MESSAGE_HANDLER(WM_THEMECHANGED
, OnThemeChanged
)
1206 MESSAGE_HANDLER(WM_TIMER
, OnTimer
)
1207 MESSAGE_HANDLER(WM_NCHITTEST
, OnNcHitTest
)
1208 MESSAGE_HANDLER(WM_SETFONT
, OnSetFont
)
1209 MESSAGE_HANDLER(TCWM_GETMINIMUMSIZE
, OnGetMinimumSize
)
1210 MESSAGE_HANDLER(TCWM_UPDATETIME
, OnUpdateTime
)
1214 HWND
_Init(IN HWND hWndParent
, IN BOOL bVisible
)
1216 IsHorizontal
= TRUE
;
1218 hWndNotify
= hWndParent
;
1220 /* Create the window. The tray window is going to move it to the correct
1221 position and resize it as needed. */
1222 DWORD dwStyle
= WS_CHILD
| WS_CLIPSIBLINGS
;
1224 dwStyle
|= WS_VISIBLE
;
1226 Create(hWndParent
, 0, NULL
, dwStyle
);
1229 SetWindowTheme(m_hWnd
, L
"TrayNotify", NULL
);
1240 static const WCHAR szTrayNotifyWndClass
[] = TEXT("TrayNotifyWnd");
1242 #define TRAY_NOTIFY_WND_SPACING_X 2
1243 #define TRAY_NOTIFY_WND_SPACING_Y 2
1245 class CTrayNotifyWnd
:
1246 public CComObjectRootEx
<CComMultiThreadModelNoCS
>,
1247 public CWindowImpl
< CTrayNotifyWnd
, CWindow
, CControlWinTraits
>
1251 CSysPagerWnd
* m_pager
;
1252 CTrayClockWnd
* m_clock
;
1254 CComPtr
<ITrayWindow
> TrayWindow
;
1257 SIZE szTrayClockMin
;
1259 MARGINS ContentMargin
;
1266 DWORD HideClock
: 1;
1267 DWORD IsHorizontal
: 1;
1280 ZeroMemory(&szTrayClockMin
, sizeof(szTrayClockMin
));
1281 ZeroMemory(&szTrayNotify
, sizeof(szTrayNotify
));
1282 ZeroMemory(&ContentMargin
, sizeof(ContentMargin
));
1284 virtual ~CTrayNotifyWnd() { }
1286 LRESULT
OnThemeChanged()
1289 CloseThemeData(TrayTheme
);
1291 if (IsThemeActive())
1292 TrayTheme
= OpenThemeData(m_hWnd
, L
"TrayNotify");
1298 SetWindowExStyle(m_hWnd
, WS_EX_STATICEDGE
, 0);
1300 GetThemeMargins(TrayTheme
,
1310 SetWindowExStyle(m_hWnd
, WS_EX_STATICEDGE
, WS_EX_STATICEDGE
);
1312 ContentMargin
.cxLeftWidth
= 0;
1313 ContentMargin
.cxRightWidth
= 0;
1314 ContentMargin
.cyTopHeight
= 0;
1315 ContentMargin
.cyBottomHeight
= 0;
1321 LRESULT
OnThemeChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1323 return OnThemeChanged();
1326 LRESULT
OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1328 m_clock
= new CTrayClockWnd();
1329 m_clock
->_Init(m_hWnd
, !HideClock
);
1331 m_pager
= new CSysPagerWnd();
1332 m_pager
->_Init(m_hWnd
, !HideClock
);
1337 BOOL
GetMinimumSize(IN OUT PSIZE pSize
)
1339 SIZE szClock
= { 0, 0 };
1340 SIZE szTray
= { 0, 0 };
1346 szClock
.cy
= pSize
->cy
- 2 * TRAY_NOTIFY_WND_SPACING_Y
;
1347 if (szClock
.cy
<= 0)
1352 szClock
.cx
= pSize
->cx
- 2 * TRAY_NOTIFY_WND_SPACING_X
;
1353 if (szClock
.cx
<= 0)
1357 m_clock
->SendMessage(TCWM_GETMINIMUMSIZE
, (WPARAM
) IsHorizontal
, (LPARAM
) &szClock
);
1359 szTrayClockMin
= szClock
;
1363 szTrayClockMin
= szClock
;
1367 szTray
.cy
= pSize
->cy
- 2 * TRAY_NOTIFY_WND_SPACING_Y
;
1371 szTray
.cx
= pSize
->cx
- 2 * TRAY_NOTIFY_WND_SPACING_X
;
1374 m_pager
->GetSize(IsHorizontal
, &szTray
);
1376 szTrayNotify
= szTray
;
1380 pSize
->cx
= 2 * TRAY_NOTIFY_WND_SPACING_X
;
1383 pSize
->cx
+= TRAY_NOTIFY_WND_SPACING_X
+ szTrayClockMin
.cx
;
1385 pSize
->cx
+= szTray
.cx
;
1389 pSize
->cy
= 2 * TRAY_NOTIFY_WND_SPACING_Y
;
1392 pSize
->cy
+= TRAY_NOTIFY_WND_SPACING_Y
+ szTrayClockMin
.cy
;
1394 pSize
->cy
+= szTray
.cy
;
1397 pSize
->cy
+= ContentMargin
.cyTopHeight
+ ContentMargin
.cyBottomHeight
;
1398 pSize
->cx
+= ContentMargin
.cxLeftWidth
+ ContentMargin
.cxRightWidth
;
1403 VOID
Size(IN
const SIZE
*pszClient
)
1412 ptClock
.x
= pszClient
->cx
- TRAY_NOTIFY_WND_SPACING_X
- szTrayClockMin
.cx
;
1413 ptClock
.y
= TRAY_NOTIFY_WND_SPACING_Y
;
1414 szClock
.cx
= szTrayClockMin
.cx
;
1415 szClock
.cy
= pszClient
->cy
- (2 * TRAY_NOTIFY_WND_SPACING_Y
);
1419 ptClock
.x
= TRAY_NOTIFY_WND_SPACING_X
;
1420 ptClock
.y
= pszClient
->cy
- TRAY_NOTIFY_WND_SPACING_Y
- szTrayClockMin
.cy
;
1421 szClock
.cx
= pszClient
->cx
- (2 * TRAY_NOTIFY_WND_SPACING_X
);
1422 szClock
.cy
= szTrayClockMin
.cy
;
1425 m_clock
->SetWindowPos(
1435 ptClock
.x
-= szTrayNotify
.cx
;
1439 ptClock
.y
-= szTrayNotify
.cy
;
1442 m_pager
->SetWindowPos(
1452 LRESULT
DrawBackground(HDC hdc
)
1457 GetClientRect(&rect
);
1461 if (IsThemeBackgroundPartiallyTransparent(TrayTheme
, TNP_BACKGROUND
, 0))
1463 DrawThemeParentBackground(m_hWnd
, hdc
, &rect
);
1466 res
= DrawThemeBackground(TrayTheme
, hdc
, TNP_BACKGROUND
, 0, &rect
, 0);
1472 LRESULT
OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1474 HDC hdc
= (HDC
) wParam
;
1482 return DrawBackground(hdc
);
1485 BOOL
NotifyIconCmd(WPARAM wParam
, LPARAM lParam
)
1489 return m_pager
->NotifyIconCmd(wParam
, lParam
);
1495 BOOL
GetClockRect(OUT PRECT rcClock
)
1497 if (!m_clock
->IsWindowVisible())
1500 return m_clock
->GetWindowRect(rcClock
);
1503 LRESULT
OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1505 BOOL Horizontal
= (BOOL
) wParam
;
1507 if (Horizontal
!= IsHorizontal
)
1509 IsHorizontal
= Horizontal
;
1511 SetWindowTheme(m_hWnd
, L
"TrayNotifyHoriz", NULL
);
1513 SetWindowTheme(m_hWnd
, L
"TrayNotifyVert", NULL
);
1516 return (LRESULT
) GetMinimumSize((PSIZE
) lParam
);
1519 LRESULT
OnUpdateTime(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1521 if (m_clock
!= NULL
)
1523 /* Forward the message to the tray clock window procedure */
1524 return m_clock
->OnUpdateTime(uMsg
, wParam
, lParam
, bHandled
);
1529 LRESULT
OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1533 szClient
.cx
= LOWORD(lParam
);
1534 szClient
.cy
= HIWORD(lParam
);
1541 LRESULT
OnNcHitTest(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1543 return HTTRANSPARENT
;
1546 LRESULT
OnShowClock(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1548 BOOL PrevHidden
= HideClock
;
1549 HideClock
= (wParam
== 0);
1551 if (m_clock
!= NULL
&& PrevHidden
!= HideClock
)
1553 m_clock
->ShowWindow(HideClock
? SW_HIDE
: SW_SHOW
);
1556 return (LRESULT
) (!PrevHidden
);
1559 LRESULT
OnNotify(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1561 const NMHDR
*nmh
= (const NMHDR
*) lParam
;
1563 if (nmh
->hwndFrom
== m_clock
->m_hWnd
)
1565 /* Pass down notifications */
1566 return m_clock
->SendMessage(WM_NOTIFY
, wParam
, lParam
);
1572 LRESULT
OnSetFont(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1574 if (m_clock
!= NULL
)
1576 m_clock
->SendMessageW(WM_SETFONT
, wParam
, lParam
);
1583 LRESULT
OnCtxMenu(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1589 DECLARE_WND_CLASS_EX(szTrayNotifyWndClass
, CS_DBLCLKS
, COLOR_3DFACE
)
1591 BEGIN_MSG_MAP(CTrayNotifyWnd
)
1592 MESSAGE_HANDLER(WM_CREATE
, OnCreate
)
1593 MESSAGE_HANDLER(WM_THEMECHANGED
, OnThemeChanged
)
1594 MESSAGE_HANDLER(WM_ERASEBKGND
, OnEraseBackground
)
1595 MESSAGE_HANDLER(WM_SIZE
, OnSize
)
1596 MESSAGE_HANDLER(WM_NCHITTEST
, OnNcHitTest
)
1597 MESSAGE_HANDLER(WM_NOTIFY
, OnNotify
)
1598 MESSAGE_HANDLER(WM_SETFONT
, OnSetFont
)
1599 MESSAGE_HANDLER(WM_CONTEXTMENU
, OnCtxMenu
) // FIXME: This handler is not necessary in Windows
1600 MESSAGE_HANDLER(TNWM_GETMINIMUMSIZE
, OnGetMinimumSize
)
1601 MESSAGE_HANDLER(TNWM_UPDATETIME
, OnUpdateTime
)
1602 MESSAGE_HANDLER(TNWM_SHOWCLOCK
, OnShowClock
)
1605 HWND
_Init(IN OUT ITrayWindow
*TrayWindow
, IN BOOL bHideClock
)
1607 HWND hWndTrayWindow
;
1609 hWndTrayWindow
= TrayWindow
->GetHWND();
1610 if (hWndTrayWindow
== NULL
)
1613 this->TrayWindow
= TrayWindow
;
1614 this->HideClock
= bHideClock
;
1615 this->hWndNotify
= hWndTrayWindow
;
1617 DWORD dwStyle
= WS_CHILD
| WS_VISIBLE
| WS_CLIPSIBLINGS
| WS_CLIPCHILDREN
;
1618 return Create(hWndTrayWindow
, 0, NULL
, dwStyle
, WS_EX_STATICEDGE
);
1622 HWND
CreateTrayNotifyWnd(IN OUT ITrayWindow
*Tray
, BOOL bHideClock
, CTrayNotifyWnd
** ppinstance
)
1624 CTrayNotifyWnd
* pTrayNotify
= new CTrayNotifyWnd();
1625 // TODO: Destroy after the window is destroyed
1626 *ppinstance
= pTrayNotify
;
1628 return pTrayNotify
->_Init(Tray
, bHideClock
);
1632 TrayNotify_NotifyIconCmd(CTrayNotifyWnd
* pTrayNotify
, WPARAM wParam
, LPARAM lParam
)
1634 return pTrayNotify
->NotifyIconCmd(wParam
, lParam
);
1638 TrayNotify_GetClockRect(CTrayNotifyWnd
* pTrayNotify
, OUT PRECT rcClock
)
1640 return pTrayNotify
->GetClockRect(rcClock
);