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