/* * a t t m e n u. c p p * * Purpose: * Attachment menu * * History * * Copyright (C) Microsoft Corp. 1995, 1996. */ #include #include "dllmain.h" #include "docobj.h" #include "resource.h" #include "shlwapi.h" #include "shlwapip.h" #include "mimeole.h" #include "mimeolep.h" #include "attmenu.h" #include "saveatt.h" #include #include "demand.h" ASSERTDATA /* * e x t e r n s */ /* * t y p e d e f s */ /* * m a c r o s */ /* * c o n s t a n t s */ /* * g l o b a l s */ static const TCHAR c_szSubThisPtr[]="AttMenu_SubThisPtr"; /* * p r o t o t y p e s */ CAttMenu::CAttMenu() { m_hMenu=NULL; m_cRef=1; m_hCharset=NULL; m_pFntCache=NULL; m_pFrame=NULL; m_pMsg=NULL; m_pfnWndProc=NULL; m_fShowingMenu=FALSE; m_pAttachVCard=NULL; m_pHostCmdTarget=NULL; m_cAttach = 0; m_cVisibleAttach = 0; m_cEnabledAttach = 0; m_fAllowUnsafe = FALSE; m_hVCard = NULL; m_rghAttach = NULL; } CAttMenu::~CAttMenu() { if (m_hMenu) DestroyMenu(m_hMenu); HrFreeAttachData(m_pAttachVCard); ReleaseObj(m_pFntCache); ReleaseObj(m_pFrame); ReleaseObj(m_pMsg); ReleaseObj(m_pHostCmdTarget); SafeMemFree(m_rghAttach); } ULONG CAttMenu::AddRef() { return ++m_cRef; } ULONG CAttMenu::Release() { m_cRef--; if (m_cRef==0) { delete this; return 0; } return m_cRef; } HRESULT CAttMenu::Init(IMimeMessage *pMsg, IFontCache *pFntCache, IOleInPlaceFrame *pFrame, IOleCommandTarget *pHostCmdTarget) { if (pMsg==NULL || pFntCache==NULL) return E_INVALIDARG; ReplaceInterface(m_pFntCache, pFntCache); ReplaceInterface(m_pFrame, pFrame); ReplaceInterface(m_pMsg, pMsg); ReplaceInterface(m_pHostCmdTarget, pHostCmdTarget); pMsg->GetCharset(&m_hCharset); return ScanForAttachmentCount(); } HRESULT CAttMenu::Show(HWND hwnd, LPPOINT ppt, BOOL fRightClick) { ULONG iCmd; LPATTACHDATA lpAttach; HRESULT hr=S_OK; // Check Params AssertSz (hwnd && ppt, "Null Parameter"); if (m_fShowingMenu) return S_OK; if (m_hMenu == NULL) { hr = BuildMenu(); if (FAILED(hr)) goto error; } Assert (m_hMenu); // BUG: If the right edge is off the screen, TrackPopupMenu picks a random point // ppt->x = min(GetSystemMetrics(SM_CXSCREEN), ppt->x); // set m_uVerb so we can show correct context menu help for Open or Save. m_uVerb = fRightClick || (GetAsyncKeyState(VK_CONTROL)&0x8000) ? AV_SAVEAS : AV_OPEN; m_fShowingMenu=TRUE; SubClassWindow(hwnd, TRUE); // sheer brillance. We subclass the parent window during the context menu loop so we can steal the // owndraw messages and also steal the menu select messages. iCmd = (ULONG)TrackPopupMenu (m_hMenu, TPM_RIGHTALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, ppt->x, ppt->y, 0, hwnd, NULL); SubClassWindow(hwnd, FALSE); m_fShowingMenu=FALSE; if (iCmd<=0) // nothing selected, bail goto error; if (iCmd == idmSaveAllAttach) return HrSaveAll(hwnd); hr = FindItem(iCmd, FALSE, &lpAttach); if (FAILED(hr)) goto error; hr = HrDoAttachmentVerb(hwnd, m_uVerb, m_pMsg, lpAttach); error: return hr; } /* * note, we build the attachment menu we hang the lpAttach data off the context menu * which contains all the temp file to delete etc. */ HRESULT CAttMenu::BuildMenu() { HRESULT hr = S_OK; MENUITEMINFO mii={0}; INT iMenu = 0; int cyMenu, cyMenuMax, cyItem; SIZE size; ULONG uAttach; LPATTACHDATA pAttach; int cSafeFiles = 0; Assert (!m_hMenu); // Create the menu m_hMenu = CreatePopupMenu(); if (!m_hMenu) return E_OUTOFMEMORY; // figure out where to put menu breaks cyMenu = 0; cyMenuMax = GetSystemMetrics(SM_CYSCREEN); // calculate the rough height of each item, and the maximum width // for the attachment name GetItemTextExtent(NULL, L"BIGGERMAXATTACHMENTNAME.TXT", &size); m_cxMaxText = size.cx; cyItem = max(size.cy, GetSystemMetrics(SM_CYSMICON)) + 8; mii.cbSize = sizeof(mii); mii.fMask = MIIM_DATA|MIIM_ID|MIIM_TYPE; mii.fType = MFT_OWNERDRAW; mii.wID = 1; /* * This is weird, but cool. So we assign menu items idms based on idmSaveAttachLast + i * where i is the item added. If we're a popup on a menubar, then we ensure that we don't go * over the reserved limit. If we're not then we are a context menu. The context menu is called with * TPM_RETURNCMD, so the commands are not sent to the owners WM_COMMAND. Therefore over-running this range * and going into someone elses idm-space is not an issue. */ for (uAttach=0; uAttach= cyMenuMax) { mii.fType |= MFT_MENUBARBREAK; cyMenu = cyItem; } if (pAttach && !pAttach->fSafe && !m_fAllowUnsafe) { mii.fMask |= MIIM_STATE; mii.fState = MFS_DISABLED; } else cSafeFiles++; if (!InsertMenuItem (m_hMenu, iMenu++, TRUE, &mii)) { MemFree(pAttach); hr = E_FAIL; goto error; } mii.fMask &= ~MIIM_STATE; mii.fState = 0; mii.wID++; } } } mii.fType = MFT_SEPARATOR; mii.dwItemData=0; InsertMenuItem (m_hMenu, iMenu++, TRUE, &mii); // we have to owner-draw this menu item as we draw the entire menu in a different font // based on the message locale mii.fType = MFT_OWNERDRAW; mii.dwTypeData = NULL; mii.dwItemData = NULL; mii.wID = idmSaveAllAttach; if (!m_fAllowUnsafe) { if (!cSafeFiles) { mii.fMask |= MIIM_STATE; mii.fState = MFS_DISABLED; } m_cEnabledAttach = cSafeFiles; } InsertMenuItem (m_hMenu, iMenu++, TRUE, &mii); error: // Failed cond if (FAILED (hr) && m_hMenu) DestroyMenu(m_hMenu); // Done return hr; } HRESULT CAttMenu::DestroyMenu(HMENU hMenu) { ULONG uItem, cItems; LPATTACHDATA pAttach; cItems = GetMenuItemCount(hMenu); for (uItem = 0; uItem < cItems; uItem++) { // free the lpAttach hanging off the menu if (FindItem(uItem, TRUE, &pAttach)==S_OK) HrFreeAttachData(pAttach); } ::DestroyMenu(hMenu); return S_OK; } HRESULT CAttMenu::OnMeasureItem(HWND hwnd, LPMEASUREITEMSTRUCT lpmis) { WORD cxIcon=(WORD)GetSystemMetrics (SM_CXSMICON), cyIcon=(WORD)GetSystemMetrics (SM_CYSMICON); SIZE rSize; LPATTACHDATA lpAttach; WCHAR rgch[CCHMAX_STRINGRES]; Assert(lpmis && hwnd); if (lpmis->CtlType == ODT_MENU) { // Default width and height lpmis->itemHeight = cyIcon + 8; lpmis->itemWidth = cxIcon + 9; lpAttach = (LPATTACHDATA)lpmis->itemData; if (lpAttach) { if (FAILED(GetItemTextExtent(hwnd, lpAttach->szDisplay, &rSize))) return E_FAIL; lpmis->itemWidth += min(rSize.cx, m_cxMaxText); lpmis->itemHeight = max (rSize.cy, cyIcon) + 8; return S_OK; } if (lpmis->itemID == idmSaveAllAttach) { LoadStringWrapW(g_hLocRes, idsSaveAllAttach, rgch, ARRAYSIZE(rgch)); if (FAILED(GetItemTextExtent(hwnd, rgch, &rSize))) return E_FAIL; lpmis->itemWidth = min(rSize.cx, m_cxMaxText) + 9; lpmis->itemHeight = max (rSize.cy, cyIcon) + 8; return S_OK; } } return E_FAIL; } HRESULT CAttMenu::GetItemTextExtent(HWND hwnd, LPWSTR szDisp, LPSIZE pSize) { HDC hdc; HFONT hFont=0, hFontOld; HRESULT hr=E_FAIL; // I need a DC to measure the size of the menu font hdc = GetDC (hwnd); if (hdc) { Assert (m_hCharset!=NULL); Assert (m_pFntCache); // Create the menu font m_pFntCache->GetFont(FNT_SYS_MENU, m_hCharset, &hFont); if (hFont) hFontOld = SelectFont (hdc, hFont); // Get the size of the text hr = GetTextExtentPoint32AthW(hdc, szDisp, lstrlenW(szDisp), pSize, DT_NOPREFIX)?S_OK:S_FALSE; if (hFont) SelectObject (hdc, hFontOld); ReleaseDC (hwnd, hdc); } return S_OK; } HRESULT CAttMenu::OnDrawItem(HWND hwnd, LPDRAWITEMSTRUCT lpdis) { DWORD rgbBack, rgbText; WORD dx, x, y, cxIcon=(WORD) GetSystemMetrics (SM_CXSMICON), cyIcon=(WORD) GetSystemMetrics (SM_CYSMICON); HFONT hFont = NULL, hFontOld = NULL; LPATTACHDATA lpAttach; RECT rc; LPWSTR pszDisplay; WCHAR rgch[CCHMAX_STRINGRES]; HICON hIcon; HCHARSET hCharset; AssertSz (lpdis, "Null Parameter"); // not a menu if (lpdis->CtlType != ODT_MENU) return E_FAIL; if (lpdis->itemID == idmSaveAllAttach) { if (!LoadStringWrapW(g_hLocRes, idsSaveAllAttach, rgch, ARRAYSIZE(rgch))) return E_FAIL; pszDisplay = rgch; hIcon=NULL; hCharset = NULL; // always draw in system font } else { lpAttach = (LPATTACHDATA)lpdis->itemData; if (!lpAttach) return E_FAIL; hIcon = lpAttach->hIcon; pszDisplay = lpAttach->szDisplay; hCharset = m_hCharset; // always draw in localised font } // Determine Colors if (lpdis->itemState & ODS_SELECTED) { rgbBack = SetBkColor (lpdis->hDC, GetSysColor(COLOR_HIGHLIGHT)); if (lpdis->itemState & ODS_DISABLED) rgbText = SetTextColor (lpdis->hDC, GetSysColor(COLOR_GRAYTEXT)); else rgbText = SetTextColor (lpdis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); } else { rgbBack = SetBkColor (lpdis->hDC, GetSysColor(COLOR_MENU)); if (lpdis->itemState & ODS_DISABLED) rgbText = SetTextColor (lpdis->hDC, GetSysColor(COLOR_GRAYTEXT)); else rgbText = SetTextColor (lpdis->hDC, GetSysColor(COLOR_WINDOWTEXT)); } // Clear the item ExtTextOutWrapW(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, ETO_OPAQUE, &lpdis->rcItem, NULL, 0, NULL); dx = 4; if (hIcon) { x = (WORD) lpdis->rcItem.left + dx; y = (WORD) lpdis->rcItem.top + (INT)(((lpdis->rcItem.bottom - lpdis->rcItem.top) / 2) - (INT)(cyIcon / 2)); DrawIconEx(lpdis->hDC, x, y, lpAttach->hIcon, cxIcon, cyIcon, NULL, NULL, DI_NORMAL); } // Create the menu font m_pFntCache->GetFont(FNT_SYS_MENU, hCharset, &hFont); if (hFont) hFontOld = (HFONT)SelectObject (lpdis->hDC, hFont); rc = lpdis->rcItem; rc.left += (cxIcon + (2*dx)); rc.right -= 2*dx; DrawTextExWrapW(lpdis->hDC, pszDisplay, lstrlenW(pszDisplay), &rc, DT_END_ELLIPSIS|DT_SINGLELINE|DT_VCENTER|DT_WORDBREAK|DT_NOPREFIX, NULL); if (hFont) SelectObject (lpdis->hDC, hFontOld); // Reset Text Colors SetTextColor (lpdis->hDC, rgbText); SetBkColor (lpdis->hDC, rgbBack); return S_OK; } HRESULT CAttMenu::OnMenuSelect(HWND hwnd, WPARAM wParam, LPARAM lParam) { LPATTACHDATA pAttach; WCHAR wszRes[CCHMAX_STRINGRES], wsz[CCHMAX_STRINGRES + ARRAYSIZE(pAttach->szDisplay)]; if (!m_pFrame) return E_FAIL; if (HIWORD(wParam)&MF_OWNERDRAW) { if (LOWORD(wParam) == idmSaveAllAttach) { SideAssert(LoadStringWrapW(g_hLocRes, idsSaveAllAttachMH, wszRes, ARRAYSIZE(wszRes))); m_pFrame->SetStatusText(wszRes); return S_OK; } if (FindItem(LOWORD(wParam), FALSE, &pAttach)==S_OK) { // if we're showing the context menu, rather than the save-attachment menu then we offer menuhelp // in the Form of 'Opens the attachment'. If a right-click context, do a save LoadStringWrapW(g_hLocRes, (m_uVerb == AV_OPEN) ? idsOpenAttachControl : idsSaveAttachControl, wszRes, ARRAYSIZE(wszRes)); wnsprintfW(wsz, ARRAYSIZE(wsz), wszRes, pAttach->szDisplay); m_pFrame->SetStatusText(wsz); return S_OK; } } return S_FALSE; } HRESULT CAttMenu::SubClassWindow(HWND hwnd, BOOL fOn) { if (fOn) { Assert (!m_pfnWndProc); SetProp(hwnd, c_szSubThisPtr, (HANDLE)this); m_pfnWndProc = (WNDPROC)SetWindowLongPtrAthW(hwnd, GWLP_WNDPROC, (LONG_PTR)ExtSubClassProc); } else { Assert (m_pfnWndProc); SetWindowLongPtrAthW(hwnd, GWLP_WNDPROC, (LONG_PTR)m_pfnWndProc); RemoveProp(hwnd, c_szSubThisPtr); m_pfnWndProc=NULL; } return S_OK; } LRESULT CAttMenu::ExtSubClassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { CAttMenu *pAttMenu = (CAttMenu *)GetProp(hwnd, c_szSubThisPtr); Assert (pAttMenu); switch (msg) { case WM_MEASUREITEM: pAttMenu->OnMeasureItem(hwnd, (LPMEASUREITEMSTRUCT)lParam); break; case WM_DRAWITEM: pAttMenu->OnDrawItem(hwnd, (LPDRAWITEMSTRUCT)lParam); break; case WM_MENUSELECT: pAttMenu->OnMenuSelect(hwnd, wParam, lParam); break; } return CallWindowProcWrapW(pAttMenu->m_pfnWndProc, hwnd, msg, wParam, lParam); } HRESULT CAttMenu::FindItem(int idm, BOOL fByPos, LPATTACHDATA *ppAttach) { MENUITEMINFO mii; mii.cbSize = sizeof(MENUITEMINFO); mii.fMask = MIIM_DATA; Assert (ppAttach); if (GetMenuItemInfo(m_hMenu, idm, fByPos, &mii) && mii.dwItemData) { *ppAttach = (LPATTACHDATA)mii.dwItemData; return S_OK; } return E_FAIL; } HRESULT CAttMenu::HasAttach() { return m_cVisibleAttach ? S_OK:S_FALSE; } HRESULT CAttMenu::HasEnabledAttach() { return m_cEnabledAttach ? S_OK:S_FALSE; } HRESULT CAttMenu::HasVCard() { return m_hVCard ? S_OK:S_FALSE; } HRESULT CAttMenu::LaunchVCard(HWND hwnd) { if (!m_hVCard) return E_FAIL; if (m_pAttachVCard==NULL && HrAttachDataFromBodyPart(m_pMsg, m_hVCard, &m_pAttachVCard)!=S_OK) return E_FAIL; return HrDoAttachmentVerb(hwnd, AV_OPEN, m_pMsg, m_pAttachVCard); } HRESULT CAttMenu::HrSaveAll(HWND hwnd) { return SaveAttachmentsWithPath(hwnd, m_pHostCmdTarget, m_pMsg); } HRESULT SaveAttachmentsWithPath(HWND hwnd, IOleCommandTarget *pCmdTarget, IMimeMessage *pMsg) { VARIANTARG va; WCHAR rgchPath[MAX_PATH]; HRESULT hr; BOOL fAllowUnsafe = FALSE; *rgchPath = 0; if (pCmdTarget && pCmdTarget->Exec(&CMDSETID_MimeEditHost, MEHOSTCMDID_SAVEATTACH_PATH, 0, NULL, &va)==S_OK && va.vt == VT_BSTR && va.bstrVal) { StrCpyNW(rgchPath, va.bstrVal, ARRAYSIZE(rgchPath)); SysFreeString(va.bstrVal); } if (pCmdTarget && pCmdTarget->Exec(&CMDSETID_MimeEditHost, MEHOSTCMDID_UNSAFEATTACHMENTS, 0, NULL, &va)==S_OK && va.vt == VT_I4 && va.lVal == 0) fAllowUnsafe = TRUE; hr = HrSaveAttachments(hwnd, pMsg, rgchPath, ARRAYSIZE(rgchPath), fAllowUnsafe); if (hr == S_OK) { // if successful, then set the save attachment path if (pCmdTarget) { DWORD cchPath = lstrlenW(rgchPath); va.bstrVal = SysAllocStringLen(NULL, cchPath); if (va.bstrVal) { StrCpyNW(va.bstrVal, rgchPath, cchPath); va.vt = VT_BSTR; pCmdTarget->Exec(&CMDSETID_MimeEditHost, MEHOSTCMDID_SAVEATTACH_PATH, 0, &va, NULL); SysFreeString(va.bstrVal); } else TraceResult(hr = E_OUTOFMEMORY); } } return hr; } HRESULT CAttMenu::ScanForAttachmentCount() { ULONG uAttach, cAttach=0; LPSTR psz; PROPVARIANT pv; VARIANTARG va; // we quickly need to determine if there's a Vcard and or/attachments // so the preview pane can update the icons. When clicked on, we then defer-load the // actual info. Assert(m_rghAttach == NULL); Assert(m_cVisibleAttach == 0); Assert(m_cEnabledAttach == 0); Assert(m_cAttach == 0); if (m_pHostCmdTarget && m_pHostCmdTarget->Exec(&CMDSETID_MimeEditHost, MEHOSTCMDID_UNSAFEATTACHMENTS, 0, NULL, &va)==S_OK && va.vt == VT_I4 && va.lVal == 0) m_fAllowUnsafe = TRUE; if (m_pMsg && m_pMsg->GetAttachments(&cAttach, &m_rghAttach)==S_OK) { for (uAttach=0; uAttach0); m_cVisibleAttach--; m_cEnabledAttach--; } if (m_fAllowUnsafe) // all visible attachments are enabled if we allow all files m_cEnabledAttach = m_cVisibleAttach; return S_OK; }