[EXPLORER] -Use WM_POPUPSYSTEMMENU to open the system menu of a window. CORE-13400
[reactos.git] / reactos / 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 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 int cx = GetSystemMetrics(SM_CXMINIMIZED);
1146 int cy = m_ButtonSize.cy = GetSystemMetrics(SM_CYSIZE);
1147 m_TaskBar.SetButtonSize(cx, cy);
1148
1149 if (GetClientRect(&rcClient) && !IsRectEmpty(&rcClient))
1150 {
1151 if (m_ButtonCount > 0)
1152 {
1153 Horizontal = m_Tray->IsHorizontal();
1154
1155 if (Horizontal)
1156 {
1157 TBMETRICS tbm = { 0 };
1158 tbm.cbSize = sizeof(tbm);
1159 tbm.dwMask = TBMF_BUTTONSPACING;
1160 m_TaskBar.GetMetrics(&tbm);
1161
1162 if (m_ButtonSize.cy + tbm.cyButtonSpacing != 0)
1163 uiRows = (rcClient.bottom + tbm.cyButtonSpacing) / (m_ButtonSize.cy + tbm.cyButtonSpacing);
1164 else
1165 uiRows = 1;
1166
1167 if (uiRows == 0)
1168 uiRows = 1;
1169
1170 uiBtnsPerLine = (m_ButtonCount + uiRows - 1) / uiRows;
1171 }
1172 else
1173 {
1174 uiBtnsPerLine = 1;
1175 uiRows = m_ButtonCount;
1176 }
1177
1178 if (!bRedrawDisabled)
1179 m_TaskBar.BeginUpdate();
1180
1181 /* We might need to update the button spacing */
1182 int cxButtonSpacing = m_TaskBar.UpdateTbButtonSpacing(
1183 Horizontal, m_Theme != NULL,
1184 uiRows, uiBtnsPerLine);
1185
1186 /* Determine the minimum and maximum width of a button */
1187 uiMin = GetSystemMetrics(SM_CXSIZE) + (2 * GetSystemMetrics(SM_CXEDGE));
1188 if (Horizontal)
1189 {
1190 uiMax = GetSystemMetrics(SM_CXMINIMIZED);
1191
1192 /* Calculate the ideal width and make sure it's within the allowed range */
1193 NewBtnSize = (rcClient.right - (uiBtnsPerLine * cxButtonSpacing)) / uiBtnsPerLine;
1194
1195 if (NewBtnSize < (LONG) uiMin)
1196 NewBtnSize = uiMin;
1197 if (NewBtnSize >(LONG)uiMax)
1198 NewBtnSize = uiMax;
1199
1200 /* Recalculate how many buttons actually fit into one line */
1201 uiBtnsPerLine = rcClient.right / (NewBtnSize + cxButtonSpacing);
1202 if (uiBtnsPerLine == 0)
1203 uiBtnsPerLine++;
1204 }
1205 else
1206 {
1207 NewBtnSize = uiMax = rcClient.right;
1208 }
1209
1210 m_ButtonSize.cx = NewBtnSize;
1211
1212 m_ButtonsPerLine = uiBtnsPerLine;
1213
1214 for (ui = 0; ui != m_ButtonCount; ui++)
1215 {
1216 TBBUTTONINFOW tbbi = { 0 };
1217 tbbi.cbSize = sizeof(tbbi);
1218 tbbi.dwMask = TBIF_BYINDEX | TBIF_SIZE | TBIF_STATE;
1219 tbbi.cx = (INT) NewBtnSize;
1220 tbbi.fsState = TBSTATE_ENABLED;
1221
1222 /* Check if we're updating a button that is the last one in the
1223 line. If so, we need to set the TBSTATE_WRAP flag! */
1224 if (Horizontal)
1225 {
1226 if ((ui + 1) % uiBtnsPerLine == 0)
1227 tbbi.fsState |= TBSTATE_WRAP;
1228 }
1229 else
1230 {
1231 tbbi.fsState |= TBSTATE_WRAP;
1232 }
1233
1234 if (m_ActiveTaskItem != NULL &&
1235 m_ActiveTaskItem->Index == (INT)ui)
1236 {
1237 tbbi.fsState |= TBSTATE_CHECKED;
1238 }
1239
1240 m_TaskBar.SetButtonInfo(ui, &tbbi);
1241 }
1242 }
1243 else
1244 {
1245 m_ButtonsPerLine = 0;
1246 m_ButtonSize.cx = 0;
1247 }
1248 }
1249
1250 // FIXME: This seems to be enabling redraws prematurely, but moving it to its right place doesn't work!
1251 m_TaskBar.EndUpdate();
1252 }
1253
1254 BOOL CALLBACK EnumWindowsProc(IN HWND hWnd)
1255 {
1256 /* Only show windows that still exist and are visible and none of explorer's
1257 special windows (such as the desktop or the tray window) */
1258 if (::IsWindow(hWnd) && ::IsWindowVisible(hWnd) &&
1259 !m_Tray->IsSpecialHWND(hWnd))
1260 {
1261 DWORD exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE);
1262 /* Don't list popup windows and also no tool windows */
1263 if ((::GetWindow(hWnd, GW_OWNER) == NULL || exStyle & WS_EX_APPWINDOW) &&
1264 !(exStyle & WS_EX_TOOLWINDOW))
1265 {
1266 TRACE("Adding task for %p...\n", hWnd);
1267 AddTask(hWnd);
1268 }
1269
1270 }
1271
1272 return TRUE;
1273 }
1274
1275 static BOOL CALLBACK s_EnumWindowsProc(IN HWND hWnd, IN LPARAM lParam)
1276 {
1277 CTaskSwitchWnd * This = (CTaskSwitchWnd *) lParam;
1278
1279 return This->EnumWindowsProc(hWnd);
1280 }
1281
1282 BOOL RefreshWindowList()
1283 {
1284 TRACE("Refreshing window list...\n");
1285 /* Add all windows to the toolbar */
1286 return EnumWindows(s_EnumWindowsProc, (LPARAM)this);
1287 }
1288
1289 LRESULT OnThemeChanged()
1290 {
1291 TRACE("OmThemeChanged\n");
1292
1293 if (m_Theme)
1294 CloseThemeData(m_Theme);
1295
1296 if (IsThemeActive())
1297 m_Theme = OpenThemeData(m_hWnd, L"TaskBand");
1298 else
1299 m_Theme = NULL;
1300
1301 return TRUE;
1302 }
1303
1304 LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1305 {
1306 return OnThemeChanged();
1307 }
1308
1309 LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1310 {
1311 if (!m_TaskBar.Initialize(m_hWnd))
1312 return FALSE;
1313
1314 SetWindowTheme(m_TaskBar.m_hWnd, L"TaskBand", NULL);
1315 OnThemeChanged();
1316
1317 m_ImageList = ImageList_Create(16, 16, ILC_COLOR32 | ILC_MASK, 0, 1000);
1318 m_TaskBar.SetImageList(m_ImageList);
1319
1320 /* Set proper spacing between buttons */
1321 m_TaskBar.UpdateTbButtonSpacing(m_Tray->IsHorizontal(), m_Theme != NULL);
1322
1323 /* Register the shell hook */
1324 m_ShellHookMsg = RegisterWindowMessageW(L"SHELLHOOK");
1325
1326 TRACE("ShellHookMsg got assigned number %d\n", m_ShellHookMsg);
1327
1328 RegisterShellHook(m_hWnd, 3); /* 1 if no NT! We're targeting NT so we don't care! */
1329
1330 RefreshWindowList();
1331
1332 /* Recalculate the button size */
1333 UpdateButtonsSize(FALSE);
1334
1335 #if DUMP_TASKS != 0
1336 SetTimer(hwnd, 1, 5000, NULL);
1337 #endif
1338 return TRUE;
1339 }
1340
1341 LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1342 {
1343 m_IsDestroying = TRUE;
1344
1345 /* Unregister the shell hook */
1346 RegisterShellHook(m_hWnd, FALSE);
1347
1348 CloseThemeData(m_Theme);
1349 DeleteAllTasks();
1350 return TRUE;
1351 }
1352
1353 BOOL HandleAppCommand(IN WPARAM wParam, IN LPARAM lParam)
1354 {
1355 BOOL Ret = FALSE;
1356
1357 switch (GET_APPCOMMAND_LPARAM(lParam))
1358 {
1359 case APPCOMMAND_BROWSER_SEARCH:
1360 Ret = SHFindFiles(NULL,
1361 NULL);
1362 break;
1363
1364 case APPCOMMAND_BROWSER_HOME:
1365 case APPCOMMAND_LAUNCH_MAIL:
1366 default:
1367 TRACE("Shell app command %d unhandled!\n", (INT) GET_APPCOMMAND_LPARAM(lParam));
1368 break;
1369 }
1370
1371 return Ret;
1372 }
1373
1374 LRESULT HandleShellHookMsg(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1375 {
1376 BOOL Ret = FALSE;
1377
1378 /* In case the shell hook wasn't registered properly, ignore WM_NULLs*/
1379 if (uMsg == 0)
1380 {
1381 bHandled = FALSE;
1382 return 0;
1383 }
1384
1385 TRACE("Received shell hook message: wParam=%08lx, lParam=%08lx\n", wParam, lParam);
1386
1387 switch ((INT) wParam)
1388 {
1389 case HSHELL_APPCOMMAND:
1390 Ret = HandleAppCommand(wParam, lParam);
1391 break;
1392
1393 case HSHELL_WINDOWCREATED:
1394 AddTask((HWND) lParam);
1395 break;
1396
1397 case HSHELL_WINDOWDESTROYED:
1398 /* The window still exists! Delay destroying it a bit */
1399 DeleteTask((HWND) lParam);
1400 break;
1401
1402 case HSHELL_RUDEAPPACTIVATED:
1403 case HSHELL_WINDOWACTIVATED:
1404 ActivateTask((HWND) lParam);
1405 break;
1406
1407 case HSHELL_FLASH:
1408 FlashTask((HWND) lParam);
1409 break;
1410
1411 case HSHELL_REDRAW:
1412 RedrawTask((HWND) lParam);
1413 break;
1414
1415 case HSHELL_TASKMAN:
1416 ::PostMessage(m_Tray->GetHWND(), TWM_OPENSTARTMENU, 0, 0);
1417 break;
1418
1419 case HSHELL_ACTIVATESHELLWINDOW:
1420 case HSHELL_LANGUAGE:
1421 case HSHELL_SYSMENU:
1422 case HSHELL_ENDTASK:
1423 case HSHELL_ACCESSIBILITYSTATE:
1424 case HSHELL_WINDOWREPLACED:
1425 case HSHELL_WINDOWREPLACING:
1426
1427 case HSHELL_GETMINRECT:
1428 default:
1429 {
1430 #if DEBUG_SHELL_HOOK
1431 int i, found;
1432 for (i = 0, found = 0; i != _countof(hshell_msg); i++)
1433 {
1434 if (hshell_msg[i].msg == (INT) wParam)
1435 {
1436 TRACE("Shell message %ws unhandled (lParam = 0x%p)!\n", hshell_msg[i].msg_name, lParam);
1437 found = 1;
1438 break;
1439 }
1440 }
1441 if (found)
1442 break;
1443 #endif
1444 TRACE("Shell message %d unhandled (lParam = 0x%p)!\n", (INT) wParam, lParam);
1445 break;
1446 }
1447 }
1448
1449 return Ret;
1450 }
1451
1452 VOID EnableGrouping(IN BOOL bEnable)
1453 {
1454 m_IsGroupingEnabled = bEnable;
1455
1456 /* Collapse or expand groups if necessary */
1457 UpdateButtonsSize(FALSE);
1458 }
1459
1460 VOID HandleTaskItemClick(IN OUT PTASK_ITEM TaskItem)
1461 {
1462 BOOL bIsMinimized;
1463 BOOL bIsActive;
1464
1465 if (::IsWindow(TaskItem->hWnd))
1466 {
1467 bIsMinimized = ::IsIconic(TaskItem->hWnd);
1468 bIsActive = (TaskItem == m_ActiveTaskItem);
1469
1470 TRACE("Active TaskItem %p, selected TaskItem %p\n", m_ActiveTaskItem, TaskItem);
1471 if (m_ActiveTaskItem)
1472 TRACE("Active TaskItem hWnd=%p, TaskItem hWnd %p\n", m_ActiveTaskItem->hWnd, TaskItem->hWnd);
1473
1474 TRACE("Valid button clicked. HWND=%p, IsMinimized=%s, IsActive=%s...\n",
1475 TaskItem->hWnd, bIsMinimized ? "Yes" : "No", bIsActive ? "Yes" : "No");
1476
1477 if (!bIsMinimized && bIsActive)
1478 {
1479 ::PostMessage(TaskItem->hWnd,
1480 WM_SYSCOMMAND,
1481 SC_MINIMIZE,
1482 0);
1483 TRACE("Valid button clicked. App window Minimized.\n");
1484 }
1485 else
1486 {
1487 if (bIsMinimized)
1488 {
1489 ::PostMessage(TaskItem->hWnd,
1490 WM_SYSCOMMAND,
1491 SC_RESTORE,
1492 0);
1493 TRACE("Valid button clicked. App window Restored.\n");
1494 }
1495
1496 SetForegroundWindow(TaskItem->hWnd);
1497 TRACE("Valid button clicked. App window Activated.\n");
1498 }
1499 }
1500 }
1501
1502 VOID HandleTaskGroupClick(IN OUT PTASK_GROUP TaskGroup)
1503 {
1504 /* TODO: Show task group menu */
1505 }
1506
1507 BOOL HandleButtonClick(IN WORD wIndex)
1508 {
1509 PTASK_ITEM TaskItem;
1510 PTASK_GROUP TaskGroup;
1511
1512 if (m_IsGroupingEnabled)
1513 {
1514 TaskGroup = FindTaskGroupByIndex((INT) wIndex);
1515 if (TaskGroup != NULL && TaskGroup->IsCollapsed)
1516 {
1517 HandleTaskGroupClick(TaskGroup);
1518 return TRUE;
1519 }
1520 }
1521
1522 TaskItem = FindTaskItemByIndex((INT) wIndex);
1523 if (TaskItem != NULL)
1524 {
1525 HandleTaskItemClick(TaskItem);
1526 return TRUE;
1527 }
1528
1529 return FALSE;
1530 }
1531
1532
1533 VOID HandleTaskItemRightClick(IN OUT PTASK_ITEM TaskItem)
1534 {
1535 POINT pt;
1536 GetCursorPos(&pt);
1537
1538 SetForegroundWindow(TaskItem->hWnd);
1539
1540 ActivateTask(TaskItem->hWnd);
1541
1542 ::SendMessageW(TaskItem->hWnd, WM_POPUPSYSTEMMENU, 0, MAKELPARAM(pt.x, pt.y));
1543 }
1544
1545 VOID HandleTaskGroupRightClick(IN OUT PTASK_GROUP TaskGroup)
1546 {
1547 /* TODO: Show task group right click menu */
1548 }
1549
1550 BOOL HandleButtonRightClick(IN WORD wIndex)
1551 {
1552 PTASK_ITEM TaskItem;
1553 PTASK_GROUP TaskGroup;
1554 if (m_IsGroupingEnabled)
1555 {
1556 TaskGroup = FindTaskGroupByIndex((INT) wIndex);
1557 if (TaskGroup != NULL && TaskGroup->IsCollapsed)
1558 {
1559 HandleTaskGroupRightClick(TaskGroup);
1560 return TRUE;
1561 }
1562 }
1563
1564 TaskItem = FindTaskItemByIndex((INT) wIndex);
1565
1566 if (TaskItem != NULL)
1567 {
1568 HandleTaskItemRightClick(TaskItem);
1569 return TRUE;
1570 }
1571
1572 return FALSE;
1573 }
1574
1575
1576 LRESULT HandleItemPaint(IN OUT NMTBCUSTOMDRAW *nmtbcd)
1577 {
1578 LRESULT Ret = CDRF_DODEFAULT;
1579 PTASK_GROUP TaskGroup;
1580 PTASK_ITEM TaskItem;
1581
1582 TaskItem = FindTaskItemByIndex((INT) nmtbcd->nmcd.dwItemSpec);
1583 TaskGroup = FindTaskGroupByIndex((INT) nmtbcd->nmcd.dwItemSpec);
1584 if (TaskGroup == NULL && TaskItem != NULL)
1585 {
1586 ASSERT(TaskItem != NULL);
1587
1588 if (TaskItem != NULL && ::IsWindow(TaskItem->hWnd))
1589 {
1590 /* Make the entire button flashing if necessary */
1591 if (nmtbcd->nmcd.uItemState & CDIS_MARKED)
1592 {
1593 Ret = TBCDRF_NOBACKGROUND;
1594 if (!m_Theme)
1595 {
1596 SelectObject(nmtbcd->nmcd.hdc, GetSysColorBrush(COLOR_HIGHLIGHT));
1597 Rectangle(nmtbcd->nmcd.hdc,
1598 nmtbcd->nmcd.rc.left,
1599 nmtbcd->nmcd.rc.top,
1600 nmtbcd->nmcd.rc.right,
1601 nmtbcd->nmcd.rc.bottom);
1602 }
1603 else
1604 {
1605 DrawThemeBackground(m_Theme, nmtbcd->nmcd.hdc, TDP_FLASHBUTTON, 0, &nmtbcd->nmcd.rc, 0);
1606 }
1607 nmtbcd->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
1608 return Ret;
1609 }
1610 }
1611 }
1612 else if (TaskGroup != NULL)
1613 {
1614 /* FIXME: Implement painting for task groups */
1615 }
1616 return Ret;
1617 }
1618
1619 LRESULT HandleToolbarNotification(IN const NMHDR *nmh)
1620 {
1621 LRESULT Ret = 0;
1622
1623 switch (nmh->code)
1624 {
1625 case NM_CUSTOMDRAW:
1626 {
1627 LPNMTBCUSTOMDRAW nmtbcd = (LPNMTBCUSTOMDRAW) nmh;
1628
1629 switch (nmtbcd->nmcd.dwDrawStage)
1630 {
1631
1632 case CDDS_ITEMPREPAINT:
1633 Ret = HandleItemPaint(nmtbcd);
1634 break;
1635
1636 case CDDS_PREPAINT:
1637 Ret = CDRF_NOTIFYITEMDRAW;
1638 break;
1639
1640 default:
1641 Ret = CDRF_DODEFAULT;
1642 break;
1643 }
1644 break;
1645 }
1646 }
1647
1648 return Ret;
1649 }
1650
1651 LRESULT DrawBackground(HDC hdc)
1652 {
1653 RECT rect;
1654
1655 GetClientRect(&rect);
1656 DrawThemeParentBackground(m_hWnd, hdc, &rect);
1657
1658 return TRUE;
1659 }
1660
1661 LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1662 {
1663 HDC hdc = (HDC) wParam;
1664
1665 if (!IsAppThemed())
1666 {
1667 bHandled = FALSE;
1668 return 0;
1669 }
1670
1671 return DrawBackground(hdc);
1672 }
1673
1674 LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1675 {
1676 SIZE szClient;
1677
1678 szClient.cx = LOWORD(lParam);
1679 szClient.cy = HIWORD(lParam);
1680 if (m_TaskBar.m_hWnd != NULL)
1681 {
1682 m_TaskBar.SetWindowPos(NULL, 0, 0, szClient.cx, szClient.cy, SWP_NOZORDER);
1683
1684 UpdateButtonsSize(FALSE);
1685 }
1686 return TRUE;
1687 }
1688
1689 LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1690 {
1691 LRESULT Ret = TRUE;
1692 /* We want the tray window to be draggable everywhere, so make the control
1693 appear transparent */
1694 Ret = DefWindowProc(uMsg, wParam, lParam);
1695 if (Ret != HTVSCROLL && Ret != HTHSCROLL)
1696 Ret = HTTRANSPARENT;
1697 return Ret;
1698 }
1699
1700 LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1701 {
1702 LRESULT Ret = TRUE;
1703 if (lParam != 0 && (HWND) lParam == m_TaskBar.m_hWnd)
1704 {
1705 HandleButtonClick(LOWORD(wParam));
1706 }
1707 return Ret;
1708 }
1709
1710 LRESULT OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1711 {
1712 LRESULT Ret = TRUE;
1713 const NMHDR *nmh = (const NMHDR *) lParam;
1714
1715 if (nmh->hwndFrom == m_TaskBar.m_hWnd)
1716 {
1717 Ret = HandleToolbarNotification(nmh);
1718 }
1719 return Ret;
1720 }
1721
1722 LRESULT OnEnableGrouping(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1723 {
1724 LRESULT Ret = m_IsGroupingEnabled;
1725 if ((BOOL)wParam != m_IsGroupingEnabled)
1726 {
1727 EnableGrouping((BOOL) wParam);
1728 }
1729 return Ret;
1730 }
1731
1732 LRESULT OnUpdateTaskbarPos(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1733 {
1734 /* Update the button spacing */
1735 m_TaskBar.UpdateTbButtonSpacing(m_Tray->IsHorizontal(), m_Theme != NULL);
1736 return TRUE;
1737 }
1738
1739 LRESULT OnContextMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1740 {
1741 LRESULT Ret = 0;
1742 INT_PTR iBtn = -1;
1743
1744 if (m_TaskBar.m_hWnd != NULL)
1745 {
1746 POINT pt;
1747
1748 pt.x = GET_X_LPARAM(lParam);
1749 pt.y = GET_Y_LPARAM(lParam);
1750
1751 ::ScreenToClient(m_TaskBar.m_hWnd, &pt);
1752
1753 iBtn = m_TaskBar.HitTest(&pt);
1754 if (iBtn >= 0)
1755 {
1756 HandleButtonRightClick(iBtn);
1757 }
1758 }
1759 if (iBtn < 0)
1760 {
1761 /* Not on a taskbar button, so forward message to tray */
1762 Ret = SendMessage(m_Tray->GetHWND(), uMsg, wParam, lParam);
1763 }
1764 return Ret;
1765 }
1766
1767 LRESULT OnKludgeItemRect(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1768 {
1769 PTASK_ITEM TaskItem = FindTaskItem((HWND) wParam);
1770 if (TaskItem)
1771 {
1772 RECT* prcMinRect = (RECT*) lParam;
1773 RECT rcItem, rcToolbar;
1774 m_TaskBar.GetItemRect(TaskItem->Index, &rcItem);
1775 m_TaskBar.GetWindowRect(&rcToolbar);
1776
1777 OffsetRect(&rcItem, rcToolbar.left, rcToolbar.top);
1778
1779 *prcMinRect = rcItem;
1780 return TRUE;
1781 }
1782 return FALSE;
1783 }
1784
1785 LRESULT OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1786 {
1787 return MA_NOACTIVATE;
1788 }
1789
1790 LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1791 {
1792 #if DUMP_TASKS != 0
1793 switch (wParam)
1794 {
1795 case 1:
1796 DumpTasks();
1797 break;
1798 }
1799 #endif
1800 return TRUE;
1801 }
1802
1803 LRESULT OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1804 {
1805 return m_TaskBar.SendMessageW(uMsg, wParam, lParam);
1806 }
1807
1808 LRESULT OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1809 {
1810 if (wParam == SPI_SETNONCLIENTMETRICS)
1811 {
1812 /* Don't update the font, this will be done when we get a WM_SETFONT from our parent */
1813 UpdateButtonsSize(FALSE);
1814 }
1815
1816 return 0;
1817 }
1818
1819 DECLARE_WND_CLASS_EX(szTaskSwitchWndClass, CS_DBLCLKS, COLOR_3DFACE)
1820
1821 BEGIN_MSG_MAP(CTaskSwitchWnd)
1822 MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
1823 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
1824 MESSAGE_HANDLER(WM_SIZE, OnSize)
1825 MESSAGE_HANDLER(WM_CREATE, OnCreate)
1826 MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
1827 MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
1828 MESSAGE_HANDLER(WM_COMMAND, OnCommand)
1829 MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
1830 MESSAGE_HANDLER(TSWM_ENABLEGROUPING, OnEnableGrouping)
1831 MESSAGE_HANDLER(TSWM_UPDATETASKBARPOS, OnUpdateTaskbarPos)
1832 MESSAGE_HANDLER(WM_CONTEXTMENU, OnContextMenu)
1833 MESSAGE_HANDLER(WM_TIMER, OnTimer)
1834 MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
1835 MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChanged)
1836 MESSAGE_HANDLER(m_ShellHookMsg, HandleShellHookMsg)
1837 MESSAGE_HANDLER(WM_MOUSEACTIVATE, OnMouseActivate)
1838 MESSAGE_HANDLER(WM_KLUDGEMINRECT, OnKludgeItemRect)
1839 END_MSG_MAP()
1840
1841 HWND _Init(IN HWND hWndParent, IN OUT ITrayWindow *tray)
1842 {
1843 m_Tray = tray;
1844 m_IsGroupingEnabled = TRUE; /* FIXME */
1845 return Create(hWndParent, 0, szRunningApps, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP);
1846 }
1847 };
1848
1849 HWND
1850 CreateTaskSwitchWnd(IN HWND hWndParent, IN OUT ITrayWindow *Tray)
1851 {
1852 CTaskSwitchWnd * instance;
1853
1854 // TODO: Destroy after the window is destroyed
1855 instance = new CTaskSwitchWnd();
1856
1857 return instance->_Init(hWndParent, Tray);
1858 }