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