Sync with trunk (r48042), except win32k/ntuser/cursoricon.c
[reactos.git] / base / shell / explorer-new / taskswnd.c
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
23 /* By default we don't use DrawCaptionTemp() because it causes some minimal
24 drawing glitches with the toolbar custom painting code */
25 #define TASK_USE_DRAWCAPTIONTEMP 1
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
31 static const TCHAR szTaskSwitchWndClass[] = TEXT("MSTaskSwWClass");
32 static const TCHAR szRunningApps[] = TEXT("Running Applications");
33
34 typedef struct _TASK_GROUP
35 {
36 /* We have to use a linked list instead of an array so we don't have to
37 update all pointers to groups in the task item array when removing
38 groups. */
39 struct _TASK_GROUP *Next;
40
41 DWORD dwTaskCount;
42 DWORD dwProcessId;
43 INT Index;
44 union
45 {
46 DWORD dwFlags;
47 struct
48 {
49
50 #if TASK_USE_DRAWCAPTIONTEMP != 0
51
52 /* DisplayTooltip is TRUE when the group button text didn't fit into
53 the button. */
54 DWORD DisplayTooltip : 1;
55
56 #endif
57
58 DWORD IsCollapsed : 1;
59 };
60 };
61 } TASK_GROUP, *PTASK_GROUP;
62
63 typedef struct _TASK_ITEM
64 {
65 HWND hWnd;
66 PTASK_GROUP Group;
67 INT Index;
68
69 #if !(TASK_USE_DRAWCAPTIONTEMP != 0)
70
71 INT IconIndex;
72
73 #endif
74
75 union
76 {
77 DWORD dwFlags;
78 struct
79 {
80
81 #if TASK_USE_DRAWCAPTIONTEMP != 0
82
83 /* DisplayTooltip is TRUE when the window text didn't fit into the
84 button. */
85 DWORD DisplayTooltip : 1;
86
87 #endif
88
89 /* IsFlashing is TRUE when the task bar item should be flashing. */
90 DWORD IsFlashing : 1;
91
92 /* RenderFlashed is only TRUE if the task bar item should be
93 drawn with a flash. */
94 DWORD RenderFlashed : 1;
95 };
96 };
97 } TASK_ITEM, *PTASK_ITEM;
98
99 #define TASK_ITEM_ARRAY_ALLOC 64
100
101 typedef struct _TASK_SWITCH_WND
102 {
103 HWND hWnd;
104 HWND hWndNotify;
105
106 UINT ShellHookMsg;
107 ITrayWindow *Tray;
108
109 PTASK_GROUP TaskGroups;
110
111 WORD TaskItemCount;
112 WORD AllocatedTaskItems;
113 PTASK_ITEM TaskItems;
114 PTASK_ITEM ActiveTaskItem;
115
116 HWND hWndToolbar;
117 UINT TbButtonsPerLine;
118 WORD ToolbarBtnCount;
119
120 union
121 {
122 DWORD dwFlags;
123 struct
124 {
125 DWORD IsGroupingEnabled : 1;
126 DWORD IsDestroying : 1;
127 DWORD IsToolbarSubclassed : 1;
128 };
129 };
130
131 SIZE ButtonSize;
132 TCHAR szBuf[255];
133 } TASK_SWITCH_WND, *PTASK_SWITCH_WND;
134
135 #define TSW_TOOLBAR_SUBCLASS_ID 1
136
137 #define MAX_TASKS_COUNT (0x7FFF)
138
139 static VOID TaskSwitchWnd_UpdateButtonsSize(IN OUT PTASK_SWITCH_WND This,
140 IN BOOL bRedrawDisabled);
141
142 #if TASK_USE_DRAWCAPTIONTEMP != 0
143
144 #define TaskSwitchWnd_GetWndTextFromTaskItem(a,b) NULL
145
146 #else /* !TASK_USE_DRAWCAPTIONTEMP */
147
148 static LPTSTR
149 TaskSwitchWnd_GetWndTextFromTaskItem(IN OUT PTASK_SWITCH_WND This,
150 IN PTASK_ITEM TaskItem)
151 {
152 /* Get the window text without sending a message so we don't hang if an
153 application isn't responding! */
154 if (InternalGetWindowText(TaskItem->hWnd,
155 This->szBuf,
156 sizeof(This->szBuf) / sizeof(This->szBuf[0])) > 0)
157 {
158 return This->szBuf;
159 }
160
161 return NULL;
162 }
163
164 #endif
165
166 #if DUMP_TASKS != 0
167 static VOID
168 TaskSwitchWnd_DumpTasks(IN OUT PTASK_SWITCH_WND This)
169 {
170 PTASK_GROUP CurrentGroup;
171 PTASK_ITEM CurrentTaskItem, LastTaskItem;
172
173 DbgPrint("Tasks dump:\n");
174 if (This->IsGroupingEnabled)
175 {
176 CurrentGroup = This->TaskGroups;
177 while (CurrentGroup != NULL)
178 {
179 DbgPrint("- Group PID: 0x%p Tasks: %d Index: %d\n", CurrentGroup->dwProcessId, CurrentGroup->dwTaskCount, CurrentGroup->Index);
180
181 CurrentTaskItem = This->TaskItems;
182 LastTaskItem = CurrentTaskItem + This->TaskItemCount;
183 while (CurrentTaskItem != LastTaskItem)
184 {
185 if (CurrentTaskItem->Group == CurrentGroup)
186 {
187 DbgPrint(" + Task hwnd: 0x%p Index: %d\n", CurrentTaskItem->hWnd, CurrentTaskItem->Index);
188 }
189 CurrentTaskItem++;
190 }
191
192 CurrentGroup = CurrentGroup->Next;
193 }
194
195 CurrentTaskItem = This->TaskItems;
196 LastTaskItem = CurrentTaskItem + This->TaskItemCount;
197 while (CurrentTaskItem != LastTaskItem)
198 {
199 if (CurrentTaskItem->Group == NULL)
200 {
201 DbgPrint("- Task hwnd: 0x%p Index: %d\n", CurrentTaskItem->hWnd, CurrentTaskItem->Index);
202 }
203 CurrentTaskItem++;
204 }
205 }
206 else
207 {
208 CurrentTaskItem = This->TaskItems;
209 LastTaskItem = CurrentTaskItem + This->TaskItemCount;
210 while (CurrentTaskItem != LastTaskItem)
211 {
212 DbgPrint("- Task hwnd: 0x%p Index: %d\n", CurrentTaskItem->hWnd, CurrentTaskItem->Index);
213 CurrentTaskItem++;
214 }
215 }
216 }
217 #endif
218
219 static VOID
220 TaskSwitchWnd_BeginUpdate(IN OUT PTASK_SWITCH_WND This)
221 {
222 SendMessage(This->hWndToolbar,
223 WM_SETREDRAW,
224 FALSE,
225 0);
226 }
227
228 static VOID
229 TaskSwitchWnd_EndUpdate(IN OUT PTASK_SWITCH_WND This)
230 {
231 SendMessage(This->hWndToolbar,
232 WM_SETREDRAW,
233 TRUE,
234 0);
235 InvalidateRect(This->hWndToolbar,
236 NULL,
237 TRUE);
238 }
239
240 static BOOL
241 TaskSwitchWnd_SetToolbarButtonCommandId(IN OUT PTASK_SWITCH_WND This,
242 IN INT iButtonIndex,
243 IN INT iCommandId)
244 {
245 TBBUTTONINFO tbbi;
246
247 tbbi.cbSize = sizeof(tbbi);
248 tbbi.dwMask = TBIF_BYINDEX | TBIF_COMMAND;
249 tbbi.idCommand = iCommandId;
250
251 return SendMessage(This->hWndToolbar,
252 TB_SETBUTTONINFO,
253 (WPARAM)iButtonIndex,
254 (LPARAM)&tbbi) != 0;
255 }
256
257 static VOID
258 TaskSwitchWnd_UpdateIndexesAfterButtonInserted(IN OUT PTASK_SWITCH_WND This,
259 IN INT iIndex)
260 {
261 PTASK_GROUP CurrentGroup;
262 PTASK_ITEM CurrentTaskItem, LastTaskItem;
263 INT NewIndex;
264
265 if (This->IsGroupingEnabled)
266 {
267 /* Update all affected groups */
268 CurrentGroup = This->TaskGroups;
269 while (CurrentGroup != NULL)
270 {
271 if (CurrentGroup->IsCollapsed &&
272 CurrentGroup->Index >= iIndex)
273 {
274 /* Update the toolbar buttons */
275 NewIndex = CurrentGroup->Index + 1;
276 if (TaskSwitchWnd_SetToolbarButtonCommandId(This,
277 CurrentGroup->Index + 1,
278 NewIndex))
279 {
280 CurrentGroup->Index = NewIndex;
281 }
282 else
283 CurrentGroup->Index = -1;
284 }
285
286 CurrentGroup = CurrentGroup->Next;
287 }
288 }
289
290 /* Update all affected task items */
291 CurrentTaskItem = This->TaskItems;
292 LastTaskItem = CurrentTaskItem + This->TaskItemCount;
293 while (CurrentTaskItem != LastTaskItem)
294 {
295 CurrentGroup = CurrentTaskItem->Group;
296 if (CurrentGroup != NULL)
297 {
298 if (!CurrentGroup->IsCollapsed &&
299 CurrentTaskItem->Index >= iIndex)
300 {
301 goto UpdateTaskItemBtn;
302 }
303 }
304 else if (CurrentTaskItem->Index >= iIndex)
305 {
306 UpdateTaskItemBtn:
307 /* Update the toolbar buttons */
308 NewIndex = CurrentTaskItem->Index + 1;
309 if (TaskSwitchWnd_SetToolbarButtonCommandId(This,
310 CurrentTaskItem->Index + 1,
311 NewIndex))
312 {
313 CurrentTaskItem->Index = NewIndex;
314 }
315 else
316 CurrentTaskItem->Index = -1;
317 }
318
319 CurrentTaskItem++;
320 }
321 }
322
323 static VOID
324 TaskSwitchWnd_UpdateIndexesAfterButtonDeleted(IN OUT PTASK_SWITCH_WND This,
325 IN INT iIndex)
326 {
327 PTASK_GROUP CurrentGroup;
328 PTASK_ITEM CurrentTaskItem, LastTaskItem;
329 INT NewIndex;
330
331 if (This->IsGroupingEnabled)
332 {
333 /* Update all affected groups */
334 CurrentGroup = This->TaskGroups;
335 while (CurrentGroup != NULL)
336 {
337 if (CurrentGroup->IsCollapsed &&
338 CurrentGroup->Index > iIndex)
339 {
340 /* Update the toolbar buttons */
341 NewIndex = CurrentGroup->Index - 1;
342 if (TaskSwitchWnd_SetToolbarButtonCommandId(This,
343 CurrentGroup->Index - 1,
344 NewIndex))
345 {
346 CurrentGroup->Index = NewIndex;
347 }
348 else
349 CurrentGroup->Index = -1;
350 }
351
352 CurrentGroup = CurrentGroup->Next;
353 }
354 }
355
356 /* Update all affected task items */
357 CurrentTaskItem = This->TaskItems;
358 LastTaskItem = CurrentTaskItem + This->TaskItemCount;
359 while (CurrentTaskItem != LastTaskItem)
360 {
361 CurrentGroup = CurrentTaskItem->Group;
362 if (CurrentGroup != NULL)
363 {
364 if (!CurrentGroup->IsCollapsed &&
365 CurrentTaskItem->Index > iIndex)
366 {
367 goto UpdateTaskItemBtn;
368 }
369 }
370 else if (CurrentTaskItem->Index > iIndex)
371 {
372 UpdateTaskItemBtn:
373 /* Update the toolbar buttons */
374 NewIndex = CurrentTaskItem->Index - 1;
375 if (TaskSwitchWnd_SetToolbarButtonCommandId(This,
376 CurrentTaskItem->Index - 1,
377 NewIndex))
378 {
379 CurrentTaskItem->Index = NewIndex;
380 }
381 else
382 CurrentTaskItem->Index = -1;
383 }
384
385 CurrentTaskItem++;
386 }
387 }
388
389 static INT
390 TaskSwitchWnd_UpdateTaskGroupButton(IN OUT PTASK_SWITCH_WND This,
391 IN PTASK_GROUP TaskGroup)
392 {
393 ASSERT(TaskGroup->Index >= 0);
394
395 /* FIXME: Implement */
396
397 return TaskGroup->Index;
398 }
399
400 static VOID
401 TaskSwitchWnd_ExpandTaskGroup(IN OUT PTASK_SWITCH_WND This,
402 IN PTASK_GROUP TaskGroup)
403 {
404 ASSERT(TaskGroup->dwTaskCount > 0);
405 ASSERT(TaskGroup->IsCollapsed);
406 ASSERT(TaskGroup->Index >= 0);
407
408 /* FIXME: Implement */
409 }
410
411 static INT
412 TaskSwitchWnd_UpdateTaskItemButton(IN OUT PTASK_SWITCH_WND This,
413 IN PTASK_ITEM TaskItem)
414 {
415 TBBUTTONINFO tbbi;
416
417 ASSERT(TaskItem->Index >= 0);
418
419 tbbi.cbSize = sizeof(tbbi);
420 tbbi.dwMask = TBIF_BYINDEX | TBIF_STATE | TBIF_TEXT;
421 tbbi.fsState = TBSTATE_ENABLED;
422 if (This->ActiveTaskItem == TaskItem)
423 tbbi.fsState |= TBSTATE_CHECKED;
424
425 /* Check if we're updating a button that is the last one in the
426 line. If so, we need to set the TBSTATE_WRAP flag! */
427 if (This->TbButtonsPerLine != 0 &&
428 (TaskItem->Index + 1) % This->TbButtonsPerLine == 0)
429 {
430 tbbi.fsState |= TBSTATE_WRAP;
431 }
432
433 tbbi.pszText = TaskSwitchWnd_GetWndTextFromTaskItem(This,
434 TaskItem);
435
436 if (!SendMessage(This->hWndToolbar,
437 TB_SETBUTTONINFO,
438 (WPARAM)TaskItem->Index,
439 (LPARAM)&tbbi))
440 {
441 TaskItem->Index = -1;
442 return -1;
443 }
444
445 DbgPrint("Updated button %d for hwnd 0x%p\n", TaskItem->Index, TaskItem->hWnd);
446 return TaskItem->Index;
447 }
448
449 static PTASK_ITEM
450 TaskSwitchWnd_FindLastTaskItemOfGroup(IN OUT PTASK_SWITCH_WND This,
451 IN PTASK_GROUP TaskGroup OPTIONAL,
452 IN PTASK_ITEM NewTaskItem OPTIONAL)
453 {
454 PTASK_ITEM TaskItem, LastTaskItem, FoundTaskItem = NULL;
455 DWORD dwTaskCount;
456
457 ASSERT(This->IsGroupingEnabled);
458
459 TaskItem = This->TaskItems;
460 LastTaskItem = TaskItem + This->TaskItemCount;
461
462 dwTaskCount = (TaskGroup != NULL ? TaskGroup->dwTaskCount : MAX_TASKS_COUNT);
463
464 ASSERT(dwTaskCount > 0);
465
466 while (TaskItem != LastTaskItem)
467 {
468 if (TaskItem->Group == TaskGroup)
469 {
470 if ((NewTaskItem != NULL && TaskItem != NewTaskItem) || NewTaskItem == NULL)
471 {
472 FoundTaskItem = TaskItem;
473 }
474
475 if (--dwTaskCount == 0)
476 {
477 /* We found the last task item in the group! */
478 break;
479 }
480 }
481
482 TaskItem++;
483 }
484
485 return FoundTaskItem;
486 }
487
488 static INT
489 TaskSwitchWnd_CalculateTaskItemNewButtonIndex(IN OUT PTASK_SWITCH_WND This,
490 IN PTASK_ITEM TaskItem)
491 {
492 PTASK_GROUP TaskGroup;
493 PTASK_ITEM LastTaskItem;
494
495 /* NOTE: This routine assumes that the group is *not* collapsed! */
496
497 TaskGroup = TaskItem->Group;
498 if (This->IsGroupingEnabled)
499 {
500 if (TaskGroup != NULL)
501 {
502 ASSERT(TaskGroup->Index < 0);
503 ASSERT(!TaskGroup->IsCollapsed);
504
505 if (TaskGroup->dwTaskCount > 1)
506 {
507 LastTaskItem = TaskSwitchWnd_FindLastTaskItemOfGroup(This,
508 TaskGroup,
509 TaskItem);
510 if (LastTaskItem != NULL)
511 {
512 /* Since the group is expanded the task items must have an index */
513 ASSERT(LastTaskItem->Index >= 0);
514
515 return LastTaskItem->Index + 1;
516 }
517 }
518 }
519 else
520 {
521 /* Find the last NULL group button. NULL groups are added at the end of the
522 task item list when grouping is enabled */
523 LastTaskItem = TaskSwitchWnd_FindLastTaskItemOfGroup(This,
524 NULL,
525 TaskItem);
526 if (LastTaskItem != NULL)
527 {
528 ASSERT(LastTaskItem->Index >= 0);
529
530 return LastTaskItem->Index + 1;
531 }
532 }
533 }
534
535 return This->ToolbarBtnCount;
536 }
537
538 static INT
539 TaskSwitchWnd_AddTaskItemButton(IN OUT PTASK_SWITCH_WND This,
540 IN OUT PTASK_ITEM TaskItem)
541 {
542 TBBUTTON tbBtn;
543 INT iIndex;
544
545 if (TaskItem->Index >= 0)
546 {
547 return TaskSwitchWnd_UpdateTaskItemButton(This,
548 TaskItem);
549 }
550
551 if (TaskItem->Group != NULL &&
552 TaskItem->Group->IsCollapsed)
553 {
554 /* The task group is collapsed, we only need to update the group button */
555 return TaskSwitchWnd_UpdateTaskGroupButton(This,
556 TaskItem->Group);
557 }
558
559 tbBtn.iBitmap = 0;
560 tbBtn.fsState = TBSTATE_ENABLED | TBSTATE_ELLIPSES;
561 tbBtn.fsStyle = BTNS_CHECK | BTNS_NOPREFIX | BTNS_SHOWTEXT;
562 tbBtn.dwData = TaskItem->Index;
563
564 tbBtn.iString = (DWORD_PTR)TaskSwitchWnd_GetWndTextFromTaskItem(This,
565 TaskItem);
566
567 /* Find out where to insert the new button */
568 iIndex = TaskSwitchWnd_CalculateTaskItemNewButtonIndex(This,
569 TaskItem);
570 ASSERT(iIndex >= 0);
571 tbBtn.idCommand = iIndex;
572
573 TaskSwitchWnd_BeginUpdate(This);
574
575 if (SendMessage(This->hWndToolbar,
576 TB_INSERTBUTTON,
577 (WPARAM)iIndex,
578 (LPARAM)&tbBtn))
579 {
580 TaskSwitchWnd_UpdateIndexesAfterButtonInserted(This,
581 iIndex);
582
583 DbgPrint("Added button %d for hwnd 0x%p\n", iIndex, TaskItem->hWnd);
584
585 TaskItem->Index = iIndex;
586 This->ToolbarBtnCount++;
587
588 /* Update button sizes and fix the button wrapping */
589 TaskSwitchWnd_UpdateButtonsSize(This,
590 TRUE);
591 return iIndex;
592 }
593
594 TaskSwitchWnd_EndUpdate(This);
595
596 return -1;
597 }
598
599 static BOOL
600 TaskSwitchWnd_DeleteTaskItemButton(IN OUT PTASK_SWITCH_WND This,
601 IN OUT PTASK_ITEM TaskItem)
602 {
603 PTASK_GROUP TaskGroup;
604 INT iIndex;
605
606 TaskGroup = TaskItem->Group;
607
608 if (TaskItem->Index >= 0)
609 {
610 if ((TaskGroup != NULL && !TaskGroup->IsCollapsed) ||
611 TaskGroup == NULL)
612 {
613 TaskSwitchWnd_BeginUpdate(This);
614
615 iIndex = TaskItem->Index;
616 if (SendMessage(This->hWndToolbar,
617 TB_DELETEBUTTON,
618 (WPARAM)iIndex,
619 0))
620 {
621 TaskItem->Index = -1;
622 This->ToolbarBtnCount--;
623
624 TaskSwitchWnd_UpdateIndexesAfterButtonDeleted(This,
625 iIndex);
626
627 /* Update button sizes and fix the button wrapping */
628 TaskSwitchWnd_UpdateButtonsSize(This,
629 TRUE);
630 return TRUE;
631 }
632
633 TaskSwitchWnd_EndUpdate(This);
634 }
635 }
636
637 return FALSE;
638 }
639
640 static PTASK_GROUP
641 TaskSwitchWnd_AddToTaskGroup(IN OUT PTASK_SWITCH_WND This,
642 IN HWND hWnd)
643 {
644 DWORD dwProcessId;
645 PTASK_GROUP TaskGroup, *PrevLink;
646
647 if (!GetWindowThreadProcessId(hWnd,
648 &dwProcessId))
649 {
650 DbgPrint("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 = This->TaskGroups;
656 PrevLink = &This->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 = HeapAlloc(hProcessHeap,
671 0,
672 sizeof(*TaskGroup));
673 if (TaskGroup != NULL)
674 {
675 ZeroMemory(TaskGroup,
676 sizeof(*TaskGroup));
677
678 TaskGroup->dwTaskCount = 1;
679 TaskGroup->dwProcessId = dwProcessId;
680 TaskGroup->Index = -1;
681
682 /* Add the task group to the list */
683 *PrevLink = TaskGroup;
684 }
685
686 return TaskGroup;
687 }
688
689 static VOID
690 TaskSwitchWnd_RemoveTaskFromTaskGroup(IN OUT PTASK_SWITCH_WND This,
691 IN OUT PTASK_ITEM TaskItem)
692 {
693 PTASK_GROUP TaskGroup, CurrentGroup, *PrevLink;
694
695 TaskGroup = TaskItem->Group;
696 if (TaskGroup != NULL)
697 {
698 DWORD dwNewTaskCount = --TaskGroup->dwTaskCount;
699 if (dwNewTaskCount == 0)
700 {
701 /* Find the previous pointer in the chain */
702 CurrentGroup = This->TaskGroups;
703 PrevLink = &This->TaskGroups;
704 while (CurrentGroup != TaskGroup)
705 {
706 PrevLink = &CurrentGroup->Next;
707 CurrentGroup = CurrentGroup->Next;
708 }
709
710 /* Remove the group from the list */
711 ASSERT(TaskGroup == CurrentGroup);
712 *PrevLink = TaskGroup->Next;
713
714 /* Free the task group */
715 HeapFree(hProcessHeap,
716 0,
717 TaskGroup);
718 }
719 else if (TaskGroup->IsCollapsed &&
720 TaskGroup->Index >= 0)
721 {
722 if (dwNewTaskCount > 1)
723 {
724 /* FIXME: Check if we should expand the group */
725 /* Update the task group button */
726 TaskSwitchWnd_UpdateTaskGroupButton(This,
727 TaskGroup);
728 }
729 else
730 {
731 /* Expand the group of one task button to a task button */
732 TaskSwitchWnd_ExpandTaskGroup(This,
733 TaskGroup);
734 }
735 }
736 }
737 }
738
739 static PTASK_ITEM
740 TaskSwitchWnd_FindTaskItem(IN OUT PTASK_SWITCH_WND This,
741 IN HWND hWnd)
742 {
743 PTASK_ITEM TaskItem, LastItem;
744
745 TaskItem = This->TaskItems;
746 LastItem = TaskItem + This->TaskItemCount;
747 while (TaskItem != LastItem)
748 {
749 if (TaskItem->hWnd == hWnd)
750 return TaskItem;
751
752 TaskItem++;
753 }
754
755 return NULL;
756 }
757
758 static PTASK_ITEM
759 TaskSwitchWnd_FindOtherTaskItem(IN OUT PTASK_SWITCH_WND This,
760 IN HWND hWnd)
761 {
762 PTASK_ITEM LastItem, TaskItem;
763 PTASK_GROUP TaskGroup;
764 DWORD dwProcessId;
765
766 if (!GetWindowThreadProcessId(hWnd,
767 &dwProcessId))
768 {
769 return NULL;
770 }
771
772 /* Try to find another task that belongs to the same
773 process as the given window */
774 TaskItem = This->TaskItems;
775 LastItem = TaskItem + This->TaskItemCount;
776 while (TaskItem != LastItem)
777 {
778 TaskGroup = TaskItem->Group;
779 if (TaskGroup != NULL)
780 {
781 if (TaskGroup->dwProcessId == dwProcessId)
782 return TaskItem;
783 }
784 else
785 {
786 DWORD dwProcessIdTask;
787
788 if (GetWindowThreadProcessId(TaskItem->hWnd,
789 &dwProcessIdTask) &&
790 dwProcessIdTask == dwProcessId)
791 {
792 return TaskItem;
793 }
794 }
795
796 TaskItem++;
797 }
798
799 return NULL;
800 }
801
802 static PTASK_ITEM
803 TaskSwitchWnd_AllocTaskItem(IN OUT PTASK_SWITCH_WND This)
804 {
805 if (This->TaskItemCount >= MAX_TASKS_COUNT)
806 {
807 /* We need the most significant bit in 16 bit command IDs to indicate whether it
808 is a task group or task item. WM_COMMAND limits command IDs to 16 bits! */
809 return NULL;
810 }
811
812 ASSERT(This->AllocatedTaskItems >= This->TaskItemCount);
813
814 if (This->TaskItemCount != 0)
815 {
816 PTASK_ITEM NewArray;
817 SIZE_T NewArrayLength, ActiveTaskItemIndex;
818
819 NewArrayLength = This->AllocatedTaskItems + TASK_ITEM_ARRAY_ALLOC;
820
821 NewArray = HeapReAlloc(hProcessHeap,
822 0,
823 This->TaskItems,
824 NewArrayLength * sizeof(*This->TaskItems));
825 if (NewArray != NULL)
826 {
827 if (This->ActiveTaskItem != NULL)
828 {
829 /* Fixup the ActiveTaskItem pointer */
830 ActiveTaskItemIndex = This->ActiveTaskItem - This->TaskItems;
831 This->ActiveTaskItem = NewArray + ActiveTaskItemIndex;
832 }
833 This->AllocatedTaskItems = (WORD)NewArrayLength;
834 This->TaskItems = NewArray;
835 }
836 else
837 return NULL;
838 }
839 else
840 {
841 This->TaskItems = HeapAlloc(hProcessHeap,
842 0,
843 TASK_ITEM_ARRAY_ALLOC * sizeof(*This->TaskItems));
844 if (This->TaskItems != NULL)
845 {
846 This->AllocatedTaskItems = TASK_ITEM_ARRAY_ALLOC;
847 }
848 else
849 return NULL;
850 }
851
852 return This->TaskItems + This->TaskItemCount++;
853 }
854
855 static VOID
856 TaskSwitchWnd_FreeTaskItem(IN OUT PTASK_SWITCH_WND This,
857 IN OUT PTASK_ITEM TaskItem)
858 {
859 WORD wIndex;
860
861 if (TaskItem == This->ActiveTaskItem)
862 This->ActiveTaskItem = NULL;
863
864 wIndex = (WORD)(TaskItem - This->TaskItems);
865 if (wIndex + 1 < This->TaskItemCount)
866 {
867 MoveMemory(TaskItem,
868 TaskItem + 1,
869 (This->TaskItemCount - wIndex - 1) * sizeof(*TaskItem));
870 }
871
872 This->TaskItemCount--;
873 }
874
875 static VOID
876 TaskSwitchWnd_DeleteTaskItem(IN OUT PTASK_SWITCH_WND This,
877 IN OUT PTASK_ITEM TaskItem)
878 {
879 if (!This->IsDestroying)
880 {
881 /* Delete the task button from the toolbar */
882 TaskSwitchWnd_DeleteTaskItemButton(This,
883 TaskItem);
884 }
885
886 /* Remove the task from it's group */
887 TaskSwitchWnd_RemoveTaskFromTaskGroup(This,
888 TaskItem);
889
890 /* Free the task item */
891 TaskSwitchWnd_FreeTaskItem(This,
892 TaskItem);
893 }
894
895 static VOID
896 TaskSwitchWnd_CheckActivateTaskItem(IN OUT PTASK_SWITCH_WND This,
897 IN OUT PTASK_ITEM TaskItem)
898 {
899 PTASK_ITEM ActiveTaskItem;
900 PTASK_GROUP TaskGroup = NULL;
901
902 ActiveTaskItem = This->ActiveTaskItem;
903
904 if (TaskItem != NULL)
905 TaskGroup = TaskItem->Group;
906
907 if (This->IsGroupingEnabled && TaskGroup != NULL)
908 {
909 if (TaskGroup->IsCollapsed)
910 {
911 /* FIXME */
912 }
913 else
914 goto ChangeTaskItemButton;
915 }
916 else
917 {
918 ChangeTaskItemButton:
919 if (ActiveTaskItem != NULL)
920 {
921 PTASK_GROUP ActiveTaskGroup;
922
923 if (ActiveTaskItem == TaskItem)
924 return;
925
926 ActiveTaskGroup = ActiveTaskItem->Group;
927
928 if (This->IsGroupingEnabled && ActiveTaskGroup != NULL)
929 {
930 if (ActiveTaskGroup->IsCollapsed)
931 {
932 if (ActiveTaskGroup == TaskGroup)
933 return;
934
935 /* FIXME */
936 }
937 else
938 goto ChangeActiveTaskItemButton;
939 }
940 else
941 {
942 ChangeActiveTaskItemButton:
943 This->ActiveTaskItem = NULL;
944 if (ActiveTaskItem->Index >= 0)
945 {
946 TaskSwitchWnd_UpdateTaskItemButton(This,
947 ActiveTaskItem);
948 }
949 }
950 }
951
952 This->ActiveTaskItem = TaskItem;
953
954 if (TaskItem != NULL && TaskItem->Index >= 0)
955 {
956 TaskSwitchWnd_UpdateTaskItemButton(This,
957 TaskItem);
958 }
959 }
960 }
961
962 static PTASK_ITEM
963 FindTaskItemByIndex(IN OUT PTASK_SWITCH_WND This,
964 IN INT Index)
965 {
966 PTASK_ITEM TaskItem, LastItem;
967
968 TaskItem = This->TaskItems;
969 LastItem = TaskItem + This->TaskItemCount;
970 while (TaskItem != LastItem)
971 {
972 if (TaskItem->Index == Index)
973 return TaskItem;
974
975 TaskItem++;
976 }
977
978 return NULL;
979 }
980
981 static PTASK_GROUP
982 FindTaskGroupByIndex(IN OUT PTASK_SWITCH_WND This,
983 IN INT Index)
984 {
985 PTASK_GROUP CurrentGroup;
986
987 CurrentGroup = This->TaskGroups;
988 while (CurrentGroup != NULL)
989 {
990 if (CurrentGroup->Index == Index)
991 break;
992
993 CurrentGroup = CurrentGroup->Next;
994 }
995
996 return CurrentGroup;
997 }
998
999 static BOOL
1000 TaskSwitchWnd_AddTask(IN OUT PTASK_SWITCH_WND This,
1001 IN HWND hWnd)
1002 {
1003 PTASK_ITEM TaskItem;
1004
1005 TaskItem = TaskSwitchWnd_FindTaskItem(This,
1006 hWnd);
1007 if (TaskItem == NULL)
1008 {
1009 DbgPrint("Add window 0x%p\n", hWnd);
1010 TaskItem = TaskSwitchWnd_AllocTaskItem(This);
1011 if (TaskItem != NULL)
1012 {
1013 ZeroMemory(TaskItem,
1014 sizeof(*TaskItem));
1015 TaskItem->hWnd = hWnd;
1016 TaskItem->Index = -1;
1017 TaskItem->Group = TaskSwitchWnd_AddToTaskGroup(This,
1018 hWnd);
1019
1020 if (!This->IsDestroying)
1021 {
1022 TaskSwitchWnd_AddTaskItemButton(This,
1023 TaskItem);
1024 }
1025 }
1026 }
1027
1028 return TaskItem != NULL;
1029 }
1030
1031 static BOOL
1032 TaskSwitchWnd_ActivateTaskItem(IN OUT PTASK_SWITCH_WND This,
1033 IN OUT PTASK_ITEM TaskItem OPTIONAL)
1034 {
1035 if (TaskItem != NULL)
1036 {
1037 DbgPrint("Activate window 0x%p on button %d\n", TaskItem->hWnd, TaskItem->Index);
1038 }
1039
1040 TaskSwitchWnd_CheckActivateTaskItem(This,
1041 TaskItem);
1042
1043 return FALSE;
1044 }
1045
1046 static BOOL
1047 TaskSwitchWnd_ActivateTask(IN OUT PTASK_SWITCH_WND This,
1048 IN HWND hWnd)
1049 {
1050 PTASK_ITEM TaskItem;
1051
1052 TaskItem = TaskSwitchWnd_FindTaskItem(This,
1053 hWnd);
1054 if (TaskItem == NULL)
1055 {
1056 TaskItem = TaskSwitchWnd_FindOtherTaskItem(This,
1057 hWnd);
1058 }
1059
1060 if (TaskItem == NULL)
1061 {
1062 DbgPrint("Activate window 0x%p, could not find task\n", hWnd);
1063 }
1064
1065 return TaskSwitchWnd_ActivateTaskItem(This,
1066 TaskItem);
1067 }
1068
1069 static BOOL
1070 TaskSwitchWnd_DeleteTask(IN OUT PTASK_SWITCH_WND This,
1071 IN HWND hWnd)
1072 {
1073 PTASK_ITEM TaskItem;
1074
1075 TaskItem = TaskSwitchWnd_FindTaskItem(This,
1076 hWnd);
1077 if (TaskItem != NULL)
1078 {
1079 DbgPrint("Delete window 0x%p on button %d\n", hWnd, TaskItem->Index);
1080 TaskSwitchWnd_DeleteTaskItem(This,
1081 TaskItem);
1082 return TRUE;
1083 }
1084 //else
1085 //DbgPrint("Failed to delete window 0x%p\n", hWnd);
1086
1087 return FALSE;
1088 }
1089
1090 static VOID
1091 TaskSwitchWnd_DeleteAllTasks(IN OUT PTASK_SWITCH_WND This)
1092 {
1093 PTASK_ITEM CurrentTask;
1094
1095 if (This->TaskItemCount > 0)
1096 {
1097 CurrentTask = This->TaskItems + This->TaskItemCount;
1098 do
1099 {
1100 TaskSwitchWnd_DeleteTaskItem(This,
1101 --CurrentTask);
1102 } while (CurrentTask != This->TaskItems);
1103 }
1104 }
1105
1106 static VOID
1107 TaskSwitchWnd_FlashTaskItem(IN OUT PTASK_SWITCH_WND This,
1108 IN OUT PTASK_ITEM TaskItem)
1109 {
1110 /* FIXME: Implement */
1111 }
1112
1113 static BOOL
1114 TaskSwitchWnd_FlashTask(IN OUT PTASK_SWITCH_WND This,
1115 IN HWND hWnd)
1116 {
1117 PTASK_ITEM TaskItem;
1118
1119 TaskItem = TaskSwitchWnd_FindTaskItem(This,
1120 hWnd);
1121 if (TaskItem != NULL)
1122 {
1123 DbgPrint("Flashing window 0x%p on button %d\n", hWnd, TaskItem->Index);
1124 TaskSwitchWnd_FlashTaskItem(This,
1125 TaskItem);
1126 return TRUE;
1127 }
1128
1129 return FALSE;
1130 }
1131
1132 static VOID
1133 TaskSwitchWnd_RedrawTaskItem(IN OUT PTASK_SWITCH_WND This,
1134 IN OUT PTASK_ITEM TaskItem)
1135 {
1136 PTASK_GROUP TaskGroup;
1137
1138 TaskGroup = TaskItem->Group;
1139 if (This->IsGroupingEnabled && TaskGroup != NULL)
1140 {
1141 if (TaskGroup->IsCollapsed && TaskGroup->Index >= 0)
1142 {
1143 TaskSwitchWnd_UpdateTaskGroupButton(This,
1144 TaskGroup);
1145 }
1146 else if (TaskItem->Index >= 0)
1147 {
1148 goto UpdateTaskItem;
1149 }
1150 }
1151 else if (TaskItem->Index >= 0)
1152 {
1153 UpdateTaskItem:
1154 TaskSwitchWnd_UpdateTaskItemButton(This,
1155 TaskItem);
1156 }
1157 }
1158
1159
1160 static BOOL
1161 TaskSwitchWnd_RedrawTask(IN OUT PTASK_SWITCH_WND This,
1162 IN HWND hWnd)
1163 {
1164 PTASK_ITEM TaskItem;
1165
1166 TaskItem = TaskSwitchWnd_FindTaskItem(This,
1167 hWnd);
1168 if (TaskItem != NULL)
1169 {
1170 TaskSwitchWnd_RedrawTaskItem(This,
1171 TaskItem);
1172 return TRUE;
1173 }
1174
1175 return FALSE;
1176 }
1177
1178 static INT
1179 TaskSwitchWnd_UpdateTbButtonSpacing(IN OUT PTASK_SWITCH_WND This,
1180 IN BOOL bHorizontal,
1181 IN UINT uiRows,
1182 IN UINT uiBtnsPerLine)
1183 {
1184 TBMETRICS tbm;
1185
1186 tbm.cbSize = sizeof(tbm);
1187 tbm.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING;
1188
1189 tbm.cxBarPad = tbm.cyBarPad = 0;
1190
1191 if (bHorizontal || uiBtnsPerLine > 1)
1192 tbm.cxButtonSpacing = (3 * GetSystemMetrics(SM_CXEDGE) / 2);
1193 else
1194 tbm.cxButtonSpacing = 0;
1195
1196 if (!bHorizontal || uiRows > 1)
1197 tbm.cyButtonSpacing = (3 * GetSystemMetrics(SM_CYEDGE) / 2);
1198 else
1199 tbm.cyButtonSpacing = 0;
1200
1201 SendMessage(This->hWndToolbar,
1202 TB_SETMETRICS,
1203 0,
1204 (LPARAM)&tbm);
1205
1206 return tbm.cxButtonSpacing;
1207 }
1208
1209
1210 static VOID
1211 TaskSwitchWnd_UpdateButtonsSize(IN OUT PTASK_SWITCH_WND This,
1212 IN BOOL bRedrawDisabled)
1213 {
1214 RECT rcClient;
1215 UINT uiRows, uiMax, uiMin, uiBtnsPerLine, ui;
1216 LONG NewBtnSize;
1217 BOOL Horizontal;
1218 TBBUTTONINFO tbbi;
1219 TBMETRICS tbm;
1220
1221 if (GetClientRect(This->hWnd,
1222 &rcClient) &&
1223 !IsRectEmpty(&rcClient))
1224 {
1225 if (This->ToolbarBtnCount > 0)
1226 {
1227 ZeroMemory (&tbm, sizeof (tbm));
1228 tbm.cbSize = sizeof(tbm);
1229 tbm.dwMask = TBMF_BUTTONSPACING;
1230 SendMessage(This->hWndToolbar,
1231 TB_GETMETRICS,
1232 0,
1233 (LPARAM)&tbm);
1234
1235 uiRows = (rcClient.bottom + tbm.cyButtonSpacing) / (This->ButtonSize.cy + tbm.cyButtonSpacing);
1236 if (uiRows == 0)
1237 uiRows = 1;
1238
1239 uiBtnsPerLine = (This->ToolbarBtnCount + uiRows - 1) / uiRows;
1240
1241 Horizontal = ITrayWindow_IsHorizontal(This->Tray);
1242
1243 if (!bRedrawDisabled)
1244 TaskSwitchWnd_BeginUpdate(This);
1245
1246 /* We might need to update the button spacing */
1247 tbm.cxButtonSpacing = TaskSwitchWnd_UpdateTbButtonSpacing(This,
1248 Horizontal,
1249 uiRows,
1250 uiBtnsPerLine);
1251
1252 /* Calculate the ideal width and make sure it's within the allowed range */
1253 NewBtnSize = (rcClient.right - (uiBtnsPerLine * tbm.cxButtonSpacing)) / uiBtnsPerLine;
1254
1255 /* Determine the minimum and maximum width of a button */
1256 if (Horizontal)
1257 uiMax = GetSystemMetrics(SM_CXMINIMIZED);
1258 else
1259 uiMax = rcClient.right;
1260
1261 uiMin = GetSystemMetrics(SM_CXSIZE) + (2 * GetSystemMetrics(SM_CXEDGE));
1262
1263 if (NewBtnSize < (LONG)uiMin)
1264 NewBtnSize = uiMin;
1265 if (NewBtnSize > (LONG)uiMax)
1266 NewBtnSize = uiMax;
1267
1268 This->ButtonSize.cx = NewBtnSize;
1269
1270 /* Recalculate how many buttons actually fit into one line */
1271 uiBtnsPerLine = rcClient.right / (NewBtnSize + tbm.cxButtonSpacing);
1272 if (uiBtnsPerLine == 0)
1273 uiBtnsPerLine++;
1274 This->TbButtonsPerLine = uiBtnsPerLine;
1275
1276 tbbi.cbSize = sizeof(tbbi);
1277 tbbi.dwMask = TBIF_BYINDEX | TBIF_SIZE | TBIF_STATE;
1278 tbbi.cx = (INT)NewBtnSize;
1279
1280 for (ui = 0; ui != This->ToolbarBtnCount; ui++)
1281 {
1282 tbbi.fsState = TBSTATE_ENABLED;
1283
1284 /* Check if we're updating a button that is the last one in the
1285 line. If so, we need to set the TBSTATE_WRAP flag! */
1286 if ((ui + 1) % uiBtnsPerLine == 0)
1287 tbbi.fsState |= TBSTATE_WRAP;
1288
1289 if (This->ActiveTaskItem != NULL &&
1290 This->ActiveTaskItem->Index == ui)
1291 {
1292 tbbi.fsState |= TBSTATE_CHECKED;
1293 }
1294
1295 SendMessage(This->hWndToolbar,
1296 TB_SETBUTTONINFO,
1297 (WPARAM)ui,
1298 (LPARAM)&tbbi);
1299 }
1300
1301 #if 0
1302 /* FIXME: Force the window to the correct position in case some idiot
1303 did something to us */
1304 SetWindowPos(This->hWndToolbar,
1305 NULL,
1306 0,
1307 0,
1308 rcClient.right, /* FIXME */
1309 rcClient.bottom, /* FIXME */
1310 SWP_NOACTIVATE | SWP_NOZORDER);
1311 #endif
1312 }
1313 else
1314 {
1315 This->TbButtonsPerLine = 0;
1316 This->ButtonSize.cx = 0;
1317 }
1318 }
1319
1320 TaskSwitchWnd_EndUpdate(This);
1321 }
1322
1323 static BOOL CALLBACK
1324 TaskSwitchWnd_EnumWindowsProc(IN HWND hWnd,
1325 IN LPARAM lParam)
1326 {
1327 PTASK_SWITCH_WND This = (PTASK_SWITCH_WND)lParam;
1328
1329 /* Only show windows that still exist and are visible and none of explorer's
1330 special windows (such as the desktop or the tray window) */
1331 if (IsWindow(hWnd) && IsWindowVisible(hWnd) &&
1332 !ITrayWindow_IsSpecialHWND(This->Tray,
1333 hWnd))
1334 {
1335 /* Don't list popup windows and also no tool windows */
1336 if (GetWindow(hWnd,
1337 GW_OWNER) == NULL &&
1338 !(GetWindowLongPtr(hWnd,
1339 GWL_EXSTYLE) & WS_EX_TOOLWINDOW))
1340 {
1341 TaskSwitchWnd_AddTask(This,
1342 hWnd);
1343 }
1344 }
1345
1346 return TRUE;
1347 }
1348
1349 static LRESULT CALLBACK
1350 TaskSwichWnd_ToolbarSubclassedProc(IN HWND hWnd,
1351 IN UINT msg,
1352 IN WPARAM wParam,
1353 IN LPARAM lParam,
1354 IN UINT_PTR uIdSubclass,
1355 IN DWORD_PTR dwRefData)
1356 {
1357 LRESULT Ret;
1358
1359 Ret = DefSubclassProc(hWnd,
1360 msg,
1361 wParam,
1362 lParam);
1363
1364 if (msg == WM_NCHITTEST && Ret == HTCLIENT)
1365 {
1366 POINT pt;
1367
1368 /* See if the mouse is on a button */
1369 pt.x = (SHORT)LOWORD(lParam);
1370 pt.y = (SHORT)HIWORD(lParam);
1371
1372 if (MapWindowPoints(HWND_DESKTOP,
1373 hWnd,
1374 &pt,
1375 1) != 0 &&
1376 (INT)SendMessage(hWnd,
1377 TB_HITTEST,
1378 0,
1379 (LPARAM)&pt) < 0)
1380 {
1381 /* Make the control appear to be transparent outside of any buttons */
1382 Ret = HTTRANSPARENT;
1383 }
1384 }
1385
1386 return Ret;
1387 }
1388
1389 static VOID
1390 TaskSwitchWnd_Create(IN OUT PTASK_SWITCH_WND This)
1391 {
1392 This->hWndToolbar = CreateWindowEx(0,
1393 TOOLBARCLASSNAME,
1394 szRunningApps,
1395 WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
1396 TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | TBSTYLE_LIST |
1397 TBSTYLE_TRANSPARENT |
1398 CCS_TOP | CCS_NORESIZE | CCS_NODIVIDER,
1399 0,
1400 0,
1401 0,
1402 0,
1403 This->hWnd,
1404 NULL,
1405 hExplorerInstance,
1406 NULL);
1407
1408 if (This->hWndToolbar != NULL)
1409 {
1410 HMODULE hShell32;
1411 SIZE BtnSize;
1412
1413 /* Identify the version we're using */
1414 SendMessage(This->hWndToolbar,
1415 TB_BUTTONSTRUCTSIZE,
1416 sizeof(TBBUTTON),
1417 0);
1418
1419 /* Calculate the default button size. Don't save this in This->ButtonSize.cx so that
1420 the actual button width gets updated correctly on the first recalculation */
1421 BtnSize.cx = GetSystemMetrics(SM_CXMINIMIZED);
1422 This->ButtonSize.cy = BtnSize.cy = GetSystemMetrics(SM_CYSIZE) + (2 * GetSystemMetrics(SM_CYEDGE));
1423 SendMessage(This->hWndToolbar,
1424 TB_SETBUTTONSIZE,
1425 0,
1426 (LPARAM)MAKELONG(BtnSize.cx,
1427 BtnSize.cy));
1428
1429 /* We don't want to see partially clipped buttons...not that we could see them... */
1430 #if 0
1431 SendMessage(This->hWndToolbar,
1432 TB_SETEXTENDEDSTYLE,
1433 0,
1434 TBSTYLE_EX_HIDECLIPPEDBUTTONS);
1435 #endif
1436
1437 /* Set proper spacing between buttons */
1438 TaskSwitchWnd_UpdateTbButtonSpacing(This,
1439 ITrayWindow_IsHorizontal(This->Tray),
1440 0,
1441 0);
1442
1443 /* Register the shell hook */
1444 This->ShellHookMsg = RegisterWindowMessage(TEXT("SHELLHOOK"));
1445 hShell32 = GetModuleHandle(TEXT("SHELL32.DLL"));
1446 if (hShell32 != NULL)
1447 {
1448 REGSHELLHOOK RegShellHook;
1449
1450 /* RegisterShellHook */
1451 RegShellHook = (REGSHELLHOOK)GetProcAddress(hShell32,
1452 (LPCSTR)((LONG)181));
1453 if (RegShellHook != NULL)
1454 {
1455 RegShellHook(This->hWnd,
1456 3); /* 1 if no NT! We're targeting NT so we don't care! */
1457 }
1458 }
1459
1460 /* Add all windows to the toolbar */
1461 EnumWindows(TaskSwitchWnd_EnumWindowsProc,
1462 (LPARAM)This);
1463
1464 /* Recalculate the button size */
1465 TaskSwitchWnd_UpdateButtonsSize(This,
1466 FALSE);
1467
1468 /* Subclass the toolbar control because it doesn't provide a
1469 NM_NCHITTEST notification */
1470 This->IsToolbarSubclassed = SetWindowSubclass(This->hWndToolbar,
1471 TaskSwichWnd_ToolbarSubclassedProc,
1472 TSW_TOOLBAR_SUBCLASS_ID,
1473 (DWORD_PTR)This);
1474 }
1475 }
1476
1477 static VOID
1478 TaskSwitchWnd_NCDestroy(IN OUT PTASK_SWITCH_WND This)
1479 {
1480 HMODULE hShell32;
1481
1482 This->IsDestroying = TRUE;
1483
1484 /* Unregister the shell hook */
1485 hShell32 = GetModuleHandle(TEXT("SHELL32.DLL"));
1486 if (hShell32 != NULL)
1487 {
1488 REGSHELLHOOK RegShellHook;
1489
1490 /* RegisterShellHook */
1491 RegShellHook = (REGSHELLHOOK)GetProcAddress(hShell32,
1492 (LPCSTR)((LONG)181));
1493 if (RegShellHook != NULL)
1494 {
1495 RegShellHook(This->hWnd,
1496 FALSE);
1497 }
1498 }
1499
1500 TaskSwitchWnd_DeleteAllTasks(This);
1501 }
1502
1503 static BOOL
1504 TaskSwitchWnd_HandleAppCommand(IN OUT PTASK_SWITCH_WND This,
1505 IN WPARAM wParam,
1506 IN LPARAM lParam)
1507 {
1508 BOOL Ret = FALSE;
1509
1510 switch (GET_APPCOMMAND_LPARAM(lParam))
1511 {
1512 case APPCOMMAND_BROWSER_SEARCH:
1513 Ret = SHFindFiles(NULL,
1514 NULL);
1515 break;
1516
1517 case APPCOMMAND_BROWSER_HOME:
1518 case APPCOMMAND_LAUNCH_MAIL:
1519 default:
1520 DbgPrint("Shell app command %d unhandled!\n", (INT)GET_APPCOMMAND_LPARAM(lParam));
1521 break;
1522 }
1523
1524 return Ret;
1525 }
1526
1527
1528 static LRESULT
1529 TaskSwitchWnd_HandleShellHookMsg(IN OUT PTASK_SWITCH_WND This,
1530 IN WPARAM wParam,
1531 IN LPARAM lParam)
1532 {
1533 BOOL Ret = FALSE;
1534
1535 switch ((INT)wParam)
1536 {
1537 case HSHELL_APPCOMMAND:
1538 TaskSwitchWnd_HandleAppCommand(This,
1539 wParam,
1540 lParam);
1541 Ret = TRUE;
1542 break;
1543
1544 case HSHELL_WINDOWCREATED:
1545 TaskSwitchWnd_AddTask(This,
1546 (HWND)lParam);
1547 Ret = TRUE;
1548 break;
1549
1550 case HSHELL_WINDOWDESTROYED:
1551 /* The window still exists! Delay destroying it a bit */
1552 TaskSwitchWnd_DeleteTask(This,
1553 (HWND)lParam);
1554 Ret = TRUE;
1555 break;
1556
1557 case HSHELL_ACTIVATESHELLWINDOW:
1558 goto UnhandledShellMessage;
1559
1560 case HSHELL_RUDEAPPACTIVATED:
1561 goto UnhandledShellMessage;
1562
1563 case HSHELL_WINDOWACTIVATED:
1564 TaskSwitchWnd_ActivateTask(This,
1565 (HWND)lParam);
1566 Ret = TRUE;
1567 break;
1568
1569 case HSHELL_GETMINRECT:
1570 goto UnhandledShellMessage;
1571
1572 case HSHELL_FLASH:
1573 TaskSwitchWnd_FlashTask(This,
1574 (HWND)lParam);
1575 Ret = TRUE;
1576 break;
1577
1578 case HSHELL_REDRAW:
1579 TaskSwitchWnd_RedrawTask(This,
1580 (HWND)lParam);
1581 Ret = TRUE;
1582 break;
1583
1584 case HSHELL_TASKMAN:
1585 case HSHELL_LANGUAGE:
1586 case HSHELL_SYSMENU:
1587 case HSHELL_ENDTASK:
1588 case HSHELL_ACCESSIBILITYSTATE:
1589 case HSHELL_WINDOWREPLACED:
1590 case HSHELL_WINDOWREPLACING:
1591 default:
1592 {
1593 static const struct {
1594 INT msg;
1595 LPCWSTR msg_name;
1596 } hshell_msg[] = {
1597 {HSHELL_WINDOWCREATED, L"HSHELL_WINDOWCREATED"},
1598 {HSHELL_WINDOWDESTROYED, L"HSHELL_WINDOWDESTROYED"},
1599 {HSHELL_ACTIVATESHELLWINDOW, L"HSHELL_ACTIVATESHELLWINDOW"},
1600 {HSHELL_WINDOWACTIVATED, L"HSHELL_WINDOWACTIVATED"},
1601 {HSHELL_GETMINRECT, L"HSHELL_GETMINRECT"},
1602 {HSHELL_REDRAW, L"HSHELL_REDRAW"},
1603 {HSHELL_TASKMAN, L"HSHELL_TASKMAN"},
1604 {HSHELL_LANGUAGE, L"HSHELL_LANGUAGE"},
1605 {HSHELL_SYSMENU, L"HSHELL_SYSMENU"},
1606 {HSHELL_ENDTASK, L"HSHELL_ENDTASK"},
1607 {HSHELL_ACCESSIBILITYSTATE, L"HSHELL_ACCESSIBILITYSTATE"},
1608 {HSHELL_APPCOMMAND, L"HSHELL_APPCOMMAND"},
1609 {HSHELL_WINDOWREPLACED, L"HSHELL_WINDOWREPLACED"},
1610 {HSHELL_WINDOWREPLACING, L"HSHELL_WINDOWREPLACING"},
1611 {HSHELL_RUDEAPPACTIVATED, L"HSHELL_RUDEAPPACTIVATED"},
1612 };
1613 int i, found;
1614 UnhandledShellMessage:
1615 for (i = 0, found = 0; i != sizeof(hshell_msg) / sizeof(hshell_msg[0]); i++)
1616 {
1617 if (hshell_msg[i].msg == (INT)wParam)
1618 {
1619 DbgPrint("Shell message %ws unhandled (lParam = 0x%p)!\n", hshell_msg[i].msg_name, lParam);
1620 found = 1;
1621 break;
1622 }
1623 }
1624 if (!found)
1625 {
1626 DbgPrint("Shell message %d unhandled (lParam = 0x%p)!\n", (INT)wParam, lParam);
1627 }
1628 break;
1629 }
1630 }
1631
1632 return Ret;
1633 }
1634
1635 static VOID
1636 TaskSwitchWnd_EnableGrouping(IN OUT PTASK_SWITCH_WND This,
1637 IN BOOL bEnable)
1638 {
1639 This->IsGroupingEnabled = bEnable;
1640
1641 /* Collapse or expand groups if neccessary */
1642 TaskSwitchWnd_UpdateButtonsSize(This,
1643 FALSE);
1644 }
1645
1646 static VOID
1647 TaskSwitchWnd_HandleTaskItemClick(IN OUT PTASK_SWITCH_WND This,
1648 IN OUT PTASK_ITEM TaskItem)
1649 {
1650 BOOL bIsMinimized;
1651 BOOL bIsActive;
1652
1653 if (IsWindow(TaskItem->hWnd))
1654 {
1655 bIsMinimized = IsIconic(TaskItem->hWnd);
1656 bIsActive = (TaskItem == This->ActiveTaskItem);
1657
1658 if (!bIsMinimized && bIsActive)
1659 {
1660 PostMessage(TaskItem->hWnd,
1661 WM_SYSCOMMAND,
1662 SC_MINIMIZE,
1663 0);
1664 }
1665 else
1666 {
1667 if (bIsMinimized)
1668 {
1669 PostMessage(TaskItem->hWnd,
1670 WM_SYSCOMMAND,
1671 SC_RESTORE,
1672 0);
1673 }
1674
1675 SetForegroundWindow(TaskItem->hWnd);
1676 }
1677 }
1678 }
1679
1680 static VOID
1681 TaskSwitchWnd_HandleTaskGroupClick(IN OUT PTASK_SWITCH_WND This,
1682 IN OUT PTASK_GROUP TaskGroup)
1683 {
1684 /* TODO: Show task group menu */
1685 }
1686
1687 static BOOL
1688 TaskSwitchWnd_HandleButtonClick(IN OUT PTASK_SWITCH_WND This,
1689 IN WORD wIndex)
1690 {
1691 PTASK_ITEM TaskItem;
1692 PTASK_GROUP TaskGroup;
1693
1694 if (This->IsGroupingEnabled)
1695 {
1696 TaskGroup = FindTaskGroupByIndex(This,
1697 (INT)wIndex);
1698 if (TaskGroup != NULL && TaskGroup->IsCollapsed)
1699 {
1700 TaskSwitchWnd_HandleTaskGroupClick(This,
1701 TaskGroup);
1702 return TRUE;
1703 }
1704 }
1705
1706 TaskItem = FindTaskItemByIndex(This,
1707 (INT)wIndex);
1708 if (TaskItem != NULL)
1709 {
1710 TaskSwitchWnd_HandleTaskItemClick(This,
1711 TaskItem);
1712 return TRUE;
1713 }
1714
1715 return FALSE;
1716 }
1717
1718 static LRESULT
1719 TaskSwichWnd_HandleItemPaint(IN OUT PTASK_SWITCH_WND This,
1720 IN OUT NMTBCUSTOMDRAW *nmtbcd)
1721 {
1722 HFONT hCaptionFont, hBoldCaptionFont;
1723 LRESULT Ret = CDRF_DODEFAULT;
1724 PTASK_GROUP TaskGroup;
1725 PTASK_ITEM TaskItem;
1726
1727 #if TASK_USE_DRAWCAPTIONTEMP != 0
1728
1729 UINT uidctFlags = DC_TEXT | DC_ICON | DC_NOSENDMSG;
1730
1731 #endif
1732 TaskItem = FindTaskItemByIndex(This,
1733 (INT)nmtbcd->nmcd.dwItemSpec);
1734 TaskGroup = FindTaskGroupByIndex(This,
1735 (INT)nmtbcd->nmcd.dwItemSpec);
1736 if (TaskGroup == NULL && TaskItem != NULL)
1737 {
1738 ASSERT(TaskItem != NULL);
1739
1740 if (TaskItem != NULL && IsWindow(TaskItem->hWnd))
1741 {
1742 hCaptionFont = ITrayWindow_GetCaptionFonts(This->Tray,
1743 &hBoldCaptionFont);
1744 if (nmtbcd->nmcd.uItemState & CDIS_CHECKED)
1745 hCaptionFont = hBoldCaptionFont;
1746
1747 #if TASK_USE_DRAWCAPTIONTEMP != 0
1748
1749 /* Make sure we don't draw on the button edges */
1750 InflateRect(&nmtbcd->nmcd.rc,
1751 -GetSystemMetrics(SM_CXEDGE),
1752 -GetSystemMetrics(SM_CYEDGE));
1753
1754 if ((nmtbcd->nmcd.uItemState & CDIS_MARKED) && TaskItem->RenderFlashed)
1755 {
1756 /* This is a slight glitch. We have to move the rectangle so that
1757 the button content appears to be pressed. However, when flashing
1758 is enabled, we can see a light line at the top and left inner
1759 border. We need to fill that area with the flashing color. Note
1760 that since we're using DrawCaptionTemp() the flashing color is
1761 COLOR_ACTIVECAPTION, not COLOR_HIGHLIGHT! */
1762 FillRect(nmtbcd->nmcd.hdc,
1763 &nmtbcd->nmcd.rc,
1764 (HBRUSH)(COLOR_ACTIVECAPTION + 1));
1765
1766 /* Make the button content appear pressed. This however draws a bit
1767 into the right and bottom border of the button edge, making it
1768 look a bit odd. However, selecting a clipping region to prevent
1769 that from happening causes problems with DrawCaptionTemp()! */
1770 OffsetRect(&nmtbcd->nmcd.rc,
1771 1,
1772 1);
1773
1774 /* Render flashed */
1775 uidctFlags |= DC_ACTIVE;
1776 }
1777 else
1778 {
1779 uidctFlags |= DC_INBUTTON;
1780 if (nmtbcd->nmcd.uItemState & CDIS_CHECKED)
1781 uidctFlags |= DC_ACTIVE;
1782 }
1783
1784 if (DrawCapTemp != NULL)
1785 {
1786 /* Draw the button content */
1787 TaskItem->DisplayTooltip = !DrawCapTemp(TaskItem->hWnd,
1788 nmtbcd->nmcd.hdc,
1789 &nmtbcd->nmcd.rc,
1790 hCaptionFont,
1791 NULL,
1792 NULL,
1793 uidctFlags);
1794 }
1795
1796 return CDRF_SKIPDEFAULT;
1797
1798 #else /* !TASK_USE_DRAWCAPTIONTEMP */
1799
1800 /* Make the entire button flashing if neccessary */
1801 if (nmtbcd->nmcd.uItemState & CDIS_MARKED)
1802 {
1803 if (TaskItem->RenderFlashed)
1804 {
1805 nmtbcd->hbrMonoDither = GetSysColorBrush(COLOR_HIGHLIGHT);
1806 nmtbcd->clrTextHighlight = GetSysColor(COLOR_HIGHLIGHTTEXT);
1807 nmtbcd->nHLStringBkMode = TRANSPARENT;
1808
1809 /* We don't really need to set clrMark because we set the
1810 background mode to TRANSPARENT! */
1811 nmtbcd->clrMark = GetSysColor(COLOR_HIGHLIGHT);
1812
1813 Ret |= TBCDRF_USECDCOLORS;
1814 }
1815 else
1816 Ret |= TBCDRF_NOMARK;
1817 }
1818
1819 /* Select the font we want to use */
1820 SelectObject(nmtbcd->nmcd.hdc,
1821 hCaptionFont);
1822 return Ret | CDRF_NEWFONT;
1823
1824 #endif
1825
1826 }
1827 }
1828 else if (TaskGroup != NULL)
1829 {
1830 /* FIXME: Implement painting for task groups */
1831 }
1832
1833 return Ret;
1834 }
1835
1836 static LRESULT
1837 TaskSwitchWnd_HandleToolbarNotification(IN OUT PTASK_SWITCH_WND This,
1838 IN const NMHDR *nmh)
1839 {
1840 LRESULT Ret = 0;
1841
1842 switch (nmh->code)
1843 {
1844 case NM_CUSTOMDRAW:
1845 {
1846 LPNMTBCUSTOMDRAW nmtbcd = (LPNMTBCUSTOMDRAW)nmh;
1847
1848 switch (nmtbcd->nmcd.dwDrawStage)
1849 {
1850
1851 #if TASK_USE_DRAWCAPTIONTEMP != 0
1852
1853 case CDDS_ITEMPREPAINT:
1854 /* We handle drawing in the post-paint stage so that we
1855 don't have to draw the button edges, etc */
1856 Ret = CDRF_NOTIFYPOSTPAINT;
1857 break;
1858
1859 case CDDS_ITEMPOSTPAINT:
1860
1861 #else /* !TASK_USE_DRAWCAPTIONTEMP */
1862
1863 case CDDS_ITEMPREPAINT:
1864
1865 #endif
1866
1867 Ret = TaskSwichWnd_HandleItemPaint(This,
1868 nmtbcd);
1869 break;
1870
1871 case CDDS_PREPAINT:
1872 Ret = CDRF_NOTIFYITEMDRAW;
1873 break;
1874
1875 default:
1876 Ret = CDRF_DODEFAULT;
1877 break;
1878 }
1879 break;
1880 }
1881 }
1882
1883 return Ret;
1884 }
1885
1886 static LRESULT CALLBACK
1887 TaskSwitchWndProc(IN HWND hwnd,
1888 IN UINT uMsg,
1889 IN WPARAM wParam,
1890 IN LPARAM lParam)
1891 {
1892 PTASK_SWITCH_WND This = NULL;
1893 LRESULT Ret = FALSE;
1894
1895 if (uMsg != WM_NCCREATE)
1896 {
1897 This = (PTASK_SWITCH_WND)GetWindowLongPtr(hwnd,
1898 0);
1899 }
1900
1901 if (This != NULL || uMsg == WM_NCCREATE)
1902 {
1903 switch (uMsg)
1904 {
1905 case WM_SIZE:
1906 {
1907 SIZE szClient;
1908
1909 szClient.cx = LOWORD(lParam);
1910 szClient.cy = HIWORD(lParam);
1911 if (This->hWndToolbar != NULL)
1912 {
1913 SetWindowPos(This->hWndToolbar,
1914 NULL,
1915 0,
1916 0,
1917 szClient.cx,
1918 szClient.cy,
1919 SWP_NOZORDER);
1920
1921 TaskSwitchWnd_UpdateButtonsSize(This,
1922 FALSE);
1923 }
1924 break;
1925 }
1926
1927 case WM_NCHITTEST:
1928 {
1929 /* We want the tray window to be draggable everywhere, so make the control
1930 appear transparent */
1931 Ret = DefWindowProc(hwnd,
1932 uMsg,
1933 wParam,
1934 lParam);
1935 if (Ret != HTVSCROLL && Ret != HTHSCROLL)
1936 Ret = HTTRANSPARENT;
1937 break;
1938 }
1939
1940 case WM_COMMAND:
1941 {
1942 if (lParam != 0 && (HWND)lParam == This->hWndToolbar)
1943 {
1944 TaskSwitchWnd_HandleButtonClick(This,
1945 LOWORD(wParam));
1946 }
1947 break;
1948 }
1949
1950 case WM_NOTIFY:
1951 {
1952 const NMHDR *nmh = (const NMHDR *)lParam;
1953
1954 if (nmh->hwndFrom == This->hWndToolbar)
1955 {
1956 Ret = TaskSwitchWnd_HandleToolbarNotification(This,
1957 nmh);
1958 }
1959 break;
1960 }
1961
1962 case TSWM_ENABLEGROUPING:
1963 {
1964 Ret = This->IsGroupingEnabled;
1965 if (wParam != This->IsGroupingEnabled)
1966 {
1967 TaskSwitchWnd_EnableGrouping(This,
1968 (BOOL)wParam);
1969 }
1970 break;
1971 }
1972
1973 case TSWM_UPDATETASKBARPOS:
1974 {
1975 /* Update the button spacing */
1976 TaskSwitchWnd_UpdateTbButtonSpacing(This,
1977 ITrayWindow_IsHorizontal(This->Tray),
1978 0,
1979 0);
1980 break;
1981 }
1982
1983 case WM_CONTEXTMENU:
1984 {
1985 if (This->hWndToolbar != NULL)
1986 {
1987 POINT pt;
1988 INT_PTR iBtn;
1989
1990 pt.x = (LONG)LOWORD(lParam);
1991 pt.y = (LONG)HIWORD(lParam);
1992
1993 MapWindowPoints(NULL,
1994 This->hWndToolbar,
1995 &pt,
1996 1);
1997
1998 iBtn = (INT_PTR)SendMessage(This->hWndToolbar,
1999 TB_HITTEST,
2000 0,
2001 (LPARAM)&pt);
2002 if (iBtn >= 0)
2003 {
2004 /* FIXME: Display the system menu of the window */
2005 }
2006 else
2007 goto ForwardContextMenuMsg;
2008 }
2009 else
2010 {
2011 ForwardContextMenuMsg:
2012 /* Forward message */
2013 Ret = SendMessage(ITrayWindow_GetHWND(This->Tray),
2014 uMsg,
2015 wParam,
2016 lParam);
2017 }
2018 break;
2019 }
2020
2021 case WM_NCCREATE:
2022 {
2023 LPCREATESTRUCT CreateStruct = (LPCREATESTRUCT)lParam;
2024 This = (PTASK_SWITCH_WND)HeapAlloc(hProcessHeap,
2025 0,
2026 sizeof(*This));
2027 if (This == NULL)
2028 return FALSE;
2029
2030 ZeroMemory(This,
2031 sizeof(*This));
2032 This->hWnd = hwnd;
2033 This->hWndNotify = CreateStruct->hwndParent;
2034 This->Tray = (ITrayWindow*)CreateStruct->lpCreateParams;
2035 This->IsGroupingEnabled = TRUE; /* FIXME */
2036 SetWindowLongPtr(hwnd,
2037 0,
2038 (LONG_PTR)This);
2039
2040 return TRUE;
2041 }
2042
2043 case WM_CREATE:
2044 TaskSwitchWnd_Create(This);
2045
2046 #if DUMP_TASKS != 0
2047 SetTimer(hwnd,
2048 1,
2049 5000,
2050 NULL);
2051 #endif
2052
2053 break;
2054
2055 case WM_DESTROY:
2056 if (This->IsToolbarSubclassed)
2057 {
2058 if (RemoveWindowSubclass(This->hWndToolbar,
2059 TaskSwichWnd_ToolbarSubclassedProc,
2060 TSW_TOOLBAR_SUBCLASS_ID))
2061 {
2062 This->IsToolbarSubclassed = FALSE;
2063 }
2064 }
2065 break;
2066
2067 case WM_NCDESTROY:
2068 TaskSwitchWnd_NCDestroy(This);
2069 HeapFree(hProcessHeap,
2070 0,
2071 This);
2072 SetWindowLongPtr(hwnd,
2073 0,
2074 0);
2075 break;
2076
2077 #if DUMP_TASKS != 0
2078 case WM_TIMER:
2079 switch(wParam)
2080 {
2081 case 1:
2082 TaskSwitchWnd_DumpTasks(This);
2083 break;
2084 }
2085 break;
2086 #endif
2087
2088 default:
2089 /* HandleDefaultMessage: */
2090 if (uMsg == This->ShellHookMsg && This->ShellHookMsg != 0)
2091 {
2092 /* Process shell messages */
2093 Ret = (LRESULT)TaskSwitchWnd_HandleShellHookMsg(This,
2094 wParam,
2095 lParam);
2096 break;
2097 }
2098
2099 Ret = DefWindowProc(hwnd,
2100 uMsg,
2101 wParam,
2102 lParam);
2103 break;
2104 }
2105 }
2106 else
2107 {
2108 Ret = DefWindowProc(hwnd,
2109 uMsg,
2110 wParam,
2111 lParam);
2112 }
2113
2114 return Ret;
2115 }
2116
2117
2118 HWND
2119 CreateTaskSwitchWnd(IN HWND hWndParent,
2120 IN OUT ITrayWindow *Tray)
2121 {
2122 HWND hwndTaskBar;
2123
2124 hwndTaskBar = CreateWindowEx(0,
2125 szTaskSwitchWndClass,
2126 szRunningApps,
2127 WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP,
2128 0,
2129 0,
2130 0,
2131 0,
2132 hWndParent,
2133 NULL,
2134 hExplorerInstance,
2135 (LPVOID)Tray);
2136
2137 return hwndTaskBar;
2138 }
2139
2140 BOOL
2141 RegisterTaskSwitchWndClass(VOID)
2142 {
2143 WNDCLASS wc;
2144
2145 wc.style = CS_DBLCLKS;
2146 wc.lpfnWndProc = TaskSwitchWndProc;
2147 wc.cbClsExtra = 0;
2148 wc.cbWndExtra = sizeof(PTASK_SWITCH_WND);
2149 wc.hInstance = hExplorerInstance;
2150 wc.hIcon = NULL;
2151 wc.hCursor = LoadCursor(NULL,
2152 IDC_ARROW);
2153 wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
2154 wc.lpszMenuName = NULL;
2155 wc.lpszClassName = szTaskSwitchWndClass;
2156
2157 return RegisterClass(&wc) != 0;
2158 }
2159
2160 VOID
2161 UnregisterTaskSwitchWndClass(VOID)
2162 {
2163 UnregisterClass(szTaskSwitchWndClass,
2164 hExplorerInstance);
2165 }