Visual C++ backend for rbuild (for now just a hacked mingw backend) and related compi...
[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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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_ActivateTask(IN OUT PTASK_SWITCH_WND This,
1033 IN HWND hWnd)
1034 {
1035 PTASK_ITEM TaskItem;
1036
1037 TaskItem = TaskSwitchWnd_FindTaskItem(This,
1038 hWnd);
1039 if (TaskItem == NULL)
1040 {
1041 TaskItem = TaskSwitchWnd_FindOtherTaskItem(This,
1042 hWnd);
1043 }
1044
1045 if (TaskItem != NULL)
1046 {
1047 DbgPrint("Activate window 0x%p on button %d\n", hWnd, TaskItem->Index);
1048 }
1049 else
1050 {
1051 DbgPrint("Activate window 0x%p, could not find task\n", hWnd);
1052 }
1053
1054 TaskSwitchWnd_CheckActivateTaskItem(This,
1055 TaskItem);
1056
1057 return FALSE;
1058 }
1059
1060 static BOOL
1061 TaskSwitchWnd_DeleteTask(IN OUT PTASK_SWITCH_WND This,
1062 IN HWND hWnd)
1063 {
1064 PTASK_ITEM TaskItem;
1065
1066 TaskItem = TaskSwitchWnd_FindTaskItem(This,
1067 hWnd);
1068 if (TaskItem != NULL)
1069 {
1070 DbgPrint("Delete window 0x%p on button %d\n", hWnd, TaskItem->Index);
1071 TaskSwitchWnd_DeleteTaskItem(This,
1072 TaskItem);
1073 return TRUE;
1074 }
1075 //else
1076 //DbgPrint("Failed to delete window 0x%p\n", hWnd);
1077
1078 return FALSE;
1079 }
1080
1081 static VOID
1082 TaskSwitchWnd_DeleteAllTasks(IN OUT PTASK_SWITCH_WND This)
1083 {
1084 PTASK_ITEM CurrentTask;
1085
1086 if (This->TaskItemCount > 0)
1087 {
1088 CurrentTask = This->TaskItems + This->TaskItemCount;
1089 do
1090 {
1091 TaskSwitchWnd_DeleteTaskItem(This,
1092 --CurrentTask);
1093 } while (CurrentTask != This->TaskItems);
1094 }
1095 }
1096
1097 static VOID
1098 TaskSwitchWnd_FlashTaskItem(IN OUT PTASK_SWITCH_WND This,
1099 IN OUT PTASK_ITEM TaskItem)
1100 {
1101 /* FIXME: Implement */
1102 }
1103
1104 static BOOL
1105 TaskSwitchWnd_FlashTask(IN OUT PTASK_SWITCH_WND This,
1106 IN HWND hWnd)
1107 {
1108 PTASK_ITEM TaskItem;
1109
1110 TaskItem = TaskSwitchWnd_FindTaskItem(This,
1111 hWnd);
1112 if (TaskItem != NULL)
1113 {
1114 DbgPrint("Flashing window 0x%p on button %d\n", hWnd, TaskItem->Index);
1115 TaskSwitchWnd_FlashTaskItem(This,
1116 TaskItem);
1117 return TRUE;
1118 }
1119
1120 return FALSE;
1121 }
1122
1123 static VOID
1124 TaskSwitchWnd_RedrawTaskItem(IN OUT PTASK_SWITCH_WND This,
1125 IN OUT PTASK_ITEM TaskItem)
1126 {
1127 PTASK_GROUP TaskGroup;
1128
1129 TaskGroup = TaskItem->Group;
1130 if (This->IsGroupingEnabled && TaskGroup != NULL)
1131 {
1132 if (TaskGroup->IsCollapsed && TaskGroup->Index >= 0)
1133 {
1134 TaskSwitchWnd_UpdateTaskGroupButton(This,
1135 TaskGroup);
1136 }
1137 else if (TaskItem->Index >= 0)
1138 {
1139 goto UpdateTaskItem;
1140 }
1141 }
1142 else if (TaskItem->Index >= 0)
1143 {
1144 UpdateTaskItem:
1145 TaskSwitchWnd_UpdateTaskItemButton(This,
1146 TaskItem);
1147 }
1148 }
1149
1150
1151 static BOOL
1152 TaskSwitchWnd_RedrawTask(IN OUT PTASK_SWITCH_WND This,
1153 IN HWND hWnd)
1154 {
1155 PTASK_ITEM TaskItem;
1156
1157 TaskItem = TaskSwitchWnd_FindTaskItem(This,
1158 hWnd);
1159 if (TaskItem != NULL)
1160 {
1161 TaskSwitchWnd_RedrawTaskItem(This,
1162 TaskItem);
1163 return TRUE;
1164 }
1165
1166 return FALSE;
1167 }
1168
1169 static INT
1170 TaskSwitchWnd_UpdateTbButtonSpacing(IN OUT PTASK_SWITCH_WND This,
1171 IN BOOL bHorizontal,
1172 IN UINT uiRows,
1173 IN UINT uiBtnsPerLine)
1174 {
1175 TBMETRICS tbm;
1176
1177 tbm.cbSize = sizeof(tbm);
1178 tbm.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING;
1179
1180 tbm.cxBarPad = tbm.cyBarPad = 0;
1181
1182 if (bHorizontal || uiBtnsPerLine > 1)
1183 tbm.cxButtonSpacing = (3 * GetSystemMetrics(SM_CXEDGE) / 2);
1184 else
1185 tbm.cxButtonSpacing = 0;
1186
1187 if (!bHorizontal || uiRows > 1)
1188 tbm.cyButtonSpacing = (3 * GetSystemMetrics(SM_CYEDGE) / 2);
1189 else
1190 tbm.cyButtonSpacing = 0;
1191
1192 SendMessage(This->hWndToolbar,
1193 TB_SETMETRICS,
1194 0,
1195 (LPARAM)&tbm);
1196
1197 return tbm.cxButtonSpacing;
1198 }
1199
1200
1201 static VOID
1202 TaskSwitchWnd_UpdateButtonsSize(IN OUT PTASK_SWITCH_WND This,
1203 IN BOOL bRedrawDisabled)
1204 {
1205 RECT rcClient;
1206 UINT uiRows, uiMax, uiMin, uiBtnsPerLine, ui;
1207 LONG NewBtnSize;
1208 BOOL Horizontal;
1209 TBBUTTONINFO tbbi;
1210 TBMETRICS tbm;
1211
1212 if (GetClientRect(This->hWnd,
1213 &rcClient) &&
1214 !IsRectEmpty(&rcClient))
1215 {
1216 if (This->ToolbarBtnCount > 0)
1217 {
1218 ZeroMemory (&tbm, sizeof (tbm));
1219 tbm.cbSize = sizeof(tbm);
1220 tbm.dwMask = TBMF_BUTTONSPACING;
1221 SendMessage(This->hWndToolbar,
1222 TB_GETMETRICS,
1223 0,
1224 (LPARAM)&tbm);
1225
1226 uiRows = (rcClient.bottom + tbm.cyButtonSpacing) / (This->ButtonSize.cy + tbm.cyButtonSpacing);
1227 if (uiRows == 0)
1228 uiRows = 1;
1229
1230 uiBtnsPerLine = (This->ToolbarBtnCount + uiRows - 1) / uiRows;
1231
1232 Horizontal = ITrayWindow_IsHorizontal(This->Tray);
1233
1234 if (!bRedrawDisabled)
1235 TaskSwitchWnd_BeginUpdate(This);
1236
1237 /* We might need to update the button spacing */
1238 tbm.cxButtonSpacing = TaskSwitchWnd_UpdateTbButtonSpacing(This,
1239 Horizontal,
1240 uiRows,
1241 uiBtnsPerLine);
1242
1243 /* Calculate the ideal width and make sure it's within the allowed range */
1244 NewBtnSize = (rcClient.right - (uiBtnsPerLine * tbm.cxButtonSpacing)) / uiBtnsPerLine;
1245
1246 /* Determine the minimum and maximum width of a button */
1247 if (Horizontal)
1248 uiMax = GetSystemMetrics(SM_CXMINIMIZED);
1249 else
1250 uiMax = rcClient.right;
1251
1252 uiMin = GetSystemMetrics(SM_CXSIZE) + (2 * GetSystemMetrics(SM_CXEDGE));
1253
1254 if (NewBtnSize < (LONG)uiMin)
1255 NewBtnSize = uiMin;
1256 if (NewBtnSize > (LONG)uiMax)
1257 NewBtnSize = uiMax;
1258
1259 This->ButtonSize.cx = NewBtnSize;
1260
1261 /* Recalculate how many buttons actually fit into one line */
1262 uiBtnsPerLine = rcClient.right / (NewBtnSize + tbm.cxButtonSpacing);
1263 if (uiBtnsPerLine == 0)
1264 uiBtnsPerLine++;
1265 This->TbButtonsPerLine = uiBtnsPerLine;
1266
1267 tbbi.cbSize = sizeof(tbbi);
1268 tbbi.dwMask = TBIF_BYINDEX | TBIF_SIZE | TBIF_STATE;
1269 tbbi.cx = (INT)NewBtnSize;
1270
1271 for (ui = 0; ui != This->ToolbarBtnCount; ui++)
1272 {
1273 tbbi.fsState = TBSTATE_ENABLED;
1274
1275 /* Check if we're updating a button that is the last one in the
1276 line. If so, we need to set the TBSTATE_WRAP flag! */
1277 if ((ui + 1) % uiBtnsPerLine == 0)
1278 tbbi.fsState |= TBSTATE_WRAP;
1279
1280 if (This->ActiveTaskItem != NULL &&
1281 This->ActiveTaskItem->Index == ui)
1282 {
1283 tbbi.fsState |= TBSTATE_CHECKED;
1284 }
1285
1286 SendMessage(This->hWndToolbar,
1287 TB_SETBUTTONINFO,
1288 (WPARAM)ui,
1289 (LPARAM)&tbbi);
1290 }
1291
1292 #if 0
1293 /* FIXME: Force the window to the correct position in case some idiot
1294 did something to us */
1295 SetWindowPos(This->hWndToolbar,
1296 NULL,
1297 0,
1298 0,
1299 rcClient.right, /* FIXME */
1300 rcClient.bottom, /* FIXME */
1301 SWP_NOACTIVATE | SWP_NOZORDER);
1302 #endif
1303 }
1304 else
1305 {
1306 This->TbButtonsPerLine = 0;
1307 This->ButtonSize.cx = 0;
1308 }
1309 }
1310
1311 TaskSwitchWnd_EndUpdate(This);
1312 }
1313
1314 static BOOL CALLBACK
1315 TaskSwitchWnd_EnumWindowsProc(IN HWND hWnd,
1316 IN LPARAM lParam)
1317 {
1318 PTASK_SWITCH_WND This = (PTASK_SWITCH_WND)lParam;
1319
1320 /* Only show windows that still exist and are visible and none of explorer's
1321 special windows (such as the desktop or the tray window) */
1322 if (IsWindow(hWnd) && IsWindowVisible(hWnd) &&
1323 !ITrayWindow_IsSpecialHWND(This->Tray,
1324 hWnd))
1325 {
1326 /* Don't list popup windows and also no tool windows */
1327 if (GetWindow(hWnd,
1328 GW_OWNER) == NULL &&
1329 !(GetWindowLong(hWnd,
1330 GWL_EXSTYLE) & WS_EX_TOOLWINDOW))
1331 {
1332 TaskSwitchWnd_AddTask(This,
1333 hWnd);
1334 }
1335 }
1336
1337 return TRUE;
1338 }
1339
1340 static LRESULT CALLBACK
1341 TaskSwichWnd_ToolbarSubclassedProc(IN HWND hWnd,
1342 IN UINT msg,
1343 IN WPARAM wParam,
1344 IN LPARAM lParam,
1345 IN UINT_PTR uIdSubclass,
1346 IN DWORD_PTR dwRefData)
1347 {
1348 LRESULT Ret;
1349
1350 Ret = DefSubclassProc(hWnd,
1351 msg,
1352 wParam,
1353 lParam);
1354
1355 if (msg == WM_NCHITTEST && Ret == HTCLIENT)
1356 {
1357 POINT pt;
1358
1359 /* See if the mouse is on a button */
1360 pt.x = (SHORT)LOWORD(lParam);
1361 pt.y = (SHORT)HIWORD(lParam);
1362
1363 if (MapWindowPoints(HWND_DESKTOP,
1364 hWnd,
1365 &pt,
1366 1) != 0 &&
1367 (INT)SendMessage(hWnd,
1368 TB_HITTEST,
1369 0,
1370 (LPARAM)&pt) < 0)
1371 {
1372 /* Make the control appear to be transparent outside of any buttons */
1373 Ret = HTTRANSPARENT;
1374 }
1375 }
1376
1377 return Ret;
1378 }
1379
1380 static VOID
1381 TaskSwitchWnd_Create(IN OUT PTASK_SWITCH_WND This)
1382 {
1383 This->hWndToolbar = CreateWindowEx(0,
1384 TOOLBARCLASSNAME,
1385 szRunningApps,
1386 WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
1387 TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | TBSTYLE_LIST |
1388 TBSTYLE_TRANSPARENT |
1389 CCS_TOP | CCS_NORESIZE | CCS_NODIVIDER,
1390 0,
1391 0,
1392 0,
1393 0,
1394 This->hWnd,
1395 NULL,
1396 hExplorerInstance,
1397 NULL);
1398
1399 if (This->hWndToolbar != NULL)
1400 {
1401 HMODULE hShell32;
1402 SIZE BtnSize;
1403
1404 /* Identify the version we're using */
1405 SendMessage(This->hWndToolbar,
1406 TB_BUTTONSTRUCTSIZE,
1407 sizeof(TBBUTTON),
1408 0);
1409
1410 /* Calculate the default button size. Don't save this in This->ButtonSize.cx so that
1411 the actual button width gets updated correctly on the first recalculation */
1412 BtnSize.cx = GetSystemMetrics(SM_CXMINIMIZED);
1413 This->ButtonSize.cy = BtnSize.cy = GetSystemMetrics(SM_CYSIZE) + (2 * GetSystemMetrics(SM_CYEDGE));
1414 SendMessage(This->hWndToolbar,
1415 TB_SETBUTTONSIZE,
1416 0,
1417 (LPARAM)MAKELONG(BtnSize.cx,
1418 BtnSize.cy));
1419
1420 /* We don't want to see partially clipped buttons...not that we could see them... */
1421 #if 0
1422 SendMessage(This->hWndToolbar,
1423 TB_SETEXTENDEDSTYLE,
1424 0,
1425 TBSTYLE_EX_HIDECLIPPEDBUTTONS);
1426 #endif
1427
1428 /* Set proper spacing between buttons */
1429 TaskSwitchWnd_UpdateTbButtonSpacing(This,
1430 ITrayWindow_IsHorizontal(This->Tray),
1431 0,
1432 0);
1433
1434 /* Register the shell hook */
1435 This->ShellHookMsg = RegisterWindowMessage(TEXT("SHELLHOOK"));
1436 hShell32 = GetModuleHandle(TEXT("SHELL32.DLL"));
1437 if (hShell32 != NULL)
1438 {
1439 REGSHELLHOOK RegShellHook;
1440
1441 /* RegisterShellHook */
1442 RegShellHook = (REGSHELLHOOK)GetProcAddress(hShell32,
1443 (LPCSTR)((LONG)181));
1444 if (RegShellHook != NULL)
1445 {
1446 RegShellHook(This->hWnd,
1447 3); /* 1 if no NT! We're targeting NT so we don't care! */
1448 }
1449 }
1450
1451 /* Add all windows to the toolbar */
1452 EnumWindows(TaskSwitchWnd_EnumWindowsProc,
1453 (LPARAM)This);
1454
1455 /* Recalculate the button size */
1456 TaskSwitchWnd_UpdateButtonsSize(This,
1457 FALSE);
1458
1459 /* Subclass the toolbar control because it doesn't provide a
1460 NM_NCHITTEST notification */
1461 This->IsToolbarSubclassed = SetWindowSubclass(This->hWndToolbar,
1462 TaskSwichWnd_ToolbarSubclassedProc,
1463 TSW_TOOLBAR_SUBCLASS_ID,
1464 (DWORD_PTR)This);
1465 }
1466 }
1467
1468 static VOID
1469 TaskSwitchWnd_NCDestroy(IN OUT PTASK_SWITCH_WND This)
1470 {
1471 HMODULE hShell32;
1472
1473 This->IsDestroying = TRUE;
1474
1475 /* Unregister the shell hook */
1476 hShell32 = GetModuleHandle(TEXT("SHELL32.DLL"));
1477 if (hShell32 != NULL)
1478 {
1479 REGSHELLHOOK RegShellHook;
1480
1481 /* RegisterShellHook */
1482 RegShellHook = (REGSHELLHOOK)GetProcAddress(hShell32,
1483 (LPCSTR)((LONG)181));
1484 if (RegShellHook != NULL)
1485 {
1486 RegShellHook(This->hWnd,
1487 FALSE);
1488 }
1489 }
1490
1491 TaskSwitchWnd_DeleteAllTasks(This);
1492 }
1493
1494 static BOOL
1495 TaskSwitchWnd_HandleAppCommand(IN OUT PTASK_SWITCH_WND This,
1496 IN WPARAM wParam,
1497 IN LPARAM lParam)
1498 {
1499 BOOL Ret = FALSE;
1500
1501 switch (GET_APPCOMMAND_LPARAM(lParam))
1502 {
1503 case APPCOMMAND_BROWSER_SEARCH:
1504 Ret = SHFindFiles(NULL,
1505 NULL);
1506 break;
1507
1508 case APPCOMMAND_BROWSER_HOME:
1509 case APPCOMMAND_LAUNCH_MAIL:
1510 default:
1511 DbgPrint("Shell app command %d unhandled!\n", (INT)GET_APPCOMMAND_LPARAM(lParam));
1512 break;
1513 }
1514
1515 return Ret;
1516 }
1517
1518
1519 static LRESULT
1520 TaskSwitchWnd_HandleShellHookMsg(IN OUT PTASK_SWITCH_WND This,
1521 IN WPARAM wParam,
1522 IN LPARAM lParam)
1523 {
1524 BOOL Ret = FALSE;
1525
1526 switch ((INT)wParam)
1527 {
1528 case HSHELL_APPCOMMAND:
1529 TaskSwitchWnd_HandleAppCommand(This,
1530 wParam,
1531 lParam);
1532 Ret = TRUE;
1533 break;
1534
1535 case HSHELL_WINDOWCREATED:
1536 TaskSwitchWnd_AddTask(This,
1537 (HWND)lParam);
1538 Ret = TRUE;
1539 break;
1540
1541 case HSHELL_WINDOWDESTROYED:
1542 /* The window still exists! Delay destroying it a bit */
1543 TaskSwitchWnd_DeleteTask(This,
1544 (HWND)lParam);
1545 Ret = TRUE;
1546 break;
1547
1548 case HSHELL_ACTIVATESHELLWINDOW:
1549 goto UnhandledShellMessage;
1550
1551 case HSHELL_RUDEAPPACTIVATED:
1552 goto UnhandledShellMessage;
1553
1554 case HSHELL_WINDOWACTIVATED:
1555 TaskSwitchWnd_ActivateTask(This,
1556 (HWND)lParam);
1557 Ret = TRUE;
1558 break;
1559
1560 case HSHELL_GETMINRECT:
1561 goto UnhandledShellMessage;
1562
1563 case HSHELL_FLASH:
1564 TaskSwitchWnd_FlashTask(This,
1565 (HWND)lParam);
1566 Ret = TRUE;
1567 break;
1568
1569 case HSHELL_REDRAW:
1570 TaskSwitchWnd_RedrawTask(This,
1571 (HWND)lParam);
1572 Ret = TRUE;
1573 break;
1574
1575 case HSHELL_TASKMAN:
1576 case HSHELL_LANGUAGE:
1577 case HSHELL_SYSMENU:
1578 case HSHELL_ENDTASK:
1579 case HSHELL_ACCESSIBILITYSTATE:
1580 case HSHELL_WINDOWREPLACED:
1581 case HSHELL_WINDOWREPLACING:
1582 default:
1583 {
1584 static const struct {
1585 INT msg;
1586 LPCWSTR msg_name;
1587 } hshell_msg[] = {
1588 {HSHELL_WINDOWCREATED, L"HSHELL_WINDOWCREATED"},
1589 {HSHELL_WINDOWDESTROYED, L"HSHELL_WINDOWDESTROYED"},
1590 {HSHELL_ACTIVATESHELLWINDOW, L"HSHELL_ACTIVATESHELLWINDOW"},
1591 {HSHELL_WINDOWACTIVATED, L"HSHELL_WINDOWACTIVATED"},
1592 {HSHELL_GETMINRECT, L"HSHELL_GETMINRECT"},
1593 {HSHELL_REDRAW, L"HSHELL_REDRAW"},
1594 {HSHELL_TASKMAN, L"HSHELL_TASKMAN"},
1595 {HSHELL_LANGUAGE, L"HSHELL_LANGUAGE"},
1596 {HSHELL_SYSMENU, L"HSHELL_SYSMENU"},
1597 {HSHELL_ENDTASK, L"HSHELL_ENDTASK"},
1598 {HSHELL_ACCESSIBILITYSTATE, L"HSHELL_ACCESSIBILITYSTATE"},
1599 {HSHELL_APPCOMMAND, L"HSHELL_APPCOMMAND"},
1600 {HSHELL_WINDOWREPLACED, L"HSHELL_WINDOWREPLACED"},
1601 {HSHELL_WINDOWREPLACING, L"HSHELL_WINDOWREPLACING"},
1602 {HSHELL_RUDEAPPACTIVATED, L"HSHELL_RUDEAPPACTIVATED"},
1603 };
1604 int i, found;
1605 UnhandledShellMessage:
1606 for (i = 0, found = 0; i != sizeof(hshell_msg) / sizeof(hshell_msg[0]); i++)
1607 {
1608 if (hshell_msg[i].msg == (INT)wParam)
1609 {
1610 DbgPrint("Shell message %ws unhandled (lParam = 0x%p)!\n", hshell_msg[i].msg_name, lParam);
1611 found = 1;
1612 break;
1613 }
1614 }
1615 if (!found)
1616 {
1617 DbgPrint("Shell message %d unhandled (lParam = 0x%p)!\n", (INT)wParam, lParam);
1618 }
1619 break;
1620 }
1621 }
1622
1623 return Ret;
1624 }
1625
1626 static VOID
1627 TaskSwitchWnd_EnableGrouping(IN OUT PTASK_SWITCH_WND This,
1628 IN BOOL bEnable)
1629 {
1630 This->IsGroupingEnabled = bEnable;
1631
1632 /* Collapse or expand groups if neccessary */
1633 TaskSwitchWnd_UpdateButtonsSize(This,
1634 FALSE);
1635 }
1636
1637 static LRESULT
1638 TaskSwichWnd_HandleItemPaint(IN OUT PTASK_SWITCH_WND This,
1639 IN OUT NMTBCUSTOMDRAW *nmtbcd)
1640 {
1641 HFONT hCaptionFont, hBoldCaptionFont;
1642 LRESULT Ret = CDRF_DODEFAULT;
1643 PTASK_GROUP TaskGroup;
1644 PTASK_ITEM TaskItem;
1645
1646 #if TASK_USE_DRAWCAPTIONTEMP != 0
1647
1648 UINT uidctFlags = DC_TEXT | DC_ICON | DC_NOSENDMSG;
1649
1650 #endif
1651 TaskItem = FindTaskItemByIndex(This,
1652 (INT)nmtbcd->nmcd.dwItemSpec);
1653 TaskGroup = FindTaskGroupByIndex(This,
1654 (INT)nmtbcd->nmcd.dwItemSpec);
1655 if (TaskGroup == NULL && TaskItem != NULL)
1656 {
1657 ASSERT(TaskItem != NULL);
1658
1659 if (TaskItem != NULL && IsWindow(TaskItem->hWnd))
1660 {
1661 hCaptionFont = ITrayWindow_GetCaptionFonts(This->Tray,
1662 &hBoldCaptionFont);
1663 if (nmtbcd->nmcd.uItemState & CDIS_CHECKED)
1664 hCaptionFont = hBoldCaptionFont;
1665
1666 #if TASK_USE_DRAWCAPTIONTEMP != 0
1667
1668 /* Make sure we don't draw on the button edges */
1669 InflateRect(&nmtbcd->nmcd.rc,
1670 -GetSystemMetrics(SM_CXEDGE),
1671 -GetSystemMetrics(SM_CYEDGE));
1672
1673 if ((nmtbcd->nmcd.uItemState & CDIS_MARKED) && TaskItem->RenderFlashed)
1674 {
1675 /* This is a slight glitch. We have to move the rectangle so that
1676 the button content appears to be pressed. However, when flashing
1677 is enabled, we can see a light line at the top and left inner
1678 border. We need to fill that area with the flashing color. Note
1679 that since we're using DrawCaptionTemp() the flashing color is
1680 COLOR_ACTIVECAPTION, not COLOR_HIGHLIGHT! */
1681 FillRect(nmtbcd->nmcd.hdc,
1682 &nmtbcd->nmcd.rc,
1683 (HBRUSH)(COLOR_ACTIVECAPTION + 1));
1684
1685 /* Make the button content appear pressed. This however draws a bit
1686 into the right and bottom border of the button edge, making it
1687 look a bit odd. However, selecting a clipping region to prevent
1688 that from happening causes problems with DrawCaptionTemp()! */
1689 OffsetRect(&nmtbcd->nmcd.rc,
1690 1,
1691 1);
1692
1693 /* Render flashed */
1694 uidctFlags |= DC_ACTIVE;
1695 }
1696 else
1697 {
1698 uidctFlags |= DC_INBUTTON;
1699 if (nmtbcd->nmcd.uItemState & CDIS_CHECKED)
1700 uidctFlags |= DC_ACTIVE;
1701 }
1702
1703 if (DrawCapTemp != NULL)
1704 {
1705 /* Draw the button content */
1706 TaskItem->DisplayTooltip = !DrawCapTemp(TaskItem->hWnd,
1707 nmtbcd->nmcd.hdc,
1708 &nmtbcd->nmcd.rc,
1709 hCaptionFont,
1710 NULL,
1711 NULL,
1712 uidctFlags);
1713 }
1714
1715 return CDRF_SKIPDEFAULT;
1716
1717 #else /* !TASK_USE_DRAWCAPTIONTEMP */
1718
1719 /* Make the entire button flashing if neccessary */
1720 if (nmtbcd->nmcd.uItemState & CDIS_MARKED)
1721 {
1722 if (TaskItem->RenderFlashed)
1723 {
1724 nmtbcd->hbrMonoDither = GetSysColorBrush(COLOR_HIGHLIGHT);
1725 nmtbcd->clrTextHighlight = GetSysColor(COLOR_HIGHLIGHTTEXT);
1726 nmtbcd->nHLStringBkMode = TRANSPARENT;
1727
1728 /* We don't really need to set clrMark because we set the
1729 background mode to TRANSPARENT! */
1730 nmtbcd->clrMark = GetSysColor(COLOR_HIGHLIGHT);
1731
1732 Ret |= TBCDRF_USECDCOLORS;
1733 }
1734 else
1735 Ret |= TBCDRF_NOMARK;
1736 }
1737
1738 /* Select the font we want to use */
1739 SelectObject(nmtbcd->nmcd.hdc,
1740 hCaptionFont);
1741 return Ret | CDRF_NEWFONT;
1742
1743 #endif
1744
1745 }
1746 }
1747 else if (TaskGroup != NULL)
1748 {
1749 /* FIXME: Implement painting for task groups */
1750 }
1751
1752 return Ret;
1753 }
1754
1755 static LRESULT
1756 TaskSwitchWnd_HandleToolbarNotification(IN OUT PTASK_SWITCH_WND This,
1757 IN const NMHDR *nmh)
1758 {
1759 LRESULT Ret = 0;
1760
1761 switch (nmh->code)
1762 {
1763 case NM_CUSTOMDRAW:
1764 {
1765 LPNMTBCUSTOMDRAW nmtbcd = (LPNMTBCUSTOMDRAW)nmh;
1766
1767 switch (nmtbcd->nmcd.dwDrawStage)
1768 {
1769
1770 #if TASK_USE_DRAWCAPTIONTEMP != 0
1771
1772 case CDDS_ITEMPREPAINT:
1773 /* We handle drawing in the post-paint stage so that we
1774 don't have to draw the button edges, etc */
1775 Ret = CDRF_NOTIFYPOSTPAINT;
1776 break;
1777
1778 case CDDS_ITEMPOSTPAINT:
1779
1780 #else /* !TASK_USE_DRAWCAPTIONTEMP */
1781
1782 case CDDS_ITEMPREPAINT:
1783
1784 #endif
1785
1786 Ret = TaskSwichWnd_HandleItemPaint(This,
1787 nmtbcd);
1788 break;
1789
1790 case CDDS_PREPAINT:
1791 Ret = CDRF_NOTIFYITEMDRAW;
1792 break;
1793
1794 default:
1795 Ret = CDRF_DODEFAULT;
1796 break;
1797 }
1798 break;
1799 }
1800 }
1801
1802 return Ret;
1803 }
1804
1805 static LRESULT CALLBACK
1806 TaskSwitchWndProc(IN HWND hwnd,
1807 IN UINT uMsg,
1808 IN WPARAM wParam,
1809 IN LPARAM lParam)
1810 {
1811 PTASK_SWITCH_WND This = NULL;
1812 LRESULT Ret = FALSE;
1813
1814 if (uMsg != WM_NCCREATE)
1815 {
1816 This = (PTASK_SWITCH_WND)GetWindowLongPtr(hwnd,
1817 0);
1818 }
1819
1820 if (This != NULL || uMsg == WM_NCCREATE)
1821 {
1822 switch (uMsg)
1823 {
1824 case WM_SIZE:
1825 {
1826 SIZE szClient;
1827
1828 szClient.cx = LOWORD(lParam);
1829 szClient.cy = HIWORD(lParam);
1830 if (This->hWndToolbar != NULL)
1831 {
1832 SetWindowPos(This->hWndToolbar,
1833 NULL,
1834 0,
1835 0,
1836 szClient.cx,
1837 szClient.cy,
1838 SWP_NOZORDER);
1839
1840 TaskSwitchWnd_UpdateButtonsSize(This,
1841 FALSE);
1842 }
1843 break;
1844 }
1845
1846 case WM_NCHITTEST:
1847 {
1848 /* We want the tray window to be draggable everywhere, so make the control
1849 appear transparent */
1850 Ret = DefWindowProc(hwnd,
1851 uMsg,
1852 wParam,
1853 lParam);
1854 if (Ret != HTVSCROLL && Ret != HTHSCROLL)
1855 Ret = HTTRANSPARENT;
1856 break;
1857 }
1858
1859 case WM_COMMAND:
1860 {
1861 if (lParam != 0 && (HWND)lParam == This->hWndToolbar)
1862 {
1863 DbgPrint("WM_COMMAND %u:%u (%u)\n", (UINT)LOWORD(wParam), (UINT)HIWORD(wParam), (UINT)wParam);
1864 }
1865 break;
1866 }
1867
1868 case WM_NOTIFY:
1869 {
1870 const NMHDR *nmh = (const NMHDR *)lParam;
1871
1872 if (nmh->hwndFrom == This->hWndToolbar)
1873 {
1874 Ret = TaskSwitchWnd_HandleToolbarNotification(This,
1875 nmh);
1876 }
1877 break;
1878 }
1879
1880 case TSWM_ENABLEGROUPING:
1881 {
1882 Ret = This->IsGroupingEnabled;
1883 if (wParam != This->IsGroupingEnabled)
1884 {
1885 TaskSwitchWnd_EnableGrouping(This,
1886 (BOOL)wParam);
1887 }
1888 break;
1889 }
1890
1891 case TSWM_UPDATETASKBARPOS:
1892 {
1893 /* Update the button spacing */
1894 TaskSwitchWnd_UpdateTbButtonSpacing(This,
1895 ITrayWindow_IsHorizontal(This->Tray),
1896 0,
1897 0);
1898 break;
1899 }
1900
1901 case WM_CONTEXTMENU:
1902 {
1903 if (This->hWndToolbar != NULL)
1904 {
1905 POINT pt;
1906 INT_PTR iBtn;
1907
1908 pt.x = (LONG)LOWORD(lParam);
1909 pt.y = (LONG)HIWORD(lParam);
1910
1911 MapWindowPoints(NULL,
1912 This->hWndToolbar,
1913 &pt,
1914 1);
1915
1916 iBtn = (INT_PTR)SendMessage(This->hWndToolbar,
1917 TB_HITTEST,
1918 0,
1919 (LPARAM)&pt);
1920 if (iBtn >= 0)
1921 {
1922 /* FIXME: Display the system menu of the window */
1923 }
1924 else
1925 goto ForwardContextMenuMsg;
1926 }
1927 else
1928 {
1929 ForwardContextMenuMsg:
1930 /* Forward message */
1931 Ret = SendMessage(ITrayWindow_GetHWND(This->Tray),
1932 uMsg,
1933 wParam,
1934 lParam);
1935 }
1936 break;
1937 }
1938
1939 case WM_NCCREATE:
1940 {
1941 LPCREATESTRUCT CreateStruct = (LPCREATESTRUCT)lParam;
1942 This = (PTASK_SWITCH_WND)HeapAlloc(hProcessHeap,
1943 0,
1944 sizeof(*This));
1945 if (This == NULL)
1946 return FALSE;
1947
1948 ZeroMemory(This,
1949 sizeof(*This));
1950 This->hWnd = hwnd;
1951 This->hWndNotify = CreateStruct->hwndParent;
1952 This->Tray = (ITrayWindow*)CreateStruct->lpCreateParams;
1953 This->IsGroupingEnabled = TRUE; /* FIXME */
1954 SetWindowLongPtr(hwnd,
1955 0,
1956 (LONG_PTR)This);
1957
1958 return TRUE;
1959 }
1960
1961 case WM_CREATE:
1962 TaskSwitchWnd_Create(This);
1963
1964 #if DUMP_TASKS != 0
1965 SetTimer(hwnd,
1966 1,
1967 5000,
1968 NULL);
1969 #endif
1970
1971 break;
1972
1973 case WM_DESTROY:
1974 if (This->IsToolbarSubclassed)
1975 {
1976 if (RemoveWindowSubclass(This->hWndToolbar,
1977 TaskSwichWnd_ToolbarSubclassedProc,
1978 TSW_TOOLBAR_SUBCLASS_ID))
1979 {
1980 This->IsToolbarSubclassed = FALSE;
1981 }
1982 }
1983 break;
1984
1985 case WM_NCDESTROY:
1986 TaskSwitchWnd_NCDestroy(This);
1987 HeapFree(hProcessHeap,
1988 0,
1989 This);
1990 SetWindowLongPtr(hwnd,
1991 0,
1992 0);
1993 break;
1994
1995 #if DUMP_TASKS != 0
1996 case WM_TIMER:
1997 switch(wParam)
1998 {
1999 case 1:
2000 TaskSwitchWnd_DumpTasks(This);
2001 break;
2002 }
2003 break;
2004 #endif
2005
2006 default:
2007 /* HandleDefaultMessage: */
2008 if (uMsg == This->ShellHookMsg && This->ShellHookMsg != 0)
2009 {
2010 /* Process shell messages */
2011 Ret = (LRESULT)TaskSwitchWnd_HandleShellHookMsg(This,
2012 wParam,
2013 lParam);
2014 break;
2015 }
2016
2017 Ret = DefWindowProc(hwnd,
2018 uMsg,
2019 wParam,
2020 lParam);
2021 break;
2022 }
2023 }
2024 else
2025 {
2026 Ret = DefWindowProc(hwnd,
2027 uMsg,
2028 wParam,
2029 lParam);
2030 }
2031
2032 return Ret;
2033 }
2034
2035
2036 HWND
2037 CreateTaskSwitchWnd(IN HWND hWndParent,
2038 IN OUT ITrayWindow *Tray)
2039 {
2040 HWND hwndTaskBar;
2041
2042 hwndTaskBar = CreateWindowEx(0,
2043 szTaskSwitchWndClass,
2044 szRunningApps,
2045 WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP,
2046 0,
2047 0,
2048 0,
2049 0,
2050 hWndParent,
2051 NULL,
2052 hExplorerInstance,
2053 (LPVOID)Tray);
2054
2055 return hwndTaskBar;
2056 }
2057
2058 BOOL
2059 RegisterTaskSwitchWndClass(VOID)
2060 {
2061 WNDCLASS wc;
2062
2063 wc.style = CS_DBLCLKS;
2064 wc.lpfnWndProc = TaskSwitchWndProc;
2065 wc.cbClsExtra = 0;
2066 wc.cbWndExtra = sizeof(PTASK_SWITCH_WND);
2067 wc.hInstance = hExplorerInstance;
2068 wc.hIcon = NULL;
2069 wc.hCursor = LoadCursor(NULL,
2070 IDC_ARROW);
2071 wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
2072 wc.lpszMenuName = NULL;
2073 wc.lpszClassName = szTaskSwitchWndClass;
2074
2075 return RegisterClass(&wc) != 0;
2076 }
2077
2078 VOID
2079 UnregisterTaskSwitchWndClass(VOID)
2080 {
2081 UnregisterClass(szTaskSwitchWndClass,
2082 hExplorerInstance);
2083 }