* Sync up to trunk head (r65147).
[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: dll/win32/user32/controls/appswitch.c
5 * PURPOSE: app switching functionality
6 * PROGRAMMERS: Johannes Anderwald (johannes.anderwald@reactos.org)
7 * David Quintana (gigaherz@gmail.com)
8 */
9
10 #include <user32.h>
11
12 #include <wine/debug.h>
13 WINE_DEFAULT_DEBUG_CHANNEL(user32);
14
15 // limit the number of windows shown in the alt-tab window
16 // 120 windows results in (12*40) by (10*40) pixels worth of icons.
17 #define MAX_WINDOWS 120
18
19 // Global variables
20 HWND switchdialog = NULL;
21 HFONT dialogFont;
22 int selectedWindow = 0;
23 BOOL isOpen = FALSE;
24
25 int fontHeight=0;
26
27 WCHAR windowText[1024];
28
29 HWND windowList[MAX_WINDOWS];
30 HICON iconList[MAX_WINDOWS];
31 int windowCount = 0;
32
33 int cxBorder, cyBorder;
34 int nItems, nCols, nRows;
35 int itemsW, itemsH;
36 int totalW, totalH;
37 int xOffset, yOffset;
38 POINT pt;
39
40 void ResizeAndCenter(HWND hwnd, int width, int height)
41 {
42 int screenwidth = GetSystemMetrics(SM_CXSCREEN);
43 int screenheight = GetSystemMetrics(SM_CYSCREEN);
44
45 pt.x = (screenwidth - width) / 2;
46 pt.y = (screenheight - height) / 2;
47
48 MoveWindow(hwnd, pt.x, pt.y, width, height, FALSE);
49 }
50
51 void MakeWindowActive(HWND hwnd)
52 {
53 WINDOWPLACEMENT wpl;
54
55 wpl.length = sizeof(WINDOWPLACEMENT);
56 GetWindowPlacement(hwnd, &wpl);
57
58 TRACE("GetWindowPlacement wpl.showCmd %d\n",wpl.showCmd);
59 if (wpl.showCmd == SW_SHOWMINIMIZED)
60 ShowWindowAsync(hwnd, SW_RESTORE);
61
62 BringWindowToTop(hwnd); // same as: SetWindowPos(hwnd,HWND_TOP,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE); ?
63 SetForegroundWindow(hwnd);
64 }
65
66 void CompleteSwitch(BOOL doSwitch)
67 {
68 if (!isOpen)
69 return;
70
71 isOpen = FALSE;
72
73 TRACE("[ATbot] CompleteSwitch Hiding Window.\n");
74 ShowWindow(switchdialog, SW_HIDE);
75
76 if(doSwitch)
77 {
78 if(selectedWindow >= windowCount)
79 return;
80
81 // FIXME: workaround because reactos fails to activate the previous window correctly.
82 //if(selectedWindow != 0)
83 {
84 HWND hwnd = windowList[selectedWindow];
85
86 GetWindowTextW(hwnd, windowText, _countof(windowText));
87
88 TRACE("[ATbot] CompleteSwitch Switching to 0x%08x (%ls)\n", hwnd, windowText);
89
90 MakeWindowActive(hwnd);
91 }
92 }
93
94 windowCount = 0;
95 }
96
97 BOOL CALLBACK EnumerateCallback(HWND window, LPARAM lParam)
98 {
99 HICON hIcon;
100
101 UNREFERENCED_PARAMETER(lParam);
102
103 if (!IsWindowVisible(window))
104 return TRUE;
105
106 GetClassNameW(window, windowText, _countof(windowText));
107 if ((wcscmp(L"Shell_TrayWnd", windowText)==0) ||
108 (wcscmp(L"Progman", windowText)==0) )
109 return TRUE;
110
111 // First try to get the big icon assigned to the window
112 hIcon = (HICON)SendMessageW(window, WM_GETICON, ICON_BIG, 0);
113 if (!hIcon)
114 {
115 // If no icon is assigned, try to get the icon assigned to the windows' class
116 hIcon = (HICON)GetClassLongPtrW(window, GCL_HICON);
117 if (!hIcon)
118 {
119 // If we still don't have an icon, see if we can do with the small icon,
120 // or a default application icon
121 hIcon = (HICON)SendMessageW(window, WM_GETICON, ICON_SMALL2, 0);
122 if (!hIcon)
123 {
124 // If all fails, give up and continue with the next window
125 return TRUE;
126 }
127 }
128 }
129
130 windowList[windowCount] = window;
131 iconList[windowCount] = CopyIcon(hIcon);
132
133 windowCount++;
134
135 // If we got to the max number of windows,
136 // we won't be able to add any more
137 if(windowCount == MAX_WINDOWS)
138 return FALSE;
139
140 return TRUE;
141 }
142
143 // Function mostly compatible with the normal EnumWindows,
144 // except it lists in Z-Order and it doesn't ensure consistency
145 // if a window is removed while enumerating
146 void EnumWindowsZOrder(WNDENUMPROC callback, LPARAM lParam)
147 {
148 HWND next = GetTopWindow(NULL);
149 while (next != NULL)
150 {
151 if(!callback(next, lParam))
152 break;
153 next = GetWindow(next, GW_HWNDNEXT);
154 }
155 }
156
157 void ProcessMouseMessage(UINT message, LPARAM lParam)
158 {
159 int xPos = LOWORD(lParam);
160 int yPos = HIWORD(lParam);
161
162 int xIndex = (xPos - xOffset)/40;
163 int xOff = (xPos - xOffset)%40;
164
165 int yIndex = (yPos - yOffset)/40;
166 int yOff = (yPos - yOffset)%40;
167
168 if(xOff > 32 || xIndex > nItems)
169 return;
170
171 if(yOff > 32 || yIndex > nRows)
172 return;
173
174 selectedWindow = (yIndex*nCols) + xIndex;
175 if (message == WM_MOUSEMOVE)
176 {
177 InvalidateRect(switchdialog, NULL, TRUE);
178 //RedrawWindow(switchdialog, NULL, NULL, 0);
179 }
180 else
181 {
182 selectedWindow = (yIndex*nCols) + xIndex;
183 CompleteSwitch(TRUE);
184 }
185 }
186
187 void OnPaint(HWND hWnd)
188 {
189 HDC dialogDC;
190 PAINTSTRUCT paint;
191 RECT cRC, textRC;
192 int i;
193 HBRUSH hBrush;
194 HPEN hPen;
195 HFONT dcFont;
196 COLORREF cr;
197 int nch = GetWindowTextW(windowList[selectedWindow], windowText, _countof(windowText));
198
199 dialogDC = BeginPaint(hWnd, &paint);
200 {
201 GetClientRect(hWnd, &cRC);
202 FillRect(dialogDC, &cRC, GetSysColorBrush(COLOR_MENU));
203
204 for(i=0; i< windowCount; i++)
205 {
206 HICON hIcon = iconList[i];
207
208 int xpos = xOffset + 40 * (i % nCols);
209 int ypos = yOffset + 40 * (i / nCols);
210
211 if (selectedWindow == i)
212 {
213 hBrush = GetSysColorBrush(COLOR_HIGHLIGHT);
214 }
215 else
216 {
217 hBrush = GetSysColorBrush(COLOR_MENU);
218 }
219 #if TRUE
220 cr = GetSysColor(COLOR_BTNTEXT); // doesn't look right! >_<
221 hPen = CreatePen(PS_DOT, 1, cr);
222 SelectObject(dialogDC, hPen);
223 SelectObject(dialogDC, hBrush);
224 Rectangle(dialogDC, xpos-2, ypos-2, xpos+32+2, ypos+32+2);
225 DeleteObject(hPen);
226 // Must NOT destroy the system brush!
227 #else
228 RECT rc = { xpos-2, ypos-2, xpos+32+2, ypos+32+2 };
229 FillRect(dialogDC, &rc, hBrush);
230 #endif
231 DrawIcon(dialogDC, xpos, ypos, hIcon);
232 }
233
234 dcFont = SelectObject(dialogDC, dialogFont);
235 SetTextColor(dialogDC, GetSysColor(COLOR_BTNTEXT));
236 SetBkColor(dialogDC, GetSysColor(COLOR_BTNFACE));
237
238 textRC.top = itemsH;
239 textRC.left = 8;
240 textRC.right = totalW - 8;
241 textRC.bottom = totalH - 8;
242 DrawTextW(dialogDC, windowText, nch, &textRC, DT_CENTER|DT_END_ELLIPSIS);
243 SelectObject(dialogDC, dcFont);
244 }
245 EndPaint(hWnd, &paint);
246 }
247
248 DWORD CreateSwitcherWindow(HINSTANCE hInstance)
249 {
250 switchdialog = CreateWindowExW( WS_EX_TOPMOST|WS_EX_DLGMODALFRAME|WS_EX_TOOLWINDOW,
251 WC_SWITCH,
252 L"",
253 WS_POPUP|WS_BORDER|WS_DISABLED,
254 CW_USEDEFAULT,
255 CW_USEDEFAULT,
256 400, 150,
257 NULL, NULL,
258 hInstance, NULL);
259 if (!switchdialog)
260 {
261 TRACE("[ATbot] Task Switcher Window failed to create.\n");
262 return 0;
263 }
264
265 isOpen = FALSE;
266 return 1;
267 }
268
269 DWORD GetDialogFont()
270 {
271 HDC tDC;
272 TEXTMETRIC tm;
273
274 dialogFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
275
276 tDC = GetDC(0);
277 GetTextMetrics(tDC, &tm);
278 fontHeight = tm.tmHeight;
279 ReleaseDC(0, tDC);
280
281 return 1;
282 }
283
284 void PrepareWindow()
285 {
286 cxBorder = GetSystemMetrics(SM_CXBORDER);
287 cyBorder = GetSystemMetrics(SM_CYBORDER);
288
289 nItems = windowCount;
290 nCols = min(max(nItems,8),12);
291 nRows = (nItems+nCols-1)/nCols;
292
293 itemsW = nCols*32 + (nCols+1)*8;
294 itemsH = nRows*32 + (nRows+1)*8;
295
296 totalW = itemsW + 2*cxBorder + 4;
297 totalH = itemsH + 2*cyBorder + fontHeight + 8; // give extra pixels for the window title
298
299 xOffset = 8;
300 yOffset = 8;
301
302 if (nItems < nCols)
303 {
304 int w2 = nItems*32 + (nItems-1)*8;
305 xOffset = (itemsW-w2)/2;
306 }
307 ResizeAndCenter(switchdialog, totalW, totalH);
308 }
309
310 void ProcessHotKey()
311 {
312 if (!isOpen)
313 {
314 windowCount=0;
315 EnumWindowsZOrder(EnumerateCallback, 0);
316
317 if (windowCount < 2)
318 return;
319
320 selectedWindow = 1;
321
322 TRACE("[ATbot] HotKey Received. Opening window.\n");
323 ShowWindow(switchdialog, SW_SHOWNORMAL);
324 MakeWindowActive(switchdialog);
325 isOpen = TRUE;
326 }
327 else
328 {
329 TRACE("[ATbot] HotKey Received Rotating.\n");
330 selectedWindow = (selectedWindow + 1)%windowCount;
331 InvalidateRect(switchdialog, NULL, TRUE);
332 }
333 }
334
335 LRESULT WINAPI DoAppSwitch( WPARAM wParam, LPARAM lParam )
336 {
337 HWND hwnd, hwndActive;
338 MSG msg;
339 BOOL Esc = FALSE;
340 INT Count = 0;
341 WCHAR Text[1024];
342
343 // Already in the loop.
344 if (switchdialog) return 0;
345
346 hwndActive = GetActiveWindow();
347 // Nothing is active so exit.
348 if (!hwndActive) return 0;
349 // Capture current active window.
350 SetCapture( hwndActive );
351
352 switch (lParam)
353 {
354 case VK_TAB:
355 if( !CreateSwitcherWindow(User32Instance) ) goto Exit;
356 if( !GetDialogFont() ) goto Exit;
357 ProcessHotKey();
358 break;
359
360 case VK_ESCAPE:
361 windowCount = 0;
362 Count = 0;
363 EnumWindowsZOrder(EnumerateCallback, 0);
364 if (windowCount < 2) goto Exit;
365 if (wParam == SC_NEXTWINDOW)
366 Count = 1;
367 else
368 {
369 if (windowCount == 2)
370 Count = 0;
371 else
372 Count = windowCount - 1;
373 }
374 TRACE("DoAppSwitch VK_ESCAPE 1 Count %d windowCount %d\n",Count,windowCount);
375 hwnd = windowList[Count];
376 GetWindowTextW(hwnd, Text, _countof(Text));
377 TRACE("[ATbot] Switching to 0x%08x (%ls)\n", hwnd, Text);
378 MakeWindowActive(hwnd);
379 Esc = TRUE;
380 break;
381
382 default:
383 goto Exit;
384 }
385 // Main message loop:
386 while (1)
387 {
388 for (;;)
389 {
390 if (PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE ))
391 {
392 if (!CallMsgFilterW( &msg, MSGF_NEXTWINDOW )) break;
393 /* remove the message from the queue */
394 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
395 }
396 else
397 WaitMessage();
398 }
399
400 switch (msg.message)
401 {
402 case WM_KEYUP:
403 {
404 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
405 if (msg.wParam == VK_MENU)
406 {
407 CompleteSwitch(TRUE);
408 }
409 else if (msg.wParam == VK_RETURN)
410 {
411 CompleteSwitch(TRUE);
412 }
413 else if (msg.wParam == VK_ESCAPE)
414 {
415 TRACE("DoAppSwitch VK_ESCAPE 2\n");
416 CompleteSwitch(FALSE);
417 }
418 goto Exit; //break;
419 }
420
421 case WM_SYSKEYDOWN:
422 {
423 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
424 if (HIWORD(msg.lParam) & KF_ALTDOWN)
425 {
426 INT Shift;
427 if ( msg.wParam == VK_TAB )
428 {
429 if (Esc) break;
430 Shift = GetKeyState(VK_SHIFT) & 0x8000 ? SC_PREVWINDOW : SC_NEXTWINDOW;
431 if (Shift == SC_NEXTWINDOW)
432 {
433 selectedWindow = (selectedWindow + 1)%windowCount;
434 }
435 else
436 {
437 selectedWindow = selectedWindow - 1;
438 if (selectedWindow < 0)
439 selectedWindow = windowCount - 1;
440 }
441 InvalidateRect(switchdialog, NULL, TRUE);
442 }
443 else if ( msg.wParam == VK_ESCAPE )
444 {
445 if (!Esc) break;
446 if (windowCount < 2)
447 goto Exit;
448 if (wParam == SC_NEXTWINDOW)
449 {
450 Count = (Count + 1)%windowCount;
451 }
452 else
453 {
454 Count--;
455 if (Count < 0)
456 Count = windowCount - 1;
457 }
458 hwnd = windowList[Count];
459 GetWindowTextW(hwnd, Text, _countof(Text));
460 MakeWindowActive(hwnd);
461 }
462 }
463 break;
464 }
465
466 case WM_LBUTTONUP:
467 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
468 ProcessMouseMessage(msg.message, msg.lParam);
469 goto Exit;
470
471 default:
472 if (PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ))
473 {
474 TranslateMessage(&msg);
475 DispatchMessageW(&msg);
476 }
477 break;
478 }
479 }
480 Exit:
481 ReleaseCapture();
482 if (switchdialog) DestroyWindow(switchdialog);
483 switchdialog = NULL;
484 selectedWindow = 0;
485 windowCount = 0;
486 return 0;
487 }
488
489 VOID
490 DestroyAppWindows()
491 {
492 INT i;
493 for (i=0; i< windowCount; i++)
494 {
495 HICON hIcon = iconList[i];
496 DestroyIcon(hIcon);
497 }
498 }
499
500 //
501 // Switch System Class Window Proc.
502 //
503 LRESULT WINAPI SwitchWndProc_common(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL unicode )
504 {
505 PWND pWnd;
506 PALTTABINFO ati;
507 pWnd = ValidateHwnd(hWnd);
508 if (pWnd)
509 {
510 if (!pWnd->fnid)
511 {
512 NtUserSetWindowFNID(hWnd, FNID_SWITCH);
513 }
514 }
515
516 switch (uMsg)
517 {
518 case WM_NCCREATE:
519 if (!(ati = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*ati))))
520 return 0;
521 SetWindowLongPtrW( hWnd, 0, (LONG_PTR)ati );
522 return TRUE;
523
524 case WM_SHOWWINDOW:
525 if (wParam == TRUE)
526 {
527 PrepareWindow();
528 ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
529 ati->cItems = nItems;
530 ati->cxItem = ati->cyItem = 43;
531 ati->cRows = nRows;
532 ati->cColumns = nCols;
533 }
534 return 0;
535
536 case WM_MOUSEMOVE:
537 ProcessMouseMessage(uMsg, lParam);
538 return 0;
539
540 case WM_ACTIVATE:
541 if (wParam == WA_INACTIVE)
542 {
543 CompleteSwitch(FALSE);
544 }
545 return 0;
546
547 case WM_PAINT:
548 OnPaint(hWnd);
549 return 0;
550
551 case WM_DESTROY:
552 isOpen = FALSE;
553 ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
554 HeapFree( GetProcessHeap(), 0, ati );
555 SetWindowLongPtrW( hWnd, 0, 0 );
556 DestroyAppWindows();
557 NtUserSetWindowFNID(hWnd, FNID_DESTROY);
558 return 0;
559 }
560 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
561 }
562
563 LRESULT WINAPI SwitchWndProcA(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
564 {
565 return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, FALSE);
566 }
567
568 LRESULT WINAPI SwitchWndProcW(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
569 {
570 return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, TRUE);
571 }