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.
988 lines
34 KiB
988 lines
34 KiB
/**************************** Module Header ********************************\
|
|
* Module Name: mnloop.c
|
|
*
|
|
* Copyright (c) 1985 - 1999, Microsoft Corporation
|
|
*
|
|
* Menu Modal Loop Routines
|
|
*
|
|
* History:
|
|
* 10-10-90 JimA Cleanup.
|
|
* 03-18-91 IanJa Window revalidation added
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
/***************************************************************************\
|
|
* xxxMNRemoveMessage
|
|
*
|
|
* History
|
|
* 11/23/96 GerardoB Created
|
|
\***************************************************************************/
|
|
BOOL xxxMNRemoveMessage (UINT message1, UINT message2)
|
|
{
|
|
MSG msg;
|
|
if (!xxxPeekMessage(&msg, NULL, 0, 0, PM_NOYIELD | PM_NOREMOVE)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ((msg.message == message1) || (msg.message == message2)) {
|
|
UserAssert(msg.message != 0);
|
|
xxxPeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE);
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
/***************************************************************************\
|
|
* xxxHandleMenuMessages
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
BOOL xxxHandleMenuMessages(
|
|
LPMSG lpmsg,
|
|
PMENUSTATE pMenuState,
|
|
PPOPUPMENU ppopupmenu)
|
|
{
|
|
DWORD ch;
|
|
ULONG_PTR cmdHitArea;
|
|
UINT cmdItem;
|
|
LPARAM lParam;
|
|
BOOL fThreadLock = FALSE;
|
|
TL tlpwndHitArea;
|
|
TL tlpwndT;
|
|
POINT pt;
|
|
PWND pwnd;
|
|
RECT rc;
|
|
|
|
/*
|
|
* Paranoia. Let's bail up front if we don't have a menu.
|
|
* Some code checks for NULL spmenu, other parts assume it's always not NULL
|
|
* Use RIP_ERROR for a while to make sure this is OK
|
|
*/
|
|
if (ppopupmenu->spmenu == NULL) {
|
|
RIPMSG2(RIP_ERROR, "xxxHandleMenuMessages NULL spmenu. pMenuSate:%p ppopupmenu:%p",
|
|
pMenuState, ppopupmenu);
|
|
return FALSE;
|
|
}
|
|
/*
|
|
* Get things out of the structure so that we can access them quicker.
|
|
*/
|
|
ch = (DWORD)lpmsg->wParam;
|
|
lParam = lpmsg->lParam;
|
|
|
|
/*
|
|
* In this switch statement, we only look at messages we want to handle and
|
|
* swallow. Messages we don't understand will get translated and
|
|
* dispatched.
|
|
*/
|
|
switch (lpmsg->message) {
|
|
case WM_RBUTTONDOWN:
|
|
case WM_NCRBUTTONDOWN:
|
|
|
|
if (ppopupmenu->fRightButton) {
|
|
goto HandleButtonDown;
|
|
}
|
|
/*
|
|
* Fall through
|
|
*/
|
|
case WM_RBUTTONDBLCLK:
|
|
case WM_NCRBUTTONDBLCLK:
|
|
/*
|
|
* Right click outside the menu dismisses the menu
|
|
* (we didn't use to do this for single right clicks on 4.0)
|
|
*/
|
|
pMenuState->mnFocus = MOUSEHOLD;
|
|
cmdHitArea = xxxMNFindWindowFromPoint(ppopupmenu, &cmdItem, MAKEPOINTS(lParam));
|
|
if (cmdHitArea == MFMWFP_OFFMENU) {
|
|
xxxMNDismiss(pMenuState);
|
|
return TRUE;
|
|
}
|
|
/*
|
|
* Do nothing on right clicks on the menu
|
|
*/
|
|
if (!pMenuState->fModelessMenu) {
|
|
xxxMNRemoveMessage(lpmsg->message, 0);
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
case WM_NCLBUTTONDOWN:
|
|
// Commented out due to TandyT whinings...
|
|
// if ((ppopupmenu->trackPopupMenuFlags & TPM_RIGHTBUTTON))
|
|
// break;
|
|
|
|
HandleButtonDown:
|
|
|
|
/*
|
|
* Find out where this mouse down occurred.
|
|
*/
|
|
pMenuState->mnFocus = MOUSEHOLD;
|
|
pMenuState->ptMouseLast.x = GET_X_LPARAM(lParam);
|
|
pMenuState->ptMouseLast.y = GET_Y_LPARAM(lParam);
|
|
cmdHitArea = xxxMNFindWindowFromPoint(ppopupmenu, &cmdItem, MAKEPOINTS(lParam));
|
|
|
|
|
|
/*
|
|
* Thread lock this if it is a pwnd. This certainly isn't the way
|
|
* you'd implement this if you had locking to begin with.
|
|
*/
|
|
fThreadLock = IsMFMWFPWindow(cmdHitArea);
|
|
if (fThreadLock) {
|
|
ThreadLock((PWND)cmdHitArea, &tlpwndHitArea);
|
|
}
|
|
|
|
/*
|
|
* If this is a drag and drop menu, remember the mouse
|
|
* position and the hit test results.
|
|
*/
|
|
if (pMenuState->fDragAndDrop) {
|
|
pMenuState->ptButtonDown = pMenuState->ptMouseLast;
|
|
pMenuState->uButtonDownIndex = cmdItem;
|
|
LockMFMWFPWindow(&pMenuState->uButtonDownHitArea, cmdHitArea);
|
|
}
|
|
|
|
/*
|
|
* Modeless menus don't capture the mouse so we might not see
|
|
* the button up. We also release capture when sending the
|
|
* WM_MENUDODRAGDROP message. So we want to remember what
|
|
* mouse button went down.
|
|
*/
|
|
if (pMenuState->fDragAndDrop || pMenuState->fModelessMenu) {
|
|
if (ch & MK_RBUTTON) {
|
|
pMenuState->vkButtonDown = VK_RBUTTON;
|
|
} else {
|
|
pMenuState->vkButtonDown = VK_LBUTTON;
|
|
}
|
|
}
|
|
|
|
|
|
if ((cmdHitArea == MFMWFP_OFFMENU) && (cmdItem == 0)) {
|
|
//
|
|
// Clicked in middle of nowhere, so terminate menus, and
|
|
// let button pass through.
|
|
CancelOut:
|
|
xxxMNDismiss(pMenuState);
|
|
goto Unlock;
|
|
} else if (ppopupmenu->fHasMenuBar && (cmdHitArea == MFMWFP_ALTMENU)) {
|
|
//
|
|
// Switching between menu bar & popup
|
|
//
|
|
xxxMNSwitchToAlternateMenu(ppopupmenu);
|
|
cmdHitArea = MFMWFP_NOITEM;
|
|
}
|
|
|
|
if (cmdHitArea == MFMWFP_NOITEM) {
|
|
//
|
|
// On menu bar (system or main)
|
|
//
|
|
xxxMNButtonDown(ppopupmenu, pMenuState, cmdItem, TRUE);
|
|
} else {
|
|
// On popup window menu
|
|
UserAssert(cmdHitArea);
|
|
xxxSendMessage((PWND)cmdHitArea, MN_BUTTONDOWN, cmdItem, 0L);
|
|
}
|
|
|
|
/*
|
|
* Swallow the message since we handled it.
|
|
*/
|
|
/*
|
|
* The excel guys change a wm_rbuttondown to a wm_lbuttondown message
|
|
* in their message filter hook. Remove the message here or we'll
|
|
* get in a nasty loop.
|
|
*
|
|
* We need to swallow msg32.message ONLY. It is possible for
|
|
* the LBUTTONDOWN to not be at the head of the input queue.
|
|
* If not, we will swallow a WM_MOUSEMOVE or something else like
|
|
* that. The reason Peek() doesn't need to check the range
|
|
* is because we've already Peek(PM_NOYIELD'ed) before, which
|
|
* locked the sys queue.
|
|
*/
|
|
if (!pMenuState->fModelessMenu) {
|
|
xxxMNRemoveMessage(lpmsg->message, WM_RBUTTONDOWN);
|
|
}
|
|
goto Unlock;
|
|
|
|
case WM_MOUSEMOVE:
|
|
case WM_NCMOUSEMOVE:
|
|
|
|
/*
|
|
* Is the user starting to drag?
|
|
*/
|
|
if (pMenuState->fDragAndDrop
|
|
&& pMenuState->fButtonDown
|
|
&& !pMenuState->fDragging
|
|
&& !pMenuState->fButtonAlwaysDown
|
|
&& (pMenuState->uButtonDownHitArea != MFMWFP_OFFMENU)) {
|
|
|
|
/*
|
|
* We expect the mouse to go down on a menu item before a drag can start
|
|
*/
|
|
UserAssert(!ppopupmenu->fFirstClick);
|
|
|
|
/*
|
|
* Calculate drag detect rectangle using the position the mouse went
|
|
* down on
|
|
*/
|
|
*(LPPOINT)&rc.left = pMenuState->ptButtonDown;
|
|
*(LPPOINT)&rc.right = pMenuState->ptButtonDown;
|
|
InflateRect(&rc, SYSMET(CXDRAG), SYSMET(CYDRAG));
|
|
|
|
pt.x = GET_X_LPARAM(lParam);
|
|
pt.y = GET_Y_LPARAM(lParam);
|
|
|
|
/*
|
|
* If we've moved outside the drag rect, then the user is dragging
|
|
*/
|
|
if (!PtInRect(&rc, pt)) {
|
|
/*
|
|
* Post a message so we'll finish processing this message
|
|
* and get out of here before telling the app that the user
|
|
* is dragging
|
|
*/
|
|
pwnd = GetMenuStateWindow(pMenuState);
|
|
if (pwnd != NULL) {
|
|
pMenuState->fDragging = TRUE;
|
|
_PostMessage(pwnd, MN_DODRAGDROP, 0, 0);
|
|
} else {
|
|
RIPMSG0(RIP_ERROR, "xxxMNMouseMove. Unble to post MN_DODGRAGDROP");
|
|
}
|
|
}
|
|
} /* if (pMenuState->fDragAndDrop */
|
|
|
|
xxxMNMouseMove(ppopupmenu, pMenuState, MAKEPOINTS(lParam));
|
|
return TRUE;
|
|
|
|
case WM_RBUTTONUP:
|
|
case WM_NCRBUTTONUP:
|
|
if (ppopupmenu->fRightButton) {
|
|
goto HandleButtonUp;
|
|
}
|
|
/*
|
|
* If the button is down, simply swallow this message
|
|
*/
|
|
if (pMenuState->fButtonDown) {
|
|
if (!pMenuState->fModelessMenu) {
|
|
xxxMNRemoveMessage(lpmsg->message, 0);
|
|
}
|
|
return TRUE;
|
|
}
|
|
// New feature for shell start menu -- notify when a right click
|
|
// occurs on a menu item, and open a window of opportunity for
|
|
// menus to recurse, allowing them to popup a context-sensitive
|
|
// menu for that item. (jeffbog 9/28/95)
|
|
//
|
|
// NOTE: Though this feature was added for Nashville, it was enabled
|
|
// by default for all apps on Win2K and exhibited no app compat
|
|
// problems. Thus, it is assumed that no version checking is required;
|
|
// moreoever, changing at this point is needlessly dangerous.
|
|
if ((lpmsg->message == WM_RBUTTONUP) && !ppopupmenu->fNoNotify) {
|
|
PPOPUPMENU ppopupActive;
|
|
|
|
if ((ppopupmenu->spwndActivePopup != NULL)
|
|
&& (ppopupActive = ((PMENUWND)(ppopupmenu->spwndActivePopup))->ppopupmenu)
|
|
&& MNIsItemSelected(ppopupActive))
|
|
{
|
|
TL tlpwndNotify;
|
|
ThreadLock( ppopupActive->spwndNotify, &tlpwndNotify );
|
|
xxxSendMessage(ppopupActive->spwndNotify, WM_MENURBUTTONUP,
|
|
ppopupActive->posSelectedItem, (LPARAM)PtoH(ppopupActive->spmenu));
|
|
ThreadUnlock( &tlpwndNotify );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_LBUTTONUP:
|
|
case WM_NCLBUTTONUP:
|
|
// Commented out due to TandyT whinings...
|
|
// if ((ppopupmenu->trackPopupMenuFlags & TPM_RIGHTBUTTON))
|
|
// break;
|
|
|
|
HandleButtonUp:
|
|
if (!pMenuState->fButtonDown) {
|
|
|
|
/*
|
|
* Don't care about this mouse up since we never saw the button
|
|
* down for some reason.
|
|
*/
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Cancel the dragging state, if any.
|
|
*/
|
|
if (pMenuState->fDragAndDrop) {
|
|
|
|
UnlockMFMWFPWindow(&pMenuState->uButtonDownHitArea);
|
|
pMenuState->fDragging = FALSE;
|
|
|
|
if (pMenuState->fIgnoreButtonUp) {
|
|
pMenuState->fButtonDown =
|
|
pMenuState->fIgnoreButtonUp = FALSE;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find out where this mouse up occurred.
|
|
*/
|
|
pMenuState->ptMouseLast.x = GET_X_LPARAM(lParam);
|
|
pMenuState->ptMouseLast.y = GET_Y_LPARAM(lParam);
|
|
cmdHitArea = xxxMNFindWindowFromPoint(ppopupmenu, &cmdItem, MAKEPOINTS(lParam));
|
|
|
|
|
|
/*
|
|
* If this is not true, some the code below won't work right.
|
|
*/
|
|
UserAssert((cmdHitArea != MFMWFP_OFFMENU) || (cmdItem == 0));
|
|
UserAssert(cmdHitArea != 0x0000FFFF);
|
|
|
|
/*
|
|
* Thread lock this if it is a pwnd. This certainly isn't the way
|
|
* you'd implement this if you had locking to begin with.
|
|
*/
|
|
fThreadLock = IsMFMWFPWindow(cmdHitArea);
|
|
if (fThreadLock) {
|
|
ThreadLock((PWND)cmdHitArea, &tlpwndHitArea);
|
|
}
|
|
|
|
|
|
if (ppopupmenu->fHasMenuBar) {
|
|
if (((cmdHitArea == MFMWFP_OFFMENU) && (cmdItem == 0)) ||
|
|
((cmdHitArea == MFMWFP_NOITEM) && ppopupmenu->fIsSysMenu && ppopupmenu->fToggle))
|
|
// Button up occurred in some random spot. Terminate
|
|
// menus and swallow the message.
|
|
goto CancelOut;
|
|
} else {
|
|
if ((cmdHitArea == MFMWFP_OFFMENU) && (cmdItem == 0)) {
|
|
if (!ppopupmenu->fFirstClick) {
|
|
//
|
|
// User upclicked in some random spot. Terminate
|
|
// menus and don't swallow the message.
|
|
//
|
|
|
|
//
|
|
// Don't do anything with HWND here cuz the window is
|
|
// destroyed after this SendMessage().
|
|
//
|
|
// DONTREVALIDATE();
|
|
ThreadLock(ppopupmenu->spwndPopupMenu, &tlpwndT);
|
|
xxxSendMessage(ppopupmenu->spwndPopupMenu, MN_CANCELMENUS, 0, 0);
|
|
ThreadUnlock(&tlpwndT);
|
|
goto Unlock;
|
|
}
|
|
}
|
|
|
|
ppopupmenu->fFirstClick = FALSE;
|
|
}
|
|
|
|
if (cmdHitArea == MFMWFP_NOITEM) {
|
|
//
|
|
// This is a system menu or a menu bar and the button up
|
|
// occurred on the system menu or on a menu bar item.
|
|
//
|
|
xxxMNButtonUp(ppopupmenu, pMenuState, cmdItem, 0);
|
|
} else if ((cmdHitArea != MFMWFP_OFFMENU) && (cmdHitArea != MFMWFP_ALTMENU)) {
|
|
//
|
|
// Warning: It's common for the popup to go away during the
|
|
// processing of this message, so don't add any code that
|
|
// messes with hwnd after this call!
|
|
//
|
|
// DONTREVALIDATE();
|
|
|
|
//
|
|
// We send lParam (that has the mouse co-ords ) for the app
|
|
// to get it in its SC_RESTORE/SC_MINIMIZE messages 3.0
|
|
// compat
|
|
//
|
|
xxxSendMessage((PWND)cmdHitArea, MN_BUTTONUP, (DWORD)cmdItem, lParam);
|
|
} else {
|
|
pMenuState->fButtonDown =
|
|
pMenuState->fButtonAlwaysDown = FALSE;
|
|
}
|
|
Unlock:
|
|
if (fThreadLock)
|
|
ThreadUnlock(&tlpwndHitArea);
|
|
return TRUE;
|
|
|
|
|
|
case WM_LBUTTONDBLCLK:
|
|
case WM_NCLBUTTONDBLCLK:
|
|
|
|
// Commented out due to TandyT whinings...
|
|
// if (ppopup->fRightButton)
|
|
// break;
|
|
pMenuState->mnFocus = MOUSEHOLD;
|
|
cmdHitArea = xxxMNFindWindowFromPoint(ppopupmenu, &cmdItem, MAKEPOINTS(lParam));
|
|
if ((cmdHitArea == MFMWFP_OFFMENU) && (cmdItem == 0)) {
|
|
// Dbl-clicked in middle of nowhere, so terminate menus, and
|
|
// let button pass through.
|
|
xxxMNDismiss(pMenuState);
|
|
return TRUE;
|
|
} else if (ppopupmenu->fHasMenuBar && (cmdHitArea == MFMWFP_ALTMENU)) {
|
|
//
|
|
// BOGUS
|
|
// TREAT LIKE BUTTON DOWN since we didn't dblclk on same item.
|
|
//
|
|
xxxMNSwitchToAlternateMenu(ppopupmenu);
|
|
cmdHitArea = MFMWFP_NOITEM;
|
|
}
|
|
|
|
if (cmdHitArea == MFMWFP_NOITEM)
|
|
xxxMNDoubleClick(pMenuState, ppopupmenu, cmdItem);
|
|
else {
|
|
UserAssert(cmdHitArea);
|
|
|
|
ThreadLock((PWND)cmdHitArea, &tlpwndHitArea);
|
|
xxxSendMessage((PWND)cmdHitArea, MN_DBLCLK,
|
|
(DWORD)cmdItem, 0L);
|
|
ThreadUnlock(&tlpwndHitArea);
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_KEYDOWN:
|
|
case WM_SYSKEYDOWN:
|
|
|
|
/*
|
|
* If mouse button is down, ignore keyboard input (fix #3899, IanJa)
|
|
*/
|
|
if (pMenuState->fButtonDown && (ch != VK_F1)) {
|
|
|
|
/*
|
|
* Check if the user wants to cancel dragging.
|
|
*/
|
|
if (pMenuState->fDragging && (ch == VK_ESCAPE)) {
|
|
RIPMSG0(RIP_WARNING, "xxxHandleMenuMessages: ESC while dragging");
|
|
pMenuState->fIgnoreButtonUp = TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
pMenuState->mnFocus = KEYBDHOLD;
|
|
switch (ch) {
|
|
case VK_UP:
|
|
case VK_DOWN:
|
|
case VK_LEFT:
|
|
case VK_RIGHT:
|
|
case VK_RETURN:
|
|
case VK_CANCEL:
|
|
case VK_ESCAPE:
|
|
case VK_MENU:
|
|
case VK_F10:
|
|
case VK_F1:
|
|
if (ppopupmenu->spwndActivePopup) {
|
|
ThreadLockAlways(ppopupmenu->spwndActivePopup, &tlpwndT);
|
|
xxxSendMessage(ppopupmenu->spwndActivePopup, lpmsg->message,
|
|
ch, 0L);
|
|
ThreadUnlock(&tlpwndT);
|
|
} else {
|
|
xxxMNKeyDown(ppopupmenu, pMenuState, (UINT)ch);
|
|
}
|
|
break;
|
|
|
|
case VK_TAB:
|
|
/*
|
|
* People hit the ALT key now just to turn underlines ON in dialogs.
|
|
* This throws them into "invisible menu mode". If they hit any char
|
|
* at that point, we'll bail in xxxMNChar. But not so if they hit ctrl-tab,
|
|
* which is used to navigate property sheets. So let's help them out.
|
|
*/
|
|
if (ppopupmenu->fIsMenuBar && (ppopupmenu->spwndActivePopup == NULL)) {
|
|
xxxMNDismiss(pMenuState);
|
|
return TRUE;
|
|
}
|
|
/*
|
|
* Fall through
|
|
*/
|
|
|
|
default:
|
|
TranslateKey:
|
|
if (!pMenuState->fModelessMenu) {
|
|
xxxTranslateMessage(lpmsg, 0);
|
|
}
|
|
break;
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_CHAR:
|
|
case WM_SYSCHAR:
|
|
if (ppopupmenu->spwndActivePopup) {
|
|
ThreadLockAlways(ppopupmenu->spwndActivePopup, &tlpwndT);
|
|
xxxSendMessage(ppopupmenu->spwndActivePopup, lpmsg->message,
|
|
ch, 0L);
|
|
ThreadUnlock(&tlpwndT);
|
|
} else {
|
|
xxxMNChar(ppopupmenu, pMenuState, (UINT)ch);
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_SYSKEYUP:
|
|
|
|
/*
|
|
* Ignore ALT and F10 keyup messages since they are handled on
|
|
* the KEYDOWN message.
|
|
*/
|
|
if (ch == VK_MENU || ch == VK_F10) {
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
** fall thru **
|
|
*/
|
|
|
|
case WM_KEYUP:
|
|
|
|
/*
|
|
* Do RETURNs on the up transition only
|
|
*/
|
|
goto TranslateKey;
|
|
|
|
case WM_SYSTIMER:
|
|
|
|
/*
|
|
* Prevent the caret from flashing by eating all WM_SYSTIMER messages.
|
|
*/
|
|
return TRUE;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
#if DBG
|
|
/*
|
|
* Nobody should be able to steal capture from modal menus.
|
|
*/
|
|
if (!pMenuState->fModelessMenu
|
|
&& !pMenuState->fInDoDragDrop
|
|
&& !ExitMenuLoop (pMenuState, ppopupmenu) ) {
|
|
|
|
UserAssert(PtiCurrent()->pq->QF_flags & QF_CAPTURELOCKED);
|
|
UserAssert(PtiCurrent()->pq->spwndCapture == ppopupmenu->spwndNotify);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* We didn't handle this message
|
|
*/
|
|
return FALSE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxEndMenuLoop
|
|
*
|
|
* Makes sure that the menu has been ended/canceled
|
|
*
|
|
* History:
|
|
* 10/25/96 GerardoB Extracted from xxxMNLoop
|
|
\***************************************************************************/
|
|
void xxxEndMenuLoop (PMENUSTATE pMenuState, PPOPUPMENU ppopupmenu)
|
|
{
|
|
|
|
UserAssert(IsRootPopupMenu(ppopupmenu));
|
|
|
|
if (ppopupmenu->fIsTrackPopup) {
|
|
if (!ppopupmenu->fInCancel) {
|
|
xxxMNDismiss(pMenuState);
|
|
}
|
|
} else {
|
|
if (pMenuState->fUnderline) {
|
|
TL tlpwnd;
|
|
ThreadLock(ppopupmenu->spwndNotify, &tlpwnd);
|
|
xxxDrawMenuBarUnderlines(ppopupmenu->spwndNotify, FALSE);
|
|
ThreadUnlock(&tlpwnd);
|
|
}
|
|
if (!pMenuState->fInEndMenu) {
|
|
xxxEndMenu(pMenuState);
|
|
}
|
|
}
|
|
/*
|
|
* If this is a modeless menu, make sure that the notification
|
|
* window caption is drawn in the proper state
|
|
*/
|
|
if (pMenuState->fModelessMenu && (ppopupmenu->spwndNotify != NULL)) {
|
|
PWND pwndNotify = ppopupmenu->spwndNotify;
|
|
PTHREADINFO pti = GETPTI(pwndNotify);
|
|
BOOL fFrameOn = (pti->pq == gpqForeground)
|
|
&& (pti->pq->spwndActive == pwndNotify);
|
|
TL tlpwndNotify;
|
|
|
|
if (fFrameOn ^ !!TestWF(pwndNotify, WFFRAMEON)) {
|
|
ThreadLockAlways(pwndNotify, &tlpwndNotify);
|
|
xxxDWP_DoNCActivate(pwndNotify,
|
|
(fFrameOn ? NCA_ACTIVE : NCA_FORCEFRAMEOFF),
|
|
HRGN_FULL);
|
|
ThreadUnlock(&tlpwndNotify);
|
|
|
|
}
|
|
}
|
|
}
|
|
/***************************************************************************\
|
|
* xxxMenuLoop
|
|
*
|
|
* The menu processing entry point.
|
|
* assumes: pMenuState->spwndMenu is the window which is the owner of the menu
|
|
* we are processing.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
int xxxMNLoop(
|
|
PPOPUPMENU ppopupmenu,
|
|
PMENUSTATE pMenuState,
|
|
LPARAM lParam,
|
|
BOOL fDblClk)
|
|
{
|
|
int hit;
|
|
MSG msg;
|
|
BOOL fSendIdle = TRUE;
|
|
BOOL fInQueue = FALSE;
|
|
DWORD menuState;
|
|
PTHREADINFO pti;
|
|
TL tlpwndT;
|
|
|
|
UserAssert(IsRootPopupMenu(ppopupmenu));
|
|
|
|
pMenuState->fInsideMenuLoop = TRUE;
|
|
pMenuState->cmdLast = 0;
|
|
|
|
pti = PtiCurrent();
|
|
|
|
pMenuState->ptMouseLast.x = pti->ptLast.x;
|
|
pMenuState->ptMouseLast.y = pti->ptLast.y;
|
|
|
|
/*
|
|
* Set flag to false, so that we can track if windows have
|
|
* been activated since entering this loop.
|
|
*/
|
|
pti->pq->QF_flags &= ~QF_ACTIVATIONCHANGE;
|
|
|
|
/*
|
|
* Were we called from xxxMenuKeyFilter? If not, simulate a LBUTTONDOWN
|
|
* message to bring up the popup.
|
|
*/
|
|
if (!pMenuState->fMenuStarted) {
|
|
if (_GetKeyState(((ppopupmenu->fRightButton) ?
|
|
VK_RBUTTON : VK_LBUTTON)) >= 0) {
|
|
|
|
/*
|
|
* We think the mouse button should be down but the call to get key
|
|
* state says different so we need to get outta menu mode. This
|
|
* happens if clicking on the menu causes a sys modal message box to
|
|
* come up before we can enter this stuff. For example, run
|
|
* winfile, click on drive a: to see its tree. Activate some other
|
|
* app, then open drive a: and activate winfile by clicking on the
|
|
* menu. This causes a sys modal msg box to come up just before
|
|
* entering menu mode. The user may have the mouse button up but
|
|
* menu mode code thinks it is down...
|
|
*/
|
|
|
|
/*
|
|
* Need to notify the app we are exiting menu mode because we told
|
|
* it we were entering menu mode just before entering this function
|
|
* in xxxSysCommand()...
|
|
*/
|
|
if (!ppopupmenu->fNoNotify) {
|
|
ThreadLock(ppopupmenu->spwndNotify, &tlpwndT);
|
|
xxxSendNotifyMessage(ppopupmenu->spwndNotify, WM_EXITMENULOOP,
|
|
((ppopupmenu->fIsTrackPopup && !ppopupmenu->fIsSysMenu) ? TRUE : FALSE), 0);
|
|
ThreadUnlock(&tlpwndT);
|
|
}
|
|
goto ExitMenuLoop;
|
|
}
|
|
|
|
/*
|
|
* Simulate a WM_LBUTTONDOWN message.
|
|
*/
|
|
if (!ppopupmenu->fIsTrackPopup) {
|
|
|
|
/*
|
|
* For TrackPopupMenus, we do it in the TrackPopupMenu function
|
|
* itself so we don't want to do it again.
|
|
*/
|
|
if (!xxxMNStartMenu(ppopupmenu, MOUSEHOLD)) {
|
|
goto ExitMenuLoop;
|
|
}
|
|
}
|
|
|
|
if ((ppopupmenu->fRightButton)) {
|
|
msg.message = (fDblClk ? WM_RBUTTONDBLCLK : WM_RBUTTONDOWN);
|
|
msg.wParam = MK_RBUTTON;
|
|
} else {
|
|
msg.message = (fDblClk ? WM_LBUTTONDBLCLK : WM_LBUTTONDOWN);
|
|
msg.wParam = MK_LBUTTON;
|
|
}
|
|
msg.lParam = lParam;
|
|
msg.hwnd = HW(ppopupmenu->spwndPopupMenu);
|
|
xxxHandleMenuMessages(&msg, pMenuState, ppopupmenu);
|
|
}
|
|
|
|
/*
|
|
* If this is a modeless menu, release capture, mark it in the menu state
|
|
* and return. Decrement foreground lock count.
|
|
*/
|
|
if (pMenuState->fModelessMenu) {
|
|
xxxMNReleaseCapture();
|
|
|
|
DecSFWLockCount();
|
|
DBGDecModalMenuCount();
|
|
return 0;
|
|
}
|
|
|
|
while (pMenuState->fInsideMenuLoop) {
|
|
|
|
/*
|
|
* Is a message waiting for us?
|
|
*/
|
|
BOOL fPeek = xxxPeekMessage(&msg, NULL, 0, 0, PM_NOYIELD | PM_NOREMOVE);
|
|
|
|
Validateppopupmenu(ppopupmenu);
|
|
|
|
if (fPeek) {
|
|
/*
|
|
* Bail if we have been forced out of menu loop
|
|
*/
|
|
if (ExitMenuLoop (pMenuState, ppopupmenu)) {
|
|
goto ExitMenuLoop;
|
|
}
|
|
|
|
/*
|
|
* Since we could have blocked in xxxWaitMessage (see last line
|
|
* of loop) or xxxPeekMessage, reset the cached copy of
|
|
* ptiCurrent()->pq: It could have changed if someone did a
|
|
* DetachThreadInput() while we were away.
|
|
*/
|
|
if ((!ppopupmenu->fIsTrackPopup &&
|
|
pti->pq->spwndActive != ppopupmenu->spwndNotify &&
|
|
((pti->pq->spwndActive == NULL) || !_IsChild(pti->pq->spwndActive, ppopupmenu->spwndNotify)))) {
|
|
|
|
/*
|
|
* End menu processing if we are no longer the active window.
|
|
* This is needed in case a system modal dialog box pops up
|
|
* while we are tracking the menu code for example. It also
|
|
* helps out Tracer if a macro is executed while a menu is down.
|
|
*/
|
|
|
|
/*
|
|
* Also, end menu processing if we think the mouse button is
|
|
* down but it really isn't. (Happens if a sys modal dialog int
|
|
* time dlg box comes up while we are in menu mode.)
|
|
*/
|
|
|
|
goto ExitMenuLoop;
|
|
}
|
|
|
|
if (ppopupmenu->fIsMenuBar && msg.message == WM_LBUTTONDBLCLK) {
|
|
|
|
/*
|
|
* Was the double click on the system menu or caption?
|
|
*/
|
|
hit = FindNCHit(ppopupmenu->spwndNotify, (LONG)msg.lParam);
|
|
if (hit == HTCAPTION) {
|
|
PWND pwnd;
|
|
PMENU pmenu;
|
|
|
|
/*
|
|
* Get the message out of the queue since we're gonna
|
|
* process it.
|
|
*/
|
|
xxxPeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE);
|
|
if (ExitMenuLoop (pMenuState, ppopupmenu)) {
|
|
goto ExitMenuLoop;
|
|
} else {
|
|
pwnd = ppopupmenu->spwndNotify;
|
|
ThreadLockAlways(pwnd, &tlpwndT);
|
|
pmenu = xxxGetSysMenuHandle(pwnd);
|
|
UserAssert(pwnd == ppopupmenu->spwndNotify);
|
|
|
|
menuState = _GetMenuState(pmenu, SC_RESTORE & 0x0000FFF0,
|
|
MF_BYCOMMAND);
|
|
|
|
/*
|
|
* Only send the sys command if the item is valid. If
|
|
* the item doesn't exist or is disabled, then don't
|
|
* post the syscommand. Note that for win2 apps, we
|
|
* always send the sys command if it is a child window.
|
|
* This is so hosebag apps can change the sys menu.
|
|
*/
|
|
if (!(menuState & MFS_GRAYED)) {
|
|
_PostMessage(pwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
|
|
}
|
|
|
|
/*
|
|
* Get out of menu mode.
|
|
*/
|
|
ThreadUnlock(&tlpwndT);
|
|
goto ExitMenuLoop;
|
|
}
|
|
}
|
|
}
|
|
|
|
fInQueue = (msg.message == WM_LBUTTONDOWN ||
|
|
msg.message == WM_RBUTTONDOWN ||
|
|
msg.message == WM_NCLBUTTONDOWN ||
|
|
msg.message == WM_NCRBUTTONDOWN);
|
|
|
|
if (!fInQueue) {
|
|
|
|
/*
|
|
* Note that we call xxxPeekMessage() with the filter
|
|
* set to the message we got from xxxPeekMessage() rather
|
|
* than simply 0, 0. This prevents problems when
|
|
* xxxPeekMessage() returns something like a WM_TIMER,
|
|
* and after we get here to remove it a WM_LBUTTONDOWN,
|
|
* or some higher-priority input message, gets in the
|
|
* queue and gets removed accidently. Basically we want
|
|
* to be sure we remove the right message in this case.
|
|
* NT bug 3852 was caused by this problem.
|
|
* Set the TIF_IGNOREPLAYBACKDELAY bit in case journal playback
|
|
* is happening: this allows us to proceed even if the hookproc
|
|
* incorrectly returns a delay now. The bit will be cleared if
|
|
* this happens, so we can see why the Peek-Remove below fails.
|
|
* Lotus' Freelance Graphics tutorial does such bad journalling
|
|
*/
|
|
|
|
pti->TIF_flags |= TIF_IGNOREPLAYBACKDELAY;
|
|
if (!xxxPeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE)) {
|
|
if (pti->TIF_flags & TIF_IGNOREPLAYBACKDELAY) {
|
|
pti->TIF_flags &= ~TIF_IGNOREPLAYBACKDELAY;
|
|
/*
|
|
* It wasn't a bad journal playback: something else
|
|
* made the previously peeked message disappear before
|
|
* we could peek it again to remove it.
|
|
*/
|
|
RIPMSG1(RIP_WARNING, "Disappearing msg 0x%08lx", msg.message);
|
|
goto NoMsg;
|
|
}
|
|
}
|
|
pti->TIF_flags &= ~TIF_IGNOREPLAYBACKDELAY;
|
|
}
|
|
|
|
if (!_CallMsgFilter(&msg, MSGF_MENU)) {
|
|
if (!xxxHandleMenuMessages(&msg, pMenuState, ppopupmenu)) {
|
|
xxxTranslateMessage(&msg, 0);
|
|
xxxDispatchMessage(&msg);
|
|
}
|
|
|
|
Validateppopupmenu(ppopupmenu);
|
|
|
|
if (ExitMenuLoop (pMenuState, ppopupmenu)) {
|
|
goto ExitMenuLoop;
|
|
}
|
|
|
|
if (pti->pq->QF_flags & QF_ACTIVATIONCHANGE) {
|
|
|
|
/*
|
|
* Run away and exit menu mode if another window has become
|
|
* active while a menu was up.
|
|
*/
|
|
RIPMSG0(RIP_WARNING, "Exiting menu mode: another window activated");
|
|
goto ExitMenuLoop;
|
|
}
|
|
|
|
#if DBG
|
|
/*
|
|
* Nobody should be able to steal capture from us.
|
|
*/
|
|
if (!pMenuState->fInDoDragDrop) {
|
|
UserAssert(pti->pq->QF_flags & QF_CAPTURELOCKED);
|
|
UserAssert(pti->pq->spwndCapture == ppopupmenu->spwndNotify);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* If we get a system timer, then it's like we're idle
|
|
*/
|
|
if (msg.message == WM_SYSTIMER) {
|
|
goto NoMsg;
|
|
}
|
|
|
|
/*
|
|
* Don't set fSendIdle if we got these messages
|
|
*/
|
|
if ((msg.message == WM_TIMER) || (msg.message == WM_PAINT)) {
|
|
continue;
|
|
}
|
|
|
|
} else {
|
|
if (fInQueue)
|
|
xxxPeekMessage(&msg, NULL, msg.message, msg.message,
|
|
PM_REMOVE);
|
|
}
|
|
|
|
/*
|
|
* Reenable WM_ENTERIDLE messages.
|
|
*/
|
|
fSendIdle = TRUE;
|
|
|
|
} else {
|
|
NoMsg:
|
|
/*
|
|
* Bail if we have been forced out of menu loop
|
|
*/
|
|
if (ExitMenuLoop (pMenuState, ppopupmenu)) {
|
|
goto ExitMenuLoop;
|
|
}
|
|
|
|
UserAssert((ppopupmenu->spwndActivePopup == NULL)
|
|
|| (TestWF(ppopupmenu->spwndActivePopup, WFVISIBLE)));
|
|
|
|
|
|
/*
|
|
* If a hierarchical popup has been destroyed, this is a
|
|
* good time to flush ppmDelayedFree
|
|
*/
|
|
if (ppopupmenu->fFlushDelayedFree) {
|
|
MNFlushDestroyedPopups (ppopupmenu, FALSE);
|
|
ppopupmenu->fFlushDelayedFree = FALSE;
|
|
}
|
|
|
|
/*
|
|
* We need to send the WM_ENTERIDLE message only the first time
|
|
* there are no messages for us to process. Subsequent times we
|
|
* need to yield via WaitMessage(). This will allow other tasks to
|
|
* get some time while we have a menu down.
|
|
*/
|
|
if (fSendIdle) {
|
|
if (ppopupmenu->spwndNotify != NULL) {
|
|
ThreadLockAlways(ppopupmenu->spwndNotify, &tlpwndT);
|
|
xxxSendMessage(ppopupmenu->spwndNotify, WM_ENTERIDLE, MSGF_MENU,
|
|
(LPARAM)HW(ppopupmenu->spwndActivePopup));
|
|
ThreadUnlock(&tlpwndT);
|
|
}
|
|
fSendIdle = FALSE;
|
|
} else {
|
|
/*
|
|
* If we're animating, sleep only 1 ms to reduce the chance
|
|
* of jerky animation.
|
|
* When not animating, this is the same as a xxxWaitMessage call
|
|
*/
|
|
#ifdef MESSAGE_PUMP_HOOK
|
|
xxxWaitMessageEx(QS_ALLINPUT | QS_EVENT, pMenuState->hdcWndAni != NULL);
|
|
#else
|
|
xxxSleepThread(QS_ALLINPUT | QS_EVENT, (pMenuState->hdcWndAni != NULL), TRUE);
|
|
#endif
|
|
}
|
|
|
|
} /* if (PeekMessage(&msg, NULL, 0, 0, PM_NOYIELD)) else */
|
|
|
|
} /* end while (fInsideMenuLoop) */
|
|
|
|
|
|
|
|
ExitMenuLoop:
|
|
pMenuState->fInsideMenuLoop = FALSE;
|
|
pMenuState->fModelessMenu = FALSE;
|
|
|
|
/*
|
|
* Make sure that the menu has been ended/canceled
|
|
*/
|
|
xxxEndMenuLoop (pMenuState, ppopupmenu);
|
|
|
|
xxxMNReleaseCapture();
|
|
|
|
// Throw in an extra peek here when we exit the menu loop to ensure that the input queue
|
|
// for this thread gets unlocked if there is no more input left for him.
|
|
xxxPeekMessage(&msg, NULL, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_NOYIELD | PM_NOREMOVE);
|
|
return(pMenuState->cmdLast);
|
|
} /* xxxMenuLoop() */
|