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.
4806 lines
153 KiB
4806 lines
153 KiB
/**************************** Module Header ********************************\
|
|
* Module Name: menu.c
|
|
*
|
|
* Copyright (c) 1985 - 1999, Microsoft Corporation
|
|
*
|
|
* Keyboard Accelerator Routines
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
|
|
/***********************************************************************\
|
|
* MNGetpItemIndex
|
|
*
|
|
* 11/19/96 GerardoB Created
|
|
\***********************************************************************/
|
|
#if DBG
|
|
UINT DBGMNGetpItemIndex(
|
|
PMENU pmenu,
|
|
PITEM pitem)
|
|
{
|
|
UINT uiPos;
|
|
|
|
UserAssert((ULONG_PTR)pitem >= (ULONG_PTR)pmenu->rgItems);
|
|
uiPos = _MNGetpItemIndex(pmenu, pitem);
|
|
UserAssert(uiPos < pmenu->cItems);
|
|
|
|
return uiPos;
|
|
}
|
|
#endif // DBG
|
|
|
|
/**************************************************************************\
|
|
* xxxMNDismiss
|
|
*
|
|
* 12/03/96 GerardoB Created
|
|
\**************************************************************************/
|
|
VOID xxxMNDismiss(
|
|
PMENUSTATE pMenuState)
|
|
{
|
|
xxxMNCancel(pMenuState, 0, 0, 0);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNFadeSelection
|
|
*
|
|
* 2/5/1998 vadimg created
|
|
\***************************************************************************/
|
|
BOOL MNFadeSelection(
|
|
PMENU pmenu,
|
|
PITEM pitem)
|
|
{
|
|
PWND pwnd;
|
|
HDC hdc;
|
|
RECT rc;
|
|
PPOPUPMENU ppopup;
|
|
|
|
if (!TestALPHA(SELECTIONFADE))
|
|
return FALSE;
|
|
|
|
/*
|
|
* Don't fade the selection if the user is using the keyboard or journalling. These are performance scenarios
|
|
*/
|
|
if (glinp.dwFlags & (LINP_KEYBOARD | LINP_JOURNALLING)) {
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get the window for the currently active popup menu.
|
|
*/
|
|
if ((ppopup = MNGetPopupFromMenu(pmenu, NULL)) == NULL)
|
|
return FALSE;
|
|
|
|
if ((pwnd = ppopup->spwndPopupMenu) == NULL)
|
|
return FALSE;
|
|
|
|
rc.left = pwnd->rcClient.left + pitem->xItem;
|
|
rc.top = pwnd->rcClient.top + pitem->yItem;
|
|
rc.right = rc.left + pitem->cxItem;
|
|
rc.bottom = rc.top + pitem->cyItem;
|
|
|
|
/*
|
|
* Initialize the fade animation and get the DC to draw the selection into.
|
|
*/
|
|
if ((hdc = CreateFade(NULL, &rc, CMS_SELECTIONFADE, 0)) == NULL)
|
|
return FALSE;
|
|
|
|
/*
|
|
* Read the current menu selection right from the screen, since the menu
|
|
* is still visible and it's always on top. In the worst case we could
|
|
* offset the origin of the DC and call xxxDrawMenuItem, but just reading
|
|
* from the screen is much faster.
|
|
*/
|
|
GreBitBlt(hdc, 0, 0, pitem->cxItem, pitem->cyItem, gpDispInfo->hdcScreen,
|
|
rc.left, rc.top, SRCCOPY, 0);
|
|
|
|
ShowFade();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
* xxxMNDismissWithNotify
|
|
*
|
|
* Generates parameters for WM_COMMAND or WM_SYSCOMMAND message.
|
|
*
|
|
* 12/03/96 GerardoB Created
|
|
\**************************************************************************/
|
|
VOID xxxMNDismissWithNotify(
|
|
PMENUSTATE pMenuState,
|
|
PMENU pmenu,
|
|
PITEM pitem,
|
|
UINT uPos,
|
|
LPARAM lParam)
|
|
{
|
|
UINT uMsg;
|
|
UINT uCmd;
|
|
|
|
if (pMenuState->pGlobalPopupMenu->fIsSysMenu) {
|
|
uMsg = WM_SYSCOMMAND;
|
|
uCmd = pitem->wID;
|
|
/* lParam set by caller */
|
|
} else if (pMenuState->fNotifyByPos) {
|
|
uMsg = WM_MENUCOMMAND;
|
|
uCmd = uPos;
|
|
lParam = (LPARAM)PtoHq(pmenu);
|
|
} else {
|
|
uMsg = WM_COMMAND;
|
|
uCmd = pitem->wID;
|
|
lParam = 0;
|
|
}
|
|
|
|
/*
|
|
* The menu is about to go away, see if we want to fade out the selection.
|
|
*/
|
|
if (MNFadeSelection(pmenu, pitem)) {
|
|
StartFade();
|
|
}
|
|
|
|
/*
|
|
* Dismiss the menu.
|
|
*/
|
|
xxxMNCancel(pMenuState, uMsg, uCmd, lParam);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
* MNGetpItem
|
|
*
|
|
* 11/15/96 GerardoB Created
|
|
\**************************************************************************/
|
|
PITEM MNGetpItem(
|
|
PPOPUPMENU ppopup,
|
|
UINT uIndex)
|
|
{
|
|
if ((ppopup == NULL) || (uIndex >= ppopup->spmenu->cItems)) {
|
|
return NULL;
|
|
}
|
|
|
|
return ppopup->spmenu->rgItems + uIndex;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNSetCapture
|
|
*
|
|
* History:
|
|
* 11/18/96 GerardoB Created
|
|
\***************************************************************************/
|
|
VOID xxxMNSetCapture(
|
|
PPOPUPMENU ppopup)
|
|
{
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
|
|
/*
|
|
* Set the capture and lock it so no one will be able to steal it
|
|
* from us.
|
|
*/
|
|
xxxCapture(ptiCurrent, ppopup->spwndNotify, SCREEN_CAPTURE);
|
|
#if DBG
|
|
if (ptiCurrent->pq->spwndCapture != ppopup->spwndNotify) {
|
|
RIPMSG2(RIP_WARNING, "xxxMNSetCapture: spwndCapture (%p) != spwndNotify (%p)", ptiCurrent->pq->spwndCapture, ppopup->spwndNotify);
|
|
}
|
|
#endif
|
|
|
|
ptiCurrent->pq->QF_flags |= QF_CAPTURELOCKED;
|
|
ptiCurrent->pMenuState->fSetCapture = TRUE;
|
|
#if DBG
|
|
/*
|
|
* Unless we're in the foreground, this menu mode won't go away
|
|
* when the user clicks outside the menu. This is because only
|
|
* the foreground queue capture sees clicks outside its windows.
|
|
*/
|
|
if (ptiCurrent->pq != gpqForeground) {
|
|
RIPMSG0(RIP_WARNING, "xxxMNSetCapture: Menu mode is not in foreground queue");
|
|
}
|
|
#endif
|
|
}
|
|
/***************************************************************************\
|
|
* xxxMNReleaseCapture
|
|
*
|
|
* History:
|
|
* 11/18/96 GerardoB Created
|
|
\***************************************************************************/
|
|
VOID xxxMNReleaseCapture(
|
|
VOID)
|
|
{
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
|
|
/*
|
|
* Bail if we didn't set capture.
|
|
*/
|
|
if ((ptiCurrent->pMenuState == NULL) ||
|
|
(!ptiCurrent->pMenuState->fSetCapture)) {
|
|
return;
|
|
}
|
|
ptiCurrent->pMenuState->fSetCapture = FALSE;
|
|
|
|
/*
|
|
* Unlock capture and release it.
|
|
*/
|
|
PtiCurrent()->pq->QF_flags &= ~QF_CAPTURELOCKED;
|
|
xxxReleaseCapture();
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNCheckButtonDownState
|
|
*
|
|
* History:
|
|
* 11/14/96 GerardoB Created
|
|
\***************************************************************************/
|
|
VOID MNCheckButtonDownState(
|
|
PMENUSTATE pMenuState)
|
|
{
|
|
/*
|
|
* Modeless menus don't capture the mouse so when a mouse down
|
|
* goes off the window, we need to keep watching its state.
|
|
*
|
|
* We also might not see the button up when going on DoDragDrop loop.
|
|
*/
|
|
UserAssert(pMenuState->fDragAndDrop || pMenuState->fModelessMenu);
|
|
pMenuState->fButtonDown = ((_GetKeyState(pMenuState->vkButtonDown) & 0x8000) != 0);
|
|
if (!pMenuState->fButtonDown) {
|
|
pMenuState->fDragging =
|
|
pMenuState->fIgnoreButtonUp = FALSE;
|
|
UnlockMFMWFPWindow(&pMenuState->uButtonDownHitArea);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* GetMenuStateWindow
|
|
*
|
|
* This function is called when we need to post a message to the menu loop.
|
|
* The actual pwnd is not important since we just want to reach
|
|
* xxxHandleMenuMessages or xxxMenuWindowProc. So we just pick a window that
|
|
* has a good chance to be around as long as we are in menu mode.
|
|
*
|
|
* History:
|
|
* 10/31/96 GerardoB Created
|
|
\***************************************************************************/
|
|
PWND GetMenuStateWindow(
|
|
PMENUSTATE pMenuState)
|
|
{
|
|
if (pMenuState == NULL) {
|
|
return NULL;
|
|
} else if (pMenuState->pGlobalPopupMenu->fIsTrackPopup) {
|
|
return pMenuState->pGlobalPopupMenu->spwndPopupMenu;
|
|
} else if (pMenuState->pGlobalPopupMenu->spwndNextPopup != NULL) {
|
|
return pMenuState->pGlobalPopupMenu->spwndNextPopup;
|
|
} else {
|
|
return pMenuState->pGlobalPopupMenu->spwndActivePopup;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* UnlockPopupMenuWindow
|
|
*
|
|
* This function is called when locking/unlocking a menu into a popup structure.
|
|
* It makes sure that pmenu doesn't keep the notification window locked
|
|
* unneccessarily.
|
|
*
|
|
* It unlocks pmenu->spwndNotify if the menu it's not locked into pmenu->spwndNotify
|
|
* itself AND it's currently locked to pwnd.
|
|
*
|
|
* It's also unlocked if pmenu->spwndNotify is marked as destroyed.
|
|
*
|
|
* History:
|
|
* 10/15/96 GerardoB Created
|
|
\***************************************************************************/
|
|
VOID UnlockPopupMenuWindow(
|
|
PMENU pmenu,
|
|
PWND pwnd)
|
|
{
|
|
/*
|
|
* Bail if there's nothing to unlock.
|
|
*/
|
|
if ((pmenu == NULL) || (pmenu->spwndNotify == NULL)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* if pmenu->spwndNotify owns the menu, bail
|
|
*/
|
|
if ((pmenu == pmenu->spwndNotify->spmenu)
|
|
|| (pmenu == pmenu->spwndNotify->spmenuSys)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If pwnd doesn't own the menu, and pmenu->spwndNotify is not destroyed, bail.
|
|
*/
|
|
if ((pwnd != pmenu->spwndNotify)
|
|
&& !TestWF(pmenu->spwndNotify, WFDESTROYED)) {
|
|
return;
|
|
}
|
|
/*
|
|
* Unlock it
|
|
*/
|
|
Unlock(&pmenu->spwndNotify);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* LockPopupMenu
|
|
*
|
|
* Locks a given menu into a popup strucuture and makes the
|
|
* popup's notification window the owner of the menu.
|
|
*
|
|
* History:
|
|
* 10/15/96 GerardoB Created
|
|
\***************************************************************************/
|
|
PVOID LockPopupMenu(
|
|
PPOPUPMENU ppopup,
|
|
PMENU *pspmenu,
|
|
PMENU pmenu)
|
|
{
|
|
/*
|
|
* If you hit this assertion, you're probably not passing the right thing.
|
|
*/
|
|
UserAssert((pspmenu == &ppopup->spmenu) || (pspmenu == &ppopup->spmenuAlternate));
|
|
Validateppopupmenu(ppopup);
|
|
|
|
/*
|
|
* This won't work properly if the popup hasn't locked the notification
|
|
* window.
|
|
*/
|
|
UserAssert(ppopup->spwndNotify != NULL);
|
|
|
|
/*
|
|
* When using modeless menus, menus can be shared by several active popups.
|
|
* If the menu has owner draw items, the app better knows how to draw them
|
|
* correctly. This shouldn't happen with modal menus though.
|
|
*/
|
|
#if DBG
|
|
if ((*pspmenu != NULL)
|
|
&& ((*pspmenu)->spwndNotify != NULL)
|
|
&& ((*pspmenu)->spwndNotify != ppopup->spwndNotify)) {
|
|
|
|
RIPMSG3(RIP_WARNING, "LockPopupMenu: Current Menu %#p shared by %#p and %#p",
|
|
*pspmenu, (*pspmenu)->spwndNotify, ppopup->spwndNotify);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Unlock the current's menu spwndNotify if needed
|
|
*/
|
|
UnlockPopupMenuWindow(*pspmenu, ppopup->spwndNotify);
|
|
|
|
/*
|
|
* Lock the notification window into the menu structure
|
|
*/
|
|
if (pmenu != NULL) {
|
|
/*
|
|
* Display a warning if this menu is being shared.
|
|
*/
|
|
#if DBG
|
|
if ((pmenu->spwndNotify != NULL)
|
|
&& (pmenu->spwndNotify != ppopup->spwndNotify)
|
|
&& (pmenu != pmenu->spwndNotify->head.rpdesk->spmenuDialogSys)) {
|
|
|
|
RIPMSG3(RIP_WARNING, "LockPopupMenu: New Menu %#p shared by %#p and %#p",
|
|
pmenu, pmenu->spwndNotify, ppopup->spwndNotify);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* spwndNotify "owns" this menu now.
|
|
*/
|
|
Lock(&pmenu->spwndNotify, ppopup->spwndNotify);
|
|
}
|
|
|
|
/*
|
|
* Lock the menu into the popup structure (unlock the previous one)
|
|
*/
|
|
return Lock(pspmenu, pmenu);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* UnlockPopupMenu
|
|
*
|
|
* Unlocks a given menu from a popup strucuture and makes sure that the
|
|
* menu is no longer "owned" by the popup's notification window; if needed.
|
|
*
|
|
* History:
|
|
* 10/15/96 GerardoB Created
|
|
\***************************************************************************/
|
|
PVOID UnlockPopupMenu(
|
|
PPOPUPMENU ppopup,
|
|
PMENU * pspmenu)
|
|
{
|
|
/*
|
|
* If you hit this assertion, you're probably not passing the right thing.
|
|
*/
|
|
UserAssert((pspmenu == &ppopup->spmenu) || (pspmenu == &ppopup->spmenuAlternate));
|
|
|
|
/*
|
|
* If nothing is locked, bail.
|
|
*/
|
|
if (*pspmenu == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* This won't work properly if the popup already unlocked the notification
|
|
* window. However, this can happen with the root popup if the
|
|
* notification window gets destroyed while in menu mode.
|
|
*/
|
|
UserAssert((ppopup->spwndNotify != NULL) || IsRootPopupMenu(ppopup));
|
|
|
|
/*
|
|
* When using modeless menus, menus can be shared by several active
|
|
* popups/notification windows. If the menu has owner draw items,
|
|
* the app better knows how to paint them right. It shouldn't
|
|
* happen with modal menus though.
|
|
*/
|
|
#if DBG
|
|
if (((*pspmenu)->spwndNotify != NULL)
|
|
&& (ppopup->spwndNotify != NULL)
|
|
&& (ppopup->spwndNotify != (*pspmenu)->spwndNotify)) {
|
|
|
|
RIPMSG3(RIP_WARNING, "UnlockPopupMenu: Menu %#p shared by %#p and %#p",
|
|
*pspmenu, (*pspmenu)->spwndNotify, ppopup->spwndNotify);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Unlock the menu's spwndNotify if needed
|
|
*/
|
|
UnlockPopupMenuWindow(*pspmenu, ppopup->spwndNotify);
|
|
|
|
/*
|
|
* Unlock the menu from the popup structure
|
|
*/
|
|
return Unlock(pspmenu);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* LockWndMenu
|
|
*
|
|
* Locks a given menu into a window structure and locks the window into
|
|
* the menu strucuture.
|
|
*
|
|
* History:
|
|
* 10/15/96 GerardoB Created
|
|
\***************************************************************************/
|
|
PVOID LockWndMenu(
|
|
PWND pwnd,
|
|
PMENU *pspmenu,
|
|
PMENU pmenu)
|
|
{
|
|
/*
|
|
* If you hit this assertion, you're probably not passing the right thing
|
|
*/
|
|
UserAssert((pspmenu == &pwnd->spmenu) || (pspmenu == &pwnd->spmenuSys));
|
|
|
|
/*
|
|
* If the current menu is owned by this window, unlock it
|
|
*/
|
|
if ((*pspmenu != NULL) && ((*pspmenu)->spwndNotify == pwnd)) {
|
|
Unlock(&((*pspmenu)->spwndNotify));
|
|
}
|
|
|
|
/*
|
|
* If nobody owns the new menu, make this window the owner
|
|
*/
|
|
if ((pmenu != NULL) && (pmenu->spwndNotify == NULL)) {
|
|
Lock(&pmenu->spwndNotify, pwnd);
|
|
}
|
|
|
|
/*
|
|
* Lock the menu into the window structure (unlock the previous menu)
|
|
*/
|
|
return Lock(pspmenu, pmenu);
|
|
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* UnlockWndMenu
|
|
*
|
|
* Unlocks a given menu from a window strucutre and the window from the
|
|
* menu strucuture
|
|
*
|
|
* History:
|
|
* 10/15/96 GerardoB Created
|
|
\***************************************************************************/
|
|
PVOID UnlockWndMenu(
|
|
PWND pwnd,
|
|
PMENU *pspmenu)
|
|
{
|
|
/*
|
|
* If you hit this assertion, you're probably not passing the right thing
|
|
*/
|
|
UserAssert((pspmenu == &pwnd->spmenu) || (pspmenu == &pwnd->spmenuSys));
|
|
|
|
/*
|
|
* If nothing is locked, bail.
|
|
*/
|
|
if (*pspmenu == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* If this window owns the menu, unlock it from the menu structure.
|
|
*/
|
|
if (pwnd == (*pspmenu)->spwndNotify) {
|
|
Unlock(&((*pspmenu)->spwndNotify));
|
|
}
|
|
|
|
/*
|
|
* Unlock the menu from the window structure
|
|
*/
|
|
return Unlock(pspmenu);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* MNSetTop
|
|
*
|
|
* Sets the first visible item in a scrollable menu to the given iNewTop.
|
|
* Returns TRUE if iTop was changed; FALSE if iNewTop is already the
|
|
* first visible item.
|
|
*
|
|
* 08/13/96 GerardoB Ported From Memphis.
|
|
\***************************************************************************/
|
|
BOOL xxxMNSetTop(
|
|
PPOPUPMENU ppopup,
|
|
int iNewTop)
|
|
{
|
|
PMENU pMenu = ppopup->spmenu;
|
|
int dy;
|
|
|
|
if (iNewTop < 0) {
|
|
iNewTop = 0;
|
|
} else if (iNewTop > pMenu->iMaxTop) {
|
|
iNewTop = pMenu->iMaxTop;
|
|
}
|
|
|
|
/*
|
|
* If no change, done.
|
|
*/
|
|
if (iNewTop == pMenu->iTop) {
|
|
return FALSE;
|
|
}
|
|
|
|
#if DBG
|
|
/*
|
|
* We're going to scroll, so validate iMaxTop, cyMax and cyMenu.
|
|
*/
|
|
UserAssert((pMenu->cyMax == 0) || (pMenu->cyMax >= pMenu->cyMenu));
|
|
if ((UINT)pMenu->iMaxTop < pMenu->cItems) {
|
|
PITEM pitemLast = pMenu->rgItems + pMenu->cItems - 1;
|
|
PITEM pitemMaxTop = pMenu->rgItems + pMenu->iMaxTop;
|
|
UINT uHeight = pitemLast->yItem + pitemLast->cyItem - pitemMaxTop->yItem;
|
|
UserAssert(uHeight <= pMenu->cyMenu);
|
|
/*
|
|
* Let's guess a max item height.
|
|
*/
|
|
UserAssert(pMenu->cyMenu - uHeight <= 2 * pitemLast->cyItem);
|
|
} else {
|
|
UserAssert((UINT)pMenu->iMaxTop < pMenu->cItems);
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* If we've made it this far, the new iTop WILL change -- thus if the
|
|
* current iTop is at the top it won't be after this change -- same goes
|
|
* for iTop at the bottom.
|
|
*/
|
|
if (pMenu->dwArrowsOn == MSA_ATTOP) {
|
|
pMenu->dwArrowsOn = MSA_ON;
|
|
if (pMenu->hbrBack == NULL) {
|
|
MNDrawArrow(NULL, ppopup, MFMWFP_UPARROW);
|
|
}
|
|
} else if (pMenu->dwArrowsOn == MSA_ATBOTTOM) {
|
|
pMenu->dwArrowsOn = MSA_ON;
|
|
if (pMenu->hbrBack == NULL) {
|
|
MNDrawArrow(NULL, ppopup, MFMWFP_DOWNARROW);
|
|
}
|
|
}
|
|
|
|
UserAssert((UINT)iNewTop < pMenu->cItems);
|
|
dy = MNGetToppItem(pMenu)->yItem - (pMenu->rgItems + iNewTop)->yItem;
|
|
|
|
if ((dy > 0 ? dy : -dy) > (int)pMenu->cyMenu) {
|
|
xxxInvalidateRect(ppopup->spwndPopupMenu, NULL, TRUE);
|
|
} else {
|
|
xxxScrollWindowEx(ppopup->spwndPopupMenu, 0, dy, NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_ERASE);
|
|
}
|
|
|
|
pMenu->iTop = iNewTop;
|
|
|
|
if (iNewTop == 0) {
|
|
pMenu->dwArrowsOn = MSA_ATTOP;
|
|
if (pMenu->hbrBack == NULL) {
|
|
MNDrawArrow(NULL, ppopup, MFMWFP_UPARROW);
|
|
}
|
|
} else if (iNewTop == pMenu->iMaxTop) {
|
|
pMenu->dwArrowsOn = MSA_ATBOTTOM;
|
|
if (pMenu->hbrBack == NULL) {
|
|
MNDrawArrow(NULL, ppopup, MFMWFP_DOWNARROW);
|
|
}
|
|
}
|
|
|
|
if (pMenu->hbrBack != NULL) {
|
|
MNDrawFullNC(ppopup->spwndPopupMenu, NULL, ppopup);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNDoScroll
|
|
*
|
|
* scrolls a scrollable menu (ppopup) if the given position (uArrow) is one of
|
|
* the menu scroll arrows and sets a timer to auto-scroll when necessary;
|
|
* returns FALSE if the given position was not a menu scroll arrow; returns
|
|
* TRUE otherwise
|
|
*
|
|
* 08/13/96 GerardoB Ported From Memphis.
|
|
\***************************************************************************/
|
|
BOOL xxxMNDoScroll(
|
|
PPOPUPMENU ppopup,
|
|
UINT uArrow,
|
|
BOOL fSetTimer)
|
|
{
|
|
int iScrollTop = ppopup->spmenu->iTop;
|
|
|
|
if (uArrow == MFMWFP_UPARROW) {
|
|
iScrollTop--;
|
|
} else if (uArrow == MFMWFP_DOWNARROW) {
|
|
iScrollTop++;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
|
|
if (!xxxMNSetTop(ppopup, iScrollTop)) {
|
|
if (!fSetTimer) {
|
|
_KillTimer(ppopup->spwndPopupMenu, uArrow);
|
|
}
|
|
} else {
|
|
/*
|
|
* Set this timer just like we do in the scrollbar code:
|
|
* the first time we wait a little longer.
|
|
*/
|
|
_SetTimer(ppopup->spwndPopupMenu, uArrow,
|
|
(fSetTimer ? gpsi->dtScroll : gpsi->dtScroll / 4), NULL);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNCheckScroll
|
|
*
|
|
* Checks to see if the given menu (pMenu) can be displayed in it's entirety
|
|
* or if it can't, in which case it sets the menu to be scrollable.
|
|
*
|
|
* 08/13/96 GerardoB Ported From Memphis.
|
|
\***************************************************************************/
|
|
int MNCheckScroll(
|
|
PMENU pMenu,
|
|
PMONITOR pMonitor)
|
|
{
|
|
int i;
|
|
UINT cyMax;
|
|
PITEM pItem;
|
|
|
|
/*
|
|
* Max height that fits on the monitor
|
|
*/
|
|
cyMax = (pMonitor->rcMonitor.bottom - pMonitor->rcMonitor.top);
|
|
|
|
/*
|
|
* If the menu has a valid max height, use it
|
|
*/
|
|
if ((pMenu->cyMax != 0) && (pMenu->cyMax < cyMax)) {
|
|
cyMax = pMenu->cyMax;
|
|
}
|
|
|
|
/*
|
|
* Bail if menu is either empty, multicolumn, or able to fit
|
|
* without scrolling
|
|
*/
|
|
if ((pMenu->rgItems == 0)
|
|
|| (pMenu->rgItems->cxItem != pMenu->cxMenu)
|
|
|| (pMenu->cyMenu + (2 * SYSMET(CYFIXEDFRAME)) <= cyMax)) {
|
|
|
|
pMenu->dwArrowsOn = MSA_OFF;
|
|
pMenu->iTop = 0;
|
|
pMenu->iMaxTop = 0;
|
|
return pMenu->cyMenu;
|
|
}
|
|
|
|
/*
|
|
* Max client height
|
|
*/
|
|
cyMax -= 2 * (SYSMET(CYFIXEDFRAME) + gcyMenuScrollArrow);
|
|
|
|
/*
|
|
* Determine the menu height
|
|
* Find the first item that won't fit.
|
|
*/
|
|
pItem = pMenu->rgItems;
|
|
for (i = 0; i < (int)pMenu->cItems; i++, pItem++) {
|
|
if (pItem->yItem > (UINT)cyMax) {
|
|
break;
|
|
}
|
|
}
|
|
if (i != 0) {
|
|
pItem--;
|
|
}
|
|
pMenu->cyMenu = pItem->yItem;
|
|
|
|
/*
|
|
* compute the last possible top item when all remaining items are fully
|
|
* visible
|
|
*/
|
|
cyMax = 0;
|
|
i = pMenu->cItems - 1;
|
|
pItem = pMenu->rgItems + i;
|
|
for (; i >= 0; i--, pItem--) {
|
|
cyMax += pItem->cyItem;
|
|
if (cyMax > pMenu->cyMenu) {
|
|
break;
|
|
}
|
|
}
|
|
if ((UINT)i != pMenu->cItems - 1) {
|
|
i++;
|
|
}
|
|
pMenu->iMaxTop = i;
|
|
|
|
/*
|
|
* Update top item and scroll state
|
|
*/
|
|
if (pMenu->iTop > i) {
|
|
pMenu->iTop = i;
|
|
}
|
|
|
|
if (pMenu->iTop == i) {
|
|
pMenu->dwArrowsOn = MSA_ATBOTTOM;
|
|
} else if (pMenu->iTop == 0) {
|
|
pMenu->dwArrowsOn = MSA_ATTOP;
|
|
} else {
|
|
pMenu->dwArrowsOn = MSA_ON;
|
|
}
|
|
|
|
/*
|
|
* This is funtion is called by MN_SIZEWINDOW which doesn't check
|
|
* if the scroll bars are present but simply adds (2 * SYSMET(CYFIXEDFRAME))
|
|
* to calculate the window height. So we add the scrollbars height
|
|
* here. (I believe MN_SIZEWINDOW is a private-but-publicly-known message)
|
|
*/
|
|
return (pMenu->cyMenu + (2 * gcyMenuScrollArrow));
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNIsPopupItem
|
|
*
|
|
*
|
|
\***************************************************************************/
|
|
BOOL MNIsPopupItem(
|
|
ITEM *lpItem)
|
|
{
|
|
return ((lpItem) && (lpItem->spSubMenu) && !TestMFS(lpItem, MFS_GRAYED));
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* Validateppopupmenu
|
|
*
|
|
* 05-15-96 GerardoB Created
|
|
\***************************************************************************/
|
|
#if DBG
|
|
VOID Validateppopupmenu(
|
|
PPOPUPMENU ppopupmenu)
|
|
{
|
|
UserAssert(ppopupmenu != NULL);
|
|
try {
|
|
UserAssert(!ppopupmenu->fFreed);
|
|
|
|
/*
|
|
* If this popup is being destroyed to soon, ppopupmenuRoot can be NULL
|
|
*/
|
|
if (ppopupmenu->ppopupmenuRoot != NULL) {
|
|
if (ppopupmenu->ppopupmenuRoot != ppopupmenu) {
|
|
/*
|
|
* This must be a hierarchical popup.
|
|
*/
|
|
UserAssert(ppopupmenu->spwndPrevPopup != NULL);
|
|
UserAssert(!ppopupmenu->fIsMenuBar && !ppopupmenu->fIsTrackPopup);
|
|
Validateppopupmenu(ppopupmenu->ppopupmenuRoot);
|
|
} else {
|
|
/*
|
|
* This must be the root popupmenu.
|
|
*/
|
|
UserAssert(ppopupmenu->spwndPrevPopup == NULL);
|
|
UserAssert(ppopupmenu->fIsMenuBar || ppopupmenu->fIsTrackPopup);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This can be NULL when called from xxxDeleteThreadInfo.
|
|
*/
|
|
if (ppopupmenu->spwndPopupMenu != NULL) {
|
|
UserAssert(ppopupmenu->spwndPopupMenu == RevalidateCatHwnd(HW(ppopupmenu->spwndPopupMenu)));
|
|
}
|
|
|
|
/*
|
|
* This can be NULL when called from xxxDestroyWindow (spwndNotify)
|
|
* or from xxxDeleteThreadInfo.
|
|
*/
|
|
if (ppopupmenu->spwndNotify != NULL) {
|
|
UserAssert(ppopupmenu->spwndNotify == RevalidateCatHwnd(HW(ppopupmenu->spwndNotify)));
|
|
}
|
|
} except (W32ExceptionHandler(FALSE, RIP_ERROR)) {
|
|
RIPMSG1(RIP_ERROR, "Validateppopupmenu: Invalid popup: 0x%p", ppopupmenu);
|
|
}
|
|
}
|
|
#endif // DBG
|
|
|
|
/***************************************************************************\
|
|
* xxxMNSwitchToAlternateMenu
|
|
*
|
|
* Switches to the alternate popupmenu. Returns TRUE if we switch,
|
|
* else FALSE.
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
\***************************************************************************/
|
|
BOOL xxxMNSwitchToAlternateMenu(
|
|
PPOPUPMENU ppopupmenu)
|
|
{
|
|
PMENU pmenuSwap = NULL;
|
|
PMENUSTATE pMenuState;
|
|
TL tlpwndPopupMenu;
|
|
|
|
if (!ppopupmenu->fIsMenuBar || !ppopupmenu->spmenuAlternate) {
|
|
/*
|
|
* Do nothing if no menu or not top level menu bar.
|
|
* ppopupmenu->spmenuAlternate can be NULL when an app has
|
|
* either system menu or menu bar but not both. If that menu
|
|
* has only one popup that it's not dropped, then hitting
|
|
* VK_RIGHT or VK_LEFT causes xxxMNKeyDown to end up here.
|
|
*
|
|
* ppopupmenu->fIsMenuBar can be false when you drop the
|
|
* system menu of an app with no menu bar; then hit VK_RIGHT
|
|
* on an item that doesn't have a popup and you'll get here
|
|
* There might be some other situations like this; in any case
|
|
* the assertion's got to go.
|
|
*/
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* If we're getting out of menu mode, do nothing.
|
|
*/
|
|
if (ppopupmenu->fDestroyed) {
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Select no items in the current menu.
|
|
*/
|
|
ThreadLock(ppopupmenu->spwndPopupMenu, &tlpwndPopupMenu);
|
|
UserAssert(ppopupmenu->spwndPopupMenu != NULL);
|
|
pMenuState = GetpMenuState(ppopupmenu->spwndPopupMenu);
|
|
if (pMenuState == NULL) {
|
|
RIPMSG0(RIP_ERROR, "xxxMNSwitchToAlternateMenu: pMenuState == NULL");
|
|
ThreadUnlock(&tlpwndPopupMenu);
|
|
return FALSE;
|
|
}
|
|
xxxMNSelectItem(ppopupmenu, pMenuState, MFMWFP_NOITEM);
|
|
|
|
|
|
UserAssert(ppopupmenu->spmenu->spwndNotify == ppopupmenu->spmenuAlternate->spwndNotify);
|
|
Lock(&pmenuSwap, ppopupmenu->spmenuAlternate);
|
|
Lock(&ppopupmenu->spmenuAlternate, ppopupmenu->spmenu);
|
|
Lock(&ppopupmenu->spmenu, pmenuSwap);
|
|
Unlock(&pmenuSwap);
|
|
|
|
if (!TestWF(ppopupmenu->spwndNotify, WFSYSMENU)) {
|
|
pMenuState->fIsSysMenu = FALSE;
|
|
} else if (ppopupmenu->spwndNotify->spmenuSys != NULL) {
|
|
pMenuState->fIsSysMenu = (ppopupmenu->spwndNotify->spmenuSys ==
|
|
ppopupmenu->spmenu);
|
|
} else {
|
|
pMenuState->fIsSysMenu = !!TestMF(ppopupmenu->spmenu, MFSYSMENU);
|
|
}
|
|
|
|
ppopupmenu->fIsSysMenu = pMenuState->fIsSysMenu;
|
|
|
|
xxxWindowEvent(EVENT_SYSTEM_MENUEND, ppopupmenu->spwndNotify,
|
|
(ppopupmenu->fIsSysMenu ? OBJID_MENU : OBJID_SYSMENU), INDEXID_CONTAINER, 0);
|
|
|
|
xxxWindowEvent(EVENT_SYSTEM_MENUSTART, ppopupmenu->spwndNotify,
|
|
(ppopupmenu->fIsSysMenu ? OBJID_SYSMENU : OBJID_MENU), INDEXID_CONTAINER, 0);
|
|
|
|
ThreadUnlock(&tlpwndPopupMenu);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNDestroyHandler
|
|
*
|
|
* Cleans up after this menu.
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
\***************************************************************************/
|
|
VOID xxxMNDestroyHandler(
|
|
PPOPUPMENU ppopupmenu)
|
|
{
|
|
PITEM pItem;
|
|
TL tlpwndT;
|
|
|
|
if (ppopupmenu == NULL) {
|
|
/*
|
|
* This can happen if WM_NCCREATE failed to allocate the ppopupmenu
|
|
* in xxxMenuWindowProc.
|
|
*/
|
|
RIPMSG0(RIP_WARNING, "xxxMNDestroyHandler: NULL \"ppopupmenu\"");
|
|
return;
|
|
}
|
|
|
|
#if DBG
|
|
Validateppopupmenu(ppopupmenu);
|
|
#endif
|
|
|
|
if (ppopupmenu->spwndNextPopup != NULL) {
|
|
/*
|
|
* We used to send the message to spwndNextPopup here. The message should
|
|
* go to the current popup so it'll close spwndNextPopup (not to the next
|
|
* to close its next, if any).
|
|
*
|
|
* I don't see how the current spwndPopupMenu can be NULL but we better
|
|
* handle it since we never accessed it before. This menu code is tricky...
|
|
*/
|
|
PWND pwnd;
|
|
UserAssert(ppopupmenu->spwndPopupMenu != NULL);
|
|
pwnd = (ppopupmenu->spwndPopupMenu != NULL ? ppopupmenu->spwndPopupMenu : ppopupmenu->spwndNextPopup);
|
|
ThreadLockAlways(pwnd, &tlpwndT);
|
|
xxxSendMessage(pwnd, MN_CLOSEHIERARCHY, 0, 0);
|
|
ThreadUnlock(&tlpwndT);
|
|
}
|
|
|
|
if ((ppopupmenu->spmenu != NULL) && MNIsItemSelected(ppopupmenu)) {
|
|
/*
|
|
* Unset the hilite bit on the hilited item.
|
|
*/
|
|
if (ppopupmenu->posSelectedItem < ppopupmenu->spmenu->cItems) {
|
|
/*
|
|
* This extra check saves Ambiente 1.02 -- they have a menu with
|
|
* one item in it. When that command is chosen, the app proceeds
|
|
* to remove that one item -- leaving us in the oh so strange state
|
|
* of having a valid hMenu with a NULL rgItems.
|
|
*/
|
|
pItem = &(ppopupmenu->spmenu->rgItems[ppopupmenu->posSelectedItem]);
|
|
pItem->fState &= ~MFS_HILITE;
|
|
}
|
|
}
|
|
|
|
if (ppopupmenu->fShowTimer) {
|
|
_KillTimer(ppopupmenu->spwndPopupMenu, IDSYS_MNSHOW);
|
|
}
|
|
|
|
if (ppopupmenu->fHideTimer) {
|
|
_KillTimer(ppopupmenu->spwndPopupMenu, IDSYS_MNHIDE);
|
|
}
|
|
|
|
/*
|
|
* Send WM_UNINITMENUPOPUP so the menu owner can clean up.
|
|
*/
|
|
if (ppopupmenu->fSendUninit
|
|
&& (ppopupmenu->spwndNotify != NULL)) {
|
|
|
|
ThreadLockAlways(ppopupmenu->spwndNotify, &tlpwndT);
|
|
xxxSendMessage(ppopupmenu->spwndNotify, WM_UNINITMENUPOPUP,
|
|
(WPARAM)PtoH(ppopupmenu->spmenu),
|
|
MAKELONG(0, (ppopupmenu->fIsSysMenu ? MF_SYSMENU: 0)));
|
|
ThreadUnlock(&tlpwndT);
|
|
}
|
|
|
|
ppopupmenu->fDestroyed = TRUE;
|
|
if (ppopupmenu->spwndPopupMenu != NULL) {
|
|
((PMENUWND)(ppopupmenu->spwndPopupMenu))->ppopupmenu = NULL;
|
|
}
|
|
|
|
if (!ppopupmenu->fDelayedFree) {
|
|
MNFreePopup(ppopupmenu);
|
|
} else if (ppopupmenu->ppopupmenuRoot != NULL) {
|
|
ppopupmenu->ppopupmenuRoot->fFlushDelayedFree = TRUE;
|
|
#if DBG
|
|
{
|
|
/*
|
|
* If this is not the rootpopup, assert that this popup is
|
|
* linked in the delayed free list.
|
|
*/
|
|
if (!IsRootPopupMenu(ppopupmenu)) {
|
|
BOOL fFound = FALSE;
|
|
PPOPUPMENU ppm = ppopupmenu->ppopupmenuRoot;
|
|
while (ppm->ppmDelayedFree != NULL) {
|
|
if (ppm->ppmDelayedFree == ppopupmenu) {
|
|
fFound = TRUE;
|
|
break;
|
|
}
|
|
ppm = ppm->ppmDelayedFree;
|
|
}
|
|
UserAssert(fFound);
|
|
}
|
|
}
|
|
#endif
|
|
} else {
|
|
UserAssertMsg1(FALSE, "Leaking ppopupmenu: %p?", ppopupmenu);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNChar
|
|
*
|
|
* Handles char messages for the given menu. This procedure is called
|
|
* directly if the menu char is for the top level menu bar else it is called
|
|
* by the menu window proc on behalf of the window that should process the
|
|
* key.
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
\***************************************************************************/
|
|
VOID xxxMNChar(
|
|
PPOPUPMENU ppopupmenu,
|
|
PMENUSTATE pMenuState,
|
|
UINT character)
|
|
{
|
|
PMENU pMenu;
|
|
UINT flags;
|
|
LRESULT result;
|
|
int item;
|
|
INT matchType;
|
|
BOOL fExecute = FALSE;
|
|
TL tlpwndNotify;
|
|
|
|
pMenu = ppopupmenu->spmenu;
|
|
|
|
Validateppopupmenu(ppopupmenu);
|
|
|
|
/*
|
|
* If this comes in with a NULL pMenu, then we could have problems.
|
|
* This could happen if the xxxMNStartMenuState never gets called
|
|
* because the fInsideMenuLoop is set.
|
|
*
|
|
* This is placed in here temporarily until we can discover why
|
|
* this pMenu isn't set. We will prevent the system from crashing
|
|
* in the meantime.
|
|
*
|
|
* HACK: ChrisWil
|
|
*/
|
|
if (pMenu == NULL) {
|
|
UserAssert(pMenu);
|
|
xxxMNDismiss(pMenuState);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If we're getting out of menu mode, bail.
|
|
*/
|
|
if (ppopupmenu->fDestroyed) {
|
|
return;
|
|
}
|
|
|
|
item = xxxMNFindChar(pMenu, character,
|
|
ppopupmenu->posSelectedItem, &matchType);
|
|
if (item != MFMWFP_NOITEM) {
|
|
int item1;
|
|
int firstItem = item;
|
|
|
|
/*
|
|
* Find first ENABLED menu item with the given mnemonic 'character'
|
|
* !!! If none found, exit menu loop !!!
|
|
*/
|
|
while (pMenu->rgItems[item].fState & MFS_GRAYED) {
|
|
item = xxxMNFindChar(pMenu, character, item, &matchType);
|
|
if (item == firstItem) {
|
|
xxxMNDismiss(pMenuState);
|
|
return;
|
|
}
|
|
}
|
|
item1 = item;
|
|
|
|
/*
|
|
* Find next ENABLED menu item with the given mnemonic 'character'
|
|
* This is to see if we have a DUPLICATE MNEMONIC situation
|
|
*/
|
|
do {
|
|
item = xxxMNFindChar(pMenu, character, item, &matchType);
|
|
} while ((pMenu->rgItems[item].fState & MFS_GRAYED) && (item != firstItem));
|
|
|
|
if ((firstItem == item) || (item == item1))
|
|
fExecute = TRUE;
|
|
|
|
item = item1;
|
|
}
|
|
|
|
if ((item == MFMWFP_NOITEM) && ppopupmenu->fIsMenuBar && (character == TEXT(' '))) {
|
|
|
|
/*
|
|
* Handle the case of the user cruising through the top level menu bar
|
|
* without any popups dropped. We need to handle switching to and from
|
|
* the system menu.
|
|
*/
|
|
if (ppopupmenu->fIsSysMenu) {
|
|
|
|
/*
|
|
* If we are on the system menu and user hits space, bring
|
|
* down thesystem menu.
|
|
*/
|
|
item = 0;
|
|
fExecute = TRUE;
|
|
} else if (ppopupmenu->spmenuAlternate != NULL) {
|
|
|
|
/*
|
|
* We are not currently on the system menu but one exists. So
|
|
* switch to it and bring it down.
|
|
*/
|
|
item = 0;
|
|
goto SwitchToAlternate;
|
|
}
|
|
}
|
|
|
|
if ((item == MFMWFP_NOITEM) && ppopupmenu->fIsMenuBar && ppopupmenu->spmenuAlternate) {
|
|
|
|
/*
|
|
* No matching item found on this top level menu (could be either the
|
|
* system menu or the menu bar). We need to check the other menu.
|
|
*/
|
|
item = xxxMNFindChar(ppopupmenu->spmenuAlternate,
|
|
character, 0, &matchType);
|
|
|
|
if (item != MFMWFP_NOITEM) {
|
|
SwitchToAlternate:
|
|
if (xxxMNSwitchToAlternateMenu(ppopupmenu)) {
|
|
xxxMNChar(ppopupmenu, pMenuState, character);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (item == MFMWFP_NOITEM) {
|
|
flags = (ppopupmenu->fIsSysMenu) ? MF_SYSMENU : 0;
|
|
|
|
if (!ppopupmenu->fIsMenuBar) {
|
|
flags |= MF_POPUP;
|
|
}
|
|
|
|
ThreadLock(ppopupmenu->spwndNotify, &tlpwndNotify);
|
|
result = xxxSendMessage(ppopupmenu->spwndNotify, WM_MENUCHAR,
|
|
MAKELONG((WORD)character, (WORD)flags),
|
|
(LPARAM)PtoH(ppopupmenu->spmenu));
|
|
ThreadUnlock(&tlpwndNotify);
|
|
|
|
switch (HIWORD(result)) {
|
|
case MNC_IGNORE:
|
|
xxxMessageBeep(0);
|
|
/*
|
|
* If we're on the menu bar, cancel menu mode (fall through).
|
|
* We do this because you can really scare an end user
|
|
* who accidentally tapped the Alt key (causing us to go
|
|
* into "invisible" menu mode) and now can't type anything!
|
|
*/
|
|
if (flags & MF_POPUP) {
|
|
return;
|
|
}
|
|
/*
|
|
* Fall through.
|
|
*/
|
|
|
|
case MNC_CLOSE:
|
|
xxxMNDismiss(pMenuState);
|
|
return;
|
|
|
|
case MNC_EXECUTE:
|
|
fExecute = TRUE;
|
|
/* fall thru */
|
|
|
|
case MNC_SELECT:
|
|
item = (UINT)(short)LOWORD(result);
|
|
if ((WORD) item >= ppopupmenu->spmenu->cItems)
|
|
{
|
|
RIPMSG1(RIP_WARNING, "Invalid item number returned from WM_MENUCHAR %#lx", result);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (item != MFMWFP_NOITEM) {
|
|
xxxMNSelectItem(ppopupmenu, pMenuState, item);
|
|
|
|
if (fExecute)
|
|
xxxMNKeyDown(ppopupmenu, pMenuState, VK_RETURN);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* GetMenuInheritedContextHelpId
|
|
*
|
|
* Given a ppopup, this function will see if that menu has a context help
|
|
* id and return it. If it does not have a context help id, it will look up
|
|
* in the parent menu, parent of the parent etc., all the way to the top
|
|
* top level menu bar till it finds a context help id and returns it. If no
|
|
* context help id is found, it returns a zero.
|
|
\***************************************************************************/
|
|
DWORD GetMenuInheritedContextHelpId(
|
|
PPOPUPMENU ppopup)
|
|
{
|
|
PWND pWnd;
|
|
|
|
/*
|
|
* If we are already at the menubar, simply return it's ContextHelpId
|
|
*/
|
|
UserAssert(ppopup != NULL);
|
|
if (ppopup->fIsMenuBar) {
|
|
goto Exit_GMI;
|
|
}
|
|
|
|
while(TRUE) {
|
|
UserAssert(ppopup != NULL);
|
|
|
|
/*
|
|
* See if the given popup has a context help id.
|
|
*/
|
|
if (ppopup->spmenu->dwContextHelpId) {
|
|
/* Found the context Id */
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Get the previous popup menu;
|
|
* Check if the previous menu is the menu bar.
|
|
*/
|
|
if ( (ppopup->fHasMenuBar) &&
|
|
(ppopup->spwndPrevPopup == ppopup->spwndNotify)) {
|
|
|
|
ppopup = ppopup -> ppopupmenuRoot;
|
|
break;
|
|
} else {
|
|
/*
|
|
* See if this has a valid prevPopup; (it could be TrackPopup menu)
|
|
*/
|
|
if ((pWnd = ppopup -> spwndPrevPopup) == NULL) {
|
|
return ((DWORD)0);
|
|
}
|
|
|
|
ppopup = ((PMENUWND)pWnd)->ppopupmenu;
|
|
}
|
|
}
|
|
|
|
Exit_GMI:
|
|
return ppopup->spmenu->dwContextHelpId;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNKeyDown
|
|
*
|
|
* Handles a keydown for the given menu.
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
\***************************************************************************/
|
|
VOID xxxMNKeyDown(
|
|
PPOPUPMENU ppopupmenu,
|
|
PMENUSTATE pMenuState,
|
|
UINT key)
|
|
{
|
|
LRESULT dwMDIMenu;
|
|
UINT item;
|
|
BOOL fHierarchyWasDropped = FALSE;
|
|
TL tlpwndT;
|
|
PPOPUPMENU ppopupSave;
|
|
BOOL bFakedKey;
|
|
UINT keyOrig = key;
|
|
|
|
/*
|
|
* Blow off keyboard if mouse down.
|
|
*/
|
|
if ((pMenuState->fButtonDown) && (key != VK_F1)) {
|
|
/*
|
|
* Check if the user wants to cancel dragging.
|
|
*/
|
|
if (pMenuState->fDragging && (key == VK_ESCAPE)) {
|
|
RIPMSG0(RIP_WARNING, "xxxMNKeyDown: ESC while dragging");
|
|
pMenuState->fIgnoreButtonUp = TRUE;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
switch (key) {
|
|
case VK_MENU:
|
|
case VK_F10:
|
|
/*
|
|
* Modeless don't go away when the menu key is hit. They just
|
|
* ignore it.
|
|
*/
|
|
if (pMenuState->fModelessMenu) {
|
|
return;
|
|
}
|
|
|
|
xxxMNDismiss(pMenuState);
|
|
|
|
/*
|
|
* We're going to exit menu mode but the ALT key is down, so clear
|
|
* pMenuState->fUnderline to cause xxxMNLoop not to erase the
|
|
* underlines.
|
|
*/
|
|
if (key == VK_MENU) {
|
|
pMenuState->fUnderline = FALSE;
|
|
}
|
|
return;
|
|
|
|
case VK_ESCAPE:
|
|
|
|
/*
|
|
* Escape key was hit. Get out of one level of menus. If no active
|
|
* popups or we are minimized and there are no active popups below
|
|
* this, we need to get out of menu mode. Otherwise, we popup up
|
|
* one level in the hierarchy.
|
|
*/
|
|
if (ppopupmenu->fIsMenuBar ||
|
|
ppopupmenu == ppopupmenu->ppopupmenuRoot ||
|
|
TestWF(ppopupmenu->ppopupmenuRoot->spwndNotify, WFMINIMIZED)) {
|
|
xxxMNDismiss(pMenuState);
|
|
} else {
|
|
/*
|
|
* Pop back one level of menus.
|
|
*/
|
|
if (ppopupmenu->fHasMenuBar &&
|
|
ppopupmenu->spwndPrevPopup == ppopupmenu->spwndNotify) {
|
|
|
|
PPOPUPMENU ppopupmenuRoot = ppopupmenu->ppopupmenuRoot;
|
|
|
|
ppopupmenuRoot->fDropNextPopup = FALSE;
|
|
|
|
#if 0
|
|
/*
|
|
* We are on a menu bar hierarchy and there is only one popup
|
|
* visible. We have to cancel this popup and put focus back on
|
|
* the menu bar.
|
|
*/
|
|
if (_IsIconic(ppopupmenuRoot->spwndNotify)) {
|
|
|
|
/*
|
|
* However, if we are iconic there really is no menu
|
|
* bar so let's make it easier for users and get out
|
|
* of menu mode completely.
|
|
*/
|
|
xxxMNDismiss(pMenuState);
|
|
} else
|
|
#endif
|
|
/*
|
|
* If the popup is closed, a modeless menu won't
|
|
* have a window to get the keys. So modeless menu
|
|
* cancel the menu at this point. Modal menus go
|
|
* to the menu bar.
|
|
*/
|
|
if (pMenuState->fModelessMenu) {
|
|
xxxMNDismiss(pMenuState);
|
|
} else {
|
|
xxxMNCloseHierarchy(ppopupmenuRoot, pMenuState);
|
|
}
|
|
} else {
|
|
ThreadLock(ppopupmenu->spwndPrevPopup, &tlpwndT);
|
|
xxxSendMessage(ppopupmenu->spwndPrevPopup, MN_CLOSEHIERARCHY,
|
|
0, 0);
|
|
ThreadUnlock(&tlpwndT);
|
|
}
|
|
}
|
|
return;
|
|
|
|
case VK_UP:
|
|
case VK_DOWN:
|
|
if (ppopupmenu->fIsMenuBar) {
|
|
|
|
/*
|
|
* If we are on the top level menu bar, try to open the popup if
|
|
* possible.
|
|
*/
|
|
if (xxxMNOpenHierarchy(ppopupmenu, pMenuState) == (PWND)-1)
|
|
return;
|
|
} else {
|
|
item = MNFindNextValidItem(ppopupmenu->spmenu,
|
|
ppopupmenu->posSelectedItem, (key == VK_UP ? -1 : 1), 0);
|
|
xxxMNSelectItem(ppopupmenu, pMenuState, item);
|
|
}
|
|
return;
|
|
|
|
case VK_LEFT:
|
|
case VK_RIGHT:
|
|
bFakedKey = (!!ppopupmenu->fRtoL) ^ (!!TestWF(ppopupmenu->spwndPopupMenu, WEFLAYOUTRTL));
|
|
if (bFakedKey)
|
|
/*
|
|
* turn the keys around, we drew the menu backwards.
|
|
*/
|
|
key = (key == VK_LEFT) ? VK_RIGHT : VK_LEFT;
|
|
if (!ppopupmenu->fIsMenuBar && (key == VK_RIGHT) &&
|
|
!ppopupmenu->spwndNextPopup) {
|
|
/*
|
|
* Try to open the hierarchy at this item if there is one.
|
|
*/
|
|
if (xxxMNOpenHierarchy(ppopupmenu, pMenuState) == (PWND)-1)
|
|
return;
|
|
if (ppopupmenu->fHierarchyDropped) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ppopupmenu->spwndNextPopup) {
|
|
fHierarchyWasDropped = TRUE;
|
|
if ((key == VK_LEFT) && !ppopupmenu->fIsMenuBar) {
|
|
xxxMNCloseHierarchy(ppopupmenu, pMenuState);
|
|
return;
|
|
}
|
|
} else if (ppopupmenu->fDropNextPopup)
|
|
fHierarchyWasDropped = TRUE;
|
|
|
|
ppopupSave = ppopupmenu;
|
|
|
|
item = MNFindItemInColumn(ppopupmenu->spmenu,
|
|
ppopupmenu->posSelectedItem,
|
|
(key == VK_LEFT ? -1 : 1),
|
|
(ppopupmenu->fHasMenuBar &&
|
|
ppopupmenu == ppopupmenu->ppopupmenuRoot));
|
|
|
|
if (item == MFMWFP_NOITEM) {
|
|
|
|
/*
|
|
* No valid item found in the given direction so send it up to our
|
|
* parent to handle.
|
|
*/
|
|
if (ppopupmenu->fHasMenuBar &&
|
|
ppopupmenu->spwndPrevPopup == ppopupmenu->spwndNotify) {
|
|
|
|
/*
|
|
* if we turned the key round, then turn it back again.
|
|
*/
|
|
if (bFakedKey)
|
|
key = (key == VK_LEFT) ? VK_RIGHT : VK_LEFT;
|
|
/*
|
|
* Go to next/prev item in menu bar since a popup was down and
|
|
* no item on the popup to go to.
|
|
*/
|
|
xxxMNKeyDown(ppopupmenu->ppopupmenuRoot, pMenuState, key);
|
|
return;
|
|
}
|
|
|
|
if (ppopupmenu == ppopupmenu->ppopupmenuRoot) {
|
|
if (!ppopupmenu->fIsMenuBar) {
|
|
|
|
/*
|
|
* No menu bar associated with this menu so do nothing.
|
|
*/
|
|
return;
|
|
}
|
|
} else {
|
|
ThreadLock(ppopupmenu->spwndPrevPopup, &tlpwndT);
|
|
xxxSendMessage(ppopupmenu->spwndPrevPopup, WM_KEYDOWN, keyOrig, 0);
|
|
ThreadUnlock(&tlpwndT);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!ppopupmenu->fIsMenuBar) {
|
|
if (item != MFMWFP_NOITEM) {
|
|
xxxMNSelectItem(ppopupmenu, pMenuState, item);
|
|
}
|
|
return;
|
|
|
|
} else {
|
|
|
|
/*
|
|
* Special handling if keydown occurred on a menu bar.
|
|
*/
|
|
if (item == MFMWFP_NOITEM) {
|
|
|
|
if (TestWF(ppopupmenu->spwndNotify, WFSYSMENU)) {
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
PWND pwndNextMenu;
|
|
PMENU pmenuNextMenu, pmenuUse;
|
|
MDINEXTMENU mnm;
|
|
TL tlpmenuNextMenu;
|
|
TL tlpwndNextMenu;
|
|
|
|
mnm.hmenuIn = (HMENU)0;
|
|
mnm.hmenuNext = (HMENU)0;
|
|
mnm.hwndNext = (HWND)0;
|
|
|
|
/*
|
|
* We are in the menu bar and need to go up to the system menu
|
|
* or go from the system menu to the menu bar.
|
|
*/
|
|
pmenuNextMenu = ppopupmenu->fIsSysMenu ?
|
|
_GetSubMenu(ppopupmenu->spmenu, 0) :
|
|
ppopupmenu->spmenu;
|
|
mnm.hmenuIn = PtoH(pmenuNextMenu);
|
|
ThreadLock(ppopupmenu->spwndNotify, &tlpwndT);
|
|
dwMDIMenu = xxxSendMessage(ppopupmenu->spwndNotify,
|
|
WM_NEXTMENU, (WPARAM)keyOrig, (LPARAM)&mnm);
|
|
ThreadUnlock(&tlpwndT);
|
|
|
|
pwndNextMenu = RevalidateHwnd(mnm.hwndNext);
|
|
if (pwndNextMenu == NULL)
|
|
goto TryAlternate;
|
|
|
|
/*
|
|
* If this window belongs to another thread, we cannot
|
|
* use it. The menu loop won't get any messages
|
|
* directed to that thread.
|
|
*/
|
|
if (GETPTI(pwndNextMenu) != ptiCurrent) {
|
|
RIPMSG1(RIP_WARNING, "xxxMNKeyDown: Ignoring mnm.hwndNext bacause it belongs to another thread: %#p", pwndNextMenu);
|
|
goto TryAlternate;
|
|
}
|
|
|
|
|
|
pmenuNextMenu = RevalidateHmenu(mnm.hmenuNext);
|
|
if (pmenuNextMenu == NULL)
|
|
goto TryAlternate;
|
|
|
|
ThreadLock(pmenuNextMenu, &tlpmenuNextMenu);
|
|
ThreadLock(pwndNextMenu, &tlpwndNextMenu);
|
|
|
|
/*
|
|
* If the system menu is for a minimized MDI child,
|
|
* make sure the menu is dropped to give the user a
|
|
* visual clue that they are in menu mode
|
|
*/
|
|
if (TestWF(pwndNextMenu, WFMINIMIZED))
|
|
fHierarchyWasDropped = TRUE;
|
|
|
|
xxxMNSelectItem(ppopupmenu, pMenuState, MFMWFP_NOITEM);
|
|
|
|
pMenuState->fIsSysMenu = TRUE;
|
|
UnlockPopupMenu(ppopupmenu, &ppopupmenu->spmenuAlternate);
|
|
ppopupmenu->fToggle = FALSE;
|
|
/*
|
|
* GetSystemMenu(pwnd, FALSE) and pwnd->spmenuSys are
|
|
* NOT equivalent -- GetSystemMenu returns the 1st submenu
|
|
* of pwnd->spmenuSys -- make up for that here
|
|
*/
|
|
pmenuUse = (((pwndNextMenu->spmenuSys != NULL)
|
|
&& (_GetSubMenu(pwndNextMenu->spmenuSys, 0) == pmenuNextMenu))
|
|
? pwndNextMenu->spmenuSys
|
|
: pmenuNextMenu);
|
|
/*
|
|
* We're going to change the notification window AND the menu.
|
|
* LockPopupMenu needs to unlock the current pmenu-spwndNotify
|
|
* but also lock the new pmenu-spwndNotify. Since we cannot
|
|
* give it the current AND the new pair, we unlock the
|
|
* current one first, switch the notification window and
|
|
* then call LockPopupMenu to lock the new pmenu-spwndNotify.
|
|
*/
|
|
UserAssert(IsRootPopupMenu(ppopupmenu));
|
|
UnlockPopupMenu(ppopupmenu, &ppopupmenu->spmenu);
|
|
Lock(&ppopupmenu->spwndNotify, pwndNextMenu);
|
|
Lock(&ppopupmenu->spwndPopupMenu, pwndNextMenu);
|
|
LockPopupMenu(ppopupmenu, &ppopupmenu->spmenu, pmenuUse);
|
|
/*
|
|
* We just switched to a new notification window so
|
|
* we need to Adjust capture accordingly
|
|
*/
|
|
if (!pMenuState->fModelessMenu) {
|
|
ptiCurrent->pq->QF_flags &= ~QF_CAPTURELOCKED;
|
|
xxxMNSetCapture(ppopupmenu);
|
|
}
|
|
|
|
|
|
if (!TestWF(pwndNextMenu, WFCHILD) &&
|
|
ppopupmenu->spmenu != NULL) {
|
|
|
|
/*
|
|
* This window has a system menu and a main menu bar
|
|
* Set the alternate menu to the appropriate menu
|
|
*/
|
|
if (pwndNextMenu->spmenu == ppopupmenu->spmenu) {
|
|
LockPopupMenu(ppopupmenu, &ppopupmenu->spmenuAlternate,
|
|
pwndNextMenu->spmenuSys);
|
|
pMenuState->fIsSysMenu = FALSE;
|
|
} else {
|
|
LockPopupMenu(ppopupmenu, &ppopupmenu->spmenuAlternate,
|
|
pwndNextMenu->spmenu);
|
|
}
|
|
}
|
|
|
|
ThreadUnlock(&tlpwndNextMenu);
|
|
ThreadUnlock(&tlpmenuNextMenu);
|
|
|
|
ppopupmenu->fIsSysMenu = pMenuState->fIsSysMenu;
|
|
|
|
item = 0;
|
|
} else
|
|
TryAlternate:
|
|
if (xxxMNSwitchToAlternateMenu(ppopupmenu)) {
|
|
/*
|
|
* go to first or last menu item int ppopup->hMenu
|
|
* based on 'key'
|
|
*/
|
|
int dir = (key == VK_RIGHT) ? 1 : -1;
|
|
|
|
item = MNFindNextValidItem(ppopupmenu->spmenu, MFMWFP_NOITEM, dir, 0);
|
|
}
|
|
}
|
|
|
|
if (item != MFMWFP_NOITEM) {
|
|
/*
|
|
* we found a new menu item to go to
|
|
* 1) close up the previous menu if it was dropped
|
|
* 2) select the new menu item to go to
|
|
* 3) drop the new menu if the previous menu was dropped
|
|
*/
|
|
|
|
if (ppopupSave->spwndNextPopup)
|
|
xxxMNCloseHierarchy(ppopupSave, pMenuState);
|
|
|
|
xxxMNSelectItem(ppopupmenu, pMenuState, item);
|
|
|
|
if (fHierarchyWasDropped) {
|
|
DropHierarchy:
|
|
if (xxxMNOpenHierarchy(ppopupmenu, pMenuState) == (PWND)-1) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
|
|
case VK_RETURN:
|
|
{
|
|
BOOL fEnabled;
|
|
PITEM pItem;
|
|
|
|
if (ppopupmenu->posSelectedItem >= ppopupmenu->spmenu->cItems) {
|
|
xxxMNDismiss(pMenuState);
|
|
return;
|
|
}
|
|
|
|
pItem = ppopupmenu->spmenu->rgItems + ppopupmenu->posSelectedItem;
|
|
fEnabled = !(pItem->fState & MFS_GRAYED);
|
|
if ((pItem->spSubMenu != NULL) && fEnabled)
|
|
goto DropHierarchy;
|
|
|
|
/*
|
|
* If no item is selected, throw away menu and return.
|
|
*/
|
|
if (fEnabled) {
|
|
xxxMNDismissWithNotify(pMenuState, ppopupmenu->spmenu, pItem, ppopupmenu->posSelectedItem, 0);
|
|
} else {
|
|
xxxMNDismiss(pMenuState);
|
|
}
|
|
return;
|
|
}
|
|
|
|
case VK_F1: /* Provide context sensitive help. */
|
|
{
|
|
PITEM pItem;
|
|
|
|
pItem = MNGetpItem(ppopupmenu, ppopupmenu->posSelectedItem);
|
|
if (pItem != NULL) {
|
|
ThreadLock(ppopupmenu->spwndNotify, &tlpwndT);
|
|
xxxSendHelpMessage(ppopupmenu->spwndNotify, HELPINFO_MENUITEM, pItem->wID,
|
|
PtoHq(ppopupmenu->spmenu),
|
|
GetMenuInheritedContextHelpId(ppopupmenu));
|
|
ThreadUnlock(&tlpwndT);
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
/***************************************************************************\
|
|
* xxxMNPositionHierarchy
|
|
*
|
|
* Calculates the x.y postion to drop a hierarchy and returns the direction
|
|
* to be used when animating (PAS_* value).
|
|
*
|
|
* 11/19/96 GerardoB Extracted from xxxMNOpenHierarchy
|
|
\***************************************************************************/
|
|
UINT xxxMNPositionHierarchy(
|
|
PPOPUPMENU ppopup,
|
|
PITEM pitem,
|
|
int cx,
|
|
int cy,
|
|
int *px,
|
|
int *py,
|
|
PMONITOR *ppMonitor)
|
|
{
|
|
int x, y;
|
|
UINT uPAS;
|
|
PMONITOR pMonitor;
|
|
|
|
UserAssert(ppopup->fHierarchyDropped && (ppopup->spwndNextPopup != NULL));
|
|
|
|
if (ppopup->fIsMenuBar) {
|
|
/*
|
|
* This is a menu being dropped from the top menu bar. We need to
|
|
* position it differently than hierarchicals which are dropped from
|
|
* another popup.
|
|
*/
|
|
|
|
BOOL fIconic = (TestWF(ppopup->spwndPopupMenu, WFMINIMIZED) != 0);
|
|
RECT rcWindow;
|
|
|
|
/*
|
|
* Menu bar popups animate down.
|
|
*/
|
|
uPAS = PAS_DOWN;
|
|
|
|
CopyRect(&rcWindow, &ppopup->spwndPopupMenu->rcWindow);
|
|
if (fIconic && IsTrayWindow(ppopup->spwndPopupMenu)) {
|
|
xxxSendMinRectMessages(ppopup->spwndPopupMenu, &rcWindow);
|
|
}
|
|
|
|
/*
|
|
* x position
|
|
*/
|
|
if (!SYSMET(MENUDROPALIGNMENT) && !TestMF(ppopup->spmenu,MFRTL)) {
|
|
if (fIconic) {
|
|
x = rcWindow.left;
|
|
} else {
|
|
x = rcWindow.left + pitem->xItem;
|
|
}
|
|
} else {
|
|
ppopup->fDroppedLeft = TRUE;
|
|
if (fIconic) {
|
|
x = rcWindow.right - cx;
|
|
} else {
|
|
x = rcWindow.left + pitem->xItem + pitem->cxItem - cx;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* For a menu bar dropdown, pin to the monitor that owns the
|
|
* majority of the menu item. Otherwise, pin to the monitor that
|
|
* owns the minimized window (the tray rect for min-to-tray dudes).
|
|
*/
|
|
if (!fIconic) {
|
|
/*
|
|
* Use rcWindow as scratch for the menu bar item rect. We want
|
|
* to pin this menu on whatever monitor owns most of the menu
|
|
* item clicked on.
|
|
*/
|
|
rcWindow.left += pitem->xItem;
|
|
rcWindow.top += pitem->yItem;
|
|
rcWindow.right = rcWindow.left + pitem->cxItem;
|
|
rcWindow.bottom = rcWindow.top + pitem->cyItem;
|
|
}
|
|
|
|
pMonitor = _MonitorFromRect(&rcWindow, MONITOR_DEFAULTTOPRIMARY);
|
|
|
|
/*
|
|
* y position
|
|
*/
|
|
if (!fIconic) {
|
|
y = rcWindow.bottom;
|
|
} else {
|
|
/*
|
|
* If the window is iconic, pop the menu up. Since we're
|
|
* minimized, the sysmenu button doesn't really exist.
|
|
*/
|
|
y = rcWindow.top - cy;
|
|
if (y < pMonitor->rcMonitor.top) {
|
|
y = rcWindow.bottom;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Make sure the menu doesn't go off the right side of the monitor
|
|
*/
|
|
x = min(x, pMonitor->rcMonitor.right - cx);
|
|
|
|
if (TestWF(ppopup->spwndPopupMenu, WEFLAYOUTRTL)) {
|
|
x = ppopup->spwndPopupMenu->rcWindow.right - x + ppopup->spwndPopupMenu->rcWindow.left - cx;
|
|
}
|
|
} else {
|
|
|
|
/* Now position the hierarchical menu window.
|
|
* We want to overlap by the amount of the frame, to help in the
|
|
* 3D illusion.
|
|
*/
|
|
|
|
/*
|
|
* By default, hierachical popups animate to the right
|
|
*/
|
|
uPAS = PAS_RIGHT;
|
|
x = ppopup->spwndPopupMenu->rcWindow.left + pitem->xItem + pitem->cxItem;
|
|
|
|
/* Note that we DO want the selections in the item and its popup to
|
|
* align horizontally.
|
|
*/
|
|
y = ppopup->spwndPopupMenu->rcWindow.top + pitem->yItem;
|
|
if (ppopup->spmenu->dwArrowsOn != MSA_OFF) {
|
|
y += gcyMenuScrollArrow - MNGetToppItem(ppopup->spmenu)->yItem;
|
|
}
|
|
|
|
/*
|
|
* Try to make sure the menu doesn't go off right side of the
|
|
* monitor. If it does, drop it left, overlapping the checkmark
|
|
* area. Unless it would cover the previous menu...
|
|
*
|
|
* Use the monitor that the parent menu is on to keep all hierarchicals
|
|
* in the same place.
|
|
*/
|
|
pMonitor = _MonitorFromWindow(
|
|
ppopup->spwndPopupMenu, MONITOR_DEFAULTTOPRIMARY);
|
|
|
|
if ((!!ppopup->fDroppedLeft) ^ (!!TestWF(ppopup->spwndPopupMenu, WEFLAYOUTRTL))) {
|
|
int xTmp;
|
|
|
|
/*
|
|
* If this menu has dropped left, see if our hierarchy can be made
|
|
* to drop to the left also.
|
|
*/
|
|
xTmp = ppopup->spwndPopupMenu->rcWindow.left + SYSMET(CXFIXEDFRAME) - cx;
|
|
if (xTmp >= pMonitor->rcMonitor.left) {
|
|
x = xTmp;
|
|
uPAS = PAS_LEFT;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Make sure the menu doesn't go off right side of screen. Make it drop
|
|
* left if it does.
|
|
*/
|
|
if (x + cx > pMonitor->rcMonitor.right) {
|
|
x = ppopup->spwndPopupMenu->rcWindow.left + SYSMET(CXFIXEDFRAME) - cx;
|
|
uPAS = PAS_LEFT;
|
|
}
|
|
|
|
if (TestWF(ppopup->spwndPopupMenu, WEFLAYOUTRTL)) {
|
|
uPAS ^= PAS_HORZ;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Does the menu extend beyond bottom of monitor?
|
|
*/
|
|
UserAssert(pMonitor);
|
|
if (y + cy > pMonitor->rcMonitor.bottom) {
|
|
y -= cy;
|
|
|
|
/*
|
|
* Try to pop above menu bar first
|
|
*/
|
|
if (ppopup->fIsMenuBar) {
|
|
y -= SYSMET(CYMENUSIZE);
|
|
if (y >= pMonitor->rcMonitor.top) {
|
|
uPAS = PAS_UP;
|
|
}
|
|
} else {
|
|
/*
|
|
* Account for nonclient frame above & below
|
|
*/
|
|
y += pitem->cyItem + 2*SYSMET(CYFIXEDFRAME);
|
|
}
|
|
|
|
/*
|
|
* Make sure that starting point is on a monitor, and all of menu shows.
|
|
*/
|
|
if ((y < pMonitor->rcMonitor.top) || (y + cy > pMonitor->rcMonitor.bottom)) {
|
|
/*
|
|
* Pin it to the bottom.
|
|
*/
|
|
y = pMonitor->rcMonitor.bottom - cy;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Make sure Upper Left corner of menu is always visible.
|
|
*/
|
|
x = max(x, pMonitor->rcMonitor.left);
|
|
y = max(y, pMonitor->rcMonitor.top);
|
|
|
|
/*
|
|
* Propagate position
|
|
*/
|
|
*px = x;
|
|
*py = y;
|
|
*ppMonitor = pMonitor;
|
|
|
|
/*
|
|
* Return animation direction
|
|
*/
|
|
return uPAS;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNOpenHierarchy
|
|
*
|
|
* Drops one level of the hierarchy at the selection.
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
\***************************************************************************/
|
|
PWND xxxMNOpenHierarchy(
|
|
PPOPUPMENU ppopupmenu,
|
|
PMENUSTATE pMenuState)
|
|
{
|
|
PWND ret = 0;
|
|
PITEM pItem;
|
|
PWND pwndHierarchy;
|
|
PPOPUPMENU ppopupmenuHierarchy;
|
|
LONG sizeHierarchy;
|
|
int xLeft;
|
|
int yTop;
|
|
int cxPopup, cyPopup;
|
|
TL tlpwndT;
|
|
TL tlpwndHierarchy;
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
PDESKTOP pdesk = ptiCurrent->rpdesk;
|
|
BOOL fSendUninit = FALSE;
|
|
HMENU hmenuInit;
|
|
PMONITOR pMonitor;
|
|
|
|
|
|
if (ppopupmenu->posSelectedItem == MFMWFP_NOITEM) {
|
|
/*
|
|
* No selection so fail.
|
|
*/
|
|
return NULL;
|
|
}
|
|
|
|
if (ppopupmenu->posSelectedItem >= ppopupmenu->spmenu->cItems)
|
|
return NULL;
|
|
|
|
if (ppopupmenu->fHierarchyDropped) {
|
|
if (ppopupmenu->fHideTimer) {
|
|
xxxMNCloseHierarchy(ppopupmenu,pMenuState);
|
|
} else {
|
|
/*
|
|
* Hierarchy already dropped. What are we doing here?
|
|
*/
|
|
UserAssert(!ppopupmenu->fHierarchyDropped);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (ppopupmenu->fShowTimer) {
|
|
_KillTimer(ppopupmenu->spwndPopupMenu, IDSYS_MNSHOW);
|
|
ppopupmenu->fShowTimer = FALSE;
|
|
}
|
|
|
|
/*
|
|
* Get a pointer to the currently selected item in this menu.
|
|
*/
|
|
pItem = &(ppopupmenu->spmenu->rgItems[ppopupmenu->posSelectedItem]);
|
|
|
|
if (pItem->spSubMenu == NULL)
|
|
goto Exit;
|
|
|
|
/*
|
|
* Send the initmenupopup message.
|
|
*/
|
|
if (!ppopupmenu->fNoNotify) {
|
|
ThreadLock(ppopupmenu->spwndNotify, &tlpwndT);
|
|
/*
|
|
* WordPerfect's Grammatik app doesn't know that TRUE means NON-ZERO,
|
|
* not 1. So we must use 0 & 1 explicitly for fIsSysMenu here
|
|
* -- Win95B B#4947 -- 2/13/95 -- jeffbog
|
|
*/
|
|
hmenuInit = PtoHq(pItem->spSubMenu);
|
|
xxxSendMessage(ppopupmenu->spwndNotify, WM_INITMENUPOPUP,
|
|
(WPARAM)hmenuInit, MAKELONG(ppopupmenu->posSelectedItem,
|
|
(ppopupmenu->fIsSysMenu ? 1: 0)));
|
|
ThreadUnlock(&tlpwndT);
|
|
fSendUninit = TRUE;
|
|
}
|
|
|
|
|
|
/*
|
|
* B#1517
|
|
* Check if we're still in menu loop
|
|
*/
|
|
if (!pMenuState->fInsideMenuLoop) {
|
|
RIPMSG0(RIP_WARNING, "Menu loop ended unexpectedly by WM_INITMENUPOPUP");
|
|
ret = (PWND)-1;
|
|
goto Exit;
|
|
}
|
|
|
|
/*
|
|
* The WM_INITMENUPOPUP message may have resulted in a change to the
|
|
* menu. Make sure the selection is still valid.
|
|
*/
|
|
if (ppopupmenu->posSelectedItem >= ppopupmenu->spmenu->cItems) {
|
|
/*
|
|
* Selection is out of range, so fail.
|
|
*/
|
|
goto Exit;
|
|
}
|
|
|
|
/*
|
|
* Get a pointer to the currently selected item in this menu.
|
|
* Bug #17867 - the call can cause this thing to change, so reload it.
|
|
*/
|
|
pItem = &(ppopupmenu->spmenu->rgItems[ppopupmenu->posSelectedItem]);
|
|
|
|
if (TestMFS(pItem, MFS_GRAYED) || (pItem->spSubMenu == NULL) || (pItem->spSubMenu->cItems == 0)) {
|
|
/*
|
|
* The item is disabled, no longer a popup, or empty so don't drop.
|
|
*/
|
|
/*
|
|
* No items in menu.
|
|
*/
|
|
goto Exit;
|
|
}
|
|
|
|
/*
|
|
* Let's make sure that the current thread is in menu mode and it uses
|
|
* this pMenuState. Otherwise the window we're about to create (or set
|
|
* the thread to) will point to a different pMenuState.
|
|
*/
|
|
UserAssert(ptiCurrent->pMenuState == pMenuState);
|
|
|
|
ThreadLock(ppopupmenu->spwndNotify, &tlpwndT);
|
|
|
|
pwndHierarchy = xxxNVCreateWindowEx(
|
|
WS_EX_TOOLWINDOW | WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE,
|
|
(PLARGE_STRING)MENUCLASS, NULL,
|
|
WS_POPUP | WS_BORDER, 0, 0, 100, 100, ppopupmenu->spwndNotify,
|
|
NULL, (HANDLE)ppopupmenu->spwndNotify->hModule,
|
|
(LPVOID)pItem->spSubMenu, WINVER);
|
|
|
|
ThreadUnlock(&tlpwndT);
|
|
|
|
if (!pwndHierarchy) {
|
|
goto Exit;
|
|
}
|
|
|
|
/*
|
|
* Do this so old apps don't get weird borders on the popups of
|
|
* hierarchical items!
|
|
*/
|
|
ClrWF(pwndHierarchy, WFOLDUI);
|
|
|
|
ppopupmenuHierarchy = ((PMENUWND)pwndHierarchy)->ppopupmenu;
|
|
|
|
/*
|
|
* Mark this as fDelayedFree and link it
|
|
*/
|
|
ppopupmenuHierarchy->fDelayedFree = TRUE;
|
|
ppopupmenuHierarchy->ppmDelayedFree = ppopupmenu->ppopupmenuRoot->ppmDelayedFree;
|
|
ppopupmenu->ppopupmenuRoot->ppmDelayedFree = ppopupmenuHierarchy;
|
|
|
|
if (TestWF(ppopupmenu->spwndPopupMenu, WEFLAYOUTRTL)) {
|
|
SetWF(pwndHierarchy, WEFLAYOUTRTL);
|
|
} else {
|
|
ClrWF(pwndHierarchy, WEFLAYOUTRTL);
|
|
}
|
|
|
|
Lock(&(ppopupmenuHierarchy->spwndNotify), ppopupmenu->spwndNotify);
|
|
#if DBG
|
|
/*
|
|
* We should associate ppopupmenuHierarchy to the same menu we sent the
|
|
* WM_INITMsENUPOPUP message. Otherwise, the WM_UNINITMENUPOPUP
|
|
* will go to the wrong window. It would be the app's fault...
|
|
*/
|
|
if (!ppopupmenu->fNoNotify && (hmenuInit != PtoHq(pItem->spSubMenu))) {
|
|
RIPMSG2(RIP_WARNING, "xxxMNOpenHierarchy: bad app changed submenu from %#p to %#p",
|
|
hmenuInit, PtoHq(pItem->spSubMenu));
|
|
}
|
|
#endif
|
|
LockPopupMenu(ppopupmenuHierarchy, &ppopupmenuHierarchy->spmenu, pItem->spSubMenu);
|
|
Lock(&(ppopupmenu->spwndNextPopup), pwndHierarchy);
|
|
ppopupmenu->posDropped = ppopupmenu->posSelectedItem;
|
|
Lock(&(ppopupmenuHierarchy->spwndPrevPopup), ppopupmenu->spwndPopupMenu);
|
|
ppopupmenuHierarchy->ppopupmenuRoot = ppopupmenu->ppopupmenuRoot;
|
|
ppopupmenuHierarchy->fHasMenuBar = ppopupmenu->fHasMenuBar;
|
|
ppopupmenuHierarchy->fIsSysMenu = ppopupmenu->fIsSysMenu;
|
|
ppopupmenuHierarchy->fNoNotify = ppopupmenu->fNoNotify;
|
|
ppopupmenuHierarchy->fSendUninit = TRUE;
|
|
ppopupmenuHierarchy->fRtoL = ppopupmenu->fRtoL;
|
|
ppopupmenuHierarchy->fDroppedLeft = ppopupmenu->fDroppedLeft;
|
|
|
|
/*
|
|
* The menu window has been created and intialized so if
|
|
* something fails, the WM_UNINITMENUPOPUP message will
|
|
* be sent from xxxMNDestroyHandler
|
|
*/
|
|
fSendUninit = FALSE;
|
|
|
|
/*
|
|
* Set/clear the underline flag
|
|
*/
|
|
if (pMenuState->fUnderline) {
|
|
SetMF(ppopupmenuHierarchy->spmenu, MFUNDERLINE);
|
|
} else {
|
|
ClearMF(ppopupmenuHierarchy->spmenu, MFUNDERLINE);
|
|
}
|
|
|
|
ppopupmenuHierarchy->fAboutToHide = FALSE;
|
|
|
|
/*
|
|
* Find the size of the menu window and actually size it (wParam = 1)
|
|
*/
|
|
ThreadLock(pwndHierarchy, &tlpwndHierarchy);
|
|
sizeHierarchy = (LONG)xxxSendMessage(pwndHierarchy, MN_SIZEWINDOW, MNSW_SIZE, 0);
|
|
|
|
if (!sizeHierarchy) {
|
|
/*
|
|
* No size for this menu so zero it and blow off.
|
|
*/
|
|
UserAssert(ppopupmenuHierarchy->fDelayedFree);
|
|
|
|
if (ThreadUnlock(&tlpwndHierarchy)) {
|
|
xxxDestroyWindow(pwndHierarchy);
|
|
}
|
|
|
|
Unlock(&ppopupmenu->spwndNextPopup);
|
|
goto Exit;
|
|
}
|
|
|
|
cxPopup = LOWORD(sizeHierarchy) + 2*SYSMET(CXFIXEDFRAME);
|
|
cyPopup = HIWORD(sizeHierarchy) + 2*SYSMET(CYFIXEDFRAME);
|
|
|
|
ppopupmenu->fHierarchyDropped = TRUE;
|
|
|
|
/*
|
|
* Find out the x,y position to drop the hierarchy and the animation
|
|
* direction
|
|
*/
|
|
ppopupmenuHierarchy->iDropDir = xxxMNPositionHierarchy(
|
|
ppopupmenu, pItem, cxPopup, cyPopup, &xLeft, &yTop, &pMonitor);
|
|
|
|
if (ppopupmenu->fIsMenuBar && _GetAsyncKeyState(VK_LBUTTON) & 0x8000) {
|
|
/*
|
|
* If the menu had to be pinned to the bottom of the screen and
|
|
* the mouse button is down, make sure the mouse isn't over the
|
|
* menu rect.
|
|
*/
|
|
RECT rc;
|
|
RECT rcParent;
|
|
int xrightdrop;
|
|
int xleftdrop;
|
|
|
|
/*
|
|
* Get rect of hierarchical
|
|
*/
|
|
CopyOffsetRect(
|
|
&rc,
|
|
&pwndHierarchy->rcWindow,
|
|
xLeft - pwndHierarchy->rcWindow.left,
|
|
yTop - pwndHierarchy->rcWindow.top);
|
|
|
|
/*
|
|
* Get the rect of the menu bar popup item
|
|
*/
|
|
rcParent.left = pItem->xItem + ppopupmenu->spwndPopupMenu->rcWindow.left;
|
|
rcParent.top = pItem->yItem + ppopupmenu->spwndPopupMenu->rcWindow.top;
|
|
rcParent.right = rcParent.left + pItem->cxItem;
|
|
rcParent.bottom = rcParent.top + pItem->cyItem;
|
|
|
|
if (IntersectRect(&rc, &rc, &rcParent)) {
|
|
|
|
/*
|
|
* Oh, oh... The cursor will sit right on top of a menu item.
|
|
* If the user up clicks, a menu will be accidently selected.
|
|
*
|
|
* Calc x position of hierarchical if we dropped it to the
|
|
* right/left of the menu bar item.
|
|
*/
|
|
xrightdrop = ppopupmenu->spwndPopupMenu->rcWindow.left +
|
|
pItem->xItem + pItem->cxItem + cxPopup;
|
|
|
|
if (xrightdrop > pMonitor->rcMonitor.right) {
|
|
xrightdrop = 0;
|
|
}
|
|
|
|
xleftdrop = ppopupmenu->spwndPopupMenu->rcWindow.left +
|
|
pItem->xItem - cxPopup;
|
|
|
|
if (xleftdrop < pMonitor->rcMonitor.left) {
|
|
xleftdrop = 0;
|
|
}
|
|
|
|
if (((SYSMET(MENUDROPALIGNMENT) || TestMFT(pItem, MFT_RIGHTORDER))
|
|
&& xleftdrop) || !xrightdrop) {
|
|
xLeft = ppopupmenu->spwndPopupMenu->rcWindow.left +
|
|
pItem->xItem - cxPopup;
|
|
ppopupmenuHierarchy->iDropDir = PAS_LEFT;
|
|
} else if (xrightdrop) {
|
|
xLeft = ppopupmenu->spwndPopupMenu->rcWindow.left +
|
|
pItem->xItem + pItem->cxItem;
|
|
ppopupmenuHierarchy->iDropDir = PAS_RIGHT;
|
|
}
|
|
|
|
/*
|
|
* If we're going to show the menu off the screen, move it to the
|
|
* right of the cursor. This might result in part of the
|
|
* menu being shown offscreen, but it's better then the entire
|
|
* thing being hidden (and will also ensure that the popup is not
|
|
* placed under the cursor). See bug #55045.
|
|
*/
|
|
if (xLeft <= pMonitor->rcMonitor.left) {
|
|
xLeft = rcParent.right;
|
|
ppopupmenuHierarchy->iDropDir = PAS_LEFT;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Take care of fDropNextPopup (menu bar) or fDroppedLeft (popups)
|
|
* Set animation flag
|
|
*/
|
|
if (ppopupmenu->fIsMenuBar) {
|
|
/*
|
|
* Only the first popup being dropped off the menu bar
|
|
* is animated.
|
|
*/
|
|
if (!ppopupmenu->fDropNextPopup) {
|
|
ppopupmenuHierarchy->iDropDir |= PAS_OUT;
|
|
}
|
|
|
|
/*
|
|
* Propagate right-to-left direction.
|
|
*/
|
|
if (ppopupmenu->fDroppedLeft || (ppopupmenuHierarchy->iDropDir == PAS_LEFT)) {
|
|
ppopupmenuHierarchy->fDroppedLeft = TRUE;
|
|
}
|
|
/*
|
|
* Once a popup is dropped from the menu bar, moving to the next
|
|
* item on the menu bar should drop the popup.
|
|
*/
|
|
ppopupmenu->fDropNextPopup = TRUE;
|
|
} else {
|
|
/*
|
|
* Submenus always animate.
|
|
*/
|
|
ppopupmenuHierarchy->iDropDir |= PAS_OUT;
|
|
|
|
/*
|
|
* Is this popup a lefty?
|
|
*/
|
|
if (ppopupmenuHierarchy->iDropDir == PAS_LEFT) {
|
|
ppopupmenuHierarchy->fDroppedLeft = TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The previous active dude must be visible
|
|
*/
|
|
UserAssert((ppopupmenu->ppopupmenuRoot->spwndActivePopup == NULL)
|
|
|| TestWF(ppopupmenu->ppopupmenuRoot->spwndActivePopup, WFVISIBLE));
|
|
|
|
/*
|
|
* This is the new active popup
|
|
*/
|
|
Lock(&(ppopupmenu->ppopupmenuRoot->spwndActivePopup), pwndHierarchy);
|
|
|
|
/*
|
|
* Paint the owner window before the popup menu comes up so that
|
|
* the proper bits are saved.
|
|
*/
|
|
if (ppopupmenuHierarchy->spwndNotify != NULL) {
|
|
ThreadLockAlways(ppopupmenuHierarchy->spwndNotify, &tlpwndT);
|
|
xxxUpdateWindow(ppopupmenuHierarchy->spwndNotify);
|
|
ThreadUnlock(&tlpwndT);
|
|
}
|
|
|
|
/*
|
|
* If this is a drag and drop menu, then we need to register the window
|
|
* as a drop target.
|
|
*/
|
|
if (pMenuState->fDragAndDrop) {
|
|
if (!NT_SUCCESS(xxxClientRegisterDragDrop(HW(pwndHierarchy)))) {
|
|
RIPMSG1(RIP_ERROR, "xxxMNOpenHierarchy: xxxClientRegisterDragDrop failed:%#p", pwndHierarchy);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Show the window. Modeless menus are not topmost and get activated.
|
|
* Modal menus are topmost but don't get activated.
|
|
*/
|
|
PlayEventSound(USER_SOUND_MENUPOPUP);
|
|
|
|
xxxSetWindowPos(pwndHierarchy,
|
|
(pMenuState->fModelessMenu ? PWND_TOP : PWND_TOPMOST),
|
|
xLeft, yTop, 0, 0,
|
|
SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOOWNERZORDER
|
|
| (pMenuState->fModelessMenu ? 0 : SWP_NOACTIVATE));
|
|
|
|
xxxWindowEvent(EVENT_SYSTEM_MENUPOPUPSTART, pwndHierarchy, OBJID_CLIENT, INDEXID_CONTAINER, 0);
|
|
|
|
/*
|
|
* Select the first item IFF we're in keyboard mode. This fixes a
|
|
* surprising number of compatibility problems with keyboard macros,
|
|
* scripts, etc.
|
|
*/
|
|
if (pMenuState->mnFocus == KEYBDHOLD) {
|
|
xxxSendMessage(pwndHierarchy, MN_SELECTITEM, 0, 0L);
|
|
}
|
|
|
|
/*
|
|
* This is needed so that popup menus are properly drawn on sys
|
|
* modal dialog boxes.
|
|
*/
|
|
xxxUpdateWindow(pwndHierarchy);
|
|
|
|
ret = pwndHierarchy;
|
|
ThreadUnlock(&tlpwndHierarchy);
|
|
|
|
Exit:
|
|
/*
|
|
* send matching WM_UNINITMENUPOPUP if needed (i.e, something
|
|
* failed).
|
|
*/
|
|
if (fSendUninit
|
|
&& (ppopupmenu->spwndNotify != NULL)) {
|
|
|
|
ThreadLockAlways(ppopupmenu->spwndNotify, &tlpwndT);
|
|
xxxSendMessage(ppopupmenu->spwndNotify, WM_UNINITMENUPOPUP,
|
|
(WPARAM)hmenuInit,
|
|
MAKELONG(0, (ppopupmenu->fIsSysMenu ? MF_SYSMENU : 0)));
|
|
ThreadUnlock(&tlpwndT);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNHideNextHierarchy
|
|
*
|
|
* Closes any submenu coming off of this popup.
|
|
\***************************************************************************/
|
|
BOOL xxxMNHideNextHierarchy(
|
|
PPOPUPMENU ppopup)
|
|
{
|
|
if (ppopup->spwndNextPopup != NULL) {
|
|
TL tlpwndT;
|
|
|
|
ThreadLockAlways(ppopup->spwndNextPopup, &tlpwndT);
|
|
if (ppopup->spwndNextPopup != ppopup->spwndActivePopup)
|
|
xxxSendMessage(ppopup->spwndNextPopup, MN_CLOSEHIERARCHY, 0, 0L);
|
|
|
|
xxxSendMessage(ppopup->spwndNextPopup, MN_SELECTITEM, (WPARAM)-1, 0L);
|
|
ThreadUnlock(&tlpwndT);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxMNCloseHierarchy
|
|
*
|
|
* Close all hierarchies from this window down.
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
\***************************************************************************/
|
|
VOID xxxMNCloseHierarchy(
|
|
PPOPUPMENU ppopupmenu,
|
|
PMENUSTATE pMenuState)
|
|
{
|
|
TL tlpwndNext;
|
|
TL tlpwnd;
|
|
TL tlpopup;
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
PDESKTOP pdesk;
|
|
PWND pwndNext;
|
|
|
|
Validateppopupmenu(ppopupmenu);
|
|
|
|
/*
|
|
* Terminate any animation
|
|
*/
|
|
MNAnimate(pMenuState, FALSE);
|
|
|
|
/*
|
|
* If a hierarchy exists, close all childen below us. Do it in reversed
|
|
* order so savebits work out.
|
|
*/
|
|
if (!ppopupmenu->fHierarchyDropped) {
|
|
/*
|
|
* Assert that there's no next or it might not get closed
|
|
*/
|
|
UserAssert(ppopupmenu->spwndNextPopup == NULL);
|
|
return;
|
|
}
|
|
|
|
if (ppopupmenu->fHideTimer)
|
|
{
|
|
_KillTimer(ppopupmenu->spwndPopupMenu, IDSYS_MNHIDE);
|
|
ppopupmenu->fHideTimer = FALSE;
|
|
}
|
|
|
|
pwndNext = ppopupmenu->spwndNextPopup;
|
|
if (pwndNext != NULL) {
|
|
|
|
ThreadLockAlways(pwndNext, &tlpwndNext);
|
|
xxxSendMessage(pwndNext, MN_CLOSEHIERARCHY, 0, 0);
|
|
|
|
/*
|
|
* If modeless menu, activate the this popup since we're about
|
|
* to destroy the current active one. We want to keep activation
|
|
* on a menu window so we can get the keys. Also, modeless menus
|
|
* are canceled when a non-menu window is activated in their queue
|
|
*/
|
|
if (pMenuState->fModelessMenu
|
|
&& pMenuState->fInsideMenuLoop
|
|
&& !ppopupmenu->fIsMenuBar) {
|
|
|
|
ThreadLockAlways(ppopupmenu->spwndPopupMenu, &tlpwnd);
|
|
xxxActivateThisWindow(ppopupmenu->spwndPopupMenu, 0, 0);
|
|
ThreadUnlock(&tlpwnd);
|
|
}
|
|
|
|
xxxWindowEvent(EVENT_SYSTEM_MENUPOPUPEND, pwndNext, OBJID_CLIENT, INDEXID_CONTAINER, 0);
|
|
|
|
/*
|
|
* If the current thread is not in the right pdesk, then that could
|
|
* be the cause of the stuck menu bug.
|
|
* In other words, are we nuking this menu out of context?
|
|
*/
|
|
UserAssert(ptiCurrent->pMenuState != NULL);
|
|
pdesk = ptiCurrent->rpdesk;
|
|
|
|
if (ThreadUnlock(&tlpwndNext)) {
|
|
xxxDestroyWindow(pwndNext);
|
|
}
|
|
|
|
Unlock(&ppopupmenu->spwndNextPopup);
|
|
ppopupmenu->fHierarchyDropped = FALSE;
|
|
}
|
|
|
|
if (ppopupmenu->fIsMenuBar) {
|
|
Unlock(&ppopupmenu->spwndActivePopup);
|
|
} else {
|
|
Lock(&(ppopupmenu->ppopupmenuRoot->spwndActivePopup),
|
|
ppopupmenu->spwndPopupMenu);
|
|
}
|
|
|
|
if (pMenuState->fInsideMenuLoop &&
|
|
(ppopupmenu->posSelectedItem != MFMWFP_NOITEM)) {
|
|
/*
|
|
* Send a menu select as if this item had just been selected. This
|
|
* allows people to easily update their menu status bars when a
|
|
* hierarchy from this item has been closed.
|
|
*/
|
|
PWND pwnd = ppopupmenu->ppopupmenuRoot->spwndNotify;
|
|
if (pwnd) {
|
|
ThreadLockAlways(pwnd, &tlpwnd);
|
|
ThreadLockAlways(ppopupmenu->spwndPopupMenu, &tlpopup);
|
|
xxxSendMenuSelect(pwnd, ppopupmenu->spwndPopupMenu,
|
|
ppopupmenu->spmenu, ppopupmenu->posSelectedItem);
|
|
ThreadUnlock(&tlpopup);
|
|
ThreadUnlock(&tlpwnd);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNDoubleClick
|
|
*
|
|
* If an item isn't a hierarchical, then the double-click works just like
|
|
* single click did. Otherwise, we traverse the submenu hierarchy to find
|
|
* a valid default element. If we reach a submenu that has no valid default
|
|
* subitems and it itself has a valid ID, that becomes the valid default
|
|
* element.
|
|
*
|
|
* Note: This function does not remove the double click message
|
|
* from the message queue, so the caller must do so.
|
|
*
|
|
* BOGUS
|
|
* How about opening the hierarchies if we don't find anything?
|
|
*
|
|
* Returns TRUE if handled.
|
|
\***************************************************************************/
|
|
BOOL xxxMNDoubleClick(
|
|
PMENUSTATE pMenuState,
|
|
PPOPUPMENU ppopup,
|
|
int idxItem)
|
|
{
|
|
PMENU pMenu;
|
|
PITEM pItem;
|
|
MSG msg;
|
|
UINT uPos;
|
|
|
|
/*
|
|
* This code to swallow double clicks isn't executed! MNLoop will
|
|
* swallow all double clicks for us. Swallow the up button for the
|
|
* double dude instead. Word will not be happy if they get a spurious
|
|
* WM_LBUTTONUP on the menu bar if their code to close the MDI child
|
|
* doesn't swallow it soon enough.
|
|
*/
|
|
|
|
/*
|
|
* Eat the click.
|
|
*/
|
|
if (xxxPeekMessage(&msg, NULL, 0, 0, PM_NOYIELD)) {
|
|
if ((msg.message == WM_LBUTTONUP) ||
|
|
(msg.message == WM_NCLBUTTONUP)) {
|
|
xxxPeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE);
|
|
}
|
|
#if DBG
|
|
else if (msg.message == WM_LBUTTONDBLCLK ||
|
|
msg.message == WM_NCLBUTTONDBLCLK)
|
|
{
|
|
UserAssertMsg0(FALSE, "xxxMNDoubleClick found a double click");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Get current item.
|
|
*/
|
|
pMenu = ppopup->spmenu;
|
|
if ((pMenu==NULL) || ((UINT)idxItem >= pMenu->cItems)) {
|
|
xxxMNDoScroll(ppopup, ppopup->posSelectedItem, FALSE);
|
|
goto Done;
|
|
}
|
|
|
|
pItem = pMenu->rgItems + idxItem;
|
|
uPos = idxItem;
|
|
|
|
/*
|
|
* Do nothing if item is disabled.
|
|
*/
|
|
if (pItem->fState & MFS_GRAYED) {
|
|
goto Done;
|
|
}
|
|
|
|
/*
|
|
* Traverse the hierarchy down as far as possible.
|
|
*/
|
|
do
|
|
{
|
|
if (pItem->spSubMenu != NULL) {
|
|
/*
|
|
* The item is a popup menu, so continue traversing.
|
|
*/
|
|
pMenu = pItem->spSubMenu;
|
|
idxItem = (UINT)_GetMenuDefaultItem(pMenu, MF_BYPOSITION, 0);
|
|
|
|
if (idxItem != -1) {
|
|
pItem = pMenu->rgItems + idxItem;
|
|
uPos = idxItem;
|
|
continue;
|
|
} else /* if (lpItem->wID == -1) How do we know this popup has an ID? */
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* We've found a leaf node of some kind, either a MFS_DEFAULT popup
|
|
* with a valid cmd ID that has no valid MFS_DEFAULT children, or
|
|
* a real cmd with MFS_DEFAULT style.
|
|
*
|
|
* Exit menu mode and send command ID.
|
|
*/
|
|
|
|
/*
|
|
* For old apps we need to generate a WM_MENUSELECT message first.
|
|
* Old apps, esp. Word 6.0, can't handle double-clicks on maximized
|
|
* child sys menus because they never get a WM_MENUSELECT for the
|
|
* item, unlike with normal keyboard/mouse choosing. We need to
|
|
* fake it so they won't fault. Several VB apps have a similar
|
|
* problem.
|
|
*/
|
|
if (!TestWF(ppopup->ppopupmenuRoot->spwndNotify, WFWIN40COMPAT)) {
|
|
TL tlpwndNotify, tlpopup;
|
|
|
|
ThreadLock(ppopup->ppopupmenuRoot->spwndNotify, &tlpwndNotify);
|
|
ThreadLock(ppopup->spwndPopupMenu, &tlpopup);
|
|
xxxSendMenuSelect(ppopup->ppopupmenuRoot->spwndNotify,
|
|
ppopup->spwndPopupMenu, pMenu, idxItem);
|
|
ThreadUnlock(&tlpopup);
|
|
ThreadUnlock(&tlpwndNotify);
|
|
}
|
|
|
|
xxxMNDismissWithNotify(pMenuState, pMenu, pItem, uPos, 0);
|
|
return TRUE;
|
|
} while (TRUE);
|
|
|
|
Done:
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxMNSelectItem
|
|
*
|
|
* Unselects the old selection, selects the item at itemPos and highlights it.
|
|
*
|
|
* MFMWFP_NOITEM if no item is to be selected.
|
|
*
|
|
* Returns the item flags of the item being selected.
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
\***************************************************************************/
|
|
PITEM xxxMNSelectItem(
|
|
PPOPUPMENU ppopupmenu,
|
|
PMENUSTATE pMenuState,
|
|
UINT itemPos)
|
|
{
|
|
PITEM pItem = NULL;
|
|
TL tlpwndNotify;
|
|
TL tlpwndPopup;
|
|
TL tlpmenu;
|
|
PWND pwndNotify;
|
|
PMENU pmenu;
|
|
|
|
if (ppopupmenu->posSelectedItem == itemPos) {
|
|
|
|
/*
|
|
* If this item is already selectected, just return its flags.
|
|
*/
|
|
if ((itemPos != MFMWFP_NOITEM) && (itemPos < ppopupmenu->spmenu->cItems)) {
|
|
return &(ppopupmenu->spmenu->rgItems[itemPos]);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Terminate any animation
|
|
*/
|
|
MNAnimate(pMenuState, FALSE);
|
|
|
|
if (ppopupmenu->fShowTimer) {
|
|
_KillTimer(ppopupmenu->spwndPopupMenu, IDSYS_MNSHOW);
|
|
ppopupmenu->fShowTimer = FALSE;
|
|
}
|
|
|
|
ThreadLock(pmenu = ppopupmenu->spmenu, &tlpmenu);
|
|
ThreadLock(pwndNotify = ppopupmenu->spwndNotify, &tlpwndNotify);
|
|
|
|
if (ppopupmenu->fAboutToHide) {
|
|
PPOPUPMENU ppopupPrev = ((PMENUWND)(ppopupmenu->spwndPrevPopup))->ppopupmenu;
|
|
|
|
_KillTimer(ppopupPrev->spwndPopupMenu, IDSYS_MNHIDE);
|
|
ppopupPrev->fHideTimer = FALSE;
|
|
if (ppopupPrev->fShowTimer) {
|
|
_KillTimer(ppopupPrev->spwndPopupMenu, IDSYS_MNSHOW);
|
|
ppopupPrev->fShowTimer = FALSE;
|
|
}
|
|
|
|
if (ppopupPrev->posSelectedItem != ppopupPrev->posDropped) {
|
|
TL tlpmenuPopupMenuPrev;
|
|
ThreadLock(ppopupPrev->spmenu, &tlpmenuPopupMenuPrev);
|
|
if (ppopupPrev->posSelectedItem != MFMWFP_NOITEM) {
|
|
xxxMNInvertItem(ppopupPrev, ppopupPrev->spmenu,
|
|
ppopupPrev->posSelectedItem, ppopupPrev->spwndNotify, FALSE);
|
|
}
|
|
|
|
ppopupPrev->posSelectedItem = ppopupPrev->posDropped;
|
|
|
|
xxxMNInvertItem(ppopupPrev, ppopupPrev->spmenu,
|
|
ppopupPrev->posDropped, ppopupPrev->spwndNotify, TRUE);
|
|
ThreadUnlock(&tlpmenuPopupMenuPrev);
|
|
}
|
|
|
|
ppopupmenu->fAboutToHide = FALSE;
|
|
Lock(&ppopupmenu->ppopupmenuRoot->spwndActivePopup, ppopupmenu->spwndPopupMenu);
|
|
}
|
|
|
|
if (MNIsItemSelected(ppopupmenu)) {
|
|
/*
|
|
* Something else is selected so we need to unselect it.
|
|
*/
|
|
if (ppopupmenu->spwndNextPopup) {
|
|
if (ppopupmenu->fIsMenuBar) {
|
|
xxxMNCloseHierarchy(ppopupmenu, pMenuState);
|
|
} else {
|
|
MNSetTimerToCloseHierarchy(ppopupmenu);
|
|
}
|
|
}
|
|
|
|
goto DeselectItem;
|
|
} else if (MNIsScrollArrowSelected(ppopupmenu)) {
|
|
_KillTimer(ppopupmenu->spwndPopupMenu, ppopupmenu->posSelectedItem);
|
|
DeselectItem:
|
|
|
|
xxxMNInvertItem(ppopupmenu, pmenu,
|
|
ppopupmenu->posSelectedItem, pwndNotify, FALSE);
|
|
}
|
|
|
|
ppopupmenu->posSelectedItem = itemPos;
|
|
|
|
if (itemPos != MFMWFP_NOITEM) {
|
|
/*
|
|
* If an item is selected, no autodismiss plus this means
|
|
* that the mouse is on the menu
|
|
*/
|
|
pMenuState->fAboutToAutoDismiss =
|
|
pMenuState->fMouseOffMenu = FALSE;
|
|
|
|
if (pMenuState->fButtonDown) {
|
|
xxxMNDoScroll(ppopupmenu, itemPos, TRUE);
|
|
}
|
|
|
|
pItem = xxxMNInvertItem(ppopupmenu, pmenu,
|
|
itemPos, pwndNotify, TRUE);
|
|
ThreadUnlock(&tlpwndNotify);
|
|
ThreadUnlock(&tlpmenu);
|
|
return pItem;
|
|
|
|
} else {
|
|
/*
|
|
* Notify that nothing is now focused in this menu.
|
|
*/
|
|
xxxWindowEvent(EVENT_OBJECT_FOCUS, ppopupmenu->spwndPopupMenu,
|
|
((ppopupmenu->spwndNotify != ppopupmenu->spwndPopupMenu) ? OBJID_CLIENT :
|
|
(ppopupmenu->fIsSysMenu ? OBJID_SYSMENU : OBJID_MENU)), 0, 0);
|
|
}
|
|
|
|
ThreadUnlock(&tlpwndNotify);
|
|
ThreadUnlock(&tlpmenu);
|
|
|
|
if (ppopupmenu->spwndPrevPopup != NULL) {
|
|
PPOPUPMENU pp;
|
|
|
|
/*
|
|
* Get the popupMenu data for the previous menu
|
|
* Use the root popupMenu if the previous menu is the menu bar
|
|
*/
|
|
if (ppopupmenu->fHasMenuBar && (ppopupmenu->spwndPrevPopup ==
|
|
ppopupmenu->spwndNotify)) {
|
|
pp = ppopupmenu->ppopupmenuRoot;
|
|
} else {
|
|
#ifdef HAVE_MN_GETPPOPUPMENU
|
|
TL tlpwndPrevPopup;
|
|
ThreadLock(ppopupmenu->spwndPrevPopup, &tlpwndPrevPopup);
|
|
pp = (PPOPUPMENU)xxxSendMessage(ppopupmenu->spwndPrevPopup,
|
|
MN_GETPPOPUPMENU, 0, 0L);
|
|
ThreadUnlock(&tlpwndPrevPopup);
|
|
#else
|
|
pp = ((PMENUWND)ppopupmenu->spwndPrevPopup)->ppopupmenu;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Generate a WM_MENUSELECT for the previous menu to re-establish
|
|
* it's current item as the SELECTED item
|
|
*/
|
|
ThreadLock(pp->spwndNotify, &tlpwndNotify);
|
|
ThreadLock(pp->spwndPopupMenu, &tlpwndPopup);
|
|
xxxSendMenuSelect(pp->spwndNotify, pp->spwndPopupMenu, pp->spmenu, pp->posSelectedItem);
|
|
ThreadUnlock(&tlpwndPopup);
|
|
ThreadUnlock(&tlpwndNotify);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNItemHitTest
|
|
*
|
|
* Given a hMenu and a point in screen coordinates, returns the position
|
|
* of the item the point is in. Returns -1 if no item exists there.
|
|
*
|
|
\***************************************************************************/
|
|
UINT MNItemHitTest(
|
|
PMENU pMenu,
|
|
PWND pwnd,
|
|
POINT pt)
|
|
{
|
|
PITEM pItem;
|
|
UINT iItem;
|
|
RECT rect;
|
|
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
|
|
if (pMenu->cItems == 0)
|
|
return MFMWFP_NOITEM;
|
|
|
|
|
|
/*
|
|
* This point is screen-relative. Menu bar coordinates relative
|
|
* to the window. But popup menu coordinates are relative to the client.
|
|
*/
|
|
if (TestMF(pMenu, MFISPOPUP)) {
|
|
|
|
/*
|
|
* Bail if it's outside rcWindow
|
|
*/
|
|
CopyInflateRect(&rect, &(pwnd->rcWindow),
|
|
-SYSMET(CXFIXEDFRAME), -SYSMET(CYFIXEDFRAME));
|
|
|
|
if (!PtInRect(&rect, pt)) {
|
|
return MFMWFP_NOITEM;
|
|
}
|
|
|
|
/* ScreenToClient */
|
|
if (TestWF(pwnd, WEFLAYOUTRTL)) {
|
|
pt.x = pwnd->rcClient.right - pt.x;
|
|
} else {
|
|
pt.x -= pwnd->rcClient.left;
|
|
}
|
|
pt.y -= pwnd->rcClient.top;
|
|
|
|
/*
|
|
* If on the non client area, then it's on the scroll arrows
|
|
*/
|
|
if (pt.y < 0) {
|
|
return MFMWFP_UPARROW;
|
|
} else if (pt.y > (int)pMenu->cyMenu) {
|
|
return MFMWFP_DOWNARROW;
|
|
}
|
|
|
|
} else {
|
|
/* ScreenToWindow */
|
|
if (TestWF(pwnd, WEFLAYOUTRTL) &&
|
|
(
|
|
(ptiCurrent->pq->codeCapture == SCREEN_CAPTURE) || (ptiCurrent->pq->codeCapture == NO_CAP_SYS)
|
|
)
|
|
) {
|
|
pt.x = pwnd->rcWindow.right - pt.x;
|
|
} else {
|
|
pt.x -= pwnd->rcWindow.left;
|
|
}
|
|
pt.y -= pwnd->rcWindow.top;
|
|
}
|
|
|
|
/*
|
|
* Step through all the items in the menu.
|
|
* If scrollable menu
|
|
*/
|
|
if (pMenu->dwArrowsOn != MSA_OFF) {
|
|
UserAssert(TestMF(pMenu, MFISPOPUP));
|
|
pItem = MNGetToppItem(pMenu);
|
|
rect.left = rect.top = 0;
|
|
rect.right = pItem->cxItem;
|
|
rect.bottom = pItem->cyItem;
|
|
for (iItem = pMenu->iTop; (iItem < (int)pMenu->cItems) && (rect.top < (int)pMenu->cyMenu); iItem++) {
|
|
|
|
if (PtInRect(&rect, pt)) {
|
|
return iItem;
|
|
}
|
|
|
|
pItem++;
|
|
rect.top = rect.bottom;
|
|
rect.bottom += pItem->cyItem;
|
|
}
|
|
} else {
|
|
/*
|
|
* No scroll bars.
|
|
*/
|
|
for (iItem = 0, pItem = pMenu->rgItems; iItem < pMenu->cItems; iItem++, pItem++) {
|
|
/* Is the mouse inside this item's rectangle? */
|
|
rect.left = pItem->xItem;
|
|
rect.top = pItem->yItem;
|
|
rect.right = pItem->xItem + pItem->cxItem;
|
|
rect.bottom = pItem->yItem + pItem->cyItem;
|
|
|
|
if (PtInRect(&rect, pt)) {
|
|
return iItem;
|
|
}
|
|
}
|
|
}
|
|
|
|
return MFMWFP_NOITEM;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* LockMFMWFPWindow
|
|
*
|
|
* This function is called when we need to save the return value of
|
|
* xxxMNFindWindowFromPoint.
|
|
*
|
|
* History:
|
|
* 11/14/96 GerardoB Created
|
|
\***************************************************************************/
|
|
VOID LockMFMWFPWindow(
|
|
PULONG_PTR puHitArea,
|
|
ULONG_PTR uNewHitArea)
|
|
{
|
|
/*
|
|
* Bail if there is nothing to do.
|
|
*/
|
|
if (*puHitArea == uNewHitArea) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Unlock current hit area
|
|
*/
|
|
UnlockMFMWFPWindow(puHitArea);
|
|
|
|
/*
|
|
* Lock new hit area
|
|
*/
|
|
if (IsMFMWFPWindow(uNewHitArea)) {
|
|
Lock(puHitArea, (PWND)uNewHitArea);
|
|
} else {
|
|
*puHitArea = uNewHitArea;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* UnlockMFMWFPWindow
|
|
*
|
|
* You must call this if you ever called LockMFMWFPWindow.
|
|
*
|
|
* History:
|
|
* 11/14/96 GerardoB Created
|
|
\***************************************************************************/
|
|
VOID UnlockMFMWFPWindow(
|
|
PULONG_PTR puHitArea)
|
|
{
|
|
if (IsMFMWFPWindow(*puHitArea)) {
|
|
Unlock(puHitArea);
|
|
} else {
|
|
*puHitArea = MFMWFP_OFFMENU;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* IsMFMWFPWindow
|
|
*
|
|
* Test whether or not the return value of xxxMNFindWindowFromPoint is
|
|
* a window. Not that uHitArea could be an HWND or a PWND.
|
|
*
|
|
* History:
|
|
* 10-02-96 GerardoB Created
|
|
\***************************************************************************/
|
|
BOOL IsMFMWFPWindow(
|
|
ULONG_PTR uHitArea)
|
|
{
|
|
switch(uHitArea) {
|
|
case MFMWFP_OFFMENU:
|
|
case MFMWFP_NOITEM:
|
|
case MFMWFP_ALTMENU:
|
|
return FALSE;
|
|
|
|
default:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNFindWindowFromPoint
|
|
*
|
|
* Determines in which window the point lies.
|
|
*
|
|
* Returns
|
|
* - PWND of the hierarchical menu the point is on,
|
|
* - MFMWFP_ALTMENU if point lies on the alternate popup menu.
|
|
* - MFMWFP_NOITEM if there is no item at that point on the menu or the
|
|
* point lies on the menu bar.
|
|
* - MFMWFP_OFFMENU if point lies elsewhere.
|
|
*
|
|
* Returns in pIndex
|
|
* - the index of the item hit,
|
|
* - MFMWFP_NOITEM if there is no item at that point on the menu or
|
|
* point lies on the menu bar.
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
* 8-11-92 Sanfords added MFMWFP_ constants
|
|
\***************************************************************************/
|
|
LONG_PTR xxxMNFindWindowFromPoint(
|
|
PPOPUPMENU ppopupmenu,
|
|
PUINT pIndex,
|
|
POINTS screenPt)
|
|
{
|
|
POINT pt;
|
|
RECT rect;
|
|
LONG_PTR longHit;
|
|
UINT itemHit;
|
|
PWND pwnd;
|
|
TL tlpwndT;
|
|
int cx;
|
|
|
|
|
|
*pIndex = 0;
|
|
|
|
if (ppopupmenu->spwndNextPopup) {
|
|
|
|
/*
|
|
* Check if this point is on any of our children before checking if it
|
|
* is on ourselves.
|
|
*/
|
|
ThreadLockAlways(ppopupmenu->spwndNextPopup, &tlpwndT);
|
|
longHit = xxxSendMessage(ppopupmenu->spwndNextPopup,
|
|
MN_FINDMENUWINDOWFROMPOINT, (WPARAM)&itemHit,
|
|
MAKELONG(screenPt.x, screenPt.y));
|
|
ThreadUnlock(&tlpwndT);
|
|
|
|
/*
|
|
* If return value is an hwnd, convert to pwnd.
|
|
*/
|
|
if (IsMFMWFPWindow(longHit)) {
|
|
longHit = (LONG_PTR)RevalidateHwnd((HWND)longHit);
|
|
}
|
|
|
|
if (longHit) {
|
|
|
|
/*
|
|
* Hit occurred on one of our children.
|
|
*/
|
|
|
|
*pIndex = itemHit;
|
|
return longHit;
|
|
}
|
|
}
|
|
|
|
if (ppopupmenu->fIsMenuBar) {
|
|
int cBorders;
|
|
|
|
/*
|
|
* Check if this point is on the menu bar
|
|
*/
|
|
pwnd = ppopupmenu->spwndNotify;
|
|
if (pwnd == NULL) {
|
|
return MFMWFP_OFFMENU;
|
|
}
|
|
|
|
pt.x = screenPt.x;
|
|
pt.y = screenPt.y;
|
|
|
|
if (ppopupmenu->fIsSysMenu) {
|
|
|
|
if (!_HasCaptionIcon(pwnd)) {
|
|
/*
|
|
* no system menu rect to click in if it doesn't have an icon
|
|
*/
|
|
return 0L;
|
|
}
|
|
|
|
/*
|
|
* Check if this is a click on the system menu icon.
|
|
*/
|
|
if (TestWF(pwnd, WFMINIMIZED)) {
|
|
|
|
/*
|
|
* If the window is minimized, then check if there was a hit in
|
|
* the client area of the icon's window.
|
|
*/
|
|
|
|
/*
|
|
* Mikehar 5/27
|
|
* Don't know how this ever worked. If we are the system menu of an icon
|
|
* we want to punt the menus if the click occurs ANYWHERE outside of the
|
|
* menu.
|
|
* Johnc 03-Jun-1992 the next 4 lines were commented out for Mike's
|
|
* problem above but that made clicking on a minimized window with
|
|
* the system menu already up, bring down the menu and put it right
|
|
* up again (bug 10951) because the mnloop wouldn't swallow the mouse
|
|
* down click message. The problem Mike mentions no longer shows up.
|
|
*/
|
|
|
|
if (PtInRect(&(pwnd->rcWindow), pt)) {
|
|
return MFMWFP_NOITEM;
|
|
}
|
|
|
|
/*
|
|
* It's an iconic window, so can't be hitting anywhere else.
|
|
*/
|
|
return MFMWFP_OFFMENU;
|
|
}
|
|
|
|
/*
|
|
* Check if we are hitting on the system menu rectangle on the top
|
|
* left of windows.
|
|
*/
|
|
rect.top = rect.left = 0;
|
|
rect.right = SYSMET(CXSIZE);
|
|
rect.bottom = SYSMET(CYSIZE);
|
|
|
|
cBorders = GetWindowBorders(pwnd->style, pwnd->ExStyle, TRUE, FALSE);
|
|
|
|
OffsetRect(&rect, pwnd->rcWindow.left + cBorders*SYSMET(CXBORDER),
|
|
pwnd->rcWindow.top + cBorders*SYSMET(CYBORDER));
|
|
/*
|
|
* Mirror the rect because the buttons in the left hand side of the window if it mirrored
|
|
*/
|
|
if (TestWF(pwnd, WEFLAYOUTRTL)) {
|
|
cx = rect.right - rect.left;
|
|
rect.right = pwnd->rcWindow.right - (rect.left - pwnd->rcWindow.left);
|
|
rect.left = rect.right - cx;
|
|
}
|
|
|
|
if (PtInRect(&rect, pt)) {
|
|
*pIndex = 0;
|
|
return MFMWFP_NOITEM;
|
|
}
|
|
/*
|
|
* Check if we hit in the alternate menu if available.
|
|
*/
|
|
if (ppopupmenu->spmenuAlternate) {
|
|
itemHit = MNItemHitTest(ppopupmenu->spmenuAlternate, pwnd, pt);
|
|
if (itemHit != MFMWFP_NOITEM) {
|
|
*pIndex = itemHit;
|
|
return MFMWFP_ALTMENU;
|
|
}
|
|
}
|
|
return MFMWFP_OFFMENU;
|
|
} else {
|
|
if (TestWF(ppopupmenu->spwndNotify, WFMINIMIZED)) {
|
|
|
|
/*
|
|
* If we are minimized, we can't hit on the main menu bar.
|
|
*/
|
|
return MFMWFP_OFFMENU;
|
|
}
|
|
}
|
|
} else {
|
|
pwnd = ppopupmenu->spwndPopupMenu;
|
|
|
|
/*
|
|
* else this is a popup window and we need to check if we are hitting
|
|
* anywhere on this popup window.
|
|
*/
|
|
pt.x = screenPt.x;
|
|
pt.y = screenPt.y;
|
|
if (!PtInRect(&pwnd->rcWindow, pt)) {
|
|
|
|
/*
|
|
* Point completely outside the popup menu window so return 0.
|
|
*/
|
|
return MFMWFP_OFFMENU;
|
|
}
|
|
}
|
|
|
|
pt.x = screenPt.x;
|
|
pt.y = screenPt.y;
|
|
|
|
itemHit = MNItemHitTest(ppopupmenu->spmenu, pwnd, pt);
|
|
|
|
if (ppopupmenu->fIsMenuBar) {
|
|
|
|
/*
|
|
* If hit is on menu bar but no item is there, treat it as if the user
|
|
* hit nothing.
|
|
*/
|
|
if (itemHit == MFMWFP_NOITEM) {
|
|
|
|
/*
|
|
* Check if we hit in the alternate menu if available.
|
|
*/
|
|
if (ppopupmenu->spmenuAlternate) {
|
|
itemHit = MNItemHitTest(ppopupmenu->spmenuAlternate, pwnd, pt);
|
|
if (itemHit != MFMWFP_NOITEM) {
|
|
*pIndex = itemHit;
|
|
return MFMWFP_ALTMENU;
|
|
}
|
|
}
|
|
return MFMWFP_OFFMENU;
|
|
}
|
|
|
|
*pIndex = itemHit;
|
|
return MFMWFP_NOITEM;
|
|
} else {
|
|
|
|
/*
|
|
* If hit is on popup menu but no item is there, itemHit
|
|
* will be MFMWFP_NOITEM
|
|
*/
|
|
*pIndex = itemHit;
|
|
return (LONG_PTR)pwnd;
|
|
}
|
|
return MFMWFP_OFFMENU;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNCancel
|
|
*
|
|
* Should only be sent to the top most ppopupmenu/menu window in the
|
|
* hierarchy.
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
\***************************************************************************/
|
|
VOID xxxMNCancel(
|
|
PMENUSTATE pMenuState,
|
|
UINT uMsg,
|
|
UINT cmd,
|
|
LPARAM lParam)
|
|
{
|
|
PPOPUPMENU ppopupmenu = pMenuState->pGlobalPopupMenu;
|
|
BOOL fSynchronous = ppopupmenu->fSynchronous;
|
|
BOOL fTrackFlagsSet = ppopupmenu->fIsTrackPopup;
|
|
BOOL fIsSysMenu = ppopupmenu->fIsSysMenu;
|
|
BOOL fIsMenuBar = ppopupmenu->fIsMenuBar;
|
|
BOOL fNotify = !ppopupmenu->fNoNotify;
|
|
PWND pwndT;
|
|
TL tlpwndT;
|
|
TL tlpwndPopupMenu;
|
|
|
|
Validateppopupmenu(ppopupmenu);
|
|
|
|
pMenuState->fInsideMenuLoop = FALSE;
|
|
pMenuState->fButtonDown = FALSE;
|
|
/*
|
|
* Mark the popup as destroyed so people will not use it anymore.
|
|
* This means that root popups can be marked as destroyed before
|
|
* actually being destroyed (nice and confusing).
|
|
*/
|
|
ppopupmenu->fDestroyed = TRUE;
|
|
|
|
/*
|
|
* Only the menu loop owner can destroy the menu windows (i.e, xxxMNCloseHierarchy)
|
|
*/
|
|
if (PtiCurrent() != pMenuState->ptiMenuStateOwner) {
|
|
RIPMSG1(RIP_WARNING, "xxxMNCancel: Thread %#p doesn't own the menu loop", PtiCurrent());
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If the menu loop is running on a thread different than the thread
|
|
* that owns spwndNotify, we can have two threads trying to cancel
|
|
* this popup at the same time.
|
|
*/
|
|
if (ppopupmenu->fInCancel) {
|
|
RIPMSG1(RIP_WARNING, "xxxMNCancel: already in cancel. ppopupmenu:%#p", ppopupmenu);
|
|
return;
|
|
}
|
|
ppopupmenu->fInCancel = TRUE;
|
|
|
|
ThreadLock(ppopupmenu->spwndPopupMenu, &tlpwndPopupMenu);
|
|
|
|
/*
|
|
* Close all hierarchies from this point down.
|
|
*/
|
|
xxxMNCloseHierarchy(ppopupmenu, pMenuState);
|
|
|
|
/*
|
|
* Unselect any items on this top level window
|
|
*/
|
|
xxxMNSelectItem(ppopupmenu, pMenuState, MFMWFP_NOITEM);
|
|
|
|
pMenuState->fMenuStarted = FALSE;
|
|
|
|
pwndT = ppopupmenu->spwndNotify;
|
|
|
|
ThreadLock(pwndT, &tlpwndT);
|
|
|
|
xxxMNReleaseCapture();
|
|
|
|
if (fTrackFlagsSet) {
|
|
/*
|
|
* Send a POPUPEND so people watching see them paired
|
|
*/
|
|
xxxWindowEvent(EVENT_SYSTEM_MENUPOPUPEND, ppopupmenu->spwndPopupMenu, OBJID_CLIENT, 0, 0);
|
|
|
|
xxxDestroyWindow(ppopupmenu->spwndPopupMenu);
|
|
}
|
|
|
|
if (pwndT == NULL) {
|
|
ThreadUnlock(&tlpwndT);
|
|
ThreadUnlock(&tlpwndPopupMenu);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* SMS_NOMENU hack so we can send MenuSelect messages with
|
|
* (loword(lparam) = -1) when
|
|
* the menu pops back up for the CBT people. In 3.0, all WM_MENUSELECT
|
|
* messages went through the message filter so go through the function
|
|
* SendMenuSelect. We need to do this in 3.1 since WordDefect for Windows
|
|
* depends on this.
|
|
*/
|
|
xxxSendMenuSelect(pwndT, NULL, SMS_NOMENU, MFMWFP_NOITEM);
|
|
|
|
xxxWindowEvent(EVENT_SYSTEM_MENUEND, pwndT, (fIsSysMenu ?
|
|
OBJID_SYSMENU : (fIsMenuBar ? OBJID_MENU : OBJID_WINDOW)),
|
|
INDEXID_CONTAINER, 0);
|
|
|
|
if (fNotify) {
|
|
/*
|
|
* Notify app we are exiting the menu loop. Mainly for WinOldApp 386.
|
|
* wParam is 1 if a TrackPopupMenu else 0.
|
|
*/
|
|
xxxSendMessage(pwndT, WM_EXITMENULOOP,
|
|
((fTrackFlagsSet && !fIsSysMenu)? 1 : 0), 0);
|
|
}
|
|
|
|
if (uMsg != 0) {
|
|
PlayEventSound(USER_SOUND_MENUCOMMAND);
|
|
pMenuState->cmdLast = cmd;
|
|
if (!fSynchronous) {
|
|
if (!fIsSysMenu && fTrackFlagsSet && !TestWF(pwndT, WFWIN31COMPAT)) {
|
|
xxxSendMessage(pwndT, uMsg, cmd, lParam);
|
|
} else {
|
|
_PostMessage(pwndT, uMsg, cmd, lParam);
|
|
}
|
|
}
|
|
} else
|
|
pMenuState->cmdLast = 0;
|
|
|
|
ThreadUnlock(&tlpwndT);
|
|
|
|
ThreadUnlock(&tlpwndPopupMenu);
|
|
|
|
}
|
|
/***************************************************************************\
|
|
* xxxMNButtonDown
|
|
*
|
|
* Handles a mouse down on the menu associated with ppopupmenu at item index
|
|
* posItemHit. posItemHit could be MFMWFP_NOITEM if user hit on a menu where
|
|
* no item exists.
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
\***************************************************************************/
|
|
VOID xxxMNButtonDown(
|
|
PPOPUPMENU ppopupmenu,
|
|
PMENUSTATE pMenuState,
|
|
UINT posItemHit, BOOL fClick)
|
|
{
|
|
PITEM pItem;
|
|
BOOL fOpenHierarchy;
|
|
|
|
/*
|
|
* A different item was hit than is currently selected, so select it
|
|
* and drop its menu if available. Make sure we toggle click state.
|
|
*/
|
|
if (ppopupmenu->posSelectedItem != posItemHit) {
|
|
/*
|
|
* We are clicking on a new item, not moving the mouse over to it.
|
|
* So reset cancel toggle state. We don't want button up from
|
|
* this button down to cancel.
|
|
*/
|
|
if (fClick) {
|
|
fOpenHierarchy = TRUE;
|
|
ppopupmenu->fToggle = FALSE;
|
|
}
|
|
else
|
|
{
|
|
fOpenHierarchy = (ppopupmenu->fDropNextPopup != 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* If the item has a popup and isn't disabled, open it. Note that
|
|
* selecting this item will cancel any hierarchies associated with
|
|
* the previously selected item.
|
|
*/
|
|
pItem = xxxMNSelectItem(ppopupmenu, pMenuState, posItemHit);
|
|
if (MNIsPopupItem(pItem) && fOpenHierarchy) {
|
|
/* Punt if menu was destroyed. */
|
|
if (xxxMNOpenHierarchy(ppopupmenu, pMenuState) == (PWND)-1) {
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* We are moving over to the already-selected item. If we are
|
|
* clicking for real, reset cancel toggle state. We want button
|
|
* up to cancel if on same item. Otherwise, do nothing if just
|
|
* moving...
|
|
*/
|
|
if (fClick) {
|
|
ppopupmenu->fToggle = TRUE;
|
|
}
|
|
|
|
if (!xxxMNHideNextHierarchy(ppopupmenu) && fClick && xxxMNOpenHierarchy(ppopupmenu, pMenuState))
|
|
ppopupmenu->fToggle = FALSE;
|
|
}
|
|
|
|
if (fClick) {
|
|
pMenuState->fButtonDown = TRUE;
|
|
xxxMNDoScroll(ppopupmenu, posItemHit, TRUE);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNSetTimerToAutoDissmiss
|
|
*
|
|
* History:
|
|
* 11/14/96 GerardoB Created
|
|
\***************************************************************************/
|
|
VOID MNSetTimerToAutoDismiss(
|
|
PMENUSTATE pMenuState,
|
|
PWND pwnd)
|
|
{
|
|
if (pMenuState->fAutoDismiss && !pMenuState->fAboutToAutoDismiss) {
|
|
if (_SetTimer(pwnd, IDSYS_MNAUTODISMISS, 16 * gdtMNDropDown, NULL)) {
|
|
pMenuState->fAboutToAutoDismiss = TRUE;
|
|
} else {
|
|
RIPMSG0(RIP_WARNING, "xxxMNMouseMove: Failed to set autodismiss timer");
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNMouseMove
|
|
*
|
|
* Handles a mouse move to the given point.
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
\***************************************************************************/
|
|
VOID xxxMNMouseMove(
|
|
PPOPUPMENU ppopup,
|
|
PMENUSTATE pMenuState,
|
|
POINTS ptScreen)
|
|
{
|
|
LONG_PTR cmdHitArea;
|
|
UINT uFlags;
|
|
UINT cmdItem;
|
|
PWND pwnd;
|
|
TL tlpwndT;
|
|
|
|
|
|
if (!IsRootPopupMenu(ppopup)) {
|
|
RIPMSG0(RIP_ERROR,
|
|
"MenuMouseMoveHandler() called for a non top most menu");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Ignore mouse moves that aren't really moves. MSTEST jiggles
|
|
* the mouse for some reason. And windows coming and going will
|
|
* force mouse moves, to reset the cursor.
|
|
*/
|
|
if ((ptScreen.x == pMenuState->ptMouseLast.x) && (ptScreen.y == pMenuState->ptMouseLast.y))
|
|
return;
|
|
|
|
pMenuState->ptMouseLast.x = ptScreen.x;
|
|
pMenuState->ptMouseLast.y = ptScreen.y;
|
|
|
|
/*
|
|
* Find out where this mouse move occurred.
|
|
*/
|
|
cmdHitArea = xxxMNFindWindowFromPoint(ppopup, &cmdItem, ptScreen);
|
|
|
|
/*
|
|
* If coming from an IDropTarget call out, remember the hit test
|
|
*/
|
|
if (pMenuState->fInDoDragDrop) {
|
|
xxxMNUpdateDraggingInfo(pMenuState, cmdHitArea, cmdItem);
|
|
}
|
|
|
|
if (pMenuState->mnFocus == KEYBDHOLD) {
|
|
/*
|
|
* Ignore mouse moves when in keyboard mode if the mouse isn't over any
|
|
* menu at all. Also ignore mouse moves if over minimized window,
|
|
* because we pretend that its entire window is like system menu.
|
|
*/
|
|
if ((cmdHitArea == MFMWFP_OFFMENU) ||
|
|
((cmdHitArea == MFMWFP_NOITEM) && TestWF(ppopup->spwndNotify, WFMINIMIZED))) {
|
|
return;
|
|
}
|
|
|
|
pMenuState->mnFocus = MOUSEHOLD;
|
|
}
|
|
|
|
if (cmdHitArea == MFMWFP_ALTMENU) {
|
|
/*
|
|
* User clicked in the other menu so switch to it ONLY IF
|
|
* MOUSE IS DOWN. Usability testing proves that people frequently
|
|
* get kicked into the system menu accidentally when browsing the
|
|
* menu bar. We support the Win3.1 behavior when the mouse is
|
|
* down however.
|
|
*/
|
|
if (pMenuState->fButtonDown) {
|
|
xxxMNSwitchToAlternateMenu(ppopup);
|
|
cmdHitArea = MFMWFP_NOITEM;
|
|
} else
|
|
goto OverNothing;
|
|
}
|
|
|
|
if (cmdHitArea == MFMWFP_NOITEM) {
|
|
/*
|
|
* Mouse move occurred to an item in the main menu bar. If the item
|
|
* is different than the one already selected, close up the current
|
|
* one, select the new one and drop its menu. But if the item is the
|
|
* same as the one currently selected, we need to pull up any popups
|
|
* if needed and just keep the current level visible. Hey, this is
|
|
* the same as a mousedown so lets do that instead.
|
|
*/
|
|
xxxMNButtonDown(ppopup, pMenuState, cmdItem, FALSE);
|
|
return;
|
|
} else if (cmdHitArea != 0) {
|
|
/* This is a popup window we moved onto. */
|
|
pwnd = (PWND)(cmdHitArea);
|
|
ThreadLock(pwnd, &tlpwndT);
|
|
|
|
UserAssert(TestWF(pwnd, WFVISIBLE));
|
|
|
|
/*
|
|
* Modeless menus don't capture the mouse, so track it to know
|
|
* when it leaves the popup.
|
|
*/
|
|
ppopup = ((PMENUWND)pwnd)->ppopupmenu;
|
|
if (pMenuState->fModelessMenu
|
|
&& !pMenuState->fInDoDragDrop
|
|
&& !ppopup->fTrackMouseEvent) {
|
|
|
|
TRACKMOUSEEVENT tme;
|
|
|
|
/* tme.cbSize = sizeof(TRACKMOUSEEVENT); Not checked on kernel side */
|
|
tme.dwFlags = TME_LEAVE;
|
|
tme.hwndTrack = PtoH(pwnd);
|
|
TrackMouseEvent(&tme);
|
|
ppopup->fTrackMouseEvent = TRUE;
|
|
|
|
/*
|
|
* We just entered this window so make sure the cursor
|
|
* is properly set.
|
|
*/
|
|
xxxSendMessage(pwnd, WM_SETCURSOR, (WPARAM)HWq(pwnd), MAKELONG(MSGF_MENU, 0));
|
|
|
|
}
|
|
|
|
/*
|
|
* Select the item.
|
|
*/
|
|
uFlags = (UINT)xxxSendMessage(pwnd, MN_SELECTITEM, (WPARAM)cmdItem, 0L);
|
|
if ((uFlags & MF_POPUP) && !(uFlags & MFS_GRAYED)) {
|
|
/*
|
|
* User moved back onto an item with a hierarchy. Hide the
|
|
* the dropped popup.
|
|
*/
|
|
if (!xxxSendMessage(pwnd, MN_SETTIMERTOOPENHIERARCHY, 0, 0L)) {
|
|
xxxMNHideNextHierarchy(ppopup);
|
|
}
|
|
}
|
|
ThreadUnlock(&tlpwndT);
|
|
} else
|
|
OverNothing:
|
|
{
|
|
/* We moved off all menu windows... */
|
|
if (ppopup->spwndActivePopup != NULL) {
|
|
pwnd = ppopup->spwndActivePopup;
|
|
|
|
ThreadLock(pwnd, &tlpwndT);
|
|
xxxSendMessage(pwnd, MN_SELECTITEM, MFMWFP_NOITEM, 0L);
|
|
MNSetTimerToAutoDismiss(pMenuState, pwnd);
|
|
ThreadUnlock(&tlpwndT);
|
|
} else {
|
|
xxxMNSelectItem(ppopup, pMenuState, MFMWFP_NOITEM);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxMNButtonUp
|
|
*
|
|
* Handles a mouse button up at the given point.
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
\***************************************************************************/
|
|
VOID xxxMNButtonUp(
|
|
PPOPUPMENU ppopup,
|
|
PMENUSTATE pMenuState,
|
|
UINT posItemHit,
|
|
LPARAM lParam)
|
|
{
|
|
PITEM pItem;
|
|
|
|
if (!pMenuState->fButtonDown) {
|
|
|
|
/*
|
|
* Ignore if button was never down... Really shouldn't happen...
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (posItemHit == MFMWFP_NOITEM) {
|
|
RIPMSG0(RIP_WARNING, "button up on no item");
|
|
goto ExitButtonUp;
|
|
}
|
|
|
|
if (ppopup->posSelectedItem != posItemHit) {
|
|
goto ExitButtonUp;
|
|
}
|
|
|
|
if (ppopup->fIsMenuBar) {
|
|
|
|
/*
|
|
* Handle button up in menubar specially.
|
|
*/
|
|
if (ppopup->fHierarchyDropped) {
|
|
if (!ppopup->fToggle) {
|
|
goto ExitButtonUp;
|
|
} else {
|
|
/*
|
|
* Cancel menu now.
|
|
*/
|
|
ppopup->fToggle = FALSE;
|
|
xxxMNDismiss(pMenuState);
|
|
return;
|
|
}
|
|
}
|
|
} else if (ppopup->fShowTimer) {
|
|
ppopup->fToggle = FALSE;
|
|
|
|
/*
|
|
* Open hierarchy on popup
|
|
*/
|
|
xxxMNOpenHierarchy(ppopup, pMenuState);
|
|
|
|
goto ExitButtonUp;
|
|
}
|
|
|
|
/*
|
|
* If nothing is selected, get out. This occurs mainly on unbalanced
|
|
* multicolumn menus where one of the columns isn't completely full.
|
|
*/
|
|
if (ppopup->posSelectedItem == MFMWFP_NOITEM)
|
|
goto ExitButtonUp;
|
|
|
|
if (ppopup->posSelectedItem >= ppopup->spmenu->cItems)
|
|
goto ExitButtonUp;
|
|
|
|
/*
|
|
* Get a pointer to the currently selected item in this menu.
|
|
*/
|
|
pItem = &(ppopup->spmenu->rgItems[ppopup->posSelectedItem]);
|
|
|
|
/*
|
|
* Kick out of menu mode if user clicked on a non-separator, enabled,
|
|
* non-hierarchical item.
|
|
*
|
|
* BOGUS
|
|
* Why doesn't MFS_GRAYED check work for separators now? Find out later.
|
|
*/
|
|
if (!(pItem->fType & MFT_SEPARATOR)
|
|
&& !(pItem->fState & MFS_GRAYED)
|
|
&& (pItem->spSubMenu == NULL)) {
|
|
|
|
xxxMNDismissWithNotify(pMenuState, ppopup->spmenu, pItem,
|
|
ppopup->posSelectedItem, lParam);
|
|
return;
|
|
}
|
|
|
|
ExitButtonUp:
|
|
pMenuState->fButtonDown =
|
|
pMenuState->fButtonAlwaysDown = FALSE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
*UINT MenuSetTimerToOpenHierarchy(PPOPUPMENU ppopupmenu)
|
|
* Given the current selection, set a timer to show this hierarchy if
|
|
* valid else return 0. If a timer should be set but couldn't return -1.
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
\***************************************************************************/
|
|
UINT MNSetTimerToOpenHierarchy(
|
|
PPOPUPMENU ppopup)
|
|
{
|
|
PITEM pItem;
|
|
|
|
/*
|
|
* No selection so fail
|
|
*/
|
|
if (ppopup->posSelectedItem == MFMWFP_NOITEM) {
|
|
return 0;
|
|
}
|
|
|
|
if (ppopup->posSelectedItem >= ppopup->spmenu->cItems) {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Is item an enabled popup?
|
|
* Get a pointer to the currently selected item in this menu.
|
|
*/
|
|
pItem = ppopup->spmenu->rgItems + ppopup->posSelectedItem;
|
|
if ((pItem->spSubMenu == NULL) || (pItem->fState & MFS_GRAYED)) {
|
|
return 0;
|
|
}
|
|
|
|
if (ppopup->fShowTimer
|
|
|| (ppopup->fHierarchyDropped
|
|
&& (ppopup->posSelectedItem == ppopup->posDropped))) {
|
|
|
|
/*
|
|
* A timer is already set or the hierarchy is already opened.
|
|
*/
|
|
return 1;
|
|
}
|
|
|
|
if (!_SetTimer(ppopup->spwndPopupMenu, IDSYS_MNSHOW, gdtMNDropDown, NULL)) {
|
|
return (UINT)-1;
|
|
}
|
|
|
|
ppopup->fShowTimer = TRUE;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* MNSetTimerToCloseHierarchy
|
|
*
|
|
\***************************************************************************/
|
|
UINT MNSetTimerToCloseHierarchy(
|
|
PPOPUPMENU ppopup)
|
|
{
|
|
|
|
if (!ppopup->fHierarchyDropped) {
|
|
return 0;
|
|
}
|
|
|
|
if (ppopup->fHideTimer) {
|
|
return 1;
|
|
}
|
|
|
|
if (!_SetTimer(ppopup->spwndPopupMenu, IDSYS_MNHIDE, gdtMNDropDown, NULL)) {
|
|
return (UINT)-1;
|
|
}
|
|
|
|
ppopup->fHideTimer = TRUE;
|
|
|
|
ppopup = ((PMENUWND)(ppopup->spwndNextPopup))->ppopupmenu;
|
|
ppopup->fAboutToHide = TRUE;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxCallHandleMenuMessages
|
|
*
|
|
* Modeless menus don't have a modal loop so we don't see the messages until
|
|
* they are dispatched to xxxMenuWindowProc. So we call this function to
|
|
* process the message just like we would in the modal case, only that
|
|
* the message has already been pulled out of the queue.
|
|
*
|
|
* This is also calledfrom xxxScanSysQueue to pass mouse messages on the menu
|
|
* bar or from xxxMNDragOver to upadate the mouse position when being draged over.
|
|
*
|
|
* History:
|
|
* 10/25/96 GerardoB Created
|
|
\***************************************************************************/
|
|
BOOL xxxCallHandleMenuMessages(
|
|
PMENUSTATE pMenuState,
|
|
PWND pwnd,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
BOOL fHandled;
|
|
MSG msg;
|
|
|
|
CheckLock(pwnd);
|
|
|
|
UserAssert(pMenuState->fModelessMenu || pMenuState->fInDoDragDrop);
|
|
|
|
/*
|
|
* Since modeless menus don't capture the mouse, then we need to
|
|
* keep checking on the mouse button when the mouse is off the
|
|
* menu.
|
|
* Note that we do not set fMouseOffMenu if fInDoDragDrop is set
|
|
*/
|
|
if (pMenuState->fMouseOffMenu && pMenuState->fButtonDown) {
|
|
UserAssert(!pMenuState->fInDoDragDrop && pMenuState->fModelessMenu);
|
|
MNCheckButtonDownState(pMenuState);
|
|
}
|
|
|
|
/*
|
|
* Setup the msg structure
|
|
*/
|
|
msg.hwnd = HW(pwnd);
|
|
msg.message = message;
|
|
msg.wParam = wParam;
|
|
|
|
/*
|
|
* xxxHandleMenuMessages expects screen coordinates
|
|
*/
|
|
if ((message >= WM_MOUSEFIRST) && (message <= WM_MOUSELAST)) {
|
|
msg.lParam = MAKELONG(GET_X_LPARAM(lParam) + pwnd->rcClient.left,
|
|
GET_Y_LPARAM(lParam) + pwnd->rcClient.top);
|
|
} else {
|
|
msg.lParam = lParam;
|
|
}
|
|
|
|
/*
|
|
* Not used by xxxHandleMenuMessages
|
|
*/
|
|
msg.time = 0;
|
|
msg.pt.x = msg.pt.x = 0;
|
|
|
|
|
|
UserAssert(pMenuState->pGlobalPopupMenu != NULL);
|
|
|
|
pMenuState->fInCallHandleMenuMessages = TRUE;
|
|
fHandled = xxxHandleMenuMessages(&msg, pMenuState, pMenuState->pGlobalPopupMenu);
|
|
pMenuState->fInCallHandleMenuMessages = FALSE;
|
|
|
|
/*
|
|
* If the message was handled and this is a modeless menu,
|
|
* check to see if it's time to go.
|
|
*/
|
|
if (fHandled
|
|
&& pMenuState->fModelessMenu
|
|
&& ExitMenuLoop (pMenuState, pMenuState->pGlobalPopupMenu)) {
|
|
|
|
xxxEndMenuLoop (pMenuState, pMenuState->pGlobalPopupMenu);
|
|
xxxMNEndMenuState(TRUE);
|
|
}
|
|
|
|
return fHandled;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* History:
|
|
* 05-25-91 Mikehar Ported from Win3.1
|
|
* 08-12-96 jparsons Catch NULL lParam on WM_CREATE [51986]
|
|
\***************************************************************************/
|
|
LRESULT xxxMenuWindowProc(
|
|
PWND pwnd,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
BOOL fIsRecursedMenu;
|
|
LRESULT lRet;
|
|
PAINTSTRUCT ps;
|
|
PPOPUPMENU ppopupmenu;
|
|
PMENUSTATE pMenuState;
|
|
PMENU pmenu;
|
|
PITEM pItem;
|
|
TL tlpmenu;
|
|
TL tlpwndNotify;
|
|
PDESKTOP pdesk = pwnd->head.rpdesk;
|
|
POINT ptOrg;
|
|
HDC hdcAni;
|
|
|
|
CheckLock(pwnd);
|
|
|
|
VALIDATECLASSANDSIZE(pwnd, message, wParam, lParam, FNID_MENU, WM_NCCREATE);
|
|
|
|
/*
|
|
* If we're not in menu mode or this window is just being created,
|
|
* there are only few messages we care about.
|
|
*/
|
|
pMenuState = GetpMenuState(pwnd);
|
|
ppopupmenu = ((PMENUWND)pwnd)->ppopupmenu;
|
|
pmenu = (ppopupmenu != NULL ? ppopupmenu->spmenu : NULL);
|
|
if ((pMenuState == NULL) || (pmenu == NULL)) {
|
|
switch (message) {
|
|
case WM_NCCREATE:
|
|
case WM_FINALDESTROY:
|
|
break;
|
|
|
|
case MN_SETHMENU:
|
|
if (ppopupmenu != NULL) {
|
|
break;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
default:
|
|
goto CallDWP;
|
|
}
|
|
} else {
|
|
/*
|
|
* TPM_RECURSE support: make sure we grab the proper pMenuState.
|
|
*/
|
|
fIsRecursedMenu = ((ppopupmenu->ppopupmenuRoot != NULL)
|
|
&& IsRecursedMenuState(pMenuState, ppopupmenu));
|
|
if (fIsRecursedMenu) {
|
|
while (IsRecursedMenuState(pMenuState, ppopupmenu)
|
|
&& (pMenuState->pmnsPrev != NULL)) {
|
|
pMenuState = pMenuState->pmnsPrev;
|
|
}
|
|
UserAssert(pMenuState->pGlobalPopupMenu == ppopupmenu->ppopupmenuRoot);
|
|
}
|
|
|
|
Validateppopupmenu(ppopupmenu);
|
|
|
|
/*
|
|
* If this is a modeless menu, give xxxHandleMenuMessages the first
|
|
* shot at the message
|
|
*/
|
|
if (pMenuState->fModelessMenu && !pMenuState->fInCallHandleMenuMessages) {
|
|
/*
|
|
* If this is a recursed menu, we don't want to process any
|
|
* input for it until the current menu goes away.
|
|
*/
|
|
if (fIsRecursedMenu) {
|
|
if (((message >= WM_MOUSEFIRST) && (message <= WM_MOUSELAST))
|
|
|| ((message >= WM_KEYFIRST) && (message <= WM_KEYLAST))
|
|
|| ((message >= WM_NCMOUSEFIRST) && (message <= WM_NCMOUSELAST))) {
|
|
|
|
goto CallDWP;
|
|
}
|
|
} else {
|
|
if (xxxCallHandleMenuMessages(pMenuState, pwnd, message, wParam, lParam)) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (message) {
|
|
case WM_NCCREATE:
|
|
/*
|
|
* Ignore evil messages to prevent leaks.
|
|
* Use RIP_ERROR for a while to make sure to see if we're getting here
|
|
*/
|
|
if (((PMENUWND)pwnd)->ppopupmenu != NULL) {
|
|
RIPMSG1(RIP_ERROR, "xxxMenuWindowProc: evil WM_NCCREATE. already initialized. pwnd:%p", pwnd);
|
|
return FALSE;
|
|
}
|
|
ppopupmenu = MNAllocPopup(TRUE);
|
|
if (ppopupmenu == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
((PMENUWND)pwnd)->ppopupmenu = ppopupmenu;
|
|
ppopupmenu->posSelectedItem = MFMWFP_NOITEM;
|
|
Lock(&(ppopupmenu->spwndPopupMenu), pwnd);
|
|
return TRUE;
|
|
|
|
case WM_NCCALCSIZE:
|
|
xxxDefWindowProc(pwnd, message, wParam, lParam);
|
|
if (pmenu->dwArrowsOn != MSA_OFF) {
|
|
InflateRect((PRECT)lParam, 0, -gcyMenuScrollArrow);
|
|
}
|
|
break;
|
|
|
|
case WM_ERASEBKGND:
|
|
if (pmenu->hbrBack != NULL) {
|
|
MNEraseBackground ((HDC) wParam, pmenu,
|
|
0, 0,
|
|
pwnd->rcClient.right - pwnd->rcClient.left,
|
|
pwnd->rcClient.bottom - pwnd->rcClient.top);
|
|
return TRUE;
|
|
} else {
|
|
goto CallDWP;
|
|
}
|
|
break;
|
|
|
|
case WM_PRINT:
|
|
/*
|
|
* default processing of WM_PRINT does not handle custom non-
|
|
* client painting -- which scrollable menus have -- so take
|
|
* care of drawing nonclient area and then let DefWindowProc
|
|
* handle the rest
|
|
*/
|
|
if ((lParam & PRF_NONCLIENT) && (pmenu->dwArrowsOn != MSA_OFF)) {
|
|
BOOL bMirrorThisDC = (wParam && TestWF(pwnd, WEFLAYOUTRTL) && !MIRRORED_HDC((HDC)wParam));
|
|
DWORD dwOldLayout;
|
|
|
|
if (bMirrorThisDC) {
|
|
dwOldLayout = GreSetLayout((HDC)wParam , pwnd->rcWindow.right - pwnd->rcWindow.left, LAYOUT_RTL);
|
|
}
|
|
|
|
MNDrawFullNC(pwnd, (HDC)wParam, ppopupmenu);
|
|
|
|
if (bMirrorThisDC) {
|
|
GreSetLayout((HDC)wParam , pwnd->rcWindow.right - pwnd->rcWindow.left, dwOldLayout);
|
|
}
|
|
|
|
GreGetWindowOrg((HDC)wParam, &ptOrg);
|
|
GreSetWindowOrg((HDC)wParam,
|
|
ptOrg.x - MNXBORDER,
|
|
ptOrg.y - MNYBORDER - gcyMenuScrollArrow,
|
|
NULL);
|
|
xxxDefWindowProc(pwnd, message, wParam, lParam & ~PRF_NONCLIENT);
|
|
GreSetWindowOrg((HDC)wParam, ptOrg.x, ptOrg.y, NULL);
|
|
|
|
} else {
|
|
if (MNIsFlatMenu()) {
|
|
/*
|
|
* Need to have DWP draw first so that WM_PRINTCLIENT gets sent
|
|
* to fill in the inside. After this is done, come back and
|
|
* redraw over the frame with the correct menu edge.
|
|
*/
|
|
lRet = xxxDefWindowProc(pwnd, message, wParam, lParam);
|
|
MNDrawEdge(pmenu, (HDC)wParam, &pwnd->rcWindow, 0);
|
|
return lRet;
|
|
} else {
|
|
goto CallDWP;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_WINDOWPOSCHANGING:
|
|
if (!(((LPWINDOWPOS)lParam)->flags & SWP_SHOWWINDOW))
|
|
goto CallDWP;
|
|
|
|
if (!TestEffectUP(MENUANIMATION) || !(ppopupmenu->iDropDir & PAS_OUT)
|
|
|| (glinp.dwFlags & (LINP_KEYBOARD | LINP_JOURNALLING))
|
|
|| (GetAppCompatFlags2(VER40) & GACF2_ANIMATIONOFF)) {
|
|
NoAnimation:
|
|
ppopupmenu->iDropDir &= ~PAS_OUT;
|
|
goto CallDWP;
|
|
}
|
|
|
|
/*
|
|
* Create the animation bitmap.
|
|
*/
|
|
pMenuState->cxAni = pwnd->rcWindow.right - pwnd->rcWindow.left;
|
|
pMenuState->cyAni = pwnd->rcWindow.bottom - pwnd->rcWindow.top;
|
|
|
|
if (TestALPHA(MENUFADE)) {
|
|
if ((hdcAni = CreateFade(pwnd, NULL, CMS_MENUFADE,
|
|
FADE_SHOW | FADE_MENU)) == NULL) {
|
|
goto NoAnimation;
|
|
}
|
|
} else {
|
|
|
|
if (!MNCreateAnimationBitmap(pMenuState, pMenuState->cxAni,
|
|
pMenuState->cyAni)) {
|
|
goto NoAnimation;
|
|
}
|
|
|
|
/*
|
|
* We shouldn't be animating at this time.
|
|
*/
|
|
UserAssert(pMenuState->hdcWndAni == NULL);
|
|
|
|
/*
|
|
* This window must be the active popup
|
|
*/
|
|
UserAssert(pMenuState->pGlobalPopupMenu->spwndActivePopup == pwnd);
|
|
|
|
/*
|
|
* Initialize animation info
|
|
*/
|
|
pMenuState->hdcWndAni = _GetDCEx(pwnd, HRGN_FULL, DCX_WINDOW | DCX_USESTYLE | DCX_INTERSECTRGN);
|
|
pMenuState->iAniDropDir = ppopupmenu->iDropDir;
|
|
pMenuState->ixAni = (pMenuState->iAniDropDir & PAS_HORZ) ? 0 : pMenuState->cxAni;
|
|
pMenuState->iyAni = (pMenuState->iAniDropDir & PAS_VERT) ? 0 : pMenuState->cyAni;
|
|
hdcAni = pMenuState->hdcAni;
|
|
}
|
|
|
|
/*
|
|
* MFWINDOWDC is used by MNEraseBackground to determine where the
|
|
* brush org should be set.
|
|
*/
|
|
SetMF(pmenu, MFWINDOWDC);
|
|
|
|
xxxSendMessage(pwnd, WM_PRINT, (WPARAM)hdcAni, PRF_CLIENT | PRF_NONCLIENT | PRF_ERASEBKGND);
|
|
|
|
ClearMF(pmenu, MFWINDOWDC);
|
|
|
|
/*
|
|
* While the window is still hidden, load the first fade animation
|
|
* frame to avoid flicker when the window is actually shown.
|
|
*
|
|
* There would still be flicker with slide animations, though. It
|
|
* could be fixed by using the window region, similar to
|
|
* AnimateWindow. For now, too many functions would become xxx, so
|
|
* let's not do it, unless it becomes a big issue.
|
|
*/
|
|
if (TestFadeFlags(FADE_MENU)) {
|
|
ShowFade();
|
|
}
|
|
goto CallDWP;
|
|
|
|
case WM_WINDOWPOSCHANGED:
|
|
if (!(((LPWINDOWPOS)lParam)->flags & SWP_SHOWWINDOW))
|
|
goto CallDWP;
|
|
|
|
/*
|
|
* If not animating, nothing else to do here.
|
|
*/
|
|
if (!(ppopupmenu->iDropDir & PAS_OUT))
|
|
goto CallDWP;
|
|
|
|
/*
|
|
* Start the animation cycle now.
|
|
*/
|
|
if (TestFadeFlags(FADE_MENU)) {
|
|
StartFade();
|
|
} else {
|
|
pMenuState->dwAniStartTime = NtGetTickCount();
|
|
_SetTimer(pwnd, IDSYS_MNANIMATE, 1, NULL);
|
|
}
|
|
ppopupmenu->iDropDir &= ~PAS_OUT;
|
|
goto CallDWP;
|
|
|
|
case WM_NCPAINT:
|
|
if (ppopupmenu->iDropDir & PAS_OUT) {
|
|
|
|
/*
|
|
* When animating, validate itself to ensure no further drawing
|
|
* that is not related to the animation.
|
|
*/
|
|
xxxValidateRect(pwnd, NULL);
|
|
} else {
|
|
|
|
/*
|
|
* If we have scroll bars, draw them
|
|
*/
|
|
if (pmenu->dwArrowsOn != MSA_OFF) {
|
|
|
|
HDC hdc = _GetDCEx(pwnd, (HRGN)wParam,
|
|
DCX_USESTYLE | DCX_WINDOW | DCX_INTERSECTRGN | DCX_NODELETERGN | DCX_LOCKWINDOWUPDATE);
|
|
MNDrawFullNC(pwnd, hdc, ppopupmenu);
|
|
_ReleaseDC(hdc);
|
|
} else {
|
|
if (MNIsFlatMenu()) {
|
|
HDC hdc;
|
|
hdc = _GetDCEx(pwnd, (HRGN)wParam,
|
|
DCX_USESTYLE | DCX_WINDOW | DCX_INTERSECTRGN | DCX_NODELETERGN | DCX_LOCKWINDOWUPDATE);
|
|
MNDrawEdge(pmenu, hdc, &pwnd->rcWindow, 0);
|
|
_ReleaseDC(hdc);
|
|
} else {
|
|
goto CallDWP;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_PRINTCLIENT:
|
|
ThreadLock(pmenu, &tlpmenu);
|
|
xxxMenuDraw((HDC)wParam, pmenu);
|
|
ThreadUnlock(&tlpmenu);
|
|
break;
|
|
|
|
case WM_FINALDESTROY:
|
|
/*
|
|
* If we're animating, we must haved been killed in a rude way....
|
|
*/
|
|
UserAssert((pMenuState == NULL) || (pMenuState->hdcWndAni == NULL));
|
|
|
|
/*
|
|
* If this is a drag and drop menu, then call RevokeDragDrop.
|
|
*/
|
|
if ((pMenuState != NULL) && pMenuState->fDragAndDrop) {
|
|
if (!SUCCEEDED(xxxClientRevokeDragDrop(HW(pwnd)))) {
|
|
RIPMSG1(RIP_ERROR, "xxxMenuWindowProc: xxxClientRevokeRegisterDragDrop failed:%#p", pwnd);
|
|
}
|
|
}
|
|
|
|
xxxMNDestroyHandler(ppopupmenu);
|
|
return 0;
|
|
|
|
|
|
case WM_PAINT:
|
|
ThreadLock(pmenu, &tlpmenu);
|
|
xxxBeginPaint(pwnd, &ps);
|
|
xxxMenuDraw(ps.hdc, pmenu);
|
|
xxxEndPaint(pwnd, &ps);
|
|
ThreadUnlock(&tlpmenu);
|
|
break;
|
|
|
|
case WM_CHAR:
|
|
case WM_SYSCHAR:
|
|
xxxMNChar(ppopupmenu, pMenuState, (UINT)wParam);
|
|
break;
|
|
|
|
case WM_KEYDOWN:
|
|
case WM_SYSKEYDOWN:
|
|
xxxMNKeyDown(ppopupmenu, pMenuState, (UINT)wParam);
|
|
break;
|
|
|
|
case WM_TIMER:
|
|
switch (wParam) {
|
|
case IDSYS_MNSHOW:
|
|
/*
|
|
* Open the window and kill the show timer.
|
|
*
|
|
* Cancel any toggle state we might have. We don't
|
|
* want to dismiss this on button up if shown from
|
|
* button down.
|
|
*/
|
|
ppopupmenu->fToggle = FALSE;
|
|
xxxMNOpenHierarchy(ppopupmenu, pMenuState);
|
|
break;
|
|
|
|
case IDSYS_MNHIDE:
|
|
ppopupmenu->fToggle = FALSE;
|
|
xxxMNCloseHierarchy(ppopupmenu,pMenuState);
|
|
break;
|
|
|
|
case IDSYS_MNUP:
|
|
case IDSYS_MNDOWN:
|
|
if (pMenuState->fButtonDown) {
|
|
xxxMNDoScroll(ppopupmenu, (UINT)wParam, FALSE);
|
|
} else {
|
|
_KillTimer(pwnd, (UINT)wParam);
|
|
}
|
|
break;
|
|
|
|
case IDSYS_MNANIMATE:
|
|
if (pMenuState->hdcWndAni != NULL) {
|
|
MNAnimate(pMenuState, TRUE);
|
|
} else {
|
|
/*
|
|
* This timer shouldn't be set. Left over in msg queue?
|
|
*/
|
|
UserAssert(pMenuState->hdcWndAni != NULL);
|
|
}
|
|
break;
|
|
|
|
case IDSYS_MNAUTODISMISS:
|
|
/*
|
|
* This is a one shot timer, so kill it.
|
|
* Dismiss the popup if the flag hasn't been reset.
|
|
*/
|
|
_KillTimer(pwnd, IDSYS_MNAUTODISMISS);
|
|
if (pMenuState->fAboutToAutoDismiss) {
|
|
goto EndMenu;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* Menu messages.
|
|
*/
|
|
case MN_SETHMENU:
|
|
|
|
/*
|
|
* wParam - new hmenu to associate with this menu window
|
|
* Don't let them set the spmenu to NULL of we have to deal with
|
|
* that all over. Use RIP_ERROR for a while to make sure this is OK
|
|
*/
|
|
if (wParam != 0) {
|
|
if ((wParam = (WPARAM)ValidateHmenu((HMENU)wParam)) == 0) {
|
|
break;
|
|
}
|
|
LockPopupMenu(ppopupmenu, &(ppopupmenu->spmenu), (PMENU)wParam);
|
|
} else {
|
|
RIPMSG1(RIP_ERROR, "xxxMenuWindowProc: MN_SETHMENU ignoring NULL wParam. pwnd:%p", pwnd);
|
|
}
|
|
break;
|
|
|
|
case MN_GETHMENU:
|
|
|
|
/*
|
|
* returns the hmenu associated with this menu window
|
|
*/
|
|
return (LRESULT)PtoH(pmenu);
|
|
|
|
case MN_SIZEWINDOW:
|
|
{
|
|
|
|
/*
|
|
* Computes the size of the menu associated with this window and resizes
|
|
* it if needed. Size is returned x in loword, y in highword. wParam
|
|
* is 0 to just return new size. wParam is non zero if we should also resize
|
|
* window.
|
|
* When called by xxxMNUpdateShownMenu, we might need to redraw the
|
|
* frame (i.e, the scrollbars). So we check for MNSW_DRAWFRAME in wParam.
|
|
* If some app is sending this message and that bit is set, then we'll
|
|
* do some extra work, but I think everything should be cool.
|
|
*/
|
|
int cx, cy;
|
|
PMONITOR pMonitor;
|
|
|
|
/*
|
|
* Call menucomputeHelper directly since this is the entry point for
|
|
* non toplevel menu bars.
|
|
*/
|
|
if (pmenu == NULL)
|
|
break;
|
|
|
|
ThreadLockAlways(pmenu, &tlpmenu);
|
|
ThreadLock(ppopupmenu->spwndNotify, &tlpwndNotify);
|
|
UserAssert(pmenu->spwndNotify == ppopupmenu->spwndNotify);
|
|
xxxMNCompute(pmenu, ppopupmenu->spwndNotify, 0, 0, 0, 0);
|
|
ThreadUnlock(&tlpwndNotify);
|
|
ThreadUnlock(&tlpmenu);
|
|
|
|
pMonitor = _MonitorFromWindow(pwnd, MONITOR_DEFAULTTOPRIMARY);
|
|
cx = pmenu->cxMenu;
|
|
cy = MNCheckScroll(pmenu, pMonitor);
|
|
|
|
/*
|
|
* Size the window?
|
|
*/
|
|
if (wParam != 0) {
|
|
LONG lPos;
|
|
int x, y;
|
|
DWORD dwFlags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER;
|
|
|
|
/*
|
|
* Need to redraw the frame?
|
|
*/
|
|
if (wParam & MNSW_DRAWFRAME) {
|
|
dwFlags |= SWP_DRAWFRAME;
|
|
}
|
|
|
|
/*
|
|
* If the window is visible, it's being resized while
|
|
* shown. So make sure that it still fits on the screen
|
|
* (i.e, move it to the best pos).
|
|
*/
|
|
if (TestWF(pwnd, WFVISIBLE)) {
|
|
lPos = FindBestPos(
|
|
pwnd->rcWindow.left,
|
|
pwnd->rcWindow.top,
|
|
cx,
|
|
cy,
|
|
NULL,
|
|
0,
|
|
ppopupmenu,
|
|
pMonitor);
|
|
|
|
x = GET_X_LPARAM(lPos);
|
|
y = GET_Y_LPARAM(lPos);
|
|
} else {
|
|
dwFlags |= SWP_NOMOVE;
|
|
}
|
|
|
|
xxxSetWindowPos(
|
|
pwnd,
|
|
PWND_TOP,
|
|
x,
|
|
y,
|
|
cx + 2 * SYSMET(CXFIXEDFRAME), /* For shadow */
|
|
cy + 2 * SYSMET(CYFIXEDFRAME), /* For shadow */
|
|
dwFlags);
|
|
|
|
}
|
|
|
|
return MAKELONG(cx, cy);
|
|
}
|
|
|
|
case MN_OPENHIERARCHY:
|
|
{
|
|
PWND pwndT;
|
|
/*
|
|
* Opens one level of the hierarchy at the selected item, if
|
|
* present. Return 0 if error, else hwnd of opened hierarchy.
|
|
*/
|
|
pwndT = xxxMNOpenHierarchy(ppopupmenu, pMenuState);
|
|
return (LRESULT)HW(pwndT);
|
|
}
|
|
|
|
case MN_CLOSEHIERARCHY:
|
|
xxxMNCloseHierarchy(ppopupmenu, pMenuState);
|
|
break;
|
|
|
|
case MN_SELECTITEM:
|
|
/*
|
|
* wParam - the item to select. Must be a valid
|
|
* Returns the item flags of the wParam (0 if failure)
|
|
*/
|
|
if ((wParam >= pmenu->cItems) && (wParam < MFMWFP_MINVALID)) {
|
|
UserAssertMsg1(FALSE, "Bad wParam %x for MN_SELECTITEM", wParam);
|
|
break;
|
|
}
|
|
|
|
pItem = xxxMNSelectItem(ppopupmenu, pMenuState, (UINT)wParam);
|
|
if (pItem != NULL) {
|
|
return((LONG)(DWORD)(WORD)(pItem->fState |
|
|
((pItem->spSubMenu != NULL) ? MF_POPUP : 0)));
|
|
}
|
|
|
|
break;
|
|
|
|
case MN_SELECTFIRSTVALIDITEM:
|
|
{
|
|
UINT item;
|
|
|
|
item = MNFindNextValidItem(pmenu, -1, 1, TRUE);
|
|
xxxSendMessage(pwnd, MN_SELECTITEM, item, 0L);
|
|
return (LONG)item;
|
|
}
|
|
|
|
case MN_CANCELMENUS:
|
|
|
|
/*
|
|
* Cancels all menus, unselects everything, destroys windows, and cleans
|
|
* everything up for this hierarchy. wParam is the command to send and
|
|
* lParam says if it is valid or not.
|
|
*/
|
|
xxxMNCancel(pMenuState, (UINT)wParam, (BOOL)LOWORD(lParam), 0);
|
|
break;
|
|
|
|
case MN_FINDMENUWINDOWFROMPOINT:
|
|
/*
|
|
* lParam is point to search for from this hierarchy down.
|
|
* returns MFMWFP_* value or a pwnd.
|
|
*/
|
|
lRet = xxxMNFindWindowFromPoint(ppopupmenu, (PUINT)wParam, MAKEPOINTS(lParam));
|
|
|
|
/*
|
|
* Convert return value to a handle.
|
|
*/
|
|
if (IsMFMWFPWindow(lRet)) {
|
|
return (LRESULT)HW((PWND)lRet);
|
|
} else {
|
|
return lRet;
|
|
}
|
|
|
|
|
|
case MN_SHOWPOPUPWINDOW:
|
|
/*
|
|
* Forces the dropped down popup to be visible and if modeless, also active.
|
|
*/
|
|
PlayEventSound(USER_SOUND_MENUPOPUP);
|
|
xxxShowWindow(pwnd, (pMenuState->fModelessMenu ? SW_SHOW : SW_SHOWNOACTIVATE));
|
|
break;
|
|
|
|
case MN_ACTIVATEPOPUP:
|
|
/*
|
|
* Activates a popup. This messages is posted in response to WM_ACTIVATEAPP
|
|
* or WM_ACTIVATE
|
|
*/
|
|
UserAssert(pMenuState->fModelessMenu);
|
|
xxxActivateThisWindow(pwnd, 0, 0);
|
|
break;
|
|
|
|
case MN_ENDMENU:
|
|
/*
|
|
* End the menu. This message is posted to avoid ending the menu
|
|
* at randmom moments. By posting the message, the request is
|
|
* queued after any pending/current processing.
|
|
*/
|
|
EndMenu:
|
|
xxxEndMenuLoop(pMenuState, pMenuState->pGlobalPopupMenu);
|
|
if (pMenuState->fModelessMenu) {
|
|
UserAssert(!pMenuState->fInCallHandleMenuMessages);
|
|
xxxMNEndMenuState(TRUE);
|
|
}
|
|
return 0;
|
|
|
|
case MN_DODRAGDROP:
|
|
/*
|
|
* Let the app know that the user is dragging.
|
|
*/
|
|
if (pMenuState->fDragging
|
|
&& (ppopupmenu->spwndNotify != NULL)
|
|
&& IsMFMWFPWindow(pMenuState->uButtonDownHitArea)) {
|
|
/*
|
|
* Get the pmenu that contains the item being dragged
|
|
*/
|
|
pmenu = (((PMENUWND)pMenuState->uButtonDownHitArea)->ppopupmenu)->spmenu;
|
|
/*
|
|
* If this is a modal menu, release the capture lock so
|
|
* DoDragDrop (if called) can get it.
|
|
*/
|
|
if (!pMenuState->fModelessMenu) {
|
|
UserAssert(PtiCurrent()->pq->QF_flags & QF_CAPTURELOCKED);
|
|
PtiCurrent()->pq->QF_flags &= ~QF_CAPTURELOCKED;
|
|
}
|
|
|
|
LockMenuState(pMenuState);
|
|
ThreadLockAlways(ppopupmenu->spwndNotify, &tlpwndNotify);
|
|
|
|
/*
|
|
* Give them a chance to call DoDragDrop
|
|
*/
|
|
pMenuState->fInDoDragDrop = TRUE;
|
|
lRet = xxxSendMessage(ppopupmenu->spwndNotify, WM_MENUDRAG,
|
|
pMenuState->uButtonDownIndex, (LPARAM)PtoH(pmenu));
|
|
pMenuState->fInDoDragDrop = FALSE;
|
|
|
|
if (lRet == MND_ENDMENU) {
|
|
/*
|
|
* Go away.
|
|
*/
|
|
ThreadUnlock(&tlpwndNotify);
|
|
if (!xxxUnlockMenuState(pMenuState)) {
|
|
goto EndMenu;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
} else {
|
|
/*
|
|
* If the user starts dragging, we always
|
|
* ignore the following button up
|
|
*/
|
|
pMenuState->fIgnoreButtonUp = TRUE;
|
|
}
|
|
|
|
/*
|
|
* Check the button state since we might have not seen the button up
|
|
* If so, this will cancel the dragging state
|
|
*/
|
|
MNCheckButtonDownState(pMenuState);
|
|
|
|
/*
|
|
* If this is a modal menu, make sure we recover capture
|
|
*/
|
|
if (!pMenuState->fModelessMenu) {
|
|
xxxMNSetCapture(ppopupmenu);
|
|
}
|
|
|
|
ThreadUnlock(&tlpwndNotify);
|
|
xxxUnlockMenuState(pMenuState);
|
|
}
|
|
return 0;
|
|
|
|
case MN_BUTTONDOWN:
|
|
|
|
/*
|
|
* wParam is position (index) of item the button was clicked on.
|
|
* Must be a valid
|
|
*/
|
|
if ((wParam >= pmenu->cItems) && (wParam < MFMWFP_MINVALID)) {
|
|
UserAssertMsg1(FALSE, "Bad wParam %x for MN_BUTTONDOWN", wParam);
|
|
break;
|
|
}
|
|
xxxMNButtonDown(ppopupmenu, pMenuState, (UINT)wParam, TRUE);
|
|
break;
|
|
|
|
case MN_MOUSEMOVE:
|
|
|
|
/*
|
|
* lParam is mouse move coordinate wrt screen.
|
|
*/
|
|
xxxMNMouseMove(ppopupmenu, pMenuState, MAKEPOINTS(lParam));
|
|
break;
|
|
|
|
case MN_BUTTONUP:
|
|
|
|
/*
|
|
* wParam is position (index) of item the button was up clicked on.
|
|
*/
|
|
if ((wParam >= pmenu->cItems) && (wParam < MFMWFP_MINVALID)) {
|
|
UserAssertMsg1(FALSE, "Bad wParam %x for MN_BUTTONUP", wParam);
|
|
break;
|
|
}
|
|
xxxMNButtonUp(ppopupmenu, pMenuState, (UINT)wParam, lParam);
|
|
break;
|
|
|
|
case MN_SETTIMERTOOPENHIERARCHY:
|
|
|
|
/*
|
|
* Given the current selection, set a timer to show this hierarchy if
|
|
* valid else return 0.
|
|
*/
|
|
return (LONG)(WORD)MNSetTimerToOpenHierarchy(ppopupmenu);
|
|
|
|
case MN_DBLCLK:
|
|
//
|
|
// User double-clicked on item. wParamLo is the item.
|
|
//
|
|
xxxMNDoubleClick(pMenuState, ppopupmenu, (int)wParam);
|
|
break;
|
|
|
|
case WM_MOUSELEAVE:
|
|
UserAssert(pMenuState->fModelessMenu);
|
|
/*
|
|
* If we're in DoDragDrop loop, we don't track the mouse
|
|
* when it goes off the menu window
|
|
*/
|
|
pMenuState->fMouseOffMenu = !pMenuState->fInDoDragDrop;
|
|
ppopupmenu->fTrackMouseEvent = FALSE;
|
|
/*
|
|
* See if we need to set the timer to autodismiss
|
|
*/
|
|
MNSetTimerToAutoDismiss(pMenuState, pwnd);
|
|
/*
|
|
* If we left the active popup, remove the selection
|
|
*/
|
|
if (ppopupmenu->spwndPopupMenu == pMenuState->pGlobalPopupMenu->spwndActivePopup) {
|
|
xxxMNSelectItem(ppopupmenu, pMenuState, MFMWFP_NOITEM);
|
|
}
|
|
break;
|
|
|
|
case WM_ACTIVATEAPP:
|
|
if (pMenuState->fModelessMenu
|
|
&& (pwnd == pMenuState->pGlobalPopupMenu->spwndActivePopup)) {
|
|
/*
|
|
* If the application is getting activated, we post a message
|
|
* to let the dust settle and then re-activate spwndPopupActive
|
|
*/
|
|
if (wParam) {
|
|
_PostMessage(pwnd, MN_ACTIVATEPOPUP, 0, 0);
|
|
/*
|
|
* If we're not in the foregruond queue, we want to keep
|
|
* the frame off.
|
|
* This flag will also tell us that if we lose activation
|
|
* while coming to the foregrund (later), we don't want
|
|
* to dismiss the menu.
|
|
*/
|
|
pMenuState->fActiveNoForeground = (gpqForeground != PtiCurrent()->pq);
|
|
}
|
|
|
|
/*
|
|
* Make the notification window frame show that we're active/inactive.
|
|
* If the application is inactive but the user moves the mouse
|
|
* over the menu, then we can get this message when the first
|
|
* window in the app gets activated (i.e., the move causes a popup to
|
|
* be closed/opened). So turn on the frame only if we're in
|
|
* the foreground.
|
|
*/
|
|
if (ppopupmenu->spwndNotify != NULL) {
|
|
ThreadLockAlways(ppopupmenu->spwndNotify, &tlpwndNotify);
|
|
xxxDWP_DoNCActivate(ppopupmenu->spwndNotify,
|
|
((wParam && !pMenuState->fActiveNoForeground) ? NCA_ACTIVE : NCA_FORCEFRAMEOFF),
|
|
HRGN_FULL);
|
|
ThreadUnlock(&tlpwndNotify);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_ACTIVATE:
|
|
if (pMenuState->fModelessMenu) {
|
|
/*
|
|
* If activation is NOT going to a menu window or
|
|
* it's going to a recursed menu, bail
|
|
*/
|
|
if ((LOWORD(wParam) == WA_INACTIVE)
|
|
&& !pMenuState->fInCallHandleMenuMessages
|
|
&& !pMenuState->pGlobalPopupMenu->fInCancel) {
|
|
|
|
lParam = (LPARAM)RevalidateHwnd((HWND)lParam);
|
|
if ((lParam != 0)
|
|
&& ((GETFNID((PWND)lParam) != FNID_MENU)
|
|
|| IsRecursedMenuState(pMenuState, ((PMENUWND)lParam)->ppopupmenu))) {
|
|
/*
|
|
* If we're just coming to the foreground, then
|
|
* activate the popup later and stay up.
|
|
*/
|
|
if (pMenuState->fActiveNoForeground
|
|
&& (gpqForeground == PtiCurrent()->pq)) {
|
|
|
|
pMenuState->fActiveNoForeground = FALSE;
|
|
_PostMessage(pwnd, MN_ACTIVATEPOPUP, 0, 0);
|
|
} else {
|
|
/*
|
|
* Since the menu window is active, ending the menu
|
|
* now would set a new active window, messing the
|
|
* current activation that sent us this message.
|
|
* so end the menu later.
|
|
*/
|
|
_PostMessage(pwnd, MN_ENDMENU, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
goto CallDWP;
|
|
}
|
|
|
|
/*
|
|
* We must make sure that the menu window does not get activated.
|
|
* Powerpoint 2.00e activates it deliberately and this causes problems.
|
|
* We try to activate the previously active window in such a case.
|
|
* Fix for Bug #13961 --SANKAR-- 09/26/91--
|
|
*/
|
|
/*
|
|
* In Win32, wParam has other information in the hi 16bits, so to
|
|
* prevent infinite recursion, we need to mask off those bits
|
|
* Fix for NT bug #13086 -- 23-Jun-1992 JonPa
|
|
*
|
|
*/
|
|
|
|
if (LOWORD(wParam)) {
|
|
TL tlpwnd;
|
|
/*
|
|
* This is a super bogus hack. Let's start failing this for 5.0 apps.
|
|
*/
|
|
if (Is500Compat(PtiCurrent()->dwExpWinVer)) {
|
|
RIPMSGF1(RIP_WARNING, "Menu window 0x%p activated", pwnd);
|
|
_PostMessage(pwnd, MN_ENDMENU, 0, 0);
|
|
break;
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
* Activate the previously active wnd
|
|
*/
|
|
xxxActivateWindow(pwnd, AW_SKIP2);
|
|
#else
|
|
/*
|
|
* Try the previously active window.
|
|
*/
|
|
if ((gpqForegroundPrev != NULL) &&
|
|
!FBadWindow(gpqForegroundPrev->spwndActivePrev) &&
|
|
!ISAMENU(gpqForegroundPrev->spwndActivePrev)) {
|
|
pwnd = gpqForegroundPrev->spwndActivePrev;
|
|
} else {
|
|
|
|
/*
|
|
* Find a new active window from the top-level window list.
|
|
* Bug 78131: Make sure we don't loop for ever. This is a pretty
|
|
* unusual scenario (in addtion, normally we should not hit this code path)
|
|
* So let's use a counter to rule out the possibility that another
|
|
* weird window configuration is going to make us loop for ever
|
|
*/
|
|
PWND pwndMenu = pwnd;
|
|
UINT uCounter = 0;
|
|
do {
|
|
pwnd = NextTopWindow(PtiCurrent(), pwnd, NULL, 0);
|
|
if (pwnd && !FBadWindow(pwnd->spwndLastActive) &&
|
|
!ISAMENU(pwnd->spwndLastActive)) {
|
|
pwnd = pwnd->spwndLastActive;
|
|
uCounter = 0;
|
|
break;
|
|
}
|
|
} while ((pwnd != NULL) && (uCounter++ < 255));
|
|
/*
|
|
* If we couldn't find a window, just bail.
|
|
*/
|
|
if (uCounter != 0) {
|
|
RIPMSG0(RIP_ERROR, "xxxMenuWindowProc: couldn't fix active window");
|
|
_PostMessage(pwndMenu, MN_ENDMENU, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pwnd != NULL) {
|
|
PTHREADINFO pti = PtiCurrent();
|
|
ThreadLockAlwaysWithPti(pti, pwnd, &tlpwnd);
|
|
|
|
/*
|
|
* If GETPTI(pwnd) isn't pqCurrent this is a AW_SKIP* activation
|
|
* we'll want to a do a xxxSetForegroundWindow().
|
|
*/
|
|
if (GETPTI(pwnd)->pq != pti->pq) {
|
|
|
|
/*
|
|
* Only allow this if we're on the current foreground queue.
|
|
*/
|
|
if (gpqForeground == pti->pq) {
|
|
xxxSetForegroundWindow(pwnd, FALSE);
|
|
}
|
|
} else {
|
|
xxxActivateThisWindow(pwnd, 0, ATW_SETFOCUS);
|
|
}
|
|
|
|
ThreadUnlock(&tlpwnd);
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
case WM_SIZE:
|
|
case WM_MOVE:
|
|
/*
|
|
* When a popup has been sized/moved, we need to make
|
|
* sure any dropped hierarchy is moved accordingly.
|
|
*/
|
|
if (ppopupmenu->spwndNextPopup != NULL) {
|
|
pItem = MNGetpItem(ppopupmenu, ppopupmenu->posDropped);
|
|
if (pItem != NULL) {
|
|
int x, y;
|
|
PMONITOR pMonitorDummy;
|
|
|
|
/*
|
|
* If the dropped hierarchy needs to be recomputed, do it
|
|
*/
|
|
#define pmenuNext (((PMENUWND)ppopupmenu->spwndNextPopup)->ppopupmenu->spmenu)
|
|
if (pmenuNext->cxMenu == 0) {
|
|
xxxSendMessage(ppopupmenu->spwndNextPopup, MN_SIZEWINDOW, MNSW_RETURNSIZE, 0L);
|
|
}
|
|
|
|
/*
|
|
* Find out the new position
|
|
*/
|
|
xxxMNPositionHierarchy(ppopupmenu, pItem,
|
|
pmenuNext->cxMenu + (2 * SYSMET(CXFIXEDFRAME)),
|
|
pmenuNext->cyMenu + (2 * SYSMET(CXFIXEDFRAME)),
|
|
&x, &y, &pMonitorDummy);
|
|
|
|
/*
|
|
* Move it
|
|
*/
|
|
ThreadLockAlways(ppopupmenu->spwndNextPopup, &tlpwndNotify);
|
|
xxxSetWindowPos(ppopupmenu->spwndNextPopup, NULL,
|
|
x, y, 0, 0,
|
|
SWP_NOSIZE | SWP_NOZORDER | SWP_NOSENDCHANGING);
|
|
ThreadUnlock(&tlpwndNotify);
|
|
#undef pmenuNext
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_NCHITTEST:
|
|
/*
|
|
* Since modeless menus don't capture the mouse, we
|
|
* process this message to make sure that we always receive
|
|
* a mouse move when the mouse in our window.
|
|
* This also causes us to receive the WM_MOUSELEAVE only when
|
|
* the mouse leaves the window and not just the client area.
|
|
*/
|
|
if (pMenuState->fModelessMenu) {
|
|
ptOrg.x = GET_X_LPARAM(lParam);
|
|
ptOrg.y = GET_Y_LPARAM(lParam);
|
|
if (PtInRect(&pwnd->rcWindow, ptOrg)) {
|
|
return HTCLIENT;
|
|
} else {
|
|
return HTNOWHERE;
|
|
}
|
|
} else {
|
|
goto CallDWP;
|
|
}
|
|
|
|
|
|
default:
|
|
CallDWP:
|
|
return xxxDefWindowProc(pwnd, message, wParam, lParam);
|
|
}
|
|
|
|
return 0;
|
|
}
|