Leaked source code of windows server 2003
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

/**************************** 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;
}