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