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