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