[BROWSEUI][DEVMGR][EXPLORER][NTGDI][NTOBJSHEX][NTVDM][SETUPAPI] Remove/comment out...
[reactos.git] / base / shell / explorer / trayntfy.cpp
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 data = (PSYS_PAGER_COPY_DATA) cpData->lpData;
514 iconData = &data->nicon_data;
515
516 TRACE("NotifyIconCmd received. Code=%d\n", data->notify_code);
517 switch (data->notify_code)
518 {
519 case NIM_ADD:
520 ret = Toolbar.AddButton(iconData);
521 break;
522 case NIM_MODIFY:
523 ret = Toolbar.UpdateButton(iconData);
524 break;
525 case NIM_DELETE:
526 ret = Toolbar.RemoveButton(iconData);
527 break;
528 default:
529 TRACE("NotifyIconCmd received with unknown code %d.\n", data->notify_code);
530 return FALSE;
531 }
532
533 SendMessage(parentHWND,
534 WM_SIZE,
535 0,
536 MAKELONG(windowRect.right - windowRect.left,
537 windowRect.bottom - windowRect.top));
538
539 return ret;
540 }
541
542 return TRUE;
543 }
544
545 void GetSize(IN BOOL IsHorizontal, IN PSIZE size)
546 {
547 /* Get the ideal height or width */
548 #if 0
549 /* Unfortunately this doens't work correctly in ros */
550 Toolbar.GetIdealSize(!IsHorizontal, size);
551
552 /* Make the reference dimension an exact multiple of the icon size */
553 if (IsHorizontal)
554 size->cy -= size->cy % GetSystemMetrics(SM_CYSMICON);
555 else
556 size->cx -= size->cx % GetSystemMetrics(SM_CXSMICON);
557
558 #else
559 INT rows = 0;
560 INT columns = 0;
561 INT cyButton = GetSystemMetrics(SM_CYSMICON) + 2;
562 INT cxButton = GetSystemMetrics(SM_CXSMICON) + 2;
563 int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
564
565 if (IsHorizontal)
566 {
567 rows = max(size->cy / cyButton, 1);
568 columns = (VisibleButtonCount + rows - 1) / rows;
569 }
570 else
571 {
572 columns = max(size->cx / cxButton, 1);
573 rows = (VisibleButtonCount + columns - 1) / columns;
574 }
575 size->cx = columns * cxButton;
576 size->cy = rows * cyButton;
577 #endif
578 }
579
580 LRESULT OnGetInfoTip(INT uCode, LPNMHDR hdr, BOOL& bHandled)
581 {
582 NMTBGETINFOTIPW * nmtip = (NMTBGETINFOTIPW *) hdr;
583 Toolbar.GetTooltipText(nmtip->iItem, nmtip->pszText, nmtip->cchTextMax);
584 return TRUE;
585 }
586
587 LRESULT OnCustomDraw(INT uCode, LPNMHDR hdr, BOOL& bHandled)
588 {
589 NMCUSTOMDRAW * cdraw = (NMCUSTOMDRAW *) hdr;
590 switch (cdraw->dwDrawStage)
591 {
592 case CDDS_PREPAINT:
593 return CDRF_NOTIFYITEMDRAW;
594
595 case CDDS_ITEMPREPAINT:
596 return TBCDRF_NOBACKGROUND | TBCDRF_NOEDGES | TBCDRF_NOOFFSET | TBCDRF_NOMARK | TBCDRF_NOETCHEDEFFECT;
597 }
598 return TRUE;
599 }
600
601 LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
602 {
603 LRESULT Ret = TRUE;
604 SIZE szClient;
605 szClient.cx = LOWORD(lParam);
606 szClient.cy = HIWORD(lParam);
607
608 Ret = DefWindowProc(uMsg, wParam, lParam);
609
610 if (Toolbar)
611 {
612 Toolbar.SetWindowPos(NULL, 0, 0, szClient.cx, szClient.cy, SWP_NOZORDER);
613 Toolbar.AutoSize();
614
615 RECT rc;
616 Toolbar.GetClientRect(&rc);
617
618 SIZE szBar = { rc.right - rc.left, rc.bottom - rc.top };
619
620 INT xOff = (szClient.cx - szBar.cx) / 2;
621 INT yOff = (szClient.cy - szBar.cy) / 2;
622
623 Toolbar.SetWindowPos(NULL, xOff, yOff, szBar.cx, szBar.cy, SWP_NOZORDER);
624 }
625 return Ret;
626 }
627
628 LRESULT OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
629 {
630 bHandled = TRUE;
631 return 0;
632 }
633
634 void ResizeImagelist()
635 {
636 Toolbar.ResizeImagelist();
637 }
638
639 DECLARE_WND_CLASS_EX(szSysPagerWndClass, CS_DBLCLKS, COLOR_3DFACE)
640
641 BEGIN_MSG_MAP(CSysPagerWnd)
642 MESSAGE_HANDLER(WM_CREATE, OnCreate)
643 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
644 MESSAGE_HANDLER(WM_SIZE, OnSize)
645 MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu)
646 NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW, OnGetInfoTip)
647 NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
648 END_MSG_MAP()
649
650 HWND _Init(IN HWND hWndParent, IN BOOL bVisible)
651 {
652 DWORD dwStyle;
653
654 /* Create the window. The tray window is going to move it to the correct
655 position and resize it as needed. */
656 dwStyle = WS_CHILD | WS_CLIPSIBLINGS;
657 if (bVisible)
658 dwStyle |= WS_VISIBLE;
659
660 Create(hWndParent, 0, NULL, dwStyle);
661
662 if (!m_hWnd)
663 {
664 return NULL;
665 }
666
667 SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
668
669 return m_hWnd;
670 }
671 };
672
673 /*
674 * TrayClockWnd
675 */
676
677 static const WCHAR szTrayClockWndClass[] = L"TrayClockWClass";
678
679 #define ID_TRAYCLOCK_TIMER 0
680 #define ID_TRAYCLOCK_TIMER_INIT 1
681
682 static const struct
683 {
684 BOOL IsTime;
685 DWORD dwFormatFlags;
686 LPCWSTR lpFormat;
687 } ClockWndFormats [] = {
688 { TRUE, 0, NULL },
689 { FALSE, 0, L"dddd" },
690 { FALSE, DATE_SHORTDATE, NULL }
691 };
692
693 #define CLOCKWND_FORMAT_COUNT (_ARRAYSIZE(ClockWndFormats))
694
695 #define TRAY_CLOCK_WND_SPACING_X 0
696 #define TRAY_CLOCK_WND_SPACING_Y 0
697
698 class CTrayClockWnd :
699 public CComObjectRootEx<CComMultiThreadModelNoCS>,
700 public CWindowImpl < CTrayClockWnd, CWindow, CControlWinTraits >
701 {
702 HWND hWndNotify;
703 HFONT hFont;
704 COLORREF textColor;
705 RECT rcText;
706 SYSTEMTIME LocalTime;
707
708 union
709 {
710 DWORD dwFlags;
711 struct
712 {
713 DWORD IsTimerEnabled : 1;
714 DWORD IsInitTimerEnabled : 1;
715 DWORD LinesMeasured : 1;
716 DWORD IsHorizontal : 1;
717 };
718 };
719 DWORD LineSpacing;
720 SIZE CurrentSize;
721 WORD VisibleLines;
722 SIZE LineSizes[CLOCKWND_FORMAT_COUNT];
723 WCHAR szLines[CLOCKWND_FORMAT_COUNT][48];
724
725 public:
726 CTrayClockWnd() :
727 hWndNotify(NULL),
728 hFont(NULL),
729 dwFlags(0),
730 LineSpacing(0),
731 VisibleLines(0)
732 {
733 ZeroMemory(&textColor, sizeof(textColor));
734 ZeroMemory(&rcText, sizeof(rcText));
735 ZeroMemory(&LocalTime, sizeof(LocalTime));
736 ZeroMemory(&CurrentSize, sizeof(CurrentSize));
737 ZeroMemory(LineSizes, sizeof(LineSizes));
738 ZeroMemory(szLines, sizeof(szLines));
739 }
740 virtual ~CTrayClockWnd() { }
741
742 LRESULT OnThemeChanged()
743 {
744 LOGFONTW clockFont;
745 HTHEME clockTheme;
746 HFONT hFont;
747
748 clockTheme = OpenThemeData(m_hWnd, L"Clock");
749
750 if (clockTheme)
751 {
752 GetThemeFont(clockTheme,
753 NULL,
754 CLP_TIME,
755 0,
756 TMT_FONT,
757 &clockFont);
758
759 hFont = CreateFontIndirectW(&clockFont);
760
761 GetThemeColor(clockTheme,
762 CLP_TIME,
763 0,
764 TMT_TEXTCOLOR,
765 &textColor);
766
767 if (this->hFont != NULL)
768 DeleteObject(this->hFont);
769
770 SetFont(hFont, FALSE);
771 }
772 else
773 {
774 /* We don't need to set a font here, our parent will use
775 * WM_SETFONT to set the right one when themes are not enabled. */
776 textColor = RGB(0, 0, 0);
777 }
778
779 CloseThemeData(clockTheme);
780
781 return TRUE;
782 }
783
784 LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
785 {
786 return OnThemeChanged();
787 }
788
789 BOOL MeasureLines()
790 {
791 HDC hDC;
792 HFONT hPrevFont;
793 UINT c, i;
794 BOOL bRet = TRUE;
795
796 hDC = GetDC();
797 if (hDC != NULL)
798 {
799 if (hFont)
800 hPrevFont = (HFONT) SelectObject(hDC, hFont);
801
802 for (i = 0; i < CLOCKWND_FORMAT_COUNT && bRet; i++)
803 {
804 if (szLines[i][0] != L'\0' &&
805 !GetTextExtentPointW(hDC, szLines[i], wcslen(szLines[i]),
806 &LineSizes[i]))
807 {
808 bRet = FALSE;
809 break;
810 }
811 }
812
813 if (hFont)
814 SelectObject(hDC, hPrevFont);
815
816 ReleaseDC(hDC);
817
818 if (bRet)
819 {
820 LineSpacing = 0;
821
822 /* calculate the line spacing */
823 for (i = 0, c = 0; i < CLOCKWND_FORMAT_COUNT; i++)
824 {
825 if (LineSizes[i].cx > 0)
826 {
827 LineSpacing += LineSizes[i].cy;
828 c++;
829 }
830 }
831
832 if (c > 0)
833 {
834 /* We want a spacing of 1/2 line */
835 LineSpacing = (LineSpacing / c) / 2;
836 }
837
838 return TRUE;
839 }
840 }
841
842 return FALSE;
843 }
844
845 WORD GetMinimumSize(IN BOOL Horizontal, IN OUT PSIZE pSize)
846 {
847 WORD iLinesVisible = 0;
848 UINT i;
849 SIZE szMax = { 0, 0 };
850
851 if (!LinesMeasured)
852 LinesMeasured = MeasureLines();
853
854 if (!LinesMeasured)
855 return 0;
856
857 for (i = 0; i < CLOCKWND_FORMAT_COUNT; i++)
858 {
859 if (LineSizes[i].cx != 0)
860 {
861 if (iLinesVisible > 0)
862 {
863 if (Horizontal)
864 {
865 if (szMax.cy + LineSizes[i].cy + (LONG) LineSpacing >
866 pSize->cy - (2 * TRAY_CLOCK_WND_SPACING_Y))
867 {
868 break;
869 }
870 }
871 else
872 {
873 if (LineSizes[i].cx > pSize->cx - (2 * TRAY_CLOCK_WND_SPACING_X))
874 break;
875 }
876
877 /* Add line spacing */
878 szMax.cy += LineSpacing;
879 }
880
881 iLinesVisible++;
882
883 /* Increase maximum rectangle */
884 szMax.cy += LineSizes[i].cy;
885 if (LineSizes[i].cx > szMax.cx - (2 * TRAY_CLOCK_WND_SPACING_X))
886 szMax.cx = LineSizes[i].cx + (2 * TRAY_CLOCK_WND_SPACING_X);
887 }
888 }
889
890 szMax.cx += 2 * TRAY_CLOCK_WND_SPACING_X;
891 szMax.cy += 2 * TRAY_CLOCK_WND_SPACING_Y;
892
893 *pSize = szMax;
894
895 return iLinesVisible;
896 }
897
898
899 VOID UpdateWnd()
900 {
901 SIZE szPrevCurrent;
902 UINT BufSize, i;
903 INT iRet;
904 RECT rcClient;
905
906 ZeroMemory(LineSizes, sizeof(LineSizes));
907
908 szPrevCurrent = CurrentSize;
909
910 for (i = 0; i < CLOCKWND_FORMAT_COUNT; i++)
911 {
912 szLines[i][0] = L'\0';
913 BufSize = _countof(szLines[0]);
914
915 if (ClockWndFormats[i].IsTime)
916 {
917 iRet = GetTimeFormat(LOCALE_USER_DEFAULT,
918 TaskBarSettings.bShowSeconds ? ClockWndFormats[i].dwFormatFlags : TIME_NOSECONDS,
919 &LocalTime,
920 ClockWndFormats[i].lpFormat,
921 szLines[i],
922 BufSize);
923 }
924 else
925 {
926 iRet = GetDateFormat(LOCALE_USER_DEFAULT,
927 ClockWndFormats[i].dwFormatFlags,
928 &LocalTime,
929 ClockWndFormats[i].lpFormat,
930 szLines[i],
931 BufSize);
932 }
933
934 if (iRet != 0 && i == 0)
935 {
936 /* Set the window text to the time only */
937 SetWindowText(szLines[i]);
938 }
939 }
940
941 LinesMeasured = MeasureLines();
942
943 if (LinesMeasured &&
944 GetClientRect(&rcClient))
945 {
946 SIZE szWnd;
947
948 szWnd.cx = rcClient.right;
949 szWnd.cy = rcClient.bottom;
950
951 VisibleLines = GetMinimumSize(IsHorizontal, &szWnd);
952 CurrentSize = szWnd;
953 }
954
955 if (IsWindowVisible())
956 {
957 InvalidateRect(NULL, TRUE);
958
959 if (hWndNotify != NULL &&
960 (szPrevCurrent.cx != CurrentSize.cx ||
961 szPrevCurrent.cy != CurrentSize.cy))
962 {
963 NMHDR nmh;
964
965 nmh.hwndFrom = m_hWnd;
966 nmh.idFrom = GetWindowLongPtr(GWLP_ID);
967 nmh.code = NTNWM_REALIGN;
968
969 SendMessage(hWndNotify,
970 WM_NOTIFY,
971 (WPARAM) nmh.idFrom,
972 (LPARAM) &nmh);
973 }
974 }
975 }
976
977 VOID Update()
978 {
979 GetLocalTime(&LocalTime);
980 UpdateWnd();
981 }
982
983 UINT CalculateDueTime()
984 {
985 UINT uiDueTime;
986
987 /* Calculate the due time */
988 GetLocalTime(&LocalTime);
989 uiDueTime = 1000 - (UINT) LocalTime.wMilliseconds;
990 if (TaskBarSettings.bShowSeconds)
991 uiDueTime += (UINT) LocalTime.wSecond * 100;
992 else
993 uiDueTime += (59 - (UINT) LocalTime.wSecond) * 1000;
994
995 if (uiDueTime < USER_TIMER_MINIMUM || uiDueTime > USER_TIMER_MAXIMUM)
996 uiDueTime = 1000;
997 else
998 {
999 /* Add an artificial delay of 0.05 seconds to make sure the timer
1000 doesn't fire too early*/
1001 uiDueTime += 50;
1002 }
1003
1004 return uiDueTime;
1005 }
1006
1007 BOOL ResetTime()
1008 {
1009 UINT uiDueTime;
1010 BOOL Ret;
1011
1012 /* Disable all timers */
1013 if (IsTimerEnabled)
1014 {
1015 KillTimer(ID_TRAYCLOCK_TIMER);
1016 IsTimerEnabled = FALSE;
1017 }
1018
1019 if (IsInitTimerEnabled)
1020 {
1021 KillTimer(ID_TRAYCLOCK_TIMER_INIT);
1022 }
1023
1024 uiDueTime = CalculateDueTime();
1025
1026 /* Set the new timer */
1027 Ret = SetTimer(ID_TRAYCLOCK_TIMER_INIT, uiDueTime, NULL) != 0;
1028 IsInitTimerEnabled = Ret;
1029
1030 /* Update the time */
1031 Update();
1032
1033 return Ret;
1034 }
1035
1036 VOID CalibrateTimer()
1037 {
1038 UINT uiDueTime;
1039 BOOL Ret;
1040 UINT uiWait1, uiWait2;
1041
1042 /* Kill the initialization timer */
1043 KillTimer(ID_TRAYCLOCK_TIMER_INIT);
1044 IsInitTimerEnabled = FALSE;
1045
1046 uiDueTime = CalculateDueTime();
1047
1048 if (TaskBarSettings.bShowSeconds)
1049 {
1050 uiWait1 = 1000 - 200;
1051 uiWait2 = 1000;
1052 }
1053 else
1054 {
1055 uiWait1 = 60 * 1000 - 200;
1056 uiWait2 = 60 * 1000;
1057 }
1058
1059 if (uiDueTime > uiWait1)
1060 {
1061 /* The update of the clock will be up to 200 ms late, but that's
1062 acceptable. We're going to setup a timer that fires depending
1063 uiWait2. */
1064 Ret = SetTimer(ID_TRAYCLOCK_TIMER, uiWait2, NULL) != 0;
1065 IsTimerEnabled = Ret;
1066
1067 /* Update the time */
1068 Update();
1069 }
1070 else
1071 {
1072 /* Recalibrate the timer and recalculate again when the current
1073 minute/second ends. */
1074 ResetTime();
1075 }
1076 }
1077
1078 LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1079 {
1080 /* Disable all timers */
1081 if (IsTimerEnabled)
1082 {
1083 KillTimer(ID_TRAYCLOCK_TIMER);
1084 }
1085
1086 if (IsInitTimerEnabled)
1087 {
1088 KillTimer(ID_TRAYCLOCK_TIMER_INIT);
1089 }
1090
1091 return TRUE;
1092 }
1093
1094 LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1095 {
1096 RECT rcClient;
1097 HFONT hPrevFont;
1098 INT iPrevBkMode;
1099 UINT i, line;
1100
1101 PAINTSTRUCT ps;
1102 HDC hDC = (HDC) wParam;
1103
1104 if (wParam == 0)
1105 {
1106 hDC = BeginPaint(&ps);
1107 }
1108
1109 if (hDC == NULL)
1110 return FALSE;
1111
1112 if (LinesMeasured &&
1113 GetClientRect(&rcClient))
1114 {
1115 iPrevBkMode = SetBkMode(hDC, TRANSPARENT);
1116
1117 SetTextColor(hDC, textColor);
1118
1119 hPrevFont = (HFONT) SelectObject(hDC, hFont);
1120
1121 rcClient.left = (rcClient.right / 2) - (CurrentSize.cx / 2);
1122 rcClient.top = (rcClient.bottom / 2) - (CurrentSize.cy / 2);
1123 rcClient.right = rcClient.left + CurrentSize.cx;
1124 rcClient.bottom = rcClient.top + CurrentSize.cy;
1125
1126 for (i = 0, line = 0;
1127 i < CLOCKWND_FORMAT_COUNT && line < VisibleLines;
1128 i++)
1129 {
1130 if (LineSizes[i].cx != 0)
1131 {
1132 TextOut(hDC,
1133 rcClient.left + (CurrentSize.cx / 2) - (LineSizes[i].cx / 2) +
1134 TRAY_CLOCK_WND_SPACING_X,
1135 rcClient.top + TRAY_CLOCK_WND_SPACING_Y,
1136 szLines[i],
1137 wcslen(szLines[i]));
1138
1139 rcClient.top += LineSizes[i].cy + LineSpacing;
1140 line++;
1141 }
1142 }
1143
1144 SelectObject(hDC, hPrevFont);
1145
1146 SetBkMode(hDC, iPrevBkMode);
1147 }
1148
1149 if (wParam == 0)
1150 {
1151 EndPaint(&ps);
1152 }
1153
1154 return TRUE;
1155 }
1156
1157 VOID SetFont(IN HFONT hNewFont, IN BOOL bRedraw)
1158 {
1159 hFont = hNewFont;
1160 LinesMeasured = MeasureLines();
1161 if (bRedraw)
1162 {
1163 InvalidateRect(NULL, TRUE);
1164 }
1165 }
1166
1167 LRESULT DrawBackground(HDC hdc)
1168 {
1169 RECT rect;
1170
1171 GetClientRect(&rect);
1172 DrawThemeParentBackground(m_hWnd, hdc, &rect);
1173
1174 return TRUE;
1175 }
1176
1177 LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1178 {
1179 HDC hdc = (HDC) wParam;
1180
1181 if (!IsAppThemed())
1182 {
1183 bHandled = FALSE;
1184 return 0;
1185 }
1186
1187 return DrawBackground(hdc);
1188 }
1189
1190 LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1191 {
1192 switch (wParam)
1193 {
1194 case ID_TRAYCLOCK_TIMER:
1195 Update();
1196 break;
1197
1198 case ID_TRAYCLOCK_TIMER_INIT:
1199 CalibrateTimer();
1200 break;
1201 }
1202 return TRUE;
1203 }
1204
1205 LRESULT OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1206 {
1207 IsHorizontal = (BOOL) wParam;
1208
1209 return (LRESULT) GetMinimumSize((BOOL) wParam, (PSIZE) lParam) != 0;
1210 }
1211
1212 LRESULT OnUpdateTime(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1213 {
1214 return (LRESULT) ResetTime();
1215 }
1216
1217 LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1218 {
1219 return HTTRANSPARENT;
1220 }
1221
1222 LRESULT OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1223 {
1224 SetFont((HFONT) wParam, (BOOL) LOWORD(lParam));
1225 return TRUE;
1226 }
1227
1228 LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1229 {
1230 ResetTime();
1231 return TRUE;
1232 }
1233
1234 LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1235 {
1236 SIZE szClient;
1237
1238 szClient.cx = LOWORD(lParam);
1239 szClient.cy = HIWORD(lParam);
1240
1241 VisibleLines = GetMinimumSize(IsHorizontal, &szClient);
1242 CurrentSize = szClient;
1243
1244 InvalidateRect(NULL, TRUE);
1245 return TRUE;
1246 }
1247
1248 DECLARE_WND_CLASS_EX(szTrayClockWndClass, CS_DBLCLKS, COLOR_3DFACE)
1249
1250 BEGIN_MSG_MAP(CTrayClockWnd)
1251 MESSAGE_HANDLER(WM_CREATE, OnCreate)
1252 MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
1253 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
1254 MESSAGE_HANDLER(WM_SIZE, OnSize)
1255 MESSAGE_HANDLER(WM_PAINT, OnPaint)
1256 MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
1257 MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
1258 MESSAGE_HANDLER(WM_TIMER, OnTimer)
1259 MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
1260 MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
1261 MESSAGE_HANDLER(TCWM_GETMINIMUMSIZE, OnGetMinimumSize)
1262 MESSAGE_HANDLER(TCWM_UPDATETIME, OnUpdateTime)
1263
1264 END_MSG_MAP()
1265
1266 HWND _Init(IN HWND hWndParent, IN BOOL bVisible)
1267 {
1268 IsHorizontal = TRUE;
1269
1270 hWndNotify = hWndParent;
1271
1272 /* Create the window. The tray window is going to move it to the correct
1273 position and resize it as needed. */
1274 DWORD dwStyle = WS_CHILD | WS_CLIPSIBLINGS;
1275 if (bVisible)
1276 dwStyle |= WS_VISIBLE;
1277
1278 Create(hWndParent, 0, NULL, dwStyle);
1279
1280 if (m_hWnd != NULL)
1281 SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
1282
1283 return m_hWnd;
1284
1285 }
1286 };
1287
1288 /*
1289 * TrayNotifyWnd
1290 */
1291
1292 static const WCHAR szTrayNotifyWndClass [] = TEXT("TrayNotifyWnd");
1293
1294 #define TRAY_NOTIFY_WND_SPACING_X 1
1295 #define TRAY_NOTIFY_WND_SPACING_Y 1
1296
1297 class CTrayNotifyWnd :
1298 public CComObjectRootEx<CComMultiThreadModelNoCS>,
1299 public CWindowImpl < CTrayNotifyWnd, CWindow, CControlWinTraits >
1300 {
1301 HWND hWndNotify;
1302
1303 CSysPagerWnd * m_pager;
1304 CTrayClockWnd * m_clock;
1305
1306 CComPtr<ITrayWindow> TrayWindow;
1307
1308 HTHEME TrayTheme;
1309 SIZE szTrayClockMin;
1310 SIZE szTrayNotify;
1311 MARGINS ContentMargin;
1312 union
1313 {
1314 DWORD dwFlags;
1315 struct
1316 {
1317 DWORD HideClock : 1;
1318 DWORD IsHorizontal : 1;
1319 };
1320 };
1321
1322 public:
1323 CTrayNotifyWnd() :
1324 hWndNotify(NULL),
1325 m_pager(NULL),
1326 m_clock(NULL),
1327 TrayTheme(NULL),
1328 dwFlags(0)
1329 {
1330 ZeroMemory(&szTrayClockMin, sizeof(szTrayClockMin));
1331 ZeroMemory(&szTrayNotify, sizeof(szTrayNotify));
1332 ZeroMemory(&ContentMargin, sizeof(ContentMargin));
1333 }
1334 virtual ~CTrayNotifyWnd() { }
1335
1336 LRESULT OnThemeChanged()
1337 {
1338 if (TrayTheme)
1339 CloseThemeData(TrayTheme);
1340
1341 if (IsThemeActive())
1342 TrayTheme = OpenThemeData(m_hWnd, L"TrayNotify");
1343 else
1344 TrayTheme = NULL;
1345
1346 if (TrayTheme)
1347 {
1348 SetWindowExStyle(m_hWnd, WS_EX_STATICEDGE, 0);
1349
1350 GetThemeMargins(TrayTheme,
1351 NULL,
1352 TNP_BACKGROUND,
1353 0,
1354 TMT_CONTENTMARGINS,
1355 NULL,
1356 &ContentMargin);
1357 }
1358 else
1359 {
1360 SetWindowExStyle(m_hWnd, WS_EX_STATICEDGE, WS_EX_STATICEDGE);
1361
1362 ContentMargin.cxLeftWidth = 2;
1363 ContentMargin.cxRightWidth = 2;
1364 ContentMargin.cyTopHeight = 2;
1365 ContentMargin.cyBottomHeight = 2;
1366 }
1367
1368 return TRUE;
1369 }
1370
1371 LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1372 {
1373 return OnThemeChanged();
1374 }
1375
1376 LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1377 {
1378 m_clock = new CTrayClockWnd();
1379 m_clock->_Init(m_hWnd, !HideClock);
1380
1381 m_pager = new CSysPagerWnd();
1382 m_pager->_Init(m_hWnd, !HideClock);
1383
1384 return TRUE;
1385 }
1386
1387 BOOL GetMinimumSize(IN OUT PSIZE pSize)
1388 {
1389 SIZE szClock = { 0, 0 };
1390 SIZE szTray = { 0, 0 };
1391
1392 if (!HideClock)
1393 {
1394 if (IsHorizontal)
1395 {
1396 szClock.cy = pSize->cy - 2 * TRAY_NOTIFY_WND_SPACING_Y;
1397 if (szClock.cy <= 0)
1398 goto NoClock;
1399 }
1400 else
1401 {
1402 szClock.cx = pSize->cx - 2 * TRAY_NOTIFY_WND_SPACING_X;
1403 if (szClock.cx <= 0)
1404 goto NoClock;
1405 }
1406
1407 m_clock->SendMessage(TCWM_GETMINIMUMSIZE, (WPARAM) IsHorizontal, (LPARAM) &szClock);
1408
1409 szTrayClockMin = szClock;
1410 }
1411 else
1412 NoClock:
1413 szTrayClockMin = szClock;
1414
1415 if (IsHorizontal)
1416 {
1417 szTray.cy = pSize->cy - 2 * TRAY_NOTIFY_WND_SPACING_Y;
1418 }
1419 else
1420 {
1421 szTray.cx = pSize->cx - 2 * TRAY_NOTIFY_WND_SPACING_X;
1422 }
1423
1424 m_pager->GetSize(IsHorizontal, &szTray);
1425
1426 szTrayNotify = szTray;
1427
1428 if (IsHorizontal)
1429 {
1430 pSize->cx = 2 * TRAY_NOTIFY_WND_SPACING_X;
1431
1432 if (!HideClock)
1433 pSize->cx += TRAY_NOTIFY_WND_SPACING_X + szTrayClockMin.cx;
1434
1435 pSize->cx += szTray.cx;
1436 }
1437 else
1438 {
1439 pSize->cy = 2 * TRAY_NOTIFY_WND_SPACING_Y;
1440
1441 if (!HideClock)
1442 pSize->cy += TRAY_NOTIFY_WND_SPACING_Y + szTrayClockMin.cy;
1443
1444 pSize->cy += szTray.cy;
1445 }
1446
1447 pSize->cy += ContentMargin.cyTopHeight + ContentMargin.cyBottomHeight;
1448 pSize->cx += ContentMargin.cxLeftWidth + ContentMargin.cxRightWidth;
1449
1450 return TRUE;
1451 }
1452
1453 VOID Size(IN const SIZE *pszClient)
1454 {
1455 if (!HideClock)
1456 {
1457 POINT ptClock;
1458 SIZE szClock;
1459
1460 if (IsHorizontal)
1461 {
1462 ptClock.x = pszClient->cx - szTrayClockMin.cx - ContentMargin.cxRightWidth;
1463 ptClock.y = ContentMargin.cyTopHeight;
1464 szClock.cx = szTrayClockMin.cx;
1465 szClock.cy = pszClient->cy - ContentMargin.cyTopHeight - ContentMargin.cyBottomHeight;
1466 }
1467 else
1468 {
1469 ptClock.x = ContentMargin.cxLeftWidth;
1470 ptClock.y = pszClient->cy - szTrayClockMin.cy;
1471 szClock.cx = pszClient->cx - ContentMargin.cxLeftWidth - ContentMargin.cxRightWidth;
1472 szClock.cy = szTrayClockMin.cy;
1473 }
1474
1475 m_clock->SetWindowPos(
1476 NULL,
1477 ptClock.x,
1478 ptClock.y,
1479 szClock.cx,
1480 szClock.cy,
1481 SWP_NOZORDER);
1482
1483 POINT ptPager;
1484
1485 if (IsHorizontal)
1486 {
1487 ptPager.x = ContentMargin.cxLeftWidth;
1488 ptPager.y = (pszClient->cy - szTrayNotify.cy)/2;
1489 }
1490 else
1491 {
1492 ptPager.x = (pszClient->cx - szTrayNotify.cx)/2;
1493 ptPager.y = ContentMargin.cyTopHeight;
1494 }
1495
1496 m_pager->SetWindowPos(
1497 NULL,
1498 ptPager.x,
1499 ptPager.y,
1500 szTrayNotify.cx,
1501 szTrayNotify.cy,
1502 SWP_NOZORDER);
1503 }
1504 }
1505
1506 LRESULT DrawBackground(HDC hdc)
1507 {
1508 HRESULT res;
1509 RECT rect;
1510
1511 GetClientRect(&rect);
1512
1513 if (TrayTheme)
1514 {
1515 if (IsThemeBackgroundPartiallyTransparent(TrayTheme, TNP_BACKGROUND, 0))
1516 {
1517 DrawThemeParentBackground(m_hWnd, hdc, &rect);
1518 }
1519
1520 res = DrawThemeBackground(TrayTheme, hdc, TNP_BACKGROUND, 0, &rect, 0);
1521 }
1522
1523 return res;
1524 }
1525
1526 LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1527 {
1528 HDC hdc = (HDC) wParam;
1529
1530 if (!TrayTheme)
1531 {
1532 bHandled = FALSE;
1533 return 0;
1534 }
1535
1536 return DrawBackground(hdc);
1537 }
1538
1539 BOOL NotifyIconCmd(WPARAM wParam, LPARAM lParam)
1540 {
1541 if (m_pager)
1542 {
1543 return m_pager->NotifyIconCmd(wParam, lParam);
1544 }
1545
1546 return TRUE;
1547 }
1548
1549 BOOL GetClockRect(OUT PRECT rcClock)
1550 {
1551 if (!m_clock->IsWindowVisible())
1552 return FALSE;
1553
1554 return m_clock->GetWindowRect(rcClock);
1555 }
1556
1557 LRESULT OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1558 {
1559 BOOL Horizontal = (BOOL) wParam;
1560
1561 if (Horizontal != IsHorizontal)
1562 {
1563 IsHorizontal = Horizontal;
1564 if (IsHorizontal)
1565 SetWindowTheme(m_hWnd, L"TrayNotifyHoriz", NULL);
1566 else
1567 SetWindowTheme(m_hWnd, L"TrayNotifyVert", NULL);
1568 }
1569
1570 return (LRESULT) GetMinimumSize((PSIZE) lParam);
1571 }
1572
1573 LRESULT OnUpdateTime(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1574 {
1575 if (m_clock != NULL)
1576 {
1577 /* Forward the message to the tray clock window procedure */
1578 return m_clock->OnUpdateTime(uMsg, wParam, lParam, bHandled);
1579 }
1580 return FALSE;
1581 }
1582
1583 LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1584 {
1585 SIZE szClient;
1586
1587 szClient.cx = LOWORD(lParam);
1588 szClient.cy = HIWORD(lParam);
1589
1590 Size(&szClient);
1591
1592 return TRUE;
1593 }
1594
1595 LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1596 {
1597 return HTTRANSPARENT;
1598 }
1599
1600 LRESULT OnShowClock(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1601 {
1602 BOOL PrevHidden = HideClock;
1603 HideClock = (wParam == 0);
1604
1605 if (m_clock != NULL && PrevHidden != HideClock)
1606 {
1607 m_clock->ShowWindow(HideClock ? SW_HIDE : SW_SHOW);
1608 }
1609
1610 return (LRESULT) (!PrevHidden);
1611 }
1612
1613 LRESULT OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1614 {
1615 const NMHDR *nmh = (const NMHDR *) lParam;
1616
1617 if (nmh->hwndFrom == m_clock->m_hWnd)
1618 {
1619 /* Pass down notifications */
1620 return m_clock->SendMessage(WM_NOTIFY, wParam, lParam);
1621 }
1622
1623 return FALSE;
1624 }
1625
1626 LRESULT OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1627 {
1628 if (m_clock != NULL)
1629 {
1630 m_clock->SendMessageW(WM_SETFONT, wParam, lParam);
1631 }
1632
1633 bHandled = FALSE;
1634 return FALSE;
1635 }
1636
1637 LRESULT OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1638 {
1639 bHandled = TRUE;
1640 return 0;
1641 }
1642
1643 LRESULT OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1644 {
1645 if (wParam == SPI_SETNONCLIENTMETRICS)
1646 {
1647 m_pager->ResizeImagelist();
1648 }
1649 return 0;
1650 }
1651
1652 DECLARE_WND_CLASS_EX(szTrayNotifyWndClass, CS_DBLCLKS, COLOR_3DFACE)
1653
1654 BEGIN_MSG_MAP(CTrayNotifyWnd)
1655 MESSAGE_HANDLER(WM_CREATE, OnCreate)
1656 MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
1657 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
1658 MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChanged)
1659 MESSAGE_HANDLER(WM_SIZE, OnSize)
1660 MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
1661 MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
1662 MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
1663 MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu) // FIXME: This handler is not necessary in Windows
1664 MESSAGE_HANDLER(TNWM_GETMINIMUMSIZE, OnGetMinimumSize)
1665 MESSAGE_HANDLER(TNWM_UPDATETIME, OnUpdateTime)
1666 MESSAGE_HANDLER(TNWM_SHOWCLOCK, OnShowClock)
1667 END_MSG_MAP()
1668
1669 HWND _Init(IN OUT ITrayWindow *TrayWindow, IN BOOL bHideClock)
1670 {
1671 HWND hWndTrayWindow;
1672
1673 hWndTrayWindow = TrayWindow->GetHWND();
1674 if (hWndTrayWindow == NULL)
1675 return NULL;
1676
1677 this->TrayWindow = TrayWindow;
1678 this->HideClock = bHideClock;
1679 this->hWndNotify = hWndTrayWindow;
1680
1681 DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
1682 return Create(hWndTrayWindow, 0, NULL, dwStyle, WS_EX_STATICEDGE);
1683 }
1684 };
1685
1686 HWND CreateTrayNotifyWnd(IN OUT ITrayWindow *Tray, BOOL bHideClock, CTrayNotifyWnd** ppinstance)
1687 {
1688 CTrayNotifyWnd * pTrayNotify = new CTrayNotifyWnd();
1689 // TODO: Destroy after the window is destroyed
1690 *ppinstance = pTrayNotify;
1691
1692 return pTrayNotify->_Init(Tray, bHideClock);
1693 }
1694
1695 BOOL
1696 TrayNotify_NotifyIconCmd(CTrayNotifyWnd* pTrayNotify, WPARAM wParam, LPARAM lParam)
1697 {
1698 return pTrayNotify->NotifyIconCmd(wParam, lParam);
1699 }
1700
1701 BOOL
1702 TrayNotify_GetClockRect(CTrayNotifyWnd* pTrayNotify, OUT PRECT rcClock)
1703 {
1704 return pTrayNotify->GetClockRect(rcClock);
1705 }