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.
602 lines
17 KiB
602 lines
17 KiB
/**************************** Module Header ********************************\
|
|
* Module Name: mnstate.c
|
|
*
|
|
* Copyright 1985-90, Microsoft Corporation
|
|
*
|
|
* Menu State Routines
|
|
*
|
|
* History:
|
|
* 10-10-90 JimA Cleanup.
|
|
* 03-18-91 IanJa Windowrevalidation added
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
PMENU GetInitMenuParam(PWND pwndMenu, BOOL * lpfSystem);
|
|
/***************************************************************************\
|
|
* void PositionSysMenu(pwnd, hSysMenu)
|
|
*
|
|
*
|
|
* History:
|
|
* 4-25-91 Mikehar Port for 3.1 merge
|
|
\***************************************************************************/
|
|
|
|
void MNPositionSysMenu(
|
|
PWND pwnd,
|
|
PMENU pmenusys)
|
|
{
|
|
RECT rc;
|
|
PITEM pItem;
|
|
|
|
if (pmenusys == NULL) {
|
|
RIPERR0(ERROR_INVALID_HANDLE,
|
|
RIP_WARNING,
|
|
"Invalid menu handle pmenusys (NULL) to MNPositionSysMenu");
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Setup the SysMenu hit rectangle.
|
|
*/
|
|
rc.top = rc.left = 0;
|
|
|
|
if (TestWF(pwnd, WEFTOOLWINDOW)) {
|
|
rc.right = SYSMET(CXSMSIZE);
|
|
rc.bottom = SYSMET(CYSMSIZE);
|
|
} else {
|
|
rc.right = SYSMET(CXSIZE);
|
|
rc.bottom = SYSMET(CYSIZE);
|
|
}
|
|
|
|
if (!TestWF(pwnd, WFMINIMIZED)) {
|
|
int cBorders;
|
|
|
|
cBorders = GetWindowBorders(pwnd->style, pwnd->ExStyle, TRUE, FALSE);
|
|
OffsetRect(&rc, cBorders*SYSMET(CXBORDER), cBorders*SYSMET(CYBORDER));
|
|
}
|
|
|
|
/*
|
|
* Offset the System popup menu.
|
|
*/
|
|
Lock(&pmenusys->spwndNotify, pwnd);
|
|
|
|
if (!TestMF(pmenusys, MF_POPUP) && (pmenusys->cItems > 0)) {
|
|
pItem = pmenusys->rgItems;
|
|
if (pItem) {
|
|
pItem->yItem = rc.top;
|
|
pItem->xItem = rc.left;
|
|
pItem->cyItem = rc.bottom - rc.top;
|
|
pItem->cxItem = rc.right - rc.left;
|
|
}
|
|
}
|
|
else
|
|
// BOGUS -- MF_POPUP should never be set on a MENU -- only a MENU ITEM
|
|
UserAssert(FALSE);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNFlushDestroyedPopups
|
|
*
|
|
* Walk the ppmDelayedFree list freeing those marked as destroyed.
|
|
*
|
|
* 05-14-96 GerardoB Created
|
|
\***************************************************************************/
|
|
void MNFlushDestroyedPopups (PPOPUPMENU ppopupmenu, BOOL fUnlock)
|
|
{
|
|
PPOPUPMENU ppmDestroyed, ppmFree;
|
|
|
|
UserAssert(IsRootPopupMenu(ppopupmenu));
|
|
|
|
/*
|
|
* Walk ppmDelayedFree
|
|
*/
|
|
ppmDestroyed = ppopupmenu;
|
|
while (ppmDestroyed->ppmDelayedFree != NULL) {
|
|
/*
|
|
* If it's marked as destroyed, unlink it and free it
|
|
*/
|
|
if (ppmDestroyed->ppmDelayedFree->fDestroyed) {
|
|
ppmFree = ppmDestroyed->ppmDelayedFree;
|
|
ppmDestroyed->ppmDelayedFree = ppmFree->ppmDelayedFree;
|
|
UserAssert(ppmFree != ppmFree->ppopupmenuRoot);
|
|
MNFreePopup(ppmFree);
|
|
} else {
|
|
/*
|
|
* fUnlock is TRUE if the root popup is being destroyed; if
|
|
* so, reset fDelayedFree and unlink it
|
|
*/
|
|
if (fUnlock) {
|
|
/*
|
|
* This means that the root popup is going away before
|
|
* some of the hierarchical popups have been destroyed.
|
|
* This can happen if someone destroys one of the menu
|
|
* windows breaking the spwndNextPopup chain.
|
|
*/
|
|
ppmDestroyed->ppmDelayedFree->fDelayedFree = FALSE;
|
|
/*
|
|
* Stop here so we can figure how this happened.
|
|
*/
|
|
UserAssert(ppmDestroyed->ppmDelayedFree->fDelayedFree);
|
|
ppmDestroyed->ppmDelayedFree = ppmDestroyed->ppmDelayedFree->ppmDelayedFree;
|
|
} else {
|
|
/*
|
|
* Not fDestroyed so move to the next one.
|
|
*/
|
|
ppmDestroyed = ppmDestroyed->ppmDelayedFree;
|
|
} /* fUnlock */
|
|
} /* fDestroyed */
|
|
} /* while ppmDelayedFree */
|
|
|
|
}
|
|
/***************************************************************************\
|
|
*
|
|
* Preallocated popupmenu structure -- this allows us to ensure that
|
|
* there is memory to pull down a menu, even when USER's heap is full.
|
|
*
|
|
* History:
|
|
* 10-Mar-1992 mikeke
|
|
\***************************************************************************/
|
|
|
|
static POPUPMENU gpopupMenu;
|
|
static BOOL gfPopupInUse = FALSE;
|
|
static MENUSTATE gpMenuState;
|
|
static BOOL gfMenuStateInUse = FALSE;
|
|
|
|
/***************************************************************************\
|
|
* MNAllocPopup
|
|
*
|
|
\***************************************************************************/
|
|
PPOPUPMENU MNAllocPopup(BOOL fForceAlloc)
|
|
{
|
|
PPOPUPMENU ppm;
|
|
|
|
if (!fForceAlloc && !gfPopupInUse) {
|
|
gfPopupInUse = TRUE;
|
|
|
|
ppm = &gpopupMenu;
|
|
} else {
|
|
ppm = (PPOPUPMENU)UserAllocPoolWithQuota(sizeof(POPUPMENU), TAG_POPUPMENU);
|
|
}
|
|
|
|
if (ppm) {
|
|
RtlZeroMemory(ppm, sizeof(POPUPMENU));
|
|
}
|
|
|
|
return (ppm);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNFreePopup
|
|
*
|
|
\***************************************************************************/
|
|
VOID MNFreePopup(
|
|
PPOPUPMENU ppopupmenu)
|
|
{
|
|
PMENU pmenu;
|
|
PMENU pmenuSys;
|
|
|
|
#ifdef DEBUG
|
|
Validateppopupmenu(ppopupmenu);
|
|
#endif
|
|
|
|
if (IsRootPopupMenu(ppopupmenu)) {
|
|
MNFlushDestroyedPopups (ppopupmenu, TRUE);
|
|
}
|
|
|
|
/*
|
|
* This app is finished using the global system menu: unlock any objects
|
|
* it is using!
|
|
*
|
|
* NOTE: This global system menu thing doesn't work: two apps can use
|
|
* it at the same time: which would be a disasterous bug!
|
|
*/
|
|
if (ppopupmenu->spwndNotify != NULL) {
|
|
pmenuSys = ppopupmenu->spwndNotify->head.rpdesk->spmenuSys;
|
|
if (pmenuSys != NULL) {
|
|
Unlock(&pmenuSys->spwndNotify);
|
|
if ((pmenu = _GetSubMenu(pmenuSys, 0)) != NULL)
|
|
Unlock(&pmenu->spwndNotify);
|
|
}
|
|
|
|
Unlock(&ppopupmenu->spwndNotify);
|
|
}
|
|
|
|
Unlock(&ppopupmenu->spwndPopupMenu);
|
|
|
|
Unlock(&ppopupmenu->spwndNextPopup);
|
|
Unlock(&ppopupmenu->spwndPrevPopup);
|
|
Unlock(&ppopupmenu->spmenu);
|
|
Unlock(&ppopupmenu->spmenuAlternate);
|
|
Unlock(&ppopupmenu->spwndActivePopup);
|
|
|
|
#ifdef DEBUG
|
|
ppopupmenu->fFreed = TRUE;
|
|
#endif
|
|
|
|
if (ppopupmenu == &gpopupMenu) {
|
|
UserAssert(gfPopupInUse);
|
|
gfPopupInUse = FALSE;
|
|
} else {
|
|
UserFreePool(ppopupmenu);
|
|
}
|
|
}
|
|
/***************************************************************************\
|
|
* MNEndMenuStateNotify
|
|
*
|
|
* spwndNotify might have been created by a thread other than the one
|
|
* the menu mode is running on. If this is the case, this function
|
|
* NULLs out pMenuState for the thread that owns spwndNotify.
|
|
*
|
|
* 05-21-96 GerardoB Created
|
|
\***************************************************************************/
|
|
void MNEndMenuStateNotify (PMENUSTATE pMenuState)
|
|
{
|
|
PTHREADINFO ptiNotify;
|
|
|
|
if (pMenuState->pGlobalPopupMenu->spwndNotify != NULL) {
|
|
ptiNotify = GETPTI(pMenuState->pGlobalPopupMenu->spwndNotify);
|
|
if (ptiNotify != pMenuState->ptiMenuStateOwner) {
|
|
UserAssert(ptiNotify->pMenuState == pMenuState);
|
|
ptiNotify->pMenuState = NULL;
|
|
}
|
|
}
|
|
}
|
|
/***************************************************************************\
|
|
* MNEndMenuState
|
|
*
|
|
* This funtion must be called to clean up pMenuState after getting out
|
|
* of menu mode. It must be called by the same thread that initialized
|
|
* pMenuState either manually or by calling xxxMNStartMenuState.
|
|
*
|
|
* 05-20-96 GerardoB Created
|
|
\***************************************************************************/
|
|
void MNEndMenuState (BOOL fFreePopup)
|
|
{
|
|
PTHREADINFO ptiCurrent;
|
|
PMENUSTATE pMenuState;
|
|
|
|
ptiCurrent = PtiCurrent();
|
|
pMenuState = ptiCurrent->pMenuState;
|
|
UserAssert(ptiCurrent->pMenuState != NULL);
|
|
UserAssert(ptiCurrent == pMenuState->ptiMenuStateOwner);
|
|
|
|
MNEndMenuStateNotify(pMenuState);
|
|
|
|
if (fFreePopup) {
|
|
UserAssert(pMenuState->pGlobalPopupMenu->fIsMenuBar || pMenuState->pGlobalPopupMenu->fDestroyed);
|
|
MNFreePopup(pMenuState->pGlobalPopupMenu);
|
|
} else {
|
|
/*
|
|
* This means that we're ending the menustate but the popup menu
|
|
* window is still around. This can happen when called from
|
|
* xxxDestroyThreadInfo.
|
|
*/
|
|
UserAssert(pMenuState->pGlobalPopupMenu->fIsTrackPopup);
|
|
pMenuState->pGlobalPopupMenu->fDelayedFree = FALSE;
|
|
}
|
|
|
|
if (pMenuState == &gpMenuState) {
|
|
UserAssert(gfMenuStateInUse);
|
|
gfMenuStateInUse = FALSE;
|
|
} else {
|
|
/*
|
|
* Don't use UserFreePool so the debug code below will work right
|
|
*/
|
|
ExFreePool(pMenuState);
|
|
}
|
|
ptiCurrent->pMenuState = NULL;
|
|
|
|
/*
|
|
* This menu mode is off
|
|
*/
|
|
UserAssert(guMenuStateCount != 0);
|
|
guMenuStateCount--;
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
/*
|
|
* No pti should point to this pMenuState anymore
|
|
* If guMenuStateCount is zero, all pMenuState must be NULL
|
|
*/
|
|
PLIST_ENTRY pHead, pEntry;
|
|
PTHREADINFO ptiT;
|
|
|
|
pHead = &(ptiCurrent->rpdesk->PtiList);
|
|
for (pEntry = pHead->Flink; pEntry != pHead; pEntry = pEntry->Flink) {
|
|
ptiT = CONTAINING_RECORD(pEntry, THREADINFO, PtiLink);
|
|
UserAssert(ptiT->pMenuState != pMenuState);
|
|
if (guMenuStateCount == 0) {
|
|
UserAssert(ptiT->pMenuState == NULL);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
/***************************************************************************\
|
|
* MNAllocMenuState
|
|
*
|
|
* Allocates and initializes a pMenuState
|
|
*
|
|
* 5-21-96 GerardoB Created
|
|
\***************************************************************************/
|
|
PMENUSTATE MNAllocMenuState(PTHREADINFO ptiCurrent, PTHREADINFO ptiNotify, PPOPUPMENU ppopupmenuRoot)
|
|
{
|
|
PMENUSTATE pMenuState;
|
|
|
|
UserAssert(PtiCurrent() == ptiCurrent);
|
|
UserAssert(ptiCurrent->rpdesk == ptiNotify->rpdesk);
|
|
|
|
/*
|
|
* If gpMenuState is already taken, allocate one.
|
|
*/
|
|
if (gfMenuStateInUse) {
|
|
pMenuState = (PMENUSTATE)UserAllocPoolWithQuota(sizeof(MENUSTATE), TAG_MENUSTATE);
|
|
if (pMenuState == NULL) {
|
|
return NULL;
|
|
}
|
|
} else {
|
|
gfMenuStateInUse = TRUE;
|
|
pMenuState = &gpMenuState;
|
|
}
|
|
|
|
/*
|
|
* This is used by IsSomeOneInMenuMode and for debugging purposes
|
|
*/
|
|
guMenuStateCount++;
|
|
|
|
/*
|
|
* Initialize pMenuState.
|
|
*/
|
|
RtlZeroMemory(pMenuState, sizeof(*pMenuState));
|
|
pMenuState->pGlobalPopupMenu = ppopupmenuRoot;
|
|
pMenuState->ptiMenuStateOwner = ptiCurrent;
|
|
UserAssert(ptiCurrent->pMenuState == NULL);
|
|
ptiCurrent->pMenuState = pMenuState;
|
|
if (ptiNotify != ptiCurrent) {
|
|
UserAssert(ptiNotify->pMenuState == NULL);
|
|
ptiNotify->pMenuState = pMenuState;
|
|
}
|
|
|
|
|
|
return pMenuState;
|
|
|
|
}
|
|
/***************************************************************************\
|
|
* xxxMNStartMenuState
|
|
*
|
|
* This function is called when the menu bar is about to be activated (the
|
|
* app's main menu). It makes sure that the threads involved are not in
|
|
* menu mode already, finds the owner/notification window, initializes
|
|
* pMenuState and sends the WM_ENTERMENULOOP message.
|
|
* It successful, it returns a pointer to a pMenuState. If so, the caller
|
|
* must call MNEndMenuState when done.
|
|
*
|
|
* History:
|
|
* 4-25-91 Mikehar Port for 3.1 merge
|
|
* 5-20-96 GerardoB Renamed and changed (Old name: xxxMNGetPopup)
|
|
\***************************************************************************/
|
|
PMENUSTATE xxxMNStartMenuState(PWND pwnd)
|
|
{
|
|
PPOPUPMENU ppopupmenu;
|
|
PTHREADINFO ptiCurrent, ptiNotify;
|
|
PMENUSTATE pMenuState;
|
|
TL tlpwnd;
|
|
|
|
CheckLock(pwnd);
|
|
|
|
/*
|
|
* Bail if the current thread is already in menu mode
|
|
*/
|
|
ptiCurrent = PtiCurrent();
|
|
if (ptiCurrent->pMenuState != NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* If the window doesn't have any children, return pwndActive.
|
|
*/
|
|
if (!TestwndChild(pwnd)) {
|
|
pwnd = GETPTI(pwnd)->pq->spwndActive;
|
|
} else {
|
|
|
|
/*
|
|
* Search up the parents for a window with a System Menu.
|
|
*/
|
|
while (TestwndChild(pwnd)) {
|
|
if (TestWF(pwnd, WFSYSMENU))
|
|
break;
|
|
pwnd = pwnd->spwndParent;
|
|
}
|
|
}
|
|
|
|
if (pwnd == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!TestwndChild(pwnd) && (pwnd->spmenu != NULL)) {
|
|
goto hasmenu;
|
|
}
|
|
|
|
if (!TestWF(pwnd, WFSYSMENU)) {
|
|
return NULL;
|
|
}
|
|
|
|
hasmenu:
|
|
/*
|
|
* If the owner/notification window was created by another thread,
|
|
* make sure that it's not in menu mode already
|
|
* This can happen if PtiCurrent() is attached to other threads, one of
|
|
* which created pwnd.
|
|
*/
|
|
ptiNotify = GETPTI(pwnd);
|
|
if (ptiNotify->pMenuState != NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Allocate ppoupmenu and pMenuState
|
|
*/
|
|
ppopupmenu = MNAllocPopup(FALSE);
|
|
if (ppopupmenu == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
pMenuState = MNAllocMenuState(ptiCurrent, ptiNotify, ppopupmenu);
|
|
if (pMenuState == NULL) {
|
|
MNFreePopup(ppopupmenu);
|
|
return NULL;
|
|
}
|
|
|
|
ppopupmenu->fIsMenuBar = TRUE;
|
|
ppopupmenu->fHasMenuBar = TRUE;
|
|
Lock(&(ppopupmenu->spwndNotify), pwnd);
|
|
ppopupmenu->posSelectedItem = MFMWFP_NOITEM;
|
|
Lock(&(ppopupmenu->spwndPopupMenu), pwnd);
|
|
ppopupmenu->ppopupmenuRoot = ppopupmenu;
|
|
|
|
/*
|
|
* Notify the app we are entering menu mode. wParam is always 0 since this
|
|
* procedure will only be called for menu bar menus not TrackPopupMenu
|
|
* menus.
|
|
*/
|
|
ThreadLockAlways(pwnd, &tlpwnd);
|
|
xxxSendMessage(pwnd, WM_ENTERMENULOOP, 0, 0L);
|
|
ThreadUnlock(&tlpwnd);
|
|
|
|
return pMenuState;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxStartMenuState
|
|
*
|
|
* Note that this function calls back many times so we might be forced
|
|
* out of menu mode at any time. We don't want to check this after
|
|
* each callback so we lock what we need and go on. Be careful.
|
|
*
|
|
* History:
|
|
* 4-25-91 Mikehar Port for 3.1 merge
|
|
\***************************************************************************/
|
|
|
|
BOOL xxxMNStartState(
|
|
PPOPUPMENU ppopupmenu,
|
|
int mn)
|
|
{
|
|
PWND pwndMenu;
|
|
PMENUSTATE pMenuState;
|
|
TL tlpwndMenu;
|
|
TL tlpmenu;
|
|
|
|
UserAssert(IsRootPopupMenu(ppopupmenu));
|
|
|
|
if (ppopupmenu->fDestroyed) {
|
|
return FALSE;
|
|
}
|
|
|
|
pwndMenu = ppopupmenu->spwndNotify;
|
|
ThreadLock(pwndMenu, &tlpwndMenu);
|
|
|
|
pMenuState = GetpMenuState(pwndMenu);
|
|
if (pMenuState == NULL) {
|
|
RIPMSG0(RIP_ERROR, "xxxMNStartState: pMenuState == NULL");
|
|
return FALSE;
|
|
}
|
|
pMenuState->mnFocus = mn;
|
|
pMenuState->fMenuStarted = TRUE;
|
|
pMenuState->fButtonDown = FALSE;
|
|
|
|
/*
|
|
* Lotus Freelance demo programs depend on GetCapture returning their hwnd
|
|
* when in menumode.
|
|
*/
|
|
xxxCapture(PtiCurrent(), ppopupmenu->spwndNotify, SCREEN_CAPTURE);
|
|
xxxSendMessage(pwndMenu, WM_SETCURSOR, (DWORD)HWq(pwndMenu),
|
|
MAKELONG(MSGF_MENU, 0));
|
|
|
|
if (ppopupmenu->fIsMenuBar) {
|
|
BOOL fSystemMenu;
|
|
|
|
PMENU pMenu;
|
|
|
|
pMenu = GetInitMenuParam(pwndMenu, &fSystemMenu);
|
|
|
|
if (pMenu == NULL)
|
|
{
|
|
pMenuState->fMenuStarted = FALSE;
|
|
xxxSetCapture(NULL);
|
|
ThreadUnlock(&tlpwndMenu);
|
|
return(FALSE);
|
|
}
|
|
|
|
Lock(&(ppopupmenu->spmenu), pMenu);
|
|
|
|
ppopupmenu->fIsSysMenu = (fSystemMenu != 0);
|
|
if (!fSystemMenu)
|
|
Lock(&(ppopupmenu->spmenuAlternate), GetSysMenu(pwndMenu, FALSE));
|
|
}
|
|
|
|
pMenuState->fIsSysMenu = (ppopupmenu->fIsSysMenu != 0);
|
|
|
|
if (!ppopupmenu->fNoNotify) {
|
|
PMENU pMenu;
|
|
|
|
if (ppopupmenu->fIsTrackPopup && ppopupmenu->fIsSysMenu)
|
|
pMenu = GetInitMenuParam(pwndMenu, NULL);
|
|
else
|
|
pMenu = ppopupmenu->spmenu;
|
|
|
|
xxxSendMessage(pwndMenu, WM_INITMENU, (DWORD)PtoH(pMenu), 0L);
|
|
}
|
|
|
|
if (!ppopupmenu->fIsTrackPopup)
|
|
{
|
|
if (ppopupmenu->fIsSysMenu) {
|
|
MNPositionSysMenu(pwndMenu, ppopupmenu->spmenu);
|
|
} else if (ppopupmenu->fIsMenuBar)
|
|
{
|
|
ThreadLock(ppopupmenu->spmenu, &tlpmenu);
|
|
xxxMNRecomputeBarIfNeeded(pwndMenu, ppopupmenu->spmenu);
|
|
ThreadUnlock(&tlpmenu);
|
|
MNPositionSysMenu(pwndMenu, ppopupmenu->spmenuAlternate);
|
|
}
|
|
}
|
|
|
|
ThreadUnlock(&tlpwndMenu);
|
|
|
|
return !ppopupmenu->fDestroyed;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// GetInitMenuParam()
|
|
//
|
|
// Gets the HMENU sent as the wParam of WM_INITMENU, and for menu bars, is
|
|
// the actual menu to be interacted with.
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
PMENU GetInitMenuParam(PWND pwndMenu, BOOL * lpfSystem)
|
|
{
|
|
//
|
|
// Find out what menu we should be sending in WM_INITMENU:
|
|
// If minimized/child/empty menubar, use system menu
|
|
//
|
|
if (TestWF(pwndMenu, WFMINIMIZED) ||
|
|
TestwndChild(pwndMenu) ||
|
|
(pwndMenu->spmenu == NULL) ||
|
|
!pwndMenu->spmenu->cItems)
|
|
{
|
|
if (lpfSystem != NULL)
|
|
*lpfSystem = TRUE;
|
|
|
|
return(GetSysMenu(pwndMenu, FALSE));
|
|
}
|
|
else
|
|
{
|
|
if (lpfSystem != NULL)
|
|
*lpfSystem = FALSE;
|
|
|
|
return(pwndMenu->spmenu);
|
|
}
|
|
}
|