mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
548 lines
17 KiB
548 lines
17 KiB
/**************************** Module Header ********************************\
|
|
* Module Name: mnpopup.c
|
|
*
|
|
* Copyright 1985-90, Microsoft Corporation
|
|
*
|
|
* Popup Menu Support
|
|
*
|
|
* History:
|
|
* 10-10-90 JimA Cleanup.
|
|
* 03-18-91 IanJa Window revalidation added
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
#define RECT_ONLEFT 0
|
|
#define RECT_ONTOP 1
|
|
#define RECT_ONRIGHT 2
|
|
#define RECT_ONBOTTOM 3
|
|
#define RECT_ORG 4
|
|
|
|
BOOL TryRect(UINT wRect, int x, int y, int cx, int cy, LPRECT prcExclude, LPPOINT ppt);
|
|
#ifdef MEMPHIS_MENU_ANIMATION
|
|
LONG FindBestPos(int x, int y, int cx, int cy, LPRECT prcExclude, UINT wFlags,
|
|
PPOPUPMENU ppopupmenu);
|
|
#else // MEMPHIS_MENU_ANIMATION
|
|
LONG FindBestPos(int x, int y, int cx, int cy, LPRECT prcExclude, UINT wFlags);
|
|
#endif// MEMPHIS_MENU_ANIMATION
|
|
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxTrackPopupMenu (API)
|
|
*
|
|
* Process a popup menu
|
|
*
|
|
* Revalidation Notes:
|
|
* o if pwndOwner is always the owner of the popup menu windows, then we don't
|
|
* really have to revalidate it: when it is destroyed the popup menu windows
|
|
* are destroyed first because it owns them - this is detected in MenuWndProc
|
|
* so we would only have to test pMenuState->fSabotaged.
|
|
* o pMenuState->fSabotaged must be cleared before this top-level routine
|
|
* returns, to be ready for next time menus are processed (unless we are
|
|
* currently inside xxxMenuLoop())
|
|
* o pMenuState->fSabotaged should be FALSE when we enter this routine.
|
|
* o xxxMenuLoop always returns with pMenuState->fSabotaged clear. Use
|
|
* a UserAssert to verify this.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
int xxxTrackPopupMenuEx(
|
|
PMENU pMenu,
|
|
UINT dwFlags,
|
|
int x,
|
|
int y,
|
|
PWND pwndOwner,
|
|
LPTPMPARAMS lpTpm)
|
|
{
|
|
PMENUSTATE pMenuState;
|
|
PWND pwndHierarchy;
|
|
PPOPUPMENU ppopupMenuHierarchy;
|
|
LONG sizeHierarchy;
|
|
int cxPopup, cyPopup;
|
|
BOOL fSync;
|
|
int cmd;
|
|
BOOL fButtonDown;
|
|
TL tlpwndHierarchy;
|
|
TL tlpwndT;
|
|
RECT rcExclude;
|
|
PTHREADINFO ptiCurrent, ptiOwner;
|
|
#ifdef MEMPHIS_MENUS
|
|
PMENUSTATE pmnStateSaved;
|
|
BOOL fSaved = FALSE;
|
|
#endif // MEMPHIS_MENUS
|
|
CheckLock(pMenu);
|
|
CheckLock(pwndOwner);
|
|
|
|
/*
|
|
* Capture the things we care about in case lpTpm goes away.
|
|
*/
|
|
if (lpTpm != NULL) {
|
|
if (lpTpm->cbSize != sizeof(TPMPARAMS)) {
|
|
RIPERR0(ERROR_INVALID_PARAMETER, RIP_WARNING, "TrackPopupMenuEx: cbSize is invalid");
|
|
return(FALSE);
|
|
}
|
|
rcExclude = lpTpm->rcExclude;
|
|
}
|
|
|
|
|
|
ptiCurrent = PtiCurrent();
|
|
ptiOwner = GETPTI(pwndOwner);
|
|
|
|
/*
|
|
* Win95 compatibility: pwndOwner must be owned by ptiCurrent.
|
|
* If we make xxxMNStartState fail this case as well (i.e, spwndNotify
|
|
* must be owned by ptiCurrent), then we could simplify the
|
|
* MenuState stuff because a pMenuState would be owned and used by one
|
|
* thread only. Win95 doesn't do that in MNGetPopup (i.e., the active
|
|
* window could be owned by a different thread); they probably
|
|
* get all messed up if it happens. We also allow it but we handle it OK.
|
|
* On the other hand though, let's keep the code in this function that
|
|
* handles this in case we decide to allow this for future generations.
|
|
*/
|
|
if (ptiCurrent != ptiOwner) {
|
|
RIPMSG0(RIP_WARNING, "xxxTrackPopupMenuEx: pwndOwner not owned by ptiCurrent");
|
|
return FALSE;
|
|
}
|
|
|
|
if ((pMenu == NULL)
|
|
|| (ptiCurrent->pMenuState != NULL)
|
|
|| (ptiOwner->pMenuState != NULL)) {
|
|
#ifdef MEMPHIS_MENUS
|
|
if ( dwFlags & TPM_RECURSE ) {
|
|
/*
|
|
* There might be several threads involved while working with
|
|
* a menu (i.e., two threads are attached; one owns pwndOwner
|
|
* and the other one calls TrackPopupMenu. So just saving
|
|
* PtiCurrent()->pMenuState is not enough.
|
|
* Also, do we want to go on if pMenu == NULL?
|
|
* I'm failing this so these won't fall through the cracks
|
|
*/
|
|
UserAssert(!(dwFlags & TPM_RECURSE));
|
|
return FALSE;
|
|
|
|
pmnStateSaved = ptiCurrent->pMenuState;
|
|
fSaved = TRUE;
|
|
} else {
|
|
#endif // MEMPHIS_MENUS
|
|
/*
|
|
* Allow only one guy to have a popup menu up at a time...
|
|
*/
|
|
RIPERR0(ERROR_POPUP_ALREADY_ACTIVE, RIP_VERBOSE, "");
|
|
return FALSE;
|
|
#ifdef MEMPHIS_MENUS
|
|
}
|
|
#endif // MEMPHIS_MENUS
|
|
}
|
|
|
|
|
|
// Is button down?
|
|
|
|
if (dwFlags & TPM_RIGHTBUTTON)
|
|
{
|
|
fButtonDown = (_GetKeyState(VK_RBUTTON) & 0x8000) != 0;
|
|
} else {
|
|
fButtonDown = (_GetKeyState(VK_LBUTTON) & 0x8000) != 0;
|
|
}
|
|
|
|
/*
|
|
* We always have to make this window topmost so FindWindow finds it
|
|
* before the cached global menu window. Note that there can be problems
|
|
* with NTSD and leaving this window around.
|
|
*/
|
|
pwndHierarchy = xxxCreateWindowEx(
|
|
WS_EX_TOOLWINDOW | WS_EX_TOPMOST |
|
|
WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE,
|
|
(PLARGE_STRING)MENUCLASS,
|
|
NULL, WS_POPUP | WS_BORDER,
|
|
x, y, 100, 100, NULL, NULL, (HANDLE)pwndOwner->hModule,
|
|
(LPVOID)pMenu, pwndOwner->dwExpWinVer);
|
|
|
|
if (!pwndHierarchy) {
|
|
#ifdef MEMPHIS_MENUS
|
|
/* BUGBUG
|
|
* What about pmnStateSaved? Let's stop here so this gets fixed someday
|
|
*/
|
|
UserAssert(pwndHierarchy != NULL);
|
|
#endif // MEMPHIS_MENUS
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Do this so that old apps don't get weird borders on tracked popups due
|
|
// to the app hack used in CreateWindowEx32.
|
|
//
|
|
ClrWF(pwndHierarchy, WFOLDUI);
|
|
|
|
ThreadLockAlways(pwndHierarchy, &tlpwndHierarchy);
|
|
|
|
#ifdef HAVE_MN_GETPPOPUPMENU
|
|
ppopupMenuHierarchy = (PPOPUPMENU)xxxSendMessage(pwndHierarchy,
|
|
MN_GETPPOPUPMENU, 0, 0);
|
|
#else
|
|
ppopupMenuHierarchy = ((PMENUWND)pwndHierarchy)->ppopupmenu;
|
|
#endif
|
|
|
|
|
|
ppopupMenuHierarchy->fDelayedFree = TRUE;
|
|
Lock(&(ppopupMenuHierarchy->spwndNotify), pwndOwner);
|
|
Lock(&(ppopupMenuHierarchy->spmenu), pMenu);
|
|
ppopupMenuHierarchy->ppopupmenuRoot = ppopupMenuHierarchy;
|
|
ppopupMenuHierarchy->fIsTrackPopup = TRUE;
|
|
ppopupMenuHierarchy->fFirstClick = fButtonDown;
|
|
ppopupMenuHierarchy->fRightButton = ((dwFlags & TPM_RIGHTBUTTON) != 0);
|
|
ppopupMenuHierarchy->fNoNotify = ((dwFlags & TPM_NONOTIFY) != 0);
|
|
|
|
if (fSync = (dwFlags & TPM_RETURNCMD))
|
|
ppopupMenuHierarchy->fSynchronous = TRUE;
|
|
|
|
ppopupMenuHierarchy->fIsSysMenu = ((dwFlags & TPM_SYSMENU) != 0);
|
|
|
|
// Set the GlobalPopupMenu variable so that EndMenu works for popupmenus so
|
|
// that WinWart II people can continue to abuse undocumented functions.
|
|
// This is nulled out in MNCancel.
|
|
/*
|
|
* This is actually needed for cleanup in case this thread ends
|
|
* execution before we can free the popup. (See xxxDestroyThreadInfo)
|
|
*
|
|
* Note that one thread might own pwndOwner and another one might call
|
|
* TrackPopupMenu (pretty normal if the two threads are attached). So
|
|
* properly setting (and initializing) pMenuState is a must here.
|
|
*/
|
|
pMenuState = MNAllocMenuState(ptiCurrent, ptiOwner, ppopupMenuHierarchy);
|
|
if (pMenuState == NULL) {
|
|
/*
|
|
* Get out. The app never knew we were here so don't notify it
|
|
*/
|
|
dwFlags |= TPM_NONOTIFY;
|
|
goto AbortTrackPopupMenuEx;
|
|
}
|
|
|
|
/*
|
|
* Notify the app we are entering menu mode. wParam is 1 since this is a
|
|
* TrackPopupMenu.
|
|
*/
|
|
|
|
if (!ppopupMenuHierarchy->fNoNotify)
|
|
xxxSendMessage(pwndOwner, WM_ENTERMENULOOP,
|
|
(ppopupMenuHierarchy->fIsSysMenu ? FALSE : TRUE), 0);
|
|
|
|
/*
|
|
* Send off the WM_INITMENU, set ourselves up for menu mode etc...
|
|
*/
|
|
if (!xxxMNStartState(ppopupMenuHierarchy, MOUSEHOLD)) {
|
|
/*
|
|
* ppopupMenuHierarchy has been destroyed already; let's bail
|
|
*/
|
|
goto AbortTrackPopupMenuEx;
|
|
}
|
|
|
|
if (!ppopupMenuHierarchy->fNoNotify) {
|
|
ThreadLock(ppopupMenuHierarchy->spwndNotify, &tlpwndT);
|
|
xxxSendMessage(ppopupMenuHierarchy->spwndNotify, WM_INITMENUPOPUP,
|
|
(DWORD)PtoHq(pMenu), MAKELONG(0, (ppopupMenuHierarchy->fIsSysMenu ? 1: 0)));
|
|
ThreadUnlock(&tlpwndT);
|
|
}
|
|
|
|
/*
|
|
* Size the menu window if needed...
|
|
*/
|
|
sizeHierarchy = xxxSendMessage(pwndHierarchy, MN_SIZEWINDOW, 1, 0);
|
|
|
|
if (!sizeHierarchy) {
|
|
|
|
AbortTrackPopupMenuEx:
|
|
/*
|
|
* Release the mouse capture we set when we called StartMenuState...
|
|
*/
|
|
xxxReleaseCapture();
|
|
|
|
/* Notify the app we have exited menu mode. wParam is 1 for real
|
|
* tracked popups, not sys menu. Check wFlags since ppopupHierarchy
|
|
* will be gone.
|
|
*/
|
|
if (!(dwFlags & TPM_NONOTIFY))
|
|
xxxSendMessage(pwndOwner, WM_EXITMENULOOP, ((dwFlags & TPM_SYSMENU) ?
|
|
FALSE : TRUE), 0L);
|
|
|
|
/*
|
|
* Make sure we return failure
|
|
*/
|
|
fSync = TRUE;
|
|
cmd = FALSE;
|
|
goto CleanupTrackPopupMenuEx;
|
|
}
|
|
|
|
//
|
|
// Setup popup window dimensions
|
|
//
|
|
cxPopup = LOWORD(sizeHierarchy) + 2*SYSMET(CXFIXEDFRAME);
|
|
cyPopup = HIWORD(sizeHierarchy) + 2*SYSMET(CYFIXEDFRAME);
|
|
|
|
//
|
|
// Horizontal alignment
|
|
//
|
|
if (dwFlags & TPM_RIGHTALIGN) {
|
|
#ifdef DEBUG
|
|
if (dwFlags & TPM_CENTERALIGN) {
|
|
RIPMSG0(RIP_WARNING, "TrackPopupMenuEx: TPM_CENTERALIGN ignored");
|
|
}
|
|
#endif // DEBUG
|
|
|
|
x -= cxPopup;
|
|
} else if (dwFlags & TPM_CENTERALIGN)
|
|
x -= (cxPopup / 2);
|
|
|
|
//
|
|
// Vertical alignment
|
|
//
|
|
if (dwFlags & TPM_BOTTOMALIGN) {
|
|
#ifdef DEBUG
|
|
if (dwFlags & TPM_VCENTERALIGN) {
|
|
RIPMSG0(RIP_WARNING, "TrackPopupMenuEx: TPM_VCENTERALIGN ignored");
|
|
}
|
|
#endif // DEBUG
|
|
|
|
y -= cyPopup;
|
|
} else if (dwFlags & TPM_VCENTERALIGN)
|
|
y -= (cyPopup / 2);
|
|
|
|
//
|
|
// Get coords to move to.
|
|
//
|
|
sizeHierarchy = FindBestPos(x, y, cxPopup, cyPopup,
|
|
#ifdef MEMPHIS_MENU_ANIMATION
|
|
((lpTpm != NULL) ? &rcExclude : NULL), dwFlags, ppopupMenuHierarchy);
|
|
#else // MEMPHIS_MENU_ANIMATION
|
|
((lpTpm != NULL) ? &rcExclude : NULL), dwFlags);
|
|
#endif // MEMPHIS_MENU_ANIMATION
|
|
xxxSetWindowPos(pwndHierarchy, PWND_TOP, LOWORD(sizeHierarchy),
|
|
HIWORD(sizeHierarchy), 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
|
|
xxxPlayEventSound(L"MenuPopup");
|
|
xxxShowWindow(pwndHierarchy, MAKELONG(SW_SHOWNOACTIVATE, gfAnimate));
|
|
|
|
|
|
//
|
|
// We need to return TRUE for compatibility w/ async TrackPopupMenu().
|
|
// It is conceivable that a menu ID could have ID 0, in which case just
|
|
// returning the cmd chosen would return FALSE instead of TRUE.
|
|
//
|
|
|
|
//
|
|
// If mouse is in client of popup, act like clicked down
|
|
//
|
|
pMenuState->fButtonDown = fButtonDown;
|
|
|
|
cmd = xxxMNLoop(ppopupMenuHierarchy, pMenuState, 0x7FFFFFFFL, FALSE);
|
|
|
|
CleanupTrackPopupMenuEx:
|
|
|
|
if (ThreadUnlock(&tlpwndHierarchy)) {
|
|
if (!TestWF(pwndHierarchy, WFDESTROYED)) {
|
|
xxxDestroyWindow(pwndHierarchy);
|
|
}
|
|
}
|
|
|
|
MNEndMenuState (TRUE);
|
|
|
|
#ifdef MEMPHIS_MENUS
|
|
if (fSaved)
|
|
ptiCurrent->pMenuState = pmnStateSaved;
|
|
#endif // MEMPHIS_MENUS
|
|
|
|
return(fSync ? cmd : TRUE);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* FindBestPos()
|
|
*
|
|
* Gets best point to move popup menu window to, given exclusion area and
|
|
* screen real estate. Note that for our purposes, we consider center
|
|
* alignment to be the same as left/top alignment.
|
|
*
|
|
* We try four possibilities if the original position fails. The order of
|
|
* these is determined by the alignment and "try" flags. Basically, we try
|
|
* to move the rectangle out of the exclusion area by sliding it horizontally
|
|
* or vertically without going offscreen. If we can't, then we know that
|
|
* sliding it in both dimensions will also fail. So we use the original
|
|
* point, clipping on screen.
|
|
*
|
|
* Take the example of a top-left justified popup, which should be moved
|
|
* horizontally before vertically. We'll try the original point. Then
|
|
* we'll try to left-justify with the right edge of the exclude rect. Then
|
|
* we'll try to top-justify with the bottom edge of the exclude rect. Then
|
|
* we'll try to right-justify with the left edge of the exclude rect. Then
|
|
* we'll try to bottom-justify with the top edge of the exclude rect.
|
|
* Finally, we'll use the original pos.
|
|
*
|
|
\***************************************************************************/
|
|
|
|
LONG FindBestPos(int x, int y, int cx, int cy, LPRECT prcExclude,
|
|
#ifdef MEMPHIS_MENU_ANIMATION
|
|
UINT wFlags, PPOPUPMENU ppopupmenu)
|
|
#else // MEMPHIS_MENU_ANIMATION
|
|
UINT wFlags)
|
|
#endif // MEMPHIS_MENU_ANIMATION
|
|
|
|
|
|
{
|
|
int iRect;
|
|
int iT;
|
|
UINT awRect[4];
|
|
POINT ptT;
|
|
RECT rcExclude;
|
|
|
|
//
|
|
// Clip our coords on screen first. We use the same algorithm to clip
|
|
// as in Win3.1 for dudes with no exclude rect.
|
|
//
|
|
|
|
if (prcExclude!=NULL)
|
|
{
|
|
// Clip exclude rect to screen!
|
|
CopyRect(&rcExclude, prcExclude);
|
|
IntersectRect(&rcExclude, &rcExclude, &gpDispInfo->rcScreen);
|
|
}
|
|
else
|
|
SetRect(&rcExclude, x, y, x, y);
|
|
|
|
#ifdef MEMPHIS_MENU_ANIMATION
|
|
ppopupmenu->iDropDir = PAS_OUT | PAS_RIGHT;
|
|
#endif // MEMPHIS_MENU_ANIMATION
|
|
|
|
if (x + cx > gpDispInfo->rcScreen.right) {
|
|
if (prcExclude!=NULL)
|
|
x = rcExclude.right;
|
|
|
|
x -= cx;
|
|
#ifdef MEMPHIS_MENU_ANIMATION
|
|
ppopupmenu->iDropDir = PAS_OUT | PAS_LEFT;
|
|
#endif // MEMPHIS_MENU_ANIMATION
|
|
}
|
|
x = max(x, 0);
|
|
|
|
y = max(0, min(y, gpDispInfo->rcScreen.bottom-cy));
|
|
//
|
|
// Try first point
|
|
//
|
|
if (TryRect(RECT_ORG, x, y, cx, cy, &rcExclude, &ptT))
|
|
goto FOUND;
|
|
|
|
//
|
|
// Sort possibilities. Get offset of horizontal rects.
|
|
//
|
|
iRect = (wFlags & TPM_VERTICAL) ? 2 : 0;
|
|
|
|
//
|
|
// Sort horizontally. Note that we treat TPM_CENTERALIGN like
|
|
// TPM_LEFTALIGN.
|
|
//
|
|
//
|
|
// If we're right-aligned, try to right-align on left side first.
|
|
// Otherwise, try to left-align on right side first.
|
|
//
|
|
iT = (wFlags & TPM_RIGHTALIGN) ? 0 : 2;
|
|
|
|
awRect[0 + iRect] = RECT_ONLEFT + iT;
|
|
awRect[1 + iRect] = RECT_ONRIGHT - iT;
|
|
|
|
//
|
|
// Sort vertically. Note that we treat TPM_VCENTERALIGN like
|
|
// TPM_TOPALIGN.
|
|
//
|
|
// If we're bottom-aligned, try to bottom-align with top of rect
|
|
// first. Otherwise, try to top-align with bottom of exclusion first.
|
|
//
|
|
iT = (wFlags & TPM_BOTTOMALIGN) ? 0 : 2;
|
|
|
|
awRect[2 - iRect] = RECT_ONTOP + iT;
|
|
awRect[3 - iRect] = RECT_ONBOTTOM - iT;
|
|
|
|
//
|
|
// Loop through sorted alternatives. Note that TryRect fails immediately
|
|
// if an exclusion coordinate is too close to screen edge.
|
|
//
|
|
|
|
for (iRect = 0; iRect < 4; iRect++) {
|
|
if (TryRect(awRect[iRect], x, y, cx, cy, &rcExclude, &ptT)) {
|
|
#ifdef MEMPHIS_MENU_ANIMATION
|
|
switch (awRect[iRect])
|
|
{
|
|
case RECT_ONTOP: ppopupmenu->iDropDir = PAS_OUT | PAS_UP; break;
|
|
case RECT_ONLEFT: ppopupmenu->iDropDir = PAS_OUT | PAS_LEFT; break;
|
|
case RECT_ONBOTTOM: ppopupmenu->iDropDir = PAS_OUT | PAS_DOWN; break;
|
|
case RECT_ONRIGHT: ppopupmenu->iDropDir = PAS_OUT | PAS_RIGHT; break;
|
|
}
|
|
#endif // MEMPHIS_MENU_ANIMATION
|
|
x = ptT.x;
|
|
y = ptT.y;
|
|
break;
|
|
}
|
|
}
|
|
|
|
FOUND:
|
|
return MAKELONG(x, y);
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* TryRect()
|
|
*
|
|
* Tries to fit rect on screen without covering exclusion area. Returns
|
|
* TRUE if success.
|
|
*
|
|
\***************************************************************************/
|
|
|
|
BOOL TryRect(UINT wRect, int x, int y, int cx, int cy, LPRECT prcExclude,
|
|
LPPOINT ppt)
|
|
{
|
|
RECT rcTry;
|
|
|
|
switch (wRect) {
|
|
case RECT_ONRIGHT:
|
|
x = prcExclude->right;
|
|
if (x + cx > gpDispInfo->rcScreen.right)
|
|
return FALSE;
|
|
break;
|
|
|
|
case RECT_ONBOTTOM:
|
|
y = prcExclude->bottom;
|
|
if (y + cy > gpDispInfo->rcScreen.bottom)
|
|
return FALSE;
|
|
break;
|
|
|
|
case RECT_ONLEFT:
|
|
x = prcExclude->left - cx;
|
|
if (x < 0)
|
|
return FALSE;
|
|
break;
|
|
|
|
case RECT_ONTOP:
|
|
y = prcExclude->top - cy;
|
|
if (y < 0)
|
|
return FALSE;
|
|
break;
|
|
|
|
//
|
|
// case RECT_ORG:
|
|
// NOP;
|
|
// break;
|
|
//
|
|
}
|
|
|
|
ppt->x = x;
|
|
ppt->y = y;
|
|
|
|
rcTry.left = x;
|
|
rcTry.top = y;
|
|
rcTry.right = x + cx;
|
|
rcTry.bottom = y + cy;
|
|
|
|
return(!IntersectRect(&rcTry, &rcTry, prcExclude));
|
|
}
|