Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1112 lines
36 KiB

  1. #include "precomp.h" // pch file
  2. #include "sendto.h"
  3. #pragma hdrstop
  4. CLIPFORMAT g_cfShellURL = 0;
  5. CLIPFORMAT g_cfFileContents = 0;
  6. CLIPFORMAT g_cfFileDescA = 0;
  7. CLIPFORMAT g_cfFileDescW = 0;
  8. CLIPFORMAT g_cfHIDA = 0;
  9. // registry key for recompressing settings
  10. struct
  11. {
  12. int cx;
  13. int cy;
  14. int iQuality;
  15. }
  16. _aQuality[] =
  17. {
  18. { 640, 480, 80 }, // low quality
  19. { 800, 600, 80 }, // medium quality
  20. { 1024, 768, 80 }, // high quality
  21. };
  22. #define QUALITY_LOW 0
  23. #define QUALITY_MEDIUM 1
  24. #define QUALITY_HIGH 2
  25. #define RESPONSE_UNKNOWN 0
  26. #define RESPONSE_CANCEL 1
  27. #define RESPONSE_ORIGINAL 2
  28. #define RESPONSE_RECOMPRESS 3
  29. #define RECTWIDTH(rc) ((rc).right-(rc).left)
  30. #define RECTHEIGHT(rc) ((rc).bottom-(rc).top)
  31. // these bits are set by the user (holding down the keys) durring drag drop,
  32. // but more importantly, they are set in the SimulateDragDrop() call that the
  33. // browser implements to get the "Send Page..." vs "Send Link..." feature
  34. #define IS_FORCE_LINK(grfKeyState) ((grfKeyState == (MK_LBUTTON | MK_CONTROL | MK_SHIFT)) || \
  35. (grfKeyState == (MK_LBUTTON | MK_ALT)))
  36. #define IS_FORCE_COPY(grfKeyState) (grfKeyState == (MK_LBUTTON | MK_CONTROL))
  37. // constructor / destructor
  38. CSendTo::CSendTo(CLSID clsid) :
  39. _clsid(clsid), _cRef(1), _iRecompSetting(QUALITY_LOW)
  40. {
  41. DllAddRef();
  42. }
  43. CSendTo::~CSendTo()
  44. {
  45. if (_pStorageTemp)
  46. _pStorageTemp->Release();
  47. DllRelease();
  48. }
  49. STDMETHODIMP CSendTo::QueryInterface(REFIID riid, void **ppv)
  50. {
  51. static const QITAB qit[] =
  52. {
  53. QITABENT(CSendTo, IShellExtInit),
  54. QITABENT(CSendTo, IDropTarget),
  55. QITABENT(CSendTo, IPersistFile),
  56. { 0 },
  57. };
  58. return QISearch(this, qit, riid, ppv);
  59. }
  60. STDMETHODIMP_(ULONG) CSendTo::AddRef()
  61. {
  62. return InterlockedIncrement(&_cRef);
  63. }
  64. STDMETHODIMP_(ULONG) CSendTo::Release()
  65. {
  66. if (InterlockedDecrement(&_cRef))
  67. return _cRef;
  68. delete this;
  69. return 0;
  70. }
  71. STDMETHODIMP CSendTo::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
  72. {
  73. TraceMsg(DM_TRACE, "CSendTo::DragEnter");
  74. _grfKeyStateLast = grfKeyState;
  75. _dwEffectLast = *pdwEffect;
  76. return S_OK;
  77. }
  78. STDMETHODIMP CSendTo::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
  79. {
  80. *pdwEffect &= ~DROPEFFECT_MOVE;
  81. if (IS_FORCE_COPY(grfKeyState))
  82. *pdwEffect &= DROPEFFECT_COPY;
  83. else if (IS_FORCE_LINK(grfKeyState))
  84. *pdwEffect &= DROPEFFECT_LINK;
  85. _grfKeyStateLast = grfKeyState;
  86. _dwEffectLast = *pdwEffect;
  87. return S_OK;
  88. }
  89. STDMETHODIMP CSendTo::Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
  90. {
  91. HRESULT hr = v_DropHandler(pdtobj, _grfKeyStateLast, _dwEffectLast);
  92. *pdwEffect = DROPEFFECT_COPY; // don't let source delete data
  93. return hr;
  94. }
  95. //
  96. // helper methods
  97. //
  98. int CSendTo::_PathCleanupSpec(/*IN OPTIONAL*/ LPCTSTR pszDir, /*IN OUT*/ LPTSTR pszSpec)
  99. {
  100. if (RunningOnNT())
  101. {
  102. WCHAR wzDir[MAX_PATH];
  103. WCHAR wzSpec[MAX_PATH];
  104. LPWSTR pwszDir = wzDir;
  105. int iRet;
  106. if (pszDir)
  107. SHTCharToUnicode(pszDir, wzDir, ARRAYSIZE(wzDir));
  108. else
  109. pwszDir = NULL;
  110. SHTCharToUnicode(pszSpec, wzSpec, ARRAYSIZE(wzSpec));
  111. iRet = PathCleanupSpec((LPTSTR)pwszDir, (LPTSTR)wzSpec);
  112. SHUnicodeToTChar(wzSpec, pszSpec, MAX_PATH);
  113. return iRet;
  114. }
  115. else
  116. {
  117. CHAR szDir[MAX_PATH];
  118. CHAR szSpec[MAX_PATH];
  119. LPSTR pszDir2 = szDir;
  120. int iRet;
  121. if (pszDir)
  122. SHTCharToAnsi(pszDir, szDir, ARRAYSIZE(szDir));
  123. else
  124. pszDir2 = NULL;
  125. SHTCharToAnsi(pszSpec, szSpec, ARRAYSIZE(szSpec));
  126. iRet = PathCleanupSpec((LPTSTR)pszDir2, (LPTSTR)szSpec);
  127. SHAnsiToTChar(szSpec, pszSpec, MAX_PATH);
  128. return iRet;
  129. }
  130. }
  131. HRESULT CSendTo::_CreateShortcutToPath(LPCTSTR pszPath, LPCTSTR pszTarget)
  132. {
  133. IUnknown *punk;
  134. HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IUnknown, &punk));
  135. if (SUCCEEDED(hr))
  136. {
  137. ShellLinkSetPath(punk, pszTarget);
  138. IPersistFile *ppf;
  139. hr = punk->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
  140. if (SUCCEEDED(hr))
  141. {
  142. WCHAR wszPath[MAX_PATH];
  143. SHTCharToUnicode(pszPath, wszPath, ARRAYSIZE(wszPath));
  144. hr = ppf->Save(wszPath, TRUE);
  145. ppf->Release();
  146. }
  147. punk->Release();
  148. }
  149. return hr;
  150. }
  151. // thunk A/W funciton to access A/W FILEGROUPDESCRIPTOR
  152. // this relies on the fact that the first part of the A/W structures are
  153. // identical. only the string buffer part is different. so all accesses to the
  154. // cFileName field need to go through this function.
  155. FILEDESCRIPTOR* CSendTo::_GetFileDescriptor(FILEGROUPDESCRIPTOR *pfgd, BOOL fUnicode, int nIndex, LPTSTR pszName)
  156. {
  157. if (fUnicode)
  158. {
  159. // Yes, so grab the data because it matches.
  160. FILEGROUPDESCRIPTORW * pfgdW = (FILEGROUPDESCRIPTORW *)pfgd; // cast to what this really is
  161. if (pszName)
  162. SHUnicodeToTChar(pfgdW->fgd[nIndex].cFileName, pszName, MAX_PATH);
  163. return (FILEDESCRIPTOR *)&pfgdW->fgd[nIndex]; // cast assume the non string parts are the same!
  164. }
  165. else
  166. {
  167. FILEGROUPDESCRIPTORA *pfgdA = (FILEGROUPDESCRIPTORA *)pfgd; // cast to what this really is
  168. if (pszName)
  169. SHAnsiToTChar(pfgdA->fgd[nIndex].cFileName, pszName, MAX_PATH);
  170. return (FILEDESCRIPTOR *)&pfgdA->fgd[nIndex]; // cast assume the non string parts are the same!
  171. }
  172. }
  173. // our own impl since URLMON IStream::CopyTo is busted, danpoz will be fixing this
  174. HRESULT CSendTo::_StreamCopyTo(IStream *pstmFrom, IStream *pstmTo, LARGE_INTEGER cb, LARGE_INTEGER *pcbRead, LARGE_INTEGER *pcbWritten)
  175. {
  176. BYTE buf[512];
  177. ULONG cbRead;
  178. HRESULT hr = S_OK;
  179. if (pcbRead)
  180. {
  181. pcbRead->LowPart = 0;
  182. pcbRead->HighPart = 0;
  183. }
  184. if (pcbWritten)
  185. {
  186. pcbWritten->LowPart = 0;
  187. pcbWritten->HighPart = 0;
  188. }
  189. ASSERT(cb.HighPart == 0);
  190. while (cb.LowPart)
  191. {
  192. hr = pstmFrom->Read(buf, min(cb.LowPart, SIZEOF(buf)), &cbRead);
  193. if (pcbRead)
  194. pcbRead->LowPart += cbRead;
  195. if (FAILED(hr) || (cbRead == 0))
  196. break;
  197. cb.LowPart -= cbRead;
  198. hr = pstmTo->Write(buf, cbRead, &cbRead);
  199. if (pcbWritten)
  200. pcbWritten->LowPart += cbRead;
  201. if (FAILED(hr) || (cbRead == 0))
  202. break;
  203. }
  204. return hr;
  205. }
  206. // create a temporary shortcut to a file
  207. // FEATURE: Colision is not handled here
  208. BOOL CSendTo::_CreateTempFileShortcut(LPCTSTR pszTarget, LPTSTR pszShortcut)
  209. {
  210. TCHAR szShortcutPath[MAX_PATH + 1];
  211. BOOL bSuccess = FALSE;
  212. if (GetTempPath(ARRAYSIZE(szShortcutPath), szShortcutPath))
  213. {
  214. PathAppend(szShortcutPath, PathFindFileName(pszTarget));
  215. if (IsShortcut(pszTarget))
  216. {
  217. TCHAR szTarget[MAX_PATH + 1];
  218. SHFILEOPSTRUCT shop = {0};
  219. shop.wFunc = FO_COPY;
  220. shop.pFrom = szTarget;
  221. shop.pTo = szShortcutPath;
  222. shop.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR;
  223. StrCpyN(szTarget, pszTarget, ARRAYSIZE(szTarget));
  224. szTarget[lstrlen(szTarget) + 1] = TEXT('\0');
  225. szShortcutPath[lstrlen(szShortcutPath) + 1] = TEXT('\0');
  226. bSuccess = (0 == SHFileOperation(&shop));
  227. }
  228. else
  229. {
  230. PathRenameExtension(szShortcutPath, TEXT(".lnk"));
  231. bSuccess = SUCCEEDED(_CreateShortcutToPath(szShortcutPath, pszTarget));
  232. }
  233. if (bSuccess)
  234. lstrcpyn(pszShortcut, szShortcutPath, MAX_PATH);
  235. }
  236. return bSuccess;
  237. }
  238. HRESULT CSendTo::_GetFileNameFromData(IDataObject *pdtobj, FORMATETC *pfmtetc, LPTSTR pszDescription)
  239. {
  240. STGMEDIUM medium;
  241. HRESULT hr = pdtobj->GetData(pfmtetc, &medium);
  242. if (SUCCEEDED(hr))
  243. {
  244. // NOTE: this is a TCHAR format, we depend on how we are compiled, we really
  245. // should test both the A and W formats
  246. FILEGROUPDESCRIPTOR *pfgd = (FILEGROUPDESCRIPTOR *)GlobalLock(medium.hGlobal);
  247. if (pfgd)
  248. {
  249. TCHAR szFdName[MAX_PATH]; // pfd->cFileName
  250. FILEDESCRIPTOR *pfd;
  251. // &pfgd->fgd[0], w/ thunk
  252. ASSERT(pfmtetc->cfFormat == g_cfFileDescW
  253. || pfmtetc->cfFormat == g_cfFileDescA);
  254. // for now, all callers are ANSI (other untested)
  255. //ASSERT(pfmtetc->cfFormat == g_cfFileDescA);
  256. pfd = _GetFileDescriptor(pfgd, pfmtetc->cfFormat == g_cfFileDescW, 0, szFdName);
  257. lstrcpy(pszDescription, szFdName); // pfd->cFileName
  258. GlobalUnlock(medium.hGlobal);
  259. hr = S_OK;
  260. }
  261. ReleaseStgMedium(&medium);
  262. }
  263. return hr;
  264. }
  265. // construct a nice title "<File Name> (<File Type>)"
  266. void CSendTo::_GetFileAndTypeDescFromPath(LPCTSTR pszPath, LPTSTR pszDesc)
  267. {
  268. SHFILEINFO sfi;
  269. if (!SHGetFileInfo(pszPath, 0, &sfi, sizeof(sfi), SHGFI_USEFILEATTRIBUTES | SHGFI_TYPENAME | SHGFI_DISPLAYNAME))
  270. {
  271. lstrcpyn(sfi.szDisplayName, PathFindFileName(pszPath), ARRAYSIZE(sfi.szDisplayName));
  272. sfi.szTypeName[0] = 0;
  273. }
  274. lstrcpyn(pszDesc, sfi.szDisplayName, MAX_PATH);
  275. }
  276. // pcszURL -> "ftp://ftp.microsoft.com"
  277. // pcszPath -> "c:\windows\desktop\internet\Microsoft FTP.url"
  278. HRESULT CSendTo::_CreateNewURLShortcut(LPCTSTR pcszURL, LPCTSTR pcszURLFile)
  279. {
  280. IUniformResourceLocator *purl;
  281. HRESULT hr = CoCreateInstance(CLSID_InternetShortcut, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IUniformResourceLocator, &purl));
  282. if (SUCCEEDED(hr))
  283. {
  284. hr = purl->SetURL(pcszURL, 0);
  285. if (SUCCEEDED(hr))
  286. {
  287. IPersistFile *ppf;
  288. hr = purl->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
  289. if (SUCCEEDED(hr))
  290. {
  291. WCHAR wszFile[INTERNET_MAX_URL_LENGTH];
  292. SHTCharToUnicode(pcszURLFile, wszFile, ARRAYSIZE(wszFile));
  293. hr = ppf->Save(wszFile, TRUE);
  294. ppf->Release();
  295. }
  296. }
  297. purl->Release();
  298. }
  299. return hr;
  300. }
  301. HRESULT CSendTo::_CreateURLFileToSend(IDataObject *pdtobj, MRPARAM *pmp)
  302. {
  303. MRFILEENTRY *pFile = pmp->pFiles;
  304. HRESULT hr = CSendTo::_CreateNewURLShortcut(pFile->pszTitle, pFile->pszFileName);
  305. if (SUCCEEDED(hr))
  306. {
  307. _GetFileAndTypeDescFromPath(pFile->pszFileName, pFile->pszTitle);
  308. pFile->dwFlags |= MRFILE_DELETE;
  309. }
  310. return hr;
  311. }
  312. HRESULT CSendTo::_GetHDROPFromData(IDataObject *pdtobj, FORMATETC *pfmtetc, STGMEDIUM *pmedium, DWORD grfKeyState, MRPARAM *pmp)
  313. {
  314. HRESULT hr = E_FAIL;
  315. HDROP hDrop = (HDROP)pmedium->hGlobal;
  316. pmp->nFiles = DragQueryFile(hDrop, -1, NULL, 0);
  317. if (pmp->nFiles && AllocatePMP(pmp, MAX_PATH, MAX_PATH))
  318. {
  319. int i;
  320. CFileEnum MREnum(pmp, NULL);
  321. MRFILEENTRY *pFile;
  322. for (i = 0; (pFile = MREnum.Next()) && DragQueryFile(hDrop, i, pFile->pszFileName, pmp->cchFile); ++i)
  323. {
  324. if (IS_FORCE_LINK(grfKeyState) || PathIsDirectory(pFile->pszFileName))
  325. {
  326. // Want to send a link even for the real file, we will create links to the real files
  327. // and send it.
  328. _CreateTempFileShortcut(pFile->pszFileName, pFile->pszFileName);
  329. pFile->dwFlags |= MRFILE_DELETE;
  330. }
  331. _GetFileAndTypeDescFromPath(pFile->pszFileName, pFile->pszTitle);
  332. }
  333. // If loop terminates early update our item count
  334. pmp->nFiles = i;
  335. hr = S_OK;
  336. }
  337. return hr;
  338. }
  339. // "Uniform Resource Locator" format
  340. HRESULT CSendTo::_GetURLFromData(IDataObject *pdtobj, FORMATETC *pfmtetc, STGMEDIUM *pmedium, DWORD grfKeyState, MRPARAM *pmp)
  341. {
  342. HRESULT hr = E_FAIL;
  343. // This DataObj is from the internet
  344. // NOTE: We only allow to send one file here.
  345. pmp->nFiles = 1;
  346. if (AllocatePMP(pmp, INTERNET_MAX_URL_LENGTH, MAX_PATH))
  347. {
  348. // n.b. STR not TSTR! since URLs only support ansi
  349. //lstrcpyn(pmp->pszTitle, (LPSTR)GlobalLock(pmedium->hGlobal), INTERNET_MAX_URL_LENGTH);
  350. MRFILEENTRY *pFile = pmp->pFiles;
  351. SHAnsiToTChar((LPSTR)GlobalLock(pmedium->hGlobal), pFile->pszTitle, INTERNET_MAX_URL_LENGTH);
  352. GlobalUnlock(pmedium->hGlobal);
  353. if (pFile->pszTitle[0])
  354. {
  355. // Note some of these functions depend on which OS we
  356. // are running on to know if we should pass ansi or unicode strings
  357. // to it Windows 95
  358. if (GetTempPath(MAX_PATH, pFile->pszFileName))
  359. {
  360. TCHAR szFileName[MAX_PATH];
  361. // it's an URL, which is always ANSI, but the filename
  362. // can still be wide (?)
  363. FORMATETC fmteW = {g_cfFileDescW, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  364. FORMATETC fmteA = {g_cfFileDescA, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  365. if (FAILED(_GetFileNameFromData(pdtobj, &fmteW, szFileName)))
  366. {
  367. if (FAILED(_GetFileNameFromData(pdtobj, &fmteA, szFileName)))
  368. {
  369. LoadString(g_hinst, IDS_SENDMAIL_URL_FILENAME, szFileName, ARRAYSIZE(szFileName));
  370. }
  371. }
  372. _PathCleanupSpec(pFile->pszFileName, szFileName);
  373. hr = _CreateURLFileToSend(pdtobj, pmp);
  374. }
  375. }
  376. }
  377. return hr;
  378. }
  379. // transfer FILECONTENTS/FILEGROUPDESCRIPTOR data to a temp file then send that in mail
  380. HRESULT CSendTo::_GetFileContentsFromData(IDataObject *pdtobj, FORMATETC *pfmtetc, STGMEDIUM *pmedium, DWORD grfKeyState, MRPARAM *pmp)
  381. {
  382. HRESULT hr = E_FAIL;
  383. MRFILEENTRY *pFile = NULL;
  384. // NOTE: We only allow to send one file here.
  385. pmp->nFiles = 1;
  386. if (AllocatePMP(pmp, INTERNET_MAX_URL_LENGTH, MAX_PATH))
  387. {
  388. pFile = pmp->pFiles;
  389. FILEGROUPDESCRIPTOR *pfgd = (FILEGROUPDESCRIPTOR *)GlobalLock(pmedium->hGlobal);
  390. if (pfgd)
  391. {
  392. TCHAR szFdName[MAX_PATH]; // pfd->cFileName
  393. FILEDESCRIPTOR *pfd;
  394. // &pfgd->fgd[0], w/ thunk
  395. ASSERT((pfmtetc->cfFormat == g_cfFileDescW) || (pfmtetc->cfFormat == g_cfFileDescA));
  396. pfd = _GetFileDescriptor(pfgd, pfmtetc->cfFormat == g_cfFileDescW, 0, szFdName);
  397. if (GetTempPath(MAX_PATH, pFile->pszFileName))
  398. {
  399. STGMEDIUM medium;
  400. FORMATETC fmte = {g_cfFileContents, NULL, pfmtetc->dwAspect, 0, TYMED_ISTREAM | TYMED_HGLOBAL};
  401. hr = pdtobj->GetData(&fmte, &medium);
  402. if (SUCCEEDED(hr))
  403. {
  404. PathAppend(pFile->pszFileName, szFdName); // pfd->cFileName
  405. _PathCleanupSpec(pFile->pszFileName, PathFindFileName(pFile->pszFileName));
  406. PathYetAnotherMakeUniqueNameT(pFile->pszFileName, pFile->pszFileName, NULL, NULL);
  407. IStream *pstmFile;
  408. hr = SHCreateStreamOnFile(pFile->pszFileName, STGM_WRITE | STGM_CREATE, &pstmFile);
  409. if (SUCCEEDED(hr))
  410. {
  411. switch (medium.tymed)
  412. {
  413. case TYMED_ISTREAM:
  414. {
  415. const LARGE_INTEGER li = {-1, 0}; // the whole thing
  416. hr = _StreamCopyTo(medium.pstm, pstmFile, li, NULL, NULL);
  417. break;
  418. }
  419. case TYMED_HGLOBAL:
  420. hr = pstmFile->Write(GlobalLock(medium.hGlobal),
  421. pfd->dwFlags & FD_FILESIZE ? pfd->nFileSizeLow:(DWORD)GlobalSize(medium.hGlobal),
  422. NULL);
  423. GlobalUnlock(medium.hGlobal);
  424. break;
  425. default:
  426. hr = E_FAIL;
  427. }
  428. pstmFile->Release();
  429. if (FAILED(hr))
  430. DeleteFile(pFile->pszFileName);
  431. }
  432. ReleaseStgMedium(&medium);
  433. }
  434. }
  435. GlobalUnlock(pmedium->hGlobal);
  436. }
  437. }
  438. if (SUCCEEDED(hr))
  439. {
  440. _GetFileAndTypeDescFromPath(pFile->pszFileName, pFile->pszTitle);
  441. pFile->dwFlags |= MRFILE_DELETE;
  442. if (pfmtetc->dwAspect == DVASPECT_COPY)
  443. {
  444. pmp->dwFlags |= MRPARAM_DOC; // we are sending the document
  445. // get the code page if there is one
  446. IQueryCodePage *pqcp;
  447. if (SUCCEEDED(pdtobj->QueryInterface(IID_PPV_ARG(IQueryCodePage, &pqcp))))
  448. {
  449. if (SUCCEEDED(pqcp->GetCodePage(&pmp->uiCodePage)))
  450. pmp->dwFlags |= MRPARAM_USECODEPAGE;
  451. pqcp->Release();
  452. }
  453. }
  454. }
  455. else if (pfmtetc->dwAspect == DVASPECT_COPY)
  456. {
  457. TCHAR szFailureMsg[MAX_PATH], szFailureMsgTitle[40];
  458. LoadString(g_hinst, IDS_SENDMAIL_FAILUREMSG, szFailureMsg, ARRAYSIZE(szFailureMsg));
  459. LoadString(g_hinst, IDS_SENDMAIL_TITLE, szFailureMsgTitle, ARRAYSIZE(szFailureMsgTitle));
  460. int iRet = MessageBox(NULL, szFailureMsg, szFailureMsgTitle, MB_YESNO);
  461. if (iRet == IDNO)
  462. hr = S_FALSE; // convert to success to we don't try DVASPECT_LINK
  463. }
  464. return hr;
  465. }
  466. // generate a set of files from the data object
  467. typedef struct
  468. {
  469. INT format;
  470. FORMATETC fmte;
  471. } DATA_HANDLER;
  472. #define GET_FILECONTENT 0
  473. #define GET_HDROP 1
  474. #define GET_URL 2
  475. // Note: If this function returns E_CANCELLED that tells the caller that the user requested us to cancel the
  476. // sendmail operation.
  477. HRESULT CSendTo::CreateSendToFilesFromDataObj(IDataObject *pdtobj, DWORD grfKeyState, MRPARAM *pmp)
  478. {
  479. HRESULT hr;
  480. DWORD dwAspectPrefered;
  481. IEnumFORMATETC *penum;
  482. if (g_cfShellURL == 0)
  483. {
  484. g_cfShellURL = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_SHELLURL); // URL is always ANSI
  485. g_cfFileContents = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_FILECONTENTS);
  486. g_cfFileDescA = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
  487. g_cfFileDescW = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
  488. }
  489. if (IS_FORCE_COPY(grfKeyState))
  490. dwAspectPrefered = DVASPECT_COPY;
  491. else if (IS_FORCE_LINK(grfKeyState))
  492. dwAspectPrefered = DVASPECT_LINK;
  493. else
  494. dwAspectPrefered = DVASPECT_CONTENT;
  495. hr = pdtobj->EnumFormatEtc(DATADIR_GET, &penum);
  496. if (SUCCEEDED(hr))
  497. {
  498. DATA_HANDLER rg_data_handlers[] = {
  499. GET_FILECONTENT, {g_cfFileDescW, NULL, dwAspectPrefered, -1, TYMED_HGLOBAL},
  500. GET_FILECONTENT, {g_cfFileDescW, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL},
  501. GET_FILECONTENT, {g_cfFileDescA, NULL, dwAspectPrefered, -1, TYMED_HGLOBAL},
  502. GET_FILECONTENT, {g_cfFileDescA, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL},
  503. GET_HDROP, {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL},
  504. GET_URL, {g_cfShellURL, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL},
  505. };
  506. FORMATETC fmte;
  507. while (penum->Next(1, &fmte, NULL) == S_OK)
  508. {
  509. SHFree(fmte.ptd);
  510. fmte.ptd = NULL; // so nobody looks at it
  511. int i;
  512. for (i = 0; i < ARRAYSIZE(rg_data_handlers); i++)
  513. {
  514. if (rg_data_handlers[i].fmte.cfFormat == fmte.cfFormat &&
  515. rg_data_handlers[i].fmte.dwAspect == fmte.dwAspect)
  516. {
  517. STGMEDIUM medium;
  518. if (SUCCEEDED(pdtobj->GetData(&rg_data_handlers[i].fmte, &medium)))
  519. {
  520. switch ( rg_data_handlers[i].format )
  521. {
  522. case GET_FILECONTENT:
  523. hr = _GetFileContentsFromData(pdtobj, &fmte, &medium, grfKeyState, pmp);
  524. break;
  525. case GET_HDROP:
  526. hr = _GetHDROPFromData(pdtobj, &fmte, &medium, grfKeyState, pmp);
  527. break;
  528. case GET_URL:
  529. hr = _GetURLFromData(pdtobj, &fmte, &medium, grfKeyState, pmp);
  530. break;
  531. }
  532. ReleaseStgMedium(&medium);
  533. if (SUCCEEDED(hr))
  534. goto Done;
  535. }
  536. }
  537. }
  538. }
  539. Done:
  540. penum->Release();
  541. }
  542. if (SUCCEEDED(hr))
  543. hr = FilterPMP(pmp);
  544. return hr;
  545. }
  546. // allocate and free a file list. pmp->nFiles MUST be initialized before calling this function!
  547. BOOL CSendTo::AllocatePMP(MRPARAM *pmp, DWORD cchTitle, DWORD cchFiles)
  548. {
  549. // Remember the array sizes for overflow checks, etc.
  550. pmp->cchFile = cchFiles;
  551. pmp->cchTitle = cchTitle;
  552. // compute size of each file entry and allocate enough for the number of files we have. Also
  553. // add a TCHAR to the end of the buffer so we can do a double null termination safely while deleting files
  554. pmp->cchFileEntry = sizeof(MRFILEENTRY) + (cchTitle + cchFiles) * sizeof(TCHAR);
  555. pmp->pFiles = (MRFILEENTRY *)GlobalAlloc(GPTR, pmp->cchFileEntry * pmp->nFiles + sizeof(TCHAR));
  556. if (!pmp->pFiles)
  557. return FALSE;
  558. CFileEnum MREnum(pmp, NULL);
  559. MRFILEENTRY *pFile;
  560. // Note: The use of the enumerator here is questionable since this is the loop that initializes the
  561. // data structure. If the implementation changes in the future be sure this assumption still holds.
  562. while (pFile = MREnum.Next())
  563. {
  564. pFile->pszFileName = (LPTSTR)pFile->chBuf;
  565. pFile->pszTitle = pFile->pszFileName + cchFiles;
  566. ASSERTMSG(pFile->dwFlags == 0, "Expected zero-inited memory allocation");
  567. ASSERTMSG(pFile->pszFileName[cchFiles-1] == 0, "Expected zero-inited memory allocation");
  568. ASSERTMSG(pFile->pszTitle[cchTitle-1] == 0, "Expected zero-inited memory allocation");
  569. ASSERTMSG(pFile->pStream == NULL, "Expected zero-inited memory allocation");
  570. }
  571. return TRUE;
  572. }
  573. BOOL CSendTo::CleanupPMP(MRPARAM *pmp)
  574. {
  575. CFileEnum MREnum(pmp, NULL);
  576. MRFILEENTRY *pFile;
  577. while (pFile = MREnum.Next())
  578. {
  579. // delete the file if we are supposed to
  580. if (pFile->dwFlags & MRFILE_DELETE)
  581. DeleteFile(pFile->pszFileName);
  582. // If we held on to a temporary stream release it so the underlying data will be deleted.
  583. ATOMICRELEASE(pFile->pStream);
  584. }
  585. if (pmp->pFiles)
  586. {
  587. GlobalFree((LPVOID)pmp->pFiles);
  588. pmp->pFiles = NULL;
  589. }
  590. GlobalFree(pmp);
  591. return TRUE;
  592. }
  593. // allow files to be massaged before sending
  594. HRESULT CSendTo::FilterPMP(MRPARAM *pmp)
  595. {
  596. // lets handle the initialization of the progress dialog
  597. IActionProgress *pap;
  598. HRESULT hr = CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IActionProgress, &pap));
  599. if (SUCCEEDED(hr))
  600. {
  601. TCHAR szBuffer[MAX_PATH];
  602. IActionProgressDialog *papd;
  603. hr = pap->QueryInterface(IID_PPV_ARG(IActionProgressDialog, &papd));
  604. if (SUCCEEDED(hr))
  605. {
  606. LoadString(g_hinst, IDS_SENDMAIL_TITLE, szBuffer, ARRAYSIZE(szBuffer));
  607. hr = papd->Initialize(0x0, szBuffer, NULL);
  608. papd->Release();
  609. }
  610. if (SUCCEEDED(hr))
  611. {
  612. LoadString(g_hinst, IDS_SENDMAIL_RECOMPRESS, szBuffer, ARRAYSIZE(szBuffer));
  613. pap->UpdateText(SPTEXT_ACTIONDESCRIPTION, szBuffer, FALSE);
  614. pap->Begin(SPACTION_COPYING, SPBEGINF_NORMAL);
  615. }
  616. if (FAILED(hr))
  617. {
  618. pap->Release();
  619. pap = NULL;
  620. }
  621. }
  622. // walk the files and perform the recompress if we need to.
  623. int iResponse = RESPONSE_UNKNOWN;
  624. CFileEnum MREnum(pmp, pap);
  625. MRFILEENTRY *pFile;
  626. for (hr = S_OK; (pFile = MREnum.Next()) && SUCCEEDED(hr); )
  627. {
  628. if (pap)
  629. pap->UpdateText(SPTEXT_ACTIONDETAIL, pFile->pszFileName, TRUE);
  630. // if this is a picture then lets off the option to recompress the image
  631. if (PathIsImage(pFile->pszFileName))
  632. {
  633. LPITEMIDLIST pidl;
  634. hr = SHILCreateFromPath(pFile->pszFileName, &pidl, NULL);
  635. if (SUCCEEDED(hr))
  636. {
  637. hr = SHCreateShellItem(NULL, NULL, pidl, &_psi);
  638. if (SUCCEEDED(hr))
  639. {
  640. // if the response is unknown then we need to prompt for which type of optimization
  641. // needs to be performed.
  642. if (iResponse == RESPONSE_UNKNOWN)
  643. {
  644. // we need the link control window
  645. INITCOMMONCONTROLSEX initComctl32;
  646. initComctl32.dwSize = sizeof(initComctl32);
  647. initComctl32.dwICC = (ICC_STANDARD_CLASSES | ICC_LINK_CLASS);
  648. InitCommonControlsEx(&initComctl32);
  649. // we need a parent window
  650. HWND hwnd = GetActiveWindow();
  651. if (pap)
  652. IUnknown_GetWindow(pap, &hwnd);
  653. iResponse = (int)DialogBoxParam(g_hinst, MAKEINTRESOURCE(IDD_RECOMPRESS),
  654. hwnd, s_ConfirmDlgProc, (LPARAM)this);
  655. }
  656. // based on the response we either have cache or the dialog lets perform
  657. // that operation as needed.
  658. if (iResponse == RESPONSE_CANCEL)
  659. {
  660. hr = E_CANCELLED;
  661. }
  662. else if (iResponse == RESPONSE_RECOMPRESS)
  663. {
  664. IStorage *pstg;
  665. hr = _GetTempStorage(&pstg);
  666. if (SUCCEEDED(hr))
  667. {
  668. IImageRecompress *pir;
  669. hr = CoCreateInstance(CLSID_ImageRecompress, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IImageRecompress, &pir));
  670. if (SUCCEEDED(hr))
  671. {
  672. IStream *pstrm;
  673. hr = pir->RecompressImage(_psi, _aQuality[_iRecompSetting].cx, _aQuality[_iRecompSetting].cy, _aQuality[_iRecompSetting].iQuality, pstg, &pstrm);
  674. if (hr == S_OK)
  675. {
  676. STATSTG stat;
  677. hr = pstrm->Stat(&stat, STATFLAG_DEFAULT);
  678. if (SUCCEEDED(hr))
  679. {
  680. // its OK to delete this file now, b/c we are going to replace it with the recompressed
  681. // stream we have just generated from the source.
  682. if (pFile->dwFlags & MRFILE_DELETE)
  683. DeleteFile(pFile->pszFileName);
  684. // get the information on the recompressed object.
  685. StrCpyNW(pFile->pszFileName, _szTempPath, pmp->cchFile);
  686. PathAppend(pFile->pszFileName, stat.pwcsName);
  687. _GetFileAndTypeDescFromPath(pFile->pszFileName, pFile->pszTitle);
  688. pFile->dwFlags |= MRFILE_DELETE;
  689. pstrm->QueryInterface(IID_PPV_ARG(IStream, &pFile->pStream));
  690. CoTaskMemFree(stat.pwcsName);
  691. }
  692. pstrm->Release();
  693. }
  694. pir->Release();
  695. }
  696. pstg->Release();
  697. }
  698. }
  699. if (SUCCEEDED(hr) && pap)
  700. {
  701. BOOL fCancelled;
  702. if (SUCCEEDED(pap->QueryCancel(&fCancelled)) && fCancelled)
  703. {
  704. hr = E_CANCELLED;
  705. }
  706. }
  707. _psi->Release();
  708. }
  709. ILFree(pidl);
  710. }
  711. }
  712. }
  713. if (pap)
  714. {
  715. pap->End();
  716. pap->Release();
  717. }
  718. return hr;
  719. }
  720. HRESULT CSendTo::_GetTempStorage(IStorage **ppStorage)
  721. {
  722. *ppStorage = NULL;
  723. HRESULT hr;
  724. if (_pStorageTemp == NULL)
  725. {
  726. if (GetTempPath(ARRAYSIZE(_szTempPath), _szTempPath))
  727. {
  728. LPITEMIDLIST pidl = NULL;
  729. hr = SHILCreateFromPath(_szTempPath, &pidl, NULL);
  730. if (SUCCEEDED(hr))
  731. {
  732. hr = SHBindToObjectEx(NULL, pidl, NULL, IID_PPV_ARG(IStorage, ppStorage));
  733. if (SUCCEEDED(hr))
  734. {
  735. hr = (*ppStorage)->QueryInterface(IID_PPV_ARG(IStorage, &_pStorageTemp));
  736. }
  737. ILFree(pidl);
  738. }
  739. }
  740. else
  741. {
  742. hr = E_FAIL;
  743. }
  744. }
  745. else
  746. {
  747. hr = _pStorageTemp->QueryInterface(IID_PPV_ARG(IStorage, ppStorage));
  748. }
  749. return hr;
  750. }
  751. void CSendTo::_CollapseOptions(HWND hwnd, BOOL fHide)
  752. {
  753. _fOptionsHidden = fHide;
  754. RECT rc1, rc2;
  755. GetWindowRect(GetDlgItem(hwnd, IDC_RECOMPORIGINAL), &rc1);
  756. GetWindowRect(GetDlgItem(hwnd, IDC_RECOMPLARGE), &rc2);
  757. int cyAdjust = (rc2.top - rc1.top) * (fHide ? -1:1);
  758. // show/hide the controls we are not going to use
  759. UINT idHide[] = { IDC_RECOMPMAKETHEM, IDC_RECOMPSMALL, IDC_RECOMPMEDIUM, IDC_RECOMPLARGE };
  760. for (int i = 0; i < ARRAYSIZE(idHide); i++)
  761. {
  762. ShowWindow(GetDlgItem(hwnd, idHide[i]), fHide ? SW_HIDE:SW_SHOW);
  763. }
  764. // move the buttons at the bottom of the dialog
  765. UINT idMove[] = { IDC_RECOMPSHOWHIDE, IDOK, IDCANCEL };
  766. for (int i = 0; i < ARRAYSIZE(idMove); i++)
  767. {
  768. RECT rcItem;
  769. GetWindowRect(GetDlgItem(hwnd, idMove[i]), &rcItem);
  770. MapWindowPoints(NULL, hwnd, (LPPOINT)&rcItem, 2);
  771. SetWindowPos(GetDlgItem(hwnd, idMove[i]), NULL,
  772. rcItem.left, rcItem.top + cyAdjust, 0, 0,
  773. SWP_NOSIZE|SWP_NOZORDER);
  774. }
  775. // resize the dialog accordingly
  776. RECT rcWindow;
  777. GetWindowRect(hwnd, &rcWindow);
  778. SetWindowPos(hwnd, NULL,
  779. 0, 0, RECTWIDTH(rcWindow), RECTHEIGHT(rcWindow) + cyAdjust,
  780. SWP_NOZORDER|SWP_NOMOVE);
  781. // update the link control
  782. TCHAR szBuffer[MAX_PATH];
  783. LoadString(g_hinst, fHide ? IDS_SENDMAIL_SHOWMORE:IDS_SENDMAIL_SHOWLESS, szBuffer, ARRAYSIZE(szBuffer));
  784. SetDlgItemText(hwnd, IDC_RECOMPSHOWHIDE, szBuffer);
  785. }
  786. // dialog proc for the recompress prompt
  787. BOOL_PTR CSendTo::s_ConfirmDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
  788. {
  789. CSendTo *pst = (CSendTo*)GetWindowLongPtr(hwnd, DWLP_USER);
  790. if (msg == WM_INITDIALOG)
  791. {
  792. SetWindowLongPtr(hwnd, DWLP_USER, lParam);
  793. pst = (CSendTo*)lParam;
  794. }
  795. return pst->_ConfirmDlgProc(hwnd, msg, wParam, lParam);
  796. }
  797. BOOL_PTR CSendTo::_ConfirmDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
  798. {
  799. switch (msg)
  800. {
  801. case WM_INITDIALOG:
  802. {
  803. HWND hwndThumbnail = GetDlgItem(hwnd, IDC_RECOMPTHUMBNAIL);
  804. LONG_PTR dwStyle = GetWindowLongPtr(hwndThumbnail, GWL_STYLE);
  805. // set the default state of the dialog
  806. _CollapseOptions(hwnd, TRUE);
  807. // set the default state of the buttons
  808. CheckRadioButton(hwnd, IDC_RECOMPORIGINAL, IDC_RECOMPALL, IDC_RECOMPALL);
  809. CheckRadioButton(hwnd, IDC_RECOMPSMALL, IDC_RECOMPLARGE, IDC_RECOMPSMALL + _iRecompSetting);
  810. // get the thumbnail and show it.
  811. IExtractImage *pei;
  812. HRESULT hr = _psi->BindToHandler(NULL, BHID_SFUIObject, IID_PPV_ARG(IExtractImage, &pei));
  813. if (SUCCEEDED(hr))
  814. {
  815. RECT rcThumbnail;
  816. GetClientRect(GetDlgItem(hwnd, IDC_RECOMPTHUMBNAIL), &rcThumbnail);
  817. SIZE sz = {RECTWIDTH(rcThumbnail), RECTHEIGHT(rcThumbnail)};
  818. WCHAR szImage[MAX_PATH];
  819. DWORD dwFlags = 0;
  820. hr = pei->GetLocation(szImage, ARRAYSIZE(szImage), NULL, &sz, 24, &dwFlags);
  821. if (SUCCEEDED(hr))
  822. {
  823. HBITMAP hbmp;
  824. hr = pei->Extract(&hbmp);
  825. if (SUCCEEDED(hr))
  826. {
  827. SetWindowLongPtr(hwndThumbnail, GWL_STYLE, dwStyle | SS_BITMAP);
  828. HBITMAP hbmp2 = (HBITMAP)SendMessage(hwndThumbnail, STM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hbmp);
  829. if (hbmp2)
  830. {
  831. DeleteObject(hbmp2);
  832. }
  833. }
  834. }
  835. pei->Release();
  836. }
  837. // if that failed then lets get the icon for the file and place that into the dialog,
  838. // this is less likely to fail - I hope.
  839. if (FAILED(hr))
  840. {
  841. IPersistIDList *ppid;
  842. hr = _psi->QueryInterface(IID_PPV_ARG(IPersistIDList, &ppid));
  843. if (SUCCEEDED(hr))
  844. {
  845. LPITEMIDLIST pidl;
  846. hr = ppid->GetIDList(&pidl);
  847. if (SUCCEEDED(hr))
  848. {
  849. SHFILEINFO sfi = {0};
  850. if (SHGetFileInfo((LPCWSTR)pidl, -1, &sfi, sizeof(sfi), SHGFI_ICON|SHGFI_PIDL|SHGFI_ADDOVERLAYS))
  851. {
  852. SetWindowLongPtr(hwndThumbnail, GWL_STYLE, dwStyle | SS_ICON);
  853. HICON hIcon = (HICON)SendMessage(hwndThumbnail, STM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)sfi.hIcon);
  854. if (hIcon)
  855. {
  856. DeleteObject(hIcon);
  857. }
  858. }
  859. ILFree(pidl);
  860. }
  861. ppid->Release();
  862. }
  863. }
  864. break;
  865. }
  866. case WM_NOTIFY:
  867. {
  868. // Did they click/keyboard on the alter settings link?
  869. NMHDR *pnmh = (NMHDR *)lParam;
  870. if ((wParam == IDC_RECOMPSHOWHIDE) &&
  871. (pnmh->code == NM_CLICK || pnmh->code == NM_RETURN))
  872. {
  873. _CollapseOptions(hwnd, !_fOptionsHidden);
  874. return TRUE;
  875. }
  876. break;
  877. }
  878. case WM_COMMAND:
  879. {
  880. switch (wParam)
  881. {
  882. case IDOK:
  883. {
  884. // read back the quality index and store
  885. if (IsDlgButtonChecked(hwnd, IDC_RECOMPSMALL))
  886. _iRecompSetting = QUALITY_LOW;
  887. else if (IsDlgButtonChecked(hwnd, IDC_RECOMPMEDIUM))
  888. _iRecompSetting = QUALITY_MEDIUM;
  889. else
  890. _iRecompSetting = QUALITY_HIGH;
  891. // dismiss the dialog, returning the radio button state
  892. EndDialog(hwnd,(IsDlgButtonChecked(hwnd, IDC_RECOMPALL)) ? RESPONSE_RECOMPRESS:RESPONSE_ORIGINAL);
  893. return FALSE;
  894. }
  895. case IDCANCEL:
  896. EndDialog(hwnd, RESPONSE_CANCEL);
  897. return FALSE;
  898. default:
  899. break;
  900. }
  901. break;
  902. }
  903. default:
  904. return FALSE;
  905. }
  906. return TRUE;
  907. }