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