xref: /reactos/base/shell/explorer/trayntfy.cpp (revision d56c9a89)
1 /*
2  * ReactOS Explorer
3  *
4  * Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org>
5  *
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.
10  *
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.
15  *
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
19  */
20 
21 #include "precomp.h"
22 
23 /*
24  * SysPagerWnd
25  */
26 static const WCHAR szSysPagerWndClass [] = L"SysPager";
27 
28 // Data comes from shell32/systray.cpp -> TrayNotifyCDS_Dummy
29 typedef struct _SYS_PAGER_COPY_DATA
30 {
31     DWORD           cookie;
32     DWORD           notify_code;
33     NOTIFYICONDATA  nicon_data;
34 } SYS_PAGER_COPY_DATA, *PSYS_PAGER_COPY_DATA;
35 
36 class CNotifyToolbar :
37     public CWindowImplBaseT< CToolbar<NOTIFYICONDATA>, CControlWinTraits >
38 {
39     HIMAGELIST m_ImageList;
40     int m_VisibleButtonCount;
41 
42 public:
43     CNotifyToolbar() :
44         m_ImageList(NULL),
45         m_VisibleButtonCount(0)
46     {
47     }
48 
49     ~CNotifyToolbar()
50     {
51     }
52 
53     int GetVisibleButtonCount()
54     {
55         return m_VisibleButtonCount;
56     }
57 
58     int FindItemByIconData(IN CONST NOTIFYICONDATA *iconData, NOTIFYICONDATA ** pdata)
59     {
60         int count = GetButtonCount();
61 
62         for (int i = 0; i < count; i++)
63         {
64             NOTIFYICONDATA * data;
65 
66             data = GetItemData(i);
67 
68             if (data->hWnd == iconData->hWnd &&
69                 data->uID == iconData->uID)
70             {
71                 if (pdata)
72                     *pdata = data;
73                 return i;
74             }
75         }
76 
77         return -1;
78     }
79 
80     BOOL AddButton(IN CONST NOTIFYICONDATA *iconData)
81     {
82         TBBUTTON tbBtn;
83         NOTIFYICONDATA * notifyItem;
84         WCHAR text[] = L"";
85 
86         int index = FindItemByIconData(iconData, &notifyItem);
87         if (index >= 0)
88         {
89             return UpdateButton(iconData);
90         }
91 
92         notifyItem = new NOTIFYICONDATA();
93         ZeroMemory(notifyItem, sizeof(*notifyItem));
94 
95         notifyItem->hWnd = iconData->hWnd;
96         notifyItem->uID = iconData->uID;
97 
98         tbBtn.fsState = TBSTATE_ENABLED;
99         tbBtn.fsStyle = BTNS_NOPREFIX;
100         tbBtn.dwData = (DWORD_PTR)notifyItem;
101         tbBtn.iString = (INT_PTR) text;
102         tbBtn.idCommand = GetButtonCount();
103 
104         if (iconData->uFlags & NIF_MESSAGE)
105         {
106             notifyItem->uCallbackMessage = iconData->uCallbackMessage;
107         }
108 
109         if (iconData->uFlags & NIF_ICON)
110         {
111             notifyItem->hIcon = (HICON)CopyImage(iconData->hIcon, IMAGE_ICON, 0, 0, 0);
112             tbBtn.iBitmap = ImageList_AddIcon(m_ImageList, iconData->hIcon);
113         }
114 
115         if (iconData->uFlags & NIF_TIP)
116         {
117             StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip);
118         }
119 
120         m_VisibleButtonCount++;
121         if (iconData->uFlags & NIF_STATE)
122         {
123             notifyItem->dwState &= ~iconData->dwStateMask;
124             notifyItem->dwState |= (iconData->dwState & iconData->dwStateMask);
125             if (notifyItem->dwState & NIS_HIDDEN)
126             {
127                 tbBtn.fsState |= TBSTATE_HIDDEN;
128                 m_VisibleButtonCount--;
129             }
130         }
131 
132         /* TODO: support NIF_INFO, NIF_GUID, NIF_REALTIME, NIF_SHOWTIP */
133 
134         CToolbar::AddButton(&tbBtn);
135         SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
136 
137         return TRUE;
138     }
139 
140     BOOL UpdateButton(IN CONST NOTIFYICONDATA *iconData)
141     {
142         NOTIFYICONDATA * notifyItem;
143         TBBUTTONINFO tbbi = { 0 };
144 
145         int index = FindItemByIconData(iconData, &notifyItem);
146         if (index < 0)
147         {
148             return AddButton(iconData);
149         }
150 
151         tbbi.cbSize = sizeof(tbbi);
152         tbbi.dwMask = TBIF_BYINDEX | TBIF_COMMAND;
153         tbbi.idCommand = index;
154 
155         if (iconData->uFlags & NIF_MESSAGE)
156         {
157             notifyItem->uCallbackMessage = iconData->uCallbackMessage;
158         }
159 
160         if (iconData->uFlags & NIF_ICON)
161         {
162             DestroyIcon(notifyItem->hIcon);
163             notifyItem->hIcon = (HICON)CopyImage(iconData->hIcon, IMAGE_ICON, 0, 0, 0);
164             tbbi.dwMask |= TBIF_IMAGE;
165             tbbi.iImage = ImageList_ReplaceIcon(m_ImageList, index, iconData->hIcon);
166         }
167 
168         if (iconData->uFlags & NIF_TIP)
169         {
170             StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip);
171         }
172 
173         if (iconData->uFlags & NIF_STATE)
174         {
175             if (iconData->dwStateMask & NIS_HIDDEN &&
176                 (notifyItem->dwState & NIS_HIDDEN) != (iconData->dwState & NIS_HIDDEN))
177             {
178                 tbbi.dwMask |= TBIF_STATE;
179                 if (iconData->dwState & NIS_HIDDEN)
180                 {
181                     tbbi.fsState |= TBSTATE_HIDDEN;
182                     m_VisibleButtonCount--;
183                 }
184                 else
185                 {
186                     tbbi.fsState &= ~TBSTATE_HIDDEN;
187                     m_VisibleButtonCount++;
188                 }
189             }
190 
191             notifyItem->dwState &= ~iconData->dwStateMask;
192             notifyItem->dwState |= (iconData->dwState & iconData->dwStateMask);
193         }
194 
195         /* TODO: support NIF_INFO, NIF_GUID, NIF_REALTIME, NIF_SHOWTIP */
196 
197         SetButtonInfo(index, &tbbi);
198 
199         return TRUE;
200     }
201 
202     BOOL RemoveButton(IN CONST NOTIFYICONDATA *iconData)
203     {
204         NOTIFYICONDATA * notifyItem;
205 
206         int index = FindItemByIconData(iconData, &notifyItem);
207         if (index < 0)
208             return FALSE;
209 
210         if (!(notifyItem->dwState & NIS_HIDDEN))
211         {
212             m_VisibleButtonCount--;
213         }
214 
215         DestroyIcon(notifyItem->hIcon);
216 
217         delete notifyItem;
218 
219         ImageList_Remove(m_ImageList, index);
220 
221         int count = GetButtonCount();
222 
223         /* shift all buttons one index to the left -- starting one index right
224            from item to delete -- to preserve their correct icon and tip */
225         for (int i = index; i < count - 1; i++)
226         {
227             notifyItem = GetItemData(i + 1);
228             SetItemData(i, notifyItem);
229             UpdateButton(notifyItem);
230         }
231 
232         /* Delete the right-most, now obsolete button */
233         DeleteButton(count - 1);
234 
235         return TRUE;
236     }
237 
238     VOID GetTooltipText(int index, LPTSTR szTip, DWORD cchTip)
239     {
240         NOTIFYICONDATA * notifyItem;
241         notifyItem = GetItemData(index);
242 
243         if (notifyItem)
244         {
245             StringCchCopy(szTip, cchTip, notifyItem->szTip);
246         }
247     }
248 
249     VOID ResizeImagelist()
250     {
251         int cx, cy;
252         HIMAGELIST iml;
253 
254         if (!ImageList_GetIconSize(m_ImageList, &cx, &cy))
255             return;
256 
257         if (cx == GetSystemMetrics(SM_CXSMICON) && cy == GetSystemMetrics(SM_CYSMICON))
258             return;
259 
260         iml = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);
261         if (!iml)
262             return;
263 
264         ImageList_Destroy(m_ImageList);
265         m_ImageList = iml;
266         SetImageList(m_ImageList);
267 
268         int count = GetButtonCount();
269         for (int i = 0; i < count; i++)
270         {
271             NOTIFYICONDATA * data = GetItemData(i);
272             INT iIcon = ImageList_AddIcon(iml, data->hIcon);
273             TBBUTTONINFO tbbi = { sizeof(tbbi), TBIF_BYINDEX | TBIF_IMAGE, 0, iIcon};
274             SetButtonInfo(i, &tbbi);
275         }
276 
277         SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
278     }
279 
280 private:
281 
282     VOID SendMouseEvent(IN WORD wIndex, IN UINT uMsg, IN WPARAM wParam)
283     {
284         static LPCWSTR eventNames [] = {
285             L"WM_MOUSEMOVE",
286             L"WM_LBUTTONDOWN",
287             L"WM_LBUTTONUP",
288             L"WM_LBUTTONDBLCLK",
289             L"WM_RBUTTONDOWN",
290             L"WM_RBUTTONUP",
291             L"WM_RBUTTONDBLCLK",
292             L"WM_MBUTTONDOWN",
293             L"WM_MBUTTONUP",
294             L"WM_MBUTTONDBLCLK",
295             L"WM_MOUSEWHEEL",
296             L"WM_XBUTTONDOWN",
297             L"WM_XBUTTONUP",
298             L"WM_XBUTTONDBLCLK"
299         };
300 
301         NOTIFYICONDATA * notifyItem = GetItemData(wIndex);
302 
303         if (!::IsWindow(notifyItem->hWnd))
304         {
305             // We detect and destroy icons with invalid handles only on mouse move over systray, same as MS does.
306             // Alternatively we could search for them periodically (would waste more resources).
307             TRACE("destroying icon with invalid handle\n");
308 
309             HWND parentHWND = GetParent();
310             parentHWND = ::GetParent(parentHWND);
311 
312             RECT windowRect;
313             ::GetClientRect(parentHWND, &windowRect);
314 
315             RemoveButton(notifyItem);
316 
317             SendMessage(parentHWND,
318                 WM_SIZE,
319                 0,
320                 MAKELONG(windowRect.right - windowRect.left,
321                          windowRect.bottom - windowRect.top));
322 
323             return;
324         }
325 
326         if (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST)
327         {
328             TRACE("Sending message %S from button %d to %p (msg=%x, w=%x, l=%x)...\n",
329                      eventNames[uMsg - WM_MOUSEFIRST], wIndex,
330                      notifyItem->hWnd, notifyItem->uCallbackMessage, notifyItem->uID, uMsg);
331         }
332 
333         DWORD pid;
334         GetWindowThreadProcessId(notifyItem->hWnd, &pid);
335 
336         if (pid == GetCurrentProcessId() ||
337             (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST))
338         {
339             ::PostMessage(notifyItem->hWnd,
340                           notifyItem->uCallbackMessage,
341                           notifyItem->uID,
342                           uMsg);
343         }
344         else
345         {
346             SendMessage(notifyItem->hWnd,
347                         notifyItem->uCallbackMessage,
348                         notifyItem->uID,
349                         uMsg);
350         }
351     }
352 
353     LRESULT OnMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
354     {
355         POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
356 
357         INT iBtn = HitTest(&pt);
358 
359         if (iBtn >= 0)
360         {
361             SendMouseEvent(iBtn, uMsg, wParam);
362         }
363 
364         bHandled = FALSE;
365         return FALSE;
366     }
367 
368     LRESULT OnTooltipShow(INT uCode, LPNMHDR hdr, BOOL& bHandled)
369     {
370         RECT rcTip, rcItem;
371         ::GetWindowRect(hdr->hwndFrom, &rcTip);
372 
373         SIZE szTip = { rcTip.right - rcTip.left, rcTip.bottom - rcTip.top };
374 
375         INT iBtn = GetHotItem();
376 
377         if (iBtn >= 0)
378         {
379             MONITORINFO monInfo = { 0 };
380             HMONITOR hMon = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST);
381 
382             monInfo.cbSize = sizeof(monInfo);
383 
384             if (hMon)
385                 GetMonitorInfo(hMon, &monInfo);
386             else
387                 ::GetWindowRect(GetDesktopWindow(), &monInfo.rcMonitor);
388 
389             GetItemRect(iBtn, &rcItem);
390 
391             POINT ptItem = { rcItem.left, rcItem.top };
392             SIZE szItem = { rcItem.right - rcItem.left, rcItem.bottom - rcItem.top };
393             ClientToScreen(&ptItem);
394 
395             ptItem.x += szItem.cx / 2;
396             ptItem.y -= szTip.cy;
397 
398             if (ptItem.x + szTip.cx > monInfo.rcMonitor.right)
399                 ptItem.x = monInfo.rcMonitor.right - szTip.cx;
400 
401             if (ptItem.y + szTip.cy > monInfo.rcMonitor.bottom)
402                 ptItem.y = monInfo.rcMonitor.bottom - szTip.cy;
403 
404             if (ptItem.x < monInfo.rcMonitor.left)
405                 ptItem.x = monInfo.rcMonitor.left;
406 
407             if (ptItem.y < monInfo.rcMonitor.top)
408                 ptItem.y = monInfo.rcMonitor.top;
409 
410             TRACE("ptItem { %d, %d }\n", ptItem.x, ptItem.y);
411 
412             ::SetWindowPos(hdr->hwndFrom, NULL, ptItem.x, ptItem.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
413 
414             return TRUE;
415         }
416 
417         bHandled = FALSE;
418         return 0;
419     }
420 
421 
422 public:
423     BEGIN_MSG_MAP(CNotifyToolbar)
424         MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseEvent)
425         NOTIFY_CODE_HANDLER(TTN_SHOW, OnTooltipShow)
426     END_MSG_MAP()
427 
428     void Initialize(HWND hWndParent)
429     {
430         DWORD styles =
431             WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
432             TBSTYLE_FLAT | TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | TBSTYLE_TRANSPARENT |
433             CCS_TOP | CCS_NORESIZE | CCS_NOPARENTALIGN | CCS_NODIVIDER;
434 
435         SubclassWindow(CToolbar::Create(hWndParent, styles));
436 
437         SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
438 
439         m_ImageList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);
440         SetImageList(m_ImageList);
441 
442         TBMETRICS tbm = {sizeof(tbm)};
443         tbm.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING | TBMF_PAD;
444         tbm.cxPad = 1;
445         tbm.cyPad = 1;
446         tbm.cxButtonSpacing = 1;
447         tbm.cyButtonSpacing = 1;
448         SetMetrics(&tbm);
449 
450         SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
451     }
452 };
453 
454 class CSysPagerWnd :
455     public CComObjectRootEx<CComMultiThreadModelNoCS>,
456     public CWindowImpl < CSysPagerWnd, CWindow, CControlWinTraits >
457 {
458     CNotifyToolbar Toolbar;
459 
460 public:
461     CSysPagerWnd() {}
462     virtual ~CSysPagerWnd() {}
463 
464     LRESULT DrawBackground(HDC hdc)
465     {
466         RECT rect;
467 
468         GetClientRect(&rect);
469         DrawThemeParentBackground(m_hWnd, hdc, &rect);
470 
471         return TRUE;
472     }
473 
474     LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
475     {
476         HDC hdc = (HDC) wParam;
477 
478         if (!IsAppThemed())
479         {
480             bHandled = FALSE;
481             return 0;
482         }
483 
484         return DrawBackground(hdc);
485     }
486 
487     LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
488     {
489         Toolbar.Initialize(m_hWnd);
490 
491         // Explicitly request running applications to re-register their systray icons
492         ::SendNotifyMessageW(HWND_BROADCAST,
493                              RegisterWindowMessageW(L"TaskbarCreated"),
494                              0, 0);
495 
496         return TRUE;
497     }
498 
499     BOOL NotifyIconCmd(WPARAM wParam, LPARAM lParam)
500     {
501         PCOPYDATASTRUCT cpData = (PCOPYDATASTRUCT) lParam;
502         if (cpData->dwData == 1)
503         {
504             SYS_PAGER_COPY_DATA * data;
505             NOTIFYICONDATA *iconData;
506             HWND parentHWND;
507             RECT windowRect;
508             BOOL ret = FALSE;
509             parentHWND = GetParent();
510             parentHWND = ::GetParent(parentHWND);
511             ::GetClientRect(parentHWND, &windowRect);
512 
513             int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
514 
515             data = (PSYS_PAGER_COPY_DATA) cpData->lpData;
516             iconData = &data->nicon_data;
517 
518             TRACE("NotifyIconCmd received. Code=%d\n", data->notify_code);
519             switch (data->notify_code)
520             {
521             case NIM_ADD:
522                 ret = Toolbar.AddButton(iconData);
523                 break;
524             case NIM_MODIFY:
525                 ret = Toolbar.UpdateButton(iconData);
526                 break;
527             case NIM_DELETE:
528                 ret = Toolbar.RemoveButton(iconData);
529                 break;
530             default:
531                 TRACE("NotifyIconCmd received with unknown code %d.\n", data->notify_code);
532                 return FALSE;
533             }
534 
535             if (VisibleButtonCount != Toolbar.GetVisibleButtonCount())
536             {
537                 SendMessage(parentHWND, WM_SIZE, 0, 0);
538             }
539 
540             return ret;
541         }
542 
543         return TRUE;
544     }
545 
546     void GetSize(IN BOOL IsHorizontal, IN PSIZE size)
547     {
548         /* Get the ideal height or width */
549 #if 0
550         /* Unfortunately this doens't work correctly in ros */
551         Toolbar.GetIdealSize(!IsHorizontal, size);
552 
553         /* Make the reference dimension an exact multiple of the icon size */
554         if (IsHorizontal)
555             size->cy -= size->cy % GetSystemMetrics(SM_CYSMICON);
556         else
557             size->cx -= size->cx % GetSystemMetrics(SM_CXSMICON);
558 
559 #else
560         INT rows = 0;
561         INT columns = 0;
562         INT cyButton = GetSystemMetrics(SM_CYSMICON) + 2;
563         INT cxButton = GetSystemMetrics(SM_CXSMICON) + 2;
564         int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
565 
566         if (IsHorizontal)
567         {
568             rows = max(size->cy / cyButton, 1);
569             columns = (VisibleButtonCount + rows - 1) / rows;
570         }
571         else
572         {
573             columns = max(size->cx / cxButton, 1);
574             rows = (VisibleButtonCount + columns - 1) / columns;
575         }
576         size->cx = columns * cxButton;
577         size->cy = rows * cyButton;
578 #endif
579     }
580 
581     LRESULT OnGetInfoTip(INT uCode, LPNMHDR hdr, BOOL& bHandled)
582     {
583         NMTBGETINFOTIPW * nmtip = (NMTBGETINFOTIPW *) hdr;
584         Toolbar.GetTooltipText(nmtip->iItem, nmtip->pszText, nmtip->cchTextMax);
585         return TRUE;
586     }
587 
588     LRESULT OnCustomDraw(INT uCode, LPNMHDR hdr, BOOL& bHandled)
589     {
590         NMCUSTOMDRAW * cdraw = (NMCUSTOMDRAW *) hdr;
591         switch (cdraw->dwDrawStage)
592         {
593         case CDDS_PREPAINT:
594             return CDRF_NOTIFYITEMDRAW;
595 
596         case CDDS_ITEMPREPAINT:
597             return TBCDRF_NOBACKGROUND | TBCDRF_NOEDGES | TBCDRF_NOOFFSET | TBCDRF_NOMARK | TBCDRF_NOETCHEDEFFECT;
598         }
599         return TRUE;
600     }
601 
602     LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
603     {
604         LRESULT Ret = TRUE;
605         SIZE szClient;
606         szClient.cx = LOWORD(lParam);
607         szClient.cy = HIWORD(lParam);
608 
609         Ret = DefWindowProc(uMsg, wParam, lParam);
610 
611         if (Toolbar)
612         {
613             Toolbar.SetWindowPos(NULL, 0, 0, szClient.cx, szClient.cy, SWP_NOZORDER);
614             Toolbar.AutoSize();
615 
616             RECT rc;
617             Toolbar.GetClientRect(&rc);
618 
619             SIZE szBar = { rc.right - rc.left, rc.bottom - rc.top };
620 
621             INT xOff = (szClient.cx - szBar.cx) / 2;
622             INT yOff = (szClient.cy - szBar.cy) / 2;
623 
624             Toolbar.SetWindowPos(NULL, xOff, yOff, szBar.cx, szBar.cy, SWP_NOZORDER);
625         }
626         return Ret;
627     }
628 
629     LRESULT OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
630     {
631         bHandled = TRUE;
632         return 0;
633     }
634 
635     void ResizeImagelist()
636     {
637         Toolbar.ResizeImagelist();
638     }
639 
640     DECLARE_WND_CLASS_EX(szSysPagerWndClass, CS_DBLCLKS, COLOR_3DFACE)
641 
642     BEGIN_MSG_MAP(CSysPagerWnd)
643         MESSAGE_HANDLER(WM_CREATE, OnCreate)
644         MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
645         MESSAGE_HANDLER(WM_SIZE, OnSize)
646         MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu)
647         NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW, OnGetInfoTip)
648         NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
649     END_MSG_MAP()
650 
651     HWND _Init(IN HWND hWndParent, IN BOOL bVisible)
652     {
653         DWORD dwStyle;
654 
655         /* Create the window. The tray window is going to move it to the correct
656             position and resize it as needed. */
657         dwStyle = WS_CHILD | WS_CLIPSIBLINGS;
658         if (bVisible)
659             dwStyle |= WS_VISIBLE;
660 
661         Create(hWndParent, 0, NULL, dwStyle);
662 
663         if (!m_hWnd)
664         {
665             return NULL;
666         }
667 
668         SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
669 
670         return m_hWnd;
671     }
672 };
673 
674 /*
675  * TrayClockWnd
676  */
677 
678 static const WCHAR szTrayClockWndClass[] = L"TrayClockWClass";
679 
680 #define ID_TRAYCLOCK_TIMER  0
681 #define ID_TRAYCLOCK_TIMER_INIT 1
682 
683 static const struct
684 {
685     BOOL IsTime;
686     DWORD dwFormatFlags;
687     LPCWSTR lpFormat;
688 } ClockWndFormats [] = {
689     { TRUE, 0, NULL },
690     { FALSE, 0, L"dddd" },
691     { FALSE, DATE_SHORTDATE, NULL }
692 };
693 
694 #define CLOCKWND_FORMAT_COUNT (_ARRAYSIZE(ClockWndFormats))
695 
696 #define TRAY_CLOCK_WND_SPACING_X    0
697 #define TRAY_CLOCK_WND_SPACING_Y    0
698 
699 class CTrayClockWnd :
700     public CComObjectRootEx<CComMultiThreadModelNoCS>,
701     public CWindowImpl < CTrayClockWnd, CWindow, CControlWinTraits >
702 {
703     HWND hWndNotify;
704     HFONT hFont;
705     COLORREF textColor;
706     RECT rcText;
707     SYSTEMTIME LocalTime;
708 
709     union
710     {
711         DWORD dwFlags;
712         struct
713         {
714             DWORD IsTimerEnabled : 1;
715             DWORD IsInitTimerEnabled : 1;
716             DWORD LinesMeasured : 1;
717             DWORD IsHorizontal : 1;
718         };
719     };
720     DWORD LineSpacing;
721     SIZE CurrentSize;
722     WORD VisibleLines;
723     SIZE LineSizes[CLOCKWND_FORMAT_COUNT];
724     WCHAR szLines[CLOCKWND_FORMAT_COUNT][48];
725 
726 public:
727     CTrayClockWnd() :
728         hWndNotify(NULL),
729         hFont(NULL),
730         dwFlags(0),
731         LineSpacing(0),
732         VisibleLines(0)
733     {
734         ZeroMemory(&textColor, sizeof(textColor));
735         ZeroMemory(&rcText, sizeof(rcText));
736         ZeroMemory(&LocalTime, sizeof(LocalTime));
737         ZeroMemory(&CurrentSize, sizeof(CurrentSize));
738         ZeroMemory(LineSizes, sizeof(LineSizes));
739         ZeroMemory(szLines, sizeof(szLines));
740     }
741     virtual ~CTrayClockWnd() { }
742 
743     LRESULT OnThemeChanged()
744     {
745         LOGFONTW clockFont;
746         HTHEME clockTheme;
747         HFONT hFont;
748 
749         clockTheme = OpenThemeData(m_hWnd, L"Clock");
750 
751         if (clockTheme)
752         {
753             GetThemeFont(clockTheme,
754                 NULL,
755                 CLP_TIME,
756                 0,
757                 TMT_FONT,
758                 &clockFont);
759 
760             hFont = CreateFontIndirectW(&clockFont);
761 
762             GetThemeColor(clockTheme,
763                 CLP_TIME,
764                 0,
765                 TMT_TEXTCOLOR,
766                 &textColor);
767 
768             if (this->hFont != NULL)
769                 DeleteObject(this->hFont);
770 
771             SetFont(hFont, FALSE);
772         }
773         else
774         {
775             /* We don't need to set a font here, our parent will use
776               * WM_SETFONT to set the right one when themes are not enabled. */
777             textColor = RGB(0, 0, 0);
778         }
779 
780         CloseThemeData(clockTheme);
781 
782         return TRUE;
783     }
784 
785     LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
786     {
787         return OnThemeChanged();
788     }
789 
790     BOOL MeasureLines()
791     {
792         HDC hDC;
793         HFONT hPrevFont;
794         UINT c, i;
795         BOOL bRet = TRUE;
796 
797         hDC = GetDC();
798         if (hDC != NULL)
799         {
800             if (hFont)
801                 hPrevFont = (HFONT) SelectObject(hDC, hFont);
802 
803             for (i = 0; i < CLOCKWND_FORMAT_COUNT && bRet; i++)
804             {
805                 if (szLines[i][0] != L'\0' &&
806                     !GetTextExtentPointW(hDC, szLines[i], wcslen(szLines[i]),
807                                          &LineSizes[i]))
808                 {
809                     bRet = FALSE;
810                     break;
811                 }
812             }
813 
814             if (hFont)
815                 SelectObject(hDC, hPrevFont);
816 
817             ReleaseDC(hDC);
818 
819             if (bRet)
820             {
821                 LineSpacing = 0;
822 
823                 /* calculate the line spacing */
824                 for (i = 0, c = 0; i < CLOCKWND_FORMAT_COUNT; i++)
825                 {
826                     if (LineSizes[i].cx > 0)
827                     {
828                         LineSpacing += LineSizes[i].cy;
829                         c++;
830                     }
831                 }
832 
833                 if (c > 0)
834                 {
835                     /* We want a spacing of 1/2 line */
836                     LineSpacing = (LineSpacing / c) / 2;
837                 }
838 
839                 return TRUE;
840             }
841         }
842 
843         return FALSE;
844     }
845 
846     WORD GetMinimumSize(IN BOOL Horizontal, IN OUT PSIZE pSize)
847     {
848         WORD iLinesVisible = 0;
849         UINT i;
850         SIZE szMax = { 0, 0 };
851 
852         if (!LinesMeasured)
853             LinesMeasured = MeasureLines();
854 
855         if (!LinesMeasured)
856             return 0;
857 
858         for (i = 0; i < CLOCKWND_FORMAT_COUNT; i++)
859         {
860             if (LineSizes[i].cx != 0)
861             {
862                 if (iLinesVisible > 0)
863                 {
864                     if (Horizontal)
865                     {
866                         if (szMax.cy + LineSizes[i].cy + (LONG) LineSpacing >
867                             pSize->cy - (2 * TRAY_CLOCK_WND_SPACING_Y))
868                         {
869                             break;
870                         }
871                     }
872                     else
873                     {
874                         if (LineSizes[i].cx > pSize->cx - (2 * TRAY_CLOCK_WND_SPACING_X))
875                             break;
876                     }
877 
878                     /* Add line spacing */
879                     szMax.cy += LineSpacing;
880                 }
881 
882                 iLinesVisible++;
883 
884                 /* Increase maximum rectangle */
885                 szMax.cy += LineSizes[i].cy;
886                 if (LineSizes[i].cx > szMax.cx - (2 * TRAY_CLOCK_WND_SPACING_X))
887                     szMax.cx = LineSizes[i].cx + (2 * TRAY_CLOCK_WND_SPACING_X);
888             }
889         }
890 
891         szMax.cx += 2 * TRAY_CLOCK_WND_SPACING_X;
892         szMax.cy += 2 * TRAY_CLOCK_WND_SPACING_Y;
893 
894         *pSize = szMax;
895 
896         return iLinesVisible;
897     }
898 
899 
900     VOID UpdateWnd()
901     {
902         SIZE szPrevCurrent;
903         UINT BufSize, i;
904         INT iRet;
905         RECT rcClient;
906 
907         ZeroMemory(LineSizes, sizeof(LineSizes));
908 
909         szPrevCurrent = CurrentSize;
910 
911         for (i = 0; i < CLOCKWND_FORMAT_COUNT; i++)
912         {
913             szLines[i][0] = L'\0';
914             BufSize = _countof(szLines[0]);
915 
916             if (ClockWndFormats[i].IsTime)
917             {
918                 iRet = GetTimeFormat(LOCALE_USER_DEFAULT,
919                     TaskBarSettings.bShowSeconds ? ClockWndFormats[i].dwFormatFlags : TIME_NOSECONDS,
920                     &LocalTime,
921                     ClockWndFormats[i].lpFormat,
922                     szLines[i],
923                     BufSize);
924             }
925             else
926             {
927                 iRet = GetDateFormat(LOCALE_USER_DEFAULT,
928                     ClockWndFormats[i].dwFormatFlags,
929                     &LocalTime,
930                     ClockWndFormats[i].lpFormat,
931                     szLines[i],
932                     BufSize);
933             }
934 
935             if (iRet != 0 && i == 0)
936             {
937                 /* Set the window text to the time only */
938                 SetWindowText(szLines[i]);
939             }
940         }
941 
942         LinesMeasured = MeasureLines();
943 
944         if (LinesMeasured &&
945             GetClientRect(&rcClient))
946         {
947             SIZE szWnd;
948 
949             szWnd.cx = rcClient.right;
950             szWnd.cy = rcClient.bottom;
951 
952             VisibleLines = GetMinimumSize(IsHorizontal, &szWnd);
953             CurrentSize = szWnd;
954         }
955 
956         if (IsWindowVisible())
957         {
958             InvalidateRect(NULL, TRUE);
959 
960             if (hWndNotify != NULL &&
961                 (szPrevCurrent.cx != CurrentSize.cx ||
962                 szPrevCurrent.cy != CurrentSize.cy))
963             {
964                 NMHDR nmh;
965 
966                 nmh.hwndFrom = m_hWnd;
967                 nmh.idFrom = GetWindowLongPtr(GWLP_ID);
968                 nmh.code = NTNWM_REALIGN;
969 
970                 SendMessage(hWndNotify,
971                     WM_NOTIFY,
972                     (WPARAM) nmh.idFrom,
973                     (LPARAM) &nmh);
974             }
975         }
976     }
977 
978     VOID Update()
979     {
980         GetLocalTime(&LocalTime);
981         UpdateWnd();
982     }
983 
984     UINT CalculateDueTime()
985     {
986         UINT uiDueTime;
987 
988         /* Calculate the due time */
989         GetLocalTime(&LocalTime);
990         uiDueTime = 1000 - (UINT) LocalTime.wMilliseconds;
991         if (TaskBarSettings.bShowSeconds)
992             uiDueTime += (UINT) LocalTime.wSecond * 100;
993         else
994             uiDueTime += (59 - (UINT) LocalTime.wSecond) * 1000;
995 
996         if (uiDueTime < USER_TIMER_MINIMUM || uiDueTime > USER_TIMER_MAXIMUM)
997             uiDueTime = 1000;
998         else
999         {
1000             /* Add an artificial delay of 0.05 seconds to make sure the timer
1001                doesn't fire too early*/
1002             uiDueTime += 50;
1003         }
1004 
1005         return uiDueTime;
1006     }
1007 
1008     BOOL ResetTime()
1009     {
1010         UINT uiDueTime;
1011         BOOL Ret;
1012 
1013         /* Disable all timers */
1014         if (IsTimerEnabled)
1015         {
1016             KillTimer(ID_TRAYCLOCK_TIMER);
1017             IsTimerEnabled = FALSE;
1018         }
1019 
1020         if (IsInitTimerEnabled)
1021         {
1022             KillTimer(ID_TRAYCLOCK_TIMER_INIT);
1023         }
1024 
1025         uiDueTime = CalculateDueTime();
1026 
1027         /* Set the new timer */
1028         Ret = SetTimer(ID_TRAYCLOCK_TIMER_INIT, uiDueTime, NULL) != 0;
1029         IsInitTimerEnabled = Ret;
1030 
1031         /* Update the time */
1032         Update();
1033 
1034         return Ret;
1035     }
1036 
1037     VOID CalibrateTimer()
1038     {
1039         UINT uiDueTime;
1040         BOOL Ret;
1041         UINT uiWait1, uiWait2;
1042 
1043         /* Kill the initialization timer */
1044         KillTimer(ID_TRAYCLOCK_TIMER_INIT);
1045         IsInitTimerEnabled = FALSE;
1046 
1047         uiDueTime = CalculateDueTime();
1048 
1049         if (TaskBarSettings.bShowSeconds)
1050         {
1051             uiWait1 = 1000 - 200;
1052             uiWait2 = 1000;
1053         }
1054         else
1055         {
1056             uiWait1 = 60 * 1000 - 200;
1057             uiWait2 = 60 * 1000;
1058         }
1059 
1060         if (uiDueTime > uiWait1)
1061         {
1062             /* The update of the clock will be up to 200 ms late, but that's
1063                acceptable. We're going to setup a timer that fires depending
1064                uiWait2. */
1065             Ret = SetTimer(ID_TRAYCLOCK_TIMER, uiWait2, NULL) != 0;
1066             IsTimerEnabled = Ret;
1067 
1068             /* Update the time */
1069             Update();
1070         }
1071         else
1072         {
1073             /* Recalibrate the timer and recalculate again when the current
1074                minute/second ends. */
1075             ResetTime();
1076         }
1077     }
1078 
1079     LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1080     {
1081         /* Disable all timers */
1082         if (IsTimerEnabled)
1083         {
1084             KillTimer(ID_TRAYCLOCK_TIMER);
1085         }
1086 
1087         if (IsInitTimerEnabled)
1088         {
1089             KillTimer(ID_TRAYCLOCK_TIMER_INIT);
1090         }
1091 
1092         return TRUE;
1093     }
1094 
1095     LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1096     {
1097         RECT rcClient;
1098         HFONT hPrevFont;
1099         INT iPrevBkMode;
1100         UINT i, line;
1101 
1102         PAINTSTRUCT ps;
1103         HDC hDC = (HDC) wParam;
1104 
1105         if (wParam == 0)
1106         {
1107             hDC = BeginPaint(&ps);
1108         }
1109 
1110         if (hDC == NULL)
1111             return FALSE;
1112 
1113         if (LinesMeasured &&
1114             GetClientRect(&rcClient))
1115         {
1116             iPrevBkMode = SetBkMode(hDC, TRANSPARENT);
1117 
1118             SetTextColor(hDC, textColor);
1119 
1120             hPrevFont = (HFONT) SelectObject(hDC, hFont);
1121 
1122             rcClient.left = (rcClient.right / 2) - (CurrentSize.cx / 2);
1123             rcClient.top = (rcClient.bottom / 2) - (CurrentSize.cy / 2);
1124             rcClient.right = rcClient.left + CurrentSize.cx;
1125             rcClient.bottom = rcClient.top + CurrentSize.cy;
1126 
1127             for (i = 0, line = 0;
1128                  i < CLOCKWND_FORMAT_COUNT && line < VisibleLines;
1129                  i++)
1130             {
1131                 if (LineSizes[i].cx != 0)
1132                 {
1133                     TextOut(hDC,
1134                         rcClient.left + (CurrentSize.cx / 2) - (LineSizes[i].cx / 2) +
1135                         TRAY_CLOCK_WND_SPACING_X,
1136                         rcClient.top + TRAY_CLOCK_WND_SPACING_Y,
1137                         szLines[i],
1138                         wcslen(szLines[i]));
1139 
1140                     rcClient.top += LineSizes[i].cy + LineSpacing;
1141                     line++;
1142                 }
1143             }
1144 
1145             SelectObject(hDC, hPrevFont);
1146 
1147             SetBkMode(hDC, iPrevBkMode);
1148         }
1149 
1150         if (wParam == 0)
1151         {
1152             EndPaint(&ps);
1153         }
1154 
1155         return TRUE;
1156     }
1157 
1158     VOID SetFont(IN HFONT hNewFont, IN BOOL bRedraw)
1159     {
1160         hFont = hNewFont;
1161         LinesMeasured = MeasureLines();
1162         if (bRedraw)
1163         {
1164             InvalidateRect(NULL, TRUE);
1165         }
1166     }
1167 
1168     LRESULT DrawBackground(HDC hdc)
1169     {
1170         RECT rect;
1171 
1172         GetClientRect(&rect);
1173         DrawThemeParentBackground(m_hWnd, hdc, &rect);
1174 
1175         return TRUE;
1176     }
1177 
1178     LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1179     {
1180         HDC hdc = (HDC) wParam;
1181 
1182         if (!IsAppThemed())
1183         {
1184             bHandled = FALSE;
1185             return 0;
1186         }
1187 
1188         return DrawBackground(hdc);
1189     }
1190 
1191     LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1192     {
1193         switch (wParam)
1194         {
1195         case ID_TRAYCLOCK_TIMER:
1196             Update();
1197             break;
1198 
1199         case ID_TRAYCLOCK_TIMER_INIT:
1200             CalibrateTimer();
1201             break;
1202         }
1203         return TRUE;
1204     }
1205 
1206     LRESULT OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1207     {
1208         IsHorizontal = (BOOL) wParam;
1209 
1210         return (LRESULT) GetMinimumSize((BOOL) wParam, (PSIZE) lParam) != 0;
1211     }
1212 
1213     LRESULT OnUpdateTime(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1214     {
1215         return (LRESULT) ResetTime();
1216     }
1217 
1218     LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1219     {
1220         return HTTRANSPARENT;
1221     }
1222 
1223     LRESULT OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1224     {
1225         SetFont((HFONT) wParam, (BOOL) LOWORD(lParam));
1226         return TRUE;
1227     }
1228 
1229     LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1230     {
1231         ResetTime();
1232         return TRUE;
1233     }
1234 
1235     LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1236     {
1237         SIZE szClient;
1238 
1239         szClient.cx = LOWORD(lParam);
1240         szClient.cy = HIWORD(lParam);
1241 
1242         VisibleLines = GetMinimumSize(IsHorizontal, &szClient);
1243         CurrentSize = szClient;
1244 
1245         InvalidateRect(NULL, TRUE);
1246         return TRUE;
1247     }
1248 
1249     DECLARE_WND_CLASS_EX(szTrayClockWndClass, CS_DBLCLKS, COLOR_3DFACE)
1250 
1251     BEGIN_MSG_MAP(CTrayClockWnd)
1252         MESSAGE_HANDLER(WM_CREATE, OnCreate)
1253         MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
1254         MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
1255         MESSAGE_HANDLER(WM_SIZE, OnSize)
1256         MESSAGE_HANDLER(WM_PAINT, OnPaint)
1257         MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
1258         MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
1259         MESSAGE_HANDLER(WM_TIMER, OnTimer)
1260         MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
1261         MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
1262         MESSAGE_HANDLER(TCWM_GETMINIMUMSIZE, OnGetMinimumSize)
1263         MESSAGE_HANDLER(TCWM_UPDATETIME, OnUpdateTime)
1264 
1265     END_MSG_MAP()
1266 
1267     HWND _Init(IN HWND hWndParent, IN BOOL bVisible)
1268     {
1269         IsHorizontal = TRUE;
1270 
1271         hWndNotify = hWndParent;
1272 
1273         /* Create the window. The tray window is going to move it to the correct
1274             position and resize it as needed. */
1275         DWORD dwStyle = WS_CHILD | WS_CLIPSIBLINGS;
1276         if (bVisible)
1277             dwStyle |= WS_VISIBLE;
1278 
1279         Create(hWndParent, 0, NULL, dwStyle);
1280 
1281         if (m_hWnd != NULL)
1282             SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
1283 
1284         return m_hWnd;
1285 
1286     }
1287 };
1288 
1289 /*
1290  * TrayNotifyWnd
1291  */
1292 
1293 static const WCHAR szTrayNotifyWndClass [] = TEXT("TrayNotifyWnd");
1294 
1295 #define TRAY_NOTIFY_WND_SPACING_X   1
1296 #define TRAY_NOTIFY_WND_SPACING_Y   1
1297 
1298 class CTrayNotifyWnd :
1299     public CComObjectRootEx<CComMultiThreadModelNoCS>,
1300     public CWindowImpl < CTrayNotifyWnd, CWindow, CControlWinTraits >
1301 {
1302     HWND hWndNotify;
1303 
1304     CSysPagerWnd * m_pager;
1305     CTrayClockWnd * m_clock;
1306 
1307     CComPtr<ITrayWindow> TrayWindow;
1308 
1309     HTHEME TrayTheme;
1310     SIZE szTrayClockMin;
1311     SIZE szTrayNotify;
1312     MARGINS ContentMargin;
1313     union
1314     {
1315         DWORD dwFlags;
1316         struct
1317         {
1318             DWORD HideClock : 1;
1319             DWORD IsHorizontal : 1;
1320         };
1321     };
1322 
1323 public:
1324     CTrayNotifyWnd() :
1325         hWndNotify(NULL),
1326         m_pager(NULL),
1327         m_clock(NULL),
1328         TrayTheme(NULL),
1329         dwFlags(0)
1330     {
1331         ZeroMemory(&szTrayClockMin, sizeof(szTrayClockMin));
1332         ZeroMemory(&szTrayNotify, sizeof(szTrayNotify));
1333         ZeroMemory(&ContentMargin, sizeof(ContentMargin));
1334     }
1335     virtual ~CTrayNotifyWnd() { }
1336 
1337     LRESULT OnThemeChanged()
1338     {
1339         if (TrayTheme)
1340             CloseThemeData(TrayTheme);
1341 
1342         if (IsThemeActive())
1343             TrayTheme = OpenThemeData(m_hWnd, L"TrayNotify");
1344         else
1345             TrayTheme = NULL;
1346 
1347         if (TrayTheme)
1348         {
1349             SetWindowExStyle(m_hWnd, WS_EX_STATICEDGE, 0);
1350 
1351             GetThemeMargins(TrayTheme,
1352                 NULL,
1353                 TNP_BACKGROUND,
1354                 0,
1355                 TMT_CONTENTMARGINS,
1356                 NULL,
1357                 &ContentMargin);
1358         }
1359         else
1360         {
1361             SetWindowExStyle(m_hWnd, WS_EX_STATICEDGE, WS_EX_STATICEDGE);
1362 
1363             ContentMargin.cxLeftWidth = 2;
1364             ContentMargin.cxRightWidth = 2;
1365             ContentMargin.cyTopHeight = 2;
1366             ContentMargin.cyBottomHeight = 2;
1367         }
1368 
1369         return TRUE;
1370     }
1371 
1372     LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1373     {
1374         return OnThemeChanged();
1375     }
1376 
1377     LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1378     {
1379         m_clock = new CTrayClockWnd();
1380         m_clock->_Init(m_hWnd, !HideClock);
1381 
1382         m_pager = new CSysPagerWnd();
1383         m_pager->_Init(m_hWnd, !HideClock);
1384 
1385         return TRUE;
1386     }
1387 
1388     BOOL GetMinimumSize(IN OUT PSIZE pSize)
1389     {
1390         SIZE szClock = { 0, 0 };
1391         SIZE szTray = { 0, 0 };
1392 
1393         if (!HideClock)
1394         {
1395             if (IsHorizontal)
1396             {
1397                 szClock.cy = pSize->cy - 2 * TRAY_NOTIFY_WND_SPACING_Y;
1398                 if (szClock.cy <= 0)
1399                     goto NoClock;
1400             }
1401             else
1402             {
1403                 szClock.cx = pSize->cx - 2 * TRAY_NOTIFY_WND_SPACING_X;
1404                 if (szClock.cx <= 0)
1405                     goto NoClock;
1406             }
1407 
1408             m_clock->SendMessage(TCWM_GETMINIMUMSIZE, (WPARAM) IsHorizontal, (LPARAM) &szClock);
1409 
1410             szTrayClockMin = szClock;
1411         }
1412         else
1413         NoClock:
1414         szTrayClockMin = szClock;
1415 
1416         if (IsHorizontal)
1417         {
1418             szTray.cy = pSize->cy - 2 * TRAY_NOTIFY_WND_SPACING_Y;
1419         }
1420         else
1421         {
1422             szTray.cx = pSize->cx - 2 * TRAY_NOTIFY_WND_SPACING_X;
1423         }
1424 
1425         m_pager->GetSize(IsHorizontal, &szTray);
1426 
1427         szTrayNotify = szTray;
1428 
1429         if (IsHorizontal)
1430         {
1431             pSize->cx = 2 * TRAY_NOTIFY_WND_SPACING_X;
1432 
1433             if (!HideClock)
1434                 pSize->cx += TRAY_NOTIFY_WND_SPACING_X + szTrayClockMin.cx;
1435 
1436             pSize->cx += szTray.cx;
1437         }
1438         else
1439         {
1440             pSize->cy = 2 * TRAY_NOTIFY_WND_SPACING_Y;
1441 
1442             if (!HideClock)
1443                 pSize->cy += TRAY_NOTIFY_WND_SPACING_Y + szTrayClockMin.cy;
1444 
1445             pSize->cy += szTray.cy;
1446         }
1447 
1448         pSize->cy += ContentMargin.cyTopHeight + ContentMargin.cyBottomHeight;
1449         pSize->cx += ContentMargin.cxLeftWidth + ContentMargin.cxRightWidth;
1450 
1451         return TRUE;
1452     }
1453 
1454     VOID Size(IN const SIZE *pszClient)
1455     {
1456         if (!HideClock)
1457         {
1458             POINT ptClock;
1459             SIZE szClock;
1460 
1461             if (IsHorizontal)
1462             {
1463                 ptClock.x = pszClient->cx - szTrayClockMin.cx - ContentMargin.cxRightWidth;
1464                 ptClock.y = ContentMargin.cyTopHeight;
1465                 szClock.cx = szTrayClockMin.cx;
1466                 szClock.cy = pszClient->cy - ContentMargin.cyTopHeight - ContentMargin.cyBottomHeight;
1467             }
1468             else
1469             {
1470                 ptClock.x = ContentMargin.cxLeftWidth;
1471                 ptClock.y = pszClient->cy - szTrayClockMin.cy;
1472                 szClock.cx = pszClient->cx - ContentMargin.cxLeftWidth - ContentMargin.cxRightWidth;
1473                 szClock.cy = szTrayClockMin.cy;
1474             }
1475 
1476             m_clock->SetWindowPos(
1477                 NULL,
1478                 ptClock.x,
1479                 ptClock.y,
1480                 szClock.cx,
1481                 szClock.cy,
1482                 SWP_NOZORDER);
1483 
1484             POINT ptPager;
1485 
1486             if (IsHorizontal)
1487             {
1488                 ptPager.x = ContentMargin.cxLeftWidth;
1489                 ptPager.y = (pszClient->cy - szTrayNotify.cy)/2;
1490             }
1491             else
1492             {
1493                 ptPager.x = (pszClient->cx - szTrayNotify.cx)/2;
1494                 ptPager.y = ContentMargin.cyTopHeight;
1495             }
1496 
1497             m_pager->SetWindowPos(
1498                 NULL,
1499                 ptPager.x,
1500                 ptPager.y,
1501                 szTrayNotify.cx,
1502                 szTrayNotify.cy,
1503                 SWP_NOZORDER);
1504         }
1505     }
1506 
1507     LRESULT DrawBackground(HDC hdc)
1508     {
1509         HRESULT res;
1510         RECT rect;
1511 
1512         GetClientRect(&rect);
1513 
1514         if (TrayTheme)
1515         {
1516             if (IsThemeBackgroundPartiallyTransparent(TrayTheme, TNP_BACKGROUND, 0))
1517             {
1518                 DrawThemeParentBackground(m_hWnd, hdc, &rect);
1519             }
1520 
1521             res = DrawThemeBackground(TrayTheme, hdc, TNP_BACKGROUND, 0, &rect, 0);
1522         }
1523 
1524         return res;
1525     }
1526 
1527     LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1528     {
1529         HDC hdc = (HDC) wParam;
1530 
1531         if (!TrayTheme)
1532         {
1533             bHandled = FALSE;
1534             return 0;
1535         }
1536 
1537         return DrawBackground(hdc);
1538     }
1539 
1540     BOOL NotifyIconCmd(WPARAM wParam, LPARAM lParam)
1541     {
1542         if (m_pager)
1543         {
1544             return m_pager->NotifyIconCmd(wParam, lParam);
1545         }
1546 
1547         return TRUE;
1548     }
1549 
1550     BOOL GetClockRect(OUT PRECT rcClock)
1551     {
1552         if (!m_clock->IsWindowVisible())
1553             return FALSE;
1554 
1555         return m_clock->GetWindowRect(rcClock);
1556     }
1557 
1558     LRESULT OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1559     {
1560         BOOL Horizontal = (BOOL) wParam;
1561 
1562         if (Horizontal != IsHorizontal)
1563         {
1564             IsHorizontal = Horizontal;
1565             if (IsHorizontal)
1566                 SetWindowTheme(m_hWnd, L"TrayNotifyHoriz", NULL);
1567             else
1568                 SetWindowTheme(m_hWnd, L"TrayNotifyVert", NULL);
1569         }
1570 
1571         return (LRESULT) GetMinimumSize((PSIZE) lParam);
1572     }
1573 
1574     LRESULT OnUpdateTime(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1575     {
1576         if (m_clock != NULL)
1577         {
1578             /* Forward the message to the tray clock window procedure */
1579             return m_clock->OnUpdateTime(uMsg, wParam, lParam, bHandled);
1580         }
1581         return FALSE;
1582     }
1583 
1584     LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1585     {
1586         SIZE szClient;
1587 
1588         szClient.cx = LOWORD(lParam);
1589         szClient.cy = HIWORD(lParam);
1590 
1591         Size(&szClient);
1592 
1593         return TRUE;
1594     }
1595 
1596     LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1597     {
1598         return HTTRANSPARENT;
1599     }
1600 
1601     LRESULT OnShowClock(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1602     {
1603         BOOL PrevHidden = HideClock;
1604         HideClock = (wParam == 0);
1605 
1606         if (m_clock != NULL && PrevHidden != HideClock)
1607         {
1608             m_clock->ShowWindow(HideClock ? SW_HIDE : SW_SHOW);
1609         }
1610 
1611         return (LRESULT) (!PrevHidden);
1612     }
1613 
1614     LRESULT OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1615     {
1616         const NMHDR *nmh = (const NMHDR *) lParam;
1617 
1618         if (nmh->hwndFrom == m_clock->m_hWnd)
1619         {
1620             /* Pass down notifications */
1621             return m_clock->SendMessage(WM_NOTIFY, wParam, lParam);
1622         }
1623 
1624         return FALSE;
1625     }
1626 
1627     LRESULT OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1628     {
1629         if (m_clock != NULL)
1630         {
1631             m_clock->SendMessageW(WM_SETFONT, wParam, lParam);
1632         }
1633 
1634         bHandled = FALSE;
1635         return FALSE;
1636     }
1637 
1638     LRESULT OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1639     {
1640         bHandled = TRUE;
1641         return 0;
1642     }
1643 
1644     LRESULT OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1645     {
1646         if (wParam == SPI_SETNONCLIENTMETRICS)
1647         {
1648             m_pager->ResizeImagelist();
1649         }
1650         return 0;
1651     }
1652 
1653     DECLARE_WND_CLASS_EX(szTrayNotifyWndClass, CS_DBLCLKS, COLOR_3DFACE)
1654 
1655     BEGIN_MSG_MAP(CTrayNotifyWnd)
1656         MESSAGE_HANDLER(WM_CREATE, OnCreate)
1657         MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
1658         MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
1659         MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChanged)
1660         MESSAGE_HANDLER(WM_SIZE, OnSize)
1661         MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
1662         MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
1663         MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
1664         MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu) // FIXME: This handler is not necessary in Windows
1665         MESSAGE_HANDLER(TNWM_GETMINIMUMSIZE, OnGetMinimumSize)
1666         MESSAGE_HANDLER(TNWM_UPDATETIME, OnUpdateTime)
1667         MESSAGE_HANDLER(TNWM_SHOWCLOCK, OnShowClock)
1668     END_MSG_MAP()
1669 
1670     HWND _Init(IN OUT ITrayWindow *TrayWindow, IN BOOL bHideClock)
1671     {
1672         HWND hWndTrayWindow;
1673 
1674         hWndTrayWindow = TrayWindow->GetHWND();
1675         if (hWndTrayWindow == NULL)
1676             return NULL;
1677 
1678         this->TrayWindow = TrayWindow;
1679         this->HideClock = bHideClock;
1680         this->hWndNotify = hWndTrayWindow;
1681 
1682         DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
1683         return Create(hWndTrayWindow, 0, NULL, dwStyle, WS_EX_STATICEDGE);
1684     }
1685 };
1686 
1687 HWND CreateTrayNotifyWnd(IN OUT ITrayWindow *Tray, BOOL bHideClock, CTrayNotifyWnd** ppinstance)
1688 {
1689     CTrayNotifyWnd * pTrayNotify = new CTrayNotifyWnd();
1690     // TODO: Destroy after the window is destroyed
1691     *ppinstance = pTrayNotify;
1692 
1693     return pTrayNotify->_Init(Tray, bHideClock);
1694 }
1695 
1696 BOOL
1697 TrayNotify_NotifyIconCmd(CTrayNotifyWnd* pTrayNotify, WPARAM wParam, LPARAM lParam)
1698 {
1699     return pTrayNotify->NotifyIconCmd(wParam, lParam);
1700 }
1701 
1702 BOOL
1703 TrayNotify_GetClockRect(CTrayNotifyWnd* pTrayNotify, OUT PRECT rcClock)
1704 {
1705     return pTrayNotify->GetClockRect(rcClock);
1706 }
1707