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.
2601 lines
75 KiB
2601 lines
75 KiB
/////////////////////////////////////////////////////////////////////////////
|
|
// Copyright (C) 1993-1996 Microsoft Corporation. All Rights Reserved.
|
|
//
|
|
// MODULE: DragDrop.cpp
|
|
//
|
|
// PURPOSE: Implements some common IDropTarget derived interfaces
|
|
//
|
|
|
|
#include "pch.hxx"
|
|
#include "dragdrop.h"
|
|
#include "dllmain.h"
|
|
#include "shlobj.h"
|
|
#include <storutil.h>
|
|
#include <storecb.h>
|
|
#include "instance.h"
|
|
#include "demand.h"
|
|
#include "mimeutil.h"
|
|
#include "storecb.h"
|
|
#include "bodyutil.h"
|
|
#include "imsgsite.h"
|
|
#include "note.h"
|
|
#include "shlwapip.h"
|
|
#include "secutil.h"
|
|
|
|
BOOL FIsFileInsertable(HDROP hDrop, LPSTREAM *ppStream, BOOL* fHTML);
|
|
HRESULT HrAttachHDrop(HWND hwnd, IMimeMessage *pMessage, HDROP hDrop, BOOL fMakeLinks);
|
|
HRESULT HrAddAttachment(IMimeMessage *pMessage, LPWSTR pszName, LPSTREAM pStream, BOOL fLink);
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::CDropTarget
|
|
//
|
|
// PURPOSE: Simple constructor, initializes everything to NULL or zero.
|
|
//
|
|
CDropTarget::CDropTarget()
|
|
{
|
|
m_cRef = 1;
|
|
|
|
m_hwndOwner = NULL;
|
|
m_idFolder = FOLDERID_INVALID;
|
|
m_fOutbox = FALSE;
|
|
|
|
m_pDataObject = NULL;
|
|
m_cf = 0;
|
|
|
|
m_hwndDlg = 0;
|
|
m_hDrop = 0;
|
|
m_cFiles = 0;
|
|
m_iFileCur = 0;
|
|
m_pFolder = 0;
|
|
m_pStoreCB = 0;
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::~CDropTarget
|
|
//
|
|
// PURPOSE: Cleans up any leftover data.
|
|
//
|
|
CDropTarget::~CDropTarget()
|
|
{
|
|
SafeRelease(m_pDataObject);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::Initialize()
|
|
//
|
|
// PURPOSE: Initializes the drop target with the ID of the folder that will
|
|
// be the target and a window handle that can parent any UI we
|
|
// need to display.
|
|
//
|
|
// PARAMETERS:
|
|
// [in] hwndOwner - Handle of a window we can parent UI to.
|
|
// [in] idFolder - ID of the folder that will be the target.
|
|
//
|
|
// RETURN VALUE:
|
|
// E_INVALIDARG - Bogus parameter passed in
|
|
// S_OK - Happiness abounds
|
|
//
|
|
HRESULT CDropTarget::Initialize(HWND hwndOwner, FOLDERID idFolder)
|
|
{
|
|
TraceCall("CDropTarget::Initialize");
|
|
|
|
if (!IsWindow(hwndOwner) || idFolder == FOLDERID_INVALID)
|
|
return (E_INVALIDARG);
|
|
|
|
m_hwndOwner = hwndOwner;
|
|
m_idFolder = idFolder;
|
|
|
|
FOLDERINFO fi;
|
|
|
|
if (SUCCEEDED(g_pStore->GetFolderInfo(m_idFolder, &fi)))
|
|
{
|
|
m_fOutbox = (fi.tySpecial == FOLDER_OUTBOX);
|
|
g_pStore->FreeRecord(&fi);
|
|
}
|
|
|
|
return (S_OK);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::QueryInterface()
|
|
//
|
|
// PURPOSE: Returns a the requested interface if supported.
|
|
//
|
|
HRESULT CDropTarget::QueryInterface(REFIID riid, LPVOID *ppvObj)
|
|
{
|
|
if (IsEqualIID(riid, IID_IUnknown))
|
|
*ppvObj = (LPVOID) (IUnknown*)(IDropTarget*) this;
|
|
else if (IsEqualIID(riid, IID_IDropTarget))
|
|
*ppvObj = (LPVOID) (IDropTarget*) this;
|
|
else
|
|
*ppvObj = NULL;
|
|
|
|
if (*ppvObj)
|
|
{
|
|
AddRef();
|
|
return (S_OK);
|
|
}
|
|
|
|
return (E_NOINTERFACE);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CBaseDropTarget::AddRef()
|
|
//
|
|
// PURPOSE: Increments the object reference count.
|
|
//
|
|
ULONG CDropTarget::AddRef(void)
|
|
{
|
|
return (++m_cRef);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::Release()
|
|
//
|
|
// PURPOSE: Decrements the object's ref count. If the ref count hit's zero
|
|
// the object is freed.
|
|
//
|
|
ULONG CDropTarget::Release(void)
|
|
{
|
|
m_cRef--;
|
|
|
|
if (m_cRef == 0)
|
|
{
|
|
delete this;
|
|
return (0);
|
|
}
|
|
|
|
return (m_cRef);
|
|
}
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::DragEnter()
|
|
//
|
|
// PURPOSE: This get's called when the user starts dragging an object
|
|
// over our target area.
|
|
//
|
|
// PARAMETERS:
|
|
// [in] pDataObject - Pointer to the data object being dragged
|
|
// [in] grfKeyState - Pointer to the current key states
|
|
// [in] pt - Point in screen coordinates of the mouse
|
|
// [out] pdwEffect - Where we return whether this is a valid place for
|
|
// pDataObject to be dropped and if so what type of
|
|
// drop.
|
|
//
|
|
// RETURN VALUE:
|
|
// S_OK - The function succeeded.
|
|
//
|
|
HRESULT CDropTarget::DragEnter(IDataObject* pDataObject, DWORD grfKeyState,
|
|
POINTL pt, DWORD* pdwEffect)
|
|
{
|
|
IEnumFORMATETC *pEnum;
|
|
FORMATETC fe;
|
|
ULONG celtFetched;
|
|
DWORD dwEffectOut = DROPEFFECT_NONE;
|
|
|
|
Assert(m_pDataObject == NULL);
|
|
|
|
// Get the FORMATETC enumerator for this object
|
|
if (SUCCEEDED(pDataObject->EnumFormatEtc(DATADIR_GET, &pEnum)))
|
|
{
|
|
// Walk through the data types available to see if there is one we
|
|
// understand
|
|
pEnum->Reset();
|
|
|
|
while (S_OK == pEnum->Next(1, &fe, &celtFetched))
|
|
{
|
|
Assert(celtFetched == 1);
|
|
if (_ValidateDropType(fe.cfFormat, pDataObject))
|
|
{
|
|
// Figure out what the right drag effect is
|
|
dwEffectOut = _DragEffectFromFormat(pDataObject, *pdwEffect, fe.cfFormat, grfKeyState);
|
|
break;
|
|
}
|
|
}
|
|
|
|
pEnum->Release();
|
|
}
|
|
|
|
// If we allow this to be dropped on us, then keep a copy of the data object
|
|
if (dwEffectOut != DROPEFFECT_NONE)
|
|
{
|
|
m_pDataObject = pDataObject;
|
|
m_pDataObject->AddRef();
|
|
m_cf = fe.cfFormat;
|
|
}
|
|
|
|
*pdwEffect = dwEffectOut;
|
|
|
|
return (S_OK);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::DragOver()
|
|
//
|
|
// PURPOSE: This is called as the user drags an object over our target.
|
|
// If we allow this object to be dropped on us, then we will have
|
|
// a pointer in m_pDataObject.
|
|
//
|
|
// PARAMETERS:
|
|
// [in] grfKeyState - Pointer to the current key states
|
|
// [in] pt - Point in screen coordinates of the mouse
|
|
// [out] pdwEffect - Where we return whether this is a valid place for
|
|
// pDataObject to be dropped and if so what type of
|
|
// drop.
|
|
//
|
|
// RETURN VALUE:
|
|
// S_OK - The function succeeded.
|
|
//
|
|
HRESULT CDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
|
|
{
|
|
// If we don't have a stored data object from CMDT::DragEnter(), then this
|
|
// isn't a data object we have any interest in.
|
|
if (NULL == m_pDataObject)
|
|
{
|
|
*pdwEffect = DROPEFFECT_NONE;
|
|
return (S_OK);
|
|
}
|
|
|
|
// We don't care about _where_ the drop happens, just what type of effect
|
|
// should be displayed.
|
|
*pdwEffect = _DragEffectFromFormat(m_pDataObject, *pdwEffect, m_cf, grfKeyState);
|
|
|
|
return (S_OK);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::DragLeave()
|
|
//
|
|
// PURPOSE: Allows us to release any stored data we have from a successful
|
|
// DragEnter()
|
|
//
|
|
// RETURN VALUE:
|
|
// S_OK - Everything is groovy
|
|
//
|
|
HRESULT CDropTarget::DragLeave(void)
|
|
{
|
|
// Free everything up at this point.
|
|
if (NULL != m_pDataObject)
|
|
{
|
|
m_pDataObject->Release();
|
|
m_pDataObject = 0;
|
|
m_cf = 0;
|
|
}
|
|
|
|
return (S_OK);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::Drop()
|
|
//
|
|
// PURPOSE: The user has let go of the object over our target. If we
|
|
// can accept this object we will already have the pDataObject
|
|
// stored in m_pDataObject.
|
|
//
|
|
// PARAMETERS:
|
|
// [in] pDataObject - Pointer to the data object being dragged
|
|
// [in] grfKeyState - Pointer to the current key states
|
|
// [in] pt - Point in screen coordinates of the mouse
|
|
// [out] pdwEffect - Where we return whether this is a valid place for
|
|
// pDataObject to be dropped and if so what type of
|
|
// drop.
|
|
//
|
|
// RETURN VALUE:
|
|
// S_OK - Everything worked OK
|
|
//
|
|
HRESULT CDropTarget::Drop(IDataObject* pDataObject, DWORD grfKeyState,
|
|
POINTL pt, DWORD* pdwEffect)
|
|
{
|
|
IEnumFORMATETC *pEnum;
|
|
FORMATETC fe;
|
|
ULONG celtFetched;
|
|
HRESULT hr;
|
|
|
|
if (!pDataObject)
|
|
return (E_INVALIDARG);
|
|
|
|
*pdwEffect = _DragEffectFromFormat(pDataObject, *pdwEffect, m_cf, grfKeyState);
|
|
hr = _HandleDrop(m_pDataObject, *pdwEffect, m_cf, grfKeyState);
|
|
|
|
SafeRelease(m_pDataObject);
|
|
return (hr);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::_CheckRoundtrip()
|
|
//
|
|
// PURPOSE: Checks to see if the source and the target are the same.
|
|
//
|
|
// PARAMETERS:
|
|
// [in] pDataObject - Object being dragged over us
|
|
//
|
|
// RETURNS:
|
|
// TRUE if the source and destination are the same, FALSE otherwise.
|
|
//
|
|
BOOL CDropTarget::_CheckRoundtrip(IDataObject *pDataObject)
|
|
{
|
|
AssertSz(FALSE, "CDropTarget::_CheckRoundtrip() - NYI");
|
|
return (FALSE);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::_ValidateDropType()
|
|
//
|
|
// PURPOSE: Examines the the specified clipboard format to see if we can
|
|
// accept this data type.
|
|
//
|
|
// PARAMETERS:
|
|
// <in> cf - Clipboard format
|
|
//
|
|
// RETURN VALUE:
|
|
// TRUE if we understand, FALSE otherwise.
|
|
//
|
|
BOOL CDropTarget::_ValidateDropType(CLIPFORMAT cf, IDataObject *pDataObject)
|
|
{
|
|
if (!pDataObject)
|
|
return (FALSE);
|
|
|
|
// OE Folders
|
|
if (cf == CF_OEFOLDER)
|
|
return (_IsValidOEFolder(pDataObject));
|
|
|
|
// Messages
|
|
if (cf == CF_OEMESSAGES)
|
|
return (_IsValidOEMessages(pDataObject));
|
|
|
|
// Files
|
|
if (cf == CF_HDROP && !m_fOutbox)
|
|
return (TRUE);
|
|
|
|
// Text
|
|
if ((cf == CF_TEXT || cf == CF_HTML || cf == CF_UNICODETEXT) && !m_fOutbox)
|
|
return (TRUE);
|
|
|
|
return (FALSE);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::_IsValidOEFolder()
|
|
//
|
|
// PURPOSE: Checks to see if the data object contains valid OE Folder
|
|
// information for this target.
|
|
//
|
|
// PARAMETERS:
|
|
// [in] pDataObject - Data Object to check
|
|
//
|
|
// RETURN VALUE:
|
|
// Returns TRUE if it's OK to drop this here, FALSE otherwise.
|
|
//
|
|
BOOL CDropTarget::_IsValidOEFolder(IDataObject *pDataObject)
|
|
{
|
|
FORMATETC fe;
|
|
STGMEDIUM stm = {0};
|
|
FOLDERID *pidFolder;
|
|
FOLDERINFO rInfoSrc = {0};
|
|
FOLDERINFO rInfoDest = {0};
|
|
BOOL fReturn = FALSE;
|
|
|
|
TraceCall("CDropTarget::_IsValidOEFolder");
|
|
|
|
// Get the folder information from the object
|
|
SETDefFormatEtc(fe, CF_OEFOLDER, TYMED_HGLOBAL);
|
|
if (FAILED(pDataObject->GetData(&fe, &stm)))
|
|
return (FALSE);
|
|
pidFolder = (FOLDERID *) GlobalLock(stm.hGlobal);
|
|
|
|
// Moving a folder onto itself would be bad
|
|
if (*pidFolder == m_idFolder)
|
|
goto exit;
|
|
|
|
// Figure out the store type of the folder
|
|
if (FAILED(g_pStore->GetFolderInfo(*pidFolder, &rInfoSrc)))
|
|
goto exit;
|
|
|
|
// You simply cannot move news or special folders
|
|
if (rInfoSrc.tyFolder == FOLDER_NEWS || rInfoSrc.tySpecial != FOLDER_NOTSPECIAL)
|
|
goto exit;
|
|
|
|
// If it's not news, we need information about the destination
|
|
if (FAILED(g_pStore->GetFolderInfo(m_idFolder, &rInfoDest)))
|
|
goto exit;
|
|
|
|
// Local to Local OK
|
|
if (rInfoSrc.tyFolder == FOLDER_LOCAL && rInfoDest.tyFolder == FOLDER_LOCAL)
|
|
{
|
|
fReturn = TRUE;
|
|
goto exit;
|
|
}
|
|
|
|
// According to Ray, IMAP folders can't be moved. I don't know about HTTP.
|
|
if (rInfoSrc.tyFolder == FOLDER_IMAP || rInfoSrc.tyFolder == FOLDER_HTTPMAIL)
|
|
goto exit;
|
|
|
|
exit:
|
|
if (rInfoDest.pAllocated)
|
|
g_pStore->FreeRecord(&rInfoDest);
|
|
if (rInfoSrc.pAllocated)
|
|
g_pStore->FreeRecord(&rInfoSrc);
|
|
|
|
GlobalUnlock(stm.hGlobal);
|
|
ReleaseStgMedium(&stm);
|
|
|
|
return (fReturn);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::_IsValidOEMessages()
|
|
//
|
|
// PURPOSE: Checks to see if the data object contains OE Messages that can
|
|
// be dropped here.
|
|
//
|
|
// PARAMETERS:
|
|
// [in] pDataObject - Data object to verify.
|
|
//
|
|
// RETURN VALUE:
|
|
// Returns TRUE if the object contains data that can be dropped here.
|
|
//
|
|
BOOL CDropTarget::_IsValidOEMessages(IDataObject *pDataObject)
|
|
{
|
|
FORMATETC fe;
|
|
STGMEDIUM stm;
|
|
FOLDERID *pidFolder;
|
|
FOLDERINFO rInfoDest = {0};
|
|
BOOL fReturn = FALSE;
|
|
|
|
TraceCall("CDropTarget::_IsValidOEMessages");
|
|
|
|
// We don't allow dropping messages on the outbox, on server nodes,
|
|
// or on the root.
|
|
if (SUCCEEDED(g_pStore->GetFolderInfo(m_idFolder, &rInfoDest)))
|
|
{
|
|
fReturn = (0 == (rInfoDest.dwFlags & FOLDER_SERVER)) &&
|
|
(FOLDERID_ROOT != m_idFolder) &&
|
|
(FOLDER_OUTBOX != rInfoDest.tySpecial) &&
|
|
(FOLDER_NEWS != GetFolderType(m_idFolder));
|
|
|
|
g_pStore->FreeRecord(&rInfoDest);
|
|
}
|
|
|
|
return (fReturn);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::_DragEffectFromFormat()
|
|
//
|
|
// PURPOSE: Examines the keyboard state and the specified clipboard format
|
|
// and determines what the right drag effect would be.
|
|
//
|
|
// PARAMETERS:
|
|
// <in> cf - Clipboard format
|
|
// <in> grfKeyState - State of the keyboard
|
|
//
|
|
// RETURN VALUE:
|
|
// Returns one of the drag effects defined by OLE, ie DRAGEFFECT_COPY, etc.
|
|
//
|
|
DWORD CDropTarget::_DragEffectFromFormat(IDataObject *pDataObject, DWORD dwEffectOk,
|
|
CLIPFORMAT cf, DWORD grfKeyState)
|
|
{
|
|
FORMATETC fe;
|
|
STGMEDIUM stm;
|
|
BOOL fRoundTrip = FALSE;
|
|
FOLDERID *pidFolder;
|
|
|
|
// Folders are always a move
|
|
if (cf == CF_OEFOLDER)
|
|
return (DROPEFFECT_MOVE);
|
|
|
|
// Messages move or copy
|
|
if (cf == CF_OEMESSAGES)
|
|
{
|
|
SETDefFormatEtc(fe, CF_OEMESSAGES, TYMED_HGLOBAL);
|
|
if (SUCCEEDED(pDataObject->GetData(&fe, &stm)))
|
|
{
|
|
pidFolder = (FOLDERID *) GlobalLock(stm.hGlobal);
|
|
|
|
fRoundTrip = (*pidFolder == m_idFolder);
|
|
|
|
GlobalUnlock(stm.hGlobal);
|
|
ReleaseStgMedium(&stm);
|
|
}
|
|
|
|
if (fRoundTrip)
|
|
return (DROPEFFECT_NONE);
|
|
else if ((dwEffectOk & DROPEFFECT_MOVE) && !(grfKeyState & MK_CONTROL))
|
|
return (DROPEFFECT_MOVE);
|
|
else
|
|
return (DROPEFFECT_COPY);
|
|
}
|
|
|
|
// Files
|
|
if (cf == CF_HDROP)
|
|
{
|
|
if (grfKeyState & MK_SHIFT && grfKeyState & MK_CONTROL)
|
|
return (DROPEFFECT_LINK);
|
|
else
|
|
return (DROPEFFECT_COPY);
|
|
}
|
|
|
|
// If it's text or HTML, create a new note with the body filled with the
|
|
// contents
|
|
if (CF_TEXT == cf || CF_HTML == cf || CF_UNICODETEXT == cf)
|
|
return (DROPEFFECT_COPY);
|
|
|
|
return (DROPEFFECT_NONE);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::_HandleDrop()
|
|
//
|
|
// PURPOSE: Takes the dropped object and get's the data out of it that
|
|
// we care about.
|
|
//
|
|
// PARAMETERS:
|
|
// <in> pDataObject - Object being dropped on us.
|
|
// <in> cf - Format to render
|
|
// <in> grfKeyState - Keyboard state when the object was dropped.
|
|
//
|
|
// RETURN VALUE:
|
|
// S_OK if we jam on it.
|
|
//
|
|
HRESULT CDropTarget::_HandleDrop(IDataObject *pDataObject, DWORD dwEffectOk,
|
|
CLIPFORMAT cf, DWORD grfKeyState)
|
|
{
|
|
DWORD dw;
|
|
|
|
if (cf == CF_OEFOLDER)
|
|
return (_HandleFolderDrop(pDataObject));
|
|
|
|
if (cf == CF_OEMESSAGES)
|
|
{
|
|
dw = _DragEffectFromFormat(pDataObject, dwEffectOk, cf, grfKeyState);
|
|
Assert(dw == DROPEFFECT_MOVE || dw == DROPEFFECT_COPY);
|
|
|
|
return (_HandleMessageDrop(pDataObject, dw == DROPEFFECT_MOVE));
|
|
}
|
|
|
|
if (cf == CF_HDROP)
|
|
return (_HandleHDrop(pDataObject, cf, grfKeyState));
|
|
|
|
if (cf == CF_TEXT || cf == CF_HTML || cf == CF_UNICODETEXT)
|
|
return (_CreateMessageFromDrop(m_hwndOwner, pDataObject, grfKeyState));
|
|
|
|
return (DV_E_FORMATETC);
|
|
}
|
|
|
|
|
|
HRESULT CDropTarget::_HandleFolderDrop(IDataObject *pDataObject)
|
|
{
|
|
FORMATETC fe;
|
|
STGMEDIUM stm;
|
|
FOLDERID *pidFolder;
|
|
HRESULT hr = E_UNEXPECTED;
|
|
|
|
if (!pDataObject)
|
|
return (E_INVALIDARG);
|
|
|
|
// Get the data from the data object
|
|
SETDefFormatEtc(fe, CF_OEFOLDER, TYMED_HGLOBAL);
|
|
if (SUCCEEDED(pDataObject->GetData(&fe, &stm)))
|
|
{
|
|
pidFolder = (FOLDERID *) GlobalLock(stm.hGlobal);
|
|
|
|
// Tell the store to move
|
|
hr = MoveFolderProgress(m_hwndOwner, *pidFolder, m_idFolder);
|
|
|
|
GlobalUnlock(stm.hGlobal);
|
|
ReleaseStgMedium(&stm);
|
|
}
|
|
|
|
return (hr);
|
|
}
|
|
|
|
HRESULT CDropTarget::_HandleMessageDrop(IDataObject *pDataObject, BOOL fMove)
|
|
{
|
|
FORMATETC fe;
|
|
STGMEDIUM stm;
|
|
OEMESSAGES *pMsgs = 0;
|
|
HRESULT hr = E_UNEXPECTED;
|
|
|
|
// Get the data from the data object
|
|
SETDefFormatEtc(fe, CF_OEMESSAGES, TYMED_HGLOBAL);
|
|
if (SUCCEEDED(hr = pDataObject->GetData(&fe, &stm)))
|
|
{
|
|
pMsgs = (OEMESSAGES *) GlobalLock(stm.hGlobal);
|
|
|
|
hr = CopyMoveMessages(m_hwndOwner, pMsgs->idSource, m_idFolder, &pMsgs->rMsgIDList, fMove ? COPY_MESSAGE_MOVE : 0);
|
|
if (FAILED(hr))
|
|
AthErrorMessageW(m_hwndOwner, MAKEINTRESOURCEW(idsAthena), fMove ? MAKEINTRESOURCEW(idsErrMoveMsgs) : MAKEINTRESOURCEW(idsErrCopyMsgs), hr);
|
|
|
|
if (NULL != g_pInstance)
|
|
{
|
|
HRESULT hrTemp;
|
|
FOLDERINFO fiFolderInfo;
|
|
|
|
hrTemp = g_pStore->GetFolderInfo(pMsgs->idSource, &fiFolderInfo);
|
|
if (SUCCEEDED(hrTemp))
|
|
{
|
|
if (FOLDER_INBOX == fiFolderInfo.tySpecial)
|
|
g_pInstance->UpdateTrayIcon(TRAYICONACTION_REMOVE);
|
|
|
|
g_pStore->FreeRecord(&fiFolderInfo);
|
|
}
|
|
}
|
|
|
|
GlobalUnlock(stm.hGlobal);
|
|
ReleaseStgMedium(&stm);
|
|
}
|
|
|
|
return (hr);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::_HandleHDrop()
|
|
//
|
|
// PURPOSE: Examines the contents of the drop to see if these are just files
|
|
// or are they .eml or .nws files.
|
|
//
|
|
// PARAMETERS:
|
|
// [in] pDataObject
|
|
// [in] cf
|
|
// [in] grfKeyState
|
|
//
|
|
// RETURN VALUE:
|
|
// HRESULT
|
|
//
|
|
HRESULT CDropTarget::_HandleHDrop(IDataObject *pDataObject, CLIPFORMAT cf, DWORD grfKeyState)
|
|
{
|
|
FORMATETC fe;
|
|
STGMEDIUM stg = {0};
|
|
HDROP hDrop;
|
|
HRESULT hr;
|
|
BOOL fRelease = FALSE;
|
|
UINT cFiles;
|
|
BOOL fMessages = TRUE;
|
|
UINT i;
|
|
|
|
TraceCall("CDropTarget::_HandleHDrop");
|
|
|
|
// Get the data from the object
|
|
SETDefFormatEtc(fe, CF_HDROP, TYMED_HGLOBAL);
|
|
|
|
if (FAILED(hr = pDataObject->GetData(&fe, &stg)))
|
|
{
|
|
AssertSz(SUCCEEDED(hr), "CDropTarget::_HandleHDrop() - GetData() failed.");
|
|
goto exit;
|
|
}
|
|
|
|
fRelease = TRUE;
|
|
hDrop = (HDROP) GlobalLock(stg.hGlobal);
|
|
|
|
if (FOLDER_NEWS != GetFolderType(m_idFolder) && (FOLDERID_ROOT != m_idFolder) &&
|
|
(FOLDERID_LOCAL_STORE != m_idFolder) && !(FOLDER_IMAP == GetFolderType(m_idFolder) && FFolderIsServer(m_idFolder)))
|
|
{
|
|
// Look inside the data to see if any of the files are .eml or .nws
|
|
cFiles = DragQueryFileWrapW(hDrop, (UINT) -1, NULL, 0);
|
|
for (i = 0; i < cFiles; i++)
|
|
{
|
|
WCHAR wszFile[MAX_PATH];
|
|
LPWSTR pwszExt;
|
|
// Get the name of the i'th file in the drop
|
|
DragQueryFileWrapW(hDrop, i, wszFile, ARRAYSIZE(wszFile));
|
|
|
|
// Get the extension for the file
|
|
pwszExt = PathFindExtensionW(wszFile);
|
|
if (*pwszExt)
|
|
{
|
|
// Once we find the first file that isn't one of our messages, we
|
|
// can give up.
|
|
if (0 != StrCmpIW(pwszExt, c_wszEmlExt) && 0 != StrCmpIW(pwszExt, c_wszNwsExt))
|
|
{
|
|
fMessages = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fMessages = FALSE;
|
|
}
|
|
|
|
// If all of the messages were news or mail messages, we can just copy them
|
|
// into our store. If even one were normal files, then we create a new message
|
|
// with everything attached.
|
|
if (fMessages)
|
|
hr = _InsertMessagesInStore(hDrop);
|
|
else
|
|
hr = _CreateMessageFromDrop(m_hwndOwner, pDataObject, grfKeyState);
|
|
|
|
exit:
|
|
if (fRelease)
|
|
ReleaseStgMedium(&stg);
|
|
|
|
return (hr);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::_InsertMessagesInStore()
|
|
//
|
|
// PURPOSE: When the user drops messages that are stored as .nws or .eml files
|
|
// onto us, we need to integrate those files into our store.
|
|
//
|
|
// PARAMETERS:
|
|
// [in] hDrop - Contains the information needed to get the files
|
|
//
|
|
// RETURN VALUE:
|
|
// HRESULT
|
|
//
|
|
HRESULT CDropTarget::_InsertMessagesInStore(HDROP hDrop)
|
|
{
|
|
HRESULT hr;
|
|
|
|
TraceCall("CDropTarget::_InsertMessagesInStore");
|
|
|
|
// Open the folder we're saving into
|
|
if (FAILED(hr = g_pStore->OpenFolder(m_idFolder, NULL, 0, &m_pFolder)))
|
|
return (hr);
|
|
|
|
if (0 == (m_pStoreCB = new CStoreDlgCB()))
|
|
{
|
|
m_pFolder->Release();
|
|
return (E_OUTOFMEMORY);
|
|
}
|
|
|
|
// Get the count of files
|
|
m_cFiles = DragQueryFileWrapW(hDrop, (UINT) -1, NULL, 0);
|
|
m_hDrop = hDrop;
|
|
m_iFileCur = 0;
|
|
|
|
// Do the dialog
|
|
DialogBoxParamWrapW(g_hLocRes, MAKEINTRESOURCEW(iddCopyMoveMessages), m_hwndOwner,
|
|
_ProgDlgProcExt, (LPARAM) this);
|
|
|
|
// Free some stuff up
|
|
m_cFiles = 0;
|
|
m_hDrop = 0;
|
|
m_pFolder->Release();
|
|
m_pStoreCB->Release();
|
|
|
|
return (S_OK);
|
|
}
|
|
|
|
INT_PTR CALLBACK CDropTarget::_ProgDlgProcExt(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
CDropTarget *pThis;
|
|
|
|
if (msg == WM_INITDIALOG)
|
|
{
|
|
SetWindowLongPtr(hwnd, DWLP_USER, lParam);
|
|
pThis = (CDropTarget*) lParam;
|
|
}
|
|
else
|
|
pThis = (CDropTarget*) GetWindowLongPtr(hwnd, DWLP_USER);
|
|
|
|
if (pThis)
|
|
return pThis->_ProgDlgProc(hwnd, msg, wParam, lParam);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::DlgProc()
|
|
//
|
|
// PURPOSE: Groovy dialog proc.
|
|
//
|
|
INT_PTR CDropTarget::_ProgDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
HWND hwndT;
|
|
|
|
switch (msg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
return (BOOL)HANDLE_WM_INITDIALOG(hwnd, wParam, lParam, _OnInitDialog);
|
|
|
|
case WM_COMMAND:
|
|
HANDLE_WM_COMMAND(hwnd, wParam, lParam, _OnCommand);
|
|
return TRUE;
|
|
|
|
case WM_STORE_COMPLETE:
|
|
m_iFileCur++;
|
|
_SaveNextMessage();
|
|
return (TRUE);
|
|
|
|
case WM_STORE_PROGRESS:
|
|
return (TRUE);
|
|
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::_OnInitDialog()
|
|
//
|
|
// PURPOSE: Initializes the progress dialog
|
|
//
|
|
BOOL CDropTarget::_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
|
|
{
|
|
Assert(m_pStoreCB);
|
|
|
|
m_hwndDlg = hwnd;
|
|
m_pStoreCB->Initialize(hwnd);
|
|
m_pStoreCB->Reset();
|
|
|
|
// Open and save the first message
|
|
_SaveNextMessage();
|
|
|
|
return (TRUE);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::_OnCommand()
|
|
//
|
|
// PURPOSE: Handle the cancel button
|
|
//
|
|
void CDropTarget::_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
|
|
{
|
|
// User's have been known to press cancel once in a while
|
|
if (id == IDCANCEL)
|
|
{
|
|
m_pStoreCB->Cancel();
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::_SaveNextMessage()
|
|
//
|
|
// PURPOSE: Opens the next message in the drop and saves it.
|
|
//
|
|
void CDropTarget::_SaveNextMessage()
|
|
{
|
|
WCHAR wszFile[MAX_PATH],
|
|
wszRes[CCHMAX_STRINGRES],
|
|
wszBuf[CCHMAX_STRINGRES + MAX_PATH];
|
|
HRESULT hr;
|
|
IMimeMessage *pMsg = 0;
|
|
|
|
TraceCall("CDropTarget::_SaveNextMessage");
|
|
|
|
// See if we're done
|
|
if (m_iFileCur >= m_cFiles)
|
|
{
|
|
EndDialog(m_hwndDlg, 0);
|
|
return;
|
|
}
|
|
|
|
// Get the name of the i'th file in the drop
|
|
DragQueryFileWrapW(m_hDrop, m_iFileCur, wszFile, ARRAYSIZE(wszFile));
|
|
|
|
// Create a new, empty message.
|
|
if (FAILED(hr = HrCreateMessage(&pMsg)))
|
|
{
|
|
AthErrorMessageW(m_hwndDlg, MAKEINTRESOURCEW(idsAthena), MAKEINTRESOURCEW(idsMemory), E_OUTOFMEMORY);
|
|
EndDialog(m_hwndDlg, 0);
|
|
}
|
|
|
|
// Load the message from the file
|
|
hr = HrLoadMsgFromFileW(pMsg, wszFile);
|
|
if (FAILED(hr))
|
|
{
|
|
AthLoadStringW(IDS_ERROR_FILE_NOEXIST, wszRes, ARRAYSIZE(wszRes));
|
|
wnsprintfW(wszBuf, ARRAYSIZE(wszBuf), wszRes, wszFile);
|
|
|
|
AthErrorMessageW(m_hwndDlg, MAKEINTRESOURCEW(idsAthena), wszBuf, hr);
|
|
PostMessage(m_hwndDlg, WM_STORE_COMPLETE, 0, 0);
|
|
goto exit;
|
|
}
|
|
|
|
// Progress
|
|
AthLoadStringW(idsSavingFmt, wszRes, ARRAYSIZE(wszRes));
|
|
wnsprintfW(wszBuf, ARRAYSIZE(wszBuf), wszRes, wszFile);
|
|
SetDlgItemTextWrapW(m_hwndDlg, idcStatic1, wszBuf);
|
|
|
|
// Tell the store to save it
|
|
hr = m_pFolder->SaveMessage(NULL, SAVE_MESSAGE_GENID, ARF_READ, 0, pMsg, m_pStoreCB);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
PostMessage(m_hwndDlg, WM_STORE_COMPLETE, 0, 0);
|
|
goto exit;
|
|
}
|
|
|
|
if (FAILED(hr) && E_PENDING != hr)
|
|
{
|
|
AthErrorMessageW(m_hwndDlg, MAKEINTRESOURCEW(idsAthena), MAKEINTRESOURCEW(idsUnableToSaveMessage), hr);
|
|
PostMessage(m_hwndDlg, WM_STORE_COMPLETE, 0, 0);
|
|
}
|
|
|
|
exit:
|
|
SafeRelease(pMsg);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// FUNCTION: CDropTarget::_CreateMessageFromDrop()
|
|
//
|
|
// PURPOSE: This function takes an IDataObject which was dropped on Athena
|
|
// and creates a new mail message from that dropped object. If
|
|
// the dropped object supports either CF_TEXT or CF_HTML, then
|
|
// that will be streamed into the body of the message. Otherwise,
|
|
// if it supports CF_HDROP we'll add it as an attachment.
|
|
//
|
|
// PARAMETERS:
|
|
// <in> pDataObject -
|
|
// <in> pStore -
|
|
// <in> grfKeyState -
|
|
// <in> pidl -
|
|
//
|
|
// RETURN VALUE:
|
|
// E_INVALIDARG -
|
|
//
|
|
// COMMENTS:
|
|
// <???>
|
|
//
|
|
HRESULT CDropTarget::_CreateMessageFromDrop(HWND hwnd, IDataObject *pDataObject,
|
|
DWORD grfKeyState)
|
|
{
|
|
IEnumFORMATETC *pEnum = NULL;
|
|
FORMATETC fe;
|
|
DWORD celtFetched;
|
|
CLIPFORMAT cf = 0;
|
|
DWORD tymed = 0;
|
|
IMimeMessage *pMessage = NULL;
|
|
HRESULT hr = S_OK;
|
|
STGMEDIUM stg;
|
|
IStream *pStream = NULL;
|
|
BOOL fRelease = TRUE;
|
|
BOOL fIsRealCFHTML=FALSE;
|
|
|
|
ZeroMemory(&stg, sizeof(STGMEDIUM));
|
|
|
|
if (!pDataObject)
|
|
{
|
|
Assert(pDataObject);
|
|
return (E_INVALIDARG);
|
|
}
|
|
|
|
// Enumerate the formats this object supports to decide which if any we
|
|
// are going to use.
|
|
if (SUCCEEDED(pDataObject->EnumFormatEtc(DATADIR_GET, &pEnum)))
|
|
{
|
|
pEnum->Reset();
|
|
while (S_OK == pEnum->Next(1, &fe, &celtFetched))
|
|
{
|
|
// HTML is the richest format we understand. If we find that, then
|
|
// we can stop looking.
|
|
if (fe.cfFormat == CF_HTML &&
|
|
(fe.tymed & TYMED_HGLOBAL || fe.tymed & TYMED_ISTREAM))
|
|
{
|
|
DOUTL(32, _T("HrNewMailFromDrop() - Accepting CF_HTML."));
|
|
cf = (CLIPFORMAT) CF_HTML;
|
|
tymed = fe.tymed;
|
|
break;
|
|
}
|
|
|
|
// UNICODETEXT is great, but only if we can't find anything richer. So we
|
|
// accept this, but keep looking.
|
|
else if (fe.cfFormat == CF_UNICODETEXT &&
|
|
(fe.tymed & TYMED_HGLOBAL || fe.tymed & TYMED_ISTREAM))
|
|
{
|
|
DOUTL(32,_T("HrNewMailFromDrop() - Accepting CF_UNICODETEXT."));
|
|
cf = CF_UNICODETEXT;
|
|
tymed = fe.tymed;
|
|
}
|
|
|
|
// TEXT is cool, but only if we can't find anything richer. So we
|
|
// accept this, but keep looking.
|
|
else if (fe.cfFormat == CF_TEXT && cf != CF_UNICODETEXT &&
|
|
(fe.tymed & TYMED_HGLOBAL || fe.tymed & TYMED_ISTREAM))
|
|
{
|
|
DOUTL(32,_T("HrNewMailFromDrop() - Accepting CF_TEXT."));
|
|
cf = CF_TEXT;
|
|
tymed = fe.tymed;
|
|
}
|
|
|
|
// If we find HDROP, then we can create an attachment. However, we
|
|
// want to find something richer so we only accept this if we haven't
|
|
// found anything else we like.
|
|
else if (fe.cfFormat == CF_HDROP && cf == 0 && fe.tymed & TYMED_HGLOBAL)
|
|
{
|
|
cf = CF_HDROP;
|
|
tymed = fe.tymed;
|
|
}
|
|
}
|
|
|
|
pEnum->Release();
|
|
}
|
|
|
|
// Make sure we found something useful
|
|
if (0 == cf)
|
|
{
|
|
AssertSz(cf, _T("HrNewMailFromDrop() - Did not find an acceptable data")
|
|
_T(" format to create a message from."));
|
|
hr = E_UNEXPECTED;
|
|
goto exit;
|
|
}
|
|
|
|
// Set the preferred TYMED to ISTREAM and set the FORMATETC struct up to
|
|
// retrieve the data type we determined above.
|
|
if (tymed & TYMED_ISTREAM)
|
|
tymed = TYMED_ISTREAM;
|
|
else
|
|
tymed = TYMED_HGLOBAL;
|
|
SETDefFormatEtc(fe, cf, tymed);
|
|
|
|
// Get the data from the object
|
|
if (FAILED(hr = pDataObject->GetData(&fe, &stg)))
|
|
{
|
|
AssertSz(SUCCEEDED(hr), _T("HrNewMailFromDrop() - pDataObject->GetData() failed."));
|
|
goto exit;
|
|
}
|
|
|
|
// Create the Message Object
|
|
hr = HrCreateMessage(&pMessage);
|
|
if (FAILED(hr))
|
|
{
|
|
AthMessageBoxW(hwnd, MAKEINTRESOURCEW(idsAthenaMail), MAKEINTRESOURCEW(idsMemory),
|
|
0, MB_ICONSTOP | MB_OK);
|
|
goto exit;
|
|
}
|
|
|
|
// Set the body appropriately
|
|
if (cf == CF_HTML || cf == CF_TEXT || cf == CF_UNICODETEXT)
|
|
{
|
|
if (fe.tymed == TYMED_HGLOBAL)
|
|
{
|
|
if (FAILED(hr = CreateStreamOnHGlobal(stg.hGlobal, TRUE, &pStream)))
|
|
{
|
|
AssertSz(FALSE, _T("HrNewMailFromDrop() - Failed CreateStreamOnHGlobal()"));
|
|
goto exit;
|
|
}
|
|
|
|
// Bug #24846 - Need to find the actual size of the stream instead of the size
|
|
// of the HGLOBAL.
|
|
LPBYTE pb = (LPBYTE) GlobalLock(stg.hGlobal);
|
|
ULARGE_INTEGER uliSize;
|
|
uliSize.QuadPart = 0;
|
|
|
|
// Raid 77624: OE mishandles CF_UNICODETEXT / TYMED_GLOBAL
|
|
if (cf == CF_TEXT || cf == CF_HTML)
|
|
uliSize.QuadPart = lstrlen((LPSTR)pb);
|
|
else if (cf == CF_UNICODETEXT)
|
|
uliSize.QuadPart = (lstrlenW((LPWSTR)pb) * sizeof(WCHAR));
|
|
|
|
GlobalUnlock(stg.hGlobal);
|
|
pStream->SetSize(uliSize);
|
|
|
|
fRelease = FALSE;
|
|
}
|
|
else
|
|
pStream = stg.pstm;
|
|
|
|
if (cf == CF_HTML && FAILED(hr = HrStripHTMLClipboardHeader(pStream, &fIsRealCFHTML)))
|
|
goto exit;
|
|
|
|
// Set Unicode Text Body
|
|
if (cf == CF_UNICODETEXT)
|
|
{
|
|
HCHARSET hCharset;
|
|
|
|
if (SUCCEEDED(MimeOleFindCharset("UTF-8", &hCharset)))
|
|
{
|
|
pMessage->SetCharset(hCharset, CSET_APPLY_ALL);
|
|
}
|
|
|
|
pMessage->SetTextBody(TXT_PLAIN, IET_UNICODE, NULL, pStream, NULL);
|
|
}
|
|
|
|
// Set HTML Text Body
|
|
else if (cf == CF_HTML)
|
|
{
|
|
// Real CF_HTML, or OE's version of CF_HTML?
|
|
if (fIsRealCFHTML)
|
|
{
|
|
// Locals
|
|
HCHARSET hCharset;
|
|
|
|
// Map to HCHARSET - Real CF_HTML is always UTF-8
|
|
if (SUCCEEDED(MimeOleFindCharset("utf-8", &hCharset)))
|
|
{
|
|
// Set It
|
|
pMessage->SetCharset(hCharset, CSET_APPLY_ALL);
|
|
}
|
|
}
|
|
|
|
// Otherwise...
|
|
else
|
|
{
|
|
// Locals
|
|
LPSTR pszCharset=NULL;
|
|
|
|
// Sniff the charset
|
|
if (SUCCEEDED(GetHtmlCharset(pStream, &pszCharset)))
|
|
{
|
|
// Locals
|
|
HCHARSET hCharset;
|
|
|
|
// Map to HCHARSET
|
|
if (SUCCEEDED(MimeOleFindCharset(pszCharset, &hCharset)))
|
|
{
|
|
// Set It
|
|
pMessage->SetCharset(hCharset, CSET_APPLY_ALL);
|
|
}
|
|
|
|
// Cleanup
|
|
SafeMemFree(pszCharset);
|
|
}
|
|
}
|
|
|
|
// We should sniff the charset from the html document and call pMessage->SetCharset
|
|
pMessage->SetTextBody(TXT_HTML, IET_INETCSET, NULL, pStream, NULL);
|
|
}
|
|
|
|
// Set plain text, non-unicode text body
|
|
else
|
|
pMessage->SetTextBody(TXT_PLAIN, IET_BINARY, NULL, pStream, NULL);
|
|
}
|
|
|
|
// If there is a single file and the content is text/plain or text/html, then
|
|
// we insert the contents of the message as the body. Otherwise, we add each
|
|
// file as an attachment.
|
|
if (cf == CF_HDROP)
|
|
{
|
|
HDROP hDrop = (HDROP) GlobalLock(stg.hGlobal);
|
|
BOOL fHTML = FALSE,
|
|
fSetCharset = FALSE,
|
|
fUnicode = FALSE,
|
|
fLittleEndian;
|
|
|
|
if (FIsFileInsertable(hDrop, &pStream, &fHTML))
|
|
{
|
|
if(fHTML)
|
|
{
|
|
LPSTR pszCharset = NULL;
|
|
|
|
// Sniff the charset
|
|
if (FAILED(GetHtmlCharset(pStream, &pszCharset)) && (S_OK == HrIsStreamUnicode(pStream, &fLittleEndian)))
|
|
pszCharset = StrDupA("utf-8");
|
|
|
|
if(pszCharset)
|
|
{
|
|
HCHARSET hCharset = NULL;
|
|
|
|
// Map to HCHARSET
|
|
if (SUCCEEDED(MimeOleFindCharset(pszCharset, &hCharset)))
|
|
{
|
|
// Set It
|
|
if(SUCCEEDED(pMessage->SetCharset(hCharset, CSET_APPLY_ALL)))
|
|
fSetCharset = TRUE;
|
|
}
|
|
|
|
// Cleanup
|
|
SafeMemFree(pszCharset);
|
|
}
|
|
|
|
}
|
|
else if(S_OK == HrIsStreamUnicode(pStream, &fLittleEndian))
|
|
{
|
|
HCHARSET hCharset;
|
|
|
|
if (SUCCEEDED(MimeOleFindCharset("UTF-8", &hCharset)))
|
|
{
|
|
pMessage->SetCharset(hCharset, CSET_APPLY_ALL);
|
|
}
|
|
|
|
fUnicode = TRUE;
|
|
}
|
|
|
|
pMessage->SetTextBody((fHTML ? TXT_HTML : TXT_PLAIN),
|
|
(fSetCharset ? IET_INETCSET : (fUnicode ? IET_UNICODE : IET_DECODED)),
|
|
NULL, pStream, NULL);
|
|
SafeRelease(pStream);
|
|
}
|
|
else
|
|
{
|
|
// Get the drop handle and add it's contents to the message
|
|
hr = HrAttachHDrop(hwnd, pMessage, hDrop, grfKeyState & MK_CONTROL && grfKeyState & MK_SHIFT);
|
|
}
|
|
|
|
GlobalUnlock(stg.hGlobal);
|
|
}
|
|
|
|
// Last step is to instantiate the send note
|
|
INIT_MSGSITE_STRUCT initStruct;
|
|
BOOL fNews;
|
|
fNews = FALSE;
|
|
|
|
initStruct.dwInitType = OEMSIT_MSG;
|
|
initStruct.folderID = 0;
|
|
initStruct.pMsg = pMessage;
|
|
|
|
if (FOLDER_NEWS == GetFolderType(m_idFolder))
|
|
{
|
|
FOLDERINFO fi = {0};
|
|
TCHAR sz[1024];
|
|
|
|
fNews = TRUE;
|
|
|
|
if (SUCCEEDED(g_pStore->GetFolderInfo(m_idFolder, &fi)))
|
|
{
|
|
// Set some news-specific fields on the message
|
|
if ((FOLDER_SERVER & fi.dwFlags) == 0)
|
|
MimeOleSetBodyPropA(pMessage, HBODY_ROOT, PIDTOSTR(PID_HDR_NEWSGROUPS), NOFLAGS, fi.pszName);
|
|
|
|
FOLDERINFO fiServer = {0};
|
|
|
|
if (SUCCEEDED(GetFolderServer(m_idFolder, &fiServer)))
|
|
{
|
|
HrSetAccount(pMessage, fiServer.pszName);
|
|
g_pStore->FreeRecord(&fiServer);
|
|
}
|
|
|
|
g_pStore->FreeRecord(&fi);
|
|
}
|
|
}
|
|
|
|
CreateAndShowNote(OENA_COMPOSE, fNews ? OENCF_NEWSFIRST : 0, &initStruct, m_hwndOwner);
|
|
|
|
exit:
|
|
SafeRelease(pMessage);
|
|
|
|
if (fRelease)
|
|
ReleaseStgMedium(&stg);
|
|
|
|
return (hr);
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// FUNCTION: CBaseDataObject::CBaseDataObject
|
|
//
|
|
// PURPOSE: Simple constructor, initializes everything to NULL or zero.
|
|
//
|
|
CBaseDataObject::CBaseDataObject()
|
|
{
|
|
m_cRef = 1;
|
|
|
|
ZeroMemory(m_rgFormatEtc, sizeof(m_rgFormatEtc));
|
|
m_cFormatEtc = 0;
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CBaseDataObject::~CBaseDataObject
|
|
//
|
|
// PURPOSE: Cleans up any leftover data.
|
|
//
|
|
CBaseDataObject::~CBaseDataObject()
|
|
{
|
|
Assert(m_cRef == 0);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CBaseDataObject::QueryInterface()
|
|
//
|
|
// PURPOSE: Returns a the requested interface if supported.
|
|
//
|
|
STDMETHODIMP CBaseDataObject::QueryInterface(REFIID riid, LPVOID* ppv)
|
|
{
|
|
*ppv = NULL;
|
|
|
|
if (IsEqualIID(riid, IID_IUnknown))
|
|
*ppv = (LPVOID)(IUnknown*) this;
|
|
else if (IsEqualIID(riid, IID_IDataObject))
|
|
*ppv = (LPVOID)(IDataObject*) this;
|
|
|
|
if (NULL == *ppv)
|
|
return (E_NOINTERFACE);
|
|
|
|
AddRef();
|
|
return (S_OK);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CBaseDataObject::AddRef()
|
|
//
|
|
// PURPOSE: Increments the object reference count.
|
|
//
|
|
STDMETHODIMP_(ULONG) CBaseDataObject::AddRef(void)
|
|
{
|
|
return (++m_cRef);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CBaseDataObject::Release()
|
|
//
|
|
// PURPOSE: Decrements the object's ref count. If the ref count hit's zero
|
|
// the object is freed.
|
|
//
|
|
STDMETHODIMP_(ULONG) CBaseDataObject::Release(void)
|
|
{
|
|
m_cRef--;
|
|
|
|
if (0 == m_cRef)
|
|
{
|
|
delete this;
|
|
return (0);
|
|
}
|
|
|
|
return (m_cRef);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CBaseDataObject::GetDataHere
|
|
//
|
|
// PURPOSE: Returns the object's data in a requested format on the storage
|
|
// the caller allocated.
|
|
//
|
|
// PARAMETERS:
|
|
// pFE - Pointer to the FORMATETC structure that specifies the
|
|
// format the data is requested in.
|
|
// pStgMedium - Pointer to the STGMEDIUM structure that contains the
|
|
// medium the caller allocated and the object provides the
|
|
// data on.
|
|
//
|
|
// RETURN VALUE:
|
|
// Returns an HRESULT indicating success or failure.
|
|
//
|
|
STDMETHODIMP CBaseDataObject::GetDataHere (LPFORMATETC pFE, LPSTGMEDIUM pStgMedium)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CBaseDataObject::GetCanonicalFormatEtc
|
|
//
|
|
// PURPOSE: Communicates to the caller which FORMATETC data structures
|
|
// produce the same output data.
|
|
//
|
|
// PARAMETERS:
|
|
// pFEIn - Points to the FORMATETC in which the caller wants the returned
|
|
// data.
|
|
// pFEOut - Points to the FORMATETC which is the canonical equivalent of
|
|
// pFEIn.
|
|
//
|
|
// RETURN VALUE:
|
|
// Returns an HRESULT signifying success or failure.
|
|
//
|
|
STDMETHODIMP CBaseDataObject::GetCanonicalFormatEtc(LPFORMATETC pFEIn,
|
|
LPFORMATETC pFEOut)
|
|
{
|
|
if (NULL == pFEOut)
|
|
return (E_INVALIDARG);
|
|
|
|
pFEOut->ptd = NULL;
|
|
return (DATA_S_SAMEFORMATETC);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CBaseDataObject::EnumFormatEtc
|
|
//
|
|
// PURPOSE: Provides an interface the caller can use to enumerate the
|
|
// FORMATETC's that the data object supports.
|
|
//
|
|
// PARAMETERS:
|
|
// dwDirection - DATADIR_GET if the caller wants to enumerate the formats
|
|
// he can get, or DATADIR_SET if he wants to enumerate the
|
|
// formats he can set.
|
|
// ppEnum - Points to the enumerator the caller can use to enumerate.
|
|
//
|
|
// RETURN VALUE:
|
|
// Returns S_OK if ppEnum contains the enumerator, or E_NOTIMPL if the
|
|
// direction dwDirection is not supported.
|
|
//
|
|
STDMETHODIMP CBaseDataObject::EnumFormatEtc(DWORD dwDirection,
|
|
IEnumFORMATETC** ppEnum)
|
|
{
|
|
LPFORMATETC pFE = 0;
|
|
ULONG cFE = 0;
|
|
|
|
if (DATADIR_GET == dwDirection)
|
|
{
|
|
// Create the enumerator and give it our list of formats
|
|
if (SUCCEEDED(_BuildFormatEtc(NULL, NULL)))
|
|
{
|
|
if (SUCCEEDED(CreateEnumFormatEtc(this, m_cFormatEtc, NULL, m_rgFormatEtc, ppEnum)))
|
|
return (S_OK);
|
|
}
|
|
|
|
*ppEnum = NULL;
|
|
return (E_FAIL);
|
|
}
|
|
else
|
|
{
|
|
*ppEnum = NULL;
|
|
return (E_NOTIMPL);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CBaseDataObject::SetData
|
|
//
|
|
// PURPOSE: pStgMedium contains data that the caller wants us to store.
|
|
// This data object does not support a caller changing our data.
|
|
//
|
|
STDMETHODIMP CBaseDataObject::SetData(LPFORMATETC pFE, LPSTGMEDIUM pStgMedium,
|
|
BOOL fRelease)
|
|
{
|
|
return (E_NOTIMPL);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CBaseDataObject::DAdvise
|
|
//
|
|
// PURPOSE: Creates a connection between the data-transfer object and an
|
|
// advisory sink through which the sink can be informed when the
|
|
// object's data changes. This object does not support advises.
|
|
STDMETHODIMP CBaseDataObject::DAdvise(LPFORMATETC pFE, DWORD advf,
|
|
IAdviseSink* ppAdviseSink,
|
|
LPDWORD pdwConnection)
|
|
{
|
|
return (E_NOTIMPL);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CBaseDataObject::DUnadvise
|
|
//
|
|
// PURPOSE: Deletes an advisory connect previously established via DAdvise.
|
|
// This object doesn't support advises.
|
|
//
|
|
STDMETHODIMP CBaseDataObject::DUnadvise(DWORD dwConnection)
|
|
{
|
|
return (E_NOTIMPL);
|
|
}
|
|
|
|
//
|
|
// FUNCTION: CBaseDataObject::EnumDAdvise
|
|
//
|
|
// PURPOSE: Enumerates the advisory connections previously established via
|
|
// DAdvise. This object doesn't support advises.
|
|
//
|
|
STDMETHODIMP CBaseDataObject::EnumDAdvise(IEnumSTATDATA** ppEnumAdvise)
|
|
{
|
|
return (E_NOTIMPL);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CFolderDataObject::GetData
|
|
//
|
|
// PURPOSE: Returns the object's data in a requested format in the
|
|
// specified storage medium which the object allocates.
|
|
//
|
|
// PARAMETERS:
|
|
// pFE - Pointer to the FORMATETC structure that specifies the
|
|
// format the data is requested in.
|
|
// pStgMedium - Pointer to the STGMEDIUM structure that contains the
|
|
// medium the object allocates and provides the data on.
|
|
//
|
|
// RETURN VALUE:
|
|
// Returns an HRESULT indicating success or failure.
|
|
//
|
|
STDMETHODIMP CFolderDataObject::GetData(LPFORMATETC pFE, LPSTGMEDIUM pStgMedium)
|
|
{
|
|
HRESULT hr;
|
|
|
|
// Initialize this
|
|
ZeroMemory(pStgMedium, sizeof(STGMEDIUM));
|
|
|
|
if (CF_OEFOLDER == pFE->cfFormat)
|
|
return (_RenderOEFolder(pFE, pStgMedium));
|
|
else if ((CF_TEXT == pFE->cfFormat) || (CF_SHELLURL == pFE->cfFormat))
|
|
return (_RenderTextOrShellURL(pFE, pStgMedium));
|
|
else if ((CF_FILEDESCRIPTORW == pFE->cfFormat) ||
|
|
(CF_FILECONTENTS == pFE->cfFormat) ||
|
|
(CF_FILEDESCRIPTORA == pFE->cfFormat))
|
|
{
|
|
AssertSz(FALSE, "These cases not implemented");
|
|
return (E_NOTIMPL);
|
|
}
|
|
else
|
|
return (DV_E_FORMATETC);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CFolderDataObject::QueryGetData
|
|
//
|
|
// PURPOSE: Determines if a call to GetData() would succeed if it were
|
|
// passed pFE.
|
|
//
|
|
// PARAMETERS:
|
|
// pFE - Pointer to the FORMATETC structure to check to see if the data
|
|
// object supports a particular format.
|
|
//
|
|
// RETURN VALUE:
|
|
// Returns an HRESULT signifying success or failure.
|
|
//
|
|
STDMETHODIMP CFolderDataObject::QueryGetData(LPFORMATETC pFE)
|
|
{
|
|
// Make sure this is already built
|
|
_BuildFormatEtc(NULL, NULL);
|
|
|
|
// Loop through our formats until we find a match
|
|
for (UINT i = 0; i < m_cFormatEtc; i++)
|
|
{
|
|
if (pFE->cfFormat == m_rgFormatEtc[i].cfFormat &&
|
|
pFE->tymed & m_rgFormatEtc[i].tymed)
|
|
{
|
|
return (S_OK);
|
|
}
|
|
}
|
|
|
|
return (DV_E_FORMATETC);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CFolderDataObject::_RenderOEFolder()
|
|
//
|
|
// PURPOSE: Renders the data into the CF_OEFOLDER format
|
|
//
|
|
HRESULT CFolderDataObject::_RenderOEFolder(LPFORMATETC pFE, LPSTGMEDIUM pStgMedium)
|
|
{
|
|
TraceCall("CFolderDataObject::_RenderOEFolder");
|
|
|
|
HGLOBAL hGlobal;
|
|
FOLDERID *pFolderID;
|
|
|
|
// We only support HGLOBALs
|
|
if (pFE->tymed & TYMED_HGLOBAL)
|
|
{
|
|
// I just love allocating 4 bytes
|
|
hGlobal = GlobalAlloc(GHND, sizeof(FOLDERID));
|
|
if (NULL == hGlobal)
|
|
return (E_OUTOFMEMORY);
|
|
|
|
// Local the memory
|
|
pFolderID = (FOLDERID *) GlobalLock(hGlobal);
|
|
|
|
// Set the value
|
|
*pFolderID = m_idFolder;
|
|
|
|
// Unlock
|
|
GlobalUnlock(hGlobal);
|
|
|
|
// Set up the return value
|
|
pStgMedium->hGlobal = hGlobal;
|
|
pStgMedium->pUnkForRelease = 0;
|
|
pStgMedium->tymed = TYMED_HGLOBAL;
|
|
|
|
return (S_OK);
|
|
}
|
|
|
|
return (DV_E_TYMED);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CFolderDataObject::_RenderShellURL()
|
|
//
|
|
// PURPOSE: Renders the data into the CF_SHELLURL format
|
|
//
|
|
HRESULT CFolderDataObject::_RenderTextOrShellURL(LPFORMATETC pFE, LPSTGMEDIUM pStgMedium)
|
|
{
|
|
TraceCall("CFolderDataObject::_RenderOEFolder");
|
|
|
|
HGLOBAL hGlobal;
|
|
FOLDERID *pFolderID;
|
|
FOLDERINFO rInfo = { 0 };
|
|
TCHAR szNewsPrefix[] = _T("news://");
|
|
LPTSTR pszURL;
|
|
DWORD cch;
|
|
HRESULT hr;
|
|
|
|
// We only support HGLOBALs
|
|
if (!(pFE->tymed & TYMED_HGLOBAL))
|
|
{
|
|
hr = DV_E_TYMED;
|
|
goto exit;
|
|
}
|
|
|
|
// Get information about the source folder
|
|
Assert(g_pStore);
|
|
if (SUCCEEDED(g_pStore->GetFolderInfo(m_idFolder, &rInfo)))
|
|
{
|
|
cch = lstrlen(rInfo.pszName) + lstrlen(szNewsPrefix) + 2;
|
|
|
|
// Allocate a buffer for the generated URL
|
|
hGlobal = GlobalAlloc(GHND, sizeof(TCHAR) * cch);
|
|
if (!hGlobal)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto exit;
|
|
}
|
|
|
|
// Double check that our source file is indeed a newsgroup
|
|
if (pFE->cfFormat == CF_SHELLURL)
|
|
{
|
|
if (!(rInfo.tyFolder == FOLDER_NEWS && !(rInfo.dwFlags & FOLDER_SERVER)))
|
|
{
|
|
hr = E_UNEXPECTED;
|
|
goto exit;
|
|
}
|
|
|
|
// Generate a URL from the newsgroup name
|
|
pszURL = (LPTSTR) GlobalLock(hGlobal);
|
|
wnsprintf(pszURL, cch, TEXT("%s%s"), szNewsPrefix, rInfo.pszName);
|
|
GlobalUnlock(hGlobal);
|
|
}
|
|
else if (pFE->cfFormat == CF_TEXT)
|
|
{
|
|
// Copy the folder name to the buffer
|
|
pszURL = (LPTSTR) GlobalLock(hGlobal);
|
|
StrCpyN(pszURL, rInfo.pszName, cch);
|
|
GlobalUnlock(hGlobal);
|
|
}
|
|
else
|
|
{
|
|
AssertSz(FALSE, "CFolderDataObject::_RenderTextOrShellURL() - How did we get here?");
|
|
hr = DV_E_FORMATETC;
|
|
goto exit;
|
|
}
|
|
|
|
// Set up the return value
|
|
pStgMedium->hGlobal = hGlobal;
|
|
pStgMedium->pUnkForRelease = 0;
|
|
pStgMedium->tymed = TYMED_HGLOBAL;
|
|
|
|
hr = S_OK;
|
|
}
|
|
|
|
exit:
|
|
if (rInfo.pAllocated)
|
|
g_pStore->FreeRecord(&rInfo);
|
|
|
|
return (hr);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CMsgDataObject::HrBuildFormatEtc()
|
|
//
|
|
// PURPOSE: Builds the list of FORMATETC structs that this object will
|
|
// support. This list is stored in m_rgFormatEtc[].
|
|
//
|
|
// PARAMTERS:
|
|
// [out] ppFE - Returns the arrray of FORMATETC structures
|
|
// [out] pcElt - Returns the size of ppFE
|
|
//
|
|
// RETURN VALUE:
|
|
// S_OK
|
|
//
|
|
HRESULT CFolderDataObject::_BuildFormatEtc(LPFORMATETC *ppFE, ULONG *pcElt)
|
|
{
|
|
BOOL fNews = FALSE;
|
|
FOLDERINFO rInfo = { 0 };
|
|
|
|
// If we haven't built our format list yet, do so first.
|
|
if (FALSE == m_fBuildFE)
|
|
{
|
|
ZeroMemory(&m_rgFormatEtc, sizeof(m_rgFormatEtc));
|
|
m_cFormatEtc = 0;
|
|
|
|
// Figure out if this is a news group first
|
|
if (SUCCEEDED(g_pStore->GetFolderInfo(m_idFolder, &rInfo)))
|
|
{
|
|
fNews = rInfo.tyFolder == FOLDER_NEWS && !(rInfo.dwFlags & FOLDER_SERVER);
|
|
g_pStore->FreeRecord(&rInfo);
|
|
}
|
|
|
|
if (fNews)
|
|
{
|
|
SETDefFormatEtc(m_rgFormatEtc[m_cFormatEtc], CF_SHELLURL, TYMED_HGLOBAL);
|
|
m_cFormatEtc++;
|
|
}
|
|
|
|
// Since you can't move a newsgroup, we don't support this format
|
|
SETDefFormatEtc(m_rgFormatEtc[m_cFormatEtc], CF_OEFOLDER, TYMED_HGLOBAL);
|
|
m_cFormatEtc++;
|
|
|
|
/*
|
|
SETDefFormatEtc(m_rgFormatEtc[m_cFormatEtc], CF_FILEDESCRIPTOR, TYMED_HGLOBAL);
|
|
m_cFormatEtc++;
|
|
SETDefFormatEtc(m_rgFormatEtc[m_cFormatEtc], CF_FILECONTENTS, TYMED_HGLOBAL);
|
|
m_cFormatEtc++;
|
|
*/
|
|
|
|
m_fBuildFE = TRUE;
|
|
}
|
|
|
|
// Return what we have if the caller cares
|
|
if (ppFE && pcElt)
|
|
{
|
|
*ppFE = m_rgFormatEtc;
|
|
*pcElt = m_cFormatEtc;
|
|
}
|
|
|
|
return (S_OK);
|
|
}
|
|
|
|
|
|
CMessageDataObject::CMessageDataObject()
|
|
{
|
|
m_pMsgIDList = NULL;
|
|
m_idSource = FOLDERID_INVALID;
|
|
m_fBuildFE = FALSE;
|
|
m_fDownloaded = FALSE;
|
|
}
|
|
|
|
CMessageDataObject::~CMessageDataObject()
|
|
{
|
|
}
|
|
|
|
|
|
HRESULT CMessageDataObject::Initialize(LPMESSAGEIDLIST pMsgs, FOLDERID idSource)
|
|
{
|
|
IMessageFolder *pFolder = 0;
|
|
HRESULT hr;
|
|
DWORD i;
|
|
MESSAGEINFO rInfo;
|
|
DWORD cDownloaded = 0;
|
|
|
|
m_pMsgIDList = pMsgs;
|
|
m_idSource = idSource;
|
|
|
|
// Open the folder that contains the message
|
|
if (SUCCEEDED(hr = g_pStore->OpenFolder(m_idSource, NULL, NOFLAGS, &pFolder)))
|
|
{
|
|
// See if they have bodies or not. All must.
|
|
for (i = 0; i < m_pMsgIDList->cMsgs; i++)
|
|
{
|
|
if (SUCCEEDED(GetMessageInfo(pFolder, m_pMsgIDList->prgidMsg[i], &rInfo)))
|
|
{
|
|
if (rInfo.dwFlags & ARF_HASBODY)
|
|
cDownloaded++;
|
|
|
|
pFolder->FreeRecord(&rInfo);
|
|
}
|
|
}
|
|
|
|
pFolder->Release();
|
|
}
|
|
|
|
m_fDownloaded = (cDownloaded == m_pMsgIDList->cMsgs);
|
|
|
|
return (S_OK);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// FUNCTION: CMessageDataObject::GetData()
|
|
//
|
|
// PURPOSE: Called by the drop target to get the data from the data object.
|
|
//
|
|
// PARAMETERS:
|
|
// LPFORMATETC pFE
|
|
// LPSTGMEDIUM pStgMedium
|
|
//
|
|
// RETURN VALUE:
|
|
// HRESULT
|
|
//
|
|
HRESULT CMessageDataObject::GetData(LPFORMATETC pFE, LPSTGMEDIUM pStgMedium)
|
|
{
|
|
HRESULT hr = DV_E_FORMATETC;
|
|
|
|
TraceCall("CMessageDataObject::GetData");
|
|
|
|
if (pFE->cfFormat == CF_OEMESSAGES)
|
|
return (_RenderOEMessages(pFE, pStgMedium));
|
|
|
|
if (m_fDownloaded)
|
|
{
|
|
IMimeMessage *pMsg = NULL;
|
|
|
|
if ((CF_FILEDESCRIPTORW == pFE->cfFormat) ||
|
|
(CF_FILEDESCRIPTORA == pFE->cfFormat))
|
|
return (_RenderFileGroupDescriptor(pFE, pStgMedium));
|
|
|
|
if (CF_FILECONTENTS == pFE->cfFormat)
|
|
return (_RenderFileContents(pFE, pStgMedium));
|
|
|
|
// Otherwise, do the default action
|
|
if (m_pMsgIDList->cMsgs == 1)
|
|
{
|
|
if (SUCCEEDED(_LoadMessage(0, &pMsg, NULL)))
|
|
{
|
|
IDataObject *pDataObject = NULL;
|
|
if (SUCCEEDED(pMsg->QueryInterface(IID_IDataObject, (LPVOID *) &pDataObject)))
|
|
{
|
|
hr = pDataObject->GetData(pFE, pStgMedium);
|
|
pDataObject->Release();
|
|
}
|
|
|
|
pMsg->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
return (hr);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CMessageDataObject::QueryGetData
|
|
//
|
|
// PURPOSE: Determines if a call to GetData() would succeed if it were
|
|
// passed pFE.
|
|
//
|
|
// PARAMETERS:
|
|
// pFE - Pointer to the FORMATETC structure to check to see if the data
|
|
// object supports a particular format.
|
|
//
|
|
// RETURN VALUE:
|
|
// Returns an HRESULT signifying success or failure.
|
|
//
|
|
HRESULT CMessageDataObject::QueryGetData(LPFORMATETC pFE)
|
|
{
|
|
IMimeMessage *pMsg = 0;
|
|
IDataObject *pDataObject = 0;
|
|
HRESULT hr = DV_E_FORMATETC;
|
|
|
|
// Walk through the formats we support to see if any match
|
|
if (SUCCEEDED(_BuildFormatEtc(NULL, NULL)))
|
|
{
|
|
for (UINT i = 0; i < m_cFormatEtc; i++)
|
|
{
|
|
if (pFE->cfFormat == m_rgFormatEtc[i].cfFormat &&
|
|
pFE->tymed == m_rgFormatEtc[i].tymed)
|
|
{
|
|
hr = S_OK;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// If we have a message object, then ask it for it's formats as well.
|
|
if (m_pMsgIDList->cMsgs == 1)
|
|
{
|
|
if (SUCCEEDED(_LoadMessage(0, &pMsg, NULL)))
|
|
{
|
|
if (SUCCEEDED(pMsg->QueryInterface(IID_IDataObject, (LPVOID *) &pDataObject)))
|
|
{
|
|
hr = pDataObject->QueryGetData(pFE);
|
|
pDataObject->Release();
|
|
}
|
|
|
|
pMsg->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
exit:
|
|
return (hr);
|
|
}
|
|
|
|
|
|
HRESULT CMessageDataObject::_BuildFormatEtc(LPFORMATETC *ppFE, ULONG *pcElt)
|
|
{
|
|
IEnumFORMATETC *pEnum = 0;
|
|
FORMATETC fe;
|
|
ULONG celtFetched;
|
|
IMimeMessage *pMessage = 0;
|
|
HRESULT hr = S_OK;
|
|
|
|
// Only build our internal list of formats once
|
|
if (FALSE == m_fBuildFE)
|
|
{
|
|
m_cFormatEtc = 0;
|
|
|
|
// We always support these formats
|
|
SETDefFormatEtc(m_rgFormatEtc[m_cFormatEtc], CF_OEMESSAGES, TYMED_HGLOBAL);
|
|
m_cFormatEtc++;
|
|
|
|
// We support these if the messages are downloaded
|
|
if (m_fDownloaded)
|
|
{
|
|
SETDefFormatEtc(m_rgFormatEtc[m_cFormatEtc], CF_FILEDESCRIPTORA, TYMED_HGLOBAL);
|
|
m_cFormatEtc++;
|
|
SETDefFormatEtc(m_rgFormatEtc[m_cFormatEtc], CF_FILEDESCRIPTORW, TYMED_HGLOBAL);
|
|
m_cFormatEtc++;
|
|
SETDefFormatEtc(m_rgFormatEtc[m_cFormatEtc], CF_FILECONTENTS, TYMED_ISTREAM);
|
|
m_cFormatEtc++;
|
|
}
|
|
|
|
// If we're holding just one message, then have the message add it's
|
|
// own formats too.
|
|
if (m_fDownloaded && m_pMsgIDList->cMsgs == 1 && SUCCEEDED(_LoadMessage(0, &pMessage, NULL)))
|
|
{
|
|
IDataObject *pDataObject = 0;
|
|
|
|
// Get the data object interface from the message
|
|
if (SUCCEEDED(hr = pMessage->QueryInterface(IID_IDataObject, (LPVOID *) &pDataObject)))
|
|
{
|
|
if (SUCCEEDED(hr = pDataObject->EnumFormatEtc(DATADIR_GET, &pEnum)))
|
|
{
|
|
pEnum->Reset();
|
|
while (S_OK == pEnum->Next(1, &fe, &celtFetched))
|
|
{
|
|
m_rgFormatEtc[m_cFormatEtc] = fe;
|
|
m_cFormatEtc++;
|
|
}
|
|
|
|
pEnum->Release();
|
|
}
|
|
|
|
pDataObject->Release();
|
|
}
|
|
|
|
pMessage->Release();
|
|
}
|
|
}
|
|
|
|
// Return what we have
|
|
if (ppFE && pcElt)
|
|
{
|
|
*ppFE = m_rgFormatEtc;
|
|
*pcElt = m_cFormatEtc;
|
|
}
|
|
|
|
return (hr);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// FUNCTION: CMessageDataObject::_LoadMessage()
|
|
//
|
|
// PURPOSE: This function loads the specified message from the store.
|
|
//
|
|
// PARAMETERS:
|
|
// iMsg - Index into m_rgMsgs of the message desired
|
|
// ppMsg - Returns a pointer to the message
|
|
//
|
|
// RETURN VALUE:
|
|
// HRESULT
|
|
//
|
|
HRESULT CMessageDataObject::_LoadMessage(DWORD iMsg, IMimeMessage **ppMsg, LPWSTR pwszFileExt)
|
|
{
|
|
TraceCall("CMessageDataObject::_LoadMessage");
|
|
|
|
IMessageFolder *pFolder = NULL;
|
|
IDataObject *pDataObj = NULL;
|
|
HRESULT hr;
|
|
|
|
// Open the folder that contains the message
|
|
*ppMsg = NULL;
|
|
hr = g_pStore->OpenFolder(m_idSource, NULL, NOFLAGS, &pFolder);
|
|
if (FAILED(hr))
|
|
goto exit;
|
|
|
|
// Open the message from the folder
|
|
hr = pFolder->OpenMessage(m_pMsgIDList->prgidMsg[iMsg],
|
|
OPEN_MESSAGE_SECURE | OPEN_MESSAGE_CACHEDONLY,
|
|
ppMsg, NOSTORECALLBACK);
|
|
if (FAILED(hr))
|
|
goto exit;
|
|
|
|
if (pwszFileExt)
|
|
{
|
|
LPTSTR pszNewsgroups = NULL;
|
|
|
|
if (SUCCEEDED(MimeOleGetBodyPropA(*ppMsg, HBODY_ROOT, PIDTOSTR(PID_HDR_NEWSGROUPS), NOFLAGS, &pszNewsgroups)))
|
|
{
|
|
MemFree(pszNewsgroups);
|
|
AthLoadStringW(idsDefNewsExt, pwszFileExt, 32);
|
|
}
|
|
else
|
|
AthLoadStringW(idsDefMailExt, pwszFileExt, 32);
|
|
|
|
}
|
|
|
|
exit:
|
|
SafeRelease(pFolder);
|
|
|
|
if (FAILED(hr))
|
|
SafeRelease((*ppMsg));
|
|
|
|
return (hr);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CMessageDataObject::_RenderOEMessages()
|
|
//
|
|
// PURPOSE: Renders the data from our object into the CF_OEMESSAGES format.
|
|
//
|
|
// PARAMETERS:
|
|
// pFE
|
|
// pStgMedium
|
|
//
|
|
// RETURN VALUE:
|
|
// HRESULT
|
|
//
|
|
HRESULT CMessageDataObject::_RenderOEMessages(LPFORMATETC pFE, LPSTGMEDIUM pStgMedium)
|
|
{
|
|
TraceCall("CMessageDataObject::_RenderOEMessages");
|
|
|
|
// Make sure the caller want's an HGLOBAL
|
|
if (pFE->tymed != TYMED_HGLOBAL)
|
|
return (DV_E_TYMED);
|
|
|
|
// Figure out how much memory to allocate for our returned information
|
|
DWORD cb = sizeof(OEMESSAGES) + (sizeof(MESSAGEID) * m_pMsgIDList->cMsgs);
|
|
|
|
// Allocate the memory
|
|
HGLOBAL hGlobal = GlobalAlloc(GHND | GMEM_SHARE, cb);
|
|
if (NULL == hGlobal)
|
|
return (E_OUTOFMEMORY);
|
|
|
|
OEMESSAGES *pOEMessages = (OEMESSAGES *) GlobalLock(hGlobal);
|
|
|
|
// Fill in the basic fields
|
|
pOEMessages->idSource = m_idSource;
|
|
pOEMessages->rMsgIDList = *m_pMsgIDList;
|
|
pOEMessages->rMsgIDList.prgidMsg = (MESSAGEID *) ((LPBYTE) pOEMessages + sizeof(OEMESSAGES));
|
|
|
|
// Copy the message ID's
|
|
CopyMemory(pOEMessages->rMsgIDList.prgidMsg, m_pMsgIDList->prgidMsg, sizeof(MESSAGEID) * m_pMsgIDList->cMsgs);
|
|
GlobalUnlock(hGlobal);
|
|
|
|
// Fill out the return value info
|
|
pStgMedium->tymed = TYMED_HGLOBAL;
|
|
pStgMedium->hGlobal = hGlobal;
|
|
pStgMedium->pUnkForRelease = 0;
|
|
|
|
return (S_OK);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CMessageDataObject::_RenderFileContents()
|
|
//
|
|
// PURPOSE: Takes the data that this object contains and renders it into an
|
|
// IStream.
|
|
//
|
|
// PARAMETERS:
|
|
// <in> pFE - Pointer to the FORMATETC struct that describes the
|
|
// type of data requested.
|
|
// <out> pStgMedium - Pointer to where we should return the rendered data.
|
|
//
|
|
// RETURN VALUE:
|
|
// DV_E_TYMED - A tymed was requested that we don't support
|
|
// E_OUTOFMEMORY - not enough memory
|
|
// S_OK - Everything succeeded
|
|
//
|
|
HRESULT CMessageDataObject::_RenderFileContents(LPFORMATETC pFE, LPSTGMEDIUM pStgMedium)
|
|
{
|
|
IMimeMessage *pMsg = 0;
|
|
IDataObject *pDataObject = 0;
|
|
FORMATETC feMsg;
|
|
HRESULT hr = E_FAIL;
|
|
DWORD dwFlags = 0;
|
|
|
|
Assert(pFE->lindex < (int) m_pMsgIDList->cMsgs);
|
|
|
|
// Get the IDataObject for the specific message
|
|
if (SUCCEEDED(_LoadMessage(pFE->lindex, &pMsg, NULL)))
|
|
{
|
|
// prevent save to file message with label
|
|
pMsg->GetFlags(&dwFlags);
|
|
|
|
if (IMF_SECURE & dwFlags)
|
|
{
|
|
hr = HandleSecurity(NULL, pMsg);
|
|
if(FAILED(hr))
|
|
goto exit;
|
|
|
|
SafeRelease(pMsg);
|
|
if (FAILED(_LoadMessage(pFE->lindex, &pMsg, NULL)))
|
|
goto exit;
|
|
}
|
|
|
|
if (SUCCEEDED(pMsg->QueryInterface(IID_IDataObject, (LPVOID *)&pDataObject)))
|
|
{
|
|
SETDefFormatEtc(feMsg, CF_INETMSG, TYMED_ISTREAM);
|
|
hr = pDataObject->GetData(&feMsg, pStgMedium);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pMsg);
|
|
SafeRelease(pDataObject);
|
|
|
|
return (hr);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CMessageDataObject::_RenderFileGroupDescriptor()
|
|
//
|
|
// PURPOSE: Takes the data that this object contains and renders it into a
|
|
// FILEGROUPDESCRIPTOR struct.
|
|
//
|
|
// PARAMETERS:
|
|
// <in> pFE - Pointer to the FORMATETC struct that describes the
|
|
// type of data requested.
|
|
// <out> pStgMedium - Pointer to where we should return the rendered data.
|
|
//
|
|
// RETURN VALUE:
|
|
// DV_E_TYMED - A tymed was requested that we don't support
|
|
// E_OUTOFMEMORY - not enough memory
|
|
// S_OK - Everything succeeded
|
|
//
|
|
HRESULT CMessageDataObject::_RenderFileGroupDescriptor(LPFORMATETC pFE, LPSTGMEDIUM pStgMedium)
|
|
{
|
|
HGLOBAL hGlobal = 0;
|
|
FILEGROUPDESCRIPTORA *pFileGDA = NULL;
|
|
FILEGROUPDESCRIPTORW *pFileGDW = NULL;
|
|
IMimeMessage *pMsg = 0;
|
|
PROPVARIANT pv;
|
|
DWORD cMsgs = m_pMsgIDList->cMsgs;
|
|
BOOL fWide = (CF_FILEDESCRIPTORW == pFE->cfFormat);
|
|
WCHAR wszFileExt[32];
|
|
UINT cbDescSize = 0;
|
|
LPWSTR pwszSubject = NULL;
|
|
LPSTR pszSubject = NULL,
|
|
pszFileExt = NULL;
|
|
HRESULT hr = S_OK;
|
|
UINT i = 0;
|
|
|
|
// Allocate a FILEGROUPDESCRIPTOR struct large enough to contain the names
|
|
// of all the messages we contain.
|
|
|
|
cbDescSize = (fWide ? (sizeof(FILEGROUPDESCRIPTORW) + (sizeof(FILEDESCRIPTORW) * (cMsgs - 1))) :
|
|
(sizeof(FILEGROUPDESCRIPTORA) + (sizeof(FILEDESCRIPTORA) * (cMsgs - 1))));
|
|
IF_NULLEXIT(hGlobal = GlobalAlloc(GHND, cbDescSize));
|
|
|
|
pFileGDA = (FILEGROUPDESCRIPTORA *) GlobalLock(hGlobal);
|
|
pFileGDA->cItems = cMsgs;
|
|
pFileGDW = (FILEGROUPDESCRIPTORW *)pFileGDA;
|
|
|
|
// Copy the file names one at a time into the pFileGDA struct
|
|
for (; i < cMsgs; i++)
|
|
{
|
|
if (SUCCEEDED(_LoadMessage(i, &pMsg, wszFileExt)))
|
|
{
|
|
if (fWide)
|
|
{
|
|
if (SUCCEEDED(MimeOleGetBodyPropW(pMsg, HBODY_ROOT, PIDTOSTR(PID_HDR_SUBJECT), NOFLAGS, &pwszSubject)) && pwszSubject && *pwszSubject)
|
|
{
|
|
wnsprintfW(pFileGDW->fgd[i].cFileName, ARRAYSIZE(pFileGDW->fgd[i].cFileName), L"%.180s.%s", pwszSubject, wszFileExt);
|
|
}
|
|
else
|
|
{
|
|
WCHAR wszBuf[CCHMAX_STRINGRES];
|
|
AthLoadStringW(idsMessageFileName, wszBuf, ARRAYSIZE(wszBuf));
|
|
wnsprintfW(pFileGDW->fgd[i].cFileName, ARRAYSIZE(pFileGDW->fgd[i].cFileName), wszBuf, i + 1, wszFileExt);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IF_NULLEXIT(pszFileExt = PszToANSI(CP_ACP, wszFileExt));
|
|
|
|
if (SUCCEEDED(MimeOleGetBodyPropW(pMsg, HBODY_ROOT, PIDTOSTR(PID_HDR_SUBJECT), NOFLAGS, &pwszSubject)) && pwszSubject && *pwszSubject)
|
|
{
|
|
IF_NULLEXIT(pszSubject = PszToANSI(CP_ACP, pwszSubject));
|
|
wnsprintf(pFileGDA->fgd[i].cFileName, ARRAYSIZE(pFileGDW->fgd[i].cFileName), TEXT("%.180s.%s"), pszSubject, pszFileExt);
|
|
}
|
|
else
|
|
{
|
|
TCHAR szBuf[CCHMAX_STRINGRES];
|
|
AthLoadString(idsMessageFileName, szBuf, ARRAYSIZE(szBuf));
|
|
wnsprintf(pFileGDA->fgd[i].cFileName, ARRAYSIZE(pFileGDW->fgd[i].cFileName), szBuf, i + 1, pszFileExt);
|
|
}
|
|
}
|
|
SafeMemFree(pszSubject);
|
|
SafeMemFree(pwszSubject);
|
|
SafeMemFree(pszFileExt);
|
|
|
|
if (fWide)
|
|
{
|
|
pFileGDW->fgd[i].dwFlags = FD_FILESIZE | FD_WRITESTIME;
|
|
pFileGDW->fgd[i].nFileSizeHigh = 0;
|
|
pMsg->GetMessageSize(&pFileGDW->fgd[i].nFileSizeLow, 0);
|
|
pv.vt = VT_FILETIME;
|
|
pMsg->GetProp(PIDTOSTR(PID_ATT_SENTTIME), 0, &pv);
|
|
pFileGDW->fgd[i].ftLastWriteTime = pv.filetime;
|
|
|
|
CleanupFileNameInPlaceW(pFileGDW->fgd[i].cFileName);
|
|
}
|
|
else
|
|
{
|
|
pFileGDA->fgd[i].dwFlags = FD_FILESIZE | FD_WRITESTIME;
|
|
pFileGDA->fgd[i].nFileSizeHigh = 0;
|
|
pMsg->GetMessageSize(&pFileGDA->fgd[i].nFileSizeLow, 0);
|
|
pv.vt = VT_FILETIME;
|
|
pMsg->GetProp(PIDTOSTR(PID_ATT_SENTTIME), 0, &pv);
|
|
pFileGDA->fgd[i].ftLastWriteTime = pv.filetime;
|
|
|
|
CleanupFileNameInPlaceA(CP_ACP, pFileGDA->fgd[i].cFileName);
|
|
}
|
|
SafeRelease(pMsg);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Put the structure into the STGMEDIUM
|
|
if (hGlobal)
|
|
GlobalUnlock(hGlobal);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
pStgMedium->hGlobal = hGlobal;
|
|
pStgMedium->pUnkForRelease = NULL;
|
|
pStgMedium->tymed = TYMED_HGLOBAL;
|
|
}
|
|
|
|
MemFree(pszSubject);
|
|
MemFree(pwszSubject);
|
|
MemFree(pszFileExt);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// FUNCTION: CShortcutDataObject::GetData
|
|
//
|
|
// PURPOSE: Returns the object's data in a requested format in the
|
|
// specified storage medium which the object allocates.
|
|
//
|
|
// PARAMETERS:
|
|
// pFE - Pointer to the FORMATETC structure that specifies the
|
|
// format the data is requested in.
|
|
// pStgMedium - Pointer to the STGMEDIUM structure that contains the
|
|
// medium the object allocates and provides the data on.
|
|
//
|
|
// RETURN VALUE:
|
|
// Returns an HRESULT indicating success or failure.
|
|
//
|
|
STDMETHODIMP CShortcutDataObject::GetData(LPFORMATETC pFE, LPSTGMEDIUM pStgMedium)
|
|
{
|
|
HRESULT hr;
|
|
|
|
// Initialize this
|
|
ZeroMemory(pStgMedium, sizeof(STGMEDIUM));
|
|
|
|
// Loop through the format array to see if any of the elements are the
|
|
// same as pFE.
|
|
if (pFE->cfFormat == CF_OESHORTCUT)
|
|
return (_RenderOEShortcut(pFE, pStgMedium));
|
|
|
|
else
|
|
return (DV_E_FORMATETC);
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CShortcutDataObject::QueryGetData
|
|
//
|
|
// PURPOSE: Determines if a call to GetData() would succeed if it were
|
|
// passed pFE.
|
|
//
|
|
// PARAMETERS:
|
|
// pFE - Pointer to the FORMATETC structure to check to see if the data
|
|
// object supports a particular format.
|
|
//
|
|
// RETURN VALUE:
|
|
// Returns an HRESULT signifying success or failure.
|
|
//
|
|
STDMETHODIMP CShortcutDataObject::QueryGetData(LPFORMATETC pFE)
|
|
{
|
|
// Make sure this is already built
|
|
_BuildFormatEtc(NULL, NULL);
|
|
|
|
// Loop through our formats until we find a match
|
|
for (UINT i = 0; i < m_cFormatEtc; i++)
|
|
{
|
|
if (pFE->cfFormat == m_rgFormatEtc[i].cfFormat &&
|
|
pFE->tymed & m_rgFormatEtc[i].tymed)
|
|
{
|
|
return (S_OK);
|
|
}
|
|
}
|
|
|
|
return (DV_E_FORMATETC);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: CShortcutDataObject::_RenderOEFolder()
|
|
//
|
|
// PURPOSE: Renders the data into the CF_OESHORTCUT format
|
|
//
|
|
HRESULT CShortcutDataObject::_RenderOEShortcut(LPFORMATETC pFE, LPSTGMEDIUM pStgMedium)
|
|
{
|
|
TraceCall("CShortcutDataObject::_RenderOEShortcut");
|
|
|
|
HGLOBAL hGlobal;
|
|
UINT *piPos;
|
|
|
|
// We only support HGLOBALs
|
|
if (pFE->tymed & TYMED_HGLOBAL)
|
|
{
|
|
// I just love allocating 4 bytes
|
|
hGlobal = GlobalAlloc(GHND, sizeof(FOLDERID));
|
|
if (NULL == hGlobal)
|
|
return (E_OUTOFMEMORY);
|
|
|
|
// Local the memory
|
|
piPos = (UINT *) GlobalLock(hGlobal);
|
|
|
|
// Set the value
|
|
*piPos = m_iPos;
|
|
|
|
// Unlock
|
|
GlobalUnlock(hGlobal);
|
|
|
|
// Set up the return value
|
|
pStgMedium->hGlobal = hGlobal;
|
|
pStgMedium->pUnkForRelease = 0;
|
|
pStgMedium->tymed = TYMED_HGLOBAL;
|
|
|
|
return (S_OK);
|
|
}
|
|
|
|
return (DV_E_TYMED);
|
|
}
|
|
|
|
//
|
|
// FUNCTION: CShortcutDataObject::HrBuildFormatEtc()
|
|
//
|
|
// PURPOSE: Builds the list of FORMATETC structs that this object will
|
|
// support. This list is stored in m_rgFormatEtc[].
|
|
//
|
|
// PARAMTERS:
|
|
// [out] ppFE - Returns the arrray of FORMATETC structures
|
|
// [out] pcElt - Returns the size of ppFE
|
|
//
|
|
// RETURN VALUE:
|
|
// S_OK
|
|
//
|
|
HRESULT CShortcutDataObject::_BuildFormatEtc(LPFORMATETC *ppFE, ULONG *pcElt)
|
|
{
|
|
BOOL fNews = FALSE;
|
|
FOLDERINFO rInfo = { 0 };
|
|
|
|
// If we haven't built our format list yet, do so first.
|
|
if (FALSE == m_fBuildFE)
|
|
{
|
|
ZeroMemory(&m_rgFormatEtc, sizeof(m_rgFormatEtc));
|
|
m_cFormatEtc = 0;
|
|
|
|
SETDefFormatEtc(m_rgFormatEtc[m_cFormatEtc], CF_OESHORTCUT, TYMED_HGLOBAL);
|
|
m_cFormatEtc++;
|
|
|
|
m_fBuildFE = TRUE;
|
|
}
|
|
|
|
// Return what we have if the caller cares
|
|
if (ppFE && pcElt)
|
|
{
|
|
*ppFE = m_rgFormatEtc;
|
|
*pcElt = m_cFormatEtc;
|
|
}
|
|
|
|
return (S_OK);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: FIsFileInsertable()
|
|
//
|
|
// PURPOSE: If there is a single file in the specified HDROP, then we check
|
|
// the mime content type of the file to see if it's text/html or
|
|
// text/plain. If so, we open the file and return an IStream on
|
|
// that file.
|
|
//
|
|
// PARAMETERS:
|
|
// <in> hDrop - HDROP handle to check.
|
|
// <out> ppStream - If not NULL and the checks listed above succeed, then
|
|
// this returns a stream pointer on the file.
|
|
//
|
|
// RETURN VALUE:
|
|
// TRUE if the file is insertable as the body of a message
|
|
// FALSE otherwise.
|
|
//
|
|
BOOL FIsFileInsertable(HDROP hDrop, LPSTREAM *ppStream, BOOL* fHTML)
|
|
{
|
|
WCHAR wszFile[MAX_PATH];
|
|
LPWSTR pwszCntType = 0,
|
|
pwszCntSubType = 0,
|
|
pwszCntDesc = 0,
|
|
pwszFName = 0;
|
|
BOOL fReturn = FALSE;
|
|
|
|
// If there is more than one file then we attach instead
|
|
if (1 == DragQueryFileWrapW(hDrop, (UINT) -1, NULL, 0))
|
|
{
|
|
// Get the file name
|
|
DragQueryFileWrapW(hDrop, 0, wszFile, ARRAYSIZE(wszFile));
|
|
|
|
// Find out it's content type
|
|
MimeOleGetFileInfoW(wszFile, &pwszCntType, &pwszCntSubType, &pwszCntDesc,
|
|
&pwszFName, NULL);
|
|
|
|
// See if the content is text/plain or text/html
|
|
if ((0 == StrCmpIW(pwszCntType, L"text")) &&
|
|
((0 == StrCmpIW(pwszCntSubType, L"plain")) ||
|
|
(0 == StrCmpIW(pwszCntSubType, L"html"))))
|
|
{
|
|
if (ppStream)
|
|
{
|
|
// Get a stream on that file
|
|
if (SUCCEEDED(CreateStreamOnHFileW(wszFile, GENERIC_READ, FILE_SHARE_READ,
|
|
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
|
|
0, ppStream)))
|
|
{
|
|
fReturn = TRUE;
|
|
*fHTML = !StrCmpIW(pwszCntSubType, L"html");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MemFree(pwszCntType);
|
|
MemFree(pwszCntSubType);
|
|
MemFree(pwszCntDesc);
|
|
MemFree(pwszFName);
|
|
|
|
return (fReturn);
|
|
}
|
|
|
|
//
|
|
// FUNCTION: HrAttachHDrop()
|
|
//
|
|
// PURPOSE: Takes an HDROP handle and attaches the files specified by the
|
|
// HDROP to the specified message object.
|
|
//
|
|
// PARAMETERS:
|
|
// <in> pAttList - Pointer to the attachment list of the object we want
|
|
// to attach to.
|
|
// <in> hDrop - HDROP handle with the file information.
|
|
// <in> fMakeLinks - TRUE if the caller wants us to create shortcuts.
|
|
//
|
|
// RETURN VALUE:
|
|
// HRESULT
|
|
//
|
|
HRESULT HrAttachHDrop(HWND hwnd, IMimeMessage *pMessage, HDROP hDrop, BOOL fMakeLinks)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
WCHAR szFile[MAX_PATH];
|
|
UINT cFiles;
|
|
BOOL fFirstDirectory = TRUE;
|
|
HCURSOR hCursor;
|
|
int id;
|
|
|
|
// This might take a bit of time
|
|
hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
|
|
// Walk through the drop information
|
|
cFiles = DragQueryFileWrapW(hDrop, (UINT) -1, NULL, 0);
|
|
for (UINT i = 0; i < cFiles; i++)
|
|
{
|
|
// Get the name of the i'th file in the drop
|
|
DragQueryFileWrapW(hDrop, i, szFile, ARRAYSIZE(szFile));
|
|
|
|
// We don't allow the user to attach an entire directory, only link.
|
|
if (!fMakeLinks && PathIsDirectoryW(szFile))
|
|
{
|
|
// Only show this error once
|
|
if (fFirstDirectory)
|
|
{
|
|
id = AthMessageBoxW(hwnd, MAKEINTRESOURCEW(idsAthena),
|
|
MAKEINTRESOURCEW(idsDropLinkDirs), 0,
|
|
MB_ICONEXCLAMATION | MB_YESNOCANCEL);
|
|
if (id == IDCANCEL)
|
|
{
|
|
hr = E_FAIL;
|
|
goto exit;
|
|
}
|
|
|
|
// If we can create a link to directories, then add one now.
|
|
if (id == IDYES)
|
|
HrAddAttachment(pMessage, szFile, NULL, TRUE);
|
|
fFirstDirectory = FALSE;
|
|
}
|
|
|
|
}
|
|
else
|
|
HrAddAttachment(pMessage, szFile, NULL, fMakeLinks);
|
|
|
|
}
|
|
|
|
exit:
|
|
SetCursor(hCursor);
|
|
return (hr);
|
|
}
|
|
|
|
|
|
//
|
|
// FUNCTION: HrAddAttachment()
|
|
//
|
|
// PURPOSE: Adds a file or stream as an attachment to a message object.
|
|
//
|
|
// PARAMETERS:
|
|
// <in> pAttList - Attachment list for the message to add to.
|
|
// <in> pszName - Name of the file to attach. This is used if pStream
|
|
// is NULL.
|
|
// <in> pStream - Stream to add to the message as an attachment.
|
|
// <in> fLink - TRUE if the caller wants to attach the file as a
|
|
// shortcut.
|
|
//
|
|
// RETURN VALUE:
|
|
// S_OK - The file/stream was attached OK.
|
|
//
|
|
HRESULT HrAddAttachment(IMimeMessage *pMessage, LPWSTR pszName, LPSTREAM pStream, BOOL fLink)
|
|
{
|
|
HRESULT hr;
|
|
HBODY hBody;
|
|
IMimeBodyW *pBody = NULL;
|
|
ULONG cbSize = 0;
|
|
WCHAR szLinkPath[MAX_PATH];
|
|
LPWSTR pszFileNameToUse;
|
|
|
|
*szLinkPath = 0;
|
|
|
|
// If we need to create a link, then call off and do that
|
|
if(fLink)
|
|
CreateNewShortCut(pszName, szLinkPath, ARRAYSIZE(szLinkPath));
|
|
|
|
pszFileNameToUse = *szLinkPath ? szLinkPath : pszName;
|
|
|
|
// Add the attachment based on whether it's a stream or file
|
|
if (pStream)
|
|
{
|
|
hr = pMessage->AttachObject(IID_IStream, (LPVOID)pStream, &hBody);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
}
|
|
else
|
|
{
|
|
LPMIMEMESSAGEW pMsgW = NULL;
|
|
hr = pMessage->QueryInterface(IID_IMimeMessageW, (LPVOID*)&pMsgW);
|
|
|
|
if (SUCCEEDED(hr))
|
|
hr = pMsgW->AttachFileW(pszFileNameToUse, NULL, &hBody);
|
|
|
|
ReleaseObj(pMsgW);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
}
|
|
|
|
// Set the display name...
|
|
Assert(hBody);
|
|
hr = pMessage->BindToObject(hBody, IID_IMimeBodyW, (LPVOID *)&pBody);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
pBody->SetDisplayNameW(pszFileNameToUse);
|
|
pBody->Release();
|
|
|
|
// Done
|
|
return (S_OK);
|
|
}
|