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.
1194 lines
37 KiB
1194 lines
37 KiB
/**************************** Module Header ********************************\
|
|
* Module Name: mnchange.c
|
|
*
|
|
* Copyright (c) 1985 - 1999, Microsoft Corporation
|
|
*
|
|
* Change Menu Routine
|
|
*
|
|
* History:
|
|
* 10-10-90 JimA Cleanup.
|
|
* 03-18-91 IanJa Window revalidation added (none required)
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
/*
|
|
* Allocation/deallocation increments. Make them
|
|
* different to avoid possible thrashing when an item
|
|
* is repeatedly added and removed.
|
|
*/
|
|
#define CMENUITEMALLOC 8
|
|
#define CMENUITEMDEALLOC 10
|
|
|
|
BOOL xxxSetLPITEMInfo(PMENU pMenu, PITEM pItem, LPMENUITEMINFOW lpmii, PUNICODE_STRING pstr);
|
|
typedef BOOL (*MENUAPIFN)(PMENU, UINT, BOOL, LPMENUITEMINFOW);
|
|
|
|
|
|
#if DBG
|
|
VOID RelocateMenuLockRecords(
|
|
PITEM pItem,
|
|
int cItem,
|
|
LONG_PTR cbMove)
|
|
{
|
|
while (cItem > 0) {
|
|
if (pItem->spSubMenu != NULL) {
|
|
HMRelocateLockRecord(&(pItem->spSubMenu), cbMove);
|
|
}
|
|
pItem++;
|
|
cItem--;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/***************************************************************************\
|
|
* UnlockSubMenu
|
|
*
|
|
* Unlocks the pSubMenu and removes the MENULIST element corresponding to pMenu
|
|
*
|
|
* History:
|
|
* Nov-20-98 MCostea
|
|
\***************************************************************************/
|
|
PMENU UnlockSubMenu(
|
|
PMENU pMenu,
|
|
PMENU* ppSubMenu)
|
|
{
|
|
PMENULIST* pp;
|
|
PMENULIST pMLFound;
|
|
|
|
if (*ppSubMenu == NULL) {
|
|
return NULL;
|
|
}
|
|
/*
|
|
* Remove the item from pMenu's pParentsList
|
|
*/
|
|
for (pp = &(*ppSubMenu)->pParentMenus; *pp != NULL; pp = &(*pp)->pNext) {
|
|
if ((*pp)->pMenu == pMenu) {
|
|
pMLFound = *pp;
|
|
*pp = (*pp)->pNext;
|
|
DesktopFree(pMenu->head.rpdesk, pMLFound);
|
|
break;
|
|
}
|
|
}
|
|
return Unlock(ppSubMenu);
|
|
}
|
|
|
|
#define NESTED_MENU_LIMIT 25
|
|
/***************************************************************************\
|
|
* GetMenuDepth
|
|
*
|
|
* Returns the menu depth (how many nested submenus this menu has).
|
|
* This helps catch loops in the menu hierarchy or deep evil apps.
|
|
*
|
|
* History:
|
|
* Sept-22-98 MCostea
|
|
\***************************************************************************/
|
|
CHAR GetMenuDepth(PMENU pMenu, UINT uMaxAllowedDepth)
|
|
{
|
|
UINT uItems, uMaxDepth = 0, uSubMenuDepth;
|
|
PITEM pItem;
|
|
|
|
/*
|
|
* This will prevent us from getting trapped in loops
|
|
*/
|
|
if (uMaxAllowedDepth == 0) {
|
|
return NESTED_MENU_LIMIT;
|
|
}
|
|
pItem = pMenu->rgItems;
|
|
for (uItems = pMenu->cItems; uItems--; pItem++) {
|
|
if (pItem->spSubMenu != NULL) {
|
|
uSubMenuDepth = GetMenuDepth(pItem->spSubMenu, uMaxAllowedDepth-1);
|
|
if (uSubMenuDepth > uMaxDepth) {
|
|
/*
|
|
* Don't walk the other submenus if a deep branch was found
|
|
*/
|
|
if (uSubMenuDepth >= NESTED_MENU_LIMIT) {
|
|
return NESTED_MENU_LIMIT;
|
|
}
|
|
uMaxDepth = uSubMenuDepth;
|
|
}
|
|
}
|
|
}
|
|
return uMaxDepth + 1;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* GetMenuAncestors
|
|
*
|
|
* Returns the maximum number of levels above pMenu in the menu hierarchy.
|
|
* Walking the "parent" tree should not be expensive as it is pretty unusual
|
|
* for menus to appear in different places in the hierarchy. The tree is
|
|
* usualy a simple linked list.
|
|
*
|
|
* History:
|
|
* Nov-10-98 MCostea
|
|
\***************************************************************************/
|
|
CHAR GetMenuAncestors(PMENU pMenu)
|
|
{
|
|
PMENULIST pParentMenu;
|
|
CHAR uParentAncestors;
|
|
CHAR retVal = 0;
|
|
|
|
for (pParentMenu = pMenu->pParentMenus; pParentMenu; pParentMenu = pParentMenu->pNext) {
|
|
uParentAncestors = GetMenuAncestors(pParentMenu->pMenu);
|
|
if (uParentAncestors > retVal) {
|
|
retVal = uParentAncestors;
|
|
}
|
|
}
|
|
return retVal+1;
|
|
}
|
|
|
|
/**********************************************
|
|
* Global Insert/Append/Set client/server interface
|
|
*
|
|
* 01-13-94 FritzS Created
|
|
***********************************************/
|
|
BOOL xxxSetMenuItemInfo(
|
|
PMENU pMenu,
|
|
UINT wIndex,
|
|
BOOL fByPosition,
|
|
LPMENUITEMINFOW lpmii,
|
|
PUNICODE_STRING pstrItem)
|
|
{
|
|
|
|
PITEM pItem;
|
|
|
|
CheckLock(pMenu);
|
|
|
|
pItem = MNLookUpItem(pMenu, wIndex, fByPosition,NULL);
|
|
if (pItem == NULL) {
|
|
/*
|
|
* Word doesn't like not finding SC_TASKLIST -- so it that's what
|
|
* they're looking for, let's pretend we changed it.
|
|
*/
|
|
if (!fByPosition && (wIndex == SC_TASKLIST))
|
|
return TRUE;
|
|
|
|
/*
|
|
* Item not found. Return false.
|
|
*/
|
|
RIPERR0(ERROR_MENU_ITEM_NOT_FOUND, RIP_WARNING, "ModifyMenu: Menu item not found");
|
|
return FALSE;
|
|
}
|
|
/*
|
|
* we need to treat MFT_RIGHTORDER separately as this is propogated down
|
|
* to the entire menu not just to this item so that we stay in ssync. This
|
|
* is pretty similar to the use of MFT_RIGHTJUST, we actually do the
|
|
* propogation because we need the flag in all sorts of places, not just
|
|
* in MBC_RightJustifyMenu()
|
|
*/
|
|
|
|
/*
|
|
* See ValidateMENUITEMINFO in client\clmenu.c will add more flags to fMask if it use to be MIIM_TYPE
|
|
* Then fMask will not be any more == MIIM_TYPE.
|
|
*/
|
|
|
|
if (lpmii->fMask & MIIM_TYPE) {
|
|
BOOL bRtoL = (lpmii->fType & MFT_RIGHTORDER) ? TRUE : FALSE;
|
|
|
|
if (bRtoL || TestMF(pMenu, MFRTL)) {
|
|
MakeMenuRtoL(pMenu, bRtoL);
|
|
}
|
|
}
|
|
return xxxSetLPITEMInfo(pMenu, pItem, lpmii, pstrItem);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxSetMenuInfo (API)
|
|
*
|
|
*
|
|
* History:
|
|
* 12-Feb-1996 JudeJ Ported from Memphis
|
|
* 23-Jun-1996 GerardoB Fixed up for 5.0
|
|
\***************************************************************************/
|
|
BOOL xxxSetMenuInfo(PMENU pMenu, LPCMENUINFO lpmi)
|
|
{
|
|
PPOPUPMENU ppopup;
|
|
BOOL fRecompute = FALSE;
|
|
BOOL fRedraw = FALSE;
|
|
UINT uFlags = MNUS_DEFAULT;
|
|
PITEM pItem;
|
|
UINT uItems;
|
|
TL tlSubMenu;
|
|
|
|
CheckLock(pMenu);
|
|
|
|
if (lpmi->fMask & MIM_STYLE) {
|
|
pMenu->fFlags ^= (pMenu->fFlags ^ lpmi->dwStyle) & MNS_VALID;
|
|
fRecompute = TRUE;
|
|
}
|
|
|
|
if (lpmi->fMask & MIM_MAXHEIGHT) {
|
|
pMenu->cyMax = lpmi->cyMax;
|
|
fRecompute = TRUE;
|
|
}
|
|
|
|
if (lpmi->fMask & MIM_BACKGROUND) {
|
|
pMenu->hbrBack = lpmi->hbrBack;
|
|
fRedraw = TRUE;
|
|
if (pMenu->dwArrowsOn != MSA_OFF) {
|
|
uFlags |= MNUS_DRAWFRAME;
|
|
}
|
|
}
|
|
|
|
if (lpmi->fMask & MIM_HELPID) {
|
|
pMenu->dwContextHelpId = lpmi->dwContextHelpID;
|
|
}
|
|
|
|
if (lpmi->fMask & MIM_MENUDATA) {
|
|
pMenu->dwMenuData = lpmi->dwMenuData;
|
|
}
|
|
|
|
/*
|
|
* Do we need to set this for all submenus?
|
|
*/
|
|
if (lpmi->fMask & MIM_APPLYTOSUBMENUS) {
|
|
pItem = pMenu->rgItems;
|
|
for (uItems = pMenu->cItems; uItems--; pItem++) {
|
|
if (pItem->spSubMenu != NULL) {
|
|
ThreadLock(pItem->spSubMenu, &tlSubMenu);
|
|
xxxSetMenuInfo(pItem->spSubMenu, lpmi);
|
|
ThreadUnlock(&tlSubMenu);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (fRecompute) {
|
|
// Set the size of this menu to be 0 so that it gets recomputed with this
|
|
// new item...
|
|
pMenu->cyMenu = pMenu->cxMenu = 0;
|
|
}
|
|
|
|
if (fRecompute || fRedraw) {
|
|
if (ppopup = MNGetPopupFromMenu(pMenu, NULL)) {
|
|
// this menu is currently being displayed -- redisplay the menu,
|
|
// recomputing if necessary
|
|
xxxMNUpdateShownMenu(ppopup, NULL, uFlags);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
/***************************************************************************\
|
|
* MNDeleteAdjustIndex
|
|
*
|
|
* History:
|
|
* 11/19/96 GerardoB Created
|
|
\***************************************************************************/
|
|
void NNDeleteAdjustIndex (UINT * puAdjustIndex, UINT uDelIndex)
|
|
{
|
|
if (*puAdjustIndex == uDelIndex) {
|
|
*puAdjustIndex = MFMWFP_NOITEM;
|
|
} else if ((int)*puAdjustIndex > (int)uDelIndex) {
|
|
(*puAdjustIndex)--;
|
|
}
|
|
}
|
|
/***************************************************************************\
|
|
* MNDeleteAdjustIndexes
|
|
*
|
|
* This function is called when an item on an active menu is about
|
|
* to be deleted. It makes sure that other indexes like posSelectedItem,
|
|
* uButtonDownIndex and uDraggingIndex are adjusted to reflect the change
|
|
* It "clears" the index if it is AT the point of deletion or
|
|
* decrements it if it is after the point of deletion
|
|
*
|
|
* History:
|
|
* 01/16/97 GerardoB Created
|
|
\***************************************************************************/
|
|
void MNDeleteAdjustIndexes (PMENUSTATE pMenuState, PPOPUPMENU ppopup, UINT uiPos)
|
|
{
|
|
/*
|
|
* Adjust the index of the selected item and the dropped popup, if needed.
|
|
*/
|
|
NNDeleteAdjustIndex(&ppopup->posSelectedItem, uiPos);
|
|
if (ppopup->fHierarchyDropped) {
|
|
NNDeleteAdjustIndex(&ppopup->posDropped, uiPos);
|
|
}
|
|
|
|
/*
|
|
* Adjust uButtonDownIndex and uDraggingIndex if needed
|
|
*/
|
|
if (pMenuState->uButtonDownHitArea == (ULONG_PTR)ppopup->spwndPopupMenu) {
|
|
NNDeleteAdjustIndex(&pMenuState->uButtonDownIndex, uiPos);
|
|
}
|
|
if (pMenuState->uDraggingHitArea == (ULONG_PTR)ppopup->spwndPopupMenu) {
|
|
NNDeleteAdjustIndex(&pMenuState->uDraggingIndex, uiPos);
|
|
}
|
|
}
|
|
/***************************************************************************\
|
|
* xxxInsertMenuItem
|
|
*
|
|
\***************************************************************************/
|
|
BOOL xxxInsertMenuItem(
|
|
PMENU pMenu,
|
|
UINT wIndex,
|
|
BOOL fByPosition,
|
|
LPMENUITEMINFOW lpmii,
|
|
PUNICODE_STRING pstrItem)
|
|
{
|
|
BOOL fRet = TRUE;
|
|
PITEM pItem;
|
|
PMENU pMenuItemIsOn;
|
|
PMENUSTATE pMenuState;
|
|
PITEM pNewItems;
|
|
PPOPUPMENU ppopup = NULL;
|
|
TL tlMenu;
|
|
UINT uiPos;
|
|
|
|
CheckLock(pMenu);
|
|
|
|
// Find out where the item we are inserting should go.
|
|
if (wIndex != MFMWFP_NOITEM) {
|
|
pItem = MNLookUpItem(pMenu, wIndex, fByPosition, &pMenuItemIsOn);
|
|
|
|
if (pItem != NULL) {
|
|
pMenu = pMenuItemIsOn;
|
|
} else {
|
|
wIndex = MFMWFP_NOITEM;
|
|
}
|
|
} else {
|
|
pItem = NULL;
|
|
}
|
|
/*
|
|
* keep normal menu items between the MDI system bitmap items
|
|
*/
|
|
if (!TestMF(pMenu, MFISPOPUP)
|
|
&& (pMenu->cItems != 0)
|
|
&& (!(lpmii->fMask & MIIM_BITMAP)
|
|
|| (lpmii->hbmpItem > HBMMENU_MBARLAST)
|
|
|| (lpmii->hbmpItem == 0)
|
|
)) {
|
|
|
|
UINT wSave, w;
|
|
PITEM pItemWalk;
|
|
wSave = w = wIndex;
|
|
|
|
if (pItem && !fByPosition) {
|
|
w = MNGetpItemIndex(pMenu, pItem);
|
|
w = (UINT)((PBYTE)pItem - (PBYTE)(pMenu->rgItems)) / sizeof(ITEM);
|
|
}
|
|
|
|
if (!w) {
|
|
pItemWalk = pMenu->rgItems;
|
|
if ((pItemWalk->hbmp == HBMMENU_SYSTEM)) {
|
|
wIndex = 1;
|
|
}
|
|
} else {
|
|
if (w == MFMWFP_NOITEM) {
|
|
w = pMenu->cItems;
|
|
}
|
|
|
|
w--;
|
|
pItemWalk = pMenu->rgItems + w;
|
|
while (w && (pItemWalk->hbmp) && (pItemWalk->hbmp < HBMMENU_MBARLAST)) {
|
|
wIndex = w--;
|
|
pItemWalk--;
|
|
}
|
|
}
|
|
|
|
if (wIndex != wSave) {
|
|
pItem = pMenu->rgItems + wIndex;
|
|
}
|
|
}
|
|
|
|
// LATER -- we currently realloc every 10 items. investigate the
|
|
// performance hit/gain we get from this and adjust accordingly.
|
|
if (pMenu->cItems >= pMenu->cAlloced) {
|
|
if (pMenu->rgItems) {
|
|
pNewItems = (PITEM)DesktopAlloc(pMenu->head.rpdesk,
|
|
(pMenu->cAlloced + CMENUITEMALLOC) * sizeof(ITEM),
|
|
DTAG_MENUITEM);
|
|
if (pNewItems) {
|
|
RtlCopyMemory(pNewItems, pMenu->rgItems,
|
|
pMenu->cAlloced * sizeof(ITEM));
|
|
#if DBG
|
|
if (IsDbgTagEnabled(DBGTAG_TrackLocks)) {
|
|
RelocateMenuLockRecords(pNewItems, pMenu->cItems,
|
|
((PBYTE)pNewItems) - (PBYTE)(pMenu->rgItems));
|
|
}
|
|
#endif
|
|
DesktopFree(pMenu->head.rpdesk, pMenu->rgItems);
|
|
}
|
|
} else {
|
|
pNewItems = (PITEM)DesktopAlloc(pMenu->head.rpdesk,
|
|
sizeof(ITEM) * CMENUITEMALLOC, DTAG_MENUITEM);
|
|
}
|
|
|
|
if (pNewItems == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
pMenu->cAlloced += CMENUITEMALLOC;
|
|
pMenu->rgItems = pNewItems;
|
|
|
|
/*
|
|
* Now look up the item again since it probably moved when we realloced the
|
|
* memory.
|
|
*/
|
|
if (wIndex != MFMWFP_NOITEM)
|
|
pItem = MNLookUpItem(pMenu, wIndex, fByPosition, &pMenuItemIsOn);
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* If this menu is being displayed right now and we're not appending
|
|
* an item, then we need to adjust the positions we keep track of.
|
|
* We want to do this before moving the items to accomodate the
|
|
* new one, in case we need to clear the insertion bar
|
|
*/
|
|
if ((pItem != NULL)
|
|
&& (ppopup = MNGetPopupFromMenu(pMenu, &pMenuState))) {
|
|
/*
|
|
* This menu is active. Adjust the index the selected
|
|
* item and the dropped popup, if needed
|
|
*/
|
|
uiPos = MNGetpItemIndex(pMenu, pItem);
|
|
if (ppopup->posSelectedItem >= (int)uiPos) {
|
|
ppopup->posSelectedItem++;
|
|
}
|
|
if (ppopup->fHierarchyDropped && (ppopup->posDropped >= (int)uiPos)) {
|
|
ppopup->posDropped++;
|
|
}
|
|
|
|
/*
|
|
* Adjust uButtonDownIndex and uDraggingIndex if needed
|
|
*/
|
|
if (pMenuState->uButtonDownHitArea == (ULONG_PTR)ppopup->spwndPopupMenu) {
|
|
if ((int)pMenuState->uButtonDownIndex >= (int)uiPos) {
|
|
pMenuState->uButtonDownIndex++;
|
|
}
|
|
}
|
|
if (pMenuState->uDraggingHitArea == (ULONG_PTR)ppopup->spwndPopupMenu) {
|
|
/*
|
|
* Check to see if an item is inserted right on the insertion
|
|
* bar. If so, clean up any present insertion bar state
|
|
*/
|
|
if (((int)pMenuState->uDraggingIndex == (int)uiPos)
|
|
&& (pMenuState->uDraggingFlags & MNGOF_TOPGAP)) {
|
|
|
|
xxxMNSetGapState(pMenuState->uDraggingHitArea,
|
|
pMenuState->uDraggingIndex,
|
|
pMenuState->uDraggingFlags,
|
|
FALSE);
|
|
}
|
|
|
|
if ((int)pMenuState->uDraggingIndex >= (int)uiPos) {
|
|
pMenuState->uDraggingIndex++;
|
|
}
|
|
}
|
|
}
|
|
|
|
pMenu->cItems++;
|
|
if (pItem != NULL) {
|
|
// Move this item up to make room for the one we want to insert.
|
|
RtlMoveMemory(pItem + 1, pItem, (pMenu->cItems - 1) *
|
|
sizeof(ITEM) - ((char *)pItem - (char *)pMenu->rgItems));
|
|
#if DBG
|
|
if (IsDbgTagEnabled(DBGTAG_TrackLocks)) {
|
|
RelocateMenuLockRecords(pItem + 1,
|
|
(int)(&(pMenu->rgItems[pMenu->cItems]) - (pItem + 1)),
|
|
sizeof(ITEM));
|
|
}
|
|
#endif
|
|
} else {
|
|
|
|
// If lpItem is null, we will be inserting the item at the end of the
|
|
// menu.
|
|
pItem = pMenu->rgItems + pMenu->cItems - 1;
|
|
}
|
|
|
|
// Need to zero these fields in case we are inserting this item in the
|
|
// middle of the item list.
|
|
pItem->fType = 0;
|
|
pItem->fState = 0;
|
|
pItem->wID = 0;
|
|
pItem->spSubMenu = NULL;
|
|
pItem->hbmpChecked = NULL;
|
|
pItem->hbmpUnchecked = NULL;
|
|
pItem->cch = 0;
|
|
pItem->dwItemData = 0;
|
|
pItem->xItem = 0;
|
|
pItem->yItem = 0;
|
|
pItem->cxItem = 0;
|
|
pItem->cyItem = 0;
|
|
pItem->hbmp = NULL;
|
|
pItem->cxBmp = MNIS_MEASUREBMP;
|
|
pItem->lpstr = NULL;
|
|
|
|
/*
|
|
* We might have reassigned pMenu above, so lock it
|
|
*/
|
|
ThreadLock(pMenu, &tlMenu);
|
|
if (!xxxSetLPITEMInfo(pMenu, pItem, lpmii, pstrItem)) {
|
|
|
|
/*
|
|
* Reset any of the indexes we might have adjusted above
|
|
*/
|
|
if (ppopup != NULL) {
|
|
MNDeleteAdjustIndexes(pMenuState, ppopup, uiPos);
|
|
}
|
|
|
|
MNFreeItem(pMenu, pItem, TRUE);
|
|
|
|
|
|
// Move things up since we removed/deleted the item
|
|
RtlMoveMemory(pItem, pItem + 1, pMenu->cItems * (UINT)sizeof(ITEM) +
|
|
(UINT)((char *)&pMenu->rgItems[0] - (char *)(pItem + 1)));
|
|
#if DBG
|
|
if (IsDbgTagEnabled(DBGTAG_TrackLocks)) {
|
|
RelocateMenuLockRecords(pItem,
|
|
(int)(&(pMenu->rgItems[pMenu->cItems - 1]) - pItem),
|
|
-(int)sizeof(ITEM));
|
|
}
|
|
#endif
|
|
pMenu->cItems--;
|
|
fRet = FALSE;
|
|
} else {
|
|
/*
|
|
* Like MFT_RIGHTJUSTIFY, this staggers down the menu,
|
|
* (but we inherit, to make localisation etc MUCH easier).
|
|
*
|
|
* MFT_RIGHTORDER is the same value as MFT_SYSMENU. We distinguish
|
|
* between the two by also looking for MFT_BITMAP.
|
|
*/
|
|
if (TestMF(pMenu, MFRTL) ||
|
|
(pItem && TestMFT(pItem, MFT_RIGHTORDER) && !TestMFT(pItem, MFT_BITMAP))) {
|
|
pItem->fType |= (MFT_RIGHTORDER | MFT_RIGHTJUSTIFY);
|
|
if (pItem->spSubMenu) {
|
|
MakeMenuRtoL(pItem->spSubMenu, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
ThreadUnlock(&tlMenu);
|
|
return fRet;
|
|
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* FreeItemBitmap
|
|
*
|
|
* History:
|
|
* 07-23-96 GerardoB - Added header and Fixed up for 5.0
|
|
\***************************************************************************/
|
|
void FreeItemBitmap(PITEM pItem)
|
|
{
|
|
// Free up hItem unless it's a bitmap handle or nonexistent.
|
|
// Apps are responsible for freeing their bitmaps.
|
|
if ((pItem->hbmp != NULL) && !TestMFS(pItem, MFS_CACHEDBMP)) {
|
|
/*
|
|
* Assign ownership of the bitmap to the process that is
|
|
* destroying the menu to ensure that bitmap will
|
|
* eventually be destroyed.
|
|
*/
|
|
GreSetBitmapOwner((HBITMAP)(pItem->hbmp), OBJECT_OWNER_CURRENT);
|
|
}
|
|
|
|
// Zap this pointer in case we try to free or reference it again
|
|
pItem->hbmp = NULL;
|
|
}
|
|
/***************************************************************************\
|
|
* FreeItemString
|
|
*
|
|
* History:
|
|
* 07-23-96 GerardoB - Added header and Fixed up for 5.0
|
|
\***************************************************************************/
|
|
|
|
void FreeItemString(PMENU pMenu, PITEM pItem)
|
|
{
|
|
// Free up Item's string
|
|
if ((pItem->lpstr != NULL)) {
|
|
DesktopFree(pMenu->head.rpdesk, pItem->lpstr);
|
|
}
|
|
// Zap this pointer in case we try to free or reference it again
|
|
pItem->lpstr = NULL;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* FreeItem
|
|
*
|
|
* Free a menu item and its associated resources.
|
|
*
|
|
* History:
|
|
* 10-11-90 JimA Translated from ASM
|
|
\***************************************************************************/
|
|
|
|
void MNFreeItem(
|
|
PMENU pMenu,
|
|
PITEM pItem,
|
|
BOOL fFreeItemPopup)
|
|
{
|
|
PMENU pSubMenu;
|
|
|
|
FreeItemBitmap(pItem);
|
|
FreeItemString(pMenu, pItem);
|
|
|
|
pSubMenu = UnlockSubMenu(pMenu, &(pItem->spSubMenu));
|
|
if (pSubMenu) {
|
|
if (fFreeItemPopup) {
|
|
_DestroyMenu(pSubMenu);
|
|
}
|
|
}
|
|
}
|
|
/***************************************************************************\
|
|
* RemoveDeleteMenuHelper
|
|
*
|
|
* This removes the menu item from the given menu. If
|
|
* fDeleteMenuItem, the memory associted with the popup menu associated with
|
|
* the item being removed is freed and recovered.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
BOOL xxxRemoveDeleteMenuHelper(
|
|
PMENU pMenu,
|
|
UINT nPosition,
|
|
DWORD wFlags,
|
|
BOOL fDeleteMenu)
|
|
{
|
|
PITEM pItem;
|
|
PITEM pNewItems;
|
|
PMENU pMenuSave;
|
|
PMENUSTATE pMenuState;
|
|
PPOPUPMENU ppopup;
|
|
UINT uiPos;
|
|
|
|
CheckLock(pMenu);
|
|
|
|
pMenuSave = pMenu;
|
|
|
|
pItem = MNLookUpItem(pMenu, nPosition, (BOOL) (wFlags & MF_BYPOSITION), &pMenu);
|
|
if (pItem == NULL) {
|
|
|
|
/*
|
|
* Hack for apps written for Win95. In Win95 the prototype for
|
|
* this function was with 'WORD nPosition' and because of this
|
|
* the HIWORD(nPosition) got set to 0.
|
|
* We are doing this just for system menu commands.
|
|
*/
|
|
if (nPosition >= 0xFFFFF000 && !(wFlags & MF_BYPOSITION)) {
|
|
nPosition &= 0x0000FFFF;
|
|
pMenu = pMenuSave;
|
|
pItem = MNLookUpItem(pMenu, nPosition, FALSE, &pMenu);
|
|
|
|
if (pItem == NULL)
|
|
return FALSE;
|
|
} else
|
|
return FALSE;
|
|
}
|
|
|
|
if (ppopup = MNGetPopupFromMenu(pMenu, &pMenuState)) {
|
|
/*
|
|
* This menu is active; since we're about to insert an item,
|
|
* make sure that any of the positions we've stored are
|
|
* adjusted properly
|
|
*/
|
|
uiPos = MNGetpItemIndex(pMenu, pItem);
|
|
MNDeleteAdjustIndexes(pMenuState, ppopup, uiPos);
|
|
}
|
|
MNFreeItem(pMenu, pItem, fDeleteMenu);
|
|
|
|
/*
|
|
* Reset the menu size so that it gets recomputed next time.
|
|
*/
|
|
pMenu->cyMenu = pMenu->cxMenu = 0;
|
|
|
|
if (pMenu->cItems == 1) {
|
|
DesktopFree(pMenu->head.rpdesk, pMenu->rgItems);
|
|
pMenu->cAlloced = 0;
|
|
pNewItems = NULL;
|
|
} else {
|
|
/*
|
|
* Move things up since we removed/deleted the item.
|
|
*/
|
|
|
|
RtlMoveMemory(pItem, pItem + 1, pMenu->cItems * (UINT)sizeof(ITEM) +
|
|
(UINT)((char *)&pMenu->rgItems[0] - (char *)(pItem + 1)));
|
|
#if DBG
|
|
if (IsDbgTagEnabled(DBGTAG_TrackLocks)) {
|
|
RelocateMenuLockRecords(pItem,
|
|
(int)(&(pMenu->rgItems[pMenu->cItems - 1]) - pItem),
|
|
-(int)sizeof(ITEM));
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* We're shrinking so if localalloc fails, just leave the mem as is.
|
|
*/
|
|
UserAssert(pMenu->cAlloced >= pMenu->cItems);
|
|
if ((pMenu->cAlloced - pMenu->cItems) >= CMENUITEMDEALLOC - 1) {
|
|
pNewItems = (PITEM)DesktopAlloc(pMenu->head.rpdesk,
|
|
(pMenu->cAlloced - CMENUITEMDEALLOC) * sizeof(ITEM),
|
|
DTAG_MENUITEM);
|
|
if (pNewItems != NULL) {
|
|
|
|
RtlCopyMemory(pNewItems, pMenu->rgItems,
|
|
(pMenu->cAlloced - CMENUITEMDEALLOC) * sizeof(ITEM));
|
|
#if DBG
|
|
if (IsDbgTagEnabled(DBGTAG_TrackLocks)) {
|
|
RelocateMenuLockRecords(pNewItems, pMenu->cItems - 1,
|
|
((PBYTE)pNewItems) - (PBYTE)(pMenu->rgItems));
|
|
}
|
|
#endif
|
|
DesktopFree(pMenu->head.rpdesk, pMenu->rgItems);
|
|
pMenu->cAlloced -= CMENUITEMDEALLOC;
|
|
} else {
|
|
pNewItems = pMenu->rgItems;
|
|
}
|
|
} else {
|
|
pNewItems = pMenu->rgItems;
|
|
}
|
|
}
|
|
|
|
pMenu->rgItems = pNewItems;
|
|
pMenu->cItems--;
|
|
|
|
if (ppopup != NULL) {
|
|
/*
|
|
* this menu is currently being displayed -- redisplay the menu with
|
|
* this item removed
|
|
*/
|
|
xxxMNUpdateShownMenu(ppopup, pMenu->rgItems + uiPos, MNUS_DELETE);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* RemoveMenu
|
|
*
|
|
* Removes and item but doesn't delete it. Only useful for items with
|
|
* an associated popup since this will remove the item from the menu with
|
|
* destroying the popup menu handle.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
BOOL xxxRemoveMenu(
|
|
PMENU pMenu,
|
|
UINT nPosition,
|
|
UINT wFlags)
|
|
{
|
|
return xxxRemoveDeleteMenuHelper(pMenu, nPosition, wFlags, FALSE);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* DeleteMenu
|
|
*
|
|
* Deletes an item. ie. Removes it and recovers the memory used by it.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
BOOL xxxDeleteMenu(
|
|
PMENU pMenu,
|
|
UINT nPosition,
|
|
UINT wFlags)
|
|
{
|
|
return xxxRemoveDeleteMenuHelper(pMenu, nPosition, wFlags, TRUE);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxSetLPITEMInfo
|
|
*
|
|
* History:
|
|
* 07-23-96 GerardoB - Added header and Fixed up for 5.0
|
|
\***************************************************************************/
|
|
BOOL NEAR xxxSetLPITEMInfo(
|
|
PMENU pMenu,
|
|
PITEM pItem,
|
|
LPMENUITEMINFOW lpmii,
|
|
PUNICODE_STRING pstrItem)
|
|
{
|
|
|
|
HANDLE hstr;
|
|
UINT cch;
|
|
BOOL fRecompute = FALSE;
|
|
BOOL fRedraw = FALSE;
|
|
PPOPUPMENU ppopup;
|
|
|
|
CheckLock(pMenu);
|
|
|
|
if (lpmii->fMask & MIIM_FTYPE) {
|
|
pItem->fType &= ~MFT_MASK;
|
|
pItem->fType |= lpmii->fType;
|
|
if (lpmii->fType & MFT_SEPARATOR ) {
|
|
pItem->fState |= MFS_DISABLED ;
|
|
}
|
|
fRecompute = TRUE;
|
|
fRedraw = (lpmii->fType & MFT_OWNERDRAW);
|
|
}
|
|
|
|
if (lpmii->fMask & MIIM_STRING) {
|
|
if (pstrItem->Buffer != NULL) {
|
|
hstr = (HANDLE)DesktopAlloc(pMenu->head.rpdesk,
|
|
pstrItem->Length + sizeof(UNICODE_NULL), DTAG_MENUTEXT);
|
|
|
|
if (hstr == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
try {
|
|
RtlCopyMemory(hstr, pstrItem->Buffer, pstrItem->Length);
|
|
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
|
|
DesktopFree(pMenu->head.rpdesk, hstr);
|
|
return FALSE;
|
|
}
|
|
cch = pstrItem->Length / sizeof(WCHAR);
|
|
/*
|
|
* We don't need to null terminate the string, since DesktopAlloc
|
|
* zero-fills for us.
|
|
*/
|
|
} else {
|
|
cch = 0;
|
|
hstr = NULL;
|
|
}
|
|
FreeItemString(pMenu,pItem);
|
|
pItem->cch = cch;
|
|
pItem->lpstr = hstr;
|
|
fRecompute = TRUE;
|
|
fRedraw = TRUE;
|
|
}
|
|
|
|
if (lpmii->fMask & MIIM_BITMAP) {
|
|
FreeItemBitmap(pItem);
|
|
pItem->hbmp = lpmii->hbmpItem;
|
|
fRecompute = TRUE;
|
|
fRedraw = TRUE;
|
|
pItem->cxBmp = MNIS_MEASUREBMP;
|
|
/*
|
|
* If this is one of the special bitmaps, mark it as such
|
|
*/
|
|
if ((pItem->hbmp > HBMMENU_MIN) && (pItem->hbmp < HBMMENU_MAX)) {
|
|
SetMFS(pItem, MFS_CACHEDBMP);
|
|
} else {
|
|
ClearMFS(pItem, MFS_CACHEDBMP);
|
|
}
|
|
}
|
|
|
|
if (lpmii->fMask & MIIM_ID) {
|
|
pItem->wID = lpmii->wID;
|
|
}
|
|
|
|
if (lpmii->fMask & MIIM_DATA) {
|
|
pItem->dwItemData = lpmii->dwItemData;
|
|
}
|
|
|
|
if (lpmii->fMask & MIIM_STATE) {
|
|
/*
|
|
* Preserve private bits (~MFS_MASK).
|
|
* Also preserve MFS_HILITE | MFS_DEFAULT if already set; if not set,
|
|
* let the caller turn them on.
|
|
*/
|
|
UserAssert(!(lpmii->fState & ~MFS_MASK));
|
|
pItem->fState &= ~MFS_MASK | MFS_HILITE | MFS_DEFAULT;
|
|
pItem->fState |= lpmii->fState;
|
|
if (pItem->fType & MFT_SEPARATOR)
|
|
pItem->fState |= MFS_DISABLED;
|
|
fRedraw = TRUE;
|
|
}
|
|
|
|
if (lpmii->fMask & MIIM_CHECKMARKS) {
|
|
pItem->hbmpChecked = lpmii->hbmpChecked;
|
|
pItem->hbmpUnchecked = lpmii->hbmpUnchecked;
|
|
fRedraw = TRUE;
|
|
}
|
|
|
|
if (lpmii->fMask & MIIM_SUBMENU) {
|
|
PMENU pSubMenu = NULL;
|
|
|
|
if (lpmii->hSubMenu != NULL) {
|
|
pSubMenu = ValidateHmenu(lpmii->hSubMenu);
|
|
}
|
|
|
|
// Free the popup associated with this item, if any and if needed.
|
|
if (pItem->spSubMenu != pSubMenu) {
|
|
if (pItem->spSubMenu != NULL) {
|
|
_DestroyMenu(pItem->spSubMenu);
|
|
}
|
|
if (pSubMenu != NULL) {
|
|
|
|
BOOL bMenuCreated = FALSE;
|
|
/*
|
|
* Fix MSTest that sets a submenu to itself by giving it a different handle.
|
|
* So the loop is broken and we won't fail their call later
|
|
* MCostea #243374
|
|
*/
|
|
if (pSubMenu == pMenu) {
|
|
pSubMenu = _CreateMenu();
|
|
if (!pSubMenu) {
|
|
return FALSE;
|
|
}
|
|
bMenuCreated = TRUE;
|
|
}
|
|
/*
|
|
* Link the submenu and then check for loops
|
|
*/
|
|
Lock(&(pItem->spSubMenu), pSubMenu);
|
|
SetMF(pItem->spSubMenu, MFISPOPUP);
|
|
/*
|
|
* We just added a submenu. Check to see if the menu tree is not
|
|
* unreasonable deep and there is no loop forming.
|
|
* This will prevent us from running out of stack
|
|
* MCostea #226460
|
|
*/
|
|
if (GetMenuDepth(pSubMenu, NESTED_MENU_LIMIT) + GetMenuAncestors(pMenu) >= NESTED_MENU_LIMIT) {
|
|
FailInsertion:
|
|
RIPMSG1(RIP_WARNING, "The menu hierarchy is very deep or has a loop %#p", pMenu);
|
|
ClearMF(pItem->spSubMenu, MFISPOPUP);
|
|
Unlock(&(pItem->spSubMenu));
|
|
if (bMenuCreated) {
|
|
_DestroyMenu(pSubMenu);
|
|
}
|
|
return FALSE;
|
|
}
|
|
/*
|
|
* Add pMenu to the pSubMenu->pParentMenus list
|
|
*/
|
|
{
|
|
PMENULIST pMenuList = DesktopAlloc(pMenu->head.rpdesk,
|
|
sizeof(MENULIST),
|
|
DTAG_MENUITEM);
|
|
if (!pMenuList) {
|
|
goto FailInsertion;
|
|
}
|
|
pMenuList->pMenu = pMenu;
|
|
pMenuList->pNext = pSubMenu->pParentMenus;
|
|
pSubMenu->pParentMenus = pMenuList;
|
|
}
|
|
} else {
|
|
UnlockSubMenu(pMenu, &(pItem->spSubMenu));
|
|
}
|
|
fRedraw = TRUE;
|
|
}
|
|
}
|
|
|
|
// For support of the old way of defining a separator i.e. if it is not a string
|
|
// or a bitmap or a ownerdraw, then it must be a separator.
|
|
// This should prpbably be moved to MIIOneWayConvert -jjk
|
|
if (!(pItem->fType & (MFT_OWNERDRAW | MFT_SEPARATOR))
|
|
&& (pItem->lpstr == NULL)
|
|
&& (pItem->hbmp == NULL)) {
|
|
|
|
pItem->fType = MFT_SEPARATOR;
|
|
pItem->fState|=MFS_DISABLED;
|
|
}
|
|
|
|
if (fRecompute) {
|
|
pItem->dxTab = 0;
|
|
pItem->ulX = UNDERLINE_RECALC;
|
|
pItem->ulWidth = 0;
|
|
|
|
// Set the size of this menu to be 0 so that it gets recomputed with this
|
|
// new item...
|
|
pMenu->cyMenu = pMenu->cxMenu = 0;
|
|
|
|
|
|
if (fRedraw) {
|
|
if (ppopup = MNGetPopupFromMenu(pMenu, NULL)) {
|
|
// this menu is currently being displayed -- redisplay the menu,
|
|
// recomputing if necessary
|
|
xxxMNUpdateShownMenu(ppopup, pItem, MNUS_DEFAULT);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL _SetMenuContextHelpId(PMENU pMenu, DWORD dwContextHelpId)
|
|
{
|
|
|
|
// Set the new context help Id;
|
|
pMenu->dwContextHelpId = dwContextHelpId;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL _SetMenuFlagRtoL(PMENU pMenu)
|
|
{
|
|
|
|
// This is a right-to-left menu being created;
|
|
SetMF(pMenu, MFRTL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MNGetPopupFromMenu
|
|
*
|
|
* checks to see if the given hMenu is currently being shown in a popup.
|
|
* returns the PPOPUPMENU associated with this hMenu if it is being shown;
|
|
* NULL if the hMenu is not currently being shown
|
|
*
|
|
* History:
|
|
* 07-23-96 GerardoB - Added header & fixed up for 5.0
|
|
\***************************************************************************/
|
|
PPOPUPMENU MNGetPopupFromMenu(PMENU pMenu, PMENUSTATE *ppMenuState)
|
|
{
|
|
PPOPUPMENU ppopup;
|
|
PMENUSTATE pMenuState;
|
|
|
|
/*
|
|
* If this menu doesn't have a notification window, then
|
|
* it cannot be in menu mode
|
|
*/
|
|
if (pMenu->spwndNotify == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* If no pMenuState, no menu mode
|
|
*/
|
|
pMenuState = GetpMenuState(pMenu->spwndNotify);
|
|
if (pMenuState == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* If not in the menu loop, not yet or no longer in menu mode
|
|
*/
|
|
if (!pMenuState->fInsideMenuLoop) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* return pMenuState if requested
|
|
*/
|
|
if (ppMenuState != NULL) {
|
|
*ppMenuState = pMenuState;
|
|
}
|
|
|
|
|
|
/*
|
|
* Starting from the root popup, find the popup associated to this menu
|
|
*/
|
|
ppopup = pMenuState->pGlobalPopupMenu;
|
|
while (ppopup != NULL) {
|
|
/*
|
|
* found?
|
|
*/
|
|
if (ppopup->spmenu == pMenu) {
|
|
if (ppopup->fIsMenuBar) {
|
|
return NULL;
|
|
}
|
|
/*
|
|
* Since the menu is being modified, let's kill any animation.
|
|
*/
|
|
MNAnimate(pMenuState, FALSE);
|
|
return ppopup;
|
|
}
|
|
/*
|
|
* If no more popups, bail
|
|
*/
|
|
if (ppopup->spwndNextPopup == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Next popup
|
|
*/
|
|
ppopup = ((PMENUWND)ppopup->spwndNextPopup)->ppopupmenu;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMNUpdateShownMenu
|
|
*
|
|
* updates a given ppopup menu window to reflect the inserting, deleting,
|
|
* or altering of the given lpItem.
|
|
*
|
|
* History:
|
|
* 07-23-96 GerardoB - Added header & fixed up for 5.0
|
|
\***************************************************************************/
|
|
void xxxMNUpdateShownMenu(PPOPUPMENU ppopup, PITEM pItem, UINT uFlags)
|
|
{
|
|
RECT rc;
|
|
PWND pwnd = ppopup->spwndPopupMenu;
|
|
PMENU pMenu = ppopup->spmenu;
|
|
TL tlpwnd;
|
|
TL tlpmenu;
|
|
|
|
/*
|
|
* The popup might get destroyed while we callback, so lock this pwnd.
|
|
*/
|
|
ThreadLock(pwnd, &tlpwnd);
|
|
ThreadLock(ppopup->spmenu, &tlpmenu);
|
|
|
|
_GetClientRect(pwnd, &rc);
|
|
|
|
/*
|
|
* If we need to resize menu window
|
|
*/
|
|
if (pMenu->cxMenu == 0) {
|
|
RECT rcScroll = rc;
|
|
int cySave = rc.bottom;
|
|
int cxSave = rc.right;
|
|
DWORD dwSize;
|
|
DWORD dwArrowsOnBefore;
|
|
|
|
dwArrowsOnBefore = pMenu->dwArrowsOn;
|
|
UserAssert(uFlags != 0);
|
|
dwSize = (DWORD)xxxSendMessage(pwnd, MN_SIZEWINDOW, uFlags, 0L);
|
|
uFlags &= ~MNUS_DRAWFRAME;
|
|
/*
|
|
* If scroll arrows appeared or disappeared, redraw entire client
|
|
*/
|
|
if (dwArrowsOnBefore ^ pMenu->dwArrowsOn) {
|
|
goto InvalidateAll;
|
|
}
|
|
|
|
rc.right = LOWORD(dwSize);
|
|
if (pItem != NULL) {
|
|
if (rc.right != cxSave) {
|
|
/*
|
|
* The width changed, so redraw everything.
|
|
* NOTE -- This could be tuned to just redraw items with
|
|
* submenus and/or accelerator fields.
|
|
*/
|
|
goto InvalidateAll;
|
|
} else {
|
|
rc.bottom = pMenu->cyMenu;
|
|
if (pMenu->dwArrowsOn != MSA_OFF) {
|
|
if (rc.bottom <= cySave) {
|
|
rc.top = pItem->yItem - MNGetToppItem(pMenu)->yItem;
|
|
goto InvalidateRest;
|
|
}
|
|
|
|
_GetClientRect(pwnd, &rcScroll);
|
|
}
|
|
|
|
rc.top = rcScroll.top = pItem->yItem - MNGetToppItem(pMenu)->yItem;
|
|
if ((rc.top >= 0) && (rc.top < (int)pMenu->cyMenu)) {
|
|
xxxScrollWindowEx(pwnd, 0, rc.bottom - cySave, &rcScroll, &rc, NULL, NULL, SW_INVALIDATE | SW_ERASE);
|
|
}
|
|
} /* else of if (rc.right != cxSave) */
|
|
} /* if (pItem != NULL) */
|
|
} /* if (pMenu->cxMenu == 0) */
|
|
|
|
if (!(uFlags & MNUS_DELETE)) {
|
|
if (pItem != NULL) {
|
|
rc.top = pItem->yItem - MNGetToppItem(pMenu)->yItem;
|
|
rc.bottom = rc.top + pItem->cyItem;
|
|
InvalidateRest:
|
|
if ((rc.top >= 0) && (rc.top < (int)pMenu->cyMenu)) {
|
|
xxxInvalidateRect(pwnd, &rc, TRUE);
|
|
}
|
|
} else {
|
|
InvalidateAll:
|
|
xxxInvalidateRect(pwnd, NULL, TRUE);
|
|
}
|
|
if (uFlags & MNUS_DRAWFRAME) {
|
|
xxxSetWindowPos(pwnd, NULL, 0, 0, 0, 0,
|
|
SWP_DRAWFRAME | SWP_NOSIZE | SWP_NOZORDER | SWP_NOMOVE
|
|
| SWP_NOACTIVATE | SWP_NOOWNERZORDER);
|
|
}
|
|
}
|
|
|
|
ThreadUnlock(&tlpmenu);
|
|
ThreadUnlock(&tlpwnd);
|
|
}
|