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.
830 lines
25 KiB
830 lines
25 KiB
/**************************** Module Header ********************************\
|
|
* Module Name: mnstate.c
|
|
*
|
|
* Copyright (c) 1985 - 1999, Microsoft Corporation
|
|
*
|
|
* Menu State Routines
|
|
*
|
|
* History:
|
|
* 10-10-90 JimA Cleanup.
|
|
* 03-18-91 IanJa Windowrevalidation added
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
PMENU xxxGetInitMenuParam(PWND pwndMenu, BOOL *lpfSystem);
|
|
|
|
/***************************************************************************\
|
|
* MNPositionSysMenu
|
|
*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* Whoever positions the menu becomes the owner
|
|
*/
|
|
if (pwnd != pmenusys->spwndNotify) {
|
|
Lock(&pmenusys->spwndNotify, pwnd);
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
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
|
|
RIPMSG1(RIP_ERROR, "pmenu %#p has MF_POPUP set or 0 items", pmenusys);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNAllocPopup
|
|
*
|
|
\***************************************************************************/
|
|
PPOPUPMENU MNAllocPopup(
|
|
BOOL fForceAlloc)
|
|
{
|
|
PPOPUPMENU ppm;
|
|
|
|
if (!fForceAlloc && !TEST_PUDF(PUDF_POPUPINUSE)) {
|
|
SET_PUDF(PUDF_POPUPINUSE);
|
|
ppm = &gpopupMenu;
|
|
} else {
|
|
ppm = (PPOPUPMENU)UserAllocPoolWithQuota(sizeof(POPUPMENU), TAG_POPUPMENU);
|
|
}
|
|
|
|
if (ppm) {
|
|
RtlZeroMemory(ppm, sizeof(POPUPMENU));
|
|
}
|
|
|
|
return ppm;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNFreePopup
|
|
*
|
|
\***************************************************************************/
|
|
VOID MNFreePopup(
|
|
PPOPUPMENU ppopupmenu)
|
|
{
|
|
Validateppopupmenu(ppopupmenu);
|
|
|
|
if (IsRootPopupMenu(ppopupmenu)) {
|
|
MNFlushDestroyedPopups(ppopupmenu, TRUE);
|
|
}
|
|
|
|
if (ppopupmenu->spwndPopupMenu &&
|
|
GETFNID(ppopupmenu->spwndPopupMenu) == FNID_MENU &&
|
|
ppopupmenu != &gpopupMenu) {
|
|
((PMENUWND)ppopupmenu->spwndPopupMenu)->ppopupmenu = NULL;
|
|
}
|
|
|
|
Unlock(&ppopupmenu->spwndPopupMenu);
|
|
/*
|
|
* If spwndNextPopup is not NULL, we're breaking the chain and spwndNext
|
|
* won't get closed. I won't remove the unlock since it has always been
|
|
* there.
|
|
*/
|
|
UserAssert(ppopupmenu->spwndNextPopup == NULL);
|
|
Unlock(&ppopupmenu->spwndNextPopup);
|
|
|
|
Unlock(&ppopupmenu->spwndPrevPopup);
|
|
UnlockPopupMenu(ppopupmenu, &ppopupmenu->spmenu);
|
|
UnlockPopupMenu(ppopupmenu, &ppopupmenu->spmenuAlternate);
|
|
Unlock(&ppopupmenu->spwndNotify);
|
|
Unlock(&ppopupmenu->spwndActivePopup);
|
|
|
|
#if DBG
|
|
ppopupmenu->fFreed = TRUE;
|
|
#endif
|
|
|
|
if (ppopupmenu == &gpopupMenu) {
|
|
UserAssert(TEST_PUDF(PUDF_POPUPINUSE));
|
|
CLEAR_PUDF(PUDF_POPUPINUSE);
|
|
} 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.
|
|
*
|
|
* It returns TRUE if the menu state owner doesn't own the notification
|
|
* window (multiple threads involved).
|
|
*
|
|
* 05-21-96 GerardoB Created
|
|
\***************************************************************************/
|
|
BOOL MNEndMenuStateNotify(
|
|
PMENUSTATE pMenuState)
|
|
{
|
|
PTHREADINFO ptiNotify;
|
|
|
|
if (pMenuState->pGlobalPopupMenu->spwndNotify != NULL) {
|
|
ptiNotify = GETPTI(pMenuState->pGlobalPopupMenu->spwndNotify);
|
|
if (ptiNotify != pMenuState->ptiMenuStateOwner) {
|
|
/*
|
|
* Later5.0 GerardoB. xxxMNStartMenuState no longer allows this.
|
|
* This is dead code that I'll remove eventually
|
|
*/
|
|
UserAssert(ptiNotify == pMenuState->ptiMenuStateOwner);
|
|
|
|
UserAssert(ptiNotify->pMenuState == pMenuState);
|
|
UserAssert(pMenuState->pmnsPrev == NULL);
|
|
ptiNotify->pMenuState = NULL;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNEndMenuState
|
|
*
|
|
* 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 xxxMNEndMenuState(
|
|
BOOL fFreePopup)
|
|
{
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
PMENUSTATE pMenuState;
|
|
pMenuState = ptiCurrent->pMenuState;
|
|
UserAssert(ptiCurrent->pMenuState != NULL);
|
|
UserAssert(ptiCurrent == pMenuState->ptiMenuStateOwner);
|
|
|
|
/*
|
|
* If the menu is locked, someone doesn't want it to go just yet.
|
|
*/
|
|
if (pMenuState->dwLockCount != 0) {
|
|
RIPMSG1(RIP_WARNING, "xxxMNEndMenuState Locked:%#p", pMenuState);
|
|
return;
|
|
}
|
|
|
|
MNEndMenuStateNotify(pMenuState);
|
|
|
|
/*
|
|
* pMenuState->pGlobalPopupMenu could be NULL if xxxMNAllocMenuState failed
|
|
*/
|
|
if (pMenuState->pGlobalPopupMenu != NULL) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Unlock MFMWFP windows.
|
|
*/
|
|
UnlockMFMWFPWindow(&pMenuState->uButtonDownHitArea);
|
|
UnlockMFMWFPWindow(&pMenuState->uDraggingHitArea);
|
|
|
|
/*
|
|
* Restore the previous state, if any
|
|
*/
|
|
ptiCurrent->pMenuState = pMenuState->pmnsPrev;
|
|
|
|
/*
|
|
* This (modal) menu mode is off
|
|
*/
|
|
if (!pMenuState->fModelessMenu) {
|
|
DecSFWLockCount();
|
|
DBGDecModalMenuCount();
|
|
}
|
|
|
|
if (pMenuState->hbmAni != NULL) {
|
|
MNDestroyAnimationBitmap(pMenuState);
|
|
}
|
|
|
|
/*
|
|
* Free the menu state
|
|
*/
|
|
if (pMenuState == &gMenuState) {
|
|
UserAssert(TEST_PUDF(PUDF_MENUSTATEINUSE));
|
|
CLEAR_PUDF(PUDF_MENUSTATEINUSE);
|
|
GreSetDCOwner(gMenuState.hdcAni, OBJECT_OWNER_PUBLIC);
|
|
} else {
|
|
if (pMenuState->hdcAni != NULL) {
|
|
GreDeleteDC(pMenuState->hdcAni);
|
|
}
|
|
UserFreePool(pMenuState);
|
|
}
|
|
|
|
/*
|
|
* If returning to a modeless menu, make sure we have activation.
|
|
* If returning to a modal menu, make sure we have capture.
|
|
*/
|
|
if (ptiCurrent->pMenuState != NULL) {
|
|
if (ptiCurrent->pMenuState->fModelessMenu) {
|
|
xxxActivateThisWindow(ptiCurrent->pMenuState->pGlobalPopupMenu->spwndActivePopup,
|
|
0, 0);
|
|
} else {
|
|
xxxMNSetCapture(ptiCurrent->pMenuState->pGlobalPopupMenu);
|
|
}
|
|
}
|
|
|
|
#if DBG
|
|
/*
|
|
* No pti should point to this pMenuState anymore.
|
|
* If guModalMenuStateCount is zero, all pMenuState must be NULL or
|
|
* modeless.
|
|
*/
|
|
{
|
|
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 (guModalMenuStateCount == 0) {
|
|
UserAssert(ptiT->pMenuState == NULL || ptiT->pMenuState->fModelessMenu);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNCreateAnimationBitmap
|
|
*
|
|
\***************************************************************************/
|
|
BOOL MNCreateAnimationBitmap(
|
|
PMENUSTATE pMenuState,
|
|
UINT cx,
|
|
UINT cy)
|
|
{
|
|
HBITMAP hbm = GreCreateCompatibleBitmap(gpDispInfo->hdcScreen, cx, cy);
|
|
if (hbm == NULL) {
|
|
RIPMSG0(RIP_WARNING, "MNSetupAnimationBitmap: Failed to create hbmAni");
|
|
return FALSE;
|
|
}
|
|
|
|
#if DBG
|
|
if (pMenuState->hdcAni == NULL) {
|
|
RIPMSG0(RIP_WARNING, "MNCreateAnimationBitmap: hdcAni is NULL");
|
|
}
|
|
if (pMenuState->hbmAni != NULL) {
|
|
RIPMSG0(RIP_WARNING, "MNCreateAnimationBitmap: hbmAni already exists");
|
|
}
|
|
#endif // DBG
|
|
|
|
GreSelectBitmap(pMenuState->hdcAni, hbm);
|
|
pMenuState->hbmAni = hbm;
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNDestroyAnimationBitmap
|
|
*
|
|
\***************************************************************************/
|
|
VOID MNDestroyAnimationBitmap(
|
|
PMENUSTATE pMenuState)
|
|
{
|
|
GreSelectBitmap(pMenuState->hdcAni, GreGetStockObject(PRIV_STOCK_BITMAP));
|
|
UserVerify(GreDeleteObject(pMenuState->hbmAni));
|
|
pMenuState->hbmAni = NULL;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNSetupAnimationDC
|
|
*
|
|
* 9/20/96 GerardoB Created
|
|
\***************************************************************************/
|
|
BOOL MNSetupAnimationDC(
|
|
PMENUSTATE pMenuState)
|
|
{
|
|
pMenuState->hdcAni = GreCreateCompatibleDC(gpDispInfo->hdcScreen);
|
|
if (pMenuState->hdcAni == NULL) {
|
|
RIPMSG0(RIP_WARNING, "MNSetupAnimationDC: Failed to create hdcAnimate");
|
|
UserAssert(pMenuState != &gMenuState);
|
|
return FALSE;
|
|
}
|
|
GreSelectFont(pMenuState->hdcAni, ghMenuFont);
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxUnlockMenuState
|
|
*
|
|
* 11/24/96 GerardoB Created
|
|
\***************************************************************************/
|
|
BOOL xxxUnlockMenuState(
|
|
PMENUSTATE pMenuState)
|
|
{
|
|
UserAssert(pMenuState->dwLockCount != 0);
|
|
|
|
(pMenuState->dwLockCount)--;
|
|
if ((pMenuState->dwLockCount == 0) && ExitMenuLoop(pMenuState, pMenuState->pGlobalPopupMenu)) {
|
|
xxxMNEndMenuState(TRUE);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNAllocMenuState
|
|
*
|
|
* Allocates and initializes a pMenuState.
|
|
*
|
|
* 5-21-96 GerardoB Created
|
|
\***************************************************************************/
|
|
PMENUSTATE xxxMNAllocMenuState(
|
|
PTHREADINFO ptiCurrent,
|
|
PTHREADINFO ptiNotify,
|
|
PPOPUPMENU ppopupmenuRoot)
|
|
{
|
|
BOOL fAllocate;
|
|
PMENUSTATE pMenuState;
|
|
|
|
UserAssert(PtiCurrent() == ptiCurrent);
|
|
UserAssert(ptiCurrent->rpdesk == ptiNotify->rpdesk);
|
|
|
|
/*
|
|
* If gMenuState is already taken, allocate one.
|
|
*/
|
|
fAllocate = TEST_PUDF(PUDF_MENUSTATEINUSE);
|
|
if (fAllocate) {
|
|
pMenuState = (PMENUSTATE)UserAllocPoolWithQuota(sizeof(MENUSTATE), TAG_MENUSTATE);
|
|
if (pMenuState == NULL) {
|
|
return NULL;
|
|
}
|
|
} else {
|
|
/*
|
|
* Use chache global which already has the animation DC setup
|
|
*/
|
|
SET_PUDF(PUDF_MENUSTATEINUSE);
|
|
pMenuState = &gMenuState;
|
|
UserAssert(gMenuState.hdcAni != NULL);
|
|
GreSetDCOwner(gMenuState.hdcAni, OBJECT_OWNER_CURRENT);
|
|
}
|
|
|
|
/*
|
|
* Prevent anyone from changing the foreground while this menu is active
|
|
*/
|
|
IncSFWLockCount();
|
|
DBGIncModalMenuCount();
|
|
|
|
/*
|
|
* Initialize pMenuState.
|
|
* Animation DC stuff is already setup so don't zero init it.
|
|
*/
|
|
RtlZeroMemory(pMenuState, sizeof(MENUSTATE) - sizeof(MENUANIDC));
|
|
pMenuState->pGlobalPopupMenu = ppopupmenuRoot;
|
|
pMenuState->ptiMenuStateOwner = ptiCurrent;
|
|
|
|
/*
|
|
* Save previous state, if any. Then set new state.
|
|
*/
|
|
pMenuState->pmnsPrev = ptiCurrent->pMenuState;
|
|
ptiCurrent->pMenuState = pMenuState;
|
|
|
|
if (ptiNotify != ptiCurrent) {
|
|
UserAssert(ptiNotify->pMenuState == NULL);
|
|
ptiNotify->pMenuState = pMenuState;
|
|
}
|
|
|
|
/*
|
|
* If the menustate was allocated, set up animation stuff.
|
|
* This is done here because in case of failure, MNEndMenuState
|
|
* will find ptiCurrent->pMenuState properly.
|
|
*/
|
|
if (fAllocate) {
|
|
RtlZeroMemory((PBYTE)pMenuState + sizeof(MENUSTATE) -
|
|
sizeof(MENUANIDC), sizeof(MENUANIDC));
|
|
if (!MNSetupAnimationDC(pMenuState)) {
|
|
xxxMNEndMenuState(TRUE);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
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,
|
|
DWORD cmd,
|
|
LPARAM lParam)
|
|
{
|
|
PPOPUPMENU ppopupmenu;
|
|
PTHREADINFO ptiCurrent, ptiNotify;
|
|
PMENUSTATE pMenuState;
|
|
TL tlpwnd;
|
|
PWND pwndT;
|
|
|
|
CheckLock(pwnd);
|
|
|
|
/*
|
|
* Bail if the current thread is already in menu mode
|
|
*/
|
|
ptiCurrent = PtiCurrent();
|
|
if (ptiCurrent->pMenuState != NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* If pwnd is not owned by ptiCurrent, the _PostMessage call below might
|
|
* send us in a loop
|
|
*/
|
|
UserAssert(ptiCurrent == GETPTI(pwnd));
|
|
|
|
/*
|
|
* If this is not a child window, use the active window on its queue
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* If the notification window is owned by another thread,
|
|
* then the menu loop wouldn't get any keyboard or mouse
|
|
* messages because we set capture to the notification window.
|
|
* So we pass the WM_SYSCOMMAND to that thread and bail
|
|
*/
|
|
if (ptiNotify != ptiCurrent) {
|
|
RIPMSG2(RIP_WARNING, "Passing WM_SYSCOMMAND SC_*MENU from thread %#p to %#p", ptiCurrent, ptiNotify);
|
|
_PostMessage(pwnd, WM_SYSCOMMAND, cmd, lParam);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Allocate ppoupmenu and pMenuState.
|
|
*/
|
|
ppopupmenu = MNAllocPopup(FALSE);
|
|
if (ppopupmenu == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
pMenuState = xxxMNAllocMenuState(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;
|
|
|
|
pwndT = pwnd;
|
|
while(TestwndChild(pwndT))
|
|
pwndT = pwndT->spwndParent;
|
|
|
|
if (pwndT->spmenu) {
|
|
ppopupmenu->fRtoL = TestMF(pwndT->spmenu, MFRTL) ?TRUE:FALSE;
|
|
} else {
|
|
//
|
|
// No way to know, no menu, but there is a system menu. Thus arrow
|
|
// keys are really not important. However lets take the next best
|
|
// thing just to be safe.
|
|
//
|
|
ppopupmenu->fRtoL = TestWF(pwnd, WEFRTLREADING) ?TRUE :FALSE;
|
|
}
|
|
|
|
/*
|
|
* 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, 0);
|
|
ThreadUnlock(&tlpwnd);
|
|
|
|
return pMenuState;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxMNStartMenu
|
|
*
|
|
* 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 xxxMNStartMenu(
|
|
PPOPUPMENU ppopupmenu,
|
|
int mn)
|
|
{
|
|
PWND pwndMenu;
|
|
PMENU pMenu;
|
|
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, "xxxMNStartMenu: pMenuState == NULL");
|
|
ThreadUnlock(&tlpwndMenu);
|
|
return FALSE;
|
|
}
|
|
pMenuState->mnFocus = mn;
|
|
pMenuState->fMenuStarted = TRUE;
|
|
pMenuState->fButtonDown =
|
|
pMenuState->fButtonAlwaysDown = ((_GetKeyState(VK_LBUTTON) & 0x8000) != 0);
|
|
|
|
xxxMNSetCapture(ppopupmenu);
|
|
|
|
xxxSendMessage(pwndMenu, WM_SETCURSOR, (WPARAM)HWq(pwndMenu),
|
|
MAKELONG(MSGF_MENU, 0));
|
|
|
|
if (ppopupmenu->fIsMenuBar) {
|
|
BOOL fSystemMenu;
|
|
|
|
pMenu = xxxGetInitMenuParam(pwndMenu, &fSystemMenu);
|
|
|
|
if (pMenu == NULL) {
|
|
pMenuState->fMenuStarted = FALSE;
|
|
xxxMNReleaseCapture();
|
|
ThreadUnlock(&tlpwndMenu);
|
|
return FALSE;
|
|
}
|
|
|
|
LockPopupMenu(ppopupmenu, &ppopupmenu->spmenu, pMenu);
|
|
|
|
ppopupmenu->fIsSysMenu = (fSystemMenu != 0);
|
|
if (!fSystemMenu) {
|
|
pMenu = xxxGetSysMenu(pwndMenu, FALSE);
|
|
LockPopupMenu(ppopupmenu, &ppopupmenu->spmenuAlternate, pMenu);
|
|
}
|
|
}
|
|
|
|
pMenuState->fIsSysMenu = (ppopupmenu->fIsSysMenu != 0);
|
|
|
|
if (!ppopupmenu->fNoNotify) {
|
|
|
|
if (ppopupmenu->fIsTrackPopup && ppopupmenu->fIsSysMenu) {
|
|
pMenu = xxxGetInitMenuParam(pwndMenu, NULL);
|
|
} else {
|
|
pMenu = ppopupmenu->spmenu;
|
|
}
|
|
|
|
xxxSendMessage(pwndMenu, WM_INITMENU, (WPARAM)PtoH(pMenu), 0L);
|
|
}
|
|
|
|
if (!ppopupmenu->fIsTrackPopup) {
|
|
if (ppopupmenu->fIsSysMenu) {
|
|
MNPositionSysMenu(pwndMenu, ppopupmenu->spmenu);
|
|
} else if (ppopupmenu->fIsMenuBar) {
|
|
ThreadLockMenuNoModify(ppopupmenu->spmenu, &tlpMenu);
|
|
xxxMNRecomputeBarIfNeeded(pwndMenu, ppopupmenu->spmenu);
|
|
ThreadUnlockMenuNoModify(&tlpMenu);
|
|
MNPositionSysMenu(pwndMenu, ppopupmenu->spmenuAlternate);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If returning TRUE, set menu style in pMenuState
|
|
*/
|
|
if (!ppopupmenu->fDestroyed) {
|
|
if (TestMF(ppopupmenu->spmenu, MNS_MODELESS)) {
|
|
pMenuState->fModelessMenu = TRUE;
|
|
}
|
|
|
|
if (TestMF(ppopupmenu->spmenu, MNS_DRAGDROP)) {
|
|
if (NT_SUCCESS(xxxClientLoadOLE())) {
|
|
pMenuState->fDragAndDrop = TRUE;
|
|
}
|
|
}
|
|
|
|
if (TestMF(ppopupmenu->spmenu, MNS_AUTODISMISS)) {
|
|
pMenuState->fAutoDismiss = TRUE;
|
|
}
|
|
|
|
if (TestMF(ppopupmenu->spmenu, MNS_NOTIFYBYPOS)) {
|
|
pMenuState->fNotifyByPos = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Bogus! We don't always know that this is the system menu. We
|
|
* will frequently pass on an OBJID_MENU even when you hit Alt+Space.
|
|
*
|
|
* Hence, MNSwitchToAlternate will send a EVENT_SYSTEM_MENUEND for the
|
|
* menu bar and an EVENT_SYSTEM_MENUSTART for the sysmenu when we "switch".
|
|
*/
|
|
xxxWindowEvent(EVENT_SYSTEM_MENUSTART, pwndMenu,
|
|
(ppopupmenu->fIsSysMenu ? OBJID_SYSMENU : (ppopupmenu->fIsMenuBar ? OBJID_MENU : OBJID_WINDOW)),
|
|
INDEXID_CONTAINER, 0);
|
|
|
|
ThreadUnlock(&tlpwndMenu);
|
|
|
|
return !ppopupmenu->fDestroyed;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxGetInitMenuParam
|
|
*
|
|
* Gets the HMENU sent as the wParam of WM_INITMENU, and for menu bars, is
|
|
* the actual menu to be interacted with.
|
|
*
|
|
* History:
|
|
* ????
|
|
\***************************************************************************/
|
|
PMENU xxxGetInitMenuParam(
|
|
PWND pwndMenu,
|
|
BOOL *lpfSystem)
|
|
{
|
|
//
|
|
// Find out what menu we should be sending in WM_INITMENU:
|
|
// If minimized/child/empty menubar, use system menu
|
|
//
|
|
CheckLock(pwndMenu);
|
|
|
|
if (TestWF(pwndMenu, WFMINIMIZED) ||
|
|
TestwndChild(pwndMenu) ||
|
|
(pwndMenu->spmenu == NULL) ||
|
|
!pwndMenu->spmenu->cItems) {
|
|
if (lpfSystem != NULL)
|
|
*lpfSystem = TRUE;
|
|
|
|
return xxxGetSysMenu(pwndMenu, FALSE);
|
|
} else {
|
|
if (lpfSystem != NULL) {
|
|
*lpfSystem = FALSE;
|
|
}
|
|
|
|
return pwndMenu->spmenu;
|
|
}
|
|
}
|