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