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