// Copyright (c) 1996-1999 Microsoft Corporation // -------------------------------------------------------------------------- // // MENU.CPP // // Menu class, for system menus and app menus. // // There are four classes here. // CMenu is the class that knows how to deal with menu bar objects. These // have children that are CMenuItem objects, or just children (rare - // this is when you have a command right on the menu bar). // CMenuItem is something that when you click on it opens a popup. // It has 1 child that is a CMenuPopupFrame. // CMenuPopupFrame is the HWND that pops up when you click on a CMenuItem. It // has 1 child, a CMenuPopup. // CMenuPopup objects represent the client area of a CMenuPopupFrame HWND. // It has children that are menu items (little m, little i), separators, and // CMenuItems (when you have cascading menus). // // Issues that came up during design/implementation: // (1) How do we select/focus menu items while in menu mode? // (2) How do we choose an item (default action)? // For menu bars, we use SendInput to send Alt+Shortcut key to // open or execute an item or command. Send just Alt to close // an item that is already open. // (3) How do we handle popup menus? // As discussed above, we treat them very strangely. There are ways // to get the children in a popup whether it is visible or not. // (4) What about "system menu" popups on tray? // // (5) In general, what about "context menus"? This may need to be // exposed by the app itself. We can't do everything! // // History: // written by Laura Butler, early 1996 // complete re-write by Steve Donie, August 1996-January 1997 // doDefaultAction changed to use keypresses rather than mouse clicks 3-97 // -------------------------------------------------------------------------- #include "oleacc_p.h" #include "default.h" #include "classmap.h" #include "ctors.h" #include "window.h" #include "client.h" #include "menu.h" #include "propmgr_util.h" #define MI_NONE -1 #ifndef MFS_HOTTRACK #define MFS_HOTTRACK 0x00000100L #endif // !MFS_HOTTRACK // -------------------------------------------------------------------------- // prototypes for local functions // -------------------------------------------------------------------------- HWND GetSubMenuWindow (HMENU hSubMenuToFind); long FindItemIDThatOwnsThisMenu (HMENU hMenuOwned,HWND* phwndOwner, BOOL* pfPopup,BOOL* pfSysMenu); STDAPI WindowFromAccessibleObjectEx(IAccessible* pacc, HWND* phwnd); BOOL MyGetMenuString( IAccessible * pTheObj, HWND hwnd, HMENU hMenu, long id, LPTSTR lpszBuf, UINT cchMax, BOOL fAllowGenerated ); BOOL TryMSAAMenuHack( IAccessible * pTheObj, HWND hWnd, DWORD_PTR dwItemData, LPTSTR lpszBuf, UINT cchMax ); BOOL GetShellOwnerDrawMenu( HWND hwnd, DWORD_PTR dwItemData, LPTSTR lpszBuf, UINT cchMax ); UINT GetMDIButtonIndex( HMENU hMenu, DWORD idPos ); BOOL GetMDIMenuDescriptionString( HMENU hMenu, DWORD idPos, BSTR * pbstr ); BOOL GetMDIMenuString( HWND hwnd, HMENU hMenu, DWORD idPos, LPTSTR lpszBuf, UINT cchMax ); HMENU MyGetSystemMenu( HWND hwnd ); HRESULT GetMenuItemName( IAccessible * pTheObj, HWND hwnd, HMENU hMenu, LONG id, BSTR * pszName ); TCHAR GetMenuItemHotkey( IAccessible * pTheObj, HWND hwnd, HMENU hMenu, LONG id, DWORD fOptions ); HRESULT GetMenuItemShortcut( IAccessible * pTheObj, HWND hwnd, HMENU hMenu, LONG id, BOOL fIsMenuBar, BSTR * pszShortcut ); enum { GMIH_ALLOW_INITIAL = 0x01, GMIH_ALLOW_SYS_SPACE = 0x02 }; // -------------------------------------------------------------------------- // // CreateSysMenuBarObject() // // EXTERNAL for CreateStdAcessibleObject // // Parameters: // hwnd IN window handle of the window that owns this menu // idChildCur IN id of the current child. Will be 0 when creating the // system menu bar and application menu bar. Will be the // id of a child when calling CMenu::Clone() // riid IN the id of the interface asked for // ppvMenu OUT ppvMenu holds the indirect pointer to the menu // object. // // Return Value: // S_OK if the interface is supported, E_NOINTERFACE if not, // E_OUTOFMEMORY if not enough memory to create the menu object, // E_FAIL if the hwnd is invalid. // // -------------------------------------------------------------------------- HRESULT CreateSysMenuBarObject(HWND hwnd, long idObject, REFIID riid, void** ppvMenu) { UNUSED(idObject); if (!IsWindow(hwnd)) return(E_FAIL); return(CreateMenuBar(hwnd, TRUE, 0, riid, ppvMenu)); } // -------------------------------------------------------------------------- // // CreateMenuBarObject() // // EXTERNAL for CreateStdAcessibleObject // // Parameters: // hwnd IN window handle of the window that owns this menu // idChildCur IN id of the current child. Will be 0 when creating the // system menu bar and application menu bar. Will be the // id of a child when calling CMenu::Clone() // riid IN the id of the interface asked for (like IAccessible, // IEnumVARIANT,IDispatch...) // ppvMenu OUT ppvMenu is where QueryInterface will return the // indirect pointer to the menu object (caller casts // this to be a pointer to the interface they asked for) // // Return Value: // S_OK if the interface is supported, E_NOINTERFACE if not, // E_OUTOFMEMORY if not enough memory to create the menu object, // E_FAIL if the hwnd is invalid. // // -------------------------------------------------------------------------- HRESULT CreateMenuBarObject(HWND hwnd, long idObject, REFIID riid, void** ppvMenu) { UNUSED(idObject); if (!IsWindow(hwnd)) return(E_FAIL); return(CreateMenuBar(hwnd, FALSE, 0, riid, ppvMenu)); } // -------------------------------------------------------------------------- // // CreateMenuBar() // // Parameters: // hwnd IN window handle of the window that owns this menu // fSysMenu IN true if this is a system menu, false if app menu // idChildCur IN id of the current child. Will be 0 when creating the // system menu bar and application menu bar. Will be the // id of a child when calling CMenu::Clone() // riid IN the id of the interface asked for // ppvMenu OUT ppvMenu is where QueryInterface will return the // indirect pointer to the menu object // // Return Value: // S_OK if the interface is supported, E_NOINTERFACE if not, // E_OUTOFMEMORY if not enough memory to create the menu object. // // Called By: // CreateMenuBarObject and CMenu::Clone // -------------------------------------------------------------------------- HRESULT CreateMenuBar(HWND hwnd, BOOL fSysMenu, long idChildCur, REFIID riid, void** ppvMenu) { HRESULT hr; CMenu* pmenu; InitPv(ppvMenu); pmenu = new CMenu(hwnd, fSysMenu, idChildCur); if (!pmenu) return(E_OUTOFMEMORY); hr = pmenu->QueryInterface(riid, ppvMenu); if (!SUCCEEDED(hr)) delete pmenu; return(hr); } // -------------------------------------------------------------------------- // // CMenu::CMenu() // // Constructor for the CMenu class. Initializes the member variables with // the passed in parameters. It is only called by CreateMenuBar. // // -------------------------------------------------------------------------- CMenu::CMenu(HWND hwnd, BOOL fSysMenu, long idChildCur) : CAccessible( CLASS_MenuObject ) { m_hwnd = hwnd; m_fSysMenu = fSysMenu; m_idChildCur = idChildCur; m_hMenu = NULL; // m_hMenu is filled in by SetupChildren() // m_cChildren is filled in by SetupChildren() } // -------------------------------------------------------------------------- // // CMenu::SetupChildren() // // This uses the object's window handle to get the handle to the appropriate // menu (hmenu type menu handle) and the count of the children in that menu. // It uses GetMenuBarInfo, a private function in USER, to do this. // These values are kept as member variables of the CMenu object. // // -------------------------------------------------------------------------- void CMenu::SetupChildren(void) { MENUBARINFO mbi; if (!MyGetMenuBarInfo(m_hwnd, (m_fSysMenu ? OBJID_SYSMENU : OBJID_MENU), 0, &mbi)) { m_hMenu = NULL; } else { m_hMenu = mbi.hMenu; } if (!m_hMenu) m_cChildren = 0; else { m_cChildren = GetMenuItemCount(m_hMenu); if( m_cChildren == -1 ) { // Paranoia in case we get an invalid HMENU m_cChildren = 0; } } } // -------------------------------------------------------------------------- // // CMenu::get_accChild() // // What we want this do do is return (in ppdisp) an IDispatch pointer to // the child specified by varChild. The children of a CMenu are either // menu commands (rare to have a command on the menu bar) and CMenuItems. // // -------------------------------------------------------------------------- STDMETHODIMP CMenu::get_accChild(VARIANT varChild, IDispatch** ppdisp) { HMENU hSubMenu; InitPv(ppdisp); if (!ValidateChild(&varChild)) return (E_INVALIDARG); if (varChild.lVal == CHILDID_SELF) return(E_INVALIDARG); Assert (m_hMenu); hSubMenu = GetSubMenu(m_hMenu, varChild.lVal-1); if (hSubMenu == NULL) { // This returns false - for commands on the menu bar, we do not create a child // object - the parent is able to answer all the questions. return (S_FALSE); } return(CreateMenuItem((IAccessible*)this, m_hwnd, m_hMenu,hSubMenu, varChild.lVal, 0, TRUE, IID_IDispatch, (void**)ppdisp)); } // -------------------------------------------------------------------------- // // CMenu::get_accName() // // Pass in a VARIANT with type VT_I4 and lVal equal to the 1-based position // of the item you want the name for. Pass in a pointer to a string and the // string will be filled with the name. // // -------------------------------------------------------------------------- STDMETHODIMP CMenu::get_accName(VARIANT varChild, BSTR* pszName) { InitPv(pszName); if (!ValidateChild(&varChild)) return(E_INVALIDARG); if (varChild.lVal != CHILDID_SELF) { return GetMenuItemName( this, m_hwnd, m_hMenu, varChild.lVal, pszName ); } if (m_fSysMenu) return HrCreateString(STR_SYSMENU_NAME, pszName); // in English = "System" else return HrCreateString(STR_MENUBAR_NAME, pszName); // in English, this is "Application" } // -------------------------------------------------------------------------- // // CMenu::get_accDescription() // // get a string with the description of this menu item. For CMenu, this // is something like "contains commands to manipulate the window" or // "Contains commands to manipulate the current view or document" // // -------------------------------------------------------------------------- STDMETHODIMP CMenu::get_accDescription(VARIANT varChild, BSTR* pszDesc) { InitPv(pszDesc); if (! ValidateChild(&varChild)) return E_INVALIDARG; // Check if they are asking about the menu bar itself or a child. // If asking about a child, return S_FALSE, because we don't have // descriptions for items, just the system and app menu bars. // if (varChild.lVal != CHILDID_SELF) { if( GetMDIMenuDescriptionString( m_hMenu, varChild.lVal - 1, pszDesc ) ) { return S_OK; } return S_FALSE; } else if (m_fSysMenu) { return HrCreateString(STR_SYSMENUBAR_DESCRIPTION, pszDesc); } else { return HrCreateString(STR_MENUBAR_DESCRIPTION, pszDesc); } } // -------------------------------------------------------------------------- // // CMenu::get_accRole() // // get the role - this is either menu item or menu bar // -------------------------------------------------------------------------- STDMETHODIMP CMenu::get_accRole(VARIANT varChild, VARIANT* pvarRole) { InitPvar(pvarRole); if (!ValidateChild(&varChild)) return(E_INVALIDARG); pvarRole->vt = VT_I4; if (varChild.lVal != CHILDID_SELF) { if( GetMDIButtonIndex( m_hMenu, varChild.lVal - 1 ) != 0 ) { // Special case for MDI child buttons - they are actually implemented // as menu items, but appear as buttons. pvarRole->lVal = ROLE_SYSTEM_PUSHBUTTON; } else { pvarRole->lVal = ROLE_SYSTEM_MENUITEM; } } else pvarRole->lVal = ROLE_SYSTEM_MENUBAR; return(S_OK); } // -------------------------------------------------------------------------- // // CMenu::get_accState() // // get the state of the child specified. returned in a variant VT_I4 // -------------------------------------------------------------------------- STDMETHODIMP CMenu::get_accState(VARIANT varChild, VARIANT* pvarState) { MENUBARINFO mbi; InitPvar(pvarState); if (!ValidateChild(&varChild)) return(E_INVALIDARG); pvarState->vt = VT_I4; pvarState->lVal = 0; if (!m_hMenu || !MyGetMenuBarInfo(m_hwnd, (m_fSysMenu ? OBJID_SYSMENU : OBJID_MENU), varChild.lVal, &mbi)) { pvarState->lVal |= STATE_SYSTEM_INVISIBLE; } else if( varChild.lVal && GetMDIButtonIndex( m_hMenu, varChild.lVal - 1 ) != 0 ) { // For MDI button elements, just leave the state as normal, to be consistent // with the top-level restore/close/minimize buttons. // Do nothing here. } else { // Non-MDI-button menu items, or CHILDID_SELF... // smd 1-29-97 - change from OFFSCREEN to INVISIBLE if (IsRectEmpty(&mbi.rcBar)) pvarState->lVal |= STATE_SYSTEM_INVISIBLE; if (GetForegroundWindow() == m_hwnd) pvarState->lVal |= STATE_SYSTEM_FOCUSABLE; if (mbi.fFocused) pvarState->lVal |= STATE_SYSTEM_FOCUSED; if (varChild.lVal) { MENUITEMINFO mi; // // Get menu item flags. NOTE: Can't use GetMenuState(). It whacks // random stuff in for hierarchicals. // mi.cbSize = SIZEOF_MENUITEMINFO; mi.fMask = MIIM_STATE | MIIM_SUBMENU; if (!GetMenuItemInfo(m_hMenu, varChild.lVal-1, TRUE, &mi)) { pvarState->lVal |= STATE_SYSTEM_INVISIBLE; return(S_FALSE); } if (mi.fState & MFS_GRAYED) pvarState->lVal |= STATE_SYSTEM_UNAVAILABLE; if (mi.fState & MFS_CHECKED) pvarState->lVal |= STATE_SYSTEM_CHECKED; if (mi.fState & MFS_DEFAULT) pvarState->lVal |= STATE_SYSTEM_DEFAULT; if (mbi.fFocused) { pvarState->lVal |= STATE_SYSTEM_HOTTRACKED; if (mi.fState & MFS_HILITE) pvarState->lVal |= STATE_SYSTEM_FOCUSED; } if (mi.hSubMenu) pvarState->lVal |= STATE_SYSTEM_HASPOPUP; } } return(S_OK); } // -------------------------------------------------------------------------- // // CMenu::get_accKeyboardShortcut() // // returns a string with the menu shortcut to the child asked for // -------------------------------------------------------------------------- STDMETHODIMP CMenu::get_accKeyboardShortcut(VARIANT varChild, BSTR* pszShortcut) { InitPv(pszShortcut); if (!ValidateChild(&varChild)) return E_INVALIDARG; if (varChild.lVal == CHILDID_SELF) { if (!m_hMenu) return S_FALSE; if (m_fSysMenu) { // Alt+Space/Hyphen is the system menu... TCHAR szFormat[16]; LoadString(hinstResDll, STR_MENU_SHORTCUT_FORMAT, szFormat, ARRAYSIZE(szFormat)); TCHAR szKey[16]; LoadString(hinstResDll, ((GetWindowLong(m_hwnd, GWL_STYLE) & WS_CHILD) ? STR_CHILDSYSMENU_KEY : STR_SYSMENU_KEY), szKey, ARRAYSIZE(szKey)); TCHAR szHotKey[32]; szHotKey[ 0 ] = '\0'; wsprintf(szHotKey, szFormat, szKey); if (*szHotKey) { *pszShortcut = TCharSysAllocString(szHotKey); if (! *pszShortcut) return E_OUTOFMEMORY; return S_OK; } return S_FALSE; } else { // "Alt" is the menu bar return HrCreateString(STR_MENU_SHORTCUT, pszShortcut); } } else { // Get menu item shortcut - TRUE means use "Alt+" format. return GetMenuItemShortcut( this, m_hwnd, m_hMenu, varChild.lVal, TRUE, pszShortcut ); } } // -------------------------------------------------------------------------- // // CMenu::get_accFocus() // // This fills in pvarFocus with the ID of the child that has the focus. // So when say you just hit "Alt" (File is now highlighted) and then call // get_accFocus(), pvarFocus will have VT_I4 and lVal = 1. // // If we are not in menu mode, then we certainly don't have the focus. // -------------------------------------------------------------------------- STDMETHODIMP CMenu::get_accFocus(VARIANT* pvarFocus) { GUITHREADINFO GuiThreadInfo; MENUITEMINFO mii; int i; // set it to empty InitPvar(pvarFocus); // // Are we in menu mode? If not, nothing. // if (!MyGetGUIThreadInfo (NULL,&GuiThreadInfo)) return(S_FALSE); if (GuiThreadInfo.flags & GUI_INMENUMODE) { // do I have to loop through all of them to see which // one is hilited?? Looks like it... mii.cbSize = SIZEOF_MENUITEMINFO; mii.fMask = MIIM_STATE; SetupChildren(); for (i=0;i < m_cChildren;i++) { GetMenuItemInfo (m_hMenu,i,TRUE,&mii); if (mii.fState & MFS_HILITE) { pvarFocus->vt = VT_I4; pvarFocus->lVal = i+1; return (S_OK); } } // I don't think this should happen return(S_FALSE); } return(S_FALSE); } // -------------------------------------------------------------------------- // // CMenu::get_accDefaultAction() // // Menu bars have no defaults. However, items do. Hierarchical items // drop down/pop up their hierarchical. Non-hierarchical items execute // their command. // // doDefaultAction follows from this. It has to do whatever getDefaultAction // says it is going to do. We use keystrokes to do this for menu bars. // // -------------------------------------------------------------------------- STDMETHODIMP CMenu::get_accDefaultAction(VARIANT varChild, BSTR* pszDefA) { GUITHREADINFO gui; HMENU hSubMenu; InitPv(pszDefA); if (!ValidateChild(&varChild)) return(E_INVALIDARG); if (varChild.lVal == CHILDID_SELF) return(E_NOT_APPLICABLE); if( GetMDIButtonIndex( m_hMenu, varChild.lVal - 1 ) != 0 ) { // Special case for MDI child buttons - they are actually implemented // as menu items, but appear as buttons. return HrCreateString(STR_BUTTON_PUSH, pszDefA); } // Only if this window is active can we do the default. // There is a slight danger here that an Always On Top window // could be covering us, but this is small. // if (!MyGetGUIThreadInfo(0, &gui)) return(E_NOT_APPLICABLE); if (m_hwnd != gui.hwndActive) return(E_NOT_APPLICABLE); varChild.lVal--; // Is this item enabled? if (GetMenuState(m_hMenu, varChild.lVal, MF_BYPOSITION) & MFS_GRAYED) return(E_NOT_APPLICABLE); // Now check if this item has a submenu that is displayed. // If there is, the action is hide, if not, the action is show. // If it doesn't have a submenu, the action is execute. if (hSubMenu = GetSubMenu(m_hMenu, varChild.lVal)) { if (GetSubMenuWindow(hSubMenu)) return(HrCreateString(STR_DROPDOWN_HIDE, pszDefA)); else return(HrCreateString(STR_DROPDOWN_SHOW, pszDefA)); } else return(HrCreateString(STR_EXECUTE, pszDefA)); } // -------------------------------------------------------------------------- // // CMenu::accSelect() // // We only accept TAKE_FOCUS. What I wanted this to do is to just put the // app into menu mode (if it isn't already - more on this later) and then // select the item specified - don't open it or anything, just select it. // // But that was a pain in the butt, so no I just use doDefaultAction to // do the work. Maybe I'll fix it for 1.1 // // If we are already in menu mode, and a popup is open, then we should // close the popup(s) and select the item. If in menu mode and no popups // are up, just select the item. // // If the app is just setting focus to the menu bar itself, and not already // in menu mode, just put us into menu mode (automatically selects first // item). If we are already in menu mode, do nothing. // // I want to try to do all this without generating a whole mess of extra // events! // // -------------------------------------------------------------------------- STDMETHODIMP CMenu::accSelect(long flagsSel, VARIANT varChild) { LPARAM lParam; GUITHREADINFO GuiThreadInfo; if (!ValidateChild(&varChild) || !ValidateSelFlags(flagsSel)) return(E_INVALIDARG); if (flagsSel != SELFLAG_TAKEFOCUS) return(E_NOT_APPLICABLE); // if this window is not the active window, fail. MyGetGUIThreadInfo (NULL,&GuiThreadInfo); if (GuiThreadInfo.hwndActive != m_hwnd) return (E_NOT_APPLICABLE); #ifdef _DEBUG if (!m_hMenu) { //DBPRINTF (TEXT("null hmenu at 1\r\n")); Assert (m_hMenu); } #endif if (varChild.lVal == CHILDID_SELF) { if (!m_fSysMenu) lParam = NULL; else if (GetWindowLong(m_hwnd, GWL_STYLE) & WS_CHILD) lParam = MAKELONG('-', 0); else lParam = MAKELONG(' ', 0); PostMessage(m_hwnd, WM_SYSCOMMAND, SC_KEYMENU, lParam); return (S_OK); } else if (GetSubMenu(m_hMenu, varChild.lVal-1)) { // for version 1.0, I'll just do this. Safe, even though it's not 100% // what I want it to do. return (accDoDefaultAction (varChild)); } return (E_FAIL); } // -------------------------------------------------------------------------- // // CMenu::accLocation() // // get the location of the child. left,top,width,height // -------------------------------------------------------------------------- STDMETHODIMP CMenu::accLocation(long* pxLeft, long* pyTop, long* pcxWidth, long* pcyHeight, VARIANT varChild) { MENUBARINFO mbi; if (!ValidateChild(&varChild)) return(E_INVALIDARG); InitAccLocation(pxLeft, pyTop, pcxWidth, pcyHeight); if (!MyGetMenuBarInfo(m_hwnd, (m_fSysMenu ? OBJID_SYSMENU : OBJID_MENU), varChild.lVal, &mbi)) return(S_FALSE); *pcxWidth = mbi.rcBar.right - mbi.rcBar.left; *pcyHeight = mbi.rcBar.bottom - mbi.rcBar.top; *pxLeft = mbi.rcBar.left; *pyTop = mbi.rcBar.top; return(S_OK); } // -------------------------------------------------------------------------- // // CMenu::accHitTest() // // if the point is in a menu bar, return the child the point is over // -------------------------------------------------------------------------- STDMETHODIMP CMenu::accHitTest(long x, long y, VARIANT* pvarHit) { InitPvar(pvarHit); SetupChildren(); if (SendMessage(m_hwnd, WM_NCHITTEST, 0, MAKELONG(x, y)) == (m_fSysMenu ? HTSYSMENU : HTMENU)) { pvarHit->vt = VT_I4; pvarHit->lVal = 0; if (m_cChildren) { if (m_fSysMenu) pvarHit->lVal = 1; else { POINT pt; pt.x = x; pt.y = y; // MenuItemFromPoint conveniently returns -1 if we are not // over any menu item, so that gets returned as 0 (CHILDID_SELF) // while others get bumped by 1 to be 1..n. Cool! pvarHit->lVal = MenuItemFromPoint(m_hwnd, m_hMenu, pt) + 1; } if (pvarHit->lVal) { IDispatch* pdispChild; pdispChild = NULL; get_accChild(*pvarHit, &pdispChild); if (pdispChild) { pvarHit->vt = VT_DISPATCH; pvarHit->pdispVal = pdispChild; } } } return(S_OK); } return(S_FALSE); } // -------------------------------------------------------------------------- // // CMenu::accNavigate() // // -------------------------------------------------------------------------- STDMETHODIMP CMenu::accNavigate(long dwNavDir, VARIANT varStart, VARIANT* pvarEnd) { long lEnd = 0; HMENU hSubMenu; InitPvar(pvarEnd); if (!ValidateChild(&varStart) || !ValidateNavDir(dwNavDir, varStart.lVal)) return(E_INVALIDARG); if (dwNavDir == NAVDIR_FIRSTCHILD) dwNavDir = NAVDIR_NEXT; else if (dwNavDir == NAVDIR_LASTCHILD) { varStart.lVal = m_cChildren + 1; dwNavDir = NAVDIR_PREVIOUS; } else if (varStart.lVal == CHILDID_SELF) return(GetParentToNavigate((m_fSysMenu ? OBJID_SYSMENU : OBJID_MENU), m_hwnd, OBJID_WINDOW, dwNavDir, pvarEnd)); // when we get to here, navdir was either firstchild // or lastchild (now changed to either next or previous) // OR // we were starting from something other than the parent object switch (dwNavDir) { case NAVDIR_RIGHT: case NAVDIR_NEXT: lEnd = varStart.lVal + 1; if (lEnd > m_cChildren) lEnd = 0; break; case NAVDIR_LEFT: case NAVDIR_PREVIOUS: lEnd = varStart.lVal - 1; break; case NAVDIR_UP: case NAVDIR_DOWN: lEnd = 0; break; } if (lEnd) { // we should give the child object back!! #ifdef _DEBUG if (!m_hMenu) { //DBPRINTF (TEXT("null hmenu at 2\r\n")); Assert (m_hMenu); } #endif hSubMenu = GetSubMenu (m_hMenu,lEnd-1); if (hSubMenu) { pvarEnd->vt=VT_DISPATCH; return(CreateMenuItem((IAccessible*)this, m_hwnd, m_hMenu, hSubMenu, lEnd, 0, FALSE, IID_IDispatch, (void**)&pvarEnd->pdispVal)); } // just return VT_I4 if it does not have a submenu. pvarEnd->vt = VT_I4; pvarEnd->lVal = lEnd; return(S_OK); } return(S_FALSE); } // -------------------------------------------------------------------------- // // CMenu::accDoDefaultAction() // // Menu bars have no defaults. However, items do. Hierarchical items // drop down/pop up their hierarchical. Non-hierarchical items execute // their command. To Open something that is closed or to Execute a command, // we use SendInput to send Alt+ShortcutKey. To Close something, we just // send Alt. // // -------------------------------------------------------------------------- STDMETHODIMP CMenu::accDoDefaultAction(VARIANT varChild) { GUITHREADINFO gui; TCHAR chHotKey; HMENU hSubMenu; int i,n; int nTries; #define MAX_TRIES 20 if (!ValidateChild(&varChild)) return(E_INVALIDARG); if (varChild.lVal == CHILDID_SELF) return(E_NOT_APPLICABLE); if( GetMDIButtonIndex( m_hMenu, varChild.lVal - 1 ) != 0 ) { // Special case for MDI child buttons - they are actually implemented // as menu items, but appear as buttons. // Active them by posting a WM_COMMAND with the command id of the // corresponding menu item. // // This WM_COMMAND gets picked up by the MDIClient window, and it then // minimizes/restores/closes the currently active child. (If we did // a WM_SYSCOMMAND instead of WM_COMMAND, the overall app window // would minimize/restore/close instead!) int id = GetMenuItemID( m_hMenu, varChild.lVal - 1 ); PostMessage(m_hwnd, WM_COMMAND, id, 0L); return S_OK; } // // Only if this window is active can we do the default. // if (!MyGetGUIThreadInfo(0, &gui)) return(E_FAIL); if (m_hwnd != gui.hwndActive) return(E_NOT_APPLICABLE); // If disabled, fail if (GetMenuState(m_hMenu, varChild.lVal-1, MF_BYPOSITION) & MFS_GRAYED) return(E_NOT_APPLICABLE); #ifdef _DEBUG if (!m_hMenu) { //DBPRINTF (TEXT("null hmenu at 3\r\n")); Assert (m_hMenu); } #endif // First check if this item has a sub menu, and if it is open. // If it has, and it is, then close it. if (hSubMenu = GetSubMenu(m_hMenu, varChild.lVal-1)) { if (GetSubMenuWindow(hSubMenu)) { MyBlockInput (TRUE); SendKey (KEYPRESS,VK_VIRTUAL,VK_MENU,0); SendKey (KEYRELEASE,VK_VIRTUAL,VK_MENU,0); MyBlockInput (FALSE); return (S_OK); } } // when we get here, either it doesn't have a submenu and we need // to execute, or the submenu is closed and we need to open it. // Our actions are the same in either case. - send Alt+Letter if // there is a letter, if not a letter.... // special case for system menus if (m_fSysMenu) { LPARAM lParam; if (GetWindowLong(m_hwnd, GWL_STYLE) & WS_CHILD) lParam = MAKELONG('-', 0); else lParam = MAKELONG(' ', 0); PostMessage(m_hwnd, WM_SYSCOMMAND, SC_KEYMENU, lParam); return (S_OK); } // // Get menu item string; get & character. // chHotKey = GetMenuItemHotkey( this, m_hwnd, m_hMenu, varChild.lVal, 0 ); if (chHotKey) { MyBlockInput (TRUE); SendKey (KEYPRESS,VK_VIRTUAL,VK_MENU,0); SendKey (KEYPRESS,VK_CHAR,0,chHotKey); SendKey (KEYRELEASE,VK_CHAR,0,chHotKey); SendKey (KEYRELEASE,VK_VIRTUAL,VK_MENU,0); MyBlockInput (FALSE); return (S_OK); } else { // Bad Apps don't define hot keys. We can try to move the selection // to that item and then hit enter. An easier way would be to just // hit Alt+FirstLetter, but if there are more than 1 item with that // letter, it will always do the first one. Not optimal, may lead to // unexpected side-effects. Better to do nothing than to do that. // // We need to put ourselves in menu mode if we aren't already, then // send right arrow keys to put us on the right one, then hit Enter. // If we are already in menu mode, take us out of menu mode to close // the heirarchy, then go back into menu mode and continue. MyBlockInput (TRUE); if (gui.flags & GUI_INMENUMODE) { SendKey (KEYPRESS,VK_VIRTUAL,VK_MENU,0); SendKey (KEYRELEASE,VK_VIRTUAL,VK_MENU,0); } // now go into menu mode and send right arrows until the one we // want is highlighted. SendKey (KEYPRESS,VK_VIRTUAL,VK_MENU,0); SendKey (KEYRELEASE,VK_VIRTUAL,VK_MENU,0); // calculate how many right arrows to hit: n = varChild.lVal-1; // if this menu is the menu of an MDI window and the window // is maximized, then the thing now highlighted is the MDI // Doc Sys menu, and we'll have to go 1 farther than we think. // To see if this is the case, we'll check if the first item // in the menu is something with a submenu and it is a bitmap menu. if (GetSubMenu(m_hMenu,0) && (GetMenuState(m_hMenu, 0, MF_BYPOSITION) & MF_BITMAP)) n++; for (i = 0; i < n;i++) { SendKey (KEYPRESS,VK_VIRTUAL,VK_RIGHT,0); SendKey (KEYRELEASE,VK_VIRTUAL,VK_RIGHT,0); } MyBlockInput (FALSE); // check if it is highlighted now. If so, hit enter to activate. // try several times - nTries = 0; while ( ((GetMenuState(m_hMenu, varChild.lVal-1, MF_BYPOSITION) & MF_HILITE) == 0) && (nTries < MAX_TRIES)) { Sleep(55); nTries++; } if (GetMenuState(m_hMenu, varChild.lVal-1, MF_BYPOSITION) & MF_HILITE) { MyBlockInput (TRUE); SendKey (KEYPRESS,VK_VIRTUAL,VK_RETURN,0); SendKey (KEYRELEASE,VK_VIRTUAL,VK_RETURN,0); MyBlockInput (FALSE); return (S_OK); } else return (E_FAIL); } } // -------------------------------------------------------------------------- // // CMenu::Clone() // // -------------------------------------------------------------------------- STDMETHODIMP CMenu::Clone(IEnumVARIANT** ppenum) { return(CreateMenuBar(m_hwnd, m_fSysMenu, m_idChildCur, IID_IEnumVARIANT, (void**)ppenum)); } STDMETHODIMP CMenu::GetIdentityString ( DWORD dwIDChild, BYTE ** ppIDString, DWORD * pdwIDStringLen ) { *ppIDString = NULL; *pdwIDStringLen = 0; BYTE * pKeyData = (BYTE *) CoTaskMemAlloc( HMENUKEYSIZE ); if( ! pKeyData ) { return E_OUTOFMEMORY; } DWORD dwpid; GetWindowThreadProcessId( m_hwnd, & dwpid ); MakeHmenuKey( pKeyData, dwpid, m_hMenu, dwIDChild ); *ppIDString = pKeyData; *pdwIDStringLen = HMENUKEYSIZE; return S_OK; } ///////////////////////////////////////////////////////////////////////////// // // MENU ITEMS // ///////////////////////////////////////////////////////////////////////////// // -------------------------------------------------------------------------- // // CreateMenuItem() // // This creates a child object for a menu item that has a sub menu. // // Parameters: // paccMenu IN pointer to the parent's IAccessible // hwnd IN the hwnd of the window that owns the parent menu // hMenu IN the hmenu of the menu that owns this item. // hSubMenu IN the hMenu of the submenu this menu item opens // ItemID IN the menu item ID. Position (1..n). // iCurChild IN ID of the current child in the enumeration // fPopup IN is this menu item in a popup or on a menu bar? // riid IN what interface are we asking for on this item? // ppvItem OUT the pointer to the interface asked for. // // -------------------------------------------------------------------------- HRESULT CreateMenuItem(IAccessible* paccMenu, HWND hwnd, HMENU hMenu, HMENU hSubMenu, long ItemID, long iCurChild, BOOL fPopup, REFIID riid, void** ppvItem) { HRESULT hr; CMenuItem* pmenuitem; InitPv(ppvItem); pmenuitem = new CMenuItem(paccMenu, hwnd, hMenu, hSubMenu, ItemID, iCurChild, fPopup); if (! pmenuitem) return(E_OUTOFMEMORY); hr = pmenuitem->QueryInterface(riid, ppvItem); if (!SUCCEEDED(hr)) delete pmenuitem; return(hr); } // -------------------------------------------------------------------------- // // CMenuItem::CMenuItem() // // We hang on to our parent object so we can forward methods up to him. // Therefore we must bump up the ref count. // // -------------------------------------------------------------------------- CMenuItem::CMenuItem(IAccessible* paccParent, HWND hwnd, HMENU hMenu, HMENU hSubMenu, long ItemID, long iCurChild, BOOL fPopup) : CAccessible( CLASS_MenuItemObject ) { m_hwnd = hwnd; m_hMenu = hMenu; m_hSubMenu = hSubMenu; m_ItemID = ItemID; m_idChildCur = iCurChild; m_fInAPopup = fPopup; m_paccParent = paccParent; paccParent->AddRef(); } // -------------------------------------------------------------------------- // // CMenuItem::~CMenuItem() // // We hung on to our parent, so we must release it on destruction. // // -------------------------------------------------------------------------- CMenuItem::~CMenuItem() { m_paccParent->Release(); } // -------------------------------------------------------------------------- // // SetupChildren() // // CMenuItems have 1 child. That one child is either a CMenuPopupFrame or // a CMenuPopup (depending if the menu is visible). // // -------------------------------------------------------------------------- void CMenuItem::SetupChildren(void) { m_cChildren = 1; } // -------------------------------------------------------------------------- // // CMenuItem::get_accParent() // // Pass it on back to the parent. // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::get_accParent(IDispatch** ppdispParent) { InitPv(ppdispParent); return(m_paccParent->QueryInterface(IID_IDispatch, (void**)ppdispParent)); } // -------------------------------------------------------------------------- // // CMenuItem::get_accChild() // // The menu item's child is either a CMenuPopupFrame (if the popup window // is visible and belongs to this CMenuItem) or a CMenuPopup. This allows // someone to enumerate the commands whether or not the popup is visible. // // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::get_accChild(VARIANT varChild, IDispatch** ppdispChild) { HWND hwndSubMenu; HRESULT hr; InitPv(ppdispChild); if (!ValidateChild(&varChild)) return(E_INVALIDARG); if (varChild.lVal != CHILDID_SELF) { // In order to create the accessible object representing the child, // we have to find the popup menu window. // Once we have found it, we check if it is visible. If so, then // our child is a CMenuPopupFrame, which we will create by // calling CreateMenuPopupWindow. // If the popup window is not visible, or if it does not belong // to this CMenuItem, then our child is a CMenuPopup, which we // will create by calling CreateMenuPopup. // hwndSubMenu = GetSubMenuWindow (m_hSubMenu); if (hwndSubMenu) return (CreateMenuPopupWindow (hwndSubMenu,0,IID_IDispatch, (void**)ppdispChild)); else { // this is where we create 'invisible' popups so apps can // walk down and see all of the commands (most, at least). // Since it is invisible, we have to tell it more about who // it's parent is. hr = CreateMenuPopupClient (NULL,0,IID_IDispatch,(void**)ppdispChild); if (SUCCEEDED (hr)) ((CMenuPopup*)*ppdispChild)->SetParentInfo((IAccessible*)this, m_hSubMenu,varChild.lVal); return(hr); } } return(E_INVALIDARG); } // -------------------------------------------------------------------------- // // CMenuItem::get_accName() // // The name for the child (CMenuPopup or CMenuPopupFrame) is the same as // the name of the Parent/Self, so whether we are asked for id=self or // id = child (1), we return the same thing. // // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::get_accName(VARIANT varChild, BSTR* pszName) { InitPv(pszName); if (!ValidateChild(&varChild)) return E_INVALIDARG; return GetMenuItemName( this, m_hwnd, m_hMenu, m_ItemID, pszName ); } // -------------------------------------------------------------------------- // // CMenuItem::get_accRole() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::get_accRole(VARIANT varChild, VARIANT* pvarRole) { MENUITEMINFO mi; InitPvar(pvarRole); if (!ValidateChild(&varChild)) return(E_INVALIDARG); pvarRole->vt = VT_I4; if (varChild.lVal == CHILDID_SELF) { mi.cbSize = SIZEOF_MENUITEMINFO; mi.fMask = MIIM_TYPE | MIIM_SUBMENU; mi.cch = 0; mi.dwTypeData = 0; GetMenuItemInfo(m_hMenu, m_ItemID-1, TRUE, &mi); if (mi.fType & MFT_SEPARATOR) pvarRole->lVal = ROLE_SYSTEM_SEPARATOR; else pvarRole->lVal = ROLE_SYSTEM_MENUITEM; } else { pvarRole->lVal = ROLE_SYSTEM_MENUPOPUP; } return(S_OK); } // -------------------------------------------------------------------------- // // CMenuItem::get_accState() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::get_accState(VARIANT varChild, VARIANT* pvarState) { HWND hwndSubMenu; InitPvar(pvarState); if (!ValidateChild (&varChild)) return(E_INVALIDARG); // We do this because sometimes we'll be asked for our own info, // and the caller will just call us item 0 (CHILDID_SELF) and we // have to make sure when we call our parent to tell her who // we are (m_ItemID). if (varChild.lVal == CHILDID_SELF) { varChild.lVal = m_ItemID; return(m_paccParent->get_accState(varChild, pvarState)); } else { // If the popup (our only child) is not showing or it belongs to // another menu item, set the state to invisible. // If it is showing and belongs to us, set the state to normal. // This starts by assuming that it is invisible, and clearing the // state if we find a visible menu that belongs to us. pvarState->vt = VT_I4; pvarState->lVal = 0 | STATE_SYSTEM_INVISIBLE; hwndSubMenu = GetSubMenuWindow (m_hSubMenu); if (hwndSubMenu) pvarState->lVal = 0; } return (S_OK); } // -------------------------------------------------------------------------- // // CMenuItem::get_accKeyboardShortcut() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::get_accKeyboardShortcut(VARIANT varChild, BSTR* pszShortcut) { InitPv(pszShortcut); if (! ValidateChild(&varChild)) return E_INVALIDARG; if (varChild.lVal == CHILDID_SELF) { // Get menu item shortcut - use "Alt+" format for menu bars... BOOL fIsMenuBar = m_hwnd && ::GetMenu( m_hwnd ) == m_hMenu; return GetMenuItemShortcut( this, m_hwnd, m_hMenu, m_ItemID, fIsMenuBar, pszShortcut ); } return S_FALSE; } // -------------------------------------------------------------------------- // // CMenuItem::get_accFocus() // // If focus is us or our popup, great. // // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::get_accFocus(VARIANT* pvarFocus) { HRESULT hr; HWND hwndSubMenu; IDispatch* pdispChild; // Ask our parent who has the focus. Is it us? hr = m_paccParent->get_accFocus(pvarFocus); if (!SUCCEEDED(hr)) return(hr); // No, so return nothing. if ((pvarFocus->vt != VT_I4) || (pvarFocus->lVal != m_ItemID)) { VariantClear(pvarFocus); pvarFocus->vt = VT_EMPTY; return(S_FALSE); } // Is the currently active popup our child? // If so, then we should return an IDispatch to // the window frame object. hwndSubMenu = GetSubMenuWindow (m_hSubMenu); if (hwndSubMenu) { hr = CreateMenuPopupWindow (hwndSubMenu,0,IID_IDispatch,(void**)&pdispChild); if (!SUCCEEDED(hr)) return (hr); pvarFocus->vt = VT_DISPATCH; pvarFocus->pdispVal = pdispChild; return (S_OK); } pvarFocus->lVal = 0; return(S_OK); } // -------------------------------------------------------------------------- // // CMenuItem::get_accDefaultAction() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::get_accDefaultAction(VARIANT varChild, BSTR* pszDefA) { InitPv(pszDefA); if (!ValidateChild (&varChild)) return(E_INVALIDARG); // We do this because sometimes we'll be asked for our own info, // and the caller will just call us item 0 (CHILDID_SELF) and we // have to make sure when we call our parent to tell her who // we are (m_ItemID). // But sometimes, we will be asked for info about our child - There // is no default action for our child. if (varChild.lVal == CHILDID_SELF) { varChild.lVal = m_ItemID; return(m_paccParent->get_accDefaultAction(varChild, pszDefA)); } return (E_NOT_APPLICABLE); } // -------------------------------------------------------------------------- // // CMenuItem::accSelect() // // We just let our parent take care of this for us. Tell her who we are by // setting varChild.lVal to our ItemID. // // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::accSelect(long flagsSel, VARIANT varChild) { if (!ValidateChild (&varChild) || !ValidateSelFlags(flagsSel)) return (E_INVALIDARG); if (varChild.lVal == CHILDID_SELF) varChild.lVal = m_ItemID; return(m_paccParent->accSelect(flagsSel, varChild)); } // -------------------------------------------------------------------------- // // CMenuItem::accLocation() // // Sometimes we are asked for the location of a peer object. This is // kinda screwy. This happens when we are asked to navigate next or prev, // and then let our parent navigate for us. The caller then starts thinking // we know about our peers. // Since this is the only case where something like this happens, we'll // have to do some sort of hack. // Problem is, when they ask for a child 0 (self) we are OK. // But when we are asked for child 1, is it the popup or peer 1? // I am going to assume that it is always the peer. // // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::accLocation(long* pxLeft, long* pyTop, long* pcxWidth, long* pcyHeight, VARIANT varChild) { // we just call this to translate empty values - not going to // check the return value. ValidateChild (&varChild); if (varChild.lVal == CHILDID_SELF) varChild.lVal = m_ItemID; return(m_paccParent->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, varChild)); } // -------------------------------------------------------------------------- // // CMenuItem::accNavigate() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::accNavigate(long dwNavDir, VARIANT varStart, VARIANT* pvarEnd) { HWND hwndSubMenu; InitPvar(pvarEnd); if (!ValidateChild(&varStart)) return (E_INVALIDARG); if (!ValidateNavDir(dwNavDir, varStart.lVal)) return(E_INVALIDARG); if (dwNavDir >= NAVDIR_FIRSTCHILD) // this means firstchild or lastchild { hwndSubMenu = GetSubMenuWindow (m_hSubMenu); if (hwndSubMenu) { pvarEnd->vt = VT_DISPATCH; return (CreateMenuPopupWindow (hwndSubMenu,0,IID_IDispatch, (void**)&(pvarEnd->pdispVal))); } return(S_FALSE); } else { if (varStart.lVal == CHILDID_SELF) varStart.lVal = m_ItemID; return(m_paccParent->accNavigate(dwNavDir, varStart, pvarEnd)); } } // -------------------------------------------------------------------------- // // CMenuItem::accHitTest() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::accHitTest(long x, long y, VARIANT* pvarHit) { HRESULT hr; HWND hwndSubMenu; RECT rc; POINT pt; InitPvar(pvarHit); hwndSubMenu = GetSubMenuWindow (m_hSubMenu); if (hwndSubMenu) { // Is point in our popup menu window child? MyGetRect(hwndSubMenu, &rc, TRUE); pt.x = x; pt.y = y; if (PtInRect(&rc, pt)) { // need to set the parent pvarHit->vt = VT_DISPATCH; return (CreateMenuPopupWindow (hwndSubMenu,0,IID_IDispatch, (void**)pvarHit->pdispVal)); } } // Is point in us? hr = m_paccParent->accHitTest(x, y, pvarHit); // #11150, CWO, 1/24/97, changed from !SUCCEEDED to !S_OK if ((hr != S_OK) || (pvarHit->vt == VT_EMPTY)) return(hr); pvarHit->vt = VT_I4; pvarHit->lVal = CHILDID_SELF; return(S_OK); } // -------------------------------------------------------------------------- // // CMenuItem::accDoDefaultAction() // // We just let our parent take care of this for us. Tell her who we are by // setting varChild.lVal to our ItemID. // // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::accDoDefaultAction(VARIANT varChild) { if (! ValidateChild(&varChild)) return(E_INVALIDARG); Assert(varChild.vt == VT_I4); if (varChild.lVal == CHILDID_SELF) varChild.lVal = m_ItemID; return(m_paccParent->accDoDefaultAction(varChild)); } // -------------------------------------------------------------------------- // // CMenuItem::Clone() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuItem::Clone(IEnumVARIANT** ppenum) { return(CreateMenuItem(m_paccParent, m_hwnd, m_hMenu, m_hSubMenu,m_ItemID, m_idChildCur, FALSE, IID_IEnumVARIANT, (void**)ppenum)); } STDMETHODIMP CMenuItem::GetIdentityString ( DWORD dwIDChild, BYTE ** ppIDString, DWORD * pdwIDStringLen ) { *ppIDString = NULL; *pdwIDStringLen = 0; if( dwIDChild != CHILDID_SELF ) { // CMenuItems have 1 child - that one child is either a CMenuPopupFrame or // a CMenuPopup (depending if the menu is visible). // We're not going to support geting the IDs of these thourhg the parent, // a client should get the interface pointers to those objects themselves, // and ask them for their string. return E_INVALIDARG; } // Weird stuff alert: // // CMenuItem repesents a menu item that has an associated popup. (ie. it is // not a command leaf node) There are two options for representing this item: // // as a child of its parent menu, // or // as the parent (CHILDID_SELF) of its own submenu // // While the HWND-based controls use the latter option, here we are going for // the former - this keeps the menu item with its siblings, regardless of // whether they are menu items with popups, or leaf-node commands. // We can do this here since we do know what our parent HMENU is and what our // child id is in that parent menu. // HWND-based proxies as generally don't have this information available to them, // so the former option is not really an option for them. // This request is for the item itself - represent it as a child of // our parent menu BYTE * pKeyData = (BYTE *) CoTaskMemAlloc( HMENUKEYSIZE ); if( ! pKeyData ) { return E_OUTOFMEMORY; } // Need to find pid of process that the menu belongs to. Can't use the // pid of the popup menu, since that's a shared/reused system window. // Instead, we assume that since the menu is present, it belongs to the // current foreground thread, which is what GetGUIThreadInfo(NULL) gets us. GUITHREADINFO GuiThreadInfo; if( ! MyGetGUIThreadInfo( NULL, & GuiThreadInfo ) ) return E_FAIL; DWORD dwPid = 0; GetWindowThreadProcessId( GuiThreadInfo.hwndActive, & dwPid ); if( dwPid == 0 ) return E_FAIL; MakeHmenuKey( pKeyData, dwPid, m_hMenu, m_ItemID ); *ppIDString = pKeyData; *pdwIDStringLen = HMENUKEYSIZE; return S_OK; } ///////////////////////////////////////////////////////////////////////////// // // MENU POPUPS // ///////////////////////////////////////////////////////////////////////////// // -------------------------------------------------------------------------- // // CreateMenuPopupClient() // // EXTERNAL for CreateClientObject... // // -------------------------------------------------------------------------- HRESULT CreateMenuPopupClient(HWND hwnd, long idChildCur, REFIID riid, void** ppvPopup) { CMenuPopup* ppopup; HRESULT hr; ppopup = new CMenuPopup(hwnd, idChildCur); if (!ppopup) return(E_OUTOFMEMORY); hr = ppopup->QueryInterface(riid, ppvPopup); if (!SUCCEEDED(hr)) delete ppopup; return(hr); } // -------------------------------------------------------------------------- // // CMenuPopup::CMenuPopup() // // -------------------------------------------------------------------------- CMenuPopup::CMenuPopup(HWND hwnd, long idChildCur) : CClient( CLASS_MenuPopupClient ) { Initialize(hwnd, idChildCur); m_hMenu = NULL; m_ItemID = 0; m_hwndParent = NULL; m_fSonOfPopup = 0; m_fSysMenu = 0; // this only works if there is a window handle. if (hwnd) { m_hMenu = (HMENU)SendMessage (m_hwnd,MN_GETHMENU,0,0); // if we didn't get back an HMENU, that means that the window // is probably invisible. Don't try to set other values. // SetupChildren will see this and set m_cChildren to 0. if (m_hMenu) { m_ItemID = FindItemIDThatOwnsThisMenu (m_hMenu,&m_hwndParent, &m_fSonOfPopup,&m_fSysMenu); } } } // -------------------------------------------------------------------------- // // The CMenuPopup objects need to know their parent when they are invisible, // so after one is created, the creator should call SetParentInfo. // // -------------------------------------------------------------------------- void CMenuPopup::SetParentInfo(IAccessible* paccParent,HMENU hMenu,long ItemID) { m_paccParent = paccParent; m_hMenu = hMenu; m_ItemID= ItemID; if (paccParent) paccParent->AddRef(); } // -------------------------------------------------------------------------- // // CMenuPopup::~CMenuPopup() // // -------------------------------------------------------------------------- CMenuPopup::~CMenuPopup(void) { if (m_paccParent) m_paccParent->Release(); } // -------------------------------------------------------------------------- // // CMenuPopup::SetupChildren() // // -------------------------------------------------------------------------- void CMenuPopup::SetupChildren(void) { // we need to be able to set up our children whether the popup is // displayed or not. So we have a m_hMenu variable, it just needs // to be set when the thing is made - It is either set by the // constructor (if we are visible) or by the dude that called the create // function if we are invisible. // PROBLEM - sometimes CMenuPopups are created by a call to // AccessibleObjectFromEvent, and the hwnd isn't always able to // give us back a good m_hMenu. So we will just set m_cChildren to 0. if (m_hMenu) { m_cChildren = GetMenuItemCount(m_hMenu); if( m_cChildren == -1 ) { // Paranoia in case we get an invalid HMENU m_cChildren = 0; } } else m_cChildren = 0; } // -------------------------------------------------------------------------- // // CMenuPopup::get_accParent() // // The parent of a CMenuPopup is either a CMenuPopupFrame or a CMenuItem. // If the popup is visible, it will have an hwnd, and lots of other stuff // will also be set. If it is not visible, it will not have an hwnd. // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::get_accParent(IDispatch** ppdispParent) { if (m_paccParent) { return (m_paccParent->QueryInterface(IID_IDispatch,(void**)ppdispParent)); } else if (m_hwnd) { // try to create a parent for us... return (CreateMenuPopupWindow (m_hwnd,0,IID_IDispatch,(void**)ppdispParent)); } else return (E_FAIL); } // -------------------------------------------------------------------------- // // CMenuPopup::get_accChild() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::get_accChild(VARIANT varChild, IDispatch** ppdispChild) { HMENU hSubMenu; InitPv(ppdispChild); if (!ValidateChild(&varChild) || varChild.lVal == CHILDID_SELF) return(E_INVALIDARG); // // Is this item a hierarchical? // Assert (m_hMenu); hSubMenu = GetSubMenu(m_hMenu, varChild.lVal-1); if (!hSubMenu) return(S_FALSE); // // Yes. // return(CreateMenuItem((IAccessible*)this, m_hwnd, m_hMenu, hSubMenu, varChild.lVal, 0, FALSE, IID_IDispatch, (void**)ppdispChild)); } // -------------------------------------------------------------------------- // // CMenuPopup::get_accName() // // The name of the popup is the name of the item it hangs off of. // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::get_accName(VARIANT varChild, BSTR* pszName) { HWND hwndOwner; TCHAR szClassName[50]; HRESULT hr; IAccessible* paccParent; InitPv(pszName); if (!ValidateChild(&varChild)) return(E_INVALIDARG); if (varChild.lVal == CHILDID_SELF) { // If we popped up from a menu bar or as another popup, // then our name is the name of the thing that popped us // up. If we are a floating popup, then our name is...? // // We implement this by either: // 1. calling our parent object, OR // 2. creating a parent object on the fly that we can // ask the name of, OR // 3. Looking for the name of the owner window, OR // 4. Checking if we are the child of the start button. // If all else fails, we'll just call ourselves "context menu". if (m_paccParent && m_ItemID) { varChild.vt = VT_I4; varChild.lVal = m_ItemID; return (m_paccParent->get_accName (varChild,pszName)); } if (m_hwndParent && m_ItemID) { varChild.vt = VT_I4; varChild.lVal = m_ItemID; if (m_fSonOfPopup) hr = CreateMenuPopupClient(m_hwndParent,0,IID_IAccessible,(void**)&paccParent); else if (m_fSysMenu) hr = CreateSysMenuBarObject(m_hwndParent,0,IID_IAccessible,(void**)&paccParent); else hr = CreateMenuBarObject(m_hwndParent,0,IID_IAccessible,(void**)&paccParent); if (SUCCEEDED(hr)) { hr = paccParent->get_accName (varChild,pszName); paccParent->Release(); } return (hr); } else { // Try to get the owner window and use that for a name // This doesn't seem to work on anything I have ever found, // but it should work if anything has an owner, so i'll //leav it in. If it starts breaking, just rip it out. if (m_hwnd) { IAccessible* pacc; HRESULT hr; hwndOwner = ::GetWindow (m_hwnd,GW_OWNER); hr = AccessibleObjectFromWindow (hwndOwner, OBJID_WINDOW, IID_IAccessible, (void**)&pacc); if (SUCCEEDED(hr)) { hr = pacc->get_accName(varChild,pszName); pacc->Release(); if (SUCCEEDED(hr)) { return (hr); } } } // check if the start button has focus hwndOwner = MyGetFocus(); if (InTheShell(hwndOwner, SHELL_TRAY)) { GetClassName(hwndOwner,szClassName,ARRAYSIZE(szClassName)); if (lstrcmp(szClassName,TEXT("Button")) == 0) { return (HrCreateString(STR_STARTBUTTON,pszName)); } } // at least return this for a name return (HrCreateString (STR_CONTEXT_MENU,pszName)); } // end else we don't have m_paccparent && m_itemid } // end if childid_self else // not childid self, childid > 0 { return GetMenuItemName( this, m_hwnd, m_hMenu, varChild.lVal, pszName ); } return(S_OK); } // -------------------------------------------------------------------------- // // CMenuPopup::get_accDescription() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::get_accDescription(VARIANT varChild, BSTR* pszDesc) { InitPv(pszDesc); if (! ValidateChild(&varChild)) return(E_INVALIDARG); if (varChild.lVal == CHILDID_SELF) return(CClient::get_accDescription(varChild, pszDesc)); return(E_NOT_APPLICABLE); } // -------------------------------------------------------------------------- // // CMenuPopup::get_accRole() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::get_accRole(VARIANT varChild, VARIANT* pvarRole) { InitPvar(pvarRole); if (!ValidateChild(&varChild)) return(E_INVALIDARG); pvarRole->vt = VT_I4; if (varChild.lVal == CHILDID_SELF) pvarRole->lVal = ROLE_SYSTEM_MENUPOPUP; else { MENUITEMINFO mi; mi.cbSize = SIZEOF_MENUITEMINFO; mi.fMask = MIIM_TYPE; mi.cch = 0; mi.dwTypeData = 0; if (GetMenuItemInfo(m_hMenu, varChild.lVal-1, TRUE, &mi) && (mi.fType & MFT_SEPARATOR)) pvarRole->lVal = ROLE_SYSTEM_SEPARATOR; else pvarRole->lVal = ROLE_SYSTEM_MENUITEM; } return(S_OK); } // -------------------------------------------------------------------------- // // CMenuPopup::get_accState() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::get_accState(VARIANT varChild, VARIANT* pvarState) { InitPvar(pvarState); if (!ValidateChild(&varChild)) return(E_INVALIDARG); pvarState->vt = VT_I4; pvarState->lVal = 0; if (varChild.lVal == CHILDID_SELF) return(CClient::get_accState(varChild, pvarState)); else { // GetMenuBarInfo fails if the menu isn't currently present. // We don't fail outright when this happens, since we still want // to collect other info using GetMenuItemInfo below. MENUBARINFO mbi; if( MyGetMenuBarInfo(m_hwnd, OBJID_CLIENT, varChild.lVal, &mbi) ) { if (mbi.fFocused) { pvarState->lVal |= STATE_SYSTEM_FOCUSED | STATE_SYSTEM_HOTTRACKED; } } // // Get menu item flags. NOTE: Can't use GetMenuState(). It whacks // random stuff in for hierarchicals. // MENUITEMINFO mi; mi.cbSize = SIZEOF_MENUITEMINFO; mi.fMask = MIIM_STATE | MIIM_SUBMENU; if (!GetMenuItemInfo(m_hMenu, varChild.lVal-1, TRUE, &mi)) { pvarState->lVal |= STATE_SYSTEM_INVISIBLE; return(S_FALSE); } if (mi.fState & MFS_GRAYED) pvarState->lVal |= STATE_SYSTEM_UNAVAILABLE; if (mi.fState & MFS_CHECKED) pvarState->lVal |= STATE_SYSTEM_CHECKED; if (mi.fState & MFS_DEFAULT) pvarState->lVal |= STATE_SYSTEM_DEFAULT; if (mi.hSubMenu) pvarState->lVal |= STATE_SYSTEM_HASPOPUP; } return(S_OK); } // -------------------------------------------------------------------------- // // CMenuPopup::get_accKeyboardShortcut() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::get_accKeyboardShortcut(VARIANT varChild, BSTR* pszShortcut) { InitPv(pszShortcut); if (!ValidateChild(&varChild)) return E_INVALIDARG; if (varChild.lVal == CHILDID_SELF) return CClient::get_accKeyboardShortcut(varChild, pszShortcut); // Get menu item shortcut. FALSE means don't use ALT+ form. return GetMenuItemShortcut( this, m_hwnd, m_hMenu, varChild.lVal, FALSE, pszShortcut ); } // -------------------------------------------------------------------------- // // CMenuPopup::get_accFocus() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::get_accFocus(VARIANT* pvarFocus) { GUITHREADINFO GuiThreadInfo; MENUITEMINFO mii; int i; // set it to empty if (IsBadWritePtr(pvarFocus,sizeof(VARIANT*))) return (E_INVALIDARG); InitPvar(pvarFocus); // // Are we in menu mode? If not, nothing. // if (!MyGetGUIThreadInfo (NULL,&GuiThreadInfo)) return(S_FALSE); if (GuiThreadInfo.flags & GUI_INMENUMODE) { // do I have to loop through all of them to see which // one is hilited?? Looks like it... mii.cbSize = SIZEOF_MENUITEMINFO; mii.fMask = MIIM_STATE; SetupChildren(); for (i=0;i < m_cChildren;i++) { GetMenuItemInfo (m_hMenu,i,TRUE,&mii); if (mii.fState & MFS_HILITE) { pvarFocus->vt = VT_I4; pvarFocus->lVal = i+1; return (S_OK); } } // I don't think this should happen return(S_FALSE); } return(S_FALSE); } // -------------------------------------------------------------------------- // // CMenuPopup::get_accDefaultAction() // // Popups have no defaults. However, items do. Hierarchical items // drop down/pop up their hierarchical. Non-hierarchical items execute // their command. // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::get_accDefaultAction(VARIANT varChild, BSTR* pszDefA) { HMENU hSubMenu; InitPv(pszDefA); if (!ValidateChild(&varChild)) return(E_INVALIDARG); if (varChild.lVal == CHILDID_SELF) return(E_NOT_APPLICABLE); varChild.lVal--; // Is this item enabled? if (GetMenuState(m_hMenu, varChild.lVal, MF_BYPOSITION) & MFS_GRAYED) return(E_NOT_APPLICABLE); // Now check if this item has a submenu that is displayed. // If there is, the action is hide, if not, the action is show. // If it doesn't have a submenu, the action is execute. #ifdef _DEBUG if (!m_hMenu) { //DBPRINTF ("null hmenu at 4\r\n"); Assert (m_hMenu); } #endif if (hSubMenu = GetSubMenu(m_hMenu, varChild.lVal)) { if (GetSubMenuWindow(hSubMenu)) return(HrCreateString(STR_DROPDOWN_HIDE, pszDefA)); else return(HrCreateString(STR_DROPDOWN_SHOW, pszDefA)); } else return(HrCreateString(STR_EXECUTE, pszDefA)); } // -------------------------------------------------------------------------- // // CMenuPopup::accSelect() // // We only accept TAKEFOCUS. // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::accSelect(long flagsSel, VARIANT varChild) { if (!ValidateChild(&varChild) || !ValidateSelFlags(flagsSel)) return E_INVALIDARG; if (flagsSel != SELFLAG_TAKEFOCUS) return E_NOT_APPLICABLE; return E_NOTIMPL; } // -------------------------------------------------------------------------- // // CMenuPopup::accLocation() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::accLocation(long* pxLeft, long* pyTop, long* pcxWidth, long* pcyHeight, VARIANT varChild) { MENUBARINFO mbi; InitAccLocation(pxLeft, pyTop, pcxWidth, pcyHeight); if (!ValidateChild(&varChild)) return(E_INVALIDARG); if (varChild.lVal == CHILDID_SELF) return(CClient::accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, varChild)); if (!MyGetMenuBarInfo(m_hwnd, OBJID_CLIENT, varChild.lVal, &mbi)) return(S_FALSE); *pcyHeight = mbi.rcBar.bottom - mbi.rcBar.top; *pcxWidth = mbi.rcBar.right - mbi.rcBar.left; *pyTop = mbi.rcBar.top; *pxLeft = mbi.rcBar.left; return(S_OK); } // -------------------------------------------------------------------------- // // CMenuPopup::accHitTest() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::accHitTest(long x, long y, VARIANT* pvarHit) { HRESULT hr; // first make sure we are pointing to our own client area hr = CClient::accHitTest(x, y, pvarHit); // #11150, CWO, 1/27/97, Replaced !SUCCEEDED with !S_OK if ((hr != S_OK) || (pvarHit->vt != VT_I4) || (pvarHit->lVal != 0)) return(hr); // now we can see which child is at this point. SetupChildren(); if (m_cChildren) { POINT pt; pt.x = x; pt.y = y; pvarHit->lVal = MenuItemFromPoint(m_hwnd, m_hMenu, pt) + 1; if (pvarHit->lVal) { IDispatch* pdispChild; pdispChild = NULL; get_accChild(*pvarHit, &pdispChild); if (pdispChild) { pvarHit->vt = VT_DISPATCH; pvarHit->pdispVal = pdispChild; } } return(S_OK); } return(S_FALSE); } // -------------------------------------------------------------------------- // // CMenuPopup::accNavigate() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::accNavigate(long dwNavDir, VARIANT varStart, VARIANT* pvarEnd) { long lEnd = 0; MENUITEMINFO mi; InitPvar(pvarEnd); if (!ValidateChild(&varStart) || !ValidateNavDir(dwNavDir, varStart.lVal)) return(E_INVALIDARG); if (dwNavDir == NAVDIR_FIRSTCHILD) dwNavDir = NAVDIR_NEXT; else if (dwNavDir == NAVDIR_LASTCHILD) { varStart.lVal = m_cChildren + 1; dwNavDir = NAVDIR_PREVIOUS; } else if (!varStart.lVal) { return(CClient::accNavigate(dwNavDir, varStart, pvarEnd)); } switch (dwNavDir) { case NAVDIR_NEXT: case NAVDIR_DOWN: lEnd = varStart.lVal + 1; if (lEnd > m_cChildren) lEnd = 0; break; case NAVDIR_PREVIOUS: case NAVDIR_UP: lEnd = varStart.lVal - 1; break; case NAVDIR_LEFT: case NAVDIR_RIGHT: lEnd = 0; break; } if (lEnd) { // we should give the child object back!! // can't use getSubMenu here because it seems to ignore // separators?? //hSubMenu = GetSubMenu (m_hMenu,lEnd-1); mi.cbSize = SIZEOF_MENUITEMINFO; mi.fMask = MIIM_SUBMENU; mi.cch = 0; mi.dwTypeData = 0; GetMenuItemInfo (m_hMenu,lEnd-1,TRUE,&mi); if (mi.hSubMenu) { pvarEnd->vt=VT_DISPATCH; return(CreateMenuItem((IAccessible*)this, m_hwnd, m_hMenu, mi.hSubMenu, lEnd, 0, FALSE, IID_IDispatch, (void**)&pvarEnd->pdispVal)); } // just return VT_I4 if it does not have a submenu. pvarEnd->vt = VT_I4; pvarEnd->lVal = lEnd; return(S_OK); } return(S_FALSE); } // -------------------------------------------------------------------------- // // CMenuPopup::accDoDefaultAction() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::accDoDefaultAction(VARIANT varChild) { RECT rcLoc; HRESULT hr; if (!ValidateChild(&varChild)) return(E_INVALIDARG); if (varChild.lVal == CHILDID_SELF) return(CClient::accDoDefaultAction(varChild)); // If disabled, fail if (GetMenuState(m_hMenu, varChild.lVal-1, MF_BYPOSITION) & MFS_GRAYED) return(E_NOT_APPLICABLE); hr = accLocation(&rcLoc.left,&rcLoc.top,&rcLoc.right,&rcLoc.bottom,varChild); if (!SUCCEEDED (hr)) return (hr); // this will check if WindowFromPoint at the click point is the same // as m_hwnd, and if not, it won't click. Cool! if (ClickOnTheRect(&rcLoc,m_hwnd,FALSE)) return (S_OK); else return (E_NOT_APPLICABLE); } // -------------------------------------------------------------------------- // // CMenuPopup::Clone() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopup::Clone(IEnumVARIANT **ppenum) { HRESULT hr; hr = CreateMenuPopupClient(m_hwnd, m_idChildCur, IID_IEnumVARIANT, (void**)ppenum); if (SUCCEEDED(hr)) ((CMenuPopup*)*ppenum)->SetParentInfo((IAccessible*)this,m_hMenu,m_ItemID); return(hr); } STDMETHODIMP CMenuPopup::GetIdentityString ( DWORD dwIDChild, BYTE ** ppIDString, DWORD * pdwIDStringLen ) { *ppIDString = NULL; *pdwIDStringLen = 0; BYTE * pKeyData = (BYTE *) CoTaskMemAlloc( HMENUKEYSIZE ); if( ! pKeyData ) { return E_OUTOFMEMORY; } // Need to find pid of process that the menu belongs to. Can't use the // pid of the popup menu, since that's a shared/reused system window. // Instead, we assume that since the menu is present, it belongs to the // current foreground thread, which is what GetGUIThreadInfo(NULL) gets us. GUITHREADINFO GuiThreadInfo; if( ! MyGetGUIThreadInfo( NULL, & GuiThreadInfo ) ) return E_FAIL; DWORD dwPid = 0; GetWindowThreadProcessId( GuiThreadInfo.hwndActive, & dwPid ); if( dwPid == 0 ) return E_FAIL; MakeHmenuKey( pKeyData, dwPid, m_hMenu, dwIDChild ); *ppIDString = pKeyData; *pdwIDStringLen = HMENUKEYSIZE; return S_OK; } // ========================================================================== // // POPUP WINDOW FRAMES // // ========================================================================== // -------------------------------------------------------------------------- // // CreateMenuPopupWindow() // // This creates a child object that represents the Window object for a // popup menu. It has no members, but has one child (a cMenuPopup) // // -------------------------------------------------------------------------- HRESULT CreateMenuPopupWindow(HWND hwnd, long idChildCur, REFIID riid, void** ppvMenuPopupW) { CMenuPopupFrame* pPopupFrame; HRESULT hr; InitPv(ppvMenuPopupW); pPopupFrame = new CMenuPopupFrame(hwnd,idChildCur); if (!pPopupFrame) return(E_OUTOFMEMORY); hr = pPopupFrame->QueryInterface(riid, ppvMenuPopupW); if (!SUCCEEDED(hr)) delete pPopupFrame; return(hr); } // -------------------------------------------------------------------------- // // CMenuPopupFrame::CMenuPopupFrame() // // -------------------------------------------------------------------------- CMenuPopupFrame::CMenuPopupFrame(HWND hwnd,long idChildCur) : CWindow( CLASS_MenuPopupWindow ) { Initialize(hwnd, idChildCur); m_hMenu = NULL; m_ItemID = 0; m_hwndParent = NULL; m_fSonOfPopup = 0; m_fSysMenu = 0; m_hMenu = (HMENU)SendMessage (m_hwnd,MN_GETHMENU,0,0); m_ItemID = FindItemIDThatOwnsThisMenu (m_hMenu,&m_hwndParent, &m_fSonOfPopup,&m_fSysMenu); } // -------------------------------------------------------------------------- // // CMenuPopupFrame::~CMenuPopupFrame() // // -------------------------------------------------------------------------- CMenuPopupFrame::~CMenuPopupFrame() { } // -------------------------------------------------------------------------- // // CMenuPopupFrame::SetupChildren() // // Frames have 1 child. That one child is the CMenuPopup. // // -------------------------------------------------------------------------- void CMenuPopupFrame::SetupChildren(void) { m_cChildren = 1; } // -------------------------------------------------------------------------- // // CMenuPopupFrame::get_accParent() // // Parent of a popupmenuframe is the CMenuItem that created it (if any). // To create one of those we need the grandparent. So we will create the // grandparent (either a CMenuPopup, or a CMenu) temporarily, then we will // create our parent CMenuItem based on that. // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopupFrame::get_accParent(IDispatch** ppdispParent) { IAccessible* paccGrandParent; HRESULT hr; CMenu* pMenu; CMenuPopup* pMenuPopup; InitPv(ppdispParent); if (m_fSonOfPopup) { hr = CreateMenuPopupClient(m_hwndParent,0,IID_IAccessible,(void**)&paccGrandParent); if (SUCCEEDED(hr)) { pMenuPopup = (CMenuPopup*)paccGrandParent; hr = CreateMenuItem (paccGrandParent, // paccMenu IN pointer to the parent's IAccessible m_hwndParent, // hwnd IN the hwnd of the window that owns the parent menu pMenuPopup->GetMenu(), // hMenu IN the hmenu of the menu that owns this item. m_hMenu, // hSubMenu IN the hMenu of the submenu this menu item opens m_ItemID, // ItemID IN the menu item ID. Position (1..n). 0, // iCurChild IN ID of the current child in the enumeration m_fSonOfPopup, // fPopup IN is this menu item in a popup or on a menu bar? IID_IDispatch, // riid IN what interface are we asking for on this item? (void**)ppdispParent); // ppvItem OUT the pointer to the interface asked for. paccGrandParent->Release(); return (hr); } } else if (m_fSysMenu) { hr = CreateSysMenuBarObject(m_hwndParent,0,IID_IAccessible,(void**)&paccGrandParent); if (SUCCEEDED(hr)) { pMenu = (CMenu*)paccGrandParent; pMenu->SetupChildren(); hr = CreateMenuItem (paccGrandParent, // paccMenu IN pointer to the parent's IAccessible m_hwndParent, // hwnd IN the hwnd of the window that owns the parent menu pMenu->GetMenu(), // hMenu IN the hmenu of the menu that owns this item. m_hMenu, // hSubMenu IN the hMenu of the submenu this menu item opens m_ItemID, // ItemID IN the menu item ID. Position (1..n). 0, // iCurChild IN ID of the current child in the enumeration m_fSonOfPopup, // fPopup IN is this menu item in a popup or on a menu bar? IID_IDispatch, // riid IN what interface are we asking for on this item? (void**)ppdispParent); // ppvItem OUT the pointer to the interface asked for. paccGrandParent->Release(); return (hr); } } else { hr = CreateMenuBarObject(m_hwndParent,0,IID_IAccessible,(void**)&paccGrandParent); if (SUCCEEDED(hr)) { pMenu = (CMenu*)paccGrandParent; pMenu->SetupChildren(); hr = CreateMenuItem (paccGrandParent, // paccMenu IN pointer to the parent's IAccessible m_hwndParent, // hwnd IN the hwnd of the window that owns the parent menu pMenu->GetMenu(), // hMenu IN the hmenu of the menu that owns this item. m_hMenu, // hSubMenu IN the hMenu of the submenu this menu item opens m_ItemID, // ItemID IN the menu item ID. Position (1..n). 0, // iCurChild IN ID of the current child in the enumeration m_fSonOfPopup, // fPopup IN is this menu item in a popup or on a menu bar? IID_IDispatch, // riid IN what interface are we asking for on this item? (void**)ppdispParent); // ppvItem OUT the pointer to the interface asked for. paccGrandParent->Release(); return (hr); } } return (hr); } // -------------------------------------------------------------------------- // // CMenuPopupFrame::get_accChild() // // What we want this do do is return (in ppdisp) an IDispatch pointer to // the child specified by varChild. The 1 child of a CMenuPopupFrame is // a cMenuPopup. // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopupFrame::get_accChild(VARIANT varChild, IDispatch** ppdisp) { InitPv(ppdisp); if (!ValidateChild(&varChild) || varChild.lVal == CHILDID_SELF) return(E_INVALIDARG); return (CreateMenuPopupClient(m_hwnd, 0,IID_IDispatch, (void**)ppdisp)); } // -------------------------------------------------------------------------- // // CMenuPopupFrame::get_accName // // Has very similar logic to CMenuPopup::get_accName // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopupFrame::get_accName(VARIANT varChild, BSTR* pszName) { HWND hwndOwner; TCHAR szClassName[50]; HRESULT hr; IAccessible* paccParent; InitPv(pszName); if (!ValidateChild(&varChild)) return(E_INVALIDARG); if (varChild.lVal == CHILDID_SELF) { // If we popped up from a menu bar or as another popup, // then our name is the name of the thing that popped us // up. If we are a floating popup, then our name is...? // // We implement this by either: // 1. creating a parent object on the fly that we can // ask the name of, OR // 2. Looking for the name of the owner window, OR // 3. Checking if we are the child of the start button. // If all else fails, we'll just call ourselves "context menu". if (m_hwndParent && m_ItemID) { varChild.vt = VT_I4; varChild.lVal = m_ItemID; if (m_fSonOfPopup) hr = CreateMenuPopupClient(m_hwndParent,0,IID_IAccessible,(void**)&paccParent); else if (m_fSysMenu) hr = CreateSysMenuBarObject(m_hwndParent,0,IID_IAccessible,(void**)&paccParent); else hr = CreateMenuBarObject(m_hwndParent,0,IID_IAccessible,(void**)&paccParent); if (SUCCEEDED(hr)) { hr = paccParent->get_accName (varChild,pszName); paccParent->Release(); } return (hr); } else { // Try to get the owner window and use that for a name // This doesn't seem to work on anything I have ever found, // but it should work if anything has an owner, so i'll //leave it in. If it starts breaking, just rip it out. if (m_hwnd) { IAccessible* pacc; HRESULT hr; hwndOwner = ::GetWindow (m_hwnd,GW_OWNER); hr = AccessibleObjectFromWindow (hwndOwner, OBJID_WINDOW, IID_IAccessible, (void**)&pacc); if (SUCCEEDED(hr)) { hr = pacc->get_accName(varChild,pszName); pacc->Release(); if (SUCCEEDED(hr)) return (hr); } } // check if the start button has focus hwndOwner = MyGetFocus(); if (InTheShell(hwndOwner, SHELL_TRAY)) { GetClassName(hwndOwner,szClassName,ARRAYSIZE(szClassName)); if (lstrcmp(szClassName,TEXT("Button")) == 0) return (HrCreateString(STR_STARTBUTTON,pszName)); } // at least return this for a name return (HrCreateString (STR_CONTEXT_MENU,pszName)); } // end else we don't have m_paccparent && m_itemid } // end if childid_self else { // not asking for name of the menupopupframe itself. We do not support asking for // name of our child - have to talk to the child itself return (E_INVALIDARG); } } // -------------------------------------------------------------------------- // // CMenuPopupFrame::accHitTest() // // We just need to return VARIANT with var.pDispVal set to be our one child, // the CMenuPopup. // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopupFrame::accHitTest(long x, long y, VARIANT* pvarHit) { IDispatch* pdispChild; HRESULT hr; InitPvar(pvarHit); SetupChildren(); pvarHit->vt = VT_I4; pvarHit->lVal = CHILDID_SELF; if (SendMessage(m_hwnd, WM_NCHITTEST, 0, MAKELONG(x, y)) == HTCLIENT) { hr = CreateMenuPopupClient (m_hwnd,0,IID_IDispatch,(void**)&pdispChild); if (SUCCEEDED (hr)) { pvarHit->vt = VT_DISPATCH; pvarHit->pdispVal = pdispChild; } return(hr); } return(S_OK); } // -------------------------------------------------------------------------- // // CMenuPopupFrame::get_accFocus() // // This fills in pvarFocus with the child that has the focus. // Since we only have one child, We'll return an IDispatch to that child. // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopupFrame::get_accFocus(VARIANT* pvarFocus) { HRESULT hr; IDispatch* pdispChild; InitPvar(pvarFocus); hr = CreateMenuPopupClient(m_hwnd, 0,IID_IDispatch, (void**)&pdispChild); if (!SUCCEEDED(hr)) return (hr); pvarFocus->vt = VT_DISPATCH; pvarFocus->pdispVal = pdispChild; return (S_OK); } // -------------------------------------------------------------------------- // // CMenuPopupFrame::accLocation() // // Location of Self and Child is the same. // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopupFrame::accLocation(long* pxLeft, long* pyTop, long* pcxWidth, long* pcyHeight, VARIANT varChild) { InitAccLocation(pxLeft, pyTop, pcxWidth, pcyHeight); if (!ValidateChild(&varChild)) return(E_INVALIDARG); return(CWindow::accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, varChild)); } // -------------------------------------------------------------------------- // // CMenuPopupFrame::accNavigate() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopupFrame::accNavigate(long dwNavDir, VARIANT varStart, VARIANT* pvarEnd) { InitPvar(pvarEnd); if (!ValidateChild(&varStart) || !ValidateNavDir(dwNavDir, varStart.lVal)) return(E_INVALIDARG); if (dwNavDir == NAVDIR_FIRSTCHILD || dwNavDir == NAVDIR_LASTCHILD) { pvarEnd->vt = VT_DISPATCH; return (CreateMenuPopupClient (m_hwnd,0,IID_IDispatch, (void**)&(pvarEnd->pdispVal))); } return (S_FALSE); } // -------------------------------------------------------------------------- // // CMenuPopupFrame::Clone() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopupFrame::Clone(IEnumVARIANT **ppenum) { return (CreateMenuPopupWindow(m_hwnd, m_idChildCur, IID_IEnumVARIANT, (void**)ppenum)); } // -------------------------------------------------------------------------- // // CMenuPopupFrame::Next() // // -------------------------------------------------------------------------- STDMETHODIMP CMenuPopupFrame::Next(ULONG celt, VARIANT* rgvar, ULONG* pceltFetched) { VARIANT* pvar; long cFetched; // Can be NULL if (pceltFetched) *pceltFetched = 0; pvar = rgvar; cFetched = 0; // we only have one child, so we can only return it if m_idChildCur == 0 if (m_idChildCur == 0) { cFetched++; m_idChildCur++; pvar->vt = VT_DISPATCH; CreateMenuPopupClient (m_hwnd,0,IID_IDispatch, (void**)&(pvar->pdispVal)); } // // Fill in the number fetched // if (pceltFetched) *pceltFetched = cFetched; // // Return S_FALSE if we grabbed fewer items than requested // return((cFetched < (long)celt) ? S_FALSE : S_OK); } // -------------------------------------------------------------------------- // // This is a private function used to get the window handle that contains // a given hSubMenu. // // -------------------------------------------------------------------------- HWND GetSubMenuWindow (HMENU hSubMenuToFind) { HWND hwndSubMenu; BOOL bFound; HMENU hSubMenuTemp; hwndSubMenu = FindWindow (TEXT("#32768"),NULL); if (hwndSubMenu == NULL) return (NULL); // random error condition - shouldn't happen if (!IsWindowVisible(hwndSubMenu)) return (NULL); bFound = FALSE; while (hwndSubMenu) { hSubMenuTemp = (HMENU)SendMessage (hwndSubMenu,MN_GETHMENU,0,0); if (hSubMenuTemp == hSubMenuToFind) { bFound = TRUE; break; } hwndSubMenu = FindWindowEx (NULL,hwndSubMenu,TEXT("#32768"),NULL); } // end while hwndSubMenu if (bFound) { return(hwndSubMenu); } return (NULL); } // -------------------------------------------------------------------------- // This looks at each item in the Active window's menu and any other menu // windows, until it finds one that has an hSubMenu that matches the hMenu // we are trying to find. It then returns the ID of that thing (1..n) and // fills in the window handle of the owner, and whether that window is a top // level window or a popup menu. // -------------------------------------------------------------------------- long FindItemIDThatOwnsThisMenu (HMENU hMenuOwned,HWND* phwndOwner, BOOL* pfPopup,BOOL *pfSysMenu) { HWND hwndMenu; HMENU hMenu; int cItems; int i; if (IsBadWritePtr(phwndOwner,sizeof(HWND*)) || IsBadWritePtr (pfPopup,sizeof(BOOL*)) || IsBadWritePtr (pfSysMenu,sizeof(BOOL*))) return 0; *pfPopup = FALSE; *pfSysMenu = FALSE; *phwndOwner = NULL; GUITHREADINFO GuiThreadInfo; if( ! MyGetGUIThreadInfo (NULL,&GuiThreadInfo) ) return 0; // check if it is from the sys menu first MENUBARINFO mbi; if( MyGetMenuBarInfo(GuiThreadInfo.hwndActive, OBJID_SYSMENU, 0, &mbi) && mbi.hMenu != NULL ) { hMenu = mbi.hMenu; if (GetSubMenu(hMenu,0) == hMenuOwned) { *pfSysMenu = TRUE; *pfPopup = FALSE; *phwndOwner = GuiThreadInfo.hwndActive; return (1); } } // if not from the sys menu, check the window's menu bar hMenu = GetMenu (GuiThreadInfo.hwndActive); if (hMenu) { cItems = GetMenuItemCount (hMenu); for (i=0;i= 4) && (lpszBuf[cchMax-4] == '.')) lpszBuf[cchMax-4] = 0; } } } CloseHandle(hProcess); return *lpszBuf != 0; } // -------------------------------------------------------------------------- // // INTERNAL // GetMDIButtonIndex // // Returns appropritate INDEX_TITLEBAR_nnn for the given menu item, if it // is really an MDI child button (restore/minimize/close). // // Returns 0 otherwise. // // -------------------------------------------------------------------------- UINT GetMDIButtonIndex( HMENU hMenu, DWORD idPos ) { switch( GetMenuItemID( hMenu, idPos ) ) { case SC_MINIMIZE: return INDEX_TITLEBAR_MINBUTTON; case SC_RESTORE: return INDEX_TITLEBAR_RESTOREBUTTON; case SC_CLOSE: return INDEX_TITLEBAR_CLOSEBUTTON; default: return 0; // INDEX_TITLEBAR_SELF } } // -------------------------------------------------------------------------- // // INTERNAL // GetMDIChildMenuString // // Check if this is a MDI menu - return strings for the document menu, // and the min/restore/close buttons // // Parameters: // hmenu IN handle of menu // id IN 0-based index of the menu item // lpszBuf IN/OUT gets filled in with the string // cchMax in number of characters in lpszBuf // // Returns: // TRUE if string was filled in, FALSE otherwise // -------------------------------------------------------------------------- BOOL GetMDIMenuString( HWND hwnd, HMENU hMenu, DWORD idPos, LPTSTR lpszBuf, UINT cchMax ) { // For MDI windows - the min/restore/close buttons of the child window are // actually owner-draw menu items. Have to check for them here... UINT iIndex = GetMDIButtonIndex( hMenu, idPos ); if( iIndex ) { return LoadString( hinstResDll, iIndex + STR_TITLEBAR_NAME, lpszBuf, cchMax ) != 0; } // Detect the document system menu by checking that it has a submenu // which contains the Restore item (GetMenuState returns -1 if not found...) // but which is not the actual system menu (since that also has a restore item). HMENU hSub = GetSubMenu( hMenu, idPos ); if( hSub && GetMenuState( hSub, SC_RESTORE, MF_BYCOMMAND ) != -1 && hSub != MyGetSystemMenu( hwnd ) ) { return LoadString( hinstResDll, STR_DOCMENU_NAME, lpszBuf, cchMax ) != 0; } return FALSE; } // -------------------------------------------------------------------------- // // INTERNAL // GetMDIMenuDescriptionString // // Check if this is a MDI menu - return description strings for the document // menu, and the min/restore/close buttons // // Parameters: // hmenu IN handle of menu // idPos IN 0-based index of the menu item // pbstr OUT returns the description of the item // // Returns: // TRUE if item is a MDI element and pbstr was set, FALSE otherwise. // -------------------------------------------------------------------------- BOOL GetMDIMenuDescriptionString( HMENU hMenu, DWORD idPos, BSTR * pbstr ) { UINT iIndex = GetMDIButtonIndex( hMenu, idPos ); if( iIndex ) { return HrCreateString( iIndex + STR_TITLEBAR_DESCRIPTION, pbstr ) == S_OK; } else { return FALSE; } } // -------------------------------------------------------------------------- // // INTERNAL // TryMSAAMenuHack() // // Checks if a menu supports the 'dwData is ptr to MSAA data' workaround. // // Parameters: // pTheObj IN the hwnd that owns the menu (used to get window handle // if hWnd is NULL) // hWnd IN hwnd of menu, NULL if not known (eg. invisible 'fake' // popups) // dwItemData IN dwItemData from the menu // lpszBuf IN/OUT gets filled in with the string // cchMax in number of characters in lpszBuf // // Returns: // TRUE if string was filled in, FALSE otherwise // -------------------------------------------------------------------------- BOOL TryMSAAMenuHack( IAccessible * pTheObj, HWND hWnd, DWORD_PTR dwItemData, LPTSTR lpszBuf, UINT cchMax ) { BOOL bGotIt = FALSE; if( ! hWnd ) { // It's an invisible 'fake' popup menu (CPopuMenu created to expose // a HMENU, but no menu, and therefore no popup window, is currenly // visible). // Need a window handle so we can get the process id... if( WindowFromAccessibleObjectEx( pTheObj, & hWnd ) != S_OK || hWnd == NULL ) return FALSE; } // ...now get process id... DWORD idProcess = 0; GetWindowThreadProcessId( hWnd, &idProcess ); if( !idProcess ) return FALSE; // Open that process so we can read its memory... HANDLE hProcess = OpenProcess( PROCESS_VM_READ, FALSE, idProcess ); if( hProcess ) { // Treat dwItemData as an address, and try to read a // MSAAMENUINFO struct from there... MSAAMENUINFO menuinfo; SIZE_T cbRead; if( ReadProcessMemory( hProcess, (LPCVOID)dwItemData, (LPVOID) & menuinfo, sizeof( menuinfo ), &cbRead ) && ( cbRead == sizeof( menuinfo ) ) ) { // Check signature... if( menuinfo.dwMSAASignature == MSAA_MENU_SIG ) { // Work out len of UNICODE string to copy (+1 for terminating NUL) DWORD copyLen = ( menuinfo.cchWText + 1 ) * sizeof( WCHAR ); WCHAR * pAlloc = (LPWSTR) LocalAlloc( LPTR, copyLen ); if( pAlloc ) { // Do the copy... also fail if we read less than expected, or terminating NUL missing... if( ReadProcessMemory( hProcess, (LPCVOID)menuinfo.pszWText, pAlloc, copyLen, &cbRead ) && ( cbRead == copyLen ) && ( pAlloc[ menuinfo.cchWText ] == '\0' ) ) { #ifdef UNICODE // Copy text to output buffer... if( cchMax > 0 ) { UINT cchCopy = menuinfo.cchWText; if( cchCopy > cchMax - 1 ) cchCopy = cchMax - 1; // -1 for terminating NUL memcpy( lpszBuf, pAlloc, cchCopy * sizeof( TCHAR ) ); lpszBuf[ cchCopy ] = L'\0'; bGotIt = TRUE; } #else // Convert (and copy) UNICODE to ANSI... if( WideCharToMultiByte( CP_ACP, 0, pAlloc, -1, lpszBuf, cchMax, NULL, NULL ) != 0 ) { bGotIt = TRUE; } #endif } LocalFree( pAlloc ); } // pAlloc } // m_Signature } // ReadProcessMemory CloseHandle( hProcess ); } // hProcess return bGotIt; } // -------------------------------------------------------------------------- // // WindowFromAccessibleObjectEx() // // This walks UP the ancestor chain until we find something who responds to // IOleWindow(). Then we get the HWND from it. // // This is effectively a local version of WindowFromAccessibleObject // This version doesn't stop till it runs out of objects, it gets a valid // hwnd. The non-ex version stops even if it getgs a NULL hwnd. // This allows us to navigate up through menupopups which have no hwnd // (return NULL), but which do have parents, which eventually leads us to // the owning hWnd. // // -------------------------------------------------------------------------- STDAPI WindowFromAccessibleObjectEx( IAccessible* pacc, HWND* phwnd ) { IAccessible* paccT; IOleWindow* polewnd; IDispatch* pdispParent; HRESULT hr; //CWO: 12/4/96, Added check for NULL object //CWO: 12/13/96, Removed NULL check, replaced with IsBadReadPtr check (#10342) if (IsBadWritePtr(phwnd,sizeof(HWND*)) || IsBadReadPtr(pacc, sizeof(void*))) return (E_INVALIDARG); *phwnd = NULL; paccT = pacc; hr = S_OK; while (paccT && SUCCEEDED(hr)) { polewnd = NULL; hr = paccT->QueryInterface(IID_IOleWindow, (void**)&polewnd); if (SUCCEEDED(hr) && polewnd) { hr = polewnd->GetWindow(phwnd); polewnd->Release(); // Don't quit if we just got a NULL hwnd... // (this is the only change from WindowFromAccessibleObject(), which // just unconditionally returned when it got here...) if( *phwnd != NULL ) { // // Release an interface we obtained on our own, but not the one // passed in. // if (paccT != pacc) { paccT->Release(); paccT = NULL; } break; } } // // Get our parent. // pdispParent = NULL; hr = paccT->get_accParent(&pdispParent); // // Release an interface we obtained on our own, but not the one // passed in. // if (paccT != pacc) { paccT->Release(); } paccT = NULL; if (SUCCEEDED(hr) && pdispParent) { hr = pdispParent->QueryInterface(IID_IAccessible, (void**)&paccT); pdispParent->Release(); } } return(hr); } // -------------------------------------------------------------------------- // // INTERNAL // GetMenuItemName() // // Returns the BSTR name for a menu item. // // Parameters: // pTheObj IN the hwnd that owns the menu (used to get window // handle if hWnd is NULL) // hWnd IN hwnd of menu, NULL if not known (eg. invisible // 'fake' popups) // hMenu IN Menu handle // id IN 1-based id of the menu item (idChild) // pszName OUT returns string containing text for menu item. // // Returns: // S_OK if string returned, S_FALSE for missing string, COM error code // otherwise. // // Catches special cases " " and "-", which get mapped to "System" and // "Document window" respectively. These are the top-level and child window // system menus. // // -------------------------------------------------------------------------- HRESULT GetMenuItemName( IAccessible * pTheObj, HWND hwnd, HMENU hMenu, LONG id, BSTR * pszName ) { Assert( hMenu ); TCHAR szItemName[256]; // TRUE -> allow generated names (eg. for MDI buttons) if( MyGetMenuString( pTheObj, hwnd, hMenu, id, szItemName, ARRAYSIZE( szItemName ), TRUE ) ) { StripMnemonic( szItemName ); if( lstrcmp( szItemName, TEXT(" ") ) == 0 ) { return HrCreateString( STR_SYSMENU_NAME, pszName ); // "System" } if( lstrcmp( szItemName, TEXT("-") ) == 0 ) { return HrCreateString( STR_DOCMENU_NAME, pszName ); // "Document window" } *pszName = TCharSysAllocString(szItemName); if( ! *pszName ) return E_OUTOFMEMORY; return S_OK; } else { *szItemName = '\0'; *pszName = NULL; return S_FALSE; } } // -------------------------------------------------------------------------- // // INTERNAL // GetMenuItemShortcut() // // Returns the BSTR name for a menu shortcut. // // Parameters: // pTheObj IN the hwnd that owns the menu (used to get window // handle if hWnd is NULL) // hWnd IN hwnd of menu, NULL if not known (eg. invisible // 'fake' popups) // hMenu IN Menu handle // id IN 1-based id of the menu item (idChild) // fIsMenuBar IN TRUE if the menu is a menubar; false for a popup // pszShortcut OUT returns string containing kbshortcut for menu item. // // Returns: // S_OK if string returned, S_FALSE for missing string, COM error code // otherwise. // // Catches special case " " for system menu. // If fMenuBar is TRUE, uses "Alt+%c" form. // // -------------------------------------------------------------------------- HRESULT GetMenuItemShortcut( IAccessible * pTheObj, HWND hwnd, HMENU hMenu, LONG id, BOOL fIsMenuBar, BSTR * pszShortcut ) { TCHAR szHotKey[32]; szHotKey[0] = GetMenuItemHotkey( pTheObj, hwnd, hMenu, id, GMIH_ALLOW_INITIAL | GMIH_ALLOW_SYS_SPACE ); szHotKey[1] = 0; if ( szHotKey[0] == ' ' ) { // Expand space character ' ' to the string "Space" szHotKey[ 0 ] = '\0'; LoadString( hinstResDll, STR_SYSMENU_KEY, szHotKey, ARRAYSIZE( szHotKey ) ); } // Fall through... This gives us c -> Alt+c if it was a single char, // or ' ' -> "Space" -> "Alt+Space" if it was a space char (for sys menu). if ( *szHotKey ) { // If this is a menu bar, use the ALT+ form... if ( fIsMenuBar ) { // Make a string of the form "Alt+ch". return HrMakeShortcut( szHotKey, pszShortcut ); } else { // otherwise use just the key *pszShortcut = TCharSysAllocString( szHotKey ); if ( ! *pszShortcut ) { return E_OUTOFMEMORY; } return S_OK; } } *pszShortcut = NULL; return S_FALSE; } // -------------------------------------------------------------------------- // // INTERNAL // GetMenuItemHotkey() // // Returns the TCHAR hotkey for a menu, if one exists. // // Parameters: // pTheObj IN the hwnd that owns the menu (used to get window // handle if hWnd is NULL) // hWnd IN hwnd of menu, NULL if not known (eg. invisible // 'fake' popups) // hMenu IN Menu handle // id IN 1-based id of the menu item (idChild) // fOption IN Option flags - see below. // // Returns: // Hotkey character of menu item, or '\0' if item has no hotkey. // // Options: // // GMIH_ALLOW_INITIAL // If set, allows the initial character of the menu item string to be // returned as the shortcut key (provided that no other items also use // that key as their menonic.) // // GMIH_ALLOW_SYS_SPACE = 0x02 // If set, returns ' ' as the shortcut key of the system menu item. // // -------------------------------------------------------------------------- TCHAR GetMenuItemHotkey( IAccessible * pTheObj, HWND hwnd, HMENU hMenu, LONG id, DWORD fOptions ) { TCHAR szItemName[ 256 ]; // FALSE -> disallow generated names (eg. for MDI buttons) if( ! MyGetMenuString( pTheObj, hwnd, hMenu, id, szItemName, ARRAYSIZE( szItemName ), FALSE ) ) { return '\0'; } // Check for menu name being " " - caller will want to treat this as a special hotkey of "Space". // (eg. givein "Alt+Space" as the entire hotkey string.) if( ( fOptions & GMIH_ALLOW_SYS_SPACE ) && lstrcmp( szItemName, TEXT(" ") ) == 0 ) { return ' '; } TCHAR ch = StripMnemonic( szItemName ); // Can stop here if caller doesn't want initial chars (ie. &-mnemonics only) if( ! ( fOptions & GMIH_ALLOW_INITIAL ) ) { return ch; } // Did we get a hot-key? If so, use it. if( ch != '\0' ) { return ch; } // Try initial letter instead... LPTSTR pScanCh = szItemName; while( *pScanCh == ' ' ) { *pScanCh++; } // Obscure USER32 menu thing - was used to right-justify Help items in a previous version? // Anyhow, USER skips over it to find the real first letter. So we do likewise... if( *pScanCh == '\x08' ) { pScanCh++; } // Just in case there's no initial letter... (all spaces) if( *pScanCh == '\0' ) { return '\0'; } // Hotkeys are always returned as lowercase... CharLowerBuff( pScanCh, 1 ); ch = *pScanCh; // Now compare against all other menu items - if one other item has this // initial letter as its mnemonic, then we can't use it for this one. // (Mnemonic takes precedence over initial letters) // GetMenuItemHotkey index is 1-based (ie. idChild), as is id, so also // using a 1-based index here. int cItems = GetMenuItemCount( hMenu ); for( int iScan = 1 ; iScan <= cItems ; iScan++ ) { // Don't compare against this item! if( iScan != id ) { if( ch == GetMenuItemHotkey( pTheObj, hwnd, hMenu, iScan, NULL ) ) { // Some other item has a mnemonic that is the same as this // item's initial char - it mnemonic takes precedence, so // this item has no kbshortcut. return '\0'; } } } // No item use this item's initial char as a mnemonic - we're clear to use // it as this item's kbshortcut. return ch; } // -------------------------------------------------------------------------- // // INTERNAL // MyGetSystemMenu() // // Returns the system HMENU for the given HWND. // // Can't use the Win32 API GetSystemMenu, since that modifies the system // HMENU for the window. // // -------------------------------------------------------------------------- HMENU MyGetSystemMenu( HWND hwnd ) { MENUBARINFO mbi; if ( ! MyGetMenuBarInfo( hwnd, OBJID_SYSMENU, 0, &mbi ) ) { return NULL; } // GetMenuBarInfo returns a menu containing the sysmenu as its only // submenu. Use GetSubMenu to access that... return GetSubMenu( mbi.hMenu, 0 ); }