/* * ReactOS Explorer * * Copyright 2006 - 2007 Thomas Weidenmueller * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "precomp.h" /* Set DUMP_TASKS to 1 to enable a dump of the tasks and task groups every 5 seconds */ #define DUMP_TASKS 0 static const TCHAR szTaskSwitchWndClass[] = TEXT("MSTaskSwWClass"); static const TCHAR szRunningApps[] = TEXT("Running Applications"); typedef struct _TASK_GROUP { /* We have to use a linked list instead of an array so we don't have to update all pointers to groups in the task item array when removing groups. */ struct _TASK_GROUP *Next; DWORD dwTaskCount; DWORD dwProcessId; INT Index; union { DWORD dwFlags; struct { DWORD IsCollapsed : 1; }; }; } TASK_GROUP, *PTASK_GROUP; typedef struct _TASK_ITEM { HWND hWnd; PTASK_GROUP Group; INT Index; INT IconIndex; union { DWORD dwFlags; struct { /* IsFlashing is TRUE when the task bar item should be flashing. */ DWORD IsFlashing : 1; /* RenderFlashed is only TRUE if the task bar item should be drawn with a flash. */ DWORD RenderFlashed : 1; }; }; } TASK_ITEM, *PTASK_ITEM; #define TASK_ITEM_ARRAY_ALLOC 64 typedef struct _TASK_SWITCH_WND { HWND hWnd; HWND hWndNotify; UINT ShellHookMsg; ITrayWindow *Tray; PTASK_GROUP TaskGroups; WORD TaskItemCount; WORD AllocatedTaskItems; PTASK_ITEM TaskItems; PTASK_ITEM ActiveTaskItem; HTHEME TaskBandTheme; HWND hWndToolbar; UINT TbButtonsPerLine; WORD ToolbarBtnCount; HIMAGELIST TaskIcons; union { DWORD dwFlags; struct { DWORD IsGroupingEnabled : 1; DWORD IsDestroying : 1; DWORD IsToolbarSubclassed : 1; }; }; SIZE ButtonSize; TCHAR szBuf[255]; } TASK_SWITCH_WND, *PTASK_SWITCH_WND; #define TSW_TOOLBAR_SUBCLASS_ID 1 #define MAX_TASKS_COUNT (0x7FFF) static VOID TaskSwitchWnd_UpdateButtonsSize(IN OUT PTASK_SWITCH_WND This, IN BOOL bRedrawDisabled); static LPTSTR TaskSwitchWnd_GetWndTextFromTaskItem(IN OUT PTASK_SWITCH_WND This, IN PTASK_ITEM TaskItem) { /* Get the window text without sending a message so we don't hang if an application isn't responding! */ if (InternalGetWindowText(TaskItem->hWnd, This->szBuf, sizeof(This->szBuf) / sizeof(This->szBuf[0])) > 0) { return This->szBuf; } return NULL; } #if DUMP_TASKS != 0 static VOID TaskSwitchWnd_DumpTasks(IN OUT PTASK_SWITCH_WND This) { PTASK_GROUP CurrentGroup; PTASK_ITEM CurrentTaskItem, LastTaskItem; DbgPrint("Tasks dump:\n"); if (This->IsGroupingEnabled) { CurrentGroup = This->TaskGroups; while (CurrentGroup != NULL) { DbgPrint("- Group PID: 0x%p Tasks: %d Index: %d\n", CurrentGroup->dwProcessId, CurrentGroup->dwTaskCount, CurrentGroup->Index); CurrentTaskItem = This->TaskItems; LastTaskItem = CurrentTaskItem + This->TaskItemCount; while (CurrentTaskItem != LastTaskItem) { if (CurrentTaskItem->Group == CurrentGroup) { DbgPrint(" + Task hwnd: 0x%p Index: %d\n", CurrentTaskItem->hWnd, CurrentTaskItem->Index); } CurrentTaskItem++; } CurrentGroup = CurrentGroup->Next; } CurrentTaskItem = This->TaskItems; LastTaskItem = CurrentTaskItem + This->TaskItemCount; while (CurrentTaskItem != LastTaskItem) { if (CurrentTaskItem->Group == NULL) { DbgPrint("- Task hwnd: 0x%p Index: %d\n", CurrentTaskItem->hWnd, CurrentTaskItem->Index); } CurrentTaskItem++; } } else { CurrentTaskItem = This->TaskItems; LastTaskItem = CurrentTaskItem + This->TaskItemCount; while (CurrentTaskItem != LastTaskItem) { DbgPrint("- Task hwnd: 0x%p Index: %d\n", CurrentTaskItem->hWnd, CurrentTaskItem->Index); CurrentTaskItem++; } } } #endif static VOID TaskSwitchWnd_BeginUpdate(IN OUT PTASK_SWITCH_WND This) { SendMessage(This->hWndToolbar, WM_SETREDRAW, FALSE, 0); } static VOID TaskSwitchWnd_EndUpdate(IN OUT PTASK_SWITCH_WND This) { SendMessage(This->hWndToolbar, WM_SETREDRAW, TRUE, 0); InvalidateRect(This->hWndToolbar, NULL, TRUE); } static BOOL TaskSwitchWnd_SetToolbarButtonCommandId(IN OUT PTASK_SWITCH_WND This, IN INT iButtonIndex, IN INT iCommandId) { TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_BYINDEX | TBIF_COMMAND; tbbi.idCommand = iCommandId; return SendMessage(This->hWndToolbar, TB_SETBUTTONINFO, (WPARAM)iButtonIndex, (LPARAM)&tbbi) != 0; } static VOID TaskSwitchWnd_UpdateIndexesAfterButtonInserted(IN OUT PTASK_SWITCH_WND This, IN INT iIndex) { PTASK_GROUP CurrentGroup; PTASK_ITEM CurrentTaskItem, LastTaskItem; INT NewIndex; if (This->IsGroupingEnabled) { /* Update all affected groups */ CurrentGroup = This->TaskGroups; while (CurrentGroup != NULL) { if (CurrentGroup->IsCollapsed && CurrentGroup->Index >= iIndex) { /* Update the toolbar buttons */ NewIndex = CurrentGroup->Index + 1; if (TaskSwitchWnd_SetToolbarButtonCommandId(This, CurrentGroup->Index + 1, NewIndex)) { CurrentGroup->Index = NewIndex; } else CurrentGroup->Index = -1; } CurrentGroup = CurrentGroup->Next; } } /* Update all affected task items */ CurrentTaskItem = This->TaskItems; LastTaskItem = CurrentTaskItem + This->TaskItemCount; while (CurrentTaskItem != LastTaskItem) { CurrentGroup = CurrentTaskItem->Group; if (CurrentGroup != NULL) { if (!CurrentGroup->IsCollapsed && CurrentTaskItem->Index >= iIndex) { goto UpdateTaskItemBtn; } } else if (CurrentTaskItem->Index >= iIndex) { UpdateTaskItemBtn: /* Update the toolbar buttons */ NewIndex = CurrentTaskItem->Index + 1; if (TaskSwitchWnd_SetToolbarButtonCommandId(This, CurrentTaskItem->Index + 1, NewIndex)) { CurrentTaskItem->Index = NewIndex; } else CurrentTaskItem->Index = -1; } CurrentTaskItem++; } } static VOID TaskSwitchWnd_UpdateIndexesAfterButtonDeleted(IN OUT PTASK_SWITCH_WND This, IN INT iIndex) { PTASK_GROUP CurrentGroup; PTASK_ITEM CurrentTaskItem, LastTaskItem; INT NewIndex; if (This->IsGroupingEnabled) { /* Update all affected groups */ CurrentGroup = This->TaskGroups; while (CurrentGroup != NULL) { if (CurrentGroup->IsCollapsed && CurrentGroup->Index > iIndex) { /* Update the toolbar buttons */ NewIndex = CurrentGroup->Index - 1; if (TaskSwitchWnd_SetToolbarButtonCommandId(This, CurrentGroup->Index - 1, NewIndex)) { CurrentGroup->Index = NewIndex; } else CurrentGroup->Index = -1; } CurrentGroup = CurrentGroup->Next; } } /* Update all affected task items */ CurrentTaskItem = This->TaskItems; LastTaskItem = CurrentTaskItem + This->TaskItemCount; while (CurrentTaskItem != LastTaskItem) { CurrentGroup = CurrentTaskItem->Group; if (CurrentGroup != NULL) { if (!CurrentGroup->IsCollapsed && CurrentTaskItem->Index > iIndex) { goto UpdateTaskItemBtn; } } else if (CurrentTaskItem->Index > iIndex) { UpdateTaskItemBtn: /* Update the toolbar buttons */ NewIndex = CurrentTaskItem->Index - 1; if (TaskSwitchWnd_SetToolbarButtonCommandId(This, CurrentTaskItem->Index - 1, NewIndex)) { CurrentTaskItem->Index = NewIndex; } else CurrentTaskItem->Index = -1; } CurrentTaskItem++; } } static INT TaskSwitchWnd_UpdateTaskGroupButton(IN OUT PTASK_SWITCH_WND This, IN PTASK_GROUP TaskGroup) { ASSERT(TaskGroup->Index >= 0); /* FIXME: Implement */ return TaskGroup->Index; } static VOID TaskSwitchWnd_ExpandTaskGroup(IN OUT PTASK_SWITCH_WND This, IN PTASK_GROUP TaskGroup) { ASSERT(TaskGroup->dwTaskCount > 0); ASSERT(TaskGroup->IsCollapsed); ASSERT(TaskGroup->Index >= 0); /* FIXME: Implement */ } static HICON TaskSwitchWnd_GetWndIcon(HWND hwnd) { HICON hIcon = 0; SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL2, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon); if (!hIcon) SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon); if (!hIcon) SendMessageTimeout(hwnd, WM_GETICON, ICON_BIG, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon); if (!hIcon) hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICONSM); if (!hIcon) hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICON); return hIcon; } static INT TaskSwitchWnd_UpdateTaskItemButton(IN OUT PTASK_SWITCH_WND This, IN PTASK_ITEM TaskItem) { TBBUTTONINFO tbbi; HICON icon; ASSERT(TaskItem->Index >= 0); tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_BYINDEX | TBIF_STATE | TBIF_TEXT | TBIF_IMAGE; tbbi.fsState = TBSTATE_ENABLED; if (This->ActiveTaskItem == TaskItem) tbbi.fsState |= TBSTATE_CHECKED; if (TaskItem->RenderFlashed) tbbi.fsState |= TBSTATE_MARKED; /* Check if we're updating a button that is the last one in the line. If so, we need to set the TBSTATE_WRAP flag! */ if (This->TbButtonsPerLine != 0 && (TaskItem->Index + 1) % This->TbButtonsPerLine == 0) { tbbi.fsState |= TBSTATE_WRAP; } tbbi.pszText = TaskSwitchWnd_GetWndTextFromTaskItem(This, TaskItem); icon = TaskSwitchWnd_GetWndIcon(TaskItem->hWnd); TaskItem->IconIndex = ImageList_ReplaceIcon(This->TaskIcons, TaskItem->IconIndex, icon); tbbi.iImage = TaskItem->IconIndex; if (!SendMessage(This->hWndToolbar, TB_SETBUTTONINFO, (WPARAM)TaskItem->Index, (LPARAM)&tbbi)) { TaskItem->Index = -1; return -1; } DbgPrint("Updated button %d for hwnd 0x%p\n", TaskItem->Index, TaskItem->hWnd); return TaskItem->Index; } static VOID TaskSwitchWnd_RemoveIcon(IN OUT PTASK_SWITCH_WND This, IN PTASK_ITEM TaskItem) { TBBUTTONINFO tbbi; PTASK_ITEM currentTaskItem, LastItem; if (TaskItem->IconIndex == -1) return; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_IMAGE; currentTaskItem = This->TaskItems; LastItem = currentTaskItem + This->TaskItemCount; while (currentTaskItem != LastItem) { if (currentTaskItem->IconIndex > TaskItem->IconIndex) { currentTaskItem->IconIndex--; tbbi.iImage = currentTaskItem->IconIndex; SendMessage(This->hWndToolbar, TB_SETBUTTONINFO, currentTaskItem->Index, (LPARAM)&tbbi); } currentTaskItem++; } ImageList_Remove(This->TaskIcons, TaskItem->IconIndex); } static PTASK_ITEM TaskSwitchWnd_FindLastTaskItemOfGroup(IN OUT PTASK_SWITCH_WND This, IN PTASK_GROUP TaskGroup OPTIONAL, IN PTASK_ITEM NewTaskItem OPTIONAL) { PTASK_ITEM TaskItem, LastTaskItem, FoundTaskItem = NULL; DWORD dwTaskCount; ASSERT(This->IsGroupingEnabled); TaskItem = This->TaskItems; LastTaskItem = TaskItem + This->TaskItemCount; dwTaskCount = (TaskGroup != NULL ? TaskGroup->dwTaskCount : MAX_TASKS_COUNT); ASSERT(dwTaskCount > 0); while (TaskItem != LastTaskItem) { if (TaskItem->Group == TaskGroup) { if ((NewTaskItem != NULL && TaskItem != NewTaskItem) || NewTaskItem == NULL) { FoundTaskItem = TaskItem; } if (--dwTaskCount == 0) { /* We found the last task item in the group! */ break; } } TaskItem++; } return FoundTaskItem; } static INT TaskSwitchWnd_CalculateTaskItemNewButtonIndex(IN OUT PTASK_SWITCH_WND This, IN PTASK_ITEM TaskItem) { PTASK_GROUP TaskGroup; PTASK_ITEM LastTaskItem; /* NOTE: This routine assumes that the group is *not* collapsed! */ TaskGroup = TaskItem->Group; if (This->IsGroupingEnabled) { if (TaskGroup != NULL) { ASSERT(TaskGroup->Index < 0); ASSERT(!TaskGroup->IsCollapsed); if (TaskGroup->dwTaskCount > 1) { LastTaskItem = TaskSwitchWnd_FindLastTaskItemOfGroup(This, TaskGroup, TaskItem); if (LastTaskItem != NULL) { /* Since the group is expanded the task items must have an index */ ASSERT(LastTaskItem->Index >= 0); return LastTaskItem->Index + 1; } } } else { /* Find the last NULL group button. NULL groups are added at the end of the task item list when grouping is enabled */ LastTaskItem = TaskSwitchWnd_FindLastTaskItemOfGroup(This, NULL, TaskItem); if (LastTaskItem != NULL) { ASSERT(LastTaskItem->Index >= 0); return LastTaskItem->Index + 1; } } } return This->ToolbarBtnCount; } static INT TaskSwitchWnd_AddTaskItemButton(IN OUT PTASK_SWITCH_WND This, IN OUT PTASK_ITEM TaskItem) { TBBUTTON tbBtn; INT iIndex; HICON icon; if (TaskItem->Index >= 0) { return TaskSwitchWnd_UpdateTaskItemButton(This, TaskItem); } if (TaskItem->Group != NULL && TaskItem->Group->IsCollapsed) { /* The task group is collapsed, we only need to update the group button */ return TaskSwitchWnd_UpdateTaskGroupButton(This, TaskItem->Group); } icon = TaskSwitchWnd_GetWndIcon(TaskItem->hWnd); TaskItem->IconIndex = ImageList_AddIcon(This->TaskIcons, icon); tbBtn.iBitmap = TaskItem->IconIndex; tbBtn.fsState = TBSTATE_ENABLED | TBSTATE_ELLIPSES; tbBtn.fsStyle = BTNS_CHECK | BTNS_NOPREFIX | BTNS_SHOWTEXT; tbBtn.dwData = TaskItem->Index; tbBtn.iString = (DWORD_PTR)TaskSwitchWnd_GetWndTextFromTaskItem(This, TaskItem); /* Find out where to insert the new button */ iIndex = TaskSwitchWnd_CalculateTaskItemNewButtonIndex(This, TaskItem); ASSERT(iIndex >= 0); tbBtn.idCommand = iIndex; TaskSwitchWnd_BeginUpdate(This); if (SendMessage(This->hWndToolbar, TB_INSERTBUTTON, (WPARAM)iIndex, (LPARAM)&tbBtn)) { TaskSwitchWnd_UpdateIndexesAfterButtonInserted(This, iIndex); DbgPrint("Added button %d for hwnd 0x%p\n", iIndex, TaskItem->hWnd); TaskItem->Index = iIndex; This->ToolbarBtnCount++; /* Update button sizes and fix the button wrapping */ TaskSwitchWnd_UpdateButtonsSize(This, TRUE); return iIndex; } TaskSwitchWnd_EndUpdate(This); return -1; } static BOOL TaskSwitchWnd_DeleteTaskItemButton(IN OUT PTASK_SWITCH_WND This, IN OUT PTASK_ITEM TaskItem) { PTASK_GROUP TaskGroup; INT iIndex; TaskGroup = TaskItem->Group; if (TaskItem->Index >= 0) { if ((TaskGroup != NULL && !TaskGroup->IsCollapsed) || TaskGroup == NULL) { TaskSwitchWnd_BeginUpdate(This); TaskSwitchWnd_RemoveIcon(This, TaskItem); iIndex = TaskItem->Index; if (SendMessage(This->hWndToolbar, TB_DELETEBUTTON, (WPARAM)iIndex, 0)) { TaskItem->Index = -1; This->ToolbarBtnCount--; TaskSwitchWnd_UpdateIndexesAfterButtonDeleted(This, iIndex); /* Update button sizes and fix the button wrapping */ TaskSwitchWnd_UpdateButtonsSize(This, TRUE); return TRUE; } TaskSwitchWnd_EndUpdate(This); } } return FALSE; } static PTASK_GROUP TaskSwitchWnd_AddToTaskGroup(IN OUT PTASK_SWITCH_WND This, IN HWND hWnd) { DWORD dwProcessId; PTASK_GROUP TaskGroup, *PrevLink; if (!GetWindowThreadProcessId(hWnd, &dwProcessId)) { DbgPrint("Cannot get process id of hwnd 0x%p\n", hWnd); return NULL; } /* Try to find an existing task group */ TaskGroup = This->TaskGroups; PrevLink = &This->TaskGroups; while (TaskGroup != NULL) { if (TaskGroup->dwProcessId == dwProcessId) { TaskGroup->dwTaskCount++; return TaskGroup; } PrevLink = &TaskGroup->Next; TaskGroup = TaskGroup->Next; } /* Allocate a new task group */ TaskGroup = HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, sizeof(*TaskGroup)); if (TaskGroup != NULL) { TaskGroup->dwTaskCount = 1; TaskGroup->dwProcessId = dwProcessId; TaskGroup->Index = -1; /* Add the task group to the list */ *PrevLink = TaskGroup; } return TaskGroup; } static VOID TaskSwitchWnd_RemoveTaskFromTaskGroup(IN OUT PTASK_SWITCH_WND This, IN OUT PTASK_ITEM TaskItem) { PTASK_GROUP TaskGroup, CurrentGroup, *PrevLink; TaskGroup = TaskItem->Group; if (TaskGroup != NULL) { DWORD dwNewTaskCount = --TaskGroup->dwTaskCount; if (dwNewTaskCount == 0) { /* Find the previous pointer in the chain */ CurrentGroup = This->TaskGroups; PrevLink = &This->TaskGroups; while (CurrentGroup != TaskGroup) { PrevLink = &CurrentGroup->Next; CurrentGroup = CurrentGroup->Next; } /* Remove the group from the list */ ASSERT(TaskGroup == CurrentGroup); *PrevLink = TaskGroup->Next; /* Free the task group */ HeapFree(hProcessHeap, 0, TaskGroup); } else if (TaskGroup->IsCollapsed && TaskGroup->Index >= 0) { if (dwNewTaskCount > 1) { /* FIXME: Check if we should expand the group */ /* Update the task group button */ TaskSwitchWnd_UpdateTaskGroupButton(This, TaskGroup); } else { /* Expand the group of one task button to a task button */ TaskSwitchWnd_ExpandTaskGroup(This, TaskGroup); } } } } static PTASK_ITEM TaskSwitchWnd_FindTaskItem(IN OUT PTASK_SWITCH_WND This, IN HWND hWnd) { PTASK_ITEM TaskItem, LastItem; TaskItem = This->TaskItems; LastItem = TaskItem + This->TaskItemCount; while (TaskItem != LastItem) { if (TaskItem->hWnd == hWnd) return TaskItem; TaskItem++; } return NULL; } static PTASK_ITEM TaskSwitchWnd_FindOtherTaskItem(IN OUT PTASK_SWITCH_WND This, IN HWND hWnd) { PTASK_ITEM LastItem, TaskItem; PTASK_GROUP TaskGroup; DWORD dwProcessId; if (!GetWindowThreadProcessId(hWnd, &dwProcessId)) { return NULL; } /* Try to find another task that belongs to the same process as the given window */ TaskItem = This->TaskItems; LastItem = TaskItem + This->TaskItemCount; while (TaskItem != LastItem) { TaskGroup = TaskItem->Group; if (TaskGroup != NULL) { if (TaskGroup->dwProcessId == dwProcessId) return TaskItem; } else { DWORD dwProcessIdTask; if (GetWindowThreadProcessId(TaskItem->hWnd, &dwProcessIdTask) && dwProcessIdTask == dwProcessId) { return TaskItem; } } TaskItem++; } return NULL; } static PTASK_ITEM TaskSwitchWnd_AllocTaskItem(IN OUT PTASK_SWITCH_WND This) { if (This->TaskItemCount >= MAX_TASKS_COUNT) { /* We need the most significant bit in 16 bit command IDs to indicate whether it is a task group or task item. WM_COMMAND limits command IDs to 16 bits! */ return NULL; } ASSERT(This->AllocatedTaskItems >= This->TaskItemCount); if (This->TaskItemCount == 0) { This->TaskItems = HeapAlloc(hProcessHeap, 0, TASK_ITEM_ARRAY_ALLOC * sizeof(*This->TaskItems)); if (This->TaskItems != NULL) { This->AllocatedTaskItems = TASK_ITEM_ARRAY_ALLOC; } else return NULL; } else if (This->TaskItemCount >= This->AllocatedTaskItems) { PTASK_ITEM NewArray; SIZE_T NewArrayLength, ActiveTaskItemIndex; NewArrayLength = This->AllocatedTaskItems + TASK_ITEM_ARRAY_ALLOC; NewArray = HeapReAlloc(hProcessHeap, 0, This->TaskItems, NewArrayLength * sizeof(*This->TaskItems)); if (NewArray != NULL) { if (This->ActiveTaskItem != NULL) { /* Fixup the ActiveTaskItem pointer */ ActiveTaskItemIndex = This->ActiveTaskItem - This->TaskItems; This->ActiveTaskItem = NewArray + ActiveTaskItemIndex; } This->AllocatedTaskItems = (WORD)NewArrayLength; This->TaskItems = NewArray; } else return NULL; } return This->TaskItems + This->TaskItemCount++; } static VOID TaskSwitchWnd_FreeTaskItem(IN OUT PTASK_SWITCH_WND This, IN OUT PTASK_ITEM TaskItem) { WORD wIndex; if (TaskItem == This->ActiveTaskItem) This->ActiveTaskItem = NULL; wIndex = (WORD)(TaskItem - This->TaskItems); if (wIndex + 1 < This->TaskItemCount) { MoveMemory(TaskItem, TaskItem + 1, (This->TaskItemCount - wIndex - 1) * sizeof(*TaskItem)); } This->TaskItemCount--; } static VOID TaskSwitchWnd_DeleteTaskItem(IN OUT PTASK_SWITCH_WND This, IN OUT PTASK_ITEM TaskItem) { if (!This->IsDestroying) { /* Delete the task button from the toolbar */ TaskSwitchWnd_DeleteTaskItemButton(This, TaskItem); } /* Remove the task from it's group */ TaskSwitchWnd_RemoveTaskFromTaskGroup(This, TaskItem); /* Free the task item */ TaskSwitchWnd_FreeTaskItem(This, TaskItem); } static VOID TaskSwitchWnd_CheckActivateTaskItem(IN OUT PTASK_SWITCH_WND This, IN OUT PTASK_ITEM TaskItem) { PTASK_ITEM ActiveTaskItem; PTASK_GROUP TaskGroup = NULL; ActiveTaskItem = This->ActiveTaskItem; if (TaskItem != NULL) TaskGroup = TaskItem->Group; if (This->IsGroupingEnabled && TaskGroup != NULL && TaskGroup->IsCollapsed) { /* FIXME */ return; } if (ActiveTaskItem != NULL) { PTASK_GROUP ActiveTaskGroup; if (ActiveTaskItem == TaskItem) return; ActiveTaskGroup = ActiveTaskItem->Group; if (This->IsGroupingEnabled && ActiveTaskGroup != NULL && ActiveTaskGroup->IsCollapsed) { if (ActiveTaskGroup == TaskGroup) return; /* FIXME */ } else { This->ActiveTaskItem = NULL; if (ActiveTaskItem->Index >= 0) { TaskSwitchWnd_UpdateTaskItemButton(This, ActiveTaskItem); } } } This->ActiveTaskItem = TaskItem; if (TaskItem != NULL && TaskItem->Index >= 0) { TaskSwitchWnd_UpdateTaskItemButton(This, TaskItem); } } static PTASK_ITEM FindTaskItemByIndex(IN OUT PTASK_SWITCH_WND This, IN INT Index) { PTASK_ITEM TaskItem, LastItem; TaskItem = This->TaskItems; LastItem = TaskItem + This->TaskItemCount; while (TaskItem != LastItem) { if (TaskItem->Index == Index) return TaskItem; TaskItem++; } return NULL; } static PTASK_GROUP FindTaskGroupByIndex(IN OUT PTASK_SWITCH_WND This, IN INT Index) { PTASK_GROUP CurrentGroup; CurrentGroup = This->TaskGroups; while (CurrentGroup != NULL) { if (CurrentGroup->Index == Index) break; CurrentGroup = CurrentGroup->Next; } return CurrentGroup; } static BOOL TaskSwitchWnd_AddTask(IN OUT PTASK_SWITCH_WND This, IN HWND hWnd) { PTASK_ITEM TaskItem; TaskItem = TaskSwitchWnd_FindTaskItem(This, hWnd); if (TaskItem == NULL) { DbgPrint("Add window 0x%p\n", hWnd); TaskItem = TaskSwitchWnd_AllocTaskItem(This); if (TaskItem != NULL) { ZeroMemory(TaskItem, sizeof(*TaskItem)); TaskItem->hWnd = hWnd; TaskItem->Index = -1; TaskItem->Group = TaskSwitchWnd_AddToTaskGroup(This, hWnd); if (!This->IsDestroying) { TaskSwitchWnd_AddTaskItemButton(This, TaskItem); } } } return TaskItem != NULL; } static BOOL TaskSwitchWnd_ActivateTaskItem(IN OUT PTASK_SWITCH_WND This, IN OUT PTASK_ITEM TaskItem OPTIONAL) { if (TaskItem != NULL) { DbgPrint("Activate window 0x%p on button %d\n", TaskItem->hWnd, TaskItem->Index); } TaskSwitchWnd_CheckActivateTaskItem(This, TaskItem); return FALSE; } static BOOL TaskSwitchWnd_ActivateTask(IN OUT PTASK_SWITCH_WND This, IN HWND hWnd) { PTASK_ITEM TaskItem; TaskItem = TaskSwitchWnd_FindTaskItem(This, hWnd); if (TaskItem == NULL) { TaskItem = TaskSwitchWnd_FindOtherTaskItem(This, hWnd); } if (TaskItem == NULL) { DbgPrint("Activate window 0x%p, could not find task\n", hWnd); } return TaskSwitchWnd_ActivateTaskItem(This, TaskItem); } static BOOL TaskSwitchWnd_DeleteTask(IN OUT PTASK_SWITCH_WND This, IN HWND hWnd) { PTASK_ITEM TaskItem; TaskItem = TaskSwitchWnd_FindTaskItem(This, hWnd); if (TaskItem != NULL) { DbgPrint("Delete window 0x%p on button %d\n", hWnd, TaskItem->Index); TaskSwitchWnd_DeleteTaskItem(This, TaskItem); return TRUE; } //else //DbgPrint("Failed to delete window 0x%p\n", hWnd); return FALSE; } static VOID TaskSwitchWnd_DeleteAllTasks(IN OUT PTASK_SWITCH_WND This) { PTASK_ITEM CurrentTask; if (This->TaskItemCount > 0) { CurrentTask = This->TaskItems + This->TaskItemCount; do { TaskSwitchWnd_DeleteTaskItem(This, --CurrentTask); } while (CurrentTask != This->TaskItems); } } static VOID TaskSwitchWnd_FlashTaskItem(IN OUT PTASK_SWITCH_WND This, IN OUT PTASK_ITEM TaskItem) { TaskItem->RenderFlashed = 1; TaskSwitchWnd_UpdateTaskItemButton(This, TaskItem); } static BOOL TaskSwitchWnd_FlashTask(IN OUT PTASK_SWITCH_WND This, IN HWND hWnd) { PTASK_ITEM TaskItem; TaskItem = TaskSwitchWnd_FindTaskItem(This, hWnd); if (TaskItem != NULL) { DbgPrint("Flashing window 0x%p on button %d\n", hWnd, TaskItem->Index); TaskSwitchWnd_FlashTaskItem(This, TaskItem); return TRUE; } return FALSE; } static VOID TaskSwitchWnd_RedrawTaskItem(IN OUT PTASK_SWITCH_WND This, IN OUT PTASK_ITEM TaskItem) { PTASK_GROUP TaskGroup; TaskGroup = TaskItem->Group; if (This->IsGroupingEnabled && TaskGroup != NULL) { if (TaskGroup->IsCollapsed && TaskGroup->Index >= 0) { TaskSwitchWnd_UpdateTaskGroupButton(This, TaskGroup); } else if (TaskItem->Index >= 0) { goto UpdateTaskItem; } } else if (TaskItem->Index >= 0) { UpdateTaskItem: TaskItem->RenderFlashed = 0; TaskSwitchWnd_UpdateTaskItemButton(This, TaskItem); } } static BOOL TaskSwitchWnd_RedrawTask(IN OUT PTASK_SWITCH_WND This, IN HWND hWnd) { PTASK_ITEM TaskItem; TaskItem = TaskSwitchWnd_FindTaskItem(This, hWnd); if (TaskItem != NULL) { TaskSwitchWnd_RedrawTaskItem(This, TaskItem); return TRUE; } return FALSE; } static INT TaskSwitchWnd_UpdateTbButtonSpacing(IN OUT PTASK_SWITCH_WND This, IN BOOL bHorizontal, IN UINT uiRows, IN UINT uiBtnsPerLine) { TBMETRICS tbm; tbm.cbSize = sizeof(tbm); tbm.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING; tbm.cxBarPad = tbm.cyBarPad = 0; if (bHorizontal || uiBtnsPerLine > 1) tbm.cxButtonSpacing = (3 * GetSystemMetrics(SM_CXEDGE) / 2); else tbm.cxButtonSpacing = 0; if (!bHorizontal || uiRows > 1) tbm.cyButtonSpacing = (3 * GetSystemMetrics(SM_CYEDGE) / 2); else tbm.cyButtonSpacing = 0; SendMessage(This->hWndToolbar, TB_SETMETRICS, 0, (LPARAM)&tbm); return tbm.cxButtonSpacing; } static VOID TaskSwitchWnd_UpdateButtonsSize(IN OUT PTASK_SWITCH_WND This, IN BOOL bRedrawDisabled) { RECT rcClient; UINT uiRows, uiMax, uiMin, uiBtnsPerLine, ui; LONG NewBtnSize; BOOL Horizontal; TBBUTTONINFO tbbi; TBMETRICS tbm; if (GetClientRect(This->hWnd, &rcClient) && !IsRectEmpty(&rcClient)) { if (This->ToolbarBtnCount > 0) { ZeroMemory (&tbm, sizeof (tbm)); tbm.cbSize = sizeof(tbm); tbm.dwMask = TBMF_BUTTONSPACING; SendMessage(This->hWndToolbar, TB_GETMETRICS, 0, (LPARAM)&tbm); uiRows = (rcClient.bottom + tbm.cyButtonSpacing) / (This->ButtonSize.cy + tbm.cyButtonSpacing); if (uiRows == 0) uiRows = 1; uiBtnsPerLine = (This->ToolbarBtnCount + uiRows - 1) / uiRows; Horizontal = ITrayWindow_IsHorizontal(This->Tray); if (!bRedrawDisabled) TaskSwitchWnd_BeginUpdate(This); /* We might need to update the button spacing */ tbm.cxButtonSpacing = TaskSwitchWnd_UpdateTbButtonSpacing(This, Horizontal, uiRows, uiBtnsPerLine); /* Calculate the ideal width and make sure it's within the allowed range */ NewBtnSize = (rcClient.right - (uiBtnsPerLine * tbm.cxButtonSpacing)) / uiBtnsPerLine; /* Determine the minimum and maximum width of a button */ if (Horizontal) uiMax = GetSystemMetrics(SM_CXMINIMIZED); else uiMax = rcClient.right; uiMin = GetSystemMetrics(SM_CXSIZE) + (2 * GetSystemMetrics(SM_CXEDGE)); if (NewBtnSize < (LONG)uiMin) NewBtnSize = uiMin; if (NewBtnSize > (LONG)uiMax) NewBtnSize = uiMax; This->ButtonSize.cx = NewBtnSize; /* Recalculate how many buttons actually fit into one line */ uiBtnsPerLine = rcClient.right / (NewBtnSize + tbm.cxButtonSpacing); if (uiBtnsPerLine == 0) uiBtnsPerLine++; This->TbButtonsPerLine = uiBtnsPerLine; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_BYINDEX | TBIF_SIZE | TBIF_STATE; tbbi.cx = (INT)NewBtnSize; for (ui = 0; ui != This->ToolbarBtnCount; ui++) { tbbi.fsState = TBSTATE_ENABLED; /* Check if we're updating a button that is the last one in the line. If so, we need to set the TBSTATE_WRAP flag! */ if ((ui + 1) % uiBtnsPerLine == 0) tbbi.fsState |= TBSTATE_WRAP; if (This->ActiveTaskItem != NULL && This->ActiveTaskItem->Index == ui) { tbbi.fsState |= TBSTATE_CHECKED; } SendMessage(This->hWndToolbar, TB_SETBUTTONINFO, (WPARAM)ui, (LPARAM)&tbbi); } #if 0 /* FIXME: Force the window to the correct position in case some idiot did something to us */ SetWindowPos(This->hWndToolbar, NULL, 0, 0, rcClient.right, /* FIXME */ rcClient.bottom, /* FIXME */ SWP_NOACTIVATE | SWP_NOZORDER); #endif } else { This->TbButtonsPerLine = 0; This->ButtonSize.cx = 0; } } TaskSwitchWnd_EndUpdate(This); } static BOOL CALLBACK TaskSwitchWnd_EnumWindowsProc(IN HWND hWnd, IN LPARAM lParam) { PTASK_SWITCH_WND This = (PTASK_SWITCH_WND)lParam; /* Only show windows that still exist and are visible and none of explorer's special windows (such as the desktop or the tray window) */ if (IsWindow(hWnd) && IsWindowVisible(hWnd) && !ITrayWindow_IsSpecialHWND(This->Tray, hWnd)) { /* Don't list popup windows and also no tool windows */ if (GetWindow(hWnd, GW_OWNER) == NULL && !(GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_TOOLWINDOW)) { TaskSwitchWnd_AddTask(This, hWnd); } } return TRUE; } static LRESULT CALLBACK TaskSwichWnd_ToolbarSubclassedProc(IN HWND hWnd, IN UINT msg, IN WPARAM wParam, IN LPARAM lParam, IN UINT_PTR uIdSubclass, IN DWORD_PTR dwRefData) { LRESULT Ret; Ret = DefSubclassProc(hWnd, msg, wParam, lParam); if (msg == WM_NCHITTEST && Ret == HTCLIENT) { POINT pt; /* See if the mouse is on a button */ pt.x = (SHORT)LOWORD(lParam); pt.y = (SHORT)HIWORD(lParam); if (MapWindowPoints(HWND_DESKTOP, hWnd, &pt, 1) != 0 && (INT)SendMessage(hWnd, TB_HITTEST, 0, (LPARAM)&pt) < 0) { /* Make the control appear to be transparent outside of any buttons */ Ret = HTTRANSPARENT; } } return Ret; } static VOID TaskSwitchWnd_UpdateTheme(IN OUT PTASK_SWITCH_WND This) { if (This->TaskBandTheme) CloseThemeData(This->TaskBandTheme); if (IsThemeActive()) This->TaskBandTheme = OpenThemeData(This->hWnd, L"TaskBand"); else This->TaskBandTheme = NULL; } static VOID TaskSwitchWnd_Create(IN OUT PTASK_SWITCH_WND This) { This->hWndToolbar = CreateWindowEx(0, TOOLBARCLASSNAME, szRunningApps, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | TBSTYLE_LIST | TBSTYLE_TRANSPARENT | CCS_TOP | CCS_NORESIZE | CCS_NODIVIDER, 0, 0, 0, 0, This->hWnd, NULL, hExplorerInstance, NULL); if (This->hWndToolbar != NULL) { HMODULE hShell32; SIZE BtnSize; SetWindowTheme(This->hWndToolbar, L"TaskBand", NULL); TaskSwitchWnd_UpdateTheme(This); /* Identify the version we're using */ SendMessage(This->hWndToolbar, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0); This->TaskIcons = ImageList_Create(16, 16, ILC_COLOR32 | ILC_MASK, 0, 1000); SendMessage(This->hWndToolbar, TB_SETIMAGELIST, 0, (LPARAM)This->TaskIcons); /* Calculate the default button size. Don't save this in This->ButtonSize.cx so that the actual button width gets updated correctly on the first recalculation */ BtnSize.cx = GetSystemMetrics(SM_CXMINIMIZED); This->ButtonSize.cy = BtnSize.cy = GetSystemMetrics(SM_CYSIZE) + (2 * GetSystemMetrics(SM_CYEDGE)); SendMessage(This->hWndToolbar, TB_SETBUTTONSIZE, 0, (LPARAM)MAKELONG(BtnSize.cx, BtnSize.cy)); /* We don't want to see partially clipped buttons...not that we could see them... */ #if 0 SendMessage(This->hWndToolbar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_HIDECLIPPEDBUTTONS); #endif /* Set proper spacing between buttons */ TaskSwitchWnd_UpdateTbButtonSpacing(This, ITrayWindow_IsHorizontal(This->Tray), 0, 0); /* Register the shell hook */ This->ShellHookMsg = RegisterWindowMessage(TEXT("SHELLHOOK")); hShell32 = GetModuleHandle(TEXT("SHELL32.DLL")); if (hShell32 != NULL) { REGSHELLHOOK RegShellHook; /* RegisterShellHook */ RegShellHook = (REGSHELLHOOK)GetProcAddress(hShell32, (LPCSTR)((LONG)181)); if (RegShellHook != NULL) { RegShellHook(This->hWnd, 3); /* 1 if no NT! We're targeting NT so we don't care! */ } } /* Add all windows to the toolbar */ EnumWindows(TaskSwitchWnd_EnumWindowsProc, (LPARAM)This); /* Recalculate the button size */ TaskSwitchWnd_UpdateButtonsSize(This, FALSE); /* Subclass the toolbar control because it doesn't provide a NM_NCHITTEST notification */ This->IsToolbarSubclassed = SetWindowSubclass(This->hWndToolbar, TaskSwichWnd_ToolbarSubclassedProc, TSW_TOOLBAR_SUBCLASS_ID, (DWORD_PTR)This); } } static VOID TaskSwitchWnd_NCDestroy(IN OUT PTASK_SWITCH_WND This) { HMODULE hShell32; This->IsDestroying = TRUE; /* Unregister the shell hook */ hShell32 = GetModuleHandle(TEXT("SHELL32.DLL")); if (hShell32 != NULL) { REGSHELLHOOK RegShellHook; /* RegisterShellHook */ RegShellHook = (REGSHELLHOOK)GetProcAddress(hShell32, (LPCSTR)((LONG)181)); if (RegShellHook != NULL) { RegShellHook(This->hWnd, FALSE); } } CloseThemeData(This->TaskBandTheme); TaskSwitchWnd_DeleteAllTasks(This); } static BOOL TaskSwitchWnd_HandleAppCommand(IN OUT PTASK_SWITCH_WND This, IN WPARAM wParam, IN LPARAM lParam) { BOOL Ret = FALSE; switch (GET_APPCOMMAND_LPARAM(lParam)) { case APPCOMMAND_BROWSER_SEARCH: Ret = SHFindFiles(NULL, NULL); break; case APPCOMMAND_BROWSER_HOME: case APPCOMMAND_LAUNCH_MAIL: default: DbgPrint("Shell app command %d unhandled!\n", (INT)GET_APPCOMMAND_LPARAM(lParam)); break; } return Ret; } static LRESULT TaskSwitchWnd_HandleShellHookMsg(IN OUT PTASK_SWITCH_WND This, IN WPARAM wParam, IN LPARAM lParam) { BOOL Ret = FALSE; switch ((INT)wParam) { case HSHELL_APPCOMMAND: TaskSwitchWnd_HandleAppCommand(This, wParam, lParam); Ret = TRUE; break; case HSHELL_WINDOWCREATED: TaskSwitchWnd_AddTask(This, (HWND)lParam); Ret = TRUE; break; case HSHELL_WINDOWDESTROYED: /* The window still exists! Delay destroying it a bit */ TaskSwitchWnd_DeleteTask(This, (HWND)lParam); Ret = TRUE; break; case HSHELL_ACTIVATESHELLWINDOW: goto UnhandledShellMessage; case HSHELL_RUDEAPPACTIVATED: goto UnhandledShellMessage; case HSHELL_WINDOWACTIVATED: TaskSwitchWnd_ActivateTask(This, (HWND)lParam); Ret = TRUE; break; case HSHELL_GETMINRECT: goto UnhandledShellMessage; case HSHELL_FLASH: TaskSwitchWnd_FlashTask(This, (HWND)lParam); Ret = TRUE; break; case HSHELL_REDRAW: TaskSwitchWnd_RedrawTask(This, (HWND)lParam); Ret = TRUE; break; case HSHELL_TASKMAN: PostMessage(ITrayWindow_GetHWND(This->Tray), TWM_OPENSTARTMENU,0, 0); break; case HSHELL_LANGUAGE: case HSHELL_SYSMENU: case HSHELL_ENDTASK: case HSHELL_ACCESSIBILITYSTATE: case HSHELL_WINDOWREPLACED: case HSHELL_WINDOWREPLACING: default: { static const struct { INT msg; LPCWSTR msg_name; } hshell_msg[] = { {HSHELL_WINDOWCREATED, L"HSHELL_WINDOWCREATED"}, {HSHELL_WINDOWDESTROYED, L"HSHELL_WINDOWDESTROYED"}, {HSHELL_ACTIVATESHELLWINDOW, L"HSHELL_ACTIVATESHELLWINDOW"}, {HSHELL_WINDOWACTIVATED, L"HSHELL_WINDOWACTIVATED"}, {HSHELL_GETMINRECT, L"HSHELL_GETMINRECT"}, {HSHELL_REDRAW, L"HSHELL_REDRAW"}, {HSHELL_TASKMAN, L"HSHELL_TASKMAN"}, {HSHELL_LANGUAGE, L"HSHELL_LANGUAGE"}, {HSHELL_SYSMENU, L"HSHELL_SYSMENU"}, {HSHELL_ENDTASK, L"HSHELL_ENDTASK"}, {HSHELL_ACCESSIBILITYSTATE, L"HSHELL_ACCESSIBILITYSTATE"}, {HSHELL_APPCOMMAND, L"HSHELL_APPCOMMAND"}, {HSHELL_WINDOWREPLACED, L"HSHELL_WINDOWREPLACED"}, {HSHELL_WINDOWREPLACING, L"HSHELL_WINDOWREPLACING"}, {HSHELL_RUDEAPPACTIVATED, L"HSHELL_RUDEAPPACTIVATED"}, }; int i, found; UnhandledShellMessage: for (i = 0, found = 0; i != sizeof(hshell_msg) / sizeof(hshell_msg[0]); i++) { if (hshell_msg[i].msg == (INT)wParam) { DbgPrint("Shell message %ws unhandled (lParam = 0x%p)!\n", hshell_msg[i].msg_name, lParam); found = 1; break; } } if (!found) { DbgPrint("Shell message %d unhandled (lParam = 0x%p)!\n", (INT)wParam, lParam); } break; } } return Ret; } static VOID TaskSwitchWnd_EnableGrouping(IN OUT PTASK_SWITCH_WND This, IN BOOL bEnable) { This->IsGroupingEnabled = bEnable; /* Collapse or expand groups if neccessary */ TaskSwitchWnd_UpdateButtonsSize(This, FALSE); } static VOID TaskSwitchWnd_HandleTaskItemClick(IN OUT PTASK_SWITCH_WND This, IN OUT PTASK_ITEM TaskItem) { BOOL bIsMinimized; BOOL bIsActive; if (IsWindow(TaskItem->hWnd)) { bIsMinimized = IsIconic(TaskItem->hWnd); bIsActive = (TaskItem == This->ActiveTaskItem); if (!bIsMinimized && bIsActive) { PostMessage(TaskItem->hWnd, WM_SYSCOMMAND, SC_MINIMIZE, 0); } else { if (bIsMinimized) { PostMessage(TaskItem->hWnd, WM_SYSCOMMAND, SC_RESTORE, 0); } SetForegroundWindow(TaskItem->hWnd); } } } static VOID TaskSwitchWnd_HandleTaskGroupClick(IN OUT PTASK_SWITCH_WND This, IN OUT PTASK_GROUP TaskGroup) { /* TODO: Show task group menu */ } static BOOL TaskSwitchWnd_HandleButtonClick(IN OUT PTASK_SWITCH_WND This, IN WORD wIndex) { PTASK_ITEM TaskItem; PTASK_GROUP TaskGroup; if (This->IsGroupingEnabled) { TaskGroup = FindTaskGroupByIndex(This, (INT)wIndex); if (TaskGroup != NULL && TaskGroup->IsCollapsed) { TaskSwitchWnd_HandleTaskGroupClick(This, TaskGroup); return TRUE; } } TaskItem = FindTaskItemByIndex(This, (INT)wIndex); if (TaskItem != NULL) { TaskSwitchWnd_HandleTaskItemClick(This, TaskItem); return TRUE; } return FALSE; } static VOID TaskSwitchWnd_HandleTaskItemRightClick(IN OUT PTASK_SWITCH_WND This, IN OUT PTASK_ITEM TaskItem) { HMENU hmenu = GetSystemMenu(TaskItem->hWnd, FALSE); if (hmenu) { POINT pt; int cmd; GetCursorPos(&pt); cmd = TrackPopupMenu(hmenu, TPM_LEFTBUTTON|TPM_RIGHTBUTTON|TPM_RETURNCMD, pt.x, pt.y, 0, This->hWndToolbar, NULL); if (cmd) { SetForegroundWindow(TaskItem->hWnd); // reactivate window after the context menu has closed PostMessage(TaskItem->hWnd, WM_SYSCOMMAND, cmd, 0); } } } static VOID TaskSwitchWnd_HandleTaskGroupRightClick(IN OUT PTASK_SWITCH_WND This, IN OUT PTASK_GROUP TaskGroup) { /* TODO: Show task group right click menu */ } static BOOL TaskSwitchWnd_HandleButtonRightClick(IN OUT PTASK_SWITCH_WND This, IN WORD wIndex) { PTASK_ITEM TaskItem; PTASK_GROUP TaskGroup; if (This->IsGroupingEnabled) { TaskGroup = FindTaskGroupByIndex(This, (INT)wIndex); if (TaskGroup != NULL && TaskGroup->IsCollapsed) { TaskSwitchWnd_HandleTaskGroupRightClick(This, TaskGroup); return TRUE; } } TaskItem = FindTaskItemByIndex(This, (INT)wIndex); if (TaskItem != NULL) { TaskSwitchWnd_HandleTaskItemRightClick(This, TaskItem); return TRUE; } return FALSE; } static LRESULT TaskSwichWnd_HandleItemPaint(IN OUT PTASK_SWITCH_WND This, IN OUT NMTBCUSTOMDRAW *nmtbcd) { LRESULT Ret = CDRF_DODEFAULT; PTASK_GROUP TaskGroup; PTASK_ITEM TaskItem; TaskItem = FindTaskItemByIndex(This, (INT)nmtbcd->nmcd.dwItemSpec); TaskGroup = FindTaskGroupByIndex(This, (INT)nmtbcd->nmcd.dwItemSpec); if (TaskGroup == NULL && TaskItem != NULL) { ASSERT(TaskItem != NULL); if (TaskItem != NULL && IsWindow(TaskItem->hWnd)) { /* Make the entire button flashing if neccessary */ if (nmtbcd->nmcd.uItemState & CDIS_MARKED) { Ret = TBCDRF_NOBACKGROUND; if (!This->TaskBandTheme) { SelectObject(nmtbcd->nmcd.hdc, GetSysColorBrush(COLOR_HIGHLIGHT)); Rectangle(nmtbcd->nmcd.hdc, nmtbcd->nmcd.rc.left, nmtbcd->nmcd.rc.top, nmtbcd->nmcd.rc.right, nmtbcd->nmcd.rc.bottom); } else { DrawThemeBackground(This->TaskBandTheme, nmtbcd->nmcd.hdc, TDP_FLASHBUTTON, 0, &nmtbcd->nmcd.rc, 0); } nmtbcd->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT); return Ret; } } } else if (TaskGroup != NULL) { /* FIXME: Implement painting for task groups */ } return Ret; } static LRESULT TaskSwitchWnd_HandleToolbarNotification(IN OUT PTASK_SWITCH_WND This, IN const NMHDR *nmh) { LRESULT Ret = 0; switch (nmh->code) { case NM_CUSTOMDRAW: { LPNMTBCUSTOMDRAW nmtbcd = (LPNMTBCUSTOMDRAW)nmh; switch (nmtbcd->nmcd.dwDrawStage) { case CDDS_ITEMPREPAINT: Ret = TaskSwichWnd_HandleItemPaint(This, nmtbcd); break; case CDDS_PREPAINT: Ret = CDRF_NOTIFYITEMDRAW; break; default: Ret = CDRF_DODEFAULT; break; } break; } } return Ret; } static VOID TaskSwitchWnd_DrawBackground(HWND hwnd, HDC hdc) { RECT rect; GetClientRect(hwnd, &rect); DrawThemeParentBackground(hwnd, hdc, &rect); } static LRESULT CALLBACK TaskSwitchWndProc(IN HWND hwnd, IN UINT uMsg, IN WPARAM wParam, IN LPARAM lParam) { PTASK_SWITCH_WND This = NULL; LRESULT Ret = FALSE; if (uMsg != WM_NCCREATE) { This = (PTASK_SWITCH_WND)GetWindowLongPtr(hwnd, 0); } if (This != NULL || uMsg == WM_NCCREATE) { switch (uMsg) { case WM_THEMECHANGED: TaskSwitchWnd_UpdateTheme(This); break; case WM_ERASEBKGND: TaskSwitchWnd_DrawBackground(hwnd, (HDC)wParam); break; case WM_SIZE: { SIZE szClient; szClient.cx = LOWORD(lParam); szClient.cy = HIWORD(lParam); if (This->hWndToolbar != NULL) { SetWindowPos(This->hWndToolbar, NULL, 0, 0, szClient.cx, szClient.cy, SWP_NOZORDER); TaskSwitchWnd_UpdateButtonsSize(This, FALSE); } break; } case WM_NCHITTEST: { /* We want the tray window to be draggable everywhere, so make the control appear transparent */ Ret = DefWindowProc(hwnd, uMsg, wParam, lParam); if (Ret != HTVSCROLL && Ret != HTHSCROLL) Ret = HTTRANSPARENT; break; } case WM_COMMAND: { if (lParam != 0 && (HWND)lParam == This->hWndToolbar) { TaskSwitchWnd_HandleButtonClick(This, LOWORD(wParam)); } break; } case WM_NOTIFY: { const NMHDR *nmh = (const NMHDR *)lParam; if (nmh->hwndFrom == This->hWndToolbar) { Ret = TaskSwitchWnd_HandleToolbarNotification(This, nmh); } break; } case TSWM_ENABLEGROUPING: { Ret = This->IsGroupingEnabled; if (wParam != This->IsGroupingEnabled) { TaskSwitchWnd_EnableGrouping(This, (BOOL)wParam); } break; } case TSWM_UPDATETASKBARPOS: { /* Update the button spacing */ TaskSwitchWnd_UpdateTbButtonSpacing(This, ITrayWindow_IsHorizontal(This->Tray), 0, 0); break; } case WM_CONTEXTMENU: { if (This->hWndToolbar != NULL) { POINT pt; INT_PTR iBtn; pt.x = (LONG)LOWORD(lParam); pt.y = (LONG)HIWORD(lParam); MapWindowPoints(NULL, This->hWndToolbar, &pt, 1); iBtn = (INT_PTR)SendMessage(This->hWndToolbar, TB_HITTEST, 0, (LPARAM)&pt); if (iBtn >= 0) { TaskSwitchWnd_HandleButtonRightClick(This, iBtn); } else goto ForwardContextMenuMsg; } else { ForwardContextMenuMsg: /* Forward message */ Ret = SendMessage(ITrayWindow_GetHWND(This->Tray), uMsg, wParam, lParam); } break; } case WM_NCCREATE: { LPCREATESTRUCT CreateStruct = (LPCREATESTRUCT)lParam; This = HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, sizeof(*This)); if (This == NULL) return FALSE; This->hWnd = hwnd; This->hWndNotify = CreateStruct->hwndParent; This->Tray = (ITrayWindow*)CreateStruct->lpCreateParams; This->IsGroupingEnabled = TRUE; /* FIXME */ SetWindowLongPtr(hwnd, 0, (LONG_PTR)This); return TRUE; } case WM_CREATE: TaskSwitchWnd_Create(This); #if DUMP_TASKS != 0 SetTimer(hwnd, 1, 5000, NULL); #endif break; case WM_DESTROY: if (This->IsToolbarSubclassed) { if (RemoveWindowSubclass(This->hWndToolbar, TaskSwichWnd_ToolbarSubclassedProc, TSW_TOOLBAR_SUBCLASS_ID)) { This->IsToolbarSubclassed = FALSE; } } break; case WM_NCDESTROY: TaskSwitchWnd_NCDestroy(This); HeapFree(hProcessHeap, 0, This); SetWindowLongPtr(hwnd, 0, 0); break; #if DUMP_TASKS != 0 case WM_TIMER: switch (wParam) { case 1: TaskSwitchWnd_DumpTasks(This); break; } break; #endif case WM_KLUDGEMINRECT: { PTASK_ITEM TaskItem = TaskSwitchWnd_FindTaskItem(This, (HWND)wParam); if (TaskItem) { RECT* prcMinRect = (RECT*)lParam; RECT rcItem, rcToolbar; SendMessageW(This->hWndToolbar,TB_GETITEMRECT, TaskItem->Index, (LPARAM)&rcItem); GetWindowRect(This->hWndToolbar, &rcToolbar); OffsetRect(&rcItem, rcToolbar.left, rcToolbar.top); *prcMinRect = rcItem; return TRUE; } return FALSE; } default: /* HandleDefaultMessage: */ if (uMsg == This->ShellHookMsg && This->ShellHookMsg != 0) { /* Process shell messages */ Ret = (LRESULT)TaskSwitchWnd_HandleShellHookMsg(This, wParam, lParam); break; } Ret = DefWindowProc(hwnd, uMsg, wParam, lParam); break; } } else { Ret = DefWindowProc(hwnd, uMsg, wParam, lParam); } return Ret; } HWND CreateTaskSwitchWnd(IN HWND hWndParent, IN OUT ITrayWindow *Tray) { HWND hwndTaskBar; hwndTaskBar = CreateWindowEx(0, szTaskSwitchWndClass, szRunningApps, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP, 0, 0, 0, 0, hWndParent, NULL, hExplorerInstance, (LPVOID)Tray); return hwndTaskBar; } BOOL RegisterTaskSwitchWndClass(VOID) { WNDCLASS wc; wc.style = CS_DBLCLKS; wc.lpfnWndProc = TaskSwitchWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof(PTASK_SWITCH_WND); wc.hInstance = hExplorerInstance; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); wc.lpszMenuName = NULL; wc.lpszClassName = szTaskSwitchWndClass; return RegisterClass(&wc) != 0; } VOID UnregisterTaskSwitchWndClass(VOID) { UnregisterClass(szTaskSwitchWndClass, hExplorerInstance); }