118b76d028688c8ec1660995f558e9f75113ad41
[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 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(CToolbar::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>(LoadImageW(NULL, MAKEINTRESOURCEW(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>(LoadImageW(NULL, MAKEINTRESOURCEW(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, sizeof(*TaskItem));
981 TaskItem->hWnd = hWnd;
982 TaskItem->Index = -1;
983 TaskItem->Group = AddToTaskGroup(hWnd);
984
985 if (!m_IsDestroying)
986 {
987 AddTaskItemButton(TaskItem);
988 }
989 }
990 }
991
992 return TaskItem != NULL;
993 }
994
995 BOOL ActivateTaskItem(IN OUT PTASK_ITEM TaskItem OPTIONAL)
996 {
997 if (TaskItem != NULL)
998 {
999 TRACE("Activate window 0x%p on button %d\n", TaskItem->hWnd, TaskItem->Index);
1000 }
1001
1002 CheckActivateTaskItem(TaskItem);
1003
1004 return FALSE;
1005 }
1006
1007 BOOL ActivateTask(IN HWND hWnd)
1008 {
1009 PTASK_ITEM TaskItem;
1010
1011 if (!hWnd)
1012 {
1013 return ActivateTaskItem(NULL);
1014 }
1015
1016 TaskItem = FindTaskItem(hWnd);
1017 if (TaskItem == NULL)
1018 {
1019 TaskItem = FindOtherTaskItem(hWnd);
1020 }
1021
1022 if (TaskItem == NULL)
1023 {
1024 WARN("Activate window 0x%p, could not find task\n", hWnd);
1025 RefreshWindowList();
1026 }
1027
1028 return ActivateTaskItem(TaskItem);
1029 }
1030
1031 BOOL DeleteTask(IN HWND hWnd)
1032 {
1033 PTASK_ITEM TaskItem;
1034
1035 TaskItem = FindTaskItem(hWnd);
1036 if (TaskItem != NULL)
1037 {
1038 TRACE("Delete window 0x%p on button %d\n", hWnd, TaskItem->Index);
1039 DeleteTaskItem(TaskItem);
1040 return TRUE;
1041 }
1042 //else
1043 //TRACE("Failed to delete window 0x%p\n", hWnd);
1044
1045 return FALSE;
1046 }
1047
1048 VOID DeleteAllTasks()
1049 {
1050 PTASK_ITEM CurrentTask;
1051
1052 if (m_TaskItemCount > 0)
1053 {
1054 CurrentTask = m_TaskItems + m_TaskItemCount;
1055 do
1056 {
1057 DeleteTaskItem(--CurrentTask);
1058 } while (CurrentTask != m_TaskItems);
1059 }
1060 }
1061
1062 VOID FlashTaskItem(IN OUT PTASK_ITEM TaskItem)
1063 {
1064 TaskItem->RenderFlashed = 1;
1065 UpdateTaskItemButton(TaskItem);
1066 }
1067
1068 BOOL FlashTask(IN HWND hWnd)
1069 {
1070 PTASK_ITEM TaskItem;
1071
1072 TaskItem = FindTaskItem(hWnd);
1073 if (TaskItem != NULL)
1074 {
1075 TRACE("Flashing window 0x%p on button %d\n", hWnd, TaskItem->Index);
1076 FlashTaskItem(TaskItem);
1077 return TRUE;
1078 }
1079
1080 return FALSE;
1081 }
1082
1083 VOID RedrawTaskItem(IN OUT PTASK_ITEM TaskItem)
1084 {
1085 PTASK_GROUP TaskGroup;
1086
1087 TaskGroup = TaskItem->Group;
1088 if (m_IsGroupingEnabled && TaskGroup != NULL)
1089 {
1090 if (TaskGroup->IsCollapsed && TaskGroup->Index >= 0)
1091 {
1092 UpdateTaskGroupButton(TaskGroup);
1093 }
1094 else if (TaskItem->Index >= 0)
1095 {
1096 goto UpdateTaskItem;
1097 }
1098 }
1099 else if (TaskItem->Index >= 0)
1100 {
1101 UpdateTaskItem:
1102 TaskItem->RenderFlashed = 0;
1103 UpdateTaskItemButton(TaskItem);
1104 }
1105 }
1106
1107
1108 BOOL RedrawTask(IN HWND hWnd)
1109 {
1110 PTASK_ITEM TaskItem;
1111
1112 TaskItem = FindTaskItem(hWnd);
1113 if (TaskItem != NULL)
1114 {
1115 RedrawTaskItem(TaskItem);
1116 return TRUE;
1117 }
1118
1119 return FALSE;
1120 }
1121
1122 VOID UpdateButtonsSize(IN BOOL bRedrawDisabled)
1123 {
1124 RECT rcClient;
1125 UINT uiRows, uiMax, uiMin, uiBtnsPerLine, ui;
1126 LONG NewBtnSize;
1127 BOOL Horizontal;
1128
1129 if (GetClientRect(&rcClient) && !IsRectEmpty(&rcClient))
1130 {
1131 if (m_ButtonCount > 0)
1132 {
1133 Horizontal = m_Tray->IsHorizontal();
1134
1135 if (Horizontal)
1136 {
1137 TBMETRICS tbm = { 0 };
1138 tbm.cbSize = sizeof(tbm);
1139 tbm.dwMask = TBMF_BUTTONSPACING;
1140 m_TaskBar.GetMetrics(&tbm);
1141
1142 uiRows = (rcClient.bottom + tbm.cyButtonSpacing) / (m_ButtonSize.cy + tbm.cyButtonSpacing);
1143 if (uiRows == 0)
1144 uiRows = 1;
1145
1146 uiBtnsPerLine = (m_ButtonCount + uiRows - 1) / uiRows;
1147 }
1148 else
1149 {
1150 uiBtnsPerLine = 1;
1151 uiRows = m_ButtonCount;
1152 }
1153
1154 if (!bRedrawDisabled)
1155 m_TaskBar.BeginUpdate();
1156
1157 /* We might need to update the button spacing */
1158 int cxButtonSpacing = m_TaskBar.UpdateTbButtonSpacing(
1159 Horizontal, m_Theme != NULL,
1160 uiRows, uiBtnsPerLine);
1161
1162 /* Determine the minimum and maximum width of a button */
1163 uiMin = GetSystemMetrics(SM_CXSIZE) + (2 * GetSystemMetrics(SM_CXEDGE));
1164 if (Horizontal)
1165 {
1166 uiMax = GetSystemMetrics(SM_CXMINIMIZED);
1167
1168 /* Calculate the ideal width and make sure it's within the allowed range */
1169 NewBtnSize = (rcClient.right - (uiBtnsPerLine * cxButtonSpacing)) / uiBtnsPerLine;
1170
1171 if (NewBtnSize < (LONG) uiMin)
1172 NewBtnSize = uiMin;
1173 if (NewBtnSize >(LONG)uiMax)
1174 NewBtnSize = uiMax;
1175
1176 /* Recalculate how many buttons actually fit into one line */
1177 uiBtnsPerLine = rcClient.right / (NewBtnSize + cxButtonSpacing);
1178 if (uiBtnsPerLine == 0)
1179 uiBtnsPerLine++;
1180 }
1181 else
1182 {
1183 NewBtnSize = uiMax = rcClient.right;
1184 }
1185
1186 m_ButtonSize.cx = NewBtnSize;
1187
1188 m_ButtonsPerLine = uiBtnsPerLine;
1189
1190 for (ui = 0; ui != m_ButtonCount; ui++)
1191 {
1192 TBBUTTONINFOW tbbi = { 0 };
1193 tbbi.cbSize = sizeof(tbbi);
1194 tbbi.dwMask = TBIF_BYINDEX | TBIF_SIZE | TBIF_STATE;
1195 tbbi.cx = (INT) NewBtnSize;
1196 tbbi.fsState = TBSTATE_ENABLED;
1197
1198 /* Check if we're updating a button that is the last one in the
1199 line. If so, we need to set the TBSTATE_WRAP flag! */
1200 if (Horizontal)
1201 {
1202 if ((ui + 1) % uiBtnsPerLine == 0)
1203 tbbi.fsState |= TBSTATE_WRAP;
1204 }
1205 else
1206 {
1207 tbbi.fsState |= TBSTATE_WRAP;
1208 }
1209
1210 if (m_ActiveTaskItem != NULL &&
1211 m_ActiveTaskItem->Index == (INT)ui)
1212 {
1213 tbbi.fsState |= TBSTATE_CHECKED;
1214 }
1215
1216 m_TaskBar.SetButtonInfo(ui, &tbbi);
1217 }
1218 }
1219 else
1220 {
1221 m_ButtonsPerLine = 0;
1222 m_ButtonSize.cx = 0;
1223 }
1224 }
1225
1226 // FIXME: This seems to be enabling redraws prematurely, but moving it to its right place doesn't work!
1227 m_TaskBar.EndUpdate();
1228 }
1229
1230 BOOL CALLBACK EnumWindowsProc(IN HWND hWnd)
1231 {
1232 /* Only show windows that still exist and are visible and none of explorer's
1233 special windows (such as the desktop or the tray window) */
1234 if (::IsWindow(hWnd) && ::IsWindowVisible(hWnd) &&
1235 !m_Tray->IsSpecialHWND(hWnd))
1236 {
1237 DWORD exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE);
1238 /* Don't list popup windows and also no tool windows */
1239 if ((::GetWindow(hWnd, GW_OWNER) == NULL || exStyle & WS_EX_APPWINDOW) &&
1240 !(exStyle & WS_EX_TOOLWINDOW))
1241 {
1242 TRACE("Adding task for %p...\n", hWnd);
1243 AddTask(hWnd);
1244 }
1245
1246 }
1247
1248 return TRUE;
1249 }
1250
1251 static BOOL CALLBACK s_EnumWindowsProc(IN HWND hWnd, IN LPARAM lParam)
1252 {
1253 CTaskSwitchWnd * This = (CTaskSwitchWnd *) lParam;
1254
1255 return This->EnumWindowsProc(hWnd);
1256 }
1257
1258 BOOL RefreshWindowList()
1259 {
1260 TRACE("Refreshing window list...\n");
1261 /* Add all windows to the toolbar */
1262 return EnumWindows(s_EnumWindowsProc, (LPARAM)this);
1263 }
1264
1265 LRESULT OnThemeChanged()
1266 {
1267 TRACE("OmThemeChanged\n");
1268
1269 if (m_Theme)
1270 CloseThemeData(m_Theme);
1271
1272 if (IsThemeActive())
1273 m_Theme = OpenThemeData(m_hWnd, L"TaskBand");
1274 else
1275 m_Theme = NULL;
1276
1277 return TRUE;
1278 }
1279
1280 LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1281 {
1282 return OnThemeChanged();
1283 }
1284
1285 LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1286 {
1287 if (!m_TaskBar.Initialize(m_hWnd))
1288 return FALSE;
1289
1290 SetWindowTheme(m_TaskBar.m_hWnd, L"TaskBand", NULL);
1291 OnThemeChanged();
1292
1293 m_ImageList = ImageList_Create(16, 16, ILC_COLOR32 | ILC_MASK, 0, 1000);
1294 m_TaskBar.SetImageList(m_ImageList);
1295
1296 /* Calculate the default button size. Don't save this in m_ButtonSize.cx so that
1297 the actual button width gets updated correctly on the first recalculation */
1298 int cx = GetSystemMetrics(SM_CXMINIMIZED);
1299 int cy = m_ButtonSize.cy = GetSystemMetrics(SM_CYSIZE) + (2 * GetSystemMetrics(SM_CYEDGE));
1300 m_TaskBar.SetButtonSize(cx, cy);
1301
1302 /* Set proper spacing between buttons */
1303 m_TaskBar.UpdateTbButtonSpacing(m_Tray->IsHorizontal(), m_Theme != NULL);
1304
1305 /* Register the shell hook */
1306 m_ShellHookMsg = RegisterWindowMessageW(L"SHELLHOOK");
1307
1308 TRACE("ShellHookMsg got assigned number %d\n", m_ShellHookMsg);
1309
1310 RegisterShellHook(m_hWnd, 3); /* 1 if no NT! We're targeting NT so we don't care! */
1311
1312 RefreshWindowList();
1313
1314 /* Recalculate the button size */
1315 UpdateButtonsSize(FALSE);
1316
1317 #if DUMP_TASKS != 0
1318 SetTimer(hwnd, 1, 5000, NULL);
1319 #endif
1320 return TRUE;
1321 }
1322
1323 LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1324 {
1325 m_IsDestroying = TRUE;
1326
1327 /* Unregister the shell hook */
1328 RegisterShellHook(m_hWnd, FALSE);
1329
1330 CloseThemeData(m_Theme);
1331 DeleteAllTasks();
1332 return TRUE;
1333 }
1334
1335 BOOL HandleAppCommand(IN WPARAM wParam, IN LPARAM lParam)
1336 {
1337 BOOL Ret = FALSE;
1338
1339 switch (GET_APPCOMMAND_LPARAM(lParam))
1340 {
1341 case APPCOMMAND_BROWSER_SEARCH:
1342 Ret = SHFindFiles(NULL,
1343 NULL);
1344 break;
1345
1346 case APPCOMMAND_BROWSER_HOME:
1347 case APPCOMMAND_LAUNCH_MAIL:
1348 default:
1349 TRACE("Shell app command %d unhandled!\n", (INT) GET_APPCOMMAND_LPARAM(lParam));
1350 break;
1351 }
1352
1353 return Ret;
1354 }
1355
1356 LRESULT HandleShellHookMsg(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1357 {
1358 BOOL Ret = FALSE;
1359
1360 /* In case the shell hook wasn't registered properly, ignore WM_NULLs*/
1361 if (uMsg == 0)
1362 {
1363 bHandled = FALSE;
1364 return 0;
1365 }
1366
1367 TRACE("Received shell hook message: wParam=%08lx, lParam=%08lx\n", wParam, lParam);
1368
1369 switch ((INT) wParam)
1370 {
1371 case HSHELL_APPCOMMAND:
1372 HandleAppCommand(wParam, lParam);
1373 Ret = TRUE;
1374 break;
1375
1376 case HSHELL_WINDOWCREATED:
1377 Ret = AddTask((HWND) lParam);
1378 break;
1379
1380 case HSHELL_WINDOWDESTROYED:
1381 /* The window still exists! Delay destroying it a bit */
1382 DeleteTask((HWND) lParam);
1383 Ret = TRUE;
1384 break;
1385
1386 case HSHELL_RUDEAPPACTIVATED:
1387 case HSHELL_WINDOWACTIVATED:
1388 if (lParam)
1389 {
1390 ActivateTask((HWND) lParam);
1391 Ret = TRUE;
1392 }
1393 break;
1394
1395 case HSHELL_FLASH:
1396 FlashTask((HWND) lParam);
1397 Ret = TRUE;
1398 break;
1399
1400 case HSHELL_REDRAW:
1401 RedrawTask((HWND) lParam);
1402 Ret = TRUE;
1403 break;
1404
1405 case HSHELL_TASKMAN:
1406 ::PostMessage(m_Tray->GetHWND(), TWM_OPENSTARTMENU, 0, 0);
1407 break;
1408
1409 case HSHELL_ACTIVATESHELLWINDOW:
1410 case HSHELL_LANGUAGE:
1411 case HSHELL_SYSMENU:
1412 case HSHELL_ENDTASK:
1413 case HSHELL_ACCESSIBILITYSTATE:
1414 case HSHELL_WINDOWREPLACED:
1415 case HSHELL_WINDOWREPLACING:
1416
1417 case HSHELL_GETMINRECT:
1418 default:
1419 {
1420 #if DEBUG_SHELL_HOOK
1421 int i, found;
1422 for (i = 0, found = 0; i != _countof(hshell_msg); i++)
1423 {
1424 if (hshell_msg[i].msg == (INT) wParam)
1425 {
1426 TRACE("Shell message %ws unhandled (lParam = 0x%p)!\n", hshell_msg[i].msg_name, lParam);
1427 found = 1;
1428 break;
1429 }
1430 }
1431 if (found)
1432 break;
1433 #endif
1434 TRACE("Shell message %d unhandled (lParam = 0x%p)!\n", (INT) wParam, lParam);
1435 break;
1436 }
1437 }
1438
1439 return Ret;
1440 }
1441
1442 VOID EnableGrouping(IN BOOL bEnable)
1443 {
1444 m_IsGroupingEnabled = bEnable;
1445
1446 /* Collapse or expand groups if neccessary */
1447 UpdateButtonsSize(FALSE);
1448 }
1449
1450 VOID HandleTaskItemClick(IN OUT PTASK_ITEM TaskItem)
1451 {
1452 BOOL bIsMinimized;
1453 BOOL bIsActive;
1454
1455 if (::IsWindow(TaskItem->hWnd))
1456 {
1457 bIsMinimized = ::IsIconic(TaskItem->hWnd);
1458 bIsActive = (TaskItem == m_ActiveTaskItem);
1459
1460 TRACE("Active TaskItem %p, selected TaskItem %p\n", m_ActiveTaskItem, TaskItem);
1461 if (m_ActiveTaskItem)
1462 TRACE("Active TaskItem hWnd=%p, TaskItem hWnd %p\n", m_ActiveTaskItem->hWnd, TaskItem->hWnd);
1463
1464 TRACE("Valid button clicked. HWND=%p, IsMinimized=%s, IsActive=%s...\n",
1465 TaskItem->hWnd, bIsMinimized ? "Yes" : "No", bIsActive ? "Yes" : "No");
1466
1467 if (!bIsMinimized && bIsActive)
1468 {
1469 ::PostMessage(TaskItem->hWnd,
1470 WM_SYSCOMMAND,
1471 SC_MINIMIZE,
1472 0);
1473 TRACE("Valid button clicked. App window Minimized.\n");
1474 }
1475 else
1476 {
1477 if (bIsMinimized)
1478 {
1479 ::PostMessage(TaskItem->hWnd,
1480 WM_SYSCOMMAND,
1481 SC_RESTORE,
1482 0);
1483 TRACE("Valid button clicked. App window Restored.\n");
1484 }
1485
1486 SetForegroundWindow(TaskItem->hWnd);
1487 TRACE("Valid button clicked. App window Activated.\n");
1488 }
1489 }
1490 }
1491
1492 VOID HandleTaskGroupClick(IN OUT PTASK_GROUP TaskGroup)
1493 {
1494 /* TODO: Show task group menu */
1495 }
1496
1497 BOOL HandleButtonClick(IN WORD wIndex)
1498 {
1499 PTASK_ITEM TaskItem;
1500 PTASK_GROUP TaskGroup;
1501
1502 if (m_IsGroupingEnabled)
1503 {
1504 TaskGroup = FindTaskGroupByIndex((INT) wIndex);
1505 if (TaskGroup != NULL && TaskGroup->IsCollapsed)
1506 {
1507 HandleTaskGroupClick(TaskGroup);
1508 return TRUE;
1509 }
1510 }
1511
1512 TaskItem = FindTaskItemByIndex((INT) wIndex);
1513 if (TaskItem != NULL)
1514 {
1515 HandleTaskItemClick(TaskItem);
1516 return TRUE;
1517 }
1518
1519 return FALSE;
1520 }
1521
1522
1523 VOID HandleTaskItemRightClick(IN OUT PTASK_ITEM TaskItem)
1524 {
1525
1526 HMENU hmenu = ::GetSystemMenu(TaskItem->hWnd, FALSE);
1527
1528 if (hmenu)
1529 {
1530 POINT pt;
1531 int cmd;
1532 GetCursorPos(&pt);
1533 cmd = TrackPopupMenu(hmenu, TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD, pt.x, pt.y, 0, m_TaskBar.m_hWnd, NULL);
1534 if (cmd)
1535 {
1536 SetForegroundWindow(TaskItem->hWnd); // reactivate window after the context menu has closed
1537 ::PostMessage(TaskItem->hWnd, WM_SYSCOMMAND, cmd, 0);
1538 }
1539 }
1540 }
1541
1542 VOID HandleTaskGroupRightClick(IN OUT PTASK_GROUP TaskGroup)
1543 {
1544 /* TODO: Show task group right click menu */
1545 }
1546
1547 BOOL HandleButtonRightClick(IN WORD wIndex)
1548 {
1549 PTASK_ITEM TaskItem;
1550 PTASK_GROUP TaskGroup;
1551 if (m_IsGroupingEnabled)
1552 {
1553 TaskGroup = FindTaskGroupByIndex((INT) wIndex);
1554 if (TaskGroup != NULL && TaskGroup->IsCollapsed)
1555 {
1556 HandleTaskGroupRightClick(TaskGroup);
1557 return TRUE;
1558 }
1559 }
1560
1561 TaskItem = FindTaskItemByIndex((INT) wIndex);
1562
1563 if (TaskItem != NULL)
1564 {
1565 HandleTaskItemRightClick(TaskItem);
1566 return TRUE;
1567 }
1568
1569 return FALSE;
1570 }
1571
1572
1573 LRESULT HandleItemPaint(IN OUT NMTBCUSTOMDRAW *nmtbcd)
1574 {
1575 LRESULT Ret = CDRF_DODEFAULT;
1576 PTASK_GROUP TaskGroup;
1577 PTASK_ITEM TaskItem;
1578
1579 TaskItem = FindTaskItemByIndex((INT) nmtbcd->nmcd.dwItemSpec);
1580 TaskGroup = FindTaskGroupByIndex((INT) nmtbcd->nmcd.dwItemSpec);
1581 if (TaskGroup == NULL && TaskItem != NULL)
1582 {
1583 ASSERT(TaskItem != NULL);
1584
1585 if (TaskItem != NULL && ::IsWindow(TaskItem->hWnd))
1586 {
1587 /* Make the entire button flashing if neccessary */
1588 if (nmtbcd->nmcd.uItemState & CDIS_MARKED)
1589 {
1590 Ret = TBCDRF_NOBACKGROUND;
1591 if (!m_Theme)
1592 {
1593 SelectObject(nmtbcd->nmcd.hdc, GetSysColorBrush(COLOR_HIGHLIGHT));
1594 Rectangle(nmtbcd->nmcd.hdc,
1595 nmtbcd->nmcd.rc.left,
1596 nmtbcd->nmcd.rc.top,
1597 nmtbcd->nmcd.rc.right,
1598 nmtbcd->nmcd.rc.bottom);
1599 }
1600 else
1601 {
1602 DrawThemeBackground(m_Theme, nmtbcd->nmcd.hdc, TDP_FLASHBUTTON, 0, &nmtbcd->nmcd.rc, 0);
1603 }
1604 nmtbcd->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
1605 return Ret;
1606 }
1607 }
1608 }
1609 else if (TaskGroup != NULL)
1610 {
1611 /* FIXME: Implement painting for task groups */
1612 }
1613 return Ret;
1614 }
1615
1616 LRESULT HandleToolbarNotification(IN const NMHDR *nmh)
1617 {
1618 LRESULT Ret = 0;
1619
1620 switch (nmh->code)
1621 {
1622 case NM_CUSTOMDRAW:
1623 {
1624 LPNMTBCUSTOMDRAW nmtbcd = (LPNMTBCUSTOMDRAW) nmh;
1625
1626 switch (nmtbcd->nmcd.dwDrawStage)
1627 {
1628
1629 case CDDS_ITEMPREPAINT:
1630 Ret = HandleItemPaint(nmtbcd);
1631 break;
1632
1633 case CDDS_PREPAINT:
1634 Ret = CDRF_NOTIFYITEMDRAW;
1635 break;
1636
1637 default:
1638 Ret = CDRF_DODEFAULT;
1639 break;
1640 }
1641 break;
1642 }
1643 }
1644
1645 return Ret;
1646 }
1647
1648 LRESULT DrawBackground(HDC hdc)
1649 {
1650 RECT rect;
1651
1652 GetClientRect(&rect);
1653 DrawThemeParentBackground(m_hWnd, hdc, &rect);
1654
1655 return TRUE;
1656 }
1657
1658 LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1659 {
1660 HDC hdc = (HDC) wParam;
1661
1662 if (!IsAppThemed())
1663 {
1664 bHandled = FALSE;
1665 return 0;
1666 }
1667
1668 return DrawBackground(hdc);
1669 }
1670
1671 LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1672 {
1673 SIZE szClient;
1674
1675 szClient.cx = LOWORD(lParam);
1676 szClient.cy = HIWORD(lParam);
1677 if (m_TaskBar.m_hWnd != NULL)
1678 {
1679 m_TaskBar.SetWindowPos(NULL, 0, 0, szClient.cx, szClient.cy, SWP_NOZORDER);
1680
1681 UpdateButtonsSize(FALSE);
1682 }
1683 return TRUE;
1684 }
1685
1686 LRESULT OnNcHitTestToolbar(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1687 {
1688 POINT pt;
1689
1690 /* See if the mouse is on a button */
1691 pt.x = GET_X_LPARAM(lParam);
1692 pt.y = GET_Y_LPARAM(lParam);
1693 ScreenToClient(&pt);
1694
1695 INT index = m_TaskBar.HitTest(&pt);
1696 if (index < 0)
1697 {
1698 /* Make the control appear to be transparent outside of any buttons */
1699 return HTTRANSPARENT;
1700 }
1701
1702 bHandled = FALSE;
1703 return 0;
1704 }
1705
1706 LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1707 {
1708 LRESULT Ret = TRUE;
1709 /* We want the tray window to be draggable everywhere, so make the control
1710 appear transparent */
1711 Ret = DefWindowProc(uMsg, wParam, lParam);
1712 if (Ret != HTVSCROLL && Ret != HTHSCROLL)
1713 Ret = HTTRANSPARENT;
1714 return Ret;
1715 }
1716
1717 LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1718 {
1719 LRESULT Ret = TRUE;
1720 if (lParam != 0 && (HWND) lParam == m_TaskBar.m_hWnd)
1721 {
1722 HandleButtonClick(LOWORD(wParam));
1723 }
1724 return Ret;
1725 }
1726
1727 LRESULT OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1728 {
1729 LRESULT Ret = TRUE;
1730 const NMHDR *nmh = (const NMHDR *) lParam;
1731
1732 if (nmh->hwndFrom == m_TaskBar.m_hWnd)
1733 {
1734 Ret = HandleToolbarNotification(nmh);
1735 }
1736 return Ret;
1737 }
1738
1739 LRESULT OnEnableGrouping(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1740 {
1741 LRESULT Ret = m_IsGroupingEnabled;
1742 if ((BOOL)wParam != m_IsGroupingEnabled)
1743 {
1744 EnableGrouping((BOOL) wParam);
1745 }
1746 return Ret;
1747 }
1748
1749 LRESULT OnUpdateTaskbarPos(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1750 {
1751 /* Update the button spacing */
1752 m_TaskBar.UpdateTbButtonSpacing(m_Tray->IsHorizontal(), m_Theme != NULL);
1753 return TRUE;
1754 }
1755
1756 LRESULT OnContextMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1757 {
1758 LRESULT Ret = 0;
1759 INT_PTR iBtn = -1;
1760
1761 if (m_TaskBar.m_hWnd != NULL)
1762 {
1763 POINT pt;
1764
1765 pt.x = GET_X_LPARAM(lParam);
1766 pt.y = GET_Y_LPARAM(lParam);
1767
1768 ::ScreenToClient(m_TaskBar.m_hWnd, &pt);
1769
1770 iBtn = m_TaskBar.HitTest(&pt);
1771 if (iBtn >= 0)
1772 {
1773 HandleButtonRightClick(iBtn);
1774 }
1775 }
1776 if (iBtn < 0)
1777 {
1778 /* Not on a taskbar button, so forward message to tray */
1779 Ret = SendMessage(m_Tray->GetHWND(), uMsg, wParam, lParam);
1780 }
1781 return Ret;
1782 }
1783
1784 LRESULT OnKludgeItemRect(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1785 {
1786 PTASK_ITEM TaskItem = FindTaskItem((HWND) wParam);
1787 if (TaskItem)
1788 {
1789 RECT* prcMinRect = (RECT*) lParam;
1790 RECT rcItem, rcToolbar;
1791 m_TaskBar.GetItemRect(TaskItem->Index, &rcItem);
1792 m_TaskBar.GetWindowRect(&rcToolbar);
1793
1794 OffsetRect(&rcItem, rcToolbar.left, rcToolbar.top);
1795
1796 *prcMinRect = rcItem;
1797 return TRUE;
1798 }
1799 return FALSE;
1800 }
1801
1802 LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1803 {
1804 #if DUMP_TASKS != 0
1805 switch (wParam)
1806 {
1807 case 1:
1808 DumpTasks();
1809 break;
1810 }
1811 #endif
1812 return TRUE;
1813 }
1814
1815 DECLARE_WND_CLASS_EX(szTaskSwitchWndClass, CS_DBLCLKS, COLOR_3DFACE)
1816
1817 BEGIN_MSG_MAP(CTaskSwitchWnd)
1818 MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
1819 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
1820 MESSAGE_HANDLER(WM_SIZE, OnSize)
1821 MESSAGE_HANDLER(WM_CREATE, OnCreate)
1822 MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
1823 MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
1824 MESSAGE_HANDLER(WM_COMMAND, OnCommand)
1825 MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
1826 MESSAGE_HANDLER(TSWM_ENABLEGROUPING, OnEnableGrouping)
1827 MESSAGE_HANDLER(TSWM_UPDATETASKBARPOS, OnUpdateTaskbarPos)
1828 MESSAGE_HANDLER(WM_CONTEXTMENU, OnContextMenu)
1829 MESSAGE_HANDLER(WM_TIMER, OnTimer)
1830 MESSAGE_HANDLER(m_ShellHookMsg, HandleShellHookMsg)
1831 ALT_MSG_MAP(1)
1832 MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTestToolbar)
1833 END_MSG_MAP()
1834
1835 HWND _Init(IN HWND hWndParent, IN OUT ITrayWindow *tray)
1836 {
1837 m_Tray = tray;
1838 m_IsGroupingEnabled = TRUE; /* FIXME */
1839 return Create(hWndParent, 0, szRunningApps, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP);
1840 }
1841 };
1842
1843 HWND
1844 CreateTaskSwitchWnd(IN HWND hWndParent, IN OUT ITrayWindow *Tray)
1845 {
1846 CTaskSwitchWnd * instance;
1847
1848 // TODO: Destroy after the window is destroyed
1849 instance = new CTaskSwitchWnd();
1850
1851 return instance->_Init(hWndParent, Tray);
1852 }