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