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.
405 lines
11 KiB
405 lines
11 KiB
/**************************** Module Header ********************************\
|
|
* Module Name: mnkey.c
|
|
*
|
|
* Copyright (c) 1985 - 1999, Microsoft Corporation
|
|
*
|
|
* Menu Keyboard Handling Routines
|
|
*
|
|
* History:
|
|
* 10-10-90 JimA Cleanup.
|
|
* 03-18-91 IanJa Window revalidation added
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
int xxxClientFindMnemChar(
|
|
PUNICODE_STRING pstrSrc,
|
|
WCHAR ch,
|
|
BOOL fFirst,
|
|
BOOL fPrefix);
|
|
|
|
/* MenuSwitch commands */
|
|
#define CMDSWITCH 1
|
|
#define CMDQUERY 2
|
|
|
|
/***************************************************************************\
|
|
* FindNextValidMenuItem
|
|
*
|
|
* !
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
UINT MNFindNextValidItem(
|
|
PMENU pMenu,
|
|
int i,
|
|
int dir,
|
|
UINT flags)
|
|
{
|
|
int iStart;
|
|
BOOL cont = TRUE;
|
|
int cItems = pMenu->cItems;
|
|
PITEM pItem;
|
|
|
|
if ((i < 0) && (dir > 0))
|
|
// going forward from beginning -- stop after last menu item
|
|
i = iStart = cItems;
|
|
else if ((i >= cItems) && (dir < 0))
|
|
// going backward from end -- stop after first menu item
|
|
i = iStart = -1;
|
|
else
|
|
iStart = i;
|
|
|
|
if (!cItems)
|
|
return(MFMWFP_NOITEM);
|
|
|
|
// b#8997 - if we have these conditions and enter
|
|
// loop will go blistic ( infin )
|
|
// fix: jump over code and come loop to i == iStart will now stop us
|
|
if ( ( i == 0 ) && ( cItems == 1 ) && ( dir > 0 ) )
|
|
{
|
|
dir = 0;
|
|
goto artsquickndirtybugfix;
|
|
}
|
|
|
|
//
|
|
// Loop thru menu items til (1) we find a valid item
|
|
// or (2) we make it back to the start item (iStart)
|
|
while (TRUE) {
|
|
i += dir;
|
|
|
|
if ((i == iStart) || (dir == 0))
|
|
// we made it back to start item -- return NOT FOUND
|
|
return MFMWFP_NOITEM;
|
|
|
|
// keep 'i' in the range: 0 <= i < cItems
|
|
if (i >= cItems) {
|
|
i = -1;
|
|
continue;
|
|
}
|
|
else if (i < 0) {
|
|
i = cItems;
|
|
continue;
|
|
}
|
|
|
|
artsquickndirtybugfix:
|
|
pItem = pMenu->rgItems + i;
|
|
|
|
// skip ownerdraw - seperators even though there not NULL
|
|
if (TestMFT(pItem, MFT_SEPARATOR)) {
|
|
//
|
|
// Skip non-separator (if asked) empty items. With hot-tracking,
|
|
// it is acceptable for them to be selected. In truth, it was possible
|
|
// in Win3.1 too, but less likely.
|
|
//
|
|
if (!(flags & MNF_DONTSKIPSEPARATORS)) {
|
|
continue;
|
|
}
|
|
} else if ((pItem->hbmp >= HBMMENU_MBARFIRST) && (pItem->hbmp <= HBMMENU_MBARLAST)) {
|
|
/*
|
|
* Skip close & minimize & restore buttons
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
// return index of found item
|
|
return(i);
|
|
}
|
|
|
|
//
|
|
// We should never get here!
|
|
//
|
|
UserAssert(FALSE);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MKF_FindMenuItemInColumn
|
|
*
|
|
* Finds closest item in the pull-down menu's next "column".
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
UINT MNFindItemInColumn(
|
|
PMENU pMenu,
|
|
UINT idxB,
|
|
int dir,
|
|
BOOL fRoot)
|
|
{
|
|
int dxMin;
|
|
int dyMin;
|
|
int dxMax;
|
|
int dyMax;
|
|
int xB;
|
|
int yB;
|
|
UINT idxE;
|
|
UINT idxR;
|
|
UINT cItems;
|
|
PITEM pItem;
|
|
|
|
cItems = pMenu->cItems;
|
|
idxR = MFMWFP_NOITEM;
|
|
idxE = MNFindNextValidItem(pMenu, MFMWFP_NOITEM, dir, 0);
|
|
if (idxE == -1)
|
|
goto End;
|
|
|
|
dxMin = dyMin = 20000;
|
|
|
|
if (idxB >= pMenu->cItems)
|
|
return idxR;
|
|
|
|
pItem = &pMenu->rgItems[idxB];
|
|
xB = pItem->xItem;
|
|
yB = pItem->yItem;
|
|
|
|
while (cItems-- > 0 &&
|
|
(idxB = MNFindNextValidItem(pMenu, idxB, dir, 0)) != idxE &&
|
|
(idxB != MFMWFP_NOITEM)) {
|
|
pItem = &pMenu->rgItems[idxB];
|
|
dxMax = xB - pItem->xItem;
|
|
dyMax = yB - pItem->yItem;
|
|
|
|
if (dxMax < 0)
|
|
dxMax = (-dxMax);
|
|
if (dyMax < 0)
|
|
dyMax = (-dyMax);
|
|
|
|
// See if this item is nearer than the last item found
|
|
// --------------------------------------------------------
|
|
// (fRoot || dxMax) -- this condition means that if it's
|
|
// not the actual menu bar menu that we're dealing with,
|
|
// then the item below/above (same X value as) the selected
|
|
// item is not a valid one to move to
|
|
if ((dyMax < dyMin) && (fRoot || dxMax) && dxMax <= dxMin) {
|
|
dxMin = dxMax;
|
|
dyMin = dyMax;
|
|
idxR = idxB;
|
|
}
|
|
}
|
|
|
|
End:
|
|
return idxR;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* MKF_FindMenuChar
|
|
*
|
|
* Translates Virtual cursor key movements into pseudo-ascii values. Maps a
|
|
* character to an item number.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
UINT xxxMNFindChar(
|
|
PMENU pMenu,
|
|
UINT ch,
|
|
int idxC,
|
|
LPINT lpr) /* Put match type here */
|
|
{
|
|
int idxFirst = MFMWFP_NOITEM;
|
|
int idxB;
|
|
int idxF;
|
|
int rT;
|
|
LPWSTR lpstr;
|
|
PITEM pItem;
|
|
|
|
if (ch == 0)
|
|
return 0;
|
|
|
|
/*
|
|
* First time thru go for the very first menu.
|
|
*/
|
|
idxF = MFMWFP_NOITEM;
|
|
rT = 0;
|
|
idxB = idxC;
|
|
|
|
if (idxB < 0)
|
|
// if (idxB & 0x8000)
|
|
idxB = MNFindNextValidItem(pMenu, pMenu->cItems, MFMWFP_NOITEM, MNF_DONTSKIPSEPARATORS);
|
|
|
|
do {
|
|
INT idxPrev;
|
|
|
|
idxPrev = idxC;
|
|
idxC = MNFindNextValidItem(pMenu, idxC, 1, MNF_DONTSKIPSEPARATORS);
|
|
if (idxC == MFMWFP_NOITEM || idxC == idxFirst)
|
|
break;
|
|
if (idxFirst == MFMWFP_NOITEM)
|
|
idxFirst = idxC;
|
|
|
|
pItem = &pMenu->rgItems[idxC];
|
|
|
|
if (pItem->lpstr != NULL) {
|
|
if (pItem->cch != 0) {
|
|
UNICODE_STRING strMnem;
|
|
|
|
lpstr = TextPointer(pItem->lpstr);
|
|
if (*lpstr == CH_HELPPREFIX) {
|
|
|
|
/*
|
|
* Skip help prefix if it is there so that we can mnemonic
|
|
* to the first character of a right justified string.
|
|
*/
|
|
lpstr++;
|
|
}
|
|
|
|
RtlInitUnicodeString(&strMnem, lpstr);
|
|
if (((rT = (UINT)xxxClientFindMnemChar(&strMnem,
|
|
(WCHAR)ch, TRUE, TRUE)) == 0x0080) &&
|
|
(idxF == MFMWFP_NOITEM))
|
|
idxF = idxC;
|
|
}
|
|
}
|
|
if (idxC == idxPrev) {
|
|
break; // no progress - break inf. loop
|
|
}
|
|
} while (rT != 1 && idxB != idxC);
|
|
|
|
*lpr = rT;
|
|
|
|
if (rT == 1)
|
|
return idxC;
|
|
|
|
return idxF;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxMenuKeyFilter
|
|
*
|
|
* !
|
|
*
|
|
* Revalidation notes:
|
|
* o Routine assumes it is called with pMenuState->hwndMenu non-NULL and valid.
|
|
* o If one or more of the popup menu windows is unexpectedly destroyed, this is
|
|
* detected in xxxMenuWndProc(), which sets pMenuState->fSabotaged and calls
|
|
* xxxKillMenuState(). Therefore, if we return from an xxxRoutine with
|
|
* pMenuState->fSabotaged set, we must abort immediately.
|
|
* o If pMenuState->hwndMenu is unexpectedly destroyed, we abort only if we
|
|
* need to use the corresponding pwndMenu.
|
|
* o pMenuState->hwndMenu may be supplied as a parameter to various routines
|
|
* (eg: xxxNextItem), whether valid or not.
|
|
* o Any label preceded with xxx (eg: xxxMKF_UnlockAndExit) may be reached with
|
|
* pMenuState->hwndMenu invalid.
|
|
* o If this routine is not called while in xxxMenuLoop(), then it must
|
|
* clear pMenuState->fSabotaged before returning.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxMNKeyFilter(
|
|
PPOPUPMENU ppopupMenu,
|
|
PMENUSTATE pMenuState,
|
|
UINT ch)
|
|
{
|
|
BOOL fLocalInsideMenuLoop = pMenuState->fInsideMenuLoop;
|
|
|
|
if (pMenuState->fButtonDown) {
|
|
|
|
/*
|
|
* Ignore keystrokes while the mouse is pressed (except ESC).
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (!pMenuState->fInsideMenuLoop) {
|
|
|
|
/*
|
|
* Need to send the WM_INITMENU message before we pull down the menu.
|
|
*/
|
|
if (!xxxMNStartMenu(ppopupMenu, KEYBDHOLD)) {
|
|
return;
|
|
}
|
|
pMenuState->fInsideMenuLoop = TRUE;
|
|
}
|
|
|
|
|
|
switch (ch) {
|
|
case 0:
|
|
|
|
/*
|
|
* If we get a WM_KEYDOWN alt key and then a KEYUP alt key, we need to
|
|
* activate the first item on the menu. ie. user hits and releases alt
|
|
* key so just select first item. USER sends us a SC_KEYMENU with
|
|
* lParam 0 when the user does this.
|
|
*/
|
|
xxxMNSelectItem(ppopupMenu, pMenuState, 0);
|
|
break;
|
|
|
|
case MENUCHILDSYSMENU:
|
|
if (!TestwndChild(ppopupMenu->spwndNotify)) {
|
|
|
|
/*
|
|
* Change made to fix MDI problem: child window gets a keymenu,
|
|
* and pops up sysmenu of frame when maximized. Need to act like
|
|
* MENUCHAR if hwndMenu is a top-level.
|
|
*/
|
|
goto MenuCharHandler;
|
|
}
|
|
|
|
/*
|
|
* else fall through.
|
|
*/
|
|
|
|
case MENUSYSMENU:
|
|
if (!TestWF(ppopupMenu->spwndNotify, WFSYSMENU)) {
|
|
xxxMessageBeep(0);
|
|
goto MenuCancel;
|
|
}
|
|
|
|
/*
|
|
* Popup any hierarchies we have.
|
|
*/
|
|
xxxMNCloseHierarchy(ppopupMenu, pMenuState);
|
|
if (!ppopupMenu->fIsSysMenu && ppopupMenu->spmenuAlternate)
|
|
xxxMNSwitchToAlternateMenu(ppopupMenu);
|
|
if (!ppopupMenu->fIsSysMenu) {
|
|
/*
|
|
* If no system menu, get out.
|
|
*/
|
|
goto MenuCancel;
|
|
}
|
|
|
|
MNPositionSysMenu(ppopupMenu->spwndPopupMenu, ppopupMenu->spmenu);
|
|
xxxMNSelectItem(ppopupMenu, pMenuState, 0);
|
|
xxxMNOpenHierarchy(ppopupMenu, pMenuState);
|
|
ppopupMenu->fToggle = FALSE;
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
/*
|
|
* Handle ALT-Character sequences for items on top level menu bar.
|
|
* Note that fInsideMenuLoop may be set to false on return from this
|
|
* function if the app decides to return 1 to the WM_MENUCHAR message.
|
|
* We detect this and not enter MenuLoop if fInsideMenuLoop is reset
|
|
* to false.
|
|
*/
|
|
MenuCharHandler:
|
|
xxxMNChar(ppopupMenu, pMenuState, ch);
|
|
if (ppopupMenu->posSelectedItem == MFMWFP_NOITEM) {
|
|
/*
|
|
* No selection found.
|
|
*/
|
|
goto MenuCancel;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!fLocalInsideMenuLoop && pMenuState->fInsideMenuLoop) {
|
|
xxxMNLoop(ppopupMenu, pMenuState, 0, FALSE);
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
MenuCancel:
|
|
pMenuState->fModelessMenu = FALSE;
|
|
if (!ppopupMenu->fInCancel) {
|
|
xxxMNDismiss(pMenuState);
|
|
}
|
|
UserAssert(!pMenuState->fInsideMenuLoop && !pMenuState->fMenuStarted);
|
|
return;
|
|
}
|