|
|
#include "precomp.h" // pch file
#include "sendto.h"
#pragma hdrstop
CLIPFORMAT g_cfShellURL = 0; CLIPFORMAT g_cfFileContents = 0; CLIPFORMAT g_cfFileDescA = 0; CLIPFORMAT g_cfFileDescW = 0; CLIPFORMAT g_cfHIDA = 0;
// registry key for recompressing settings
struct { int cx; int cy; int iQuality; } _aQuality[] = { { 640, 480, 80 }, // low quality
{ 800, 600, 80 }, // medium quality
{ 1024, 768, 80 }, // high quality
};
#define QUALITY_LOW 0
#define QUALITY_MEDIUM 1
#define QUALITY_HIGH 2
#define RESPONSE_UNKNOWN 0
#define RESPONSE_CANCEL 1
#define RESPONSE_ORIGINAL 2
#define RESPONSE_RECOMPRESS 3
#define RECTWIDTH(rc) ((rc).right-(rc).left)
#define RECTHEIGHT(rc) ((rc).bottom-(rc).top)
// these bits are set by the user (holding down the keys) durring drag drop,
// but more importantly, they are set in the SimulateDragDrop() call that the
// browser implements to get the "Send Page..." vs "Send Link..." feature
#define IS_FORCE_LINK(grfKeyState) ((grfKeyState == (MK_LBUTTON | MK_CONTROL | MK_SHIFT)) || \
(grfKeyState == (MK_LBUTTON | MK_ALT)))
#define IS_FORCE_COPY(grfKeyState) (grfKeyState == (MK_LBUTTON | MK_CONTROL))
// constructor / destructor
CSendTo::CSendTo(CLSID clsid) : _clsid(clsid), _cRef(1), _iRecompSetting(QUALITY_LOW) { DllAddRef(); }
CSendTo::~CSendTo() { if (_pStorageTemp) _pStorageTemp->Release(); DllRelease(); }
STDMETHODIMP CSendTo::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CSendTo, IShellExtInit), QITABENT(CSendTo, IDropTarget), QITABENT(CSendTo, IPersistFile), { 0 }, }; return QISearch(this, qit, riid, ppv); }
STDMETHODIMP_(ULONG) CSendTo::AddRef() { return InterlockedIncrement(&_cRef); }
STDMETHODIMP_(ULONG) CSendTo::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; }
STDMETHODIMP CSendTo::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { TraceMsg(DM_TRACE, "CSendTo::DragEnter"); _grfKeyStateLast = grfKeyState; _dwEffectLast = *pdwEffect;
return S_OK; }
STDMETHODIMP CSendTo::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { *pdwEffect &= ~DROPEFFECT_MOVE;
if (IS_FORCE_COPY(grfKeyState)) *pdwEffect &= DROPEFFECT_COPY; else if (IS_FORCE_LINK(grfKeyState)) *pdwEffect &= DROPEFFECT_LINK;
_grfKeyStateLast = grfKeyState; _dwEffectLast = *pdwEffect;
return S_OK; }
STDMETHODIMP CSendTo::Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { HRESULT hr = v_DropHandler(pdtobj, _grfKeyStateLast, _dwEffectLast); *pdwEffect = DROPEFFECT_COPY; // don't let source delete data
return hr; }
//
// helper methods
//
int CSendTo::_PathCleanupSpec(/*IN OPTIONAL*/ LPCTSTR pszDir, /*IN OUT*/ LPTSTR pszSpec) { WCHAR wzDir[MAX_PATH]; WCHAR wzSpec[MAX_PATH]; LPWSTR pwszDir = wzDir; int iRet;
if (pszDir) SHTCharToUnicode(pszDir, wzDir, ARRAYSIZE(wzDir)); else pwszDir = NULL;
SHTCharToUnicode(pszSpec, wzSpec, ARRAYSIZE(wzSpec)); iRet = PathCleanupSpec((LPTSTR)pwszDir, (LPTSTR)wzSpec);
SHUnicodeToTChar(wzSpec, pszSpec, MAX_PATH); return iRet; }
HRESULT CSendTo::_CreateShortcutToPath(LPCTSTR pszPath, LPCTSTR pszTarget) { IUnknown *punk; HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IUnknown, &punk)); if (SUCCEEDED(hr)) { ShellLinkSetPath(punk, pszTarget);
IPersistFile *ppf; hr = punk->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)); if (SUCCEEDED(hr)) { WCHAR wszPath[MAX_PATH]; SHTCharToUnicode(pszPath, wszPath, ARRAYSIZE(wszPath));
hr = ppf->Save(wszPath, TRUE); ppf->Release(); } punk->Release(); } return hr; }
// thunk A/W funciton to access A/W FILEGROUPDESCRIPTOR
// this relies on the fact that the first part of the A/W structures are
// identical. only the string buffer part is different. so all accesses to the
// cFileName field need to go through this function.
FILEDESCRIPTOR* CSendTo::_GetFileDescriptor(FILEGROUPDESCRIPTOR *pfgd, BOOL fUnicode, int nIndex, LPTSTR pszName) { if (fUnicode) { // Yes, so grab the data because it matches.
FILEGROUPDESCRIPTORW * pfgdW = (FILEGROUPDESCRIPTORW *)pfgd; // cast to what this really is
if (pszName) SHUnicodeToTChar(pfgdW->fgd[nIndex].cFileName, pszName, MAX_PATH);
return (FILEDESCRIPTOR *)&pfgdW->fgd[nIndex]; // cast assume the non string parts are the same!
} else { FILEGROUPDESCRIPTORA *pfgdA = (FILEGROUPDESCRIPTORA *)pfgd; // cast to what this really is
if (pszName) SHAnsiToTChar(pfgdA->fgd[nIndex].cFileName, pszName, MAX_PATH);
return (FILEDESCRIPTOR *)&pfgdA->fgd[nIndex]; // cast assume the non string parts are the same!
} }
// our own impl since URLMON IStream::CopyTo is busted, danpoz will be fixing this
HRESULT CSendTo::_StreamCopyTo(IStream *pstmFrom, IStream *pstmTo, LARGE_INTEGER cb, LARGE_INTEGER *pcbRead, LARGE_INTEGER *pcbWritten) { BYTE buf[512]; ULONG cbRead; HRESULT hr = S_OK;
if (pcbRead) { pcbRead->LowPart = 0; pcbRead->HighPart = 0; } if (pcbWritten) { pcbWritten->LowPart = 0; pcbWritten->HighPart = 0; }
ASSERT(cb.HighPart == 0);
while (cb.LowPart) { hr = pstmFrom->Read(buf, min(cb.LowPart, SIZEOF(buf)), &cbRead);
if (pcbRead) pcbRead->LowPart += cbRead;
if (FAILED(hr) || (cbRead == 0)) break;
cb.LowPart -= cbRead;
hr = pstmTo->Write(buf, cbRead, &cbRead);
if (pcbWritten) pcbWritten->LowPart += cbRead;
if (FAILED(hr) || (cbRead == 0)) break; }
return hr; }
// create a temporary shortcut to a file
// FEATURE: Colision is not handled here
BOOL CSendTo::_CreateTempFileShortcut(LPCTSTR pszTarget, LPTSTR pszShortcut, int cchShortcut) { TCHAR szShortcutPath[MAX_PATH + 1]; BOOL bSuccess = FALSE; if (GetTempPath(ARRAYSIZE(szShortcutPath), szShortcutPath)) { PathAppend(szShortcutPath, PathFindFileName(pszTarget));
if (IsShortcut(pszTarget)) { TCHAR szTarget[MAX_PATH + 1]; SHFILEOPSTRUCT shop = {0}; shop.wFunc = FO_COPY; shop.pFrom = szTarget; shop.pTo = szShortcutPath; shop.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR;
StrCpyN(szTarget, pszTarget, ARRAYSIZE(szTarget)); szTarget[lstrlen(szTarget) + 1] = TEXT('\0');
szShortcutPath[lstrlen(szShortcutPath) + 1] = TEXT('\0');
bSuccess = (0 == SHFileOperation(&shop)); } else { PathRenameExtension(szShortcutPath, TEXT(".lnk")); bSuccess = SUCCEEDED(_CreateShortcutToPath(szShortcutPath, pszTarget)); }
if (bSuccess) StrCpyN(pszShortcut, szShortcutPath, cchShortcut); } return bSuccess; }
HRESULT CSendTo::_GetFileNameFromData(IDataObject *pdtobj, FORMATETC *pfmtetc, LPTSTR pszDescription, int cchDescription) { STGMEDIUM medium; HRESULT hr = pdtobj->GetData(pfmtetc, &medium); if (SUCCEEDED(hr)) { // NOTE: this is a TCHAR format, we depend on how we are compiled, we really
// should test both the A and W formats
FILEGROUPDESCRIPTOR *pfgd = (FILEGROUPDESCRIPTOR *)GlobalLock(medium.hGlobal); if (pfgd) { TCHAR szFdName[MAX_PATH]; // pfd->cFileName
FILEDESCRIPTOR *pfd;
// &pfgd->fgd[0], w/ thunk
ASSERT(pfmtetc->cfFormat == g_cfFileDescW || pfmtetc->cfFormat == g_cfFileDescA); // for now, all callers are ANSI (other untested)
//ASSERT(pfmtetc->cfFormat == g_cfFileDescA);
pfd = _GetFileDescriptor(pfgd, pfmtetc->cfFormat == g_cfFileDescW, 0, szFdName);
StrCpyN(pszDescription, szFdName, cchDescription); // pfd->cFileName
GlobalUnlock(medium.hGlobal); hr = S_OK; } ReleaseStgMedium(&medium); } return hr; }
// construct a nice title "<File Name> (<File Type>)"
void CSendTo::_GetFileAndTypeDescFromPath(LPCTSTR pszPath, LPTSTR pszDesc, int cchDesc) { SHFILEINFO sfi; if (!SHGetFileInfo(pszPath, 0, &sfi, sizeof(sfi), SHGFI_USEFILEATTRIBUTES | SHGFI_TYPENAME | SHGFI_DISPLAYNAME)) { StrCpyN(sfi.szDisplayName, PathFindFileName(pszPath), ARRAYSIZE(sfi.szDisplayName)); sfi.szTypeName[0] = 0; } StrCpyN(pszDesc, sfi.szDisplayName, cchDesc); }
// pcszURL -> "ftp://ftp.microsoft.com"
// pcszPath -> "c:\windows\desktop\internet\Microsoft FTP.url"
HRESULT CSendTo::_CreateNewURLShortcut(LPCTSTR pcszURL, LPCTSTR pcszURLFile) { IUniformResourceLocator *purl; HRESULT hr = CoCreateInstance(CLSID_InternetShortcut, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IUniformResourceLocator, &purl)); if (SUCCEEDED(hr)) { hr = purl->SetURL(pcszURL, 0); if (SUCCEEDED(hr)) { IPersistFile *ppf; hr = purl->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)); if (SUCCEEDED(hr)) { WCHAR wszFile[INTERNET_MAX_URL_LENGTH]; SHTCharToUnicode(pcszURLFile, wszFile, ARRAYSIZE(wszFile));
hr = ppf->Save(wszFile, TRUE); ppf->Release(); } } purl->Release(); } return hr; }
HRESULT CSendTo::_CreateURLFileToSend(IDataObject *pdtobj, MRPARAM *pmp) { MRFILEENTRY *pFile = pmp->pFiles; HRESULT hr = CSendTo::_CreateNewURLShortcut(pFile->pszTitle, pFile->pszFileName); if (SUCCEEDED(hr)) { _GetFileAndTypeDescFromPath(pFile->pszFileName, pFile->pszTitle, pmp->cchTitle); pFile->dwFlags |= MRFILE_DELETE; } return hr; }
HRESULT CSendTo::_GetHDROPFromData(IDataObject *pdtobj, FORMATETC *pfmtetc, STGMEDIUM *pmedium, DWORD grfKeyState, MRPARAM *pmp) { HRESULT hr = E_FAIL; HDROP hDrop = (HDROP)pmedium->hGlobal;
pmp->nFiles = DragQueryFile(hDrop, -1, NULL, 0);
if (pmp->nFiles && AllocatePMP(pmp, MAX_PATH, MAX_PATH)) { int i; CFileEnum MREnum(pmp, NULL); MRFILEENTRY *pFile;
for (i = 0; (pFile = MREnum.Next()) && DragQueryFile(hDrop, i, pFile->pszFileName, pmp->cchFile); ++i) { if (IS_FORCE_LINK(grfKeyState) || PathIsDirectory(pFile->pszFileName)) { // Want to send a link even for the real file, we will create links to the real files
// and send it.
_CreateTempFileShortcut(pFile->pszFileName, pFile->pszFileName, pmp->cchFile); pFile->dwFlags |= MRFILE_DELETE; }
_GetFileAndTypeDescFromPath(pFile->pszFileName, pFile->pszTitle, pmp->cchTitle); }
// If loop terminates early update our item count
pmp->nFiles = i;
hr = S_OK; } return hr; }
// "Uniform Resource Locator" format
HRESULT CSendTo::_GetURLFromData(IDataObject *pdtobj, FORMATETC *pfmtetc, STGMEDIUM *pmedium, DWORD grfKeyState, MRPARAM *pmp) { HRESULT hr = E_FAIL;
// This DataObj is from the internet
// NOTE: We only allow to send one file here.
pmp->nFiles = 1; if (AllocatePMP(pmp, INTERNET_MAX_URL_LENGTH, MAX_PATH)) { // n.b. STR not TSTR! since URLs only support ansi
//lstrcpyn(pmp->pszTitle, (LPSTR)GlobalLock(pmedium->hGlobal), INTERNET_MAX_URL_LENGTH);
MRFILEENTRY *pFile = pmp->pFiles; SHAnsiToTChar((LPSTR)GlobalLock(pmedium->hGlobal), pFile->pszTitle, INTERNET_MAX_URL_LENGTH); GlobalUnlock(pmedium->hGlobal); if (pFile->pszTitle[0]) { // Note some of these functions depend on which OS we
// are running on to know if we should pass ansi or unicode strings
// to it Windows 95
if (GetTempPath(MAX_PATH, pFile->pszFileName)) { TCHAR szFileName[MAX_PATH];
// it's an URL, which is always ANSI, but the filename
// can still be wide (?)
FORMATETC fmteW = {g_cfFileDescW, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; FORMATETC fmteA = {g_cfFileDescA, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; if (FAILED(_GetFileNameFromData(pdtobj, &fmteW, szFileName, ARRAYSIZE(szFileName)))) { if (FAILED(_GetFileNameFromData(pdtobj, &fmteA, szFileName, ARRAYSIZE(szFileName)))) { LoadString(g_hinst, IDS_SENDMAIL_URL_FILENAME, szFileName, ARRAYSIZE(szFileName)); } }
_PathCleanupSpec(pFile->pszFileName, szFileName); hr = _CreateURLFileToSend(pdtobj, pmp); } } } return hr; }
// transfer FILECONTENTS/FILEGROUPDESCRIPTOR data to a temp file then send that in mail
HRESULT CSendTo::_GetFileContentsFromData(IDataObject *pdtobj, FORMATETC *pfmtetc, STGMEDIUM *pmedium, DWORD grfKeyState, MRPARAM *pmp) { HRESULT hr = E_FAIL; MRFILEENTRY *pFile = NULL;
// NOTE: We only allow to send one file here.
pmp->nFiles = 1; if (AllocatePMP(pmp, INTERNET_MAX_URL_LENGTH, MAX_PATH)) { pFile = pmp->pFiles; FILEGROUPDESCRIPTOR *pfgd = (FILEGROUPDESCRIPTOR *)GlobalLock(pmedium->hGlobal); if (pfgd) { TCHAR szFdName[MAX_PATH]; // pfd->cFileName
FILEDESCRIPTOR *pfd;
// &pfgd->fgd[0], w/ thunk
ASSERT((pfmtetc->cfFormat == g_cfFileDescW) || (pfmtetc->cfFormat == g_cfFileDescA)); pfd = _GetFileDescriptor(pfgd, pfmtetc->cfFormat == g_cfFileDescW, 0, szFdName);
// the file we're about to create has contents from the internet.
// use the internet cache to mark it as "unsafe" so other pages that might know
// the filename can't refer to it and elevate their privileges.
// createurlcacheentry says first param has to be a unique string so just use this GUID.
if (CreateUrlCacheEntry(L"67a3caff-bedc-4090-a21e-492dd8935102", 0, NULL, pFile->pszFileName, 0)) { PathRemoveFileSpec(pFile->pszFileName); // only interested in the dir that it sits in.
DeleteUrlCacheEntry(L"67a3caff-bedc-4090-a21e-492dd8935102");
STGMEDIUM medium; FORMATETC fmte = {g_cfFileContents, NULL, pfmtetc->dwAspect, 0, TYMED_ISTREAM | TYMED_HGLOBAL}; hr = pdtobj->GetData(&fmte, &medium); if (SUCCEEDED(hr)) { PathAppend(pFile->pszFileName, szFdName); // pfd->cFileName
_PathCleanupSpec(pFile->pszFileName, PathFindFileName(pFile->pszFileName)); PathYetAnotherMakeUniqueName(pFile->pszFileName, pFile->pszFileName, NULL, NULL);
IStream *pstmFile; hr = SHCreateStreamOnFile(pFile->pszFileName, STGM_WRITE | STGM_CREATE, &pstmFile); if (SUCCEEDED(hr)) { switch (medium.tymed) { case TYMED_ISTREAM: { const LARGE_INTEGER li = {-1, 0}; // the whole thing
hr = _StreamCopyTo(medium.pstm, pstmFile, li, NULL, NULL); break; }
case TYMED_HGLOBAL: hr = pstmFile->Write(GlobalLock(medium.hGlobal), pfd->dwFlags & FD_FILESIZE ? pfd->nFileSizeLow:(DWORD)GlobalSize(medium.hGlobal), NULL); GlobalUnlock(medium.hGlobal); break;
default: hr = E_FAIL; }
pstmFile->Release(); if (FAILED(hr)) DeleteFile(pFile->pszFileName); } ReleaseStgMedium(&medium); } } GlobalUnlock(pmedium->hGlobal); } }
if (SUCCEEDED(hr)) { _GetFileAndTypeDescFromPath(pFile->pszFileName, pFile->pszTitle, pmp->cchTitle); pFile->dwFlags |= MRFILE_DELETE; if (pfmtetc->dwAspect == DVASPECT_COPY) { pmp->dwFlags |= MRPARAM_DOC; // we are sending the document
// get the code page if there is one
IQueryCodePage *pqcp; if (SUCCEEDED(pdtobj->QueryInterface(IID_PPV_ARG(IQueryCodePage, &pqcp)))) { if (SUCCEEDED(pqcp->GetCodePage(&pmp->uiCodePage))) pmp->dwFlags |= MRPARAM_USECODEPAGE; pqcp->Release(); } } } else if (pfmtetc->dwAspect == DVASPECT_COPY) { TCHAR szFailureMsg[MAX_PATH], szFailureMsgTitle[40]; LoadString(g_hinst, IDS_SENDMAIL_FAILUREMSG, szFailureMsg, ARRAYSIZE(szFailureMsg)); LoadString(g_hinst, IDS_SENDMAIL_TITLE, szFailureMsgTitle, ARRAYSIZE(szFailureMsgTitle)); int iRet = MessageBox(NULL, szFailureMsg, szFailureMsgTitle, MB_YESNO); if (iRet == IDNO) hr = S_FALSE; // convert to success to we don't try DVASPECT_LINK
}
return hr; }
// generate a set of files from the data object
typedef struct { INT format; FORMATETC fmte; } DATA_HANDLER;
#define GET_FILECONTENT 0
#define GET_HDROP 1
#define GET_URL 2
// Note: If this function returns E_CANCELLED that tells the caller that the user requested us to cancel the
// sendmail operation.
HRESULT CSendTo::CreateSendToFilesFromDataObj(IDataObject *pdtobj, DWORD grfKeyState, MRPARAM *pmp) { HRESULT hr; DWORD dwAspectPrefered; IEnumFORMATETC *penum;
if (g_cfShellURL == 0) { g_cfShellURL = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_SHELLURL); // URL is always ANSI
g_cfFileContents = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_FILECONTENTS); g_cfFileDescA = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA); g_cfFileDescW = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW); }
if (IS_FORCE_COPY(grfKeyState)) dwAspectPrefered = DVASPECT_COPY; else if (IS_FORCE_LINK(grfKeyState)) dwAspectPrefered = DVASPECT_LINK; else dwAspectPrefered = DVASPECT_CONTENT;
hr = pdtobj->EnumFormatEtc(DATADIR_GET, &penum); if (SUCCEEDED(hr)) { DATA_HANDLER rg_data_handlers[] = { GET_FILECONTENT, {g_cfFileDescW, NULL, dwAspectPrefered, -1, TYMED_HGLOBAL}, GET_FILECONTENT, {g_cfFileDescW, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}, GET_FILECONTENT, {g_cfFileDescA, NULL, dwAspectPrefered, -1, TYMED_HGLOBAL}, GET_FILECONTENT, {g_cfFileDescA, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}, GET_HDROP, {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}, GET_URL, {g_cfShellURL, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}, };
FORMATETC fmte; while (penum->Next(1, &fmte, NULL) == S_OK) { SHFree(fmte.ptd); fmte.ptd = NULL; // so nobody looks at it
int i; for (i = 0; i < ARRAYSIZE(rg_data_handlers); i++) { if (rg_data_handlers[i].fmte.cfFormat == fmte.cfFormat && rg_data_handlers[i].fmte.dwAspect == fmte.dwAspect) { STGMEDIUM medium; if (SUCCEEDED(pdtobj->GetData(&rg_data_handlers[i].fmte, &medium))) { switch ( rg_data_handlers[i].format ) { case GET_FILECONTENT: hr = _GetFileContentsFromData(pdtobj, &fmte, &medium, grfKeyState, pmp); break; case GET_HDROP: hr = _GetHDROPFromData(pdtobj, &fmte, &medium, grfKeyState, pmp); break;
case GET_URL: hr = _GetURLFromData(pdtobj, &fmte, &medium, grfKeyState, pmp); break; }
ReleaseStgMedium(&medium);
if (SUCCEEDED(hr)) goto Done; } } } } Done: penum->Release(); }
if (SUCCEEDED(hr)) hr = FilterPMP(pmp); return hr; }
// allocate and free a file list. pmp->nFiles MUST be initialized before calling this function!
BOOL CSendTo::AllocatePMP(MRPARAM *pmp, DWORD cchTitle, DWORD cchFiles) { // Remember the array sizes for overflow checks, etc.
pmp->cchFile = cchFiles; pmp->cchTitle = cchTitle;
// compute size of each file entry and allocate enough for the number of files we have. Also
// add a TCHAR to the end of the buffer so we can do a double null termination safely while deleting files
pmp->cbFileEntry = sizeof(MRFILEENTRY) + ((cchTitle + cchFiles) * sizeof(TCHAR)); pmp->pFiles = (MRFILEENTRY *)GlobalAlloc(GPTR, (pmp->cbFileEntry * pmp->nFiles) + sizeof(TCHAR)); if (!pmp->pFiles) return FALSE;
CFileEnum MREnum(pmp, NULL); MRFILEENTRY *pFile;
// Note: The use of the enumerator here is questionable since this is the loop that initializes the
// data structure. If the implementation changes in the future be sure this assumption still holds.
while (pFile = MREnum.Next()) { pFile->pszFileName = (LPTSTR)pFile->chBuf; pFile->pszTitle = pFile->pszFileName + cchFiles;
ASSERTMSG(pFile->dwFlags == 0, "Expected zero-inited memory allocation"); ASSERTMSG(pFile->pszFileName[cchFiles-1] == 0, "Expected zero-inited memory allocation"); ASSERTMSG(pFile->pszTitle[cchTitle-1] == 0, "Expected zero-inited memory allocation"); ASSERTMSG(pFile->pStream == NULL, "Expected zero-inited memory allocation"); } return TRUE; }
BOOL CSendTo::CleanupPMP(MRPARAM *pmp) { CFileEnum MREnum(pmp, NULL); MRFILEENTRY *pFile;
while (pFile = MREnum.Next()) { // delete the file if we are supposed to
if (pFile->dwFlags & MRFILE_DELETE) DeleteFile(pFile->pszFileName);
// If we held on to a temporary stream release it so the underlying data will be deleted.
ATOMICRELEASE(pFile->pStream); }
if (pmp->pFiles) { GlobalFree((LPVOID)pmp->pFiles); pmp->pFiles = NULL; }
GlobalFree(pmp); return TRUE; }
// allow files to be massaged before sending
HRESULT CSendTo::FilterPMP(MRPARAM *pmp) { // lets handle the initialization of the progress dialog
IActionProgress *pap; HRESULT hr = CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IActionProgress, &pap)); if (SUCCEEDED(hr)) { TCHAR szBuffer[MAX_PATH]; IActionProgressDialog *papd; hr = pap->QueryInterface(IID_PPV_ARG(IActionProgressDialog, &papd)); if (SUCCEEDED(hr)) { LoadString(g_hinst, IDS_SENDMAIL_TITLE, szBuffer, ARRAYSIZE(szBuffer)); hr = papd->Initialize(0x0, szBuffer, NULL); papd->Release(); } if (SUCCEEDED(hr)) { LoadString(g_hinst, IDS_SENDMAIL_RECOMPRESS, szBuffer, ARRAYSIZE(szBuffer)); pap->UpdateText(SPTEXT_ACTIONDESCRIPTION, szBuffer, FALSE); pap->Begin(SPACTION_COPYING, SPBEGINF_NORMAL); }
if (FAILED(hr)) { pap->Release(); pap = NULL; } }
// walk the files and perform the recompress if we need to.
int iResponse = RESPONSE_UNKNOWN;
CFileEnum MREnum(pmp, pap); MRFILEENTRY *pFile; for (hr = S_OK; (pFile = MREnum.Next()) && SUCCEEDED(hr); ) { if (pap) pap->UpdateText(SPTEXT_ACTIONDETAIL, pFile->pszFileName, TRUE);
// if this is a picture then lets off the option to recompress the image
if (PathIsImage(pFile->pszFileName)) { LPITEMIDLIST pidl; hr = SHILCreateFromPath(pFile->pszFileName, &pidl, NULL); if (SUCCEEDED(hr)) { hr = SHCreateShellItem(NULL, NULL, pidl, &_psi); if (SUCCEEDED(hr)) { // if the response is unknown then we need to prompt for which type of optimization
// needs to be performed.
if (iResponse == RESPONSE_UNKNOWN) { // we need the link control window
INITCOMMONCONTROLSEX initComctl32; initComctl32.dwSize = sizeof(initComctl32); initComctl32.dwICC = (ICC_STANDARD_CLASSES | ICC_LINK_CLASS); InitCommonControlsEx(&initComctl32);
// we need a parent window
HWND hwnd = GetActiveWindow(); if (pap) IUnknown_GetWindow(pap, &hwnd);
iResponse = (int)DialogBoxParam(g_hinst, MAKEINTRESOURCE(IDD_RECOMPRESS), hwnd, s_ConfirmDlgProc, (LPARAM)this);
} // based on the response we either have cache or the dialog lets perform
// that operation as needed.
if (iResponse == RESPONSE_CANCEL) { hr = E_CANCELLED; } else if (iResponse == RESPONSE_RECOMPRESS) { IStorage *pstg; hr = _GetTempStorage(&pstg); if (SUCCEEDED(hr)) { IImageRecompress *pir; hr = CoCreateInstance(CLSID_ImageRecompress, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IImageRecompress, &pir)); if (SUCCEEDED(hr)) { IStream *pstrm; hr = pir->RecompressImage(_psi, _aQuality[_iRecompSetting].cx, _aQuality[_iRecompSetting].cy, _aQuality[_iRecompSetting].iQuality, pstg, &pstrm); if (hr == S_OK) { STATSTG stat; hr = pstrm->Stat(&stat, STATFLAG_DEFAULT); if (SUCCEEDED(hr)) { // its OK to delete this file now, b/c we are going to replace it with the recompressed
// stream we have just generated from the source.
if (pFile->dwFlags & MRFILE_DELETE) DeleteFile(pFile->pszFileName);
// get the information on the recompressed object.
StrCpyNW(pFile->pszFileName, _szTempPath, pmp->cchFile); PathAppend(pFile->pszFileName, stat.pwcsName); _GetFileAndTypeDescFromPath(pFile->pszFileName, pFile->pszTitle, pmp->cchTitle);
pFile->dwFlags |= MRFILE_DELETE; pstrm->QueryInterface(IID_PPV_ARG(IStream, &pFile->pStream)); CoTaskMemFree(stat.pwcsName); } pstrm->Release(); } pir->Release(); } pstg->Release(); } }
if (SUCCEEDED(hr) && pap) { BOOL fCancelled; if (SUCCEEDED(pap->QueryCancel(&fCancelled)) && fCancelled) { hr = E_CANCELLED; } }
_psi->Release(); } ILFree(pidl); } } }
if (pap) { pap->End(); pap->Release(); }
return hr; }
HRESULT CSendTo::_GetTempStorage(IStorage **ppStorage) { *ppStorage = NULL; HRESULT hr; if (_pStorageTemp == NULL) { if (GetTempPath(ARRAYSIZE(_szTempPath), _szTempPath)) { LPITEMIDLIST pidl = NULL; hr = SHILCreateFromPath(_szTempPath, &pidl, NULL); if (SUCCEEDED(hr)) { hr = SHBindToObjectEx(NULL, pidl, NULL, IID_PPV_ARG(IStorage, ppStorage)); if (SUCCEEDED(hr)) { hr = (*ppStorage)->QueryInterface(IID_PPV_ARG(IStorage, &_pStorageTemp)); } ILFree(pidl); } } else { hr = E_FAIL; } } else { hr = _pStorageTemp->QueryInterface(IID_PPV_ARG(IStorage, ppStorage)); }
return hr; }
void CSendTo::_CollapseOptions(HWND hwnd, BOOL fHide) { _fOptionsHidden = fHide;
RECT rc1, rc2; GetWindowRect(GetDlgItem(hwnd, IDC_RECOMPORIGINAL), &rc1); GetWindowRect(GetDlgItem(hwnd, IDC_RECOMPLARGE), &rc2); int cyAdjust = (rc2.top - rc1.top) * (fHide ? -1:1);
// show/hide the controls we are not going to use
UINT idHide[] = { IDC_RECOMPMAKETHEM, IDC_RECOMPSMALL, IDC_RECOMPMEDIUM, IDC_RECOMPLARGE }; for (int i = 0; i < ARRAYSIZE(idHide); i++) { ShowWindow(GetDlgItem(hwnd, idHide[i]), fHide ? SW_HIDE:SW_SHOW); }
// move the buttons at the bottom of the dialog
UINT idMove[] = { IDC_RECOMPSHOWHIDE, IDOK, IDCANCEL }; for (int i = 0; i < ARRAYSIZE(idMove); i++) { RECT rcItem; GetWindowRect(GetDlgItem(hwnd, idMove[i]), &rcItem); MapWindowPoints(NULL, hwnd, (LPPOINT)&rcItem, 2);
SetWindowPos(GetDlgItem(hwnd, idMove[i]), NULL, rcItem.left, rcItem.top + cyAdjust, 0, 0, SWP_NOSIZE|SWP_NOZORDER); }
// resize the dialog accordingly
RECT rcWindow; GetWindowRect(hwnd, &rcWindow); SetWindowPos(hwnd, NULL, 0, 0, RECTWIDTH(rcWindow), RECTHEIGHT(rcWindow) + cyAdjust, SWP_NOZORDER|SWP_NOMOVE);
// update the link control
TCHAR szBuffer[MAX_PATH]; LoadString(g_hinst, fHide ? IDS_SENDMAIL_SHOWMORE:IDS_SENDMAIL_SHOWLESS, szBuffer, ARRAYSIZE(szBuffer)); SetDlgItemText(hwnd, IDC_RECOMPSHOWHIDE, szBuffer); }
// dialog proc for the recompress prompt
BOOL_PTR CSendTo::s_ConfirmDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { CSendTo *pst = (CSendTo*)GetWindowLongPtr(hwnd, DWLP_USER); if (msg == WM_INITDIALOG) { SetWindowLongPtr(hwnd, DWLP_USER, lParam); pst = (CSendTo*)lParam; } return pst->_ConfirmDlgProc(hwnd, msg, wParam, lParam); }
BOOL_PTR CSendTo::_ConfirmDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: { HWND hwndThumbnail = GetDlgItem(hwnd, IDC_RECOMPTHUMBNAIL); LONG_PTR dwStyle = GetWindowLongPtr(hwndThumbnail, GWL_STYLE);
// set the default state of the dialog
_CollapseOptions(hwnd, TRUE);
// set the default state of the buttons
CheckRadioButton(hwnd, IDC_RECOMPORIGINAL, IDC_RECOMPALL, IDC_RECOMPALL); CheckRadioButton(hwnd, IDC_RECOMPSMALL, IDC_RECOMPLARGE, IDC_RECOMPSMALL + _iRecompSetting);
// get the thumbnail and show it.
IExtractImage *pei; HRESULT hr = _psi->BindToHandler(NULL, BHID_SFUIObject, IID_PPV_ARG(IExtractImage, &pei)); if (SUCCEEDED(hr)) { RECT rcThumbnail; GetClientRect(GetDlgItem(hwnd, IDC_RECOMPTHUMBNAIL), &rcThumbnail);
SIZE sz = {RECTWIDTH(rcThumbnail), RECTHEIGHT(rcThumbnail)}; WCHAR szImage[MAX_PATH]; DWORD dwFlags = 0;
hr = pei->GetLocation(szImage, ARRAYSIZE(szImage), NULL, &sz, 24, &dwFlags); if (SUCCEEDED(hr)) { HBITMAP hbmp; hr = pei->Extract(&hbmp); if (SUCCEEDED(hr)) { SetWindowLongPtr(hwndThumbnail, GWL_STYLE, dwStyle | SS_BITMAP); HBITMAP hbmp2 = (HBITMAP)SendMessage(hwndThumbnail, STM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hbmp); if (hbmp2) { DeleteObject(hbmp2); } } } pei->Release(); }
// if that failed then lets get the icon for the file and place that into the dialog,
// this is less likely to fail - I hope.
if (FAILED(hr)) { IPersistIDList *ppid; hr = _psi->QueryInterface(IID_PPV_ARG(IPersistIDList, &ppid)); if (SUCCEEDED(hr)) { LPITEMIDLIST pidl; hr = ppid->GetIDList(&pidl); if (SUCCEEDED(hr)) { SHFILEINFO sfi = {0}; if (SHGetFileInfo((LPCWSTR)pidl, -1, &sfi, sizeof(sfi), SHGFI_ICON|SHGFI_PIDL|SHGFI_ADDOVERLAYS)) { SetWindowLongPtr(hwndThumbnail, GWL_STYLE, dwStyle | SS_ICON); HICON hIcon = (HICON)SendMessage(hwndThumbnail, STM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)sfi.hIcon); if (hIcon) { DeleteObject(hIcon); } } ILFree(pidl); } ppid->Release(); } }
break; }
case WM_NOTIFY: { // Did they click/keyboard on the alter settings link?
NMHDR *pnmh = (NMHDR *)lParam; if ((wParam == IDC_RECOMPSHOWHIDE) && (pnmh->code == NM_CLICK || pnmh->code == NM_RETURN)) { _CollapseOptions(hwnd, !_fOptionsHidden); return TRUE; } break; } case WM_COMMAND: { switch (wParam) { case IDOK: { // read back the quality index and store
if (IsDlgButtonChecked(hwnd, IDC_RECOMPSMALL)) _iRecompSetting = QUALITY_LOW; else if (IsDlgButtonChecked(hwnd, IDC_RECOMPMEDIUM)) _iRecompSetting = QUALITY_MEDIUM; else _iRecompSetting = QUALITY_HIGH;
// dismiss the dialog, returning the radio button state
EndDialog(hwnd,(IsDlgButtonChecked(hwnd, IDC_RECOMPALL)) ? RESPONSE_RECOMPRESS:RESPONSE_ORIGINAL); return FALSE; }
case IDCANCEL: EndDialog(hwnd, RESPONSE_CANCEL); return FALSE;
default: break; } break; } default: return FALSE; } return TRUE; }
|