// ShellContextMenu.cpp: implementation of the CShellContextMenu class. // ///////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2000-2001 by Paolo Messina // (http://www.geocities.com/ppescher - ppescher@yahoo.com) // // The contents of this file are subject to the Artistic License (the "License"). // You may not use this file except in compliance with the License. // You may obtain a copy of the License at: // http://www.opensource.org/licenses/artistic-license.html // // If you find this code useful, credits would be nice! // ///////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "ShellContextMenu.h" ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// BOOL CShellContextMenu::InvokeCommand(UINT nCmdID) { if (!IsValid() || !IsCommandInRange(nCmdID)) return FALSE; CMINVOKECOMMANDINFO cmi; ZeroMemory(&cmi, sizeof(CMINVOKECOMMANDINFO)); cmi.cbSize = sizeof(CMINVOKECOMMANDINFO); cmi.hwnd = m_pOwner->GetSafeHwnd(); cmi.lpVerb = MAKEINTRESOURCE(nCmdID - m_nCmdFirstID); cmi.nShow = SW_SHOWNORMAL; return (NOERROR == m_pCtxMenu->InvokeCommand(&cmi)); } BOOL CShellContextMenu::FillMenu(CMenu *pMenu, UINT nStartIndex, UINT nCmdFirstID, UINT nCmdLastID, UINT nFlags) { if (!IsValid()) return FALSE; m_nCmdFirstID = nCmdFirstID; m_nCmdLastID = nCmdLastID; UINT nDefID = pMenu->GetDefaultItem(GMDI_USEDISABLED, TRUE); HRESULT hr = m_pCtxMenu->QueryContextMenu(pMenu->GetSafeHmenu(), nStartIndex, m_nCmdFirstID, m_nCmdLastID, nFlags); if (nFlags & CMF_NODEFAULT) pMenu->SetDefaultItem(nDefID, TRUE); if (FAILED(hr)) { m_nCmdLastID = m_nCmdFirstID = 0; return FALSE; } m_nCmdLastID = m_nCmdFirstID + HRESULT_CODE(hr) - 1; return TRUE; } BOOL CShellContextMenu::HandleMenuMessage(UINT message, WPARAM wParam, LPARAM lParam, LRESULT *pLResult) { if (!IsValid()) return FALSE; switch (message) { case WM_MENUSELECT: { // look for a parent frame, to update its status bar if (m_pOwner == NULL) return FALSE; CFrameWnd* pFrame = m_pOwner->GetTopLevelFrame(); if (pFrame == NULL) return FALSE; // provide status bar text *pLResult = 0; CString sStatusText; UINT uItem = (UINT) LOWORD(wParam); UINT fuFlags = (UINT) HIWORD(wParam); if (fuFlags == 0xFFFF) // menu was closed return FALSE; // get the first item of submenu, if popup item if (fuFlags & MF_POPUP) uItem = ::GetMenuItemID(::GetSubMenu((HMENU)lParam, uItem), 0); // check if it's a valid item if (!(fuFlags & MF_SEPARATOR) && (uItem == 0 || !IsCommandInRange(uItem))) return FALSE; // get description if (!(fuFlags & MF_SEPARATOR)) GetCommandDescription(uItem, sStatusText); // update status text, even if empty pFrame->SetMessageText(sStatusText); return TRUE; } break; case WM_INITMENUPOPUP: // initialize sub-menus case WM_MEASUREITEM: // handle owner-draw items case WM_DRAWITEM: { // prepare result in case we handle the message // (if we don't, it will be discarded) *pLResult = TRUE; if (message == WM_INITMENUPOPUP) *pLResult = 0; // check if it's a menu message if (message == WM_MEASUREITEM && ((LPMEASUREITEMSTRUCT)lParam)->CtlType != ODT_MENU) return FALSE; if (message == WM_DRAWITEM && ((LPDRAWITEMSTRUCT)lParam)->CtlType != ODT_MENU) return FALSE; // these messages need interface version 2 SContextMenu2Ptr pCtxMenu2(m_pCtxMenu); if (!pCtxMenu2.IsValid()) return FALSE; return (NOERROR == pCtxMenu2->HandleMenuMsg(message, wParam, lParam)); } } return FALSE; } BOOL CShellContextMenu::GetCommandDescription(UINT nCmdID, CString& rText) { if (!IsValid() || !IsCommandInRange(nCmdID)) return FALSE; // make a buffer large enough to hold either a Unicode or Ansi // string and initialize it to zeroes, because some shell // extensions return success even when they don't copy anything // to the buffer. Conversion is achieved by a CString object. WCHAR szHelpText[MAX_PATH]; ZeroMemory(szHelpText, sizeof(WCHAR)*MAX_PATH); // try with Unicode first (it seems Explorer does so) HRESULT hr = m_pCtxMenu->GetCommandString(nCmdID - m_nCmdFirstID, GCS_HELPTEXTW, NULL, (LPSTR)szHelpText, MAX_PATH); if (*(LPWSTR)szHelpText != 0) // buffer was used rText = (LPWSTR)szHelpText; else { // otherwise try with Ansi hr = m_pCtxMenu->GetCommandString(nCmdID - m_nCmdFirstID, GCS_HELPTEXTA, NULL, (LPSTR)szHelpText, MAX_PATH); if (*(LPSTR)szHelpText != 0) // buffer was used rText = (LPSTR)szHelpText; } return SUCCEEDED(hr); } BOOL CShellContextMenu::Create(IShellFolder *pParentFolder, LPCITEMIDLIST pidl) { m_pCtxMenu = SContextMenuPtr(pParentFolder, pidl); return m_pCtxMenu.IsValid(); } // a NULL pointer to unsubclass void CShellContextMenu::SetOwner(CWnd *pOwner) { if (m_pOwner != NULL || pOwner == NULL) UnsubclassOwner(); m_pOwner = pOwner; if (m_pOwner != NULL) SubclassOwner(); } const LPCTSTR SH_CTXMENU_OBJ = _T("ShellContextMenu"); void CShellContextMenu::SubclassOwner() { ASSERT(::IsWindow(m_pOwner->GetSafeHwnd())); m_oldWndProc = (WNDPROC)::GetWindowLong(*m_pOwner, GWL_WNDPROC); ::SetProp(*m_pOwner, SH_CTXMENU_OBJ, (HANDLE)this); ::SetWindowLong(*m_pOwner, GWL_WNDPROC, (LONG)WindowProc); } LRESULT CALLBACK CShellContextMenu::WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { CShellContextMenu *pThis = (CShellContextMenu*)::GetProp(hwnd, SH_CTXMENU_OBJ); LRESULT lResult; if (pThis->HandleMenuMessage(message, wParam, lParam, &lResult)) return lResult; // success, no default processing if (message == WM_DESTROY) pThis->UnsubclassOwner(); return ::CallWindowProc(pThis->m_oldWndProc, hwnd, message, wParam, lParam); } void CShellContextMenu::UnsubclassOwner() { ASSERT(::IsWindow(m_pOwner->GetSafeHwnd())); ::RemoveProp(*m_pOwner, SH_CTXMENU_OBJ); ::SetWindowLong(*m_pOwner, GWL_WNDPROC, (LONG)m_oldWndProc); m_pOwner = NULL; }