fe8ec05ac6398a99804d67f102c7b6cb9f03443c
[reactos.git] / base / shell / explorer / taskswnd.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 #include <commoncontrols.h>
23
24 /* Set DUMP_TASKS to 1 to enable a dump of the tasks and task groups every
25 5 seconds */
26 #define DUMP_TASKS 0
27 #define DEBUG_SHELL_HOOK 0
28
29 #define MAX_TASKS_COUNT (0x7FFF)
30 #define TASK_ITEM_ARRAY_ALLOC 64
31
32 const WCHAR szTaskSwitchWndClass[] = L"MSTaskSwWClass";
33 const WCHAR szRunningApps[] = L"Running Applications";
34
35 #if DEBUG_SHELL_HOOK
36 const struct {
37 INT msg;
38 LPCWSTR msg_name;
39 } hshell_msg [] = {
40 { HSHELL_WINDOWCREATED, L"HSHELL_WINDOWCREATED" },
41 { HSHELL_WINDOWDESTROYED, L"HSHELL_WINDOWDESTROYED" },
42 { HSHELL_ACTIVATESHELLWINDOW, L"HSHELL_ACTIVATESHELLWINDOW" },
43 { HSHELL_WINDOWACTIVATED, L"HSHELL_WINDOWACTIVATED" },
44 { HSHELL_GETMINRECT, L"HSHELL_GETMINRECT" },
45 { HSHELL_REDRAW, L"HSHELL_REDRAW" },
46 { HSHELL_TASKMAN, L"HSHELL_TASKMAN" },
47 { HSHELL_LANGUAGE, L"HSHELL_LANGUAGE" },
48 { HSHELL_SYSMENU, L"HSHELL_SYSMENU" },
49 { HSHELL_ENDTASK, L"HSHELL_ENDTASK" },
50 { HSHELL_ACCESSIBILITYSTATE, L"HSHELL_ACCESSIBILITYSTATE" },
51 { HSHELL_APPCOMMAND, L"HSHELL_APPCOMMAND" },
52 { HSHELL_WINDOWREPLACED, L"HSHELL_WINDOWREPLACED" },
53 { HSHELL_WINDOWREPLACING, L"HSHELL_WINDOWREPLACING" },
54 { HSHELL_RUDEAPPACTIVATED, L"HSHELL_RUDEAPPACTIVATED" },
55 };
56 #endif
57
58 typedef struct _TASK_GROUP
59 {
60 /* We have to use a linked list instead of an array so we don't have to
61 update all pointers to groups in the task item array when removing
62 groups. */
63 struct _TASK_GROUP *Next;
64
65 DWORD dwTaskCount;
66 DWORD dwProcessId;
67 INT Index;
68 union
69 {
70 DWORD dwFlags;
71 struct
72 {
73
74 DWORD IsCollapsed : 1;
75 };
76 };
77 } TASK_GROUP, *PTASK_GROUP;
78
79 typedef struct _TASK_ITEM
80 {
81 HWND hWnd;
82 PTASK_GROUP Group;
83 INT Index;
84 INT IconIndex;
85
86 union
87 {
88 DWORD dwFlags;
89 struct
90 {
91
92 /* IsFlashing is TRUE when the task bar item should be flashing. */
93 DWORD IsFlashing : 1;
94
95 /* RenderFlashed is only TRUE if the task bar item should be
96 drawn with a flash. */
97 DWORD RenderFlashed : 1;
98 };
99 };
100 } TASK_ITEM, *PTASK_ITEM;
101
102
103 class CHardErrorThread
104 {
105 DWORD m_ThreadId;
106 HANDLE m_hThread;
107 LONG m_bThreadRunning;
108 DWORD m_Status;
109 DWORD m_dwType;
110 CStringW m_Title;
111 CStringW m_Text;
112 public:
113
114 CHardErrorThread():
115 m_ThreadId(0),
116 m_hThread(NULL),
117 m_bThreadRunning(FALSE),
118 m_Status(NULL),
119 m_dwType(NULL)
120 {
121 }
122
123 ~CHardErrorThread()
124 {
125 if (m_bThreadRunning)
126 {
127 /* Try to unstuck Show */
128 PostThreadMessage(m_ThreadId, WM_QUIT, 0, 0);
129 DWORD ret = WaitForSingleObject(m_hThread, 3*1000);
130 if (ret == WAIT_TIMEOUT)
131 TerminateThread(m_hThread, 0);
132 CloseHandle(m_hThread);
133 }
134 }
135
136 HRESULT ThreadProc()
137 {
138 HRESULT hr;
139 CComPtr<IUserNotification> pnotification;
140
141 hr = OleInitialize(NULL);
142 if (FAILED_UNEXPECTEDLY(hr))
143 return hr;
144
145 hr = CoCreateInstance(CLSID_UserNotification,
146 NULL,
147 CLSCTX_INPROC_SERVER,
148 IID_PPV_ARG(IUserNotification, &pnotification));
149 if (FAILED_UNEXPECTEDLY(hr))
150 return hr;
151
152 hr = pnotification->SetBalloonInfo(m_Title, m_Text, NIIF_WARNING);
153 if (FAILED_UNEXPECTEDLY(hr))
154 return hr;
155
156 hr = pnotification->SetIconInfo(NULL, NULL);
157 if (FAILED_UNEXPECTEDLY(hr))
158 return hr;
159
160 /* Show will block until the balloon closes */
161 hr = pnotification->Show(NULL, 0);
162 if (FAILED_UNEXPECTEDLY(hr))
163 return hr;
164
165 return S_OK;
166 }
167
168 static DWORD CALLBACK s_HardErrorThreadProc(IN OUT LPVOID lpParameter)
169 {
170 CHardErrorThread* pThis = reinterpret_cast<CHardErrorThread*>(lpParameter);
171 pThis->ThreadProc();
172 CloseHandle(pThis->m_hThread);
173 OleUninitialize();
174 InterlockedExchange(&pThis->m_bThreadRunning, FALSE);
175 return 0;
176 }
177
178 void StartThread(PBALLOON_HARD_ERROR_DATA pData)
179 {
180 BOOL bIsRunning = InterlockedExchange(&m_bThreadRunning, TRUE);
181
182 /* Ignore the new message if we are already showing one */
183 if (bIsRunning)
184 return;
185
186 m_Status = pData->Status;
187 m_dwType = pData->dwType;
188 m_Title = (PWCHAR)((ULONG_PTR)pData + pData->TitleOffset);
189 m_Text = (PWCHAR)((ULONG_PTR)pData + pData->MessageOffset);
190 m_hThread = CreateThread(NULL, 0, s_HardErrorThreadProc, this, 0, &m_ThreadId);
191 if (!m_hThread)
192 {
193 m_bThreadRunning = FALSE;
194 CloseHandle(m_hThread);
195 }
196 }
197 };
198
199 class CTaskToolbar :
200 public CWindowImplBaseT< CToolbar<TASK_ITEM>, CControlWinTraits >
201 {
202 public:
203 INT UpdateTbButtonSpacing(IN BOOL bHorizontal, IN BOOL bThemed, IN UINT uiRows = 0, IN UINT uiBtnsPerLine = 0)
204 {
205 TBMETRICS tbm;
206
207 tbm.cbSize = sizeof(tbm);
208 tbm.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING;
209
210 tbm.cxBarPad = tbm.cyBarPad = 0;
211
212 if (bThemed)
213 {
214 tbm.cxButtonSpacing = 0;
215 tbm.cyButtonSpacing = 0;
216 }
217 else
218 {
219 if (bHorizontal || uiBtnsPerLine > 1)
220 tbm.cxButtonSpacing = (3 * GetSystemMetrics(SM_CXEDGE) / 2);
221 else
222 tbm.cxButtonSpacing = 0;
223
224 if (!bHorizontal || uiRows > 1)
225 tbm.cyButtonSpacing = (3 * GetSystemMetrics(SM_CYEDGE) / 2);
226 else
227 tbm.cyButtonSpacing = 0;
228 }
229
230 SetMetrics(&tbm);
231
232 return tbm.cxButtonSpacing;
233 }
234
235 VOID BeginUpdate()
236 {
237 SetRedraw(FALSE);
238 }
239
240 VOID EndUpdate()
241 {
242 SendMessageW(WM_SETREDRAW, TRUE);
243 InvalidateRect(NULL, TRUE);
244 }
245
246 BOOL SetButtonCommandId(IN INT iButtonIndex, IN INT iCommandId)
247 {
248 TBBUTTONINFO tbbi;
249
250 tbbi.cbSize = sizeof(tbbi);
251 tbbi.dwMask = TBIF_BYINDEX | TBIF_COMMAND;
252 tbbi.idCommand = iCommandId;
253
254 return SetButtonInfo(iButtonIndex, &tbbi) != 0;
255 }
256
257 LRESULT OnNcHitTestToolbar(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
258 {
259 POINT pt;
260
261 /* See if the mouse is on a button */
262 pt.x = GET_X_LPARAM(lParam);
263 pt.y = GET_Y_LPARAM(lParam);
264 ScreenToClient(&pt);
265
266 INT index = HitTest(&pt);
267 if (index < 0)
268 {
269 /* Make the control appear to be transparent outside of any buttons */
270 return HTTRANSPARENT;
271 }
272
273 bHandled = FALSE;
274 return 0;
275 }
276
277 public:
278 BEGIN_MSG_MAP(CNotifyToolbar)
279 MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTestToolbar)
280 END_MSG_MAP()
281
282 BOOL Initialize(HWND hWndParent)
283 {
284 DWORD styles = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
285 TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | TBSTYLE_LIST | TBSTYLE_TRANSPARENT |
286 CCS_TOP | CCS_NORESIZE | CCS_NODIVIDER;
287
288 return SubclassWindow(CToolbar::Create(hWndParent, styles));
289 }
290 };
291
292 class CTaskSwitchWnd :
293 public CComCoClass<CTaskSwitchWnd>,
294 public CComObjectRootEx<CComMultiThreadModelNoCS>,
295 public CWindowImpl < CTaskSwitchWnd, CWindow, CControlWinTraits >,
296 public IOleWindow
297 {
298 CTaskToolbar m_TaskBar;
299
300 CComPtr<ITrayWindow> m_Tray;
301
302 UINT m_ShellHookMsg;
303
304 WORD m_TaskItemCount;
305 WORD m_AllocatedTaskItems;
306
307 PTASK_GROUP m_TaskGroups;
308 PTASK_ITEM m_TaskItems;
309 PTASK_ITEM m_ActiveTaskItem;
310
311 HTHEME m_Theme;
312 UINT m_ButtonsPerLine;
313 WORD m_ButtonCount;
314
315 HIMAGELIST m_ImageList;
316
317 BOOL m_IsGroupingEnabled;
318 BOOL m_IsDestroying;
319
320 SIZE m_ButtonSize;
321
322 UINT m_uHardErrorMsg;
323 CHardErrorThread m_HardErrorThread;
324
325 public:
326 CTaskSwitchWnd() :
327 m_ShellHookMsg(NULL),
328 m_TaskItemCount(0),
329 m_AllocatedTaskItems(0),
330 m_TaskGroups(NULL),
331 m_TaskItems(NULL),
332 m_ActiveTaskItem(NULL),
333 m_Theme(NULL),
334 m_ButtonsPerLine(0),
335 m_ButtonCount(0),
336 m_ImageList(NULL),
337 m_IsGroupingEnabled(FALSE),
338 m_IsDestroying(FALSE)
339 {
340 ZeroMemory(&m_ButtonSize, sizeof(m_ButtonSize));
341 m_uHardErrorMsg = RegisterWindowMessageW(L"HardError");
342 }
343 virtual ~CTaskSwitchWnd() { }
344
345 INT GetWndTextFromTaskItem(IN PTASK_ITEM TaskItem, LPWSTR szBuf, DWORD cchBuf)
346 {
347 /* Get the window text without sending a message so we don't hang if an
348 application isn't responding! */
349 return InternalGetWindowText(TaskItem->hWnd, szBuf, cchBuf);
350 }
351
352
353 #if DUMP_TASKS != 0
354 VOID DumpTasks()
355 {
356 PTASK_GROUP CurrentGroup;
357 PTASK_ITEM CurrentTaskItem, LastTaskItem;
358
359 TRACE("Tasks dump:\n");
360 if (m_IsGroupingEnabled)
361 {
362 CurrentGroup = m_TaskGroups;
363 while (CurrentGroup != NULL)
364 {
365 TRACE("- Group PID: 0x%p Tasks: %d Index: %d\n", CurrentGroup->dwProcessId, CurrentGroup->dwTaskCount, CurrentGroup->Index);
366
367 CurrentTaskItem = m_TaskItems;
368 LastTaskItem = CurrentTaskItem + m_TaskItemCount;
369 while (CurrentTaskItem != LastTaskItem)
370 {
371 if (CurrentTaskItem->Group == CurrentGroup)
372 {
373 TRACE(" + Task hwnd: 0x%p Index: %d\n", CurrentTaskItem->hWnd, CurrentTaskItem->Index);
374 }
375 CurrentTaskItem++;
376 }
377
378 CurrentGroup = CurrentGroup->Next;
379 }
380
381 CurrentTaskItem = m_TaskItems;
382 LastTaskItem = CurrentTaskItem + m_TaskItemCount;
383 while (CurrentTaskItem != LastTaskItem)
384 {
385 if (CurrentTaskItem->Group == NULL)
386 {
387 TRACE("- Task hwnd: 0x%p Index: %d\n", CurrentTaskItem->hWnd, CurrentTaskItem->Index);
388 }
389 CurrentTaskItem++;
390 }
391 }
392 else
393 {
394 CurrentTaskItem = m_TaskItems;
395 LastTaskItem = CurrentTaskItem + m_TaskItemCount;
396 while (CurrentTaskItem != LastTaskItem)
397 {
398 TRACE("- Task hwnd: 0x%p Index: %d\n", CurrentTaskItem->hWnd, CurrentTaskItem->Index);
399 CurrentTaskItem++;
400 }
401 }
402 }
403 #endif
404
405 VOID UpdateIndexesAfter(IN INT iIndex, BOOL bInserted)
406 {
407 PTASK_GROUP CurrentGroup;
408 PTASK_ITEM CurrentTaskItem, LastTaskItem;
409 INT NewIndex;
410
411 int offset = bInserted ? +1 : -1;
412
413 if (m_IsGroupingEnabled)
414 {
415 /* Update all affected groups */
416 CurrentGroup = m_TaskGroups;
417 while (CurrentGroup != NULL)
418 {
419 if (CurrentGroup->IsCollapsed &&
420 CurrentGroup->Index >= iIndex)
421 {
422 /* Update the toolbar buttons */
423 NewIndex = CurrentGroup->Index + offset;
424 if (m_TaskBar.SetButtonCommandId(CurrentGroup->Index + offset, NewIndex))
425 {
426 CurrentGroup->Index = NewIndex;
427 }
428 else
429 CurrentGroup->Index = -1;
430 }
431
432 CurrentGroup = CurrentGroup->Next;
433 }
434 }
435
436 /* Update all affected task items */
437 CurrentTaskItem = m_TaskItems;
438 LastTaskItem = CurrentTaskItem + m_TaskItemCount;
439 while (CurrentTaskItem != LastTaskItem)
440 {
441 CurrentGroup = CurrentTaskItem->Group;
442 if (CurrentGroup != NULL)
443 {
444 if (!CurrentGroup->IsCollapsed &&
445 CurrentTaskItem->Index >= iIndex)
446 {
447 goto UpdateTaskItemBtn;
448 }
449 }
450 else if (CurrentTaskItem->Index >= iIndex)
451 {
452 UpdateTaskItemBtn:
453 /* Update the toolbar buttons */
454 NewIndex = CurrentTaskItem->Index + offset;
455 if (m_TaskBar.SetButtonCommandId(CurrentTaskItem->Index + offset, NewIndex))
456 {
457 CurrentTaskItem->Index = NewIndex;
458 }
459 else
460 CurrentTaskItem->Index = -1;
461 }
462
463 CurrentTaskItem++;
464 }
465 }
466
467
468 INT UpdateTaskGroupButton(IN PTASK_GROUP TaskGroup)
469 {
470 ASSERT(TaskGroup->Index >= 0);
471
472 /* FIXME: Implement */
473
474 return TaskGroup->Index;
475 }
476
477 VOID ExpandTaskGroup(IN PTASK_GROUP TaskGroup)
478 {
479 ASSERT(TaskGroup->dwTaskCount > 0);
480 ASSERT(TaskGroup->IsCollapsed);
481 ASSERT(TaskGroup->Index >= 0);
482
483 /* FIXME: Implement */
484 }
485
486 HICON GetWndIcon(HWND hwnd)
487 {
488 HICON hIcon = 0;
489
490 SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL2, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR) &hIcon);
491 if (hIcon)
492 return hIcon;
493
494 SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR) &hIcon);
495 if (hIcon)
496 return hIcon;
497
498 SendMessageTimeout(hwnd, WM_GETICON, ICON_BIG, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR) &hIcon);
499 if (hIcon)
500 return hIcon;
501
502 hIcon = (HICON) GetClassLongPtr(hwnd, GCL_HICONSM);
503 if (hIcon)
504 return hIcon;
505
506 hIcon = (HICON) GetClassLongPtr(hwnd, GCL_HICON);
507
508 return hIcon;
509 }
510
511 INT UpdateTaskItemButton(IN PTASK_ITEM TaskItem)
512 {
513 TBBUTTONINFO tbbi = { 0 };
514 HICON icon;
515 WCHAR windowText[255];
516
517 ASSERT(TaskItem->Index >= 0);
518
519 tbbi.cbSize = sizeof(tbbi);
520 tbbi.dwMask = TBIF_BYINDEX | TBIF_STATE | TBIF_TEXT | TBIF_IMAGE;
521 tbbi.fsState = TBSTATE_ENABLED;
522 if (m_ActiveTaskItem == TaskItem)
523 tbbi.fsState |= TBSTATE_CHECKED;
524
525 if (TaskItem->RenderFlashed)
526 tbbi.fsState |= TBSTATE_MARKED;
527
528 /* Check if we're updating a button that is the last one in the
529 line. If so, we need to set the TBSTATE_WRAP flag! */
530 if (!m_Tray->IsHorizontal() || (m_ButtonsPerLine != 0 &&
531 (TaskItem->Index + 1) % m_ButtonsPerLine == 0))
532 {
533 tbbi.fsState |= TBSTATE_WRAP;
534 }
535
536 if (GetWndTextFromTaskItem(TaskItem, windowText, _countof(windowText)) > 0)
537 {
538 tbbi.pszText = windowText;
539 }
540
541 icon = GetWndIcon(TaskItem->hWnd);
542 if (!icon)
543 icon = static_cast<HICON>(LoadImageW(NULL, MAKEINTRESOURCEW(OIC_SAMPLE), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE));
544 TaskItem->IconIndex = ImageList_ReplaceIcon(m_ImageList, TaskItem->IconIndex, icon);
545 tbbi.iImage = TaskItem->IconIndex;
546
547 if (!m_TaskBar.SetButtonInfo(TaskItem->Index, &tbbi))
548 {
549 TaskItem->Index = -1;
550 return -1;
551 }
552
553 TRACE("Updated button %d for hwnd 0x%p\n", TaskItem->Index, TaskItem->hWnd);
554 return TaskItem->Index;
555 }
556
557 VOID RemoveIcon(IN PTASK_ITEM TaskItem)
558 {
559 TBBUTTONINFO tbbi;
560 PTASK_ITEM currentTaskItem, LastItem;
561
562 if (TaskItem->IconIndex == -1)
563 return;
564
565 tbbi.cbSize = sizeof(tbbi);
566 tbbi.dwMask = TBIF_IMAGE;
567
568 currentTaskItem = m_TaskItems;
569 LastItem = currentTaskItem + m_TaskItemCount;
570 while (currentTaskItem != LastItem)
571 {
572 if (currentTaskItem->IconIndex > TaskItem->IconIndex)
573 {
574 currentTaskItem->IconIndex--;
575 tbbi.iImage = currentTaskItem->IconIndex;
576
577 m_TaskBar.SetButtonInfo(currentTaskItem->Index, &tbbi);
578 }
579 currentTaskItem++;
580 }
581
582 ImageList_Remove(m_ImageList, TaskItem->IconIndex);
583 }
584
585 PTASK_ITEM FindLastTaskItemOfGroup(
586 IN PTASK_GROUP TaskGroup OPTIONAL,
587 IN PTASK_ITEM NewTaskItem OPTIONAL)
588 {
589 PTASK_ITEM TaskItem, LastTaskItem, FoundTaskItem = NULL;
590 DWORD dwTaskCount;
591
592 ASSERT(m_IsGroupingEnabled);
593
594 TaskItem = m_TaskItems;
595 LastTaskItem = TaskItem + m_TaskItemCount;
596
597 dwTaskCount = (TaskGroup != NULL ? TaskGroup->dwTaskCount : MAX_TASKS_COUNT);
598
599 ASSERT(dwTaskCount > 0);
600
601 while (TaskItem != LastTaskItem)
602 {
603 if (TaskItem->Group == TaskGroup)
604 {
605 if ((NewTaskItem != NULL && TaskItem != NewTaskItem) || NewTaskItem == NULL)
606 {
607 FoundTaskItem = TaskItem;
608 }
609
610 if (--dwTaskCount == 0)
611 {
612 /* We found the last task item in the group! */
613 break;
614 }
615 }
616
617 TaskItem++;
618 }
619
620 return FoundTaskItem;
621 }
622
623 INT CalculateTaskItemNewButtonIndex(IN PTASK_ITEM TaskItem)
624 {
625 PTASK_GROUP TaskGroup;
626 PTASK_ITEM LastTaskItem;
627
628 /* NOTE: This routine assumes that the group is *not* collapsed! */
629
630 TaskGroup = TaskItem->Group;
631 if (m_IsGroupingEnabled)
632 {
633 if (TaskGroup != NULL)
634 {
635 ASSERT(TaskGroup->Index < 0);
636 ASSERT(!TaskGroup->IsCollapsed);
637
638 if (TaskGroup->dwTaskCount > 1)
639 {
640 LastTaskItem = FindLastTaskItemOfGroup(TaskGroup, TaskItem);
641 if (LastTaskItem != NULL)
642 {
643 /* Since the group is expanded the task items must have an index */
644 ASSERT(LastTaskItem->Index >= 0);
645
646 return LastTaskItem->Index + 1;
647 }
648 }
649 }
650 else
651 {
652 /* Find the last NULL group button. NULL groups are added at the end of the
653 task item list when grouping is enabled */
654 LastTaskItem = FindLastTaskItemOfGroup(NULL, TaskItem);
655 if (LastTaskItem != NULL)
656 {
657 ASSERT(LastTaskItem->Index >= 0);
658
659 return LastTaskItem->Index + 1;
660 }
661 }
662 }
663
664 return m_ButtonCount;
665 }
666
667 INT AddTaskItemButton(IN OUT PTASK_ITEM TaskItem)
668 {
669 WCHAR windowText[255];
670 TBBUTTON tbBtn = { 0 };
671 INT iIndex;
672 HICON icon;
673
674 if (TaskItem->Index >= 0)
675 {
676 return UpdateTaskItemButton(TaskItem);
677 }
678
679 if (TaskItem->Group != NULL &&
680 TaskItem->Group->IsCollapsed)
681 {
682 /* The task group is collapsed, we only need to update the group button */
683 return UpdateTaskGroupButton(TaskItem->Group);
684 }
685
686 icon = GetWndIcon(TaskItem->hWnd);
687 if (!icon)
688 icon = static_cast<HICON>(LoadImageW(NULL, MAKEINTRESOURCEW(OIC_SAMPLE), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE));
689 TaskItem->IconIndex = ImageList_ReplaceIcon(m_ImageList, -1, icon);
690
691 tbBtn.iBitmap = TaskItem->IconIndex;
692 tbBtn.fsState = TBSTATE_ENABLED | TBSTATE_ELLIPSES;
693 tbBtn.fsStyle = BTNS_CHECK | BTNS_NOPREFIX | BTNS_SHOWTEXT;
694 tbBtn.dwData = TaskItem->Index;
695
696 if (GetWndTextFromTaskItem(TaskItem, windowText, _countof(windowText)) > 0)
697 {
698 tbBtn.iString = (DWORD_PTR) windowText;
699 }
700
701 /* Find out where to insert the new button */
702 iIndex = CalculateTaskItemNewButtonIndex(TaskItem);
703 ASSERT(iIndex >= 0);
704 tbBtn.idCommand = iIndex;
705
706 m_TaskBar.BeginUpdate();
707
708 if (m_TaskBar.InsertButton(iIndex, &tbBtn))
709 {
710 UpdateIndexesAfter(iIndex, TRUE);
711
712 TRACE("Added button %d for hwnd 0x%p\n", iIndex, TaskItem->hWnd);
713
714 TaskItem->Index = iIndex;
715 m_ButtonCount++;
716
717 /* Update button sizes and fix the button wrapping */
718 UpdateButtonsSize(TRUE);
719 return iIndex;
720 }
721
722 m_TaskBar.EndUpdate();
723
724 return -1;
725 }
726
727 BOOL DeleteTaskItemButton(IN OUT PTASK_ITEM TaskItem)
728 {
729 PTASK_GROUP TaskGroup;
730 INT iIndex;
731
732 TaskGroup = TaskItem->Group;
733
734 if (TaskItem->Index >= 0)
735 {
736 if ((TaskGroup != NULL && !TaskGroup->IsCollapsed) ||
737 TaskGroup == NULL)
738 {
739 m_TaskBar.BeginUpdate();
740
741 RemoveIcon(TaskItem);
742 iIndex = TaskItem->Index;
743 if (m_TaskBar.DeleteButton(iIndex))
744 {
745 TaskItem->Index = -1;
746 m_ButtonCount--;
747
748 UpdateIndexesAfter(iIndex, FALSE);
749
750 /* Update button sizes and fix the button wrapping */
751 UpdateButtonsSize(TRUE);
752 return TRUE;
753 }
754
755 m_TaskBar.EndUpdate();
756 }
757 }
758
759 return FALSE;
760 }
761
762 PTASK_GROUP AddToTaskGroup(IN HWND hWnd)
763 {
764 DWORD dwProcessId;
765 PTASK_GROUP TaskGroup, *PrevLink;
766
767 if (!GetWindowThreadProcessId(hWnd,
768 &dwProcessId))
769 {
770 TRACE("Cannot get process id of hwnd 0x%p\n", hWnd);
771 return NULL;
772 }
773
774 /* Try to find an existing task group */
775 TaskGroup = m_TaskGroups;
776 PrevLink = &m_TaskGroups;
777 while (TaskGroup != NULL)
778 {
779 if (TaskGroup->dwProcessId == dwProcessId)
780 {
781 TaskGroup->dwTaskCount++;
782 return TaskGroup;
783 }
784
785 PrevLink = &TaskGroup->Next;
786 TaskGroup = TaskGroup->Next;
787 }
788
789 /* Allocate a new task group */
790 TaskGroup = (PTASK_GROUP) HeapAlloc(hProcessHeap,
791 HEAP_ZERO_MEMORY,
792 sizeof(*TaskGroup));
793 if (TaskGroup != NULL)
794 {
795 TaskGroup->dwTaskCount = 1;
796 TaskGroup->dwProcessId = dwProcessId;
797 TaskGroup->Index = -1;
798
799 /* Add the task group to the list */
800 *PrevLink = TaskGroup;
801 }
802
803 return TaskGroup;
804 }
805
806 VOID RemoveTaskFromTaskGroup(IN OUT PTASK_ITEM TaskItem)
807 {
808 PTASK_GROUP TaskGroup, CurrentGroup, *PrevLink;
809
810 TaskGroup = TaskItem->Group;
811 if (TaskGroup != NULL)
812 {
813 DWORD dwNewTaskCount = --TaskGroup->dwTaskCount;
814 if (dwNewTaskCount == 0)
815 {
816 /* Find the previous pointer in the chain */
817 CurrentGroup = m_TaskGroups;
818 PrevLink = &m_TaskGroups;
819 while (CurrentGroup != TaskGroup)
820 {
821 PrevLink = &CurrentGroup->Next;
822 CurrentGroup = CurrentGroup->Next;
823 }
824
825 /* Remove the group from the list */
826 ASSERT(TaskGroup == CurrentGroup);
827 *PrevLink = TaskGroup->Next;
828
829 /* Free the task group */
830 HeapFree(hProcessHeap,
831 0,
832 TaskGroup);
833 }
834 else if (TaskGroup->IsCollapsed &&
835 TaskGroup->Index >= 0)
836 {
837 if (dwNewTaskCount > 1)
838 {
839 /* FIXME: Check if we should expand the group */
840 /* Update the task group button */
841 UpdateTaskGroupButton(TaskGroup);
842 }
843 else
844 {
845 /* Expand the group of one task button to a task button */
846 ExpandTaskGroup(TaskGroup);
847 }
848 }
849 }
850 }
851
852 PTASK_ITEM FindTaskItem(IN HWND hWnd)
853 {
854 PTASK_ITEM TaskItem, LastItem;
855
856 TaskItem = m_TaskItems;
857 LastItem = TaskItem + m_TaskItemCount;
858 while (TaskItem != LastItem)
859 {
860 if (TaskItem->hWnd == hWnd)
861 return TaskItem;
862
863 TaskItem++;
864 }
865
866 return NULL;
867 }
868
869 PTASK_ITEM FindOtherTaskItem(IN HWND hWnd)
870 {
871 PTASK_ITEM LastItem, TaskItem;
872 PTASK_GROUP TaskGroup;
873 DWORD dwProcessId;
874
875 if (!GetWindowThreadProcessId(hWnd, &dwProcessId))
876 {
877 return NULL;
878 }
879
880 /* Try to find another task that belongs to the same
881 process as the given window */
882 TaskItem = m_TaskItems;
883 LastItem = TaskItem + m_TaskItemCount;
884 while (TaskItem != LastItem)
885 {
886 TaskGroup = TaskItem->Group;
887 if (TaskGroup != NULL)
888 {
889 if (TaskGroup->dwProcessId == dwProcessId)
890 return TaskItem;
891 }
892 else
893 {
894 DWORD dwProcessIdTask;
895
896 if (GetWindowThreadProcessId(TaskItem->hWnd,
897 &dwProcessIdTask) &&
898 dwProcessIdTask == dwProcessId)
899 {
900 return TaskItem;
901 }
902 }
903
904 TaskItem++;
905 }
906
907 return NULL;
908 }
909
910 PTASK_ITEM AllocTaskItem()
911 {
912 if (m_TaskItemCount >= MAX_TASKS_COUNT)
913 {
914 /* We need the most significant bit in 16 bit command IDs to indicate whether it
915 is a task group or task item. WM_COMMAND limits command IDs to 16 bits! */
916 return NULL;
917 }
918
919 ASSERT(m_AllocatedTaskItems >= m_TaskItemCount);
920
921 if (m_TaskItemCount == 0)
922 {
923 m_TaskItems = (PTASK_ITEM) HeapAlloc(hProcessHeap,
924 0,
925 TASK_ITEM_ARRAY_ALLOC * sizeof(*m_TaskItems));
926 if (m_TaskItems != NULL)
927 {
928 m_AllocatedTaskItems = TASK_ITEM_ARRAY_ALLOC;
929 }
930 else
931 return NULL;
932 }
933 else if (m_TaskItemCount >= m_AllocatedTaskItems)
934 {
935 PTASK_ITEM NewArray;
936 SIZE_T NewArrayLength, ActiveTaskItemIndex;
937
938 NewArrayLength = m_AllocatedTaskItems + TASK_ITEM_ARRAY_ALLOC;
939
940 NewArray = (PTASK_ITEM) HeapReAlloc(hProcessHeap,
941 0,
942 m_TaskItems,
943 NewArrayLength * sizeof(*m_TaskItems));
944 if (NewArray != NULL)
945 {
946 if (m_ActiveTaskItem != NULL)
947 {
948 /* Fixup the ActiveTaskItem pointer */
949 ActiveTaskItemIndex = m_ActiveTaskItem - m_TaskItems;
950 m_ActiveTaskItem = NewArray + ActiveTaskItemIndex;
951 }
952 m_AllocatedTaskItems = (WORD) NewArrayLength;
953 m_TaskItems = NewArray;
954 }
955 else
956 return NULL;
957 }
958
959 return m_TaskItems + m_TaskItemCount++;
960 }
961
962 VOID FreeTaskItem(IN OUT PTASK_ITEM TaskItem)
963 {
964 WORD wIndex;
965
966 if (TaskItem == m_ActiveTaskItem)
967 m_ActiveTaskItem = NULL;
968
969 wIndex = (WORD) (TaskItem - m_TaskItems);
970 if (wIndex + 1 < m_TaskItemCount)
971 {
972 MoveMemory(TaskItem,
973 TaskItem + 1,
974 (m_TaskItemCount - wIndex - 1) * sizeof(*TaskItem));
975 }
976
977 m_TaskItemCount--;
978 }
979
980 VOID DeleteTaskItem(IN OUT PTASK_ITEM TaskItem)
981 {
982 if (!m_IsDestroying)
983 {
984 /* Delete the task button from the toolbar */
985 DeleteTaskItemButton(TaskItem);
986 }
987
988 /* Remove the task from it's group */
989 RemoveTaskFromTaskGroup(TaskItem);
990
991 /* Free the task item */
992 FreeTaskItem(TaskItem);
993 }
994
995 VOID CheckActivateTaskItem(IN OUT PTASK_ITEM TaskItem)
996 {
997 PTASK_ITEM CurrentTaskItem;
998 PTASK_GROUP TaskGroup = NULL;
999
1000 CurrentTaskItem = m_ActiveTaskItem;
1001
1002 if (TaskItem != NULL)
1003 TaskGroup = TaskItem->Group;
1004
1005 if (m_IsGroupingEnabled &&
1006 TaskGroup != NULL &&
1007 TaskGroup->IsCollapsed)
1008 {
1009 /* FIXME */
1010 return;
1011 }
1012
1013 if (CurrentTaskItem != NULL)
1014 {
1015 PTASK_GROUP CurrentTaskGroup;
1016
1017 if (CurrentTaskItem == TaskItem)
1018 return;
1019
1020 CurrentTaskGroup = CurrentTaskItem->Group;
1021
1022 if (m_IsGroupingEnabled &&
1023 CurrentTaskGroup != NULL &&
1024 CurrentTaskGroup->IsCollapsed)
1025 {
1026 if (CurrentTaskGroup == TaskGroup)
1027 return;
1028
1029 /* FIXME */
1030 }
1031 else
1032 {
1033 m_ActiveTaskItem = NULL;
1034 if (CurrentTaskItem->Index >= 0)
1035 {
1036 UpdateTaskItemButton(CurrentTaskItem);
1037 }
1038 }
1039 }
1040
1041 m_ActiveTaskItem = TaskItem;
1042
1043 if (TaskItem != NULL && TaskItem->Index >= 0)
1044 {
1045 UpdateTaskItemButton(TaskItem);
1046 }
1047 else if (TaskItem == NULL)
1048 {
1049 TRACE("Active TaskItem now NULL\n");
1050 }
1051 }
1052
1053 PTASK_ITEM FindTaskItemByIndex(IN INT Index)
1054 {
1055 PTASK_ITEM TaskItem, LastItem;
1056
1057 TaskItem = m_TaskItems;
1058 LastItem = TaskItem + m_TaskItemCount;
1059 while (TaskItem != LastItem)
1060 {
1061 if (TaskItem->Index == Index)
1062 return TaskItem;
1063
1064 TaskItem++;
1065 }
1066
1067 return NULL;
1068 }
1069
1070 PTASK_GROUP FindTaskGroupByIndex(IN INT Index)
1071 {
1072 PTASK_GROUP CurrentGroup;
1073
1074 CurrentGroup = m_TaskGroups;
1075 while (CurrentGroup != NULL)
1076 {
1077 if (CurrentGroup->Index == Index)
1078 break;
1079
1080 CurrentGroup = CurrentGroup->Next;
1081 }
1082
1083 return CurrentGroup;
1084 }
1085
1086 BOOL AddTask(IN HWND hWnd)
1087 {
1088 PTASK_ITEM TaskItem;
1089
1090 if (!::IsWindow(hWnd) || m_Tray->IsSpecialHWND(hWnd))
1091 return FALSE;
1092
1093 TaskItem = FindTaskItem(hWnd);
1094 if (TaskItem == NULL)
1095 {
1096 TRACE("Add window 0x%p\n", hWnd);
1097 TaskItem = AllocTaskItem();
1098 if (TaskItem != NULL)
1099 {
1100 ZeroMemory(TaskItem, sizeof(*TaskItem));
1101 TaskItem->hWnd = hWnd;
1102 TaskItem->Index = -1;
1103 TaskItem->Group = AddToTaskGroup(hWnd);
1104
1105 if (!m_IsDestroying)
1106 {
1107 AddTaskItemButton(TaskItem);
1108 }
1109 }
1110 }
1111
1112 return TaskItem != NULL;
1113 }
1114
1115 BOOL ActivateTaskItem(IN OUT PTASK_ITEM TaskItem OPTIONAL)
1116 {
1117 if (TaskItem != NULL)
1118 {
1119 TRACE("Activate window 0x%p on button %d\n", TaskItem->hWnd, TaskItem->Index);
1120 }
1121
1122 CheckActivateTaskItem(TaskItem);
1123
1124 return FALSE;
1125 }
1126
1127 BOOL ActivateTask(IN HWND hWnd)
1128 {
1129 PTASK_ITEM TaskItem;
1130
1131 if (!hWnd)
1132 {
1133 return ActivateTaskItem(NULL);
1134 }
1135
1136 TaskItem = FindTaskItem(hWnd);
1137 if (TaskItem == NULL)
1138 {
1139 TaskItem = FindOtherTaskItem(hWnd);
1140 }
1141
1142 if (TaskItem == NULL)
1143 {
1144 WARN("Activate window 0x%p, could not find task\n", hWnd);
1145 RefreshWindowList();
1146 }
1147
1148 return ActivateTaskItem(TaskItem);
1149 }
1150
1151 BOOL DeleteTask(IN HWND hWnd)
1152 {
1153 PTASK_ITEM TaskItem;
1154
1155 TaskItem = FindTaskItem(hWnd);
1156 if (TaskItem != NULL)
1157 {
1158 TRACE("Delete window 0x%p on button %d\n", hWnd, TaskItem->Index);
1159 DeleteTaskItem(TaskItem);
1160 return TRUE;
1161 }
1162 //else
1163 //TRACE("Failed to delete window 0x%p\n", hWnd);
1164
1165 return FALSE;
1166 }
1167
1168 VOID DeleteAllTasks()
1169 {
1170 PTASK_ITEM CurrentTask;
1171
1172 if (m_TaskItemCount > 0)
1173 {
1174 CurrentTask = m_TaskItems + m_TaskItemCount;
1175 do
1176 {
1177 DeleteTaskItem(--CurrentTask);
1178 } while (CurrentTask != m_TaskItems);
1179 }
1180 }
1181
1182 VOID FlashTaskItem(IN OUT PTASK_ITEM TaskItem)
1183 {
1184 TaskItem->RenderFlashed = 1;
1185 UpdateTaskItemButton(TaskItem);
1186 }
1187
1188 BOOL FlashTask(IN HWND hWnd)
1189 {
1190 PTASK_ITEM TaskItem;
1191
1192 TaskItem = FindTaskItem(hWnd);
1193 if (TaskItem != NULL)
1194 {
1195 TRACE("Flashing window 0x%p on button %d\n", hWnd, TaskItem->Index);
1196 FlashTaskItem(TaskItem);
1197 return TRUE;
1198 }
1199
1200 return FALSE;
1201 }
1202
1203 VOID RedrawTaskItem(IN OUT PTASK_ITEM TaskItem)
1204 {
1205 PTASK_GROUP TaskGroup;
1206
1207 TaskGroup = TaskItem->Group;
1208 if (m_IsGroupingEnabled && TaskGroup != NULL)
1209 {
1210 if (TaskGroup->IsCollapsed && TaskGroup->Index >= 0)
1211 {
1212 UpdateTaskGroupButton(TaskGroup);
1213 }
1214 else if (TaskItem->Index >= 0)
1215 {
1216 goto UpdateTaskItem;
1217 }
1218 }
1219 else if (TaskItem->Index >= 0)
1220 {
1221 UpdateTaskItem:
1222 TaskItem->RenderFlashed = 0;
1223 UpdateTaskItemButton(TaskItem);
1224 }
1225 }
1226
1227
1228 BOOL RedrawTask(IN HWND hWnd)
1229 {
1230 PTASK_ITEM TaskItem;
1231
1232 TaskItem = FindTaskItem(hWnd);
1233 if (TaskItem != NULL)
1234 {
1235 RedrawTaskItem(TaskItem);
1236 return TRUE;
1237 }
1238
1239 return FALSE;
1240 }
1241
1242 VOID UpdateButtonsSize(IN BOOL bRedrawDisabled)
1243 {
1244 RECT rcClient;
1245 UINT uiRows, uiMax, uiMin, uiBtnsPerLine, ui;
1246 LONG NewBtnSize;
1247 BOOL Horizontal;
1248
1249 /* Update the size of the image list if needed */
1250 int cx, cy;
1251 ImageList_GetIconSize(m_ImageList, &cx, &cy);
1252 if (cx != GetSystemMetrics(SM_CXSMICON) || cy != GetSystemMetrics(SM_CYSMICON))
1253 {
1254 ImageList_SetIconSize(m_ImageList, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
1255
1256 /* SetIconSize removes all icons so we have to reinsert them */
1257 PTASK_ITEM TaskItem = m_TaskItems;
1258 PTASK_ITEM LastTaskItem = m_TaskItems + m_TaskItemCount;
1259 while (TaskItem != LastTaskItem)
1260 {
1261 TaskItem->IconIndex = -1;
1262 UpdateTaskItemButton(TaskItem);
1263
1264 TaskItem++;
1265 }
1266 m_TaskBar.SetImageList(m_ImageList);
1267 }
1268
1269 if (GetClientRect(&rcClient) && !IsRectEmpty(&rcClient))
1270 {
1271 if (m_ButtonCount > 0)
1272 {
1273 Horizontal = m_Tray->IsHorizontal();
1274
1275 if (Horizontal)
1276 {
1277 TBMETRICS tbm = { 0 };
1278 tbm.cbSize = sizeof(tbm);
1279 tbm.dwMask = TBMF_BUTTONSPACING;
1280 m_TaskBar.GetMetrics(&tbm);
1281
1282 if (m_ButtonSize.cy + tbm.cyButtonSpacing != 0)
1283 uiRows = (rcClient.bottom + tbm.cyButtonSpacing) / (m_ButtonSize.cy + tbm.cyButtonSpacing);
1284 else
1285 uiRows = 1;
1286
1287 if (uiRows == 0)
1288 uiRows = 1;
1289
1290 uiBtnsPerLine = (m_ButtonCount + uiRows - 1) / uiRows;
1291 }
1292 else
1293 {
1294 uiBtnsPerLine = 1;
1295 uiRows = m_ButtonCount;
1296 }
1297
1298 if (!bRedrawDisabled)
1299 m_TaskBar.BeginUpdate();
1300
1301 /* We might need to update the button spacing */
1302 int cxButtonSpacing = m_TaskBar.UpdateTbButtonSpacing(
1303 Horizontal, m_Theme != NULL,
1304 uiRows, uiBtnsPerLine);
1305
1306 /* Determine the minimum and maximum width of a button */
1307 uiMin = GetSystemMetrics(SM_CXSIZE) + (2 * GetSystemMetrics(SM_CXEDGE));
1308 if (Horizontal)
1309 {
1310 uiMax = GetSystemMetrics(SM_CXMINIMIZED);
1311
1312 /* Calculate the ideal width and make sure it's within the allowed range */
1313 NewBtnSize = (rcClient.right - (uiBtnsPerLine * cxButtonSpacing)) / uiBtnsPerLine;
1314
1315 if (NewBtnSize < (LONG) uiMin)
1316 NewBtnSize = uiMin;
1317 if (NewBtnSize >(LONG)uiMax)
1318 NewBtnSize = uiMax;
1319
1320 /* Recalculate how many buttons actually fit into one line */
1321 uiBtnsPerLine = rcClient.right / (NewBtnSize + cxButtonSpacing);
1322 if (uiBtnsPerLine == 0)
1323 uiBtnsPerLine++;
1324 }
1325 else
1326 {
1327 NewBtnSize = uiMax = rcClient.right;
1328 }
1329
1330 m_ButtonSize.cx = NewBtnSize;
1331
1332 m_ButtonsPerLine = uiBtnsPerLine;
1333
1334 for (ui = 0; ui != m_ButtonCount; ui++)
1335 {
1336 TBBUTTONINFOW tbbi = { 0 };
1337 tbbi.cbSize = sizeof(tbbi);
1338 tbbi.dwMask = TBIF_BYINDEX | TBIF_SIZE | TBIF_STATE;
1339 tbbi.cx = (INT) NewBtnSize;
1340 tbbi.fsState = TBSTATE_ENABLED;
1341
1342 /* Check if we're updating a button that is the last one in the
1343 line. If so, we need to set the TBSTATE_WRAP flag! */
1344 if (Horizontal)
1345 {
1346 if ((ui + 1) % uiBtnsPerLine == 0)
1347 tbbi.fsState |= TBSTATE_WRAP;
1348 }
1349 else
1350 {
1351 tbbi.fsState |= TBSTATE_WRAP;
1352 }
1353
1354 if (m_ActiveTaskItem != NULL &&
1355 m_ActiveTaskItem->Index == (INT)ui)
1356 {
1357 tbbi.fsState |= TBSTATE_CHECKED;
1358 }
1359
1360 m_TaskBar.SetButtonInfo(ui, &tbbi);
1361 }
1362 }
1363 else
1364 {
1365 m_ButtonsPerLine = 0;
1366 m_ButtonSize.cx = 0;
1367 }
1368 }
1369
1370 // FIXME: This seems to be enabling redraws prematurely, but moving it to its right place doesn't work!
1371 m_TaskBar.EndUpdate();
1372 }
1373
1374 BOOL CALLBACK EnumWindowsProc(IN HWND hWnd)
1375 {
1376 /* Only show windows that still exist and are visible and none of explorer's
1377 special windows (such as the desktop or the tray window) */
1378 if (::IsWindow(hWnd) && ::IsWindowVisible(hWnd) &&
1379 !m_Tray->IsSpecialHWND(hWnd))
1380 {
1381 DWORD exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE);
1382 /* Don't list popup windows and also no tool windows */
1383 if ((::GetWindow(hWnd, GW_OWNER) == NULL || exStyle & WS_EX_APPWINDOW) &&
1384 !(exStyle & WS_EX_TOOLWINDOW))
1385 {
1386 TRACE("Adding task for %p...\n", hWnd);
1387 AddTask(hWnd);
1388 }
1389
1390 }
1391
1392 return TRUE;
1393 }
1394
1395 static BOOL CALLBACK s_EnumWindowsProc(IN HWND hWnd, IN LPARAM lParam)
1396 {
1397 CTaskSwitchWnd * This = (CTaskSwitchWnd *) lParam;
1398
1399 return This->EnumWindowsProc(hWnd);
1400 }
1401
1402 BOOL RefreshWindowList()
1403 {
1404 TRACE("Refreshing window list...\n");
1405 /* Add all windows to the toolbar */
1406 return EnumWindows(s_EnumWindowsProc, (LPARAM)this);
1407 }
1408
1409 LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1410 {
1411 TRACE("OmThemeChanged\n");
1412
1413 if (m_Theme)
1414 CloseThemeData(m_Theme);
1415
1416 if (IsThemeActive())
1417 m_Theme = OpenThemeData(m_hWnd, L"TaskBand");
1418 else
1419 m_Theme = NULL;
1420
1421 return TRUE;
1422 }
1423
1424 LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1425 {
1426 if (!m_TaskBar.Initialize(m_hWnd))
1427 return FALSE;
1428
1429 SetWindowTheme(m_TaskBar.m_hWnd, L"TaskBand", NULL);
1430
1431 m_ImageList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);
1432 m_TaskBar.SetImageList(m_ImageList);
1433
1434 /* Set proper spacing between buttons */
1435 m_TaskBar.UpdateTbButtonSpacing(m_Tray->IsHorizontal(), m_Theme != NULL);
1436
1437 /* Register the shell hook */
1438 m_ShellHookMsg = RegisterWindowMessageW(L"SHELLHOOK");
1439
1440 TRACE("ShellHookMsg got assigned number %d\n", m_ShellHookMsg);
1441
1442 RegisterShellHook(m_hWnd, 3); /* 1 if no NT! We're targeting NT so we don't care! */
1443
1444 RefreshWindowList();
1445
1446 /* Recalculate the button size */
1447 UpdateButtonsSize(FALSE);
1448
1449 #if DUMP_TASKS != 0
1450 SetTimer(hwnd, 1, 5000, NULL);
1451 #endif
1452 return TRUE;
1453 }
1454
1455 LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1456 {
1457 m_IsDestroying = TRUE;
1458
1459 /* Unregister the shell hook */
1460 RegisterShellHook(m_hWnd, FALSE);
1461
1462 CloseThemeData(m_Theme);
1463 DeleteAllTasks();
1464 return TRUE;
1465 }
1466
1467 BOOL HandleAppCommand(IN WPARAM wParam, IN LPARAM lParam)
1468 {
1469 BOOL Ret = FALSE;
1470
1471 switch (GET_APPCOMMAND_LPARAM(lParam))
1472 {
1473 case APPCOMMAND_BROWSER_SEARCH:
1474 Ret = SHFindFiles(NULL,
1475 NULL);
1476 break;
1477
1478 case APPCOMMAND_BROWSER_HOME:
1479 case APPCOMMAND_LAUNCH_MAIL:
1480 default:
1481 TRACE("Shell app command %d unhandled!\n", (INT) GET_APPCOMMAND_LPARAM(lParam));
1482 break;
1483 }
1484
1485 return Ret;
1486 }
1487
1488 LRESULT OnShellHook(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1489 {
1490 BOOL Ret = FALSE;
1491
1492 /* In case the shell hook wasn't registered properly, ignore WM_NULLs*/
1493 if (uMsg == 0)
1494 {
1495 bHandled = FALSE;
1496 return 0;
1497 }
1498
1499 TRACE("Received shell hook message: wParam=%08lx, lParam=%08lx\n", wParam, lParam);
1500
1501 switch ((INT) wParam)
1502 {
1503 case HSHELL_APPCOMMAND:
1504 Ret = HandleAppCommand(wParam, lParam);
1505 break;
1506
1507 case HSHELL_WINDOWCREATED:
1508 AddTask((HWND) lParam);
1509 break;
1510
1511 case HSHELL_WINDOWDESTROYED:
1512 /* The window still exists! Delay destroying it a bit */
1513 DeleteTask((HWND) lParam);
1514 break;
1515
1516 case HSHELL_RUDEAPPACTIVATED:
1517 case HSHELL_WINDOWACTIVATED:
1518 ActivateTask((HWND) lParam);
1519 break;
1520
1521 case HSHELL_FLASH:
1522 FlashTask((HWND) lParam);
1523 break;
1524
1525 case HSHELL_REDRAW:
1526 RedrawTask((HWND) lParam);
1527 break;
1528
1529 case HSHELL_TASKMAN:
1530 ::PostMessage(m_Tray->GetHWND(), TWM_OPENSTARTMENU, 0, 0);
1531 break;
1532
1533 case HSHELL_ACTIVATESHELLWINDOW:
1534 case HSHELL_LANGUAGE:
1535 case HSHELL_SYSMENU:
1536 case HSHELL_ENDTASK:
1537 case HSHELL_ACCESSIBILITYSTATE:
1538 case HSHELL_WINDOWREPLACED:
1539 case HSHELL_WINDOWREPLACING:
1540
1541 case HSHELL_GETMINRECT:
1542 default:
1543 {
1544 #if DEBUG_SHELL_HOOK
1545 int i, found;
1546 for (i = 0, found = 0; i != _countof(hshell_msg); i++)
1547 {
1548 if (hshell_msg[i].msg == (INT) wParam)
1549 {
1550 TRACE("Shell message %ws unhandled (lParam = 0x%p)!\n", hshell_msg[i].msg_name, lParam);
1551 found = 1;
1552 break;
1553 }
1554 }
1555 if (found)
1556 break;
1557 #endif
1558 TRACE("Shell message %d unhandled (lParam = 0x%p)!\n", (INT) wParam, lParam);
1559 break;
1560 }
1561 }
1562
1563 return Ret;
1564 }
1565
1566 VOID HandleTaskItemClick(IN OUT PTASK_ITEM TaskItem)
1567 {
1568 BOOL bIsMinimized;
1569 BOOL bIsActive;
1570
1571 if (::IsWindow(TaskItem->hWnd))
1572 {
1573 bIsMinimized = ::IsIconic(TaskItem->hWnd);
1574 bIsActive = (TaskItem == m_ActiveTaskItem);
1575
1576 TRACE("Active TaskItem %p, selected TaskItem %p\n", m_ActiveTaskItem, TaskItem);
1577 if (m_ActiveTaskItem)
1578 TRACE("Active TaskItem hWnd=%p, TaskItem hWnd %p\n", m_ActiveTaskItem->hWnd, TaskItem->hWnd);
1579
1580 TRACE("Valid button clicked. HWND=%p, IsMinimized=%s, IsActive=%s...\n",
1581 TaskItem->hWnd, bIsMinimized ? "Yes" : "No", bIsActive ? "Yes" : "No");
1582
1583 if (!bIsMinimized && bIsActive)
1584 {
1585 ::PostMessage(TaskItem->hWnd,
1586 WM_SYSCOMMAND,
1587 SC_MINIMIZE,
1588 0);
1589 TRACE("Valid button clicked. App window Minimized.\n");
1590 }
1591 else
1592 {
1593 if (bIsMinimized)
1594 {
1595 ::PostMessage(TaskItem->hWnd,
1596 WM_SYSCOMMAND,
1597 SC_RESTORE,
1598 0);
1599 TRACE("Valid button clicked. App window Restored.\n");
1600 }
1601
1602 SetForegroundWindow(TaskItem->hWnd);
1603 TRACE("Valid button clicked. App window Activated.\n");
1604 }
1605 }
1606 }
1607
1608 VOID HandleTaskGroupClick(IN OUT PTASK_GROUP TaskGroup)
1609 {
1610 /* TODO: Show task group menu */
1611 }
1612
1613 BOOL HandleButtonClick(IN WORD wIndex)
1614 {
1615 PTASK_ITEM TaskItem;
1616 PTASK_GROUP TaskGroup;
1617
1618 if (m_IsGroupingEnabled)
1619 {
1620 TaskGroup = FindTaskGroupByIndex((INT) wIndex);
1621 if (TaskGroup != NULL && TaskGroup->IsCollapsed)
1622 {
1623 HandleTaskGroupClick(TaskGroup);
1624 return TRUE;
1625 }
1626 }
1627
1628 TaskItem = FindTaskItemByIndex((INT) wIndex);
1629 if (TaskItem != NULL)
1630 {
1631 HandleTaskItemClick(TaskItem);
1632 return TRUE;
1633 }
1634
1635 return FALSE;
1636 }
1637
1638
1639 VOID HandleTaskItemRightClick(IN OUT PTASK_ITEM TaskItem)
1640 {
1641 POINT pt;
1642 GetCursorPos(&pt);
1643
1644 SetForegroundWindow(TaskItem->hWnd);
1645
1646 ActivateTask(TaskItem->hWnd);
1647
1648 ::SendMessageW(TaskItem->hWnd, WM_POPUPSYSTEMMENU, 0, MAKELPARAM(pt.x, pt.y));
1649 }
1650
1651 VOID HandleTaskGroupRightClick(IN OUT PTASK_GROUP TaskGroup)
1652 {
1653 /* TODO: Show task group right click menu */
1654 }
1655
1656 BOOL HandleButtonRightClick(IN WORD wIndex)
1657 {
1658 PTASK_ITEM TaskItem;
1659 PTASK_GROUP TaskGroup;
1660 if (m_IsGroupingEnabled)
1661 {
1662 TaskGroup = FindTaskGroupByIndex((INT) wIndex);
1663 if (TaskGroup != NULL && TaskGroup->IsCollapsed)
1664 {
1665 HandleTaskGroupRightClick(TaskGroup);
1666 return TRUE;
1667 }
1668 }
1669
1670 TaskItem = FindTaskItemByIndex((INT) wIndex);
1671
1672 if (TaskItem != NULL)
1673 {
1674 HandleTaskItemRightClick(TaskItem);
1675 return TRUE;
1676 }
1677
1678 return FALSE;
1679 }
1680
1681
1682 LRESULT HandleItemPaint(IN OUT NMTBCUSTOMDRAW *nmtbcd)
1683 {
1684 LRESULT Ret = CDRF_DODEFAULT;
1685 PTASK_GROUP TaskGroup;
1686 PTASK_ITEM TaskItem;
1687
1688 TaskItem = FindTaskItemByIndex((INT) nmtbcd->nmcd.dwItemSpec);
1689 TaskGroup = FindTaskGroupByIndex((INT) nmtbcd->nmcd.dwItemSpec);
1690 if (TaskGroup == NULL && TaskItem != NULL)
1691 {
1692 ASSERT(TaskItem != NULL);
1693
1694 if (TaskItem != NULL && ::IsWindow(TaskItem->hWnd))
1695 {
1696 /* Make the entire button flashing if necessary */
1697 if (nmtbcd->nmcd.uItemState & CDIS_MARKED)
1698 {
1699 Ret = TBCDRF_NOBACKGROUND;
1700 if (!m_Theme)
1701 {
1702 SelectObject(nmtbcd->nmcd.hdc, GetSysColorBrush(COLOR_HIGHLIGHT));
1703 Rectangle(nmtbcd->nmcd.hdc,
1704 nmtbcd->nmcd.rc.left,
1705 nmtbcd->nmcd.rc.top,
1706 nmtbcd->nmcd.rc.right,
1707 nmtbcd->nmcd.rc.bottom);
1708 }
1709 else
1710 {
1711 DrawThemeBackground(m_Theme, nmtbcd->nmcd.hdc, TDP_FLASHBUTTON, 0, &nmtbcd->nmcd.rc, 0);
1712 }
1713 nmtbcd->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
1714 return Ret;
1715 }
1716 }
1717 }
1718 else if (TaskGroup != NULL)
1719 {
1720 /* FIXME: Implement painting for task groups */
1721 }
1722 return Ret;
1723 }
1724
1725 LRESULT HandleToolbarNotification(IN const NMHDR *nmh)
1726 {
1727 LRESULT Ret = 0;
1728
1729 switch (nmh->code)
1730 {
1731 case NM_CUSTOMDRAW:
1732 {
1733 LPNMTBCUSTOMDRAW nmtbcd = (LPNMTBCUSTOMDRAW) nmh;
1734
1735 switch (nmtbcd->nmcd.dwDrawStage)
1736 {
1737
1738 case CDDS_ITEMPREPAINT:
1739 Ret = HandleItemPaint(nmtbcd);
1740 break;
1741
1742 case CDDS_PREPAINT:
1743 Ret = CDRF_NOTIFYITEMDRAW;
1744 break;
1745
1746 default:
1747 Ret = CDRF_DODEFAULT;
1748 break;
1749 }
1750 break;
1751 }
1752 }
1753
1754 return Ret;
1755 }
1756
1757 LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1758 {
1759 HDC hdc = (HDC) wParam;
1760
1761 if (!IsAppThemed())
1762 {
1763 bHandled = FALSE;
1764 return 0;
1765 }
1766
1767 RECT rect;
1768 GetClientRect(&rect);
1769 DrawThemeParentBackground(m_hWnd, hdc, &rect);
1770
1771 return TRUE;
1772 }
1773
1774 LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1775 {
1776 SIZE szClient;
1777
1778 szClient.cx = LOWORD(lParam);
1779 szClient.cy = HIWORD(lParam);
1780 if (m_TaskBar.m_hWnd != NULL)
1781 {
1782 m_TaskBar.SetWindowPos(NULL, 0, 0, szClient.cx, szClient.cy, SWP_NOZORDER);
1783
1784 UpdateButtonsSize(FALSE);
1785 }
1786 return TRUE;
1787 }
1788
1789 LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1790 {
1791 LRESULT Ret = TRUE;
1792 /* We want the tray window to be draggable everywhere, so make the control
1793 appear transparent */
1794 Ret = DefWindowProc(uMsg, wParam, lParam);
1795 if (Ret != HTVSCROLL && Ret != HTHSCROLL)
1796 Ret = HTTRANSPARENT;
1797 return Ret;
1798 }
1799
1800 LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1801 {
1802 LRESULT Ret = TRUE;
1803 if (lParam != 0 && (HWND) lParam == m_TaskBar.m_hWnd)
1804 {
1805 HandleButtonClick(LOWORD(wParam));
1806 }
1807 return Ret;
1808 }
1809
1810 LRESULT OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1811 {
1812 LRESULT Ret = TRUE;
1813 const NMHDR *nmh = (const NMHDR *) lParam;
1814
1815 if (nmh->hwndFrom == m_TaskBar.m_hWnd)
1816 {
1817 Ret = HandleToolbarNotification(nmh);
1818 }
1819 return Ret;
1820 }
1821
1822 LRESULT OnUpdateTaskbarPos(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1823 {
1824 /* Update the button spacing */
1825 m_TaskBar.UpdateTbButtonSpacing(m_Tray->IsHorizontal(), m_Theme != NULL);
1826 return TRUE;
1827 }
1828
1829 LRESULT OnTaskbarSettingsChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1830 {
1831 TaskbarSettings* newSettings = (TaskbarSettings*)lParam;
1832 if (newSettings->bGroupButtons != g_TaskbarSettings.bGroupButtons)
1833 {
1834 g_TaskbarSettings.bGroupButtons = newSettings->bGroupButtons;
1835 m_IsGroupingEnabled = g_TaskbarSettings.bGroupButtons;
1836
1837 /* Collapse or expand groups if necessary */
1838 RefreshWindowList();
1839 UpdateButtonsSize(FALSE);
1840 }
1841
1842 return 0;
1843 }
1844
1845 LRESULT OnContextMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1846 {
1847 LRESULT Ret = 0;
1848 INT_PTR iBtn = -1;
1849
1850 if (m_TaskBar.m_hWnd != NULL)
1851 {
1852 POINT pt;
1853
1854 pt.x = GET_X_LPARAM(lParam);
1855 pt.y = GET_Y_LPARAM(lParam);
1856
1857 ::ScreenToClient(m_TaskBar.m_hWnd, &pt);
1858
1859 iBtn = m_TaskBar.HitTest(&pt);
1860 if (iBtn >= 0)
1861 {
1862 HandleButtonRightClick(iBtn);
1863 }
1864 }
1865 if (iBtn < 0)
1866 {
1867 /* Not on a taskbar button, so forward message to tray */
1868 Ret = SendMessage(m_Tray->GetHWND(), uMsg, wParam, lParam);
1869 }
1870 return Ret;
1871 }
1872
1873 LRESULT OnKludgeItemRect(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1874 {
1875 PTASK_ITEM TaskItem = FindTaskItem((HWND) wParam);
1876 if (TaskItem)
1877 {
1878 RECT* prcMinRect = (RECT*) lParam;
1879 RECT rcItem, rcToolbar;
1880 m_TaskBar.GetItemRect(TaskItem->Index, &rcItem);
1881 m_TaskBar.GetWindowRect(&rcToolbar);
1882
1883 OffsetRect(&rcItem, rcToolbar.left, rcToolbar.top);
1884
1885 *prcMinRect = rcItem;
1886 return TRUE;
1887 }
1888 return FALSE;
1889 }
1890
1891 LRESULT OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1892 {
1893 return MA_NOACTIVATE;
1894 }
1895
1896 LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1897 {
1898 #if DUMP_TASKS != 0
1899 switch (wParam)
1900 {
1901 case 1:
1902 DumpTasks();
1903 break;
1904 }
1905 #endif
1906 return TRUE;
1907 }
1908
1909 LRESULT OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1910 {
1911 return m_TaskBar.SendMessageW(uMsg, wParam, lParam);
1912 }
1913
1914 LRESULT OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1915 {
1916 if (wParam == SPI_SETNONCLIENTMETRICS)
1917 {
1918 /* Don't update the font, this will be done when we get a WM_SETFONT from our parent */
1919 UpdateButtonsSize(FALSE);
1920 }
1921
1922 return 0;
1923 }
1924
1925 LRESULT OnCopyData(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1926 {
1927 PCOPYDATASTRUCT cpData = (PCOPYDATASTRUCT)lParam;
1928 if (cpData->dwData == m_uHardErrorMsg)
1929 {
1930 /* A hard error balloon message */
1931 PBALLOON_HARD_ERROR_DATA pData = (PBALLOON_HARD_ERROR_DATA)cpData->lpData;
1932 ERR("Got balloon data 0x%x, 0x%x, %S, %S!\n", pData->Status, pData->dwType, (WCHAR*)((ULONG_PTR)pData + pData->TitleOffset), (WCHAR*)((ULONG_PTR)pData + pData->MessageOffset));
1933 if (pData->cbHeaderSize == sizeof(BALLOON_HARD_ERROR_DATA))
1934 m_HardErrorThread.StartThread(pData);
1935 return TRUE;
1936 }
1937
1938 return FALSE;
1939 }
1940
1941 HRESULT Initialize(IN HWND hWndParent, IN OUT ITrayWindow *tray)
1942 {
1943 m_Tray = tray;
1944 m_IsGroupingEnabled = g_TaskbarSettings.bGroupButtons;
1945 Create(hWndParent, 0, szRunningApps, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP);
1946 if (!m_hWnd)
1947 return E_FAIL;
1948 return S_OK;
1949 }
1950
1951 HRESULT WINAPI GetWindow(HWND* phwnd)
1952 {
1953 if (!phwnd)
1954 return E_INVALIDARG;
1955 *phwnd = m_hWnd;
1956 return S_OK;
1957 }
1958
1959 HRESULT WINAPI ContextSensitiveHelp(BOOL fEnterMode)
1960 {
1961 return E_NOTIMPL;
1962 }
1963
1964 DECLARE_WND_CLASS_EX(szTaskSwitchWndClass, CS_DBLCLKS, COLOR_3DFACE)
1965
1966 BEGIN_MSG_MAP(CTaskSwitchWnd)
1967 MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
1968 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
1969 MESSAGE_HANDLER(WM_SIZE, OnSize)
1970 MESSAGE_HANDLER(WM_CREATE, OnCreate)
1971 MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
1972 MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
1973 MESSAGE_HANDLER(WM_COMMAND, OnCommand)
1974 MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
1975 MESSAGE_HANDLER(TSWM_UPDATETASKBARPOS, OnUpdateTaskbarPos)
1976 MESSAGE_HANDLER(TWM_SETTINGSCHANGED, OnTaskbarSettingsChanged)
1977 MESSAGE_HANDLER(WM_CONTEXTMENU, OnContextMenu)
1978 MESSAGE_HANDLER(WM_TIMER, OnTimer)
1979 MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
1980 MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChanged)
1981 MESSAGE_HANDLER(m_ShellHookMsg, OnShellHook)
1982 MESSAGE_HANDLER(WM_MOUSEACTIVATE, OnMouseActivate)
1983 MESSAGE_HANDLER(WM_KLUDGEMINRECT, OnKludgeItemRect)
1984 MESSAGE_HANDLER(WM_COPYDATA, OnCopyData)
1985 END_MSG_MAP()
1986
1987 DECLARE_NOT_AGGREGATABLE(CTaskSwitchWnd)
1988
1989 DECLARE_PROTECT_FINAL_CONSTRUCT()
1990 BEGIN_COM_MAP(CTaskSwitchWnd)
1991 COM_INTERFACE_ENTRY_IID(IID_IOleWindow, IOleWindow)
1992 END_COM_MAP()
1993 };
1994
1995 HRESULT CTaskSwitchWnd_CreateInstance(IN HWND hWndParent, IN OUT ITrayWindow *Tray, REFIID riid, void **ppv)
1996 {
1997 return ShellObjectCreatorInit<CTaskSwitchWnd>(hWndParent, Tray, riid, ppv);
1998 }