[EXPLORER] -Implement changing the size of the icons in the notification area when...
[reactos.git] / reactos / 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 = max((VisibleButtonCount + rows) / rows, 1);
569 }
570 else
571 {
572 columns = max(size->cx / cxButton, 1);
573 rows = max((VisibleButtonCount + columns) / columns, 1);
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 HFONT hFontClock;
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 hFontClock(NULL),
1330 dwFlags(0)
1331 {
1332 ZeroMemory(&szTrayClockMin, sizeof(szTrayClockMin));
1333 ZeroMemory(&szTrayNotify, sizeof(szTrayNotify));
1334 ZeroMemory(&ContentMargin, sizeof(ContentMargin));
1335 }
1336 virtual ~CTrayNotifyWnd() { }
1337
1338 LRESULT OnThemeChanged()
1339 {
1340 if (TrayTheme)
1341 CloseThemeData(TrayTheme);
1342
1343 if (IsThemeActive())
1344 TrayTheme = OpenThemeData(m_hWnd, L"TrayNotify");
1345 else
1346 TrayTheme = NULL;
1347
1348 if (TrayTheme)
1349 {
1350 SetWindowExStyle(m_hWnd, WS_EX_STATICEDGE, 0);
1351
1352 GetThemeMargins(TrayTheme,
1353 NULL,
1354 TNP_BACKGROUND,
1355 0,
1356 TMT_CONTENTMARGINS,
1357 NULL,
1358 &ContentMargin);
1359 }
1360 else
1361 {
1362 SetWindowExStyle(m_hWnd, WS_EX_STATICEDGE, WS_EX_STATICEDGE);
1363
1364 ContentMargin.cxLeftWidth = 0;
1365 ContentMargin.cxRightWidth = 0;
1366 ContentMargin.cyTopHeight = 0;
1367 ContentMargin.cyBottomHeight = 0;
1368 }
1369
1370 return TRUE;
1371 }
1372
1373 LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1374 {
1375 return OnThemeChanged();
1376 }
1377
1378 LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1379 {
1380 m_clock = new CTrayClockWnd();
1381 m_clock->_Init(m_hWnd, !HideClock);
1382
1383 m_pager = new CSysPagerWnd();
1384 m_pager->_Init(m_hWnd, !HideClock);
1385
1386 return TRUE;
1387 }
1388
1389 BOOL GetMinimumSize(IN OUT PSIZE pSize)
1390 {
1391 SIZE szClock = { 0, 0 };
1392 SIZE szTray = { 0, 0 };
1393
1394 if (!HideClock)
1395 {
1396 if (IsHorizontal)
1397 {
1398 szClock.cy = pSize->cy - 2 * TRAY_NOTIFY_WND_SPACING_Y;
1399 if (szClock.cy <= 0)
1400 goto NoClock;
1401 }
1402 else
1403 {
1404 szClock.cx = pSize->cx - 2 * TRAY_NOTIFY_WND_SPACING_X;
1405 if (szClock.cx <= 0)
1406 goto NoClock;
1407 }
1408
1409 m_clock->SendMessage(TCWM_GETMINIMUMSIZE, (WPARAM) IsHorizontal, (LPARAM) &szClock);
1410
1411 szTrayClockMin = szClock;
1412 }
1413 else
1414 NoClock:
1415 szTrayClockMin = szClock;
1416
1417 if (IsHorizontal)
1418 {
1419 szTray.cy = pSize->cy - 2 * TRAY_NOTIFY_WND_SPACING_Y;
1420 }
1421 else
1422 {
1423 szTray.cx = pSize->cx - 2 * TRAY_NOTIFY_WND_SPACING_X;
1424 }
1425
1426 m_pager->GetSize(IsHorizontal, &szTray);
1427
1428 szTrayNotify = szTray;
1429
1430 if (IsHorizontal)
1431 {
1432 pSize->cx = 2 * TRAY_NOTIFY_WND_SPACING_X;
1433
1434 if (!HideClock)
1435 pSize->cx += TRAY_NOTIFY_WND_SPACING_X + szTrayClockMin.cx;
1436
1437 pSize->cx += szTray.cx;
1438 }
1439 else
1440 {
1441 pSize->cy = 2 * TRAY_NOTIFY_WND_SPACING_Y;
1442
1443 if (!HideClock)
1444 pSize->cy += TRAY_NOTIFY_WND_SPACING_Y + szTrayClockMin.cy;
1445
1446 pSize->cy += szTray.cy;
1447 }
1448
1449 pSize->cy += ContentMargin.cyTopHeight + ContentMargin.cyBottomHeight;
1450 pSize->cx += ContentMargin.cxLeftWidth + ContentMargin.cxRightWidth;
1451
1452 return TRUE;
1453 }
1454
1455 VOID Size(IN const SIZE *pszClient)
1456 {
1457 if (!HideClock)
1458 {
1459 POINT ptClock;
1460 SIZE szClock;
1461
1462 if (IsHorizontal)
1463 {
1464 ptClock.x = pszClient->cx - TRAY_NOTIFY_WND_SPACING_X - szTrayClockMin.cx;
1465 ptClock.y = TRAY_NOTIFY_WND_SPACING_Y;
1466 szClock.cx = szTrayClockMin.cx;
1467 szClock.cy = pszClient->cy - (2 * TRAY_NOTIFY_WND_SPACING_Y);
1468 }
1469 else
1470 {
1471 ptClock.x = TRAY_NOTIFY_WND_SPACING_X;
1472 ptClock.y = pszClient->cy - TRAY_NOTIFY_WND_SPACING_Y - szTrayClockMin.cy;
1473 szClock.cx = pszClient->cx - (2 * TRAY_NOTIFY_WND_SPACING_X);
1474 szClock.cy = szTrayClockMin.cy;
1475 }
1476
1477 m_clock->SetWindowPos(
1478 NULL,
1479 ptClock.x,
1480 ptClock.y,
1481 szClock.cx,
1482 szClock.cy,
1483 SWP_NOZORDER);
1484
1485 POINT ptPager;
1486
1487 if (IsHorizontal)
1488 {
1489 ptPager.x = ptClock.x - szTrayNotify.cx;
1490 ptPager.y = (pszClient->cy - szTrayNotify.cy)/2;
1491 }
1492 else
1493 {
1494 ptPager.x = (pszClient->cx - szTrayNotify.cx)/2;
1495 ptPager.y = ptClock.y - szTrayNotify.cy;
1496 }
1497
1498 m_pager->SetWindowPos(
1499 NULL,
1500 ptPager.x,
1501 ptPager.y,
1502 szTrayNotify.cx,
1503 szTrayNotify.cy,
1504 SWP_NOZORDER);
1505 }
1506 }
1507
1508 LRESULT DrawBackground(HDC hdc)
1509 {
1510 HRESULT res;
1511 RECT rect;
1512
1513 GetClientRect(&rect);
1514
1515 if (TrayTheme)
1516 {
1517 if (IsThemeBackgroundPartiallyTransparent(TrayTheme, TNP_BACKGROUND, 0))
1518 {
1519 DrawThemeParentBackground(m_hWnd, hdc, &rect);
1520 }
1521
1522 res = DrawThemeBackground(TrayTheme, hdc, TNP_BACKGROUND, 0, &rect, 0);
1523 }
1524
1525 return res;
1526 }
1527
1528 LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1529 {
1530 HDC hdc = (HDC) wParam;
1531
1532 if (!TrayTheme)
1533 {
1534 bHandled = FALSE;
1535 return 0;
1536 }
1537
1538 return DrawBackground(hdc);
1539 }
1540
1541 BOOL NotifyIconCmd(WPARAM wParam, LPARAM lParam)
1542 {
1543 if (m_pager)
1544 {
1545 return m_pager->NotifyIconCmd(wParam, lParam);
1546 }
1547
1548 return TRUE;
1549 }
1550
1551 BOOL GetClockRect(OUT PRECT rcClock)
1552 {
1553 if (!m_clock->IsWindowVisible())
1554 return FALSE;
1555
1556 return m_clock->GetWindowRect(rcClock);
1557 }
1558
1559 LRESULT OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1560 {
1561 BOOL Horizontal = (BOOL) wParam;
1562
1563 if (Horizontal != IsHorizontal)
1564 {
1565 IsHorizontal = Horizontal;
1566 if (IsHorizontal)
1567 SetWindowTheme(m_hWnd, L"TrayNotifyHoriz", NULL);
1568 else
1569 SetWindowTheme(m_hWnd, L"TrayNotifyVert", NULL);
1570 }
1571
1572 return (LRESULT) GetMinimumSize((PSIZE) lParam);
1573 }
1574
1575 LRESULT OnUpdateTime(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1576 {
1577 if (m_clock != NULL)
1578 {
1579 /* Forward the message to the tray clock window procedure */
1580 return m_clock->OnUpdateTime(uMsg, wParam, lParam, bHandled);
1581 }
1582 return FALSE;
1583 }
1584
1585 LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1586 {
1587 SIZE szClient;
1588
1589 szClient.cx = LOWORD(lParam);
1590 szClient.cy = HIWORD(lParam);
1591
1592 Size(&szClient);
1593
1594 return TRUE;
1595 }
1596
1597 LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1598 {
1599 return HTTRANSPARENT;
1600 }
1601
1602 LRESULT OnShowClock(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1603 {
1604 BOOL PrevHidden = HideClock;
1605 HideClock = (wParam == 0);
1606
1607 if (m_clock != NULL && PrevHidden != HideClock)
1608 {
1609 m_clock->ShowWindow(HideClock ? SW_HIDE : SW_SHOW);
1610 }
1611
1612 return (LRESULT) (!PrevHidden);
1613 }
1614
1615 LRESULT OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1616 {
1617 const NMHDR *nmh = (const NMHDR *) lParam;
1618
1619 if (nmh->hwndFrom == m_clock->m_hWnd)
1620 {
1621 /* Pass down notifications */
1622 return m_clock->SendMessage(WM_NOTIFY, wParam, lParam);
1623 }
1624
1625 return FALSE;
1626 }
1627
1628 LRESULT OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1629 {
1630 if (m_clock != NULL)
1631 {
1632 m_clock->SendMessageW(WM_SETFONT, wParam, lParam);
1633 }
1634
1635 bHandled = FALSE;
1636 return FALSE;
1637 }
1638
1639 LRESULT OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1640 {
1641 bHandled = TRUE;
1642 return 0;
1643 }
1644
1645 LRESULT OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1646 {
1647 if (wParam == SPI_SETNONCLIENTMETRICS)
1648 {
1649 m_pager->ResizeImagelist();
1650 }
1651 return 0;
1652 }
1653
1654 DECLARE_WND_CLASS_EX(szTrayNotifyWndClass, CS_DBLCLKS, COLOR_3DFACE)
1655
1656 BEGIN_MSG_MAP(CTrayNotifyWnd)
1657 MESSAGE_HANDLER(WM_CREATE, OnCreate)
1658 MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
1659 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
1660 MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChanged)
1661 MESSAGE_HANDLER(WM_SIZE, OnSize)
1662 MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
1663 MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
1664 MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
1665 MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu) // FIXME: This handler is not necessary in Windows
1666 MESSAGE_HANDLER(TNWM_GETMINIMUMSIZE, OnGetMinimumSize)
1667 MESSAGE_HANDLER(TNWM_UPDATETIME, OnUpdateTime)
1668 MESSAGE_HANDLER(TNWM_SHOWCLOCK, OnShowClock)
1669 END_MSG_MAP()
1670
1671 HWND _Init(IN OUT ITrayWindow *TrayWindow, IN BOOL bHideClock)
1672 {
1673 HWND hWndTrayWindow;
1674
1675 hWndTrayWindow = TrayWindow->GetHWND();
1676 if (hWndTrayWindow == NULL)
1677 return NULL;
1678
1679 this->TrayWindow = TrayWindow;
1680 this->HideClock = bHideClock;
1681 this->hWndNotify = hWndTrayWindow;
1682
1683 DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
1684 return Create(hWndTrayWindow, 0, NULL, dwStyle, WS_EX_STATICEDGE);
1685 }
1686 };
1687
1688 HWND CreateTrayNotifyWnd(IN OUT ITrayWindow *Tray, BOOL bHideClock, CTrayNotifyWnd** ppinstance)
1689 {
1690 CTrayNotifyWnd * pTrayNotify = new CTrayNotifyWnd();
1691 // TODO: Destroy after the window is destroyed
1692 *ppinstance = pTrayNotify;
1693
1694 return pTrayNotify->_Init(Tray, bHideClock);
1695 }
1696
1697 BOOL
1698 TrayNotify_NotifyIconCmd(CTrayNotifyWnd* pTrayNotify, WPARAM wParam, LPARAM lParam)
1699 {
1700 return pTrayNotify->NotifyIconCmd(wParam, lParam);
1701 }
1702
1703 BOOL
1704 TrayNotify_GetClockRect(CTrayNotifyWnd* pTrayNotify, OUT PRECT rcClock)
1705 {
1706 return pTrayNotify->GetClockRect(rcClock);
1707 }