c776008ad0a5e1d857b7dcc9af706fa9ed26fd33
[reactos.git] / win32ss / user / user32 / controls / appswitch.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS system libraries
4 * FILE: win32ss/user/user32/controls/appswitch.c
5 * PURPOSE: app switching functionality
6 * PROGRAMMERS: Johannes Anderwald (johannes.anderwald@reactos.org)
7 * David Quintana (gigaherz@gmail.com)
8 * Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
9 */
10
11 //
12 // TODO:
13 // Move to Win32k.
14 // Add registry support.
15 //
16 //
17
18 #include <user32.h>
19
20 WINE_DEFAULT_DEBUG_CHANNEL(user32);
21
22 #define DIALOG_MARGIN 8 // margin of dialog contents
23
24 #define CX_ICON 32 // width of icon
25 #define CY_ICON 32 // height of icon
26 #define ICON_MARGIN 4 // margin width around an icon
27
28 #define CX_ITEM (CX_ICON + 2 * ICON_MARGIN)
29 #define CY_ITEM (CY_ICON + 2 * ICON_MARGIN)
30 #define ITEM_MARGIN 4 // margin width around an item
31
32 #define CX_ITEM_SPACE (CX_ITEM + 2 * ITEM_MARGIN)
33 #define CY_ITEM_SPACE (CY_ITEM + 2 * ITEM_MARGIN)
34
35 #define CY_TEXT_MARGIN 4 // margin height around text
36
37 // limit the number of windows shown in the alt-tab window
38 // 120 windows results in (12*40) by (10*40) pixels worth of icons.
39 #define MAX_WINDOWS 120
40
41 // Global variables
42 HWND switchdialog = NULL;
43 HFONT dialogFont;
44 int selectedWindow = 0;
45 BOOL isOpen = FALSE;
46
47 int fontHeight=0;
48
49 WCHAR windowText[1024];
50
51 HWND windowList[MAX_WINDOWS];
52 HICON iconList[MAX_WINDOWS];
53 int windowCount = 0;
54
55 int cxBorder, cyBorder;
56 int nItems, nCols, nRows;
57 int itemsW, itemsH;
58 int totalW, totalH;
59 int xOffset, yOffset;
60 POINT ptStart;
61
62 int nShift = 0;
63
64 BOOL Esc = FALSE;
65
66 BOOL CoolSwitch = TRUE;
67 int CoolSwitchRows = 3;
68 int CoolSwitchColumns = 7;
69
70 // window style
71 const DWORD Style = WS_POPUP | WS_BORDER | WS_DISABLED;
72 const DWORD ExStyle = WS_EX_TOPMOST | WS_EX_DLGMODALFRAME | WS_EX_TOOLWINDOW;
73
74 DWORD wtodw(const WCHAR *psz)
75 {
76 const WCHAR *pch = psz;
77 DWORD Value = 0;
78 while ('0' <= *pch && *pch <= '9')
79 {
80 Value *= 10;
81 Value += *pch - L'0';
82 }
83 return Value;
84 }
85
86 BOOL LoadCoolSwitchSettings(void)
87 {
88 CoolSwitch = TRUE;
89 CoolSwitchRows = 3;
90 CoolSwitchColumns = 7;
91
92 // FIXME: load the settings from registry
93
94 TRACE("CoolSwitch: %d\n", CoolSwitch);
95 TRACE("CoolSwitchRows: %d\n", CoolSwitchRows);
96 TRACE("CoolSwitchColumns: %d\n", CoolSwitchColumns);
97
98 return TRUE;
99 }
100
101 void ResizeAndCenter(HWND hwnd, int width, int height)
102 {
103 int x, y;
104 RECT Rect;
105
106 int screenwidth = GetSystemMetrics(SM_CXSCREEN);
107 int screenheight = GetSystemMetrics(SM_CYSCREEN);
108
109 x = (screenwidth - width) / 2;
110 y = (screenheight - height) / 2;
111
112 SetRect(&Rect, x, y, x + width, y + height);
113 AdjustWindowRectEx(&Rect, Style, FALSE, ExStyle);
114
115 x = Rect.left;
116 y = Rect.top;
117 width = Rect.right - Rect.left;
118 height = Rect.bottom - Rect.top;
119 MoveWindow(hwnd, x, y, width, height, FALSE);
120
121 ptStart.x = x;
122 ptStart.y = y;
123 }
124
125 void MakeWindowActive(HWND hwnd)
126 {
127 if (IsIconic(hwnd))
128 ShowWindowAsync(hwnd, SW_RESTORE);
129
130 BringWindowToTop(hwnd); // same as: SetWindowPos(hwnd,HWND_TOP,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE); ?
131 SetForegroundWindow(hwnd);
132 }
133
134 void CompleteSwitch(BOOL doSwitch)
135 {
136 if (!isOpen)
137 return;
138
139 isOpen = FALSE;
140
141 TRACE("[ATbot] CompleteSwitch Hiding Window.\n");
142 ShowWindow(switchdialog, SW_HIDE);
143
144 if(doSwitch)
145 {
146 if(selectedWindow >= windowCount)
147 return;
148
149 // FIXME: workaround because reactos fails to activate the previous window correctly.
150 //if(selectedWindow != 0)
151 {
152 HWND hwnd = windowList[selectedWindow];
153
154 GetWindowTextW(hwnd, windowText, _countof(windowText));
155
156 TRACE("[ATbot] CompleteSwitch Switching to 0x%08x (%ls)\n", hwnd, windowText);
157
158 MakeWindowActive(hwnd);
159 }
160 }
161
162 windowCount = 0;
163 }
164
165 BOOL CALLBACK EnumerateCallback(HWND window, LPARAM lParam)
166 {
167 HICON hIcon;
168
169 UNREFERENCED_PARAMETER(lParam);
170
171 if (!IsWindowVisible(window))
172 return TRUE;
173
174 GetClassNameW(window, windowText, _countof(windowText));
175 if ((wcscmp(L"Shell_TrayWnd", windowText)==0) ||
176 (wcscmp(L"Progman", windowText)==0) )
177 return TRUE;
178
179 // First try to get the big icon assigned to the window
180 hIcon = (HICON)SendMessageW(window, WM_GETICON, ICON_BIG, 0);
181 if (!hIcon)
182 {
183 // If no icon is assigned, try to get the icon assigned to the windows' class
184 hIcon = (HICON)GetClassLongPtrW(window, GCL_HICON);
185 if (!hIcon)
186 {
187 // If we still don't have an icon, see if we can do with the small icon,
188 // or a default application icon
189 hIcon = (HICON)SendMessageW(window, WM_GETICON, ICON_SMALL2, 0);
190 if (!hIcon)
191 {
192 // using windows logo icon as default
193 hIcon = gpsi->hIconWindows;
194 if (!hIcon)
195 {
196 //if all attempts to get icon fails go to the next window
197 return TRUE;
198 }
199 }
200 }
201 }
202
203 windowList[windowCount] = window;
204 iconList[windowCount] = CopyIcon(hIcon);
205
206 windowCount++;
207
208 // If we got to the max number of windows,
209 // we won't be able to add any more
210 if(windowCount >= MAX_WINDOWS)
211 return FALSE;
212
213 return TRUE;
214 }
215
216 // Function mostly compatible with the normal EnumWindows,
217 // except it lists in Z-Order and it doesn't ensure consistency
218 // if a window is removed while enumerating
219 void EnumWindowsZOrder(WNDENUMPROC callback, LPARAM lParam)
220 {
221 HWND next = GetTopWindow(NULL);
222 while (next != NULL)
223 {
224 if(!callback(next, lParam))
225 break;
226 next = GetWindow(next, GW_HWNDNEXT);
227 }
228 }
229
230 void ProcessMouseMessage(UINT message, LPARAM lParam)
231 {
232 int xPos = LOWORD(lParam);
233 int yPos = HIWORD(lParam);
234
235 int xIndex = (xPos - DIALOG_MARGIN) / CX_ITEM_SPACE;
236 int yIndex = (yPos - DIALOG_MARGIN) / CY_ITEM_SPACE;
237
238 if (xIndex < 0 || nCols <= xIndex ||
239 yIndex < 0 || nRows <= yIndex)
240 {
241 return;
242 }
243
244 selectedWindow = (yIndex*nCols) + xIndex;
245 if (message == WM_MOUSEMOVE)
246 {
247 InvalidateRect(switchdialog, NULL, TRUE);
248 //RedrawWindow(switchdialog, NULL, NULL, 0);
249 }
250 else
251 {
252 selectedWindow = (yIndex*nCols) + xIndex;
253 CompleteSwitch(TRUE);
254 }
255 }
256
257 void OnPaint(HWND hWnd)
258 {
259 HDC dialogDC;
260 PAINTSTRUCT paint;
261 RECT cRC, textRC;
262 int i, xPos, yPos, CharCount;
263 HFONT dcFont;
264 HICON hIcon;
265 HPEN hPen;
266 COLORREF Color;
267
268 // check
269 if (nCols == 0 || nItems == 0)
270 return;
271
272 // begin painting
273 dialogDC = BeginPaint(hWnd, &paint);
274 if (dialogDC == NULL)
275 return;
276
277 // fill the client area
278 GetClientRect(hWnd, &cRC);
279 FillRect(dialogDC, &cRC, (HBRUSH)(COLOR_3DFACE + 1));
280
281 // if the selection index exceeded the display items, then
282 // do display item shifting
283 if (selectedWindow >= nItems)
284 nShift = selectedWindow - nItems + 1;
285 else
286 nShift = 0;
287
288 for (i = 0; i < nItems; ++i)
289 {
290 // get the icon to display
291 hIcon = iconList[i + nShift];
292
293 // calculate the position where we start drawing
294 xPos = DIALOG_MARGIN + CX_ITEM_SPACE * (i % nCols) + ITEM_MARGIN;
295 yPos = DIALOG_MARGIN + CY_ITEM_SPACE * (i / nCols) + ITEM_MARGIN;
296
297 // centering
298 if (nItems < CoolSwitchColumns)
299 {
300 xPos += (itemsW - nItems * CX_ITEM_SPACE) / 2;
301 }
302
303 // if this position is selected,
304 if (selectedWindow == i + nShift)
305 {
306 // create a solid pen
307 Color = GetSysColor(COLOR_HIGHLIGHT);
308 hPen = CreatePen(PS_SOLID, 1, Color);
309
310 // draw a rectangle with using the pen
311 SelectObject(dialogDC, hPen);
312 SelectObject(dialogDC, GetStockObject(NULL_BRUSH));
313 Rectangle(dialogDC, xPos, yPos, xPos + CX_ITEM, yPos + CY_ITEM);
314 Rectangle(dialogDC, xPos + 1, yPos + 1,
315 xPos + CX_ITEM - 1, yPos + CY_ITEM - 1);
316
317 // delete the pen
318 DeleteObject(hPen);
319 }
320
321 // draw icon
322 DrawIconEx(dialogDC, xPos + ICON_MARGIN, yPos + ICON_MARGIN,
323 hIcon, CX_ICON, CY_ICON, 0, NULL, DI_NORMAL);
324 }
325
326 // set the text rectangle
327 SetRect(&textRC, DIALOG_MARGIN, DIALOG_MARGIN + itemsH,
328 totalW - DIALOG_MARGIN, totalH - DIALOG_MARGIN);
329
330 // draw the sunken button around text
331 DrawFrameControl(dialogDC, &textRC, DFC_BUTTON,
332 DFCS_BUTTONPUSH | DFCS_PUSHED);
333
334 // get text
335 CharCount = GetWindowTextW(windowList[selectedWindow], windowText,
336 _countof(windowText));
337
338 // draw text
339 dcFont = SelectObject(dialogDC, dialogFont);
340 SetTextColor(dialogDC, GetSysColor(COLOR_BTNTEXT));
341 SetBkMode(dialogDC, TRANSPARENT);
342 DrawTextW(dialogDC, windowText, CharCount, &textRC,
343 DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE);
344 SelectObject(dialogDC, dcFont);
345
346 // end painting
347 EndPaint(hWnd, &paint);
348 }
349
350 DWORD CreateSwitcherWindow(HINSTANCE hInstance)
351 {
352 switchdialog = CreateWindowExW( WS_EX_TOPMOST|WS_EX_DLGMODALFRAME|WS_EX_TOOLWINDOW,
353 WC_SWITCH,
354 L"",
355 WS_POPUP|WS_BORDER|WS_DISABLED,
356 CW_USEDEFAULT,
357 CW_USEDEFAULT,
358 400, 150,
359 NULL, NULL,
360 hInstance, NULL);
361 if (!switchdialog)
362 {
363 TRACE("[ATbot] Task Switcher Window failed to create.\n");
364 return 0;
365 }
366
367 isOpen = FALSE;
368 return 1;
369 }
370
371 DWORD GetDialogFont(VOID)
372 {
373 HDC tDC;
374 TEXTMETRIC tm;
375
376 dialogFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
377
378 tDC = GetDC(0);
379 GetTextMetrics(tDC, &tm);
380 fontHeight = tm.tmHeight;
381 ReleaseDC(0, tDC);
382
383 return 1;
384 }
385
386 void PrepareWindow(VOID)
387 {
388 nItems = windowCount;
389
390 nCols = CoolSwitchColumns;
391 nRows = (nItems + CoolSwitchColumns - 1) / CoolSwitchColumns;
392 if (nRows > CoolSwitchRows)
393 {
394 nRows = CoolSwitchRows;
395 nItems = nRows * nCols;
396 }
397
398 itemsW = nCols * CX_ITEM_SPACE;
399 itemsH = nRows * CY_ITEM_SPACE;
400
401 totalW = itemsW + 2 * DIALOG_MARGIN;
402 totalH = itemsH + 2 * DIALOG_MARGIN;
403 totalH += fontHeight + 2 * CY_TEXT_MARGIN;
404
405 ResizeAndCenter(switchdialog, totalW, totalH);
406 }
407
408 BOOL ProcessHotKey(VOID)
409 {
410 if (!isOpen)
411 {
412 windowCount=0;
413 EnumWindowsZOrder(EnumerateCallback, 0);
414
415 if (windowCount < 2)
416 return FALSE;
417
418 selectedWindow = 1;
419
420 TRACE("[ATbot] HotKey Received. Opening window.\n");
421 ShowWindow(switchdialog, SW_SHOWNORMAL);
422 MakeWindowActive(switchdialog);
423 isOpen = TRUE;
424 }
425 else
426 {
427 TRACE("[ATbot] HotKey Received Rotating.\n");
428 selectedWindow = (selectedWindow + 1)%windowCount;
429 InvalidateRect(switchdialog, NULL, TRUE);
430 }
431 return TRUE;
432 }
433
434 void RotateTasks(BOOL bShift)
435 {
436 HWND hwndFirst, hwndLast;
437 DWORD Size;
438
439 if (windowCount < 2 || !Esc)
440 return;
441
442 hwndFirst = windowList[0];
443 hwndLast = windowList[windowCount - 1];
444
445 if (bShift)
446 {
447 SetWindowPos(hwndLast, HWND_TOP, 0, 0, 0, 0,
448 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE |
449 SWP_NOOWNERZORDER | SWP_NOREPOSITION);
450
451 MakeWindowActive(hwndLast);
452
453 Size = (windowCount - 1) * sizeof(HWND);
454 MoveMemory(&windowList[1], &windowList[0], Size);
455 windowList[0] = hwndLast;
456 }
457 else
458 {
459 SetWindowPos(hwndFirst, hwndLast, 0, 0, 0, 0,
460 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE |
461 SWP_NOOWNERZORDER | SWP_NOREPOSITION);
462
463 MakeWindowActive(windowList[1]);
464
465 Size = (windowCount - 1) * sizeof(HWND);
466 MoveMemory(&windowList[0], &windowList[1], Size);
467 windowList[windowCount - 1] = hwndFirst;
468 }
469 }
470
471 VOID
472 DestroyAppWindows(VOID)
473 {
474 // for every item of the icon list:
475 INT i;
476 for (i = 0; i < windowCount; ++i)
477 {
478 // destroy the icon
479 DestroyIcon(iconList[i]);
480 iconList[i] = NULL;
481 }
482 }
483
484 LRESULT WINAPI DoAppSwitch( WPARAM wParam, LPARAM lParam )
485 {
486 HWND hwndActive;
487 MSG msg;
488
489 // FIXME: Is loading timing OK?
490 LoadCoolSwitchSettings();
491
492 if (!CoolSwitch)
493 return 0;
494
495 // Already in the loop.
496 if (switchdialog || Esc) return 0;
497
498 hwndActive = GetActiveWindow();
499 // Nothing is active so exit.
500 if (!hwndActive) return 0;
501
502 if (lParam == VK_ESCAPE)
503 {
504 Esc = TRUE;
505
506 windowCount = 0;
507 EnumWindowsZOrder(EnumerateCallback, 0);
508
509 if (windowCount < 2)
510 return 0;
511
512 RotateTasks(GetAsyncKeyState(VK_SHIFT) < 0);
513
514 hwndActive = GetActiveWindow();
515
516 if (hwndActive == NULL)
517 {
518 Esc = FALSE;
519 return 0;
520 }
521 }
522
523 // Capture current active window.
524 SetCapture( hwndActive );
525
526 switch (lParam)
527 {
528 case VK_TAB:
529 if( !CreateSwitcherWindow(User32Instance) ) goto Exit;
530 if( !GetDialogFont() ) goto Exit;
531 if( !ProcessHotKey() ) goto Exit;
532 break;
533
534 case VK_ESCAPE:
535 break;
536
537 default:
538 goto Exit;
539 }
540 // Main message loop:
541 while (1)
542 {
543 for (;;)
544 {
545 if (PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE ))
546 {
547 if (!CallMsgFilterW( &msg, MSGF_NEXTWINDOW )) break;
548 /* remove the message from the queue */
549 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
550 }
551 else
552 WaitMessage();
553 }
554
555 switch (msg.message)
556 {
557 case WM_KEYUP:
558 {
559 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
560 if (msg.wParam == VK_MENU)
561 {
562 CompleteSwitch(TRUE);
563 }
564 else if (msg.wParam == VK_RETURN)
565 {
566 CompleteSwitch(TRUE);
567 }
568 else if (msg.wParam == VK_ESCAPE)
569 {
570 TRACE("DoAppSwitch VK_ESCAPE 2\n");
571 CompleteSwitch(FALSE);
572 }
573 goto Exit; //break;
574 }
575
576 case WM_SYSKEYDOWN:
577 {
578 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
579 if (HIWORD(msg.lParam) & KF_ALTDOWN)
580 {
581 if ( msg.wParam == VK_TAB )
582 {
583 if (Esc) break;
584 if (GetKeyState(VK_SHIFT) < 0)
585 {
586 selectedWindow = selectedWindow - 1;
587 if (selectedWindow < 0)
588 selectedWindow = windowCount - 1;
589 }
590 else
591 {
592 selectedWindow = (selectedWindow + 1)%windowCount;
593 }
594 InvalidateRect(switchdialog, NULL, TRUE);
595 }
596 else if ( msg.wParam == VK_ESCAPE )
597 {
598 if (!Esc) break;
599 RotateTasks(GetKeyState(VK_SHIFT) < 0);
600 }
601 }
602 break;
603 }
604
605 case WM_LBUTTONUP:
606 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
607 ProcessMouseMessage(msg.message, msg.lParam);
608 goto Exit;
609
610 default:
611 if (PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ))
612 {
613 TranslateMessage(&msg);
614 DispatchMessageW(&msg);
615 }
616 break;
617 }
618 }
619 Exit:
620 ReleaseCapture();
621 if (switchdialog) DestroyWindow(switchdialog);
622 if (Esc) DestroyAppWindows();
623 switchdialog = NULL;
624 selectedWindow = 0;
625 windowCount = 0;
626 Esc = FALSE;
627 return 0;
628 }
629
630 //
631 // Switch System Class Window Proc.
632 //
633 LRESULT WINAPI SwitchWndProc_common(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL unicode )
634 {
635 PWND pWnd;
636 PALTTABINFO ati;
637 pWnd = ValidateHwnd(hWnd);
638 if (pWnd)
639 {
640 if (!pWnd->fnid)
641 {
642 NtUserSetWindowFNID(hWnd, FNID_SWITCH);
643 }
644 }
645
646 switch (uMsg)
647 {
648 case WM_NCCREATE:
649 if (!(ati = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*ati))))
650 return 0;
651 SetWindowLongPtrW( hWnd, 0, (LONG_PTR)ati );
652 return TRUE;
653
654 case WM_SHOWWINDOW:
655 if (wParam)
656 {
657 PrepareWindow();
658 ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
659 ati->cbSize = sizeof(ALTTABINFO);
660 ati->cItems = nItems;
661 ati->cColumns = nCols;
662 ati->cRows = nRows;
663 if (nCols)
664 {
665 ati->iColFocus = (selectedWindow - nShift) % nCols;
666 ati->iRowFocus = (selectedWindow - nShift) / nCols;
667 }
668 else
669 {
670 ati->iColFocus = 0;
671 ati->iRowFocus = 0;
672 }
673 ati->cxItem = CX_ITEM_SPACE;
674 ati->cyItem = CY_ITEM_SPACE;
675 ati->ptStart = ptStart;
676 }
677 return 0;
678
679 case WM_MOUSEMOVE:
680 ProcessMouseMessage(uMsg, lParam);
681 return 0;
682
683 case WM_ACTIVATE:
684 if (wParam == WA_INACTIVE)
685 {
686 CompleteSwitch(FALSE);
687 }
688 return 0;
689
690 case WM_PAINT:
691 OnPaint(hWnd);
692 return 0;
693
694 case WM_DESTROY:
695 isOpen = FALSE;
696 ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
697 HeapFree( GetProcessHeap(), 0, ati );
698 SetWindowLongPtrW( hWnd, 0, 0 );
699 DestroyAppWindows();
700 NtUserSetWindowFNID(hWnd, FNID_DESTROY);
701 return 0;
702 }
703 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
704 }
705
706 LRESULT WINAPI SwitchWndProcA(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
707 {
708 return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, FALSE);
709 }
710
711 LRESULT WINAPI SwitchWndProcW(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
712 {
713 return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, TRUE);
714 }