[WIN32SS] Rewrite MENU_ShowPopup to take the exclude rectangle into account
authorMark Jansen <mark.jansen@reactos.org>
Sat, 15 Jun 2019 19:14:41 +0000 (21:14 +0200)
committerMark Jansen <mark.jansen@reactos.org>
Tue, 6 Aug 2019 19:17:35 +0000 (21:17 +0200)
CORE-15863

win32ss/user/ntuser/menu.c

index ccd4d8c..c731514 100644 (file)
@@ -2860,16 +2860,57 @@ static BOOL MENU_InitPopup( PWND pWndOwner, PMENU menu, UINT flags )
     return TRUE;
 }
 
+
+#define SHOW_DEBUGRECT      0
+
+#if SHOW_DEBUGRECT
+static void DebugRect(const RECT* rectl, COLORREF color)
+{
+    HBRUSH brush;
+    RECT rr;
+    HDC hdc;
+
+    if (!rectl)
+        return;
+
+    hdc = UserGetDCEx(NULL, 0, DCX_USESTYLE);
+
+    brush = IntGdiCreateSolidBrush(color);
+
+    rr = *rectl;
+    RECTL_vInflateRect(&rr, 1, 1);
+    FrameRect(hdc, rectl, brush);
+    FrameRect(hdc, &rr, brush);
+
+    NtGdiDeleteObjectApp(brush);
+    UserReleaseDC(NULL, hdc, TRUE);
+}
+
+static void DebugPoint(INT x, INT y, COLORREF color)
+{
+    RECT rr = {x, y, x, y};
+    DebugRect(&rr, color);
+}
+#endif
+
+static BOOL RECTL_Intersect(const RECT* pRect, INT x, INT y, UINT width, UINT height)
+{
+    RECT other = {x, y, x + width, y + height};
+    RECT dum;
+
+    return RECTL_bIntersectRect(&dum, pRect, &other);
+}
+
 /***********************************************************************
  *           MenuShowPopup
  *
  * Display a popup menu.
  */
 static BOOL FASTCALL MENU_ShowPopup(PWND pwndOwner, PMENU menu, UINT id, UINT flags,
-                              INT x, INT y)
+                              INT x, INT y, const RECT* pExclude)
 {
     UINT width, height;
-    POINT pt;
+    POINT ptx;
     PMONITOR monitor;
     PWND pWnd;
     USER_REFERENCE_ENTRY Ref;
@@ -2884,6 +2925,11 @@ static BOOL FASTCALL MENU_ShowPopup(PWND pwndOwner, PMENU menu, UINT id, UINT fl
         menu->iItem = NO_SELECTED_ITEM;
     }
 
+#if SHOW_DEBUGRECT
+    if (pExclude)
+        DebugRect(pExclude, RGB(255, 0, 0));
+#endif
+
     menu->dwArrowsOn = 0;
     MENU_PopupMenuCalcSize(menu, pwndOwner);
 
@@ -2892,61 +2938,106 @@ static BOOL FASTCALL MENU_ShowPopup(PWND pwndOwner, PMENU menu, UINT id, UINT fl
     width = menu->cxMenu + UserGetSystemMetrics(SM_CXBORDER);
     height = menu->cyMenu + UserGetSystemMetrics(SM_CYBORDER);
 
-    /* FIXME: should use item rect */
-    pt.x = x;
-    pt.y = y;
-    monitor = UserMonitorFromPoint( pt, MONITOR_DEFAULTTONEAREST );
-
     if (flags & TPM_LAYOUTRTL)
         flags ^= TPM_RIGHTALIGN;
 
-    if( flags & TPM_RIGHTALIGN ) x -= width;
-    if( flags & TPM_CENTERALIGN ) x -= width / 2;
+    if (flags & TPM_RIGHTALIGN)
+        x -= width;
+    if (flags & TPM_CENTERALIGN)
+        x -= width / 2;
 
-    if( flags & TPM_BOTTOMALIGN ) y -= height;
-    if( flags & TPM_VCENTERALIGN ) y -= height / 2;
+    if (flags & TPM_BOTTOMALIGN)
+        y -= height;
+    if (flags & TPM_VCENTERALIGN)
+        y -= height / 2;
 
-    if( x + width > monitor->rcMonitor.right)
+    /* FIXME: should use item rect */
+    ptx.x = x;
+    ptx.y = y;
+#if SHOW_DEBUGRECT
+    DebugPoint(x, y, RGB(0, 0, 255));
+#endif
+    monitor = UserMonitorFromPoint( ptx, MONITOR_DEFAULTTONEAREST );
+
+    /* We are off the right side of the screen */
+    if (x + width > monitor->rcMonitor.right)
     {
-        if( x + width > monitor->rcMonitor.right)
-        {
-            /* If we would flip around our origin, would we go off screen on the other side?
-               Or is our origin itself too far to the right already? */
-            if (!bIsPopup || x - width < monitor->rcMonitor.left || x > monitor->rcMonitor.right)
-                x = monitor->rcMonitor.right - width;
-            else
-                x -= width;
-        }
+        if ((x - width) < monitor->rcMonitor.left || x >= monitor->rcMonitor.right || bIsPopup)
+            x = monitor->rcMonitor.right - width;
+        else
+            x -= width;
     }
-    if( x < monitor->rcMonitor.left )
+
+    /* We are off the left side of the screen */
+    if (x < monitor->rcMonitor.left)
     {
-        /* If we would flip around our origin, would we go off screen on the other side? */
-        if (!bIsPopup || x + width > monitor->rcMonitor.right)
+        /* Re-orient the menu around the x-axis */
+        x += width;
+
+        if (x < monitor->rcMonitor.left || x >= monitor->rcMonitor.right || bIsPopup)
             x = monitor->rcMonitor.left;
+    }
+
+    /* Same here, but then the top */
+    if (y < monitor->rcMonitor.top)
+    {
+        y += height;
+
+        if (y < monitor->rcMonitor.top || y >= monitor->rcMonitor.bottom || bIsPopup)
+            y = monitor->rcMonitor.top;
+    }
+
+    /* And the bottom */
+    if (y + height > monitor->rcMonitor.bottom)
+    {
+        if ((y - height) < monitor->rcMonitor.top || y >= monitor->rcMonitor.bottom || bIsPopup)
+            y = monitor->rcMonitor.bottom - height;
         else
-            x += width;
+            y -= height;
     }
 
-    if( y + height > monitor->rcMonitor.bottom)
+    if (pExclude || bIsPopup)
     {
-        if( y + height > monitor->rcMonitor.bottom)
+        RECT PopupOrigin = {x-1, y-1, x+1, y+1};
+        RECT Cleaned;
+
+        if (RECTL_bIntersectRect(&Cleaned, pExclude ? pExclude : &PopupOrigin, &monitor->rcMonitor) &&
+            RECTL_Intersect(&Cleaned, x, y, width, height))
         {
-            /* If we would flip around our origin, would we go off screen on the other side?
-               Or is our origin itself too far to the bottom already? */
-            if (!bIsPopup || y - height < monitor->rcMonitor.top || y > monitor->rcMonitor.bottom)
-                y = monitor->rcMonitor.bottom - height;
+            /* Figure out if we should move vertical or horizontal */
+            if (flags & TPM_VERTICAL)
+            {
+                /* Move in the vertical direction: TPM_BOTTOMALIGN means drop it above, otherways drop it below */
+                if (flags & TPM_BOTTOMALIGN)
+                {
+                    y = Cleaned.top - height;
+                }
+                else
+                {
+                    y = Cleaned.bottom;
+                }
+            }
             else
-                y -= height;
+            {
+                /* Move in the horizontal direction: TPM_RIGHTALIGN means drop it to the left, otherways go right */
+                if (flags & TPM_RIGHTALIGN)
+                {
+                    x = Cleaned.left - width;
+                }
+                else
+                {
+                    x = Cleaned.right;
+                }
+            }
         }
     }
-    if( y < monitor->rcMonitor.top )
+
+#if SHOW_DEBUGRECT
     {
-        /* If we would flip around our origin, would we go off screen on the other side? */
-        if (!bIsPopup || y + height > monitor->rcMonitor.bottom)
-            y = monitor->rcMonitor.top;
-        else
-            y += height;
+        RECT rr = {x, y, x + width, y + height};
+        DebugRect(&rr, RGB(0, 255, 0));
     }
+#endif
 
     pWnd = ValidateHwndNoErr( menu->hWnd );
 
@@ -3194,7 +3285,7 @@ static void FASTCALL MENU_HideSubPopups(PWND pWndOwner, PMENU Menu,
  */
 static PMENU FASTCALL MENU_ShowSubPopup(PWND WndOwner, PMENU Menu, BOOL SelectFirst, UINT Flags)
 {
-  RECT Rect;
+  RECT Rect, ParentRect;
   ITEM *Item;
   HDC Dc;
   PWND pWnd;
@@ -3229,6 +3320,14 @@ static PMENU FASTCALL MENU_ShowSubPopup(PWND WndOwner, PMENU Menu, BOOL SelectFi
 
   pWnd = ValidateHwndNoErr(Menu->hWnd);
 
+  ParentRect = Rect;
+  if (pWnd)
+  {
+      POINT pt = {0, 0};
+      IntClientToScreen(pWnd, &pt);
+      RECTL_vOffsetRect(&ParentRect, pt.x, pt.y);
+  }
+
   /* correct item if modified as a reaction to WM_INITMENUPOPUP message */
   if (!(Item->fState & MF_HILITE))
   {
@@ -3305,7 +3404,7 @@ static PMENU FASTCALL MENU_ShowSubPopup(PWND WndOwner, PMENU Menu, BOOL SelectFi
   MENU_InitPopup( WndOwner, Item->spSubMenu, Flags );
 
   MENU_ShowPopup( WndOwner, Item->spSubMenu, Menu->iItem, Flags,
-                Rect.left, Rect.top);
+                Rect.left, Rect.top, &ParentRect);
   if (SelectFirst)
   {
       MENU_MoveSelection(WndOwner, Item->spSubMenu, ITEM_NEXT);
@@ -3905,7 +4004,7 @@ static void FASTCALL MENU_KeyRight(MTRACKER *pmt, UINT Flags, UINT msg)
  * Menu tracking code.
  */
 static INT FASTCALL MENU_TrackMenu(PMENU pmenu, UINT wFlags, INT x, INT y,
-                            PWND pwnd, const RECT *lprect )
+                            PWND pwnd)
 {
     MSG msg;
     BOOL fRemove;
@@ -3928,9 +4027,8 @@ static INT FASTCALL MENU_TrackMenu(PMENU pmenu, UINT wFlags, INT x, INT y,
     mt.Pt.x = x;
     mt.Pt.y = y;
 
-    TRACE("MTM : hmenu=%p flags=0x%08x (%d,%d) hwnd=%x (%ld,%ld)-(%ld,%ld)\n",
-         UserHMGetHandle(pmenu), wFlags, x, y, UserHMGetHandle(pwnd), lprect ? lprect->left : 0, lprect ? lprect->top : 0,
-         lprect ? lprect->right : 0, lprect ? lprect->bottom : 0);
+    TRACE("MTM : hmenu=%p flags=0x%08x (%d,%d) hwnd=%x\n",
+         UserHMGetHandle(pmenu), wFlags, x, y, UserHMGetHandle(pwnd));
 
     pti->MessageQueue->QF_flags &= ~QF_ACTIVATIONCHANGE;
 
@@ -4341,7 +4439,7 @@ VOID MENU_TrackMouseMenuBar( PWND pWnd, ULONG ht, POINT pt)
         MENU_InitTracking(pWnd, pMenu, FALSE, wFlags);
         /* fetch the window menu again, it may have changed */
         pMenu = (ht == HTSYSMENU) ? get_win_sys_menu( UserHMGetHandle(pWnd) ) : IntGetMenu( UserHMGetHandle(pWnd) );
-        MENU_TrackMenu(pMenu, wFlags, pt.x, pt.y, pWnd, NULL);
+        MENU_TrackMenu(pMenu, wFlags, pt.x, pt.y, pWnd);
         MENU_ExitTracking(pWnd, FALSE, wFlags);
     }
 }
@@ -4405,7 +4503,7 @@ VOID MENU_TrackKbdMenuBar(PWND pwnd, UINT wParam, WCHAR wChar)
     }
 
 track_menu:
-    MENU_TrackMenu( TrackMenu, wFlags, 0, 0, pwnd, NULL );
+    MENU_TrackMenu( TrackMenu, wFlags, 0, 0, pwnd );
     MENU_ExitTracking( pwnd, FALSE, wFlags);
 }
 
@@ -4447,9 +4545,8 @@ BOOL WINAPI IntTrackPopupMenuEx( PMENU menu, UINT wFlags, int x, int y,
        if (menu->fFlags & MNF_SYSMENU)
           MENU_InitSysMenuPopup( menu, pWnd->style, pWnd->pcls->style, HTSYSMENU);
 
-       if (MENU_ShowPopup(pWnd, menu, 0, wFlags | TPM_POPUPMENU, x, y))
-          ret = MENU_TrackMenu( menu, wFlags | TPM_POPUPMENU, 0, 0, pWnd,
-                                lpTpm ? &lpTpm->rcExclude : NULL);
+       if (MENU_ShowPopup(pWnd, menu, 0, wFlags | TPM_POPUPMENU, x, y, lpTpm ? &lpTpm->rcExclude : NULL))
+          ret = MENU_TrackMenu( menu, wFlags | TPM_POPUPMENU, 0, 0, pWnd);
        else
        {
           MsqSetStateWindow(pti, MSQ_STATE_MENUOWNER, NULL);