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