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       PostMessageW(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
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    HWND hwndIcon, hwndOwner;
169 
170    UNREFERENCED_PARAMETER(lParam);
171 
172    hwndOwner = GetWindow(window, GW_OWNER);
173    hwndIcon = (hwndOwner ? hwndOwner : window);
174 
175    // First try to get the big icon assigned to the window
176    hIcon = (HICON)SendMessageW(hwndIcon, WM_GETICON, ICON_BIG, 0);
177    if (!hIcon)
178    {
179       // If no icon is assigned, try to get the icon assigned to the windows' class
180       hIcon = (HICON)GetClassLongPtrW(hwndIcon, GCL_HICON);
181       if (!hIcon)
182       {
183          // If we still don't have an icon, see if we can do with the small icon,
184          // or a default application icon
185          hIcon = (HICON)SendMessageW(hwndIcon, WM_GETICON, ICON_SMALL2, 0);
186          if (!hIcon)
187          {
188             // using windows logo icon as default
189             hIcon = gpsi->hIconWindows;
190             if (!hIcon)
191             {
192                //if all attempts to get icon fails go to the next window
193                return TRUE;
194             }
195          }
196       }
197    }
198 
199    windowList[windowCount] = window;
200    iconList[windowCount] = CopyIcon(hIcon);
201 
202    windowCount++;
203 
204    // If we got to the max number of windows,
205    // we won't be able to add any more
206    if(windowCount >= MAX_WINDOWS)
207       return FALSE;
208 
209    return TRUE;
210 }
211 
212 // Function mostly compatible with the normal EnumChildWindows,
213 // except it lists in Z-Order and it doesn't ensure consistency
214 // if a window is removed while enumerating
215 void EnumWindowsZOrder(WNDENUMPROC callback, LPARAM lParam)
216 {
217     HWND hwnd, hwndOwner;
218     WCHAR szClass[64];
219     DWORD ExStyle;
220 
221     for (hwnd = GetTopWindow(NULL); hwnd; hwnd = GetWindow(hwnd, GW_HWNDNEXT))
222     {
223         if (!IsWindowVisible(hwnd))
224             continue;
225 
226         // check special windows
227         if (!GetClassNameW(hwnd, szClass, _countof(szClass)) ||
228             wcscmp(szClass, L"Shell_TrayWnd") == 0 ||
229             wcscmp(szClass, L"Progman") == 0)
230         {
231             continue;
232         }
233 
234         ExStyle = GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
235         if (ExStyle & WS_EX_TOOLWINDOW)
236             continue;
237 
238         hwndOwner = GetWindow(hwnd, GW_OWNER);
239         if ((ExStyle & WS_EX_APPWINDOW) || !IsWindowVisible(hwndOwner))
240         {
241             if (!callback(hwnd, lParam))
242                 break;
243 
244             continue;
245         }
246     }
247 }
248 
249 void ProcessMouseMessage(UINT message, LPARAM lParam)
250 {
251    int xPos = LOWORD(lParam);
252    int yPos = HIWORD(lParam);
253 
254    int xIndex = (xPos - DIALOG_MARGIN) / CX_ITEM_SPACE;
255    int yIndex = (yPos - DIALOG_MARGIN) / CY_ITEM_SPACE;
256 
257    if (xIndex < 0 || nCols <= xIndex ||
258        yIndex < 0 || nRows <= yIndex)
259    {
260         return;
261    }
262 
263    selectedWindow = (yIndex*nCols) + xIndex;
264    if (message == WM_MOUSEMOVE)
265    {
266       InvalidateRect(switchdialog, NULL, TRUE);
267       //RedrawWindow(switchdialog, NULL, NULL, 0);
268    }
269    else
270    {
271       selectedWindow = (yIndex*nCols) + xIndex;
272       CompleteSwitch(TRUE);
273    }
274 }
275 
276 void OnPaint(HWND hWnd)
277 {
278     HDC dialogDC;
279     PAINTSTRUCT paint;
280     RECT cRC, textRC;
281     int i, xPos, yPos, CharCount;
282     HFONT dcFont;
283     HICON hIcon;
284     HPEN hPen;
285     COLORREF Color;
286 
287     // check
288     if (nCols == 0 || nItems == 0)
289         return;
290 
291     // begin painting
292     dialogDC = BeginPaint(hWnd, &paint);
293     if (dialogDC == NULL)
294         return;
295 
296     // fill the client area
297     GetClientRect(hWnd, &cRC);
298     FillRect(dialogDC, &cRC, (HBRUSH)(COLOR_3DFACE + 1));
299 
300     // if the selection index exceeded the display items, then
301     // do display item shifting
302     if (selectedWindow >= nItems)
303         nShift = selectedWindow - nItems + 1;
304     else
305         nShift = 0;
306 
307     for (i = 0; i < nItems; ++i)
308     {
309         // get the icon to display
310         hIcon = iconList[i + nShift];
311 
312         // calculate the position where we start drawing
313         xPos = DIALOG_MARGIN + CX_ITEM_SPACE * (i % nCols) + ITEM_MARGIN;
314         yPos = DIALOG_MARGIN + CY_ITEM_SPACE * (i / nCols) + ITEM_MARGIN;
315 
316         // centering
317         if (nItems < CoolSwitchColumns)
318         {
319             xPos += (itemsW - nItems * CX_ITEM_SPACE) / 2;
320         }
321 
322         // if this position is selected,
323         if (selectedWindow == i + nShift)
324         {
325             // create a solid pen
326             Color = GetSysColor(COLOR_HIGHLIGHT);
327             hPen = CreatePen(PS_SOLID, 1, Color);
328 
329             // draw a rectangle with using the pen
330             SelectObject(dialogDC, hPen);
331             SelectObject(dialogDC, GetStockObject(NULL_BRUSH));
332             Rectangle(dialogDC, xPos, yPos, xPos + CX_ITEM, yPos + CY_ITEM);
333             Rectangle(dialogDC, xPos + 1, yPos + 1,
334                                 xPos + CX_ITEM - 1, yPos + CY_ITEM - 1);
335 
336             // delete the pen
337             DeleteObject(hPen);
338         }
339 
340         // draw icon
341         DrawIconEx(dialogDC, xPos + ICON_MARGIN, yPos + ICON_MARGIN,
342                    hIcon, CX_ICON, CY_ICON, 0, NULL, DI_NORMAL);
343     }
344 
345     // set the text rectangle
346     SetRect(&textRC, DIALOG_MARGIN, DIALOG_MARGIN + itemsH,
347             totalW - DIALOG_MARGIN, totalH - DIALOG_MARGIN);
348 
349     // draw the sunken button around text
350     DrawFrameControl(dialogDC, &textRC, DFC_BUTTON,
351                      DFCS_BUTTONPUSH | DFCS_PUSHED);
352 
353     // get text
354     CharCount = GetWindowTextW(windowList[selectedWindow], windowText,
355                                _countof(windowText));
356 
357     // draw text
358     dcFont = SelectObject(dialogDC, dialogFont);
359     SetTextColor(dialogDC, GetSysColor(COLOR_BTNTEXT));
360     SetBkMode(dialogDC, TRANSPARENT);
361     DrawTextW(dialogDC, windowText, CharCount, &textRC,
362               DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE);
363     SelectObject(dialogDC, dcFont);
364 
365     // end painting
366     EndPaint(hWnd, &paint);
367 }
368 
369 DWORD CreateSwitcherWindow(HINSTANCE hInstance)
370 {
371     switchdialog = CreateWindowExW( WS_EX_TOPMOST|WS_EX_DLGMODALFRAME|WS_EX_TOOLWINDOW,
372                                     WC_SWITCH,
373                                     L"",
374                                     WS_POPUP|WS_BORDER|WS_DISABLED,
375                                     CW_USEDEFAULT,
376                                     CW_USEDEFAULT,
377                                     400, 150,
378                                     NULL, NULL,
379                                     hInstance, NULL);
380     if (!switchdialog)
381     {
382        TRACE("[ATbot] Task Switcher Window failed to create.\n");
383        return 0;
384     }
385 
386     isOpen = FALSE;
387     return 1;
388 }
389 
390 DWORD GetDialogFont(VOID)
391 {
392    HDC tDC;
393    TEXTMETRIC tm;
394 
395    dialogFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
396 
397    tDC = GetDC(0);
398    GetTextMetrics(tDC, &tm);
399    fontHeight = tm.tmHeight;
400    ReleaseDC(0, tDC);
401 
402    return 1;
403 }
404 
405 void PrepareWindow(VOID)
406 {
407    nItems = windowCount;
408 
409    nCols = CoolSwitchColumns;
410    nRows = (nItems + CoolSwitchColumns - 1) / CoolSwitchColumns;
411    if (nRows > CoolSwitchRows)
412    {
413       nRows = CoolSwitchRows;
414       nItems = nRows * nCols;
415    }
416 
417    itemsW = nCols * CX_ITEM_SPACE;
418    itemsH = nRows * CY_ITEM_SPACE;
419 
420    totalW = itemsW + 2 * DIALOG_MARGIN;
421    totalH = itemsH + 2 * DIALOG_MARGIN;
422    totalH += fontHeight + 2 * CY_TEXT_MARGIN;
423 
424    ResizeAndCenter(switchdialog, totalW, totalH);
425 }
426 
427 BOOL ProcessHotKey(VOID)
428 {
429    if (!isOpen)
430    {
431       windowCount=0;
432       EnumWindowsZOrder(EnumerateCallback, 0);
433 
434       if (windowCount == 0)
435          return FALSE;
436 
437       if (windowCount == 1)
438       {
439          selectedWindow = 0;
440          CompleteSwitch(TRUE);
441          return TRUE;
442       }
443 
444       selectedWindow = 1;
445 
446       TRACE("[ATbot] HotKey Received. Opening window.\n");
447       ShowWindow(switchdialog, SW_SHOWNORMAL);
448       MakeWindowActive(switchdialog);
449       isOpen = TRUE;
450    }
451    else
452    {
453       TRACE("[ATbot] HotKey Received  Rotating.\n");
454       selectedWindow = (selectedWindow + 1)%windowCount;
455       InvalidateRect(switchdialog, NULL, TRUE);
456    }
457    return TRUE;
458 }
459 
460 void RotateTasks(BOOL bShift)
461 {
462     HWND hwndFirst, hwndLast;
463     DWORD Size;
464 
465     if (windowCount < 2 || !Esc)
466         return;
467 
468     hwndFirst = windowList[0];
469     hwndLast = windowList[windowCount - 1];
470 
471     if (bShift)
472     {
473         SetWindowPos(hwndLast, HWND_TOP, 0, 0, 0, 0,
474                      SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE |
475                      SWP_NOOWNERZORDER | SWP_NOREPOSITION);
476 
477         MakeWindowActive(hwndLast);
478 
479         Size = (windowCount - 1) * sizeof(HWND);
480         MoveMemory(&windowList[1], &windowList[0], Size);
481         windowList[0] = hwndLast;
482     }
483     else
484     {
485         SetWindowPos(hwndFirst, hwndLast, 0, 0, 0, 0,
486                      SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE |
487                      SWP_NOOWNERZORDER | SWP_NOREPOSITION);
488 
489         MakeWindowActive(windowList[1]);
490 
491         Size = (windowCount - 1) * sizeof(HWND);
492         MoveMemory(&windowList[0], &windowList[1], Size);
493         windowList[windowCount - 1] = hwndFirst;
494     }
495 }
496 
497 static void MoveLeft(void)
498 {
499     selectedWindow = selectedWindow - 1;
500     if (selectedWindow < 0)
501         selectedWindow = windowCount - 1;
502     InvalidateRect(switchdialog, NULL, TRUE);
503 }
504 
505 static void MoveRight(void)
506 {
507     selectedWindow = (selectedWindow + 1) % windowCount;
508     InvalidateRect(switchdialog, NULL, TRUE);
509 }
510 
511 static void MoveUp(void)
512 {
513     INT iRow = selectedWindow / nCols;
514     INT iCol = selectedWindow % nCols;
515 
516     --iRow;
517     if (iRow < 0)
518         iRow = nRows - 1;
519 
520     selectedWindow = iRow * nCols + iCol;
521     if (selectedWindow >= windowCount)
522         selectedWindow = windowCount - 1;
523     InvalidateRect(switchdialog, NULL, TRUE);
524 }
525 
526 static void MoveDown(void)
527 {
528     INT iRow = selectedWindow / nCols;
529     INT iCol = selectedWindow % nCols;
530 
531     ++iRow;
532     if (iRow >= nRows)
533         iRow = 0;
534 
535     selectedWindow = iRow * nCols + iCol;
536     if (selectedWindow >= windowCount)
537         selectedWindow = windowCount - 1;
538     InvalidateRect(switchdialog, NULL, TRUE);
539 }
540 
541 VOID
542 DestroyAppWindows(VOID)
543 {
544     // for every item of the icon list:
545     INT i;
546     for (i = 0; i < windowCount; ++i)
547     {
548         // destroy the icon
549         DestroyIcon(iconList[i]);
550         iconList[i] = NULL;
551     }
552 }
553 
554 LRESULT WINAPI DoAppSwitch( WPARAM wParam, LPARAM lParam )
555 {
556    HWND hwndActive;
557    MSG msg;
558 
559    // FIXME: Is loading timing OK?
560    LoadCoolSwitchSettings();
561 
562    if (!CoolSwitch)
563       return 0;
564 
565    // Already in the loop.
566    if (switchdialog || Esc) return 0;
567 
568    hwndActive = GetActiveWindow();
569    // Nothing is active so exit.
570    if (!hwndActive) return 0;
571 
572    if (lParam == VK_ESCAPE)
573    {
574       Esc = TRUE;
575 
576       windowCount = 0;
577       EnumWindowsZOrder(EnumerateCallback, 0);
578 
579       if (windowCount < 2)
580           return 0;
581 
582       RotateTasks(GetAsyncKeyState(VK_SHIFT) < 0);
583 
584       hwndActive = GetActiveWindow();
585 
586       if (hwndActive == NULL)
587       {
588           Esc = FALSE;
589           return 0;
590       }
591    }
592 
593    // Capture current active window.
594    SetCapture( hwndActive );
595 
596    switch (lParam)
597    {
598       case VK_TAB:
599          if( !CreateSwitcherWindow(User32Instance) ) goto Exit;
600          if( !GetDialogFont() ) goto Exit;
601          if( !ProcessHotKey() ) goto Exit;
602          break;
603 
604       case VK_ESCAPE:
605          break;
606 
607       default:
608          goto Exit;
609    }
610    // Main message loop:
611    while (1)
612    {
613       for (;;)
614       {
615          if (PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE ))
616          {
617              if (!CallMsgFilterW( &msg, MSGF_NEXTWINDOW )) break;
618              /* remove the message from the queue */
619              PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
620          }
621          else
622              WaitMessage();
623       }
624 
625       switch (msg.message)
626       {
627         case WM_KEYUP:
628         {
629           PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
630           if (msg.wParam == VK_MENU)
631           {
632              CompleteSwitch(TRUE);
633           }
634           else if (msg.wParam == VK_RETURN)
635           {
636              CompleteSwitch(TRUE);
637           }
638           else if (msg.wParam == VK_ESCAPE)
639           {
640              TRACE("DoAppSwitch VK_ESCAPE 2\n");
641              CompleteSwitch(FALSE);
642           }
643           goto Exit; //break;
644         }
645 
646         case WM_SYSKEYDOWN:
647         {
648           PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
649           if (HIWORD(msg.lParam) & KF_ALTDOWN)
650           {
651              if ( msg.wParam == VK_TAB )
652              {
653                 if (Esc) break;
654                 if (GetKeyState(VK_SHIFT) < 0)
655                 {
656                     MoveLeft();
657                 }
658                 else
659                 {
660                     MoveRight();
661                 }
662              }
663              else if ( msg.wParam == VK_ESCAPE )
664              {
665                 if (!Esc) break;
666                 RotateTasks(GetKeyState(VK_SHIFT) < 0);
667              }
668              else if ( msg.wParam == VK_LEFT )
669              {
670                 MoveLeft();
671              }
672              else if ( msg.wParam == VK_RIGHT )
673              {
674                 MoveRight();
675              }
676              else if ( msg.wParam == VK_UP )
677              {
678                 MoveUp();
679              }
680              else if ( msg.wParam == VK_DOWN )
681              {
682                 MoveDown();
683              }
684           }
685           break;
686         }
687 
688         case WM_LBUTTONUP:
689           PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
690           ProcessMouseMessage(msg.message, msg.lParam);
691           goto Exit;
692 
693         default:
694           if (PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ))
695           {
696              TranslateMessage(&msg);
697              DispatchMessageW(&msg);
698           }
699           break;
700       }
701    }
702 Exit:
703    ReleaseCapture();
704    if (switchdialog) DestroyWindow(switchdialog);
705    if (Esc) DestroyAppWindows();
706    switchdialog = NULL;
707    selectedWindow = 0;
708    windowCount = 0;
709    Esc = FALSE;
710    return 0;
711 }
712 
713 //
714 // Switch System Class Window Proc.
715 //
716 LRESULT WINAPI SwitchWndProc_common(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL unicode )
717 {
718    PWND pWnd;
719    PALTTABINFO ati;
720    pWnd = ValidateHwnd(hWnd);
721    if (pWnd)
722    {
723       if (!pWnd->fnid)
724       {
725          NtUserSetWindowFNID(hWnd, FNID_SWITCH);
726       }
727    }
728 
729    switch (uMsg)
730    {
731       case WM_NCCREATE:
732          if (!(ati = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*ati))))
733             return 0;
734          SetWindowLongPtrW( hWnd, 0, (LONG_PTR)ati );
735          return TRUE;
736 
737       case WM_SHOWWINDOW:
738          if (wParam)
739          {
740             PrepareWindow();
741             ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
742             ati->cbSize = sizeof(ALTTABINFO);
743             ati->cItems = nItems;
744             ati->cColumns = nCols;
745             ati->cRows = nRows;
746             if (nCols)
747             {
748                ati->iColFocus = (selectedWindow - nShift) % nCols;
749                ati->iRowFocus = (selectedWindow - nShift) / nCols;
750             }
751             else
752             {
753                ati->iColFocus = 0;
754                ati->iRowFocus = 0;
755             }
756             ati->cxItem = CX_ITEM_SPACE;
757             ati->cyItem = CY_ITEM_SPACE;
758             ati->ptStart = ptStart;
759          }
760          return 0;
761 
762       case WM_MOUSEMOVE:
763          ProcessMouseMessage(uMsg, lParam);
764          return 0;
765 
766       case WM_ACTIVATE:
767          if (wParam == WA_INACTIVE)
768          {
769             CompleteSwitch(FALSE);
770          }
771          return 0;
772 
773       case WM_PAINT:
774          OnPaint(hWnd);
775          return 0;
776 
777       case WM_DESTROY:
778          isOpen = FALSE;
779          ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
780          HeapFree( GetProcessHeap(), 0, ati );
781          SetWindowLongPtrW( hWnd, 0, 0 );
782          DestroyAppWindows();
783          NtUserSetWindowFNID(hWnd, FNID_DESTROY);
784          return 0;
785    }
786    return DefWindowProcW(hWnd, uMsg, wParam, lParam);
787 }
788 
789 LRESULT WINAPI SwitchWndProcA(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
790 {
791    return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, FALSE);
792 }
793 
794 LRESULT WINAPI SwitchWndProcW(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
795 {
796    return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, TRUE);
797 }
798