* Update Johannes's email address.
[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: 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;
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, 1023);
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,4095);
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, 1023);
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;
338 MSG msg;
339 BOOL Esc = FALSE;
340 INT Count = 0;
341 WCHAR Text[1024];
342
343 switchdialog = NULL;
344
345 switch (lParam)
346 {
347 case VK_TAB:
348 if( !CreateSwitcherWindow(User32Instance) ) return 0;
349 if( !GetDialogFont() ) return 0;
350 ProcessHotKey();
351 break;
352
353 case VK_ESCAPE:
354 windowCount = 0;
355 Count = 0;
356 EnumWindowsZOrder(EnumerateCallback, 0);
357 if (windowCount < 2) return 0;
358 if (wParam == SC_NEXTWINDOW)
359 Count = 1;
360 else
361 {
362 if (windowCount == 2)
363 Count = 0;
364 else
365 Count = windowCount - 1;
366 }
367 TRACE("DoAppSwitch VK_ESCAPE 1 Count %d windowCount %d\n",Count,windowCount);
368 hwnd = windowList[Count];
369 GetWindowTextW(hwnd, Text, 1023);
370 TRACE("[ATbot] Switching to 0x%08x (%ls)\n", hwnd, Text);
371 MakeWindowActive(hwnd);
372 Esc = TRUE;
373 break;
374
375 default:
376 return 0;
377 }
378 // Main message loop:
379 while (1)
380 {
381 for (;;)
382 {
383 if (PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE ))
384 {
385 if (!CallMsgFilterW( &msg, MSGF_NEXTWINDOW )) break;
386 /* remove the message from the queue */
387 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
388 }
389 else
390 WaitMessage();
391 }
392
393 switch (msg.message)
394 {
395 case WM_KEYUP:
396 {
397 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
398 if (msg.wParam == VK_MENU)
399 {
400 CompleteSwitch(TRUE);
401 }
402 else if (msg.wParam == VK_RETURN)
403 {
404 CompleteSwitch(TRUE);
405 }
406 else if (msg.wParam == VK_ESCAPE)
407 {
408 TRACE("DoAppSwitch VK_ESCAPE 2\n");
409 CompleteSwitch(FALSE);
410 }
411 goto Exit; //break;
412 }
413
414 case WM_SYSKEYDOWN:
415 {
416 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
417 if (HIWORD(msg.lParam) & KF_ALTDOWN)
418 {
419 INT Shift;
420 if ( msg.wParam == VK_TAB )
421 {
422 if (Esc) break;
423 Shift = GetKeyState(VK_SHIFT) & 0x8000 ? SC_PREVWINDOW : SC_NEXTWINDOW;
424 if (Shift == SC_NEXTWINDOW)
425 {
426 selectedWindow = (selectedWindow + 1)%windowCount;
427 }
428 else
429 {
430 selectedWindow = selectedWindow - 1;
431 if (selectedWindow < 0)
432 selectedWindow = windowCount - 1;
433 }
434 InvalidateRect(switchdialog, NULL, TRUE);
435 }
436 else if ( msg.wParam == VK_ESCAPE )
437 {
438 if (!Esc) break;
439 if (windowCount < 2)
440 goto Exit;
441 if (wParam == SC_NEXTWINDOW)
442 {
443 Count = (Count + 1)%windowCount;
444 }
445 else
446 {
447 Count--;
448 if (Count < 0)
449 Count = windowCount - 1;
450 }
451 hwnd = windowList[Count];
452 GetWindowTextW(hwnd, Text, 1023);
453 MakeWindowActive(hwnd);
454 }
455 }
456 break;
457 }
458
459 case WM_LBUTTONUP:
460 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
461 ProcessMouseMessage(msg.message, msg.lParam);
462 goto Exit;
463
464 default:
465 if (PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ))
466 {
467 TranslateMessage(&msg);
468 DispatchMessageW(&msg);
469 }
470 break;
471 }
472 }
473 Exit:
474 if (switchdialog) DestroyWindow(switchdialog);
475 switchdialog = NULL;
476 selectedWindow = 0;
477 windowCount = 0;
478 return 0;
479 }
480
481 VOID
482 DestroyAppWindows()
483 {
484 INT i;
485 for (i=0; i< windowCount; i++)
486 {
487 HICON hIcon = iconList[i];
488 DestroyIcon(hIcon);
489 }
490 }
491
492 //
493 // Switch System Class Window Proc.
494 //
495 LRESULT WINAPI SwitchWndProc_common(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL unicode )
496 {
497 PWND pWnd;
498 PALTTABINFO ati;
499 pWnd = ValidateHwnd(hWnd);
500 if (pWnd)
501 {
502 if (!pWnd->fnid)
503 {
504 NtUserSetWindowFNID(hWnd, FNID_SWITCH);
505 }
506 }
507
508 switch (uMsg)
509 {
510 case WM_NCCREATE:
511 if (!(ati = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*ati))))
512 return 0;
513 SetWindowLongPtrW( hWnd, 0, (LONG_PTR)ati );
514 return TRUE;
515
516 case WM_SHOWWINDOW:
517 if (wParam == TRUE)
518 {
519 PrepareWindow();
520 ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
521 ati->cItems = nItems;
522 ati->cxItem = ati->cyItem = 43;
523 ati->cRows = nRows;
524 ati->cColumns = nCols;
525 }
526 return 0;
527
528 case WM_MOUSEMOVE:
529 ProcessMouseMessage(uMsg, lParam);
530 return 0;
531
532 case WM_ACTIVATE:
533 if (wParam == WA_INACTIVE)
534 {
535 CompleteSwitch(FALSE);
536 }
537 return 0;
538
539 case WM_PAINT:
540 OnPaint(hWnd);
541 return 0;
542
543 case WM_DESTROY:
544 isOpen = FALSE;
545 ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
546 HeapFree( GetProcessHeap(), 0, ati );
547 SetWindowLongPtrW( hWnd, 0, 0 );
548 DestroyAppWindows();
549 NtUserSetWindowFNID(hWnd, FNID_DESTROY);
550 return 0;
551 }
552 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
553 }
554
555 LRESULT WINAPI SwitchWndProcA(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
556 {
557 return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, FALSE);
558 }
559
560 LRESULT WINAPI SwitchWndProcW(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
561 {
562 return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, TRUE);
563 }