/**************************** Module Header ********************************\ * Module Name: mnsys.c * * Copyright (c) 1985 - 1999, Microsoft Corporation * * System Menu Routines * * History: * 10-10-90 JimA Cleanup. * 03-18-91 IanJa Window revalidation added (none required) \***************************************************************************/ #include "precomp.h" #pragma hdrstop void _SetCloseDefault(PMENU pSubMenu); PWND FindFakeMDIChild(PWND pwndParent); /***************************************************************************\ * LoadSysDesktopMenu * * Loads and locks a desktop system menu. Since we have to call the client * to load the menu, while thread 1 is loading the menu, thread 2 * might grab the critical section, check pdesk->spmenu* and decide that * the menu needs to be loaded. Hence we could load the menu more than once. * this function handles that case to avoid leaking menus. * * 10/24/97 Gerardob Created \***************************************************************************/ #ifdef LAME_BUTTON PMENU xxxLoadSysDesktopMenu (PMENU * ppmenu, UINT uMenuId, PWND pwnd) #else PMENU xxxLoadSysDesktopMenu (PMENU * ppmenu, UINT uMenuId) #endif // LAME_BUTTON { PMENU pmenu; /* * This should only be called when the menu hasn't been loaded */ UserAssert(*ppmenu == NULL); #ifdef LAME_BUTTON pmenu = xxxLoadSysMenu(uMenuId, pwnd); #else pmenu = xxxLoadSysMenu(uMenuId); #endif // LAME_BUTTON if (pmenu == NULL) { return NULL; } /* * If someone beat us loading the menu, destroy this one * and return the one already loaded */ if (*ppmenu != NULL) { UserAssert(TestMF(*ppmenu, MFSYSMENU)); RIPMSG1(RIP_WARNING, "LoadSysDesktopMenu: Menu loaded during callback. ppmenu:%#p", ppmenu); _DestroyMenu(pmenu); return *ppmenu; } /* * Mark it, lock it and done */ SetMF(pmenu, MFSYSMENU); LockDesktopMenu(ppmenu, pmenu); return pmenu; } /***************************************************************************\ * Lock/UnlockDesktopMenu * * These functions lock/unlock a pmenu into a desktop structure (spmenuSys or * spmenuDialogSys) and mark/clear it as such. * We mark these menus so we can identify them quickly on single bit test. * We also don't want any one to modify these menus or any submenu. * * Note that this assumes that there is only one submenu. If more are added, * these functions have to be fixed accordingly. * * 08/18/97 Gerardob Created \***************************************************************************/ PVOID LockDesktopMenu(PMENU * ppmenu, PMENU pmenu) { PMENU pSubMenu; PTHREADINFO ptiDesktop; /* * We only load desktop sys menus once. */ UserAssert(*ppmenu == NULL); if (pmenu == NULL) { return NULL; } SetMF(pmenu, MFDESKTOP); /* * This is awful but this is the real owner of this object. We used to set it * to NULL but that was forcing us to handle the NULL owner all over the place */ ptiDesktop = PtiCurrent()->rpdesk->rpwinstaParent->pTerm->ptiDesktop; HMChangeOwnerProcess(pmenu, ptiDesktop); pSubMenu = pmenu->rgItems->spSubMenu; UserAssert(pSubMenu != NULL); SetMF(pSubMenu, MFDESKTOP); HMChangeOwnerProcess(pSubMenu, ptiDesktop); #if DBG { /* * Assert that there are no other submenus that would need to be * marked as MFDESKTOP. */ PITEM pitem; UINT uItems; UserAssert(pmenu->cItems == 1); pitem = pSubMenu->rgItems; uItems = pSubMenu->cItems; while (uItems--) { UserAssert(pitem->spSubMenu == NULL); pitem++; } } #endif return Lock(ppmenu, pmenu); } PVOID UnlockDesktopMenu(PMENU * ppmenu) { UserAssert(*ppmenu != NULL); UserAssert(TestMF(*ppmenu, MFDESKTOP)); ClearMF(*ppmenu, MFDESKTOP); UserAssert(TestMF((*ppmenu)->rgItems->spSubMenu, MFDESKTOP)); ClearMF((*ppmenu)->rgItems->spSubMenu, MFDESKTOP); return Unlock(ppmenu); } /***************************************************************************\ * GetSysMenuHandle * * Returns a handle to the system menu of the given window. NULL if * the window doesn't have a system menu. * * History: \***************************************************************************/ PMENU xxxGetSysMenuHandle( PWND pwnd) { PMENU pMenu; CheckLock(pwnd); if (TestWF(pwnd, WFSYSMENU)) { pMenu = pwnd->spmenuSys; /* * If the window doesn't have a System Menu, use the default one. */ if (pMenu == NULL) { /* * Grab the menu from the desktop. If the desktop menu * has not been loaded and this is not a system thread, * load it now. Callbacks cannot be made from a system * thread or when a thread is in cleanup. */ pMenu = pwnd->head.rpdesk->spmenuSys; /* * Do not do callbacks if the thread is exiting. We ran into this when * destroying a thread's window and the window it was promoting to * foreground was a hard error popup. */ if (pMenu == NULL && !(PtiCurrent()->TIF_flags & (TIF_SYSTEMTHREAD | TIF_INCLEANUP))) { #ifdef LAME_BUTTON pMenu = xxxLoadSysDesktopMenu (&pwnd->head.rpdesk->spmenuSys, ID_SYSMENU, pwnd); #else pMenu = xxxLoadSysDesktopMenu (&pwnd->head.rpdesk->spmenuSys, ID_SYSMENU); #endif // LAME_BUTTON } } } else { pMenu = NULL; } return pMenu; } /***************************************************************************\ * * GetSysMenu() * * Sets up the system menu first, then returns it. * \***************************************************************************/ PMENU xxxGetSysMenu(PWND pwnd, BOOL fSubMenu) { PMENU pMenu; CheckLock(pwnd); xxxSetSysMenu(pwnd); if ((pMenu = xxxGetSysMenuHandle(pwnd)) != NULL) { if (fSubMenu) pMenu = _GetSubMenu(pMenu, 0); } return(pMenu); } /***************************************************************************\ * IsSmallerThanScreen * \***************************************************************************/ BOOL IsSmallerThanScreen(PWND pwnd) { int dxMax, dyMax; PMONITOR pMonitor; pMonitor = _MonitorFromWindow(pwnd, MONITOR_DEFAULTTOPRIMARY); dxMax = pMonitor->rcWork.right - pMonitor->rcWork.left; dyMax = pMonitor->rcWork.bottom - pMonitor->rcWork.top; if ((pwnd->rcWindow.right - pwnd->rcWindow.left < dxMax) || (pwnd->rcWindow.bottom - pwnd->rcWindow.top < dyMax)) { return TRUE; } return FALSE; } /***************************************************************************\ * SetSysMenu * * ! * * History: \***************************************************************************/ void xxxSetSysMenu( PWND pwnd) { PMENU pMenu; UINT wSize; UINT wMinimize; UINT wMaximize; UINT wMove; UINT wRestore; UINT wDefault; BOOL fFramedDialogBox; TL tlmenu; CheckLock(pwnd); /* * Get the handle of the current system menu. */ if ((pMenu = xxxGetSysMenuHandle(pwnd)) != NULL) { pMenu = _GetSubMenu(pMenu, 0); if (!pMenu) return; ThreadLockAlways(pMenu, &tlmenu); /* * System modal window: no size, icon, zoom, or move. */ // No system modal windows on NT. // wSize = wMaximize = wMinimize = wMove = // (UINT)((_GetSysModalWindow() == NULL) || hTaskLockInput ? 0: MFS_GRAYED); wSize = wMaximize = wMinimize = wMove = 0; wRestore = MFS_GRAYED; // // Default menu command is close. // wDefault = SC_CLOSE; /* * Minimized exceptions: no minimize, restore. */ // we need to reverse these because VB has a "special" window // that is both minimized but without a minbox. if (TestWF(pwnd, WFMINIMIZED)) { wRestore = 0; wMinimize = MFS_GRAYED; wSize = MFS_GRAYED; wDefault = SC_RESTORE; if (IsTrayWindow(pwnd)) wMove = MFS_GRAYED; } else if (!TestWF(pwnd, WFMINBOX)) wMinimize = MFS_GRAYED; /* * Maximized exceptions: no maximize, restore. */ if (!TestWF(pwnd, WFMAXBOX)) wMaximize = MFS_GRAYED; else if (TestWF(pwnd, WFMAXIMIZED)) { wRestore = 0; /* * If the window is maximized but it isn't larger than the * screen, we allow the user to move the window around the * desktop (but we don't allow resizing). */ wMove = MFS_GRAYED; if (!TestWF(pwnd, WFCHILD)) { if (IsSmallerThanScreen(pwnd)) { wMove = 0; } } wSize = MFS_GRAYED; wMaximize = MFS_GRAYED; } if (!TestWF(pwnd, WFSIZEBOX)) wSize = MFS_GRAYED; /* * Are we dealing with a framed dialog box with a sys menu? * Dialogs with min/max/size boxes get a regular system menu * (as opposed to the dialog menu) */ fFramedDialogBox = (((TestWF(pwnd, WFBORDERMASK) == (BYTE)LOBYTE(WFDLGFRAME)) || (TestWF(pwnd, WEFDLGMODALFRAME))) && !TestWF(pwnd, WFSIZEBOX | WFMINBOX | WFMAXBOX)); if (!fFramedDialogBox) { xxxEnableMenuItem(pMenu, (UINT)SC_SIZE, wSize); if (!TestWF(pwnd, WEFTOOLWINDOW)) { xxxEnableMenuItem(pMenu, (UINT)SC_MINIMIZE, wMinimize); xxxEnableMenuItem(pMenu, (UINT)SC_MAXIMIZE, wMaximize); xxxEnableMenuItem(pMenu, (UINT)SC_RESTORE, wRestore); } } xxxEnableMenuItem(pMenu, (UINT)SC_MOVE, wMove); #if DBG /* * Assert that nobody managed to change the desktop menus. */ if (TestMF(pMenu, MFSYSMENU)) { PITEM pItem = MNLookUpItem(pMenu, SC_CLOSE, FALSE, NULL); UserAssert((pItem != NULL) && !TestMFS(pItem, MFS_GRAYED)); } #endif if (wDefault == SC_CLOSE) _SetCloseDefault(pMenu); else _SetMenuDefaultItem(pMenu, wDefault, MF_BYCOMMAND); ThreadUnlock(&tlmenu); } } /***************************************************************************\ * GetSystemMenu * * ! * * History: \***************************************************************************/ PMENU xxxGetSystemMenu( PWND pwnd, BOOL fRevert) { PMENU pmenu; CheckLock(pwnd); /* * Should we start with a fresh copy? */ pmenu = pwnd->spmenuSys; if (fRevert) { /* * Destroy the old system menu. */ if ((pmenu != NULL) && !TestMF(pmenu, MFSYSMENU)) { if (UnlockWndMenu(pwnd, &pwnd->spmenuSys)) { _DestroyMenu(pmenu); } } } else { /* * Do we need to load a new system menu? */ if (((pmenu == NULL) || TestMF(pmenu, MFSYSMENU)) && TestWF(pwnd, WFSYSMENU)) { PPOPUPMENU pGlobalPopupMenu; UINT uMenuId = (pwnd->spmenuSys == NULL ? ID_SYSMENU : ID_DIALOGSYSMENU); #ifdef LAME_BUTTON pmenu = xxxLoadSysMenu(uMenuId, pwnd); #else pmenu = xxxLoadSysMenu(uMenuId); #endif // LAME_BUTTON if (pmenu == NULL) { RIPMSG1(RIP_WARNING, "_GetSystemMenu: xxxLoadSysMenu Failed. pwnd:%#p", pwnd); } LockWndMenu(pwnd, &pwnd->spmenuSys, pmenu); pmenu = pwnd->spmenuSys; pGlobalPopupMenu = GetpGlobalPopupMenu(pwnd); if ((pGlobalPopupMenu != NULL) && !pGlobalPopupMenu->fIsTrackPopup && (pGlobalPopupMenu->spwndPopupMenu == pwnd)) { UserAssert(pGlobalPopupMenu->spwndNotify == pwnd); if (pGlobalPopupMenu->fIsSysMenu) { Lock(&pGlobalPopupMenu->spmenu, pmenu); } else { Lock(&pGlobalPopupMenu->spmenuAlternate, pmenu); } } } } /* * Return the handle to the system menu. */ if (pwnd->spmenuSys != NULL) { /* * The app is probably going to modify this menu and then we'll need to * redraw the caption buttons. Hence we need to store the window pointer * in this pmenu or we won't be able to know what window to repaint. * The bogus thing is that we cannot call LockWndMenu here because this is * not the actual pmenuSys. */ pmenu = _GetSubMenu(pwnd->spmenuSys, 0); if (pmenu) { SetMF(pmenu, MFAPPSYSMENU); Lock(&pmenu->spwndNotify, pwnd); } return pmenu; } return NULL; } /***************************************************************************\ * MenuItemState * * Sets the menu item flags identified by wMask to the states identified * by wFlags. * * History: * 10-11-90 JimA Translated from ASM \***************************************************************************/ DWORD MenuItemState( PMENU pMenu, UINT wCmd, DWORD wFlags, DWORD wMask, PMENU *ppMenu) { PITEM pItem; DWORD wRet; /* * Get a pointer the the menu item */ if ((pItem = MNLookUpItem(pMenu, wCmd, (BOOL) (wFlags & MF_BYPOSITION), ppMenu)) == NULL) return (DWORD)-1; /* * Return previous state */ wRet = pItem->fState & wMask; /* * Set new state */ pItem->fState ^= ((wRet ^ wFlags) & wMask); return wRet; } /***************************************************************************\ * EnableMenuItem * * Enable, disable or gray a menu item. * * History: * 10-11-90 JimA Translated from ASM \***************************************************************************/ DWORD xxxEnableMenuItem( PMENU pMenu, UINT wIDEnableItem, UINT wEnable) { DWORD dres; PMENU pRealMenu; PPOPUPMENU ppopup; CheckLock(pMenu); dres = MenuItemState(pMenu, wIDEnableItem, wEnable, MFS_GRAYED, &pRealMenu); /* * If enabling/disabling a system menu item, redraw the caption buttons */ if (TestMF(pMenu, MFAPPSYSMENU) && (pMenu->spwndNotify != NULL) && (wEnable != dres)) { TL tlpwnd; switch (wIDEnableItem) { case SC_SIZE: case SC_MOVE: case SC_MINIMIZE: case SC_MAXIMIZE: case SC_CLOSE: case SC_RESTORE: ThreadLock(pMenu->spwndNotify, &tlpwnd); xxxRedrawTitle(pMenu->spwndNotify, DC_BUTTONS); ThreadUnlock(&tlpwnd); } } /* 367162: If the menu is already being displayed we need to redraw it */ if(pRealMenu && (ppopup = MNGetPopupFromMenu(pRealMenu, NULL))){ xxxMNUpdateShownMenu(ppopup, NULL, MNUS_DEFAULT); } return dres; } /***************************************************************************\ * CheckMenuItem (API) * * Check or un-check a popup menu item. * * History: * 10-11-90 JimA Translated from ASM \***************************************************************************/ DWORD _CheckMenuItem( PMENU pMenu, UINT wIDCheckItem, UINT wCheck) { return MenuItemState(pMenu, wIDCheckItem, wCheck, (UINT)MF_CHECKED, NULL); } /***************************************************************************\ * * SetMenuDefaultItem() - * * Sets the default item in the menu, by command or by position based on the * fByPosition flag. * We unset all the other items as the default, then set the given one. * * The return value is TRUE if the given item was set as default, FALSE * if not. * \***************************************************************************/ BOOL _SetMenuDefaultItem(PMENU pMenu, UINT wID, BOOL fByPosition) { UINT iItem; UINT cItems; PITEM pItem; PITEM pItemFound; PMENU pMenuFound; // // We need to check if wId actually exists on this menu. 0xFFFF means // clear all default items. // if (wID != MFMWFP_NOITEM) { pItemFound = MNLookUpItem(pMenu, wID, fByPosition, &pMenuFound); // item must be on same menu and can't be a separator if ((pItemFound == NULL) || (pMenuFound != pMenu) || TestMFT(pItemFound, MFT_SEPARATOR)) return(FALSE); } else pItemFound = NULL; pItem = pMenu->rgItems; cItems = pMenu->cItems; // Walk the menu list, clearing MFS_DEFAULT from all other items, and // setting MFS_DEFAULT on the requested one. for (iItem = 0; iItem < cItems; iItem++, pItem++) { // // Note we don't change the state of lpItemFound if it exists. This // is so that below, where we try to set the default, we can tell // if we need to recalculate the underline. // if (TestMFS(pItem, MFS_DEFAULT) && (pItem != pItemFound)) { // // We are changing the default item. As such, it will be drawn // with a different font than the one used to calculate it, if // the menu has already been drawn once. We need to ensure // that the underline gets drawn in the right place the next // time the menu comes up. Cause it to recalculate. // // We do NOT do this if the item // (a) isn't default--otherwise we'll recalculate the // underline for every system menu item every time we go into // menu mode because sysmenu init will call SetMenuDefaultItem. // (b) isn't the item we're going to set as the default. // That way we don't recalculate the underline when the item // isn't changing state. // ClearMFS(pItem, MFS_DEFAULT); pItem->ulX = UNDERLINE_RECALC; pItem->ulWidth = 0; } } if (wID != MFMWFP_NOITEM) { if (!TestMFS(pItemFound, MFS_DEFAULT)) { // // We are changing from non-default to default. Clear out // the underline info. If the menu has never painted, this // won't do anything. But it matters a lot if it has. // SetMFS(pItemFound, MFS_DEFAULT); pItemFound->ulX = UNDERLINE_RECALC; pItemFound->ulWidth = 0; } } return(TRUE); } // -------------------------------------------------------------------------- // // SetCloseDefault() // // Tries to find a close item in the first level of menu items. Looks // for SC_CLOSE, then a couple other IDs. We'd rather not do lstrstri's // for "Close", which is slow. // // -------------------------------------------------------------------------- void _SetCloseDefault(PMENU pSubMenu) { if (!_SetMenuDefaultItem(pSubMenu, SC_CLOSE, MF_BYCOMMAND)) { // // Let's try a couple other values. // * Project -- 0x7000 less // * FoxPro -- 0xC070 // if (!_SetMenuDefaultItem(pSubMenu, SC_CLOSE - 0x7000, MF_BYCOMMAND)) _SetMenuDefaultItem(pSubMenu, 0xC070, MF_BYCOMMAND); } } // -------------------------------------------------------------------------- // // FindFakeMDIChild() // // Attempts to find first child visible child window in the zorder that // has a system menu or is maxed. We can't check for an exact system // menu match because several apps make their own copy of the sys menu. // // -------------------------------------------------------------------------- PWND FindFakeMDIChild(PWND pwnd) { PWND pwndReturn; // Skip invisible windows and their descendants if (!TestWF(pwnd, WFVISIBLE)) return(NULL); // Did we hit pay dirt? if (TestWF(pwnd, WFCHILD) && (TestWF(pwnd, WFMAXIMIZED) || (pwnd->spmenuSys))) return(pwnd); // Check our children for (pwnd = pwnd->spwndChild; pwnd; pwnd = pwnd->spwndNext) { pwndReturn = FindFakeMDIChild(pwnd); if (pwndReturn) return(pwndReturn); } return(NULL); } // -------------------------------------------------------------------------- // // SetupFakeMDIAppStuff() // // For apps that mess around with their own MDI (Excel, Word, Project, // Quattro Pro), we want to make them a little more Chicago friendly. // Namely we: // // (1) Set the default menu item to SC_CLOSE if there isn't one (this // won't help FoxPro, but they do so much wrong stuff it doesn't // really matter). // That way double-clicks will still work. // // (2) Get the right small icon. // // The way we do this is to go find the child window of the menu bar parent // who has a system menu that is this one. // // If the system menu is the standard one, then we can't do (2). // // -------------------------------------------------------------------------- void SetupFakeMDIAppStuff(PMENU lpMenu, PITEM lpItem) { PMENU pSubMenu; PWND pwndParent; PWND pwndChild; if (!(pSubMenu = lpItem->spSubMenu)) return; pwndParent = lpMenu->spwndNotify; // // Set up the default menu item. Project and FoxPro renumber their // IDs so we do some special stuff for them, among others. // if (!TestWF(pwndParent, WFWIN40COMPAT)) { if (_GetMenuDefaultItem(pSubMenu, TRUE, GMDI_USEDISABLED) == -1L) _SetCloseDefault(pSubMenu); } // // Don't touch the HIWORD if we don't find an HWND. That way apps // like Excel which have starting-up maxed children can benefit a little. // The first time the menu bar is redrawn, the child isn't visible/ // around (they add the item too early). But if it redraws later, or // you max a child, the icon will kick in. // if (pwndChild = FindFakeMDIChild(pwndParent)) { lpItem->dwItemData = (ULONG_PTR)HWq(pwndChild); // lpItem->dwTypeData = MAKELONG(LOWORD(lpItem->dwTypeData), HW16(hwndChild)); } }