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