Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

2957 lines
92 KiB

/**************************** Module Header ********************************\
* Module Name: menu.c
*
* Copyright 1985-90, Microsoft Corporation
*
* Keyboard Accelerator Routines
*
* History:
* 05-25-91 Mikehar Ported from Win3.1
\***************************************************************************/
#include "precomp.h"
#pragma hdrstop
UINT MNSetTimerToCloseHierarchy(PPOPUPMENU ppopup);
// --------------------------------------------------------------------------
//
// MNIsPopupItem()
//
// --------------------------------------------------------------------------
BOOL MNIsPopupItem(ITEM *lpItem)
{
return((lpItem) && (lpItem->spSubMenu) &&
!TestMFS(lpItem, MFS_GRAYED));
}
/***************************************************************************\
* Validateppopupmenu
*
* 05-15-96 GerardoB Created
\***************************************************************************/
#ifdef DEBUG
void Validateppopupmenu (PPOPUPMENU ppopupmenu)
{
UserAssert(ppopupmenu != NULL);
try {
UserAssert(!ppopupmenu->fFreed && !ppopupmenu->dwUnused);
/*
* 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(RevalidateHwnd(HW(ppopupmenu->spwndPopupMenu)));
}
/*
* This can be NULL when called from xxxDestroyWindow (spwndNotify)
* or from xxxDeleteThreadInfo
*/
if (ppopupmenu->spwndNotify != NULL) {
UserAssert(RevalidateHwnd(HW(ppopupmenu->spwndNotify)));
}
} except (EXCEPTION_EXECUTE_HANDLER) {
RIPMSG1(RIP_ERROR, "Validateppopupmenu: Invalid popup:%#lx", ppopupmenu);
}
}
#endif
/***************************************************************************\
* xxxSwitchToAlternateMenu
*
* 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) {
RIPERR0(ERROR_INVALID_PARAMETER, RIP_ERROR, "not a menu bar");
/*
* Do nothing if no menu or not top level menu bar.
*/
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");
return FALSE;
}
xxxMNSelectItem(ppopupmenu, pMenuState, MFMWFP_NOITEM);
Lock(&pmenuSwap, ppopupmenu->spmenuAlternate);
Lock(&ppopupmenu->spmenuAlternate, ppopupmenu->spmenu);
Lock(&ppopupmenu->spmenu, pmenuSwap);
Unlock(&pmenuSwap);
/*
* Set this global because it is used in SendMenuSelect()
*/
if (!TestWF(ppopupmenu->spwndNotify, WFSYSMENU)) {
pMenuState->fIsSysMenu = FALSE;
} else if (ppopupmenu->spwndNotify->spmenuSys != NULL) {
pMenuState->fIsSysMenu = (ppopupmenu->spwndNotify->spmenuSys ==
ppopupmenu->spmenu);
} else {
pMenuState->fIsSysMenu =
(ppopupmenu->spwndNotify->head.rpdesk->spmenuSys ==
ppopupmenu->spmenu);
}
ppopupmenu->fIsSysMenu = pMenuState->fIsSysMenu;
ThreadUnlock(&tlpwndPopupMenu);
return TRUE;
}
/***************************************************************************\
* MenuDestroyHandler
*
* cleans up after this menu
*
* History:
* 05-25-91 Mikehar Ported from Win3.1
\***************************************************************************/
void xxxMNDestroyHandler(
PDESKTOP pdesk,
PPOPUPMENU ppopupmenu)
{
PITEM pItem;
TL tlpwndT;
#ifdef DEBUG
/*
* When destroying a desktop's spwndMenu that is not in use (i.e., the
* desktop is going away), the ppopupmenu is not exactly valid (i.e.,
* we're not in menu mode) but it should be properly NULLed out so
* everything should go smoothly
*/
Validateppopupmenu(ppopupmenu);
#endif
if (!ppopupmenu) {
RIPERR0(ERROR_INVALID_PARAMETER, RIP_ERROR,
"Invalid parameter \"ppopupmenu\" (NULL) to xxxMNDestroyHandler");
return;
}
if (ppopupmenu->spwndNextPopup) {
ThreadLockAlways(ppopupmenu->spwndNextPopup, &tlpwndT);
xxxSendMessage(ppopupmenu->spwndNextPopup, MN_CLOSEHIERARCHY, 0, 0);
ThreadUnlock(&tlpwndT);
}
if ((ppopupmenu->spmenu!=NULL) && (ppopupmenu->posSelectedItem != MFMWFP_NOITEM))
{
// 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);
}
ppopupmenu->fDestroyed = TRUE;
if (ppopupmenu->spwndPopupMenu != pdesk->spwndMenu) {
if (ppopupmenu->spwndPopupMenu != NULL) {
((PMENUWND)(ppopupmenu->spwndPopupMenu))->ppopupmenu = NULL;
}
if (!ppopupmenu->fDelayedFree) {
MNFreePopup(ppopupmenu);
} else if (ppopupmenu->ppopupmenuRoot != NULL) {
ppopupmenu->ppopupmenuRoot->fFlushDelayedFree = TRUE;
}
} else {
pdesk->fMenuInUse = FALSE;
}
}
/***************************************************************************\
* MenuCharHandler
*
* 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;
LONG result;
int item;
INT matchType;
BOOL fExecute = FALSE;
TL tlpwndNotify;
pMenu = ppopupmenu->spmenu;
#ifdef DEBUG
Validateppopupmenu(ppopupmenu);
#endif
/*
* 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);
xxxMNCancel(ppopupmenu->ppopupmenuRoot, 0, FALSE, 0);
return;
}
/*
* If we're getting out of menu mode, bail
*/
if (ppopupmenu->fDestroyed) {
return;
}
item = MNFindChar(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 = MNFindChar(pMenu, character, item, &matchType);
if (item == firstItem) {
xxxMNCancel(ppopupmenu->ppopupmenuRoot, 0, FALSE, 0);
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 = MNFindChar(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 = MNFindChar(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),
(LONG)PtoH(ppopupmenu->spmenu));
ThreadUnlock(&tlpwndNotify);
switch (HIWORD(result)) {
case MNC_IGNORE:
xxxMessageBeep(0);
return;
case MNC_CLOSE:
xxxMNCancel(ppopupmenu->ppopupmenuRoot, 0, FALSE, 0);
return;
case MNC_EXECUTE:
fExecute = TRUE;
// fall thru
case MNC_SELECT:
item = (UINT)(short)LOWORD(result);
if ((WORD) item >= ppopupmenu->spmenu->cItems)
{
RIPMSG0(RIP_ERROR, "Invalid item number returned from WM_MENUCHAR");
return;
}
break;
}
}
if (item != MFMWFP_NOITEM) {
xxxMNSelectItem(ppopupmenu, pMenuState, item);
if (fExecute)
xxxMNKeyDown(ppopupmenu, pMenuState, VK_RETURN);
}
}
/***************************************************************************\
*
* GetMenuInheritedContextHelpId(PPOPUPMENU ppopup)
* 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)
break; // Found the context Id
// 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);
}
/***************************************************************************\
* void MenuKeyDownHandler(PPOPUPMENU ppopupmenu, UINT key)
* effects: 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;
if ((pMenuState->fButtonDown) && (key != VK_F1)) {
/*
* Blow off keyboard if mouse down.
*/
return;
}
switch (key) {
case VK_MENU:
case VK_F10:
if (winOldAppHackoMaticFlags & WOAHACK_CHECKALTKEYSTATE) {
/*
* Winoldapp is telling us to put up/down the system menu. Due to
* possible race conditions, we need to check the state of the alt
* key before throwing away the menu.
*/
if (winOldAppHackoMaticFlags & WOAHACK_IGNOREALTKEYDOWN) {
return;
}
}
xxxMNCancel(ppopupmenu->ppopupmenuRoot, 0, 0, 0);
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 or if this is a 2.x application, we need to get out of menu
* mode. Otherwise, we popup up one level in the hierarchy.
*/
if (LOWORD(ppopupmenu->spwndNotify->dwExpWinVer) < VER30 ||
ppopupmenu->fIsMenuBar ||
ppopupmenu == ppopupmenu->ppopupmenuRoot ||
TestWF(ppopupmenu->ppopupmenuRoot->spwndNotify, WFMINIMIZED)) {
xxxMNCancel(ppopupmenu->ppopupmenuRoot, 0, 0, 0);
} 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.
*/
xxxMNCancel(ppopupmenuRoot, 0, FALSE, 0);
} else
#endif
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:
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) {
/*
* 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, key, 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)) {
PWND pwndNextMenu;
PMENU pmenuNextMenu;
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)key, (LONG)&mnm);
ThreadUnlock(&tlpwndT);
pwndNextMenu = RevalidateHwnd(mnm.hwndNext);
if (pwndNextMenu == NULL)
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;
Unlock(&ppopupmenu->spmenuAlternate);
ppopupmenu->fToggle = FALSE;
Lock(&ppopupmenu->spmenu, pmenuNextMenu);
Lock(&ppopupmenu->spwndNotify, pwndNextMenu);
Lock(&ppopupmenu->spwndPopupMenu, pwndNextMenu);
/*
* GetSystemMenu(pwnd, FALSE) and pwnd->spmenuSys are
* NOT equivalent -- GetSystemMenu returns the 1st submenu
* of pwnd->spmenuSys -- make up for that here
*/
if (_GetSubMenu((PMENU)pwndNextMenu->spmenuSys, 0) ==
ppopupmenu->spmenu) {
Lock(&ppopupmenu->spmenu, pwndNextMenu->spmenuSys);
}
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) {
Lock(&ppopupmenu->spmenuAlternate,
pwndNextMenu->spmenuSys);
pMenuState->fIsSysMenu = FALSE;
} else
Lock(&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) {
xxxMNCancel(ppopupmenu->ppopupmenuRoot, 0, 0, 0);
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.
*/
xxxMNCancel(ppopupmenu->ppopupmenuRoot, pItem->wID, fEnabled, 0L);
return;
}
case VK_F1: // Provide context sensitive help.
{
PITEM pItem;
pItem = ppopupmenu->spmenu->rgItems + ppopupmenu->posSelectedItem;
ThreadLock(ppopupmenu->spwndNotify, &tlpwndT);
xxxSendHelpMessage(ppopupmenu->spwndNotify, HELPINFO_MENUITEM, pItem->wID,
PtoHq(ppopupmenu->spmenu),
GetMenuInheritedContextHelpId(ppopupmenu), NULL);
ThreadUnlock(&tlpwndT);
break;
}
}
}
/***************************************************************************\
* PWND MenuOpenHierarchyHandler(PPOPUPMENU ppopupmenu)
* effects: 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;
PDESKTOP pdesk = PtiCurrent()->rpdesk;
PTHREADINFO pti;
if (ppopupmenu->posSelectedItem == MFMWFP_NOITEM) {
/*
* No selection so fail.
*/
// RIPERR0(ERROR_CAN_NOT_COMPLETE, RIP_WARNING, "No menu selection");
return NULL;
}
if (ppopupmenu->posSelectedItem >= ppopupmenu->spmenu->cItems)
return NULL;
if (ppopupmenu->fHierarchyDropped) {
if (ppopupmenu->fHideTimer) {
xxxMNCloseHierarchy(ppopupmenu,pMenuState);
} else {
/*
* Hierarchy already dropped so fail
*/
// RIPERR0(ERROR_CAN_NOT_COMPLETE, RIP_WARNING, "menu already open");
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
xxxSendMessage(ppopupmenu->spwndNotify, WM_INITMENUPOPUP,
(DWORD)PtoHq(pItem->spSubMenu), MAKELONG(ppopupmenu->posSelectedItem,
(ppopupmenu->fIsSysMenu ? 1: 0)));
ThreadUnlock(&tlpwndT);
}
//
// B#1517
// Check if we're still in menu loop
//
if (!pMenuState->fInsideMenuLoop) {
RIPMSG0(RIP_ERROR, "Menu loop ended unexpectedly by WM_INITMENUPOPUP");
return((PWND) -1);
}
/*
* 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);
if (ppopupmenu->fIsMenuBar && (pdesk->spwndMenu != NULL) &&
(pdesk->fMenuInUse == FALSE) &&
!TestWF(pdesk->spwndMenu, WFVISIBLE)) {
pdesk->fMenuInUse = TRUE;
if (HMPheFromObject(pdesk->spwndMenu)->bFlags & HANDLEF_DESTROY) {
PPROCESSINFO ppi = pdesk->rpwinstaParent->ptiDesktop->ppi;
PPROCESSINFO ppiSave;
PTHREADINFO ptiCurrent = PtiCurrent();
PWND pwndMenu;
DWORD dwDisableHooks;
/* the menu window is destroyed -- recreate it
*/
Unlock(&pdesk->spwndMenu);
ppiSave = ptiCurrent->ppi;
ptiCurrent->ppi = ppi;
/*
* HACK HACK HACK!!! (adams) In order to create the menu window
* with the correct desktop, we set the desktop of the current thread
* to the new desktop. But in so doing we allow hooks on the current
* thread to also hook this new desktop. This is bad, because we don't
* want the menu window to be hooked while it is created. So we
* temporarily disable hooks of the current thread or desktop,
* and reenable them after switching back to the original desktop.
*/
dwDisableHooks = ptiCurrent->TIF_flags & TIF_DISABLEHOOKS;
ptiCurrent->TIF_flags |= TIF_DISABLEHOOKS;
pwndMenu = xxxCreateWindowEx(
WS_EX_TOOLWINDOW | WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE,
(PLARGE_STRING)MENUCLASS,
NULL,
WS_POPUP | WS_BORDER,
0,
0,
100,
100,
NULL,
NULL,
hModuleWin,
NULL,
VER40);
UserAssert(ptiCurrent->TIF_flags & TIF_DISABLEHOOKS);
ptiCurrent->TIF_flags = (ptiCurrent->TIF_flags & ~TIF_DISABLEHOOKS) | dwDisableHooks;
Lock(&(pdesk->spwndMenu),pwndMenu);
ptiCurrent->ppi = ppiSave;
HMChangeOwnerThread(pdesk->spwndMenu, pdesk->rpwinstaParent->ptiDesktop);
}
pwndHierarchy = pdesk->spwndMenu;
Lock(&pwndHierarchy->spwndOwner, ppopupmenu->spwndNotify);
pwndHierarchy->head.pti = PtiCurrent();
if (!TestWF(pdesk->spwndMenu, WEFTOPMOST)) {
// If menu hwnd isn't topmost, make it so
ThreadLock(pdesk->spwndMenu, &tlpwndHierarchy);
xxxSetWindowPos(pdesk->spwndMenu, PWND_TOPMOST, 0,0,0,0,
SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOOWNERZORDER);
ThreadUnlock(&tlpwndHierarchy);
}
ppopupmenuHierarchy = ((PMENUWND)pwndHierarchy)->ppopupmenu;
ppopupmenuHierarchy->posSelectedItem = MFMWFP_NOITEM;
Lock(&ppopupmenuHierarchy->spwndPopupMenu, pdesk->spwndMenu);
} else {
ThreadLock(ppopupmenu->spwndNotify, &tlpwndT);
pwndHierarchy = xxxCreateWindowEx(
WS_EX_TOOLWINDOW | WS_EX_TOPMOST | 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, VER40);
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;
}
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;
Lock(&(ppopupmenuHierarchy->spwndNotify), ppopupmenu->spwndNotify);
Lock(&(ppopupmenuHierarchy->spmenu), pItem->spSubMenu);
ppopupmenuHierarchy->fAboutToHide = FALSE;
// Find the size of the menu window and actually size it (wParam = 1)
ThreadLock(pwndHierarchy, &tlpwndHierarchy);
sizeHierarchy = xxxSendMessage(pwndHierarchy, MN_SIZEWINDOW, 1, 0);
if (!sizeHierarchy) {
/*
* No size for this menu so zero it and blow off.
*/
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;
pti = PtiCurrent();
if (ppopupmenu->fIsMenuBar) {
BOOL fIconic = (TestWF(ppopupmenu->spwndPopupMenu, WFMINIMIZED) != 0);
RECT rcWindow;
#ifdef MEMPHIS_MENU_ANIMATION
ppopupmenuHierarchy->iDropDir = PAS_DOWN;
#endif // MEMPHIS_MENU_ANIMATION
CopyRect(&rcWindow, &ppopupmenu->spwndPopupMenu->rcWindow);
if (fIconic && IsTrayWindow(ppopupmenu->spwndPopupMenu))
xxxSendMinRectMessages(ppopupmenu->spwndPopupMenu, &rcWindow);
ppopupmenu->fDropNextPopup = TRUE;
/*
* 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.
*/
if (!SYSMET(MENUDROPALIGNMENT)) {
if (fIconic)
xLeft = rcWindow.left;
else
xLeft = rcWindow.left + pItem->xItem;
} else {
if (fIconic)
xLeft = rcWindow.right - cxPopup;
else
xLeft = rcWindow.left + pItem->xItem +
pItem->cxItem - cxPopup;
}
/*
* Make sure the menu doesn't go off right side of screen?
*/
if ((xLeft + cxPopup) > gpDispInfo->rcScreen.right)
xLeft = gpDispInfo->rcScreen.right - cxPopup;
if (fIconic)
{
// If the window is iconic, pop the menu up. Since we're
// minimized, the sysmenu button doesn't really exist.
yTop = rcWindow.top - cyPopup;
if (yTop < 0)
yTop = rcWindow.bottom;
}
else
yTop = pItem->yItem + pItem->cyItem + rcWindow.top;
} else {
/* Now position the hierarchical menu window.
* We want to overlap by the amount of the frame, to help in the
* 3D illusion.
*/
#ifdef MEMPHIS_MENU_ANIMATION
ppopupmenuHierarchy->iDropDir = PAS_RIGHT;
#endif // MEMPHIS_MENU_ANIMATION
xLeft = ppopupmenu->spwndPopupMenu->rcWindow.left +
pItem->xItem + pItem->cxItem;
/* Note that we DO want the selections in the item and its popup to
* align horizontally.
*/
yTop = ppopupmenu->spwndPopupMenu->rcWindow.top + pItem->yItem;
if (ppopupmenu->fDroppedLeft) {
int xTmp;
/*
* If this menu has dropped left, see if our hierarchy can be made
* to drop to the left also.
*/
xTmp = ppopupmenu->spwndPopupMenu->rcWindow.left + SYSMET(CXFIXEDFRAME) - cxPopup;
if (xTmp >= 0) {
xLeft = xTmp;
ppopupmenuHierarchy->fDroppedLeft = TRUE;
#ifdef MEMPHIS_MENU_ANIMATION
ppopupmenuHierarchy->iDropDir = PAS_LEFT;
#endif // MEMPHIS_MENU_ANIMATION
}
}
/*
* Make sure the menu doesn't go off right side of screen. Make it drop
* left if it does.
*/
if ((xLeft + cxPopup) > gpDispInfo->rcScreen.right) {
xLeft = ppopupmenu->spwndPopupMenu->rcWindow.left + SYSMET(CXFIXEDFRAME) - cxPopup;
ppopupmenuHierarchy->fDroppedLeft = TRUE;
#ifdef MEMPHIS_MENU_ANIMATION
ppopupmenuHierarchy->iDropDir = PAS_LEFT;
#endif // MEMPHIS_MENU_ANIMATION
}
}
/*
* Does the menu extend beyond bottom of screen?
*/
if ((yTop + cyPopup) > gpDispInfo->rcScreen.bottom) {
yTop -= cyPopup;
// Try to pop above menu bar first
if (ppopupmenu->fIsMenuBar) {
yTop -= SYSMET(CYMENUSIZE);
#ifdef MEMPHIS_MENU_ANIMATION
ppopupmenuHierarchy->iDropDir = PAS_UP;
#endif // MEMPHIS_MENU_ANIMATION
} else {
// Account for nonclient frame above & below
yTop += pItem->cyItem + 2*SYSMET(CYFIXEDFRAME);
}
//
// Make sure that starting point is on screen, and all of menu shows.
//
if ((yTop < 0) || (yTop + cyPopup > gpDispInfo->rcScreen.bottom))
// Pin it to the bottom.
yTop = gpDispInfo->rcScreen.bottom - cyPopup;
}
/*
* Make sure Upper Left corner of menu is always visible.
*/
if (xLeft < 0)
xLeft = 0;
if (yTop < 0)
yTop = 0;
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 > gpDispInfo->rcScreen.right)
xrightdrop = 0;
xleftdrop = ppopupmenu->spwndPopupMenu->rcWindow.left +
pItem->xItem - cxPopup;
if (xleftdrop < 0)
xleftdrop = 0;
if ((SYSMET(MENUDROPALIGNMENT) && xleftdrop) || !xrightdrop) {
xLeft = ppopupmenu->spwndPopupMenu->rcWindow.left +
pItem->xItem - cxPopup;
#ifdef MEMPHIS_MENU_ANIMATION
ppopupmenuHierarchy->iDropDir = PAS_LEFT;
#endif // MEMPHIS_MENU_ANIMATION
} else if (xrightdrop) {
xLeft = ppopupmenu->spwndPopupMenu->rcWindow.left +
pItem->xItem + pItem->cxItem;
#ifdef MEMPHIS_MENU_ANIMATION
ppopupmenuHierarchy->iDropDir = PAS_RIGHT;
#endif // MEMPHIS_MENU_ANIMATION
}
}
}
//
// 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);
// Make the menu popup a topmost window.
xxxSetWindowPos(pwndHierarchy, PWND_TOPMOST, xLeft, yTop, 0, 0,
SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOOWNERZORDER);
ret = pwndHierarchy;
#ifdef MEMPHIS_MENU_ANIMATION
ppopupmenuHierarchy->iDropDir |= PAS_OUT;
#endif // MEMPHIS_MENU_ANIMATION
if (ppopupmenu->ppopupmenuRoot->spwndActivePopup) {
if (!TestWF(ppopupmenu->ppopupmenuRoot->spwndActivePopup,
WFVISIBLE)) {
/*
* If the previously active popup wasn't visible, now is a good time to
* make it visible.
*/
TL tlpwndActivePopup;
ThreadLock(ppopupmenu->ppopupmenuRoot->spwndActivePopup, &tlpwndActivePopup);
xxxSendMessage(ppopupmenu->ppopupmenuRoot->spwndActivePopup,
MN_SHOWPOPUPWINDOW, 0, 0);
ThreadUnlock(&tlpwndActivePopup);
}
}
Lock(&(ppopupmenu->ppopupmenuRoot->spwndActivePopup), pwndHierarchy);
ThreadUnlock(&tlpwndHierarchy);
Exit:
return ret;
}
/***************************************************************************\
*
* MNHideNextHierarchy()
*
* 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);
}
else
return(FALSE);
}
/***************************************************************************\
* void MenuCloseHierarchyHandler(PPOPUPMENU ppopupmenu)
* effects: Close all hierarchies from this window down.
*
* History:
* 05-25-91 Mikehar Ported from Win3.1
\***************************************************************************/
void xxxMNCloseHierarchy(
PPOPUPMENU ppopupmenu, PMENUSTATE pMenuState)
{
TL tlpwndT;
TL tlpmenu;
PDESKTOP pdesk = PtiCurrent()->rpdesk;
#ifdef DEBUG
Validateppopupmenu(ppopupmenu);
#endif
#ifdef MEMPHIS_MENU_ANIMATION
MNAnimate(FALSE); // terminate any animation
#endif // MEMPHIS_MENU_ANIMATION
/*
* If a hierarchy exists, close all childen below us. Do it in reversed
* order so savebits work out.
*/
if (!ppopupmenu->fHierarchyDropped)
return;
if (ppopupmenu->fHideTimer)
{
_KillTimer(ppopupmenu->spwndPopupMenu, IDSYS_MNHIDE);
ppopupmenu->fHideTimer = FALSE;
}
if (ppopupmenu->spwndNextPopup) {
ThreadLockAlways(ppopupmenu->spwndNextPopup, &tlpwndT);
xxxSendMessage(ppopupmenu->spwndNextPopup, MN_CLOSEHIERARCHY, 0, 0);
if (ppopupmenu->spwndNextPopup == pdesk->spwndMenu) {
PPOPUPMENU ppopupmenuReal;
PWND spwndMenu = pdesk->spwndMenu;
ThreadLock(spwndMenu, &tlpmenu);
/*
* If this is our precreated real popup window,
* initialize ourselves and just hide.
*/
xxxShowWindow(spwndMenu, MAKELONG(SW_HIDE, gfAnimate));
/*
* Its possible that during Logoff the above xxxShowWindow
* won't get prossessed and because this window is a special
* window that is owned by they desktop we have to manually mark
* it as invisible.
*/
ClrWF(spwndMenu, WFVISIBLE);
#ifdef HAVE_MN_GETPPOPUPMENU
ppopupmenuReal = (PPOPUPMENU)xxxSendMessage(spwndMenu,
MN_GETPPOPUPMENU,0, 0L);
#else
ppopupmenuReal = ((PMENUWND)spwndMenu)->ppopupmenu;
#endif
if (ppopupmenuReal != NULL) {
UserAssert(!ppopupmenuReal->fDelayedFree);
xxxMNDestroyHandler(pdesk, ppopupmenuReal);
UserAssert(!ppopupmenuReal->fFreed);
Unlock(&ppopupmenuReal->spwndNotify);
Unlock(&ppopupmenuReal->spwndPopupMenu);
Unlock(&ppopupmenuReal->spwndNextPopup);
Unlock(&ppopupmenuReal->spwndPrevPopup);
Unlock(&ppopupmenuReal->spmenu);
Unlock(&ppopupmenuReal->spmenuAlternate);
Unlock(&ppopupmenuReal->spwndActivePopup);
RtlZeroMemory((PVOID)ppopupmenuReal, sizeof(POPUPMENU));
ppopupmenuReal->fHasMenuBar = TRUE;
ppopupmenuReal->posSelectedItem = MFMWFP_NOITEM;
}
pdesk->fMenuInUse = FALSE;
spwndMenu->head.pti = pdesk->pDeskInfo->spwnd->head.pti;
Unlock(&spwndMenu->spwndOwner);
ThreadUnlock(&tlpmenu);
ThreadUnlock(&tlpwndT);
} else if (ThreadUnlock(&tlpwndT)) {
xxxDestroyWindow(ppopupmenu->spwndNextPopup);
}
Unlock(&ppopupmenu->spwndNextPopup);
ppopupmenu->fHierarchyDropped = FALSE;
ppopupmenu->fHierarchyVisible = 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, &tlpwndT);
xxxSendMenuSelect(pwnd, ppopupmenu->spmenu,
ppopupmenu->posSelectedItem);
ThreadUnlock(&tlpwndT);
}
}
}
/***************************************************************************\
*
* MNDoubleClick()
*
* 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(PPOPUPMENU ppopup, int idxItem)
{
PMENU pMenu;
PITEM pItem;
MSG msg;
//
// 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);
}
#ifdef DEBUG
else if (msg.message == WM_LBUTTONDBLCLK ||
msg.message == WM_NCLBUTTONDBLCLK)
{
UserAssert(FALSE);
}
#endif
}
//
// Get current item.
//
pMenu = ppopup->spmenu;
if ((pMenu==NULL) || ((UINT)idxItem >= pMenu->cItems))
goto Done;
pItem = pMenu->rgItems + 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;
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;
ThreadLock(ppopup->ppopupmenuRoot->spwndNotify, &tlpwndNotify);
xxxSendMenuSelect(ppopup->ppopupmenuRoot->spwndNotify,
pMenu, idxItem);
ThreadUnlock(&tlpwndNotify);
}
xxxMNCancel(ppopup->ppopupmenuRoot, pItem->wID, TRUE, 0L);
return TRUE;
}
while (TRUE);
Done:
return(FALSE);
}
/***************************************************************************\
* UINT MenuSelectItemHandler(PPOPUPMENU ppopupmenu, int itemPos)
*
* 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 tlpmenu;
TL tlpwndPopupMenu;
PWND pwndPopupMenu;
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;
}
#ifdef MEMPHIS_MENU_ANIMATION
MNAnimate(FALSE); // terminate any animation
#endif // MEMPHIS_MENU_ANIMATION
if (ppopupmenu->fShowTimer) {
_KillTimer(ppopupmenu->spwndPopupMenu, IDSYS_MNSHOW);
ppopupmenu->fShowTimer = FALSE;
}
ThreadLock(pwndPopupMenu = ppopupmenu->spwndPopupMenu, &tlpwndPopupMenu);
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;
TL tlpwndPopupMenuPrev;
ThreadLock(ppopupPrev->spwndPopupMenu, &tlpwndPopupMenuPrev);
ThreadLock(ppopupPrev->spmenu, &tlpmenuPopupMenuPrev);
if (ppopupPrev->posSelectedItem != MFMWFP_NOITEM) {
xxxMNInvertItem(ppopupPrev->spwndPopupMenu, ppopupPrev->spmenu,
ppopupPrev->posSelectedItem, ppopupPrev->spwndNotify, FALSE);
}
ppopupPrev->posSelectedItem = ppopupPrev->posDropped;
xxxMNInvertItem(ppopupPrev->spwndPopupMenu, ppopupPrev->spmenu,
ppopupPrev->posDropped, ppopupPrev->spwndNotify, TRUE);
ThreadUnlock(&tlpmenuPopupMenuPrev);
ThreadUnlock(&tlpwndPopupMenuPrev);
}
ppopupmenu->fAboutToHide = FALSE;
Lock(&ppopupmenu->ppopupmenuRoot->spwndActivePopup, ppopupmenu->spwndPopupMenu);
}
if (ppopupmenu->posSelectedItem != MFMWFP_NOITEM) {
/*
* Something else is selected so we need to unselect it.
*/
if (ppopupmenu->spwndNextPopup) {
if (ppopupmenu->fIsMenuBar)
xxxMNCloseHierarchy(ppopupmenu, pMenuState);
else
MNSetTimerToCloseHierarchy(ppopupmenu);
}
xxxMNInvertItem(pwndPopupMenu, pmenu,
ppopupmenu->posSelectedItem, pwndNotify, FALSE);
}
ppopupmenu->posSelectedItem = itemPos;
if (itemPos != MFMWFP_NOITEM) {
pItem = xxxMNInvertItem(pwndPopupMenu, pmenu,
itemPos, pwndNotify, TRUE);
ThreadUnlock(&tlpwndNotify);
ThreadUnlock(&tlpmenu);
ThreadUnlock(&tlpwndPopupMenu);
return pItem;
}
ThreadUnlock(&tlpwndNotify);
ThreadUnlock(&tlpmenu);
ThreadUnlock(&tlpwndPopupMenu);
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);
xxxSendMenuSelect(pp->spwndNotify, pp->spmenu, pp->posSelectedItem);
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;
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)) {
// ScreenToClient
pt.x -= pwnd->rcClient.left;
pt.y -= pwnd->rcClient.top;
} else {
// ScreenToWindow
pt.x -= pwnd->rcWindow.left;
pt.y -= pwnd->rcWindow.top;
}
// Step through all the items in the menu.
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);
}
/***************************************************************************\
* LONG MenuFindMenuWindowFromPoint(
* PPOPUPMENU ppopupmenu, PUINT pIndex, POINT screenPt)
*
* effects: Determines in which window the point lies.
*
* Returns
* - PWND of the hierarchical menu the point is on,
* - MFMWFP_MAINMENU if point lies on mainmenu bar & ppopupmenu is a main
* menu bar.
* - MFMWFP_ALTMENU if point lies on the alternate popup menu.
* - MFMWFP_NOITEM if there is no item at that point on the menu.
* - 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.
*
* History:
* 05-25-91 Mikehar Ported from Win3.1
* 8-11-92 Sanfords added MFMWFP_ constants
\***************************************************************************/
LONG xxxMNFindWindowFromPoint(
PPOPUPMENU ppopupmenu,
PUINT pIndex,
POINTS screenPt)
{
POINT pt;
RECT rect;
LONG longHit;
UINT itemHit;
PWND pwnd;
TL tlpwndT;
*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, (DWORD)&itemHit,
MAKELONG(screenPt.x, screenPt.y));
ThreadUnlock(&tlpwndT);
/*
* If return value is an hwnd, convert to pwnd.
*/
switch (longHit) {
case MFMWFP_OFFMENU:
case MFMWFP_NOITEM:
case MFMWFP_MAINMENU:
case MFMWFP_ALTMENU:
break;
default:
/*
* Note: MFMFWP_OFFMENU == 0, so an invalid pwnd in longHit
* is handled the same as MFMWFP_OFFMENU
*/
longHit = (LONG)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;
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));
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)pwnd;
}
return MFMWFP_OFFMENU;
}
/***************************************************************************\
*void MenuCancelMenus(PPOPUPMENU ppopupmenu,
* UINT cmd, BOOL fSend)
* 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(
PPOPUPMENU ppopupmenu,
UINT cmd,
BOOL fSend,
LONG lParam)
{
BOOL fSynchronous = ppopupmenu->fSynchronous;
BOOL fTrackFlagsSet = ppopupmenu->fIsTrackPopup;
BOOL fIsSysMenu = ppopupmenu->fIsSysMenu;
BOOL fNotify = !ppopupmenu->fNoNotify;
PMENUSTATE pMenuState;
PWND pwndT;
TL tlpwndT;
TL tlpwndPopupMenu;
#ifdef DEBUG
Validateppopupmenu(ppopupmenu);
#endif
if (!IsRootPopupMenu(ppopupmenu)) {
RIPMSG0(RIP_ERROR, "CancelMenus() called for a non top most menu");
return;
}
ThreadLock(ppopupmenu->spwndPopupMenu, &tlpwndPopupMenu);
pMenuState = GetpMenuState(ppopupmenu->spwndPopupMenu);
if (pMenuState == NULL) {
RIPMSG0(RIP_ERROR, "xxxMNCancel: pMenuState == NULL");
goto UnlockspwndPopupMenu;
}
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 %#lx 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_ERROR, "xxxMNCancel: already in cancel. ppopupmenu:%#lx", ppopupmenu);
goto UnlockspwndPopupMenu;
}
ppopupmenu->fInCancel = TRUE;
/*
* 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);
xxxSetCapture(NULL);
if (fTrackFlagsSet) {
xxxDestroyWindow(ppopupmenu->spwndPopupMenu);
}
if (pwndT == NULL) {
ThreadUnlock(&tlpwndT);
ThreadUnlock(&tlpwndPopupMenu);
return;
}
xxxSendMenuSelect(pwndT, (PMENU)-1, MFMWFP_NOITEM);
/*
* Hack so we can send MenuSelect messages with MFMWFP_MAINMENU
* (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.
*/
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 (fSend) {
xxxPlayEventSound(L"MenuCommand");
pMenuState->cmdLast = cmd;
if (!fSynchronous) {
if (fIsSysMenu)
_PostMessage(pwndT, WM_SYSCOMMAND, cmd, lParam);
else {
if (fTrackFlagsSet && !TestWF(pwndT, WFWIN31COMPAT)) {
xxxSendMessage(pwndT, WM_COMMAND, cmd, 0);
} else {
_PostMessage(pwndT, WM_COMMAND, cmd, 0);
}
}
}
} else
pMenuState->cmdLast = 0;
ThreadUnlock(&tlpwndT);
UnlockspwndPopupMenu:
ThreadUnlock(&tlpwndPopupMenu);
}
/***************************************************************************\
* void MenuShowPopupMenuWindow(PPOPUPMENU ppopupmenu)
*
* History:
* 05-25-91 Mikehar Ported from Win3.1
\***************************************************************************/
void xxxMNShowPopupWindow(
PPOPUPMENU ppopupmenu)
{
PPOPUPMENU ppopupmenuParent = NULL;
TL tlpwndT;
if (ppopupmenu->fIsMenuBar)
return;
xxxPlayEventSound(L"MenuPopup");
ThreadLock(ppopupmenu->spwndPopupMenu, &tlpwndT);
xxxShowWindow(ppopupmenu->spwndPopupMenu, MAKELONG(SW_SHOWNOACTIVATE, gfAnimate));
ThreadUnlock(&tlpwndT);
//
// In the parent ppopup, mark the hierarchy as being visible (and
// dropped).
//
if (ppopupmenu->fHasMenuBar &&
(ppopupmenu->spwndPrevPopup == ppopupmenu->spwndNotify)) {
ppopupmenuParent = ppopupmenu->ppopupmenuRoot;
} else {
#ifdef HAVE_MN_GETPPOPUPMENU
ppopupmenuParent = (PPOPUPMENU)(WORD)(DWORD)SendMessage32(
ppopup->hwndPrevPopup, MN_GETPPOPUPMENU, 0, 0L, 0);
#else
if ((PMENUWND)ppopupmenu->spwndPrevPopup)
ppopupmenuParent = ((PMENUWND)ppopupmenu->spwndPrevPopup)->ppopupmenu;
#endif
}
if (ppopupmenuParent != NULL)
ppopupmenuParent->fHierarchyVisible = TRUE;
return;
}
/***************************************************************************\
* void MenuButtonDownHandler(PPOPUPMENU ppopupmenu, int posItemHit)
* effects: 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) {
//
// THIS SHOULD ONLY HAPPEN ON ENTRANCE INTO MENU MODE WITH MOUSE
// EITHER FROM SCRATCH OR SWITCHING FROM KEYHOLD.
//
//
// 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;
}
/***************************************************************************\
* void MenuMouseMoveHandler(PPOPUPMENU ppopupmenu, POINT screenPt)
* 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 cmdHitArea;
UINT uFlags;
UINT cmdItem;
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;
/*
* Find out where this mouse move occurred.
* - PWND of the hierarchical menu the point is on,
* - MFMWFP_MAINMENU if point lies on mainmenu bar & ppopupmenu is a main
* menu bar.
* - MFMWFP_ALTMENU if point lies on the alternate popup menu.
* - MFMWFP_NOITEM if there is no item at that point on the menu.
* - MFMWFP_OFFMENU if point lies elsewhere.
*/
cmdHitArea = xxxMNFindWindowFromPoint(ppopup, &cmdItem,
ptScreen);
pMenuState->ptMouseLast.x = ptScreen.x;
pMenuState->ptMouseLast.y = ptScreen.y;
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) {
PWND pwndPopup;
TL tlpwndT;
// This is a popup window we moved onto.
pwndPopup = (PWND)(cmdHitArea);
ThreadLock(pwndPopup, &tlpwndT);
if (!TestWF(pwndPopup, WFVISIBLE)) {
// We moved onto this popup and it isn't visible yet so show it.
xxxSendMessage(pwndPopup, MN_SHOWPOPUPWINDOW, 0, 0L);
}
//
// Select the item.
//
uFlags = xxxSendMessage(pwndPopup, 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(pwndPopup, MN_SETTIMERTOOPENHIERARCHY, 0, 0L)) {
#ifdef HAVE_MN_GETPPOPUPMENU
PPOPUPMENU pp = (PPOPUPMENU)(WORD)(DWORD)
SendMessage32(hwndPopup, MN_GETPPOPUPMENU, 0, 0L, 0);
#else
PPOPUPMENU pp = ((PMENUWND)pwndPopup)->ppopupmenu;
#endif
xxxMNHideNextHierarchy(pp);
}
}
ThreadUnlock(&tlpwndT);
} else
OverNothing:
{
// We moved off all menu windows...
if (ppopup->spwndActivePopup != NULL) {
TL tlpwndT;
PWND pwndActive = ppopup->spwndActivePopup;
ThreadLock(pwndActive, &tlpwndT);
xxxSendMessage(pwndActive, MN_SELECTITEM, MFMWFP_NOITEM, 0L);
ThreadUnlock(&tlpwndT);
} else
xxxMNSelectItem(ppopup, pMenuState, MFMWFP_NOITEM);
}
}
/***************************************************************************\
* void MenuButtonUpHandler(PPOPUPMENU ppopupmenu, int posItemHit)
* effects: 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,
LONG 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) {
//RIPMSG0(RIP_WARNING, "wrong item selected in menu");
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;
xxxMNCancel(ppopup->ppopupmenuRoot, 0, 0, lParam);
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)) {
xxxMNCancel(ppopup->ppopupmenuRoot, pItem->wID, TRUE, lParam);
return;
}
ExitButtonUp:
pMenuState->fButtonDown = 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) {
/*
* A timer is already set.
*/
return 1;
}
if (!_SetTimer(ppopup->spwndPopupMenu, IDSYS_MNSHOW, dtMNDropDown, NULL))
return (UINT)-1;
ppopup->fShowTimer = TRUE;
return 1;
}
// ----------------------------------------------------------------------------
//
// MNSetTimerToCloseHierarchy
//
// ----------------------------------------------------------------------------
UINT MNSetTimerToCloseHierarchy(PPOPUPMENU ppopup)
{
if (!ppopup->fHierarchyDropped)
return(0);
#ifdef MEMPHIS_MENU_ANIMATION
MNAnimate(FALSE); // terminate any animation
#endif // MEMPHIS_MENU_ANIMATION
if (ppopup->fHideTimer)
return(1);
if (!_SetTimer(ppopup->spwndPopupMenu, IDSYS_MNHIDE, dtMNDropDown, NULL))
return((UINT) -1);
ppopup->fHideTimer = TRUE;
ppopup = ((PMENUWND)(ppopup->spwndNextPopup))->ppopupmenu;
ppopup->fAboutToHide = TRUE;
return(1);
}
/***************************************************************************\
*
* History:
* 05-25-91 Mikehar Ported from Win3.1
* 08-12-96 jparsons Catch NULL lParam on WM_CREATE [51986]
\***************************************************************************/
LONG xxxMenuWindowProc(
PWND pwnd,
UINT message,
DWORD wParam,
LONG lParam)
{
PAINTSTRUCT ps;
PPOPUPMENU ppopupmenu;
PMENUSTATE pMenuState;
TL tlpmenu;
TL tlpwndNotify;
PDESKTOP pdesk = pwnd->head.rpdesk;
CheckLock(pwnd);
VALIDATECLASSANDSIZE(pwnd, FNID_MENU);
/*
* If we're not in menu mode, there are only few message we care about
*/
pMenuState = GetpMenuState(pwnd);
if (pMenuState == NULL) {
switch (message) {
case WM_NCCREATE:
case WM_CREATE:
case WM_FINALDESTROY:
break;
default:
return xxxDefWindowProc(pwnd, message, wParam, lParam);
}
} else {
#ifdef DEBUG
Validateppopupmenu(pMenuState->pGlobalPopupMenu);
#endif
}
ppopupmenu = ((PMENUWND)pwnd)->ppopupmenu;
switch (message) {
case WM_NCCREATE:
/*
* To avoid setting the window text lets do nothing on nccreates.
*/
return 1L;
#ifdef MEMPHIS_MENU_WATERMARKS
case WM_ERASEBKGND:
if (ppopupmenu->spmenu->hbrBack) {
HBRUSH hbrOld = GreSelectBrush((HDC) wParam, ppopupmenu->spmenu->hbrBack);
GrePatBlt((HDC) wParam, 0, 0,
pwnd->rcClient.right - pwnd->rcClient.left,
pwnd->rcClient.bottom - pwnd->rcClient.top, PATCOPY);
GreSelectBrush((HDC) wParam, hbrOld);
}
else
return xxxDefWindowProc(pwnd, message, wParam, lParam);
break;
#endif // MEMPHIS_MENU_WATERMARKS
#ifdef MEMPHIS_MENU_ANIMATION
case WM_NCPAINT:
if (gfAnimate && (ppopupmenu->iDropDir & PAS_OUT) && xxxMNInitAnimation(pwnd, ppopupmenu)) {
xxxValidateRect(pwnd, NULL);
ppopupmenu->iDropDir &= ~PAS_OUT;
} else {
return xxxDefWindowProc(pwnd, message, wParam, lParam);
}
break;
case WM_PRINTCLIENT: {
TL tlMenu;
ThreadLock( ppopupmenu->spmenu, &tlMenu);
xxxMenuDraw((HDC) wParam, ppopupmenu->spmenu);
ThreadUnlock( &tlMenu);
break;
}
#endif // MEMPHIS_MENU_ANIMATION
case WM_CREATE:
/*
* lParam must not be NULL or we will trap [51986]
*/
if (lParam) {
ppopupmenu = MNAllocPopup(TRUE);
if (!ppopupmenu)
return -1;
((PMENUWND)pwnd)->ppopupmenu = ppopupmenu;
Lock(&(ppopupmenu->spmenu), ((LPCREATESTRUCT)lParam)->lpCreateParams);
Lock(&(ppopupmenu->spwndNotify), pwnd->spwndOwner);
ppopupmenu->posSelectedItem = MFMWFP_NOITEM;
Lock(&(ppopupmenu->spwndPopupMenu), pwnd);
} /* if */
else {
RIPERR0(ERROR_INVALID_PARAMETER, RIP_WARNING,
"xxxMenuWindowProc - NULL lParam for WM_CREATE\n") ;
} /* else */
break;
// case WM_DESTROY:
// xxxMNDestroyHandler(ppopupmenu);
// break;
case WM_FINALDESTROY:
xxxMNDestroyHandler(pdesk, ppopupmenu);
break;
case WM_PAINT: {
TL tlpmenu;
xxxBeginPaint(pwnd, &ps);
ThreadLock(ppopupmenu->spmenu, &tlpmenu);
xxxMenuDraw(ps.hdc, ppopupmenu->spmenu);
ThreadUnlock(&tlpmenu);
_EndPaint(pwnd, &ps);
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:
if (wParam == 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);
} else if (wParam == IDSYS_MNHIDE) {
ppopupmenu->fToggle = FALSE;
xxxMNCloseHierarchy(ppopupmenu,pMenuState);
}
break;
/*
* Menu messages.
*/
case MN_SETHMENU:
/*
* wParam - new hmenu to associate with this menu window
*/
if (wParam != 0) {
if ((wParam = (DWORD)ValidateHmenu((HMENU)wParam)) == 0) {
return 0;
}
}
Lock(&(ppopupmenu->spmenu), wParam);
break;
case MN_GETHMENU:
/*
* returns the hmenu associated with this menu window
*/
return (LONG)PtoH(ppopupmenu->spmenu);
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 1 if we should also resize
* window.
*/
int cx, cy;
/*
* Call menucomputeHelper directly since this is the entry point for
* non toplevel menu bars.
*/
if (ppopupmenu->spmenu == NULL)
break;
ThreadLockAlways(ppopupmenu->spmenu, &tlpmenu);
ThreadLock(ppopupmenu->spwndNotify, &tlpwndNotify);
xxxMNCompute(ppopupmenu->spmenu, ppopupmenu->spwndNotify,
0, 0, 0, 0);
ThreadUnlock(&tlpwndNotify);
ThreadUnlock(&tlpmenu);
cx = ppopupmenu->spmenu->cxMenu;
cy = ppopupmenu->spmenu->cyMenu;
if (wParam) {
xxxSetWindowPos(pwnd, PWND_TOP, 0, 0,
cx + 2*SYSMET(CXFIXEDFRAME), /* For shadow */
cy + 2*SYSMET(CYFIXEDFRAME), /* For shadow */
SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
}
return MAKELONG(cx, cy);
}
break;
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 (LONG)HW(pwndT);
}
case MN_CLOSEHIERARCHY:
xxxMNCloseHierarchy(ppopupmenu, pMenuState);
break;
case MN_SELECTITEM: {
PITEM pItem;
/*
* wParam - the item to select. Must be a valid index or MFMWFP_NOITEM
* Returns the item flags of the wParam (0 if failure)
*/
if ((wParam >= ppopupmenu->spmenu->cItems) && (wParam != MFMWFP_NOITEM)) {
UserAssert(FALSE /* Bad wParam for MN_SELECTITEM */ );
return 0;
}
pItem = xxxMNSelectItem(ppopupmenu, pMenuState, (UINT)wParam);
if (pItem != NULL) {
return((LONG)(DWORD)(WORD)(pItem->fState |
((pItem->spSubMenu != NULL) ? MF_POPUP : 0)));
} else {
return(0);
}
break;
}
case MN_SELECTFIRSTVALIDITEM: {
UINT item;
item = MNFindNextValidItem(ppopupmenu->spmenu, -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(ppopupmenu, (UINT)wParam, (BOOL)LOWORD(lParam), 0);
break;
case MN_FINDMENUWINDOWFROMPOINT:
{
LONG lRet;
/*
* 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.
*/
switch (lRet) {
case MFMWFP_OFFMENU:
case MFMWFP_NOITEM:
case MFMWFP_MAINMENU:
case MFMWFP_ALTMENU:
return lRet;
default:
return (LONG)HW((PWND)lRet);
}
}
case MN_SHOWPOPUPWINDOW:
/*
* Forces the dropped down popup to be visible.
*/
xxxMNShowPopupWindow(ppopupmenu);
break;
case MN_BUTTONDOWN:
/*
* wParam is position (index) of item the button was clicked on.
* Must be a valid index or MFMWFP_NOITEM
*/
if ((wParam >= ppopupmenu->spmenu->cItems) && (wParam != MFMWFP_NOITEM)) {
UserAssert(FALSE /* Bad wParam for MN_BUTTONDOWN */ );
return 0;
}
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 >= ppopupmenu->spmenu->cItems) && (wParam != MFMWFP_NOITEM)) {
UserAssert(FALSE /* Bad wParam for MN_BUTTONUP */ );
return 0;
}
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(ppopupmenu, (int)wParam);
break;
case WM_ACTIVATE:
/*
* 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;
#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.
*/
do {
pwnd = NextTopWindow(PtiCurrent(), pwnd, NULL, 0);
if (pwnd && !FBadWindow(pwnd->spwndLastActive) &&
!ISAMENU(pwnd->spwndLastActive)) {
pwnd = pwnd->spwndLastActive;
break;
}
} while(pwnd != NULL);
}
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);
}
} else {
xxxActivateThisWindow(pwnd, 0, ATW_SETFOCUS);
}
ThreadUnlock(&tlpwnd);
}
#endif
}
break;
default:
return xxxDefWindowProc(pwnd, message, wParam, lParam);
}
return 0;
}