[PRINTING]
[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 */
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 // using windows logo icon as default
125 hIcon = gpsi->hIconWindows;
126 if (!hIcon)
127 {
128 //if all attempts to get icon fails go to the next window
129 return TRUE;
130 }
131 }
132 }
133 }
134
135 windowList[windowCount] = window;
136 iconList[windowCount] = CopyIcon(hIcon);
137
138 windowCount++;
139
140 // If we got to the max number of windows,
141 // we won't be able to add any more
142 if(windowCount == MAX_WINDOWS)
143 return FALSE;
144
145 return TRUE;
146 }
147
148 // Function mostly compatible with the normal EnumWindows,
149 // except it lists in Z-Order and it doesn't ensure consistency
150 // if a window is removed while enumerating
151 void EnumWindowsZOrder(WNDENUMPROC callback, LPARAM lParam)
152 {
153 HWND next = GetTopWindow(NULL);
154 while (next != NULL)
155 {
156 if(!callback(next, lParam))
157 break;
158 next = GetWindow(next, GW_HWNDNEXT);
159 }
160 }
161
162 void ProcessMouseMessage(UINT message, LPARAM lParam)
163 {
164 int xPos = LOWORD(lParam);
165 int yPos = HIWORD(lParam);
166
167 int xIndex = (xPos - xOffset)/40;
168 int xOff = (xPos - xOffset)%40;
169
170 int yIndex = (yPos - yOffset)/40;
171 int yOff = (yPos - yOffset)%40;
172
173 if(xOff > 32 || xIndex > nItems)
174 return;
175
176 if(yOff > 32 || yIndex > nRows)
177 return;
178
179 selectedWindow = (yIndex*nCols) + xIndex;
180 if (message == WM_MOUSEMOVE)
181 {
182 InvalidateRect(switchdialog, NULL, TRUE);
183 //RedrawWindow(switchdialog, NULL, NULL, 0);
184 }
185 else
186 {
187 selectedWindow = (yIndex*nCols) + xIndex;
188 CompleteSwitch(TRUE);
189 }
190 }
191
192 void OnPaint(HWND hWnd)
193 {
194 HDC dialogDC;
195 PAINTSTRUCT paint;
196 RECT cRC, textRC;
197 int i;
198 HBRUSH hBrush;
199 HPEN hPen;
200 HFONT dcFont;
201 COLORREF cr;
202 int nch = GetWindowTextW(windowList[selectedWindow], windowText, _countof(windowText));
203
204 dialogDC = BeginPaint(hWnd, &paint);
205 {
206 GetClientRect(hWnd, &cRC);
207 FillRect(dialogDC, &cRC, GetSysColorBrush(COLOR_MENU));
208
209 for(i=0; i< windowCount; i++)
210 {
211 HICON hIcon = iconList[i];
212
213 int xpos = xOffset + 40 * (i % nCols);
214 int ypos = yOffset + 40 * (i / nCols);
215
216 if (selectedWindow == i)
217 {
218 hBrush = GetSysColorBrush(COLOR_HIGHLIGHT);
219 }
220 else
221 {
222 hBrush = GetSysColorBrush(COLOR_MENU);
223 }
224 #if TRUE
225 cr = GetSysColor(COLOR_BTNTEXT); // doesn't look right! >_<
226 hPen = CreatePen(PS_DOT, 1, cr);
227 SelectObject(dialogDC, hPen);
228 SelectObject(dialogDC, hBrush);
229 Rectangle(dialogDC, xpos-2, ypos-2, xpos+32+2, ypos+32+2);
230 DeleteObject(hPen);
231 // Must NOT destroy the system brush!
232 #else
233 RECT rc = { xpos-2, ypos-2, xpos+32+2, ypos+32+2 };
234 FillRect(dialogDC, &rc, hBrush);
235 #endif
236 DrawIcon(dialogDC, xpos, ypos, hIcon);
237 }
238
239 dcFont = SelectObject(dialogDC, dialogFont);
240 SetTextColor(dialogDC, GetSysColor(COLOR_BTNTEXT));
241 SetBkMode(dialogDC, TRANSPARENT);
242
243 textRC.top = itemsH;
244 textRC.left = 8;
245 textRC.right = totalW - 8;
246 textRC.bottom = totalH - 8;
247 DrawTextW(dialogDC, windowText, nch, &textRC, DT_CENTER|DT_END_ELLIPSIS);
248 SelectObject(dialogDC, dcFont);
249 }
250 EndPaint(hWnd, &paint);
251 }
252
253 DWORD CreateSwitcherWindow(HINSTANCE hInstance)
254 {
255 switchdialog = CreateWindowExW( WS_EX_TOPMOST|WS_EX_DLGMODALFRAME|WS_EX_TOOLWINDOW,
256 WC_SWITCH,
257 L"",
258 WS_POPUP|WS_BORDER|WS_DISABLED,
259 CW_USEDEFAULT,
260 CW_USEDEFAULT,
261 400, 150,
262 NULL, NULL,
263 hInstance, NULL);
264 if (!switchdialog)
265 {
266 TRACE("[ATbot] Task Switcher Window failed to create.\n");
267 return 0;
268 }
269
270 isOpen = FALSE;
271 return 1;
272 }
273
274 DWORD GetDialogFont(VOID)
275 {
276 HDC tDC;
277 TEXTMETRIC tm;
278
279 dialogFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
280
281 tDC = GetDC(0);
282 GetTextMetrics(tDC, &tm);
283 fontHeight = tm.tmHeight;
284 ReleaseDC(0, tDC);
285
286 return 1;
287 }
288
289 void PrepareWindow(VOID)
290 {
291 cxBorder = GetSystemMetrics(SM_CXBORDER);
292 cyBorder = GetSystemMetrics(SM_CYBORDER);
293
294 nItems = windowCount;
295 nCols = min(max(nItems,8),12);
296 nRows = (nItems+nCols-1)/nCols;
297
298 itemsW = nCols*32 + (nCols+1)*8;
299 itemsH = nRows*32 + (nRows+1)*8;
300
301 totalW = itemsW + 2*cxBorder + 4;
302 totalH = itemsH + 2*cyBorder + fontHeight + 8; // give extra pixels for the window title
303
304 xOffset = 8;
305 yOffset = 8;
306
307 if (nItems < nCols)
308 {
309 int w2 = nItems*32 + (nItems-1)*8;
310 xOffset = (itemsW-w2)/2;
311 }
312 ResizeAndCenter(switchdialog, totalW, totalH);
313 }
314
315 BOOL ProcessHotKey(VOID)
316 {
317 if (!isOpen)
318 {
319 windowCount=0;
320 EnumWindowsZOrder(EnumerateCallback, 0);
321
322 if (windowCount < 2)
323 return FALSE;
324
325 selectedWindow = 1;
326
327 TRACE("[ATbot] HotKey Received. Opening window.\n");
328 ShowWindow(switchdialog, SW_SHOWNORMAL);
329 MakeWindowActive(switchdialog);
330 isOpen = TRUE;
331 }
332 else
333 {
334 TRACE("[ATbot] HotKey Received Rotating.\n");
335 selectedWindow = (selectedWindow + 1)%windowCount;
336 InvalidateRect(switchdialog, NULL, TRUE);
337 }
338 return TRUE;
339 }
340
341 LRESULT WINAPI DoAppSwitch( WPARAM wParam, LPARAM lParam )
342 {
343 HWND hwnd, hwndActive;
344 MSG msg;
345 BOOL Esc = FALSE;
346 INT Count = 0;
347 WCHAR Text[1024];
348
349 // Already in the loop.
350 if (switchdialog) return 0;
351
352 hwndActive = GetActiveWindow();
353 // Nothing is active so exit.
354 if (!hwndActive) return 0;
355 // Capture current active window.
356 SetCapture( hwndActive );
357
358 switch (lParam)
359 {
360 case VK_TAB:
361 if( !CreateSwitcherWindow(User32Instance) ) goto Exit;
362 if( !GetDialogFont() ) goto Exit;
363 if( !ProcessHotKey() ) goto Exit;
364 break;
365
366 case VK_ESCAPE:
367 windowCount = 0;
368 Count = 0;
369 EnumWindowsZOrder(EnumerateCallback, 0);
370 if (windowCount < 2) goto Exit;
371 if (wParam == SC_NEXTWINDOW)
372 Count = 1;
373 else
374 {
375 if (windowCount == 2)
376 Count = 0;
377 else
378 Count = windowCount - 1;
379 }
380 TRACE("DoAppSwitch VK_ESCAPE 1 Count %d windowCount %d\n",Count,windowCount);
381 hwnd = windowList[Count];
382 GetWindowTextW(hwnd, Text, _countof(Text));
383 TRACE("[ATbot] Switching to 0x%08x (%ls)\n", hwnd, Text);
384 MakeWindowActive(hwnd);
385 Esc = TRUE;
386 break;
387
388 default:
389 goto Exit;
390 }
391 // Main message loop:
392 while (1)
393 {
394 for (;;)
395 {
396 if (PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE ))
397 {
398 if (!CallMsgFilterW( &msg, MSGF_NEXTWINDOW )) break;
399 /* remove the message from the queue */
400 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
401 }
402 else
403 WaitMessage();
404 }
405
406 switch (msg.message)
407 {
408 case WM_KEYUP:
409 {
410 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
411 if (msg.wParam == VK_MENU)
412 {
413 CompleteSwitch(TRUE);
414 }
415 else if (msg.wParam == VK_RETURN)
416 {
417 CompleteSwitch(TRUE);
418 }
419 else if (msg.wParam == VK_ESCAPE)
420 {
421 TRACE("DoAppSwitch VK_ESCAPE 2\n");
422 CompleteSwitch(FALSE);
423 }
424 goto Exit; //break;
425 }
426
427 case WM_SYSKEYDOWN:
428 {
429 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
430 if (HIWORD(msg.lParam) & KF_ALTDOWN)
431 {
432 INT Shift;
433 if ( msg.wParam == VK_TAB )
434 {
435 if (Esc) break;
436 Shift = GetKeyState(VK_SHIFT) & 0x8000 ? SC_PREVWINDOW : SC_NEXTWINDOW;
437 if (Shift == SC_NEXTWINDOW)
438 {
439 selectedWindow = (selectedWindow + 1)%windowCount;
440 }
441 else
442 {
443 selectedWindow = selectedWindow - 1;
444 if (selectedWindow < 0)
445 selectedWindow = windowCount - 1;
446 }
447 InvalidateRect(switchdialog, NULL, TRUE);
448 }
449 else if ( msg.wParam == VK_ESCAPE )
450 {
451 if (!Esc) break;
452 if (windowCount < 2)
453 goto Exit;
454 if (wParam == SC_NEXTWINDOW)
455 {
456 Count = (Count + 1)%windowCount;
457 }
458 else
459 {
460 Count--;
461 if (Count < 0)
462 Count = windowCount - 1;
463 }
464 hwnd = windowList[Count];
465 GetWindowTextW(hwnd, Text, _countof(Text));
466 MakeWindowActive(hwnd);
467 }
468 }
469 break;
470 }
471
472 case WM_LBUTTONUP:
473 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
474 ProcessMouseMessage(msg.message, msg.lParam);
475 goto Exit;
476
477 default:
478 if (PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ))
479 {
480 TranslateMessage(&msg);
481 DispatchMessageW(&msg);
482 }
483 break;
484 }
485 }
486 Exit:
487 ReleaseCapture();
488 if (switchdialog) DestroyWindow(switchdialog);
489 switchdialog = NULL;
490 selectedWindow = 0;
491 windowCount = 0;
492 return 0;
493 }
494
495 VOID
496 DestroyAppWindows(VOID)
497 {
498 INT i;
499 for (i=0; i< windowCount; i++)
500 {
501 HICON hIcon = iconList[i];
502 DestroyIcon(hIcon);
503 }
504 }
505
506 //
507 // Switch System Class Window Proc.
508 //
509 LRESULT WINAPI SwitchWndProc_common(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL unicode )
510 {
511 PWND pWnd;
512 PALTTABINFO ati;
513 pWnd = ValidateHwnd(hWnd);
514 if (pWnd)
515 {
516 if (!pWnd->fnid)
517 {
518 NtUserSetWindowFNID(hWnd, FNID_SWITCH);
519 }
520 }
521
522 switch (uMsg)
523 {
524 case WM_NCCREATE:
525 if (!(ati = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*ati))))
526 return 0;
527 SetWindowLongPtrW( hWnd, 0, (LONG_PTR)ati );
528 return TRUE;
529
530 case WM_SHOWWINDOW:
531 if (wParam)
532 {
533 PrepareWindow();
534 ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
535 ati->cItems = nItems;
536 ati->cxItem = ati->cyItem = 43;
537 ati->cRows = nRows;
538 ati->cColumns = nCols;
539 }
540 return 0;
541
542 case WM_MOUSEMOVE:
543 ProcessMouseMessage(uMsg, lParam);
544 return 0;
545
546 case WM_ACTIVATE:
547 if (wParam == WA_INACTIVE)
548 {
549 CompleteSwitch(FALSE);
550 }
551 return 0;
552
553 case WM_PAINT:
554 OnPaint(hWnd);
555 return 0;
556
557 case WM_DESTROY:
558 isOpen = FALSE;
559 ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
560 HeapFree( GetProcessHeap(), 0, ati );
561 SetWindowLongPtrW( hWnd, 0, 0 );
562 DestroyAppWindows();
563 NtUserSetWindowFNID(hWnd, FNID_DESTROY);
564 return 0;
565 }
566 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
567 }
568
569 LRESULT WINAPI SwitchWndProcA(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
570 {
571 return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, FALSE);
572 }
573
574 LRESULT WINAPI SwitchWndProcW(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
575 {
576 return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, TRUE);
577 }