FORMBASE.CPP

////////////////////////////////////////////////////////////////////////////// 
//
// FILE: FORMBASE.CPP
//
//
//
// Copyright (c) 1986-1996, Microsoft Corporation.
// All rights reserved.
//
//--

#include "precomp.h"
#include <cindex.h>

static BOOL g_FModalUp = FALSE;
static BOOL g_FMBoxUp = FALSE;
static HWND g_hwndUp = NULL;


BOOL CALLBACK FormDlgProcSend(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK FormDlgProcRead(HWND, UINT, WPARAM, LPARAM);

SizedSPropTagArray(cpropMsg, tagaRead) = MESSAGE_TAGS;

//szRE_PREFIX and szFW_PREFIX have to have the same length
char szRE_PREFIX[] = "RE: ";
char szFW_PREFIX[] = "FW: ";


#define EXCLUDED_PROPS_ON_REPLY 32
SizedSPropTagArray (EXCLUDED_PROPS_ON_REPLY, sptExcludedProps) =
{
EXCLUDED_PROPS_ON_REPLY,
{
PR_SENDER_NAME,
PR_SENDER_ENTRYID,
PR_SENDER_SEARCH_KEY,
PR_SENDER_EMAIL_ADDRESS,
PR_SENDER_ADDRTYPE,

PR_RECEIVED_BY_NAME,
PR_RECEIVED_BY_ENTRYID,
PR_RECEIVED_BY_SEARCH_KEY,

PR_SENT_REPRESENTING_NAME,
PR_SENT_REPRESENTING_ENTRYID,
PR_SENT_REPRESENTING_SEARCH_KEY,
PR_SENT_REPRESENTING_EMAIL_ADDRESS,
PR_SENT_REPRESENTING_ADDRTYPE,

PR_RCVD_REPRESENTING_NAME,
PR_RCVD_REPRESENTING_ENTRYID,
PR_RCVD_REPRESENTING_SEARCH_KEY,

PR_MESSAGE_FLAGS,
PR_MESSAGE_RECIPIENTS,

PR_READ_RECEIPT_ENTRYID,
PR_REPORT_ENTRYID,

PR_REPLY_RECIPIENT_ENTRIES,
PR_REPLY_RECIPIENT_NAMES,

PR_PARENT_KEY,

PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED,

PR_READ_RECEIPT_REQUESTED,

PR_CLIENT_SUBMIT_TIME,
PR_MESSAGE_DELIVERY_TIME,
PR_MESSAGE_DOWNLOAD_TIME,

PR_BODY,
PR_SUBJECT,
PR_SUBJECT_PREFIX,
PR_MESSAGE_ATTACHMENTS

}
};


//// CBaseForm::CBaseForm
//

CBaseForm::CBaseForm(CClassFactory * pClassFactory)
: m_lsterr(g_szFormName)
{
m_pClassFactory = pClassFactory;
m_pClassFactory->AddRef();

m_ulViewStatus = 0;
m_ulSiteStatus = 0;
m_cRef = 1;

m_pviewctxOverride = NULL;
m_pviewctx = NULL;
m_pmsgsite = NULL;
m_pmsg = NULL;

m_hwnd = NULL;
m_hwndDialog = NULL;

m_pses = NULL;
m_pab = NULL;
m_pval = NULL;


m_padrlist = NULL;
m_fRecipientsDirty = FALSE;

m_fDirty = FALSE;

m_fSameAsLoaded = FALSE;

//
// Add self to the head of the linked list of forms
//

m_pfrmNext = g_PfrmFirst;
g_PfrmFirst = this;

m_cbConvIdx = 0;
m_lpbConvIdx = NULL;
m_fConvTopicSet = FALSE;

m_hChsFldDll = NULL;
m_lpfnHrPickFolder = NULL;
m_cbCFDState = 0;
m_pbCFDState = NULL;


m_state = stateUninit;
}


//// CBaseForm::~CBaseForm
//
CBaseForm::~CBaseForm()
{
CBaseForm * pfrm;
//
// Remove ourselves from the form link list
//

if (g_PfrmFirst == this)
{
g_PfrmFirst = m_pfrmNext;
}
else
{
for (pfrm = g_PfrmFirst; pfrm != NULL; pfrm = pfrm->GetNext())
{
if (pfrm->m_pfrmNext == this)
{
pfrm->m_pfrmNext = m_pfrmNext;
break;
}
}
Assert(pfrm != NULL);
}

MAPIFreeBuffer(m_pval);

FreePadrlist(m_padrlist);

MAPIFreeBuffer(m_lpbConvIdx);

if(m_hChsFldDll)
FreeLibrary(m_hChsFldDll);

MAPIFreeBuffer(m_pbCFDState);

//
// Let the class factory know we are gone
//

UlRelease(m_pClassFactory);

}


//// CBaseForm::DeInitObjects
//

void CBaseForm::DeInitObjects()
{
if (m_hwnd != NULL)
{
DestroyWindow(m_hwnd); // Destroy the frame window
}

Assert(m_pmsg == NULL);
Assert(m_pmsgsite == NULL);
Assert(m_pviewctx == NULL);
Assert(m_pviewctxOverride == NULL);
Assert(m_pab == NULL);
Assert(m_pses == NULL);

m_state = stateDead;
}

///////////////////////////////////////////////////////////////////////////////
//
// IUnknown interface
//
///////////////////////////////////////////////////////////////////////////////

//// CBaseForm::QueryInterface
//
//
// DESCRIPTION: The form supports IMAPIform,
// IPersistMessage and IAMPIFormAdviseSink

STDMETHODIMP CBaseForm::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
if (riid == IID_IUnknown || riid == IID_IMAPIForm)
{
*ppvObj = (LPVOID) (IMAPIForm *) this;
}
else if (riid == IID_IPersistMessage)
{
*ppvObj = (LPVOID) (IPersistMessage *) this;
}
else if (riid == IID_IMAPIFormAdviseSink)
{
*ppvObj = (LPVOID) (IMAPIFormAdviseSink *) this;
}
else
{
*ppvObj = NULL;
return m_lsterr.HrSetLastError(ResultFromScode(E_NOINTERFACE));
}

AddRef();
return hrSuccess;
}

//// CBaseForm::AddRef
//
// Description:
//

STDMETHODIMP_(ULONG) CBaseForm::AddRef ()
{
m_cRef += 1;
return m_cRef;
}

//// CBaseForm::Release
//

STDMETHODIMP_(ULONG) CBaseForm::Release ()
{
ULONG cRef = -- m_cRef;

if (cRef == 0)
{
// Let the class factory now we are gone
m_pClassFactory->ObjDestroyedCallback();
delete this;
}

return cRef;
}


///////////////////////////////////////////////////////////////////////////////
//
// IMAPIForm interface
//
///////////////////////////////////////////////////////////////////////////////


//// IMAPIForm::SetViewContext
//
//

STDMETHODIMP CBaseForm::SetViewContext(IN IMAPIViewContext * pvc)
{

//
// If we currently have a view context, then release it
//

if (m_pviewctx != NULL)
{
m_pviewctx->SetAdviseSink(NULL);
m_pviewctx->Release();
}

//
// Accept the new view context.
//

m_pviewctx = pvc;

//
// If the new view context is non-null, then save it away, setup
// the advise sink back to check for things and get the current set
// of status flags
//

m_ulViewStatus = 0;
if (pvc != NULL)
{
m_pviewctx->AddRef ();
m_pviewctx->SetAdviseSink (this);
m_pviewctx->GetViewStatus(&m_ulViewStatus);
}

ConfigWinMenu();

return hrSuccess;
}


//// IMAPIForm::GetViewContext
//

STDMETHODIMP CBaseForm::GetViewContext(OUT IMAPIViewContext * FAR * ppvc)
{
Assert(ppvc);

*ppvc = m_pviewctx;

if(m_pviewctx != NULL)
{
m_pviewctx->AddRef();
return hrSuccess;
}
else
return ResultFromScode(S_FALSE);
}


//// IMAPIForm::ShutdownForm
//
// Description:
// This routine is called to shut down the form and if necessary
// to cause save changes to the form.
//

STDMETHODIMP CBaseForm::ShutdownForm(DWORD dwSaveOptions)
{
HRESULT hr;

//
// Check for valid state to make the call
//

switch( m_state )
{
default:
case stateDead:
m_viewnotify.OnShutdown ();
return m_lsterr.HrSetLastError(ResultFromScode(E_UNEXPECTED));

case stateUninit:
case stateNormal:
case stateNoScribble:
case stateHandsOffFromSave:
case stateHandsOffFromNormal:
break;
}


hr = HrQuerySave(dwSaveOptions);
if(HR_FAILED(hr))
return hr;
//
// Save us from ourselfs by add-refing the object
//

AddRef();

//
// Release the view context
//

if (m_pviewctx != NULL)
{
m_pviewctx->SetAdviseSink(NULL);
m_pviewctx->Release();
m_pviewctx = NULL;
}

//
// We need to notify anyone who has an advise on us that we are
// shutting down. We want to do this in such a manner as to
// protect ourselves since we are referencing the data structure
// internally. Thus the AddRef/Release pair.
//

m_viewnotify.OnShutdown ();

//
// Release message objects if we have them
//

if(g_FModalUp)
m_pviewctxOverride = NULL;

UlRelease(m_pmsg);
m_pmsg = NULL;

UlRelease(m_pmsgsite);
m_pmsgsite = NULL;

UlRelease(m_pab);
m_pab = NULL;

UlRelease(m_pses);
m_pses = NULL;


//
// Tell all objects to be closed and de-initialized, only IUnknown
// calls are legal after this.
//

DeInitObjects();

//
// We are now all done -- release our internal reference
//

Release();

return hrSuccess;
}


//// IMAPIForm::DoVerb
//

STDMETHODIMP CBaseForm::DoVerb(LONG iVerb, LPMAPIVIEWCONTEXT pviewctx,
ULONG hwndParent, LPCRECT lprcPosRect)
{
HRESULT hr;

//
// If a view context was passed in, then we need to get the
// status bits from this view context. Also we are going to save
// the current view context and use this view context for the
// duration of the verb execution.
//

if (pviewctx != NULL)
{
m_pviewctxOverride = pviewctx;
pviewctx->GetViewStatus(&m_ulViewStatus);
}

//
// Execute the requested verb. If we do not understand the verb
// or we do not support the verb then we return NO SUPPORT and let
// the viewer deal with this.
//

switch (iVerb)
{

case EXCHIVERB_OPEN:
hr = HrOpenForm((HWND) hwndParent, lprcPosRect, m_ulViewStatus);
break;

case EXCHIVERB_REPLYTOSENDER:
hr = HrReply(eREPLY, (HWND) hwndParent, lprcPosRect);
if(HR_SUCCEEDED(hr))
{
m_pviewctxOverride = NULL;
ShutdownForm(SAVEOPTS_NOSAVE);
}
break;

case EXCHIVERB_FORWARD:
hr = HrReply(eFORWARD, (HWND) hwndParent, lprcPosRect);
if(HR_SUCCEEDED(hr))
{
m_pviewctxOverride = NULL;
ShutdownForm(SAVEOPTS_NOSAVE);
}
break;

case EXCHIVERB_PRINT:
case EXCHIVERB_REPLYTOALL:
case EXCHIVERB_SAVEAS:
case EXCHIVERB_REPLYTOFOLDER:
//the viewer should not call us here
//(see Value in extensions section of smpfrm.cfg)
Assert(FALSE);

default:
hr = m_lsterr.HrSetLastError(ResultFromScode(MAPI_E_NO_SUPPORT));
break;
}

//
// If we moved to a different view context, then switch back to
// the one we started with.
//

m_pviewctxOverride = NULL;

if(m_pviewctx != NULL)
{
m_ulViewStatus =0;
m_pviewctx->GetViewStatus(&m_ulViewStatus);
ConfigWinMenu();
}

return hr;
}


//// IMAPIForm::Advise
//

STDMETHODIMP CBaseForm::Advise (IN IMAPIViewAdviseSink * pViewAdvise,
OUT ULONG FAR * pulConnection)
{
HRESULT hr;

hr = m_viewnotify.Advise (pViewAdvise, pulConnection);
if (FAILED(hr))
{
hr = m_lsterr.HrSetLastError(hr);
}
return hr;
}


//// IMAPIForm::Unadvise
//

STDMETHODIMP CBaseForm::Unadvise(ULONG ulConnection)
{
HRESULT hr;

hr = m_viewnotify.Unadvise(ulConnection);
if (FAILED(hr))
{
hr = m_lsterr.HrSetLastError(hr);
}

return hr;
}


///////////////////////////////////////////////////////////////////////////////
//
// IPersistMessage interface
//
///////////////////////////////////////////////////////////////////////////////

//// IPersistMessage::GetClassID

STDMETHODIMP CBaseForm::GetClassID(LPCLSID lpClassID)
{
*lpClassID = CLSID_IPM_NOTE_SAMPLE;
return hrSuccess;
}


//// IPersistMessage::GetLastError
//
// Description: This routine is used to get back a string giving more
// information about the last error in the form.
//

STDMETHODIMP CBaseForm::GetLastError(HRESULT hr, ULONG ulFlags,
LPMAPIERROR FAR * lppMAPIError)
{
return m_lsterr.HrGetLastError(hr, ulFlags, lppMAPIError);
}


//// IPersistMessage::IsDirty
//

STDMETHODIMP CBaseForm::IsDirty ()
{

if(m_fDirty)
return ResultFromScode(S_OK);

if(NULL == m_hwndDialog)
{
m_fDirty = FALSE;
}
else if(m_eFormType == eformRead)
{
m_fDirty = (m_fRecipientsDirty ||
Edit_GetModify(GetDlgItem(m_hwndDialog, ID_BODY)));
}
else
{
m_fDirty = (m_fRecipientsDirty ||
Edit_GetModify(GetDlgItem(m_hwndDialog, ID_BODY)) ||
Edit_GetModify(GetDlgItem(m_hwndDialog, ID_SUBJECT)) ||
Edit_GetModify(GetDlgItem(m_hwndDialog, ID_TO)) ||
Edit_GetModify(GetDlgItem(m_hwndDialog, ID_CC)));
}

return ResultFromScode ((m_fDirty ? S_OK : S_FALSE));
}


//// IPersistMessage::InitNew
//
// Description: This function is called in the case of composing a new
// message. There is a small set of properties which are set by
// the constructor of the message, however in general it can be
// assumed the message is clean.
//

STDMETHODIMP CBaseForm::InitNew(LPMAPIMESSAGESITE pmsgsite, LPMESSAGE pmsg)
{
//
// Ensure we are in a state where we can accept this call
//

switch(m_state)
{
case stateUninit:
case stateHandsOffFromSave:
case stateHandsOffFromNormal:
break;

default:
return m_lsterr.HrSetLastError(ResultFromScode(E_UNEXPECTED));
}

//
// If we currently have a message site, then release it as we
// will no longer be using it.
//

UlRelease(m_pmsgsite);
m_pmsgsite = NULL;

//
// Save away the pointers to the message and message site
//

m_pmsgsite = pmsgsite;
pmsgsite->AddRef();

m_ulSiteStatus = 0;
m_pmsgsite->GetSiteStatus(&m_ulSiteStatus);

m_pmsg = pmsg;
pmsg->AddRef();

//
// Make an assumption on the message flags and status
//

m_ulMsgStatus = 0;
m_ulMsgFlags = MSGFLAG_UNSENT;

if(m_hwnd)
DisplayMessage();

//
// We succeeded in doing the InitNew so move to the normal state
//

m_state = stateNormal;

//
// Tell everybody who cares that we just loaded a new message
//

m_viewnotify.OnNewMessage();
return hrSuccess;

}

//// IPersistMessage::Load
//
// Description: This routine is called as part of loading an existing
// message into the form.
//

STDMETHODIMP CBaseForm::Load(LPMAPIMESSAGESITE pmsgsite, LPMESSAGE pmsg,
ULONG ulMsgStatus, ULONG ulMsgFlags)
{
//
// Ensure we are in a state where we can accept this call
//

switch(m_state)
{
case stateUninit:
case stateHandsOffFromSave:
case stateHandsOffFromNormal:
break;

default:
return m_lsterr.HrSetLastError(ResultFromScode(E_UNEXPECTED));
}

//
// If we currently have a message site, then release it as we
// will no longer be using it.
//

UlRelease(m_pmsgsite);
m_pmsgsite = NULL;

UlRelease(m_pmsg);
m_pmsg = NULL;


HRESULT hr = HrGetMsgDataFromMsg(pmsg, ulMsgFlags);
if(HR_FAILED(hr))
goto err;

//
// Save away the message and message site which are passed in.
//

m_pmsg = pmsg;
pmsg->AddRef();

m_pmsgsite = pmsgsite;
pmsgsite->AddRef();

//
// Get the site status flags for disabling buttons & menus
//
m_ulSiteStatus = 0;
m_pmsgsite->GetSiteStatus(&m_ulSiteStatus);

//
// Save away these properties
//

m_ulMsgStatus = ulMsgStatus;
m_ulMsgFlags = ulMsgFlags;

//
// Put us into the normal state
//

m_state = stateNormal;


//
// if our form is up, display the message
//
if(m_hwnd)
DisplayMessage();

//
// Tell everybody who cares that we just loaded a new message
//

m_viewnotify.OnNewMessage();
return hrSuccess;

err:
return hr;
}

//// IPersistMessage::Save
//
// Description:
// This function will be called whenever a save operation of the
// information into the form should be done. We should only make
// modifications to the message in this function.
//

STDMETHODIMP CBaseForm::Save(IN LPMESSAGE pmsg, IN ULONG fSameAsLoad)
{
HRESULT hr;

//
// Check that we are in a state where we are willing to accept
// this call. Must have a message.
//

switch( m_state )
{
default:
Assert(FALSE);
case stateDead:
case stateUninit:
case stateNoScribble:
case stateHandsOffFromSave:
case stateHandsOffFromNormal:
return m_lsterr.HrSetLastError(ResultFromScode(E_UNEXPECTED));

case stateNormal:
break;
}

if (fSameAsLoad)
{
//
// Its the same message interface as was loaded into us. We can
// assume that the pmsg passed in is either NULL or an interface
// on the same object as the message we already have loaded
//

hr = HrSaveInto(m_pmsg);
}
else
{
//
// We need to copy everything into the new message as we are going
// to clone ourselves into it.
//

hr = m_pmsg->CopyTo(0, NULL, NULL, 0, NULL, &IID_IMessage, pmsg, 0, NULL);
if (FAILED(hr))
{
m_lsterr.HrSetLastError(hr, m_pmsg);
return hr;
}

//
// Now make all of the incremental changes
//

hr = HrSaveInto(pmsg);
}

if (FAILED(hr))
{
return hr;
}

m_state = stateNoScribble;
m_fSameAsLoaded = fSameAsLoad;

return S_OK;
}


//// IPersistMessage::SaveCompleted
//
//


STDMETHODIMP CBaseForm::SaveCompleted(IN LPMESSAGE pmsg)
{

switch( m_state )
{
case stateHandsOffFromNormal:
case stateHandsOffFromSave:
case stateNoScribble:
break;

default:
return m_lsterr.HrSetLastError(ResultFromScode(E_UNEXPECTED));
}

if((stateHandsOffFromNormal == m_state ||
stateHandsOffFromSave == m_state) && NULL == pmsg)
{
DebugTrace("smpfrm: SaveCompleted called in handsOff state with pmsg==NULL\r\n");
return m_lsterr.HrSetLastError(ResultFromScode(E_INVALIDARG));
}

ULONG ulOldState = m_state;
m_state = stateNormal;

//state == NoScribble , pmsg == NULL
if(NULL == pmsg)
{
if(m_fSameAsLoaded)
{
ClearDirty();
m_viewnotify.OnSaved();
}

return hrSuccess;
}


//state == handsOffFromNormal, pmsg != NULL
if(stateHandsOffFromNormal == ulOldState)
{
UlRelease(m_pmsg);

m_pmsg = pmsg;
pmsg->AddRef();

return hrSuccess;
}

//state == handsOffFromSave || NoScribble, pmsg != NULL
if(stateNoScribble == ulOldState)
{
UlRelease(m_pmsg);
m_pmsg = pmsg;
pmsg->AddRef();
}

m_viewnotify.OnSaved();
ClearDirty();

return hrSuccess;
}


//// IPersistMessage::HandsOffMessage
//
// Description: store, folder and message objects has to be released
// in this method.
//
//

STDMETHODIMP CBaseForm::HandsOffMessage ()
{

switch( m_state )
{
case stateNormal:
case stateNoScribble:
break;

default:
return m_lsterr.HrSetLastError(ResultFromScode(E_UNEXPECTED));
}

if(stateNormal == m_state)
m_state = stateHandsOffFromNormal;
else
m_state = stateHandsOffFromSave;

//
// We must have a message
//

Assert(m_pmsg != NULL);
m_pmsg->Release();
m_pmsg = NULL;


return hrSuccess;
}


///////////////////////////////////////////////////////////////////////////////
//
// IMAPIFormAdviseSink interfaces
//
///////////////////////////////////////////////////////////////////////////////



//// IMAPIFormAdviseSink::OnChange
//
// Description: called to notify about changes in viewctx status

STDMETHODIMP CBaseForm::OnChange(ULONG ulflag)
{
if(m_pviewctxOverride == NULL)
{
m_ulViewStatus = ulflag;
ConfigWinMenu();
}

return hrSuccess;
}

//// CBaseForm::OnActivateNext
//
// Description: We only say that we will handle the next message if
// it is the exact same message class as the current message.
// If the next message has the same "unsentness" will reuse the
// current object, otherwise ask our ClassFactory for a new one.
//

STDMETHODIMP CBaseForm::OnActivateNext(LPCSTR lpszMessageClass, ULONG ulMessageStatus,
ULONG ulMessageFlags,
LPPERSISTMESSAGE FAR * ppPersistMessage)
{
HRESULT hr;

*ppPersistMessage = NULL;

Assert(m_pval);


if(PR_MESSAGE_CLASS == m_pval[irtClass].ulPropTag)
{
//the message class comparison has to be case insensitive
if((lstrcmpi(m_pval[irtClass].Value.LPSZ, lpszMessageClass) != 0) &&
lstrcmpi(FormClassName, lpszMessageClass) != 0)
{
return ResultFromScode(S_FALSE);
}
}
else
{
if(lstrcmpi(FormClassName, lpszMessageClass) != 0)
{
return ResultFromScode(S_FALSE);
}
}


if((m_ulMsgFlags & MSGFLAG_UNSENT) == (ulMessageFlags & MSGFLAG_UNSENT))
//tell the viewer to reuse our object
return ResultFromScode(S_OK);


//Get a new object from our class factory
hr = m_pClassFactory->CreateInstance(NULL, IID_IPersistMessage, (LPVOID FAR *)ppPersistMessage);
if(hr)
return ResultFromScode (S_FALSE);
else
return ResultFromScode(S_OK);
}


///////////////////////////////////////////////////////////////////////////////
//
// Non-IMAPIinterface functions
//
///////////////////////////////////////////////////////////////////////////////


/// CBaseForm::HrGetMsgDataFromMsg
//
// fills in m_pval and m_padrlist (for unsent msgs only)
// with the info from pmsg
HRESULT CBaseForm::HrGetMsgDataFromMsg(LPMESSAGE pmsg, ULONG ulMsgFlags)
{
Assert(pmsg);

FreePadrlist(m_padrlist);
m_padrlist = NULL;


ULONG cValues = 0;
MAPIFreeBuffer(m_pval);
m_pval = NULL;

MAPIFreeBuffer(m_lpbConvIdx);
m_lpbConvIdx = NULL;

HRESULT hr = pmsg->GetProps((LPSPropTagArray) &tagaRead, 0,
&cValues, &m_pval);
if (HR_FAILED(hr))
{
m_lsterr.HrSetLastError(hr, pmsg);
goto err;
}

if(PROP_TYPE(m_pval[irtBody].ulPropTag) == PT_ERROR &&
GetScode(m_pval[irtBody].Value.l) == MAPI_E_NOT_ENOUGH_MEMORY)
{
hr = HrStreamInMsgBody(pmsg, m_pval, &m_pval[irtBody].Value.LPSZ, &m_lsterr);
if(hr)
{
goto err;
}
else
{
m_pval[irtBody].ulPropTag = PR_BODY;
}
}

Assert(cValues == cpropMsg);

if(PR_CONVERSATION_INDEX == m_pval[irtConvIdx].ulPropTag)
{
LPSPropValue pval = &m_pval[irtConvIdx];

m_cbConvIdx = pval->Value.bin.cb;
if(MAPIAllocateBuffer(m_cbConvIdx, (LPVOID *)&m_lpbConvIdx))
{
m_lpbConvIdx = NULL;
m_cbConvIdx = 0;
}
else
{
CopyMemory(m_lpbConvIdx, pval->Value.bin.lpb, m_cbConvIdx);
}
}
else
{
m_lpbConvIdx = NULL;
m_cbConvIdx = 0;
}

m_fConvTopicSet = (PR_CONVERSATION_TOPIC == m_pval[irtConvTopic].ulPropTag);


if(ulMsgFlags & MSGFLAG_UNSENT)
{
hr = GetMsgAdrlist (pmsg, &m_padrlist, &m_lsterr);
if(HR_FAILED(hr))
{
goto err;
}
}

return hrSuccess;

err:
MAPIFreeBuffer(m_pval);
m_pval = NULL;

FreePadrlist(m_padrlist);
m_padrlist = NULL;

return hr;

}

/// CBaseForm::ClearDirty
//
// Clears dirty state
void CBaseForm::ClearDirty(void)
{
m_fDirty = FALSE;
m_fRecipientsDirty = FALSE;

if(m_eFormType == eformSend)
{
Edit_SetModify(GetDlgItem(m_hwndDialog, ID_BODY), FALSE);
Edit_SetModify(GetDlgItem(m_hwndDialog, ID_SUBJECT), FALSE);
Edit_SetModify(GetDlgItem(m_hwndDialog, ID_TO), FALSE);
Edit_SetModify(GetDlgItem(m_hwndDialog, ID_CC), FALSE);
}
else
{
Edit_SetModify(GetDlgItem(m_hwndDialog, ID_BODY), FALSE);
}
}

//// CBaseForm::Address
//
// Description:
// This function is used to address the form.
// The parameter determines which button in the address
// dialog has the focus.
//

void CBaseForm::Address(int id)
{
Assert( ID_TO_BUTTON == id || ID_CC_BUTTON == id);

HRESULT hr;

if (m_pses == NULL)
{
hr = m_pmsgsite->GetSession(&m_pses);
if(hr)
{
m_lsterr.HrSetLastError(hr, m_pmsgsite);
ShowError();
return;
}
}

Assert(m_pses != NULL);

if (m_pab == NULL)
{
hr = m_pses->OpenAddressBook((ULONG) m_hwnd, NULL, 0, &m_pab);
if(hr)
{
m_lsterr.HrSetLastError(hr, m_pses);
ShowError();
}
if(HR_FAILED(hr)) //if it's a real error (not a warning)
return;
}

Assert(m_pab != NULL);


ADRPARM adrparm = { 0, NULL, AB_RESOLVE | DIALOG_MODAL, NULL, 0L,
NULL, NULL, NULL, NULL, "Address Book", NULL,
"Send Note To", 2, (id == ID_TO_BUTTON ? 0:1),
NULL, NULL, NULL, NULL };
ULONG ulHwndAddr = (ULONG) m_hwnd;

hr = m_pab->Address(&ulHwndAddr, &adrparm, &m_padrlist);
if (!hr)
{
m_fRecipientsDirty = TRUE;
UpdateRecipientsDisplay();
}
else if(GetScode(hr) != MAPI_E_USER_CANCEL)
{
m_lsterr.HrSetLastError(hr, m_pab);
ShowError();
}

}

//// CBaseForm::ClearWindow
//
// Description:
// This routine is called when the window for this form object is
// destroyed. We clear out pointer to the window and the windows
// pointer to us.
//

void CBaseForm::ClearWindow()
{
Assert(m_hwnd != NULL);

//
// Clear the back pointer to us and remove the reference count for it.
//

SetWindowLong(m_hwnd, 0, 0);
Release();

//
// Clear out pointer to the window since it is now dead
//

m_hwnd = NULL;
m_hwndDialog = NULL;
}

//// CBaseForm::HrOpenForm
//
// Description: This is the internal routine which is called from the
// open/display verb. It will cause UI to appear if there is none
// and force the window to the foreground if there is already UI.
//

HRESULT CBaseForm::HrOpenForm(HWND hwndParent, LPCRECT lprcPosRect,

ULONG ulViewFlags) 
{

if (lprcPosRect == NULL)
return m_lsterr.HrSetLastError(ResultFromScode(E_INVALIDARG));

//
// If any modal forms are visible then do not do anything
//

Assert( g_FModalUp && g_hwndUp || !g_FModalUp && !g_hwndUp);
if (g_FMBoxUp || (g_FModalUp && hwndParent != g_hwndUp))
return m_lsterr.HrSetLastError(
ResultFromScode(OLEOBJ_S_CANNOT_DOVERB_NOW));

if (!(ulViewFlags & VCSTATUS_MODAL))
{
// If we are not modal then don't do anything relative to the parent
hwndParent = NULL;
}

//
// Check to see if we have a window up
//

if (m_hwnd != 0)
{
MoveWindow(m_hwnd, lprcPosRect->left, lprcPosRect->top,
lprcPosRect->right - lprcPosRect->left,
lprcPosRect->bottom - lprcPosRect->top,
TRUE);
SetForegroundWindow(m_hwnd);

return S_OK;
}

m_hwnd = CreateWindow((m_ulMsgFlags & MSGFLAG_UNSENT) ?
g_szSendWinClass : g_szReadWinClass,
g_szWindowCaption, WS_OVERLAPPEDWINDOW,
0, 0, 10, 10, hwndParent, NULL, g_hinst, NULL);

if (m_hwnd == NULL)
{
return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
}

//
// Put the pointer to this object into the window
//

SetWindowLong(m_hwnd, 0, (long) (void FAR *) this);
//we just created another pointer to ourself, so addref
AddRef();

//
// Create the dialog as a child of this window
//

if (m_ulMsgFlags & MSGFLAG_UNSENT)
{
m_eFormType = eformSend;
m_hwndDialog = CreateDialog(g_hinst, MAKEINTRESOURCE(IDR_SEND_FORM),
m_hwnd, &FormDlgProcSend);
m_HAccelTable = LoadAccelerators(g_hinst, MAKEINTRESOURCE(IDR_SEND_FORM));
}
else
{
m_eFormType = eformRead;
m_hwndDialog = CreateDialog(g_hinst, MAKEINTRESOURCE(IDR_READ_FORM),
m_hwnd, &FormDlgProcRead);
m_HAccelTable = LoadAccelerators(g_hinst, MAKEINTRESOURCE(IDR_READ_FORM));
}


DisplayMessage();

//
// Position the window where it is suppose to be
//

MoveWindow(m_hwnd, lprcPosRect->left, lprcPosRect->top,
lprcPosRect->right - lprcPosRect->left,
lprcPosRect->bottom - lprcPosRect->top,
TRUE);

ShowWindow(m_hwnd, SW_SHOW);

SetForegroundWindow(m_hwnd);

//
// If we are modal, then we loop until the form is closed
//

if (ulViewFlags & VCSTATUS_MODAL)
{
MSG msg;

BOOL fOldModalUp = g_FModalUp;
HWND hwndOldUp = g_hwndUp;

g_FModalUp = TRUE;
g_hwndUp = m_hwnd;

while ((m_hwnd != NULL) && (GetMessage(&msg, m_hwnd, 0, 0)))
{
//first call our method and see if this message makes sense to us.
//if not, let WIN API care about it.
if (!TranslateMessage(msg))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}

g_FModalUp = fOldModalUp;
g_hwndUp = hwndOldUp;
}

return hrSuccess;
}

//// CBaseForm::HrSaveInto
//
// Description:
// This routine gives one central location which save all modified
// properties into a message.
//

HRESULT CBaseForm::HrSaveInto(LPMESSAGE pmsg)
{
ULONG cval = 0;
HRESULT hr;

//
// First, write out the recipient table
//

if(m_padrlist && m_fRecipientsDirty)
{
hr = pmsg->ModifyRecipients(0, m_padrlist);
if (FAILED(hr))
{
m_lsterr.HrSetLastError(hr, pmsg);
return hr;
}
}
//
// Next set up and save the rest of the info
//

HrGetMsgDataFromUI(m_hwndDialog);

//PR_DISPLAY_TO AND PR_DISPLAY_CC can't be set
ULONG ulToTag = m_pval[irtTo].ulPropTag;
m_pval[irtTo].ulPropTag = PR_NULL;

ULONG ulCcTag = m_pval[irtCc].ulPropTag;
m_pval[irtCc].ulPropTag = PR_NULL;

ULONG ulNormSubjectTag = m_pval[irtNormSubject].ulPropTag;
m_pval[irtNormSubject].ulPropTag = PR_NULL;

m_pval[irtClass].ulPropTag = PR_MESSAGE_CLASS;
m_pval[irtClass].Value.lpszA = FormClassName;

/*
*If the message didn't have PR_CONVERSATION_TOPIC when we loaded it, we'll
* set it every time we save the message. Otherwise we don't touch it
*/
if(!m_fConvTopicSet)
{
m_pval[irtConvTopic].ulPropTag = PR_CONVERSATION_TOPIC;
if(PR_SUBJECT == m_pval[irtSubject].ulPropTag)
{
m_pval[irtConvTopic].Value.LPSZ = m_pval[irtSubject].Value.LPSZ;
}
else
{
m_pval[irtConvTopic].Value.LPSZ = "";
}
}
else
{
m_pval[irtConvTopic].ulPropTag = PR_NULL;
}

/*
* if the message doesn't have a PR_CONVERSATION_INDEX, create and set it
*
*/
if(m_cbConvIdx == 0)
{
if(!ScAddConversationIndex(0, NULL, &m_cbConvIdx, &m_lpbConvIdx))
{
m_pval[irtConvIdx].ulPropTag = PR_CONVERSATION_INDEX;
m_pval[irtConvIdx].Value.bin.lpb = m_lpbConvIdx;
m_pval[irtConvIdx].Value.bin.cb = m_cbConvIdx;
}
else
{
m_pval[irtConvIdx].ulPropTag = PR_NULL;
}
}
else
{
m_pval[irtConvIdx].ulPropTag = PR_NULL;
}

LPSPropProblemArray pProblems = NULL;

hr = pmsg->SetProps(cpropMsg, m_pval, &pProblems);
if (hr)
{
m_lsterr.HrSetLastError(hr, pmsg);
goto err;
}

if(pProblems)
{
for(UINT ind = 0; ind < pProblems->cProblem; ++ind)
{
if(PR_BODY == pProblems->aProblem[ind].ulPropTag &&
MAPI_E_NOT_ENOUGH_MEMORY == pProblems->aProblem[ind].scode)
{
hr = HrStreamOutMsgBody(pmsg, m_pval[irtBody].Value.LPSZ, &m_lsterr);
if(hr)
{
MAPIFreeBuffer(pProblems);
pProblems = NULL;
goto err;
}
break;
}
}

MAPIFreeBuffer(pProblems);
pProblems = NULL;
}

err:
m_pval[irtTo].ulPropTag = ulToTag;
m_pval[irtCc].ulPropTag = ulCcTag;
m_pval[irtNormSubject].ulPropTag = ulNormSubjectTag;

return hr;
}


BOOL CBaseForm::TranslateMessage(MSG& msg)
{
//
// We translate accelerators before the dialog message so that we
// can get our accelerators to override the dialog's.
//

if(msg.hwnd == m_hwnd || IsChild(m_hwnd, msg.hwnd))
{
if (::TranslateAccelerator(m_hwnd, m_HAccelTable, &msg))
{
return TRUE;
}
}

if ((m_hwndDialog != NULL) && ::IsDialogMessage(m_hwndDialog, &msg))
{
return TRUE;
}

return FALSE;
}

/// CBaseForm::UpdateRecipientsDisplay
// go through m_padrlist and display TO and CC recipients in the
// edit boxes.
// Called only for send form. The Read form can use PR_DISPLAY_*
void CBaseForm::UpdateRecipientsDisplay(void)
{
Assert(m_hwndDialog);

Assert(m_eFormType == eformSend);

HWND hwTo = GetDlgItem(m_hwndDialog, ID_TO);
HWND hwCC = GetDlgItem(m_hwndDialog, ID_CC);

Edit_SetText(hwTo, "");
Edit_SetText(hwCC, "");

if(m_padrlist == NULL || m_padrlist->cEntries == 0) return;

#define ADRTEXTLEN 512
char szTo [ADRTEXTLEN] = "";
char szCC [ADRTEXTLEN] = "";

BOOL fToFull = FALSE;
BOOL fCCFull = FALSE;


LPADRENTRY pae;
for(pae = m_padrlist->aEntries;
pae < m_padrlist->aEntries + m_padrlist->cEntries;
++pae)
{
if( NULL == pae->rgPropVals) continue;

LPSPropValue lpsprop;
LPSTR szDisplay = NULL;
LPSTR szName = NULL;
BOOL * pfFull = NULL;
for(lpsprop = pae->rgPropVals;
lpsprop < pae->rgPropVals + pae->cValues;
++lpsprop)
{
if(lpsprop->ulPropTag == PR_RECIPIENT_TYPE)
{
if(lpsprop->Value.l == MAPI_TO)
{
szDisplay = szTo;
pfFull = &fToFull;
}
else if(lpsprop->Value.l == MAPI_CC)
{
szDisplay = szCC;
pfFull = &fCCFull;
}
}
else if(lpsprop->ulPropTag == PR_DISPLAY_NAME)
{
szName = lpsprop->Value.LPSZ;
}

if(NULL != szName && NULL != szDisplay)
{
Assert(pfFull);

if(*pfFull) break;

if(lstrlen(szDisplay) + lstrlen(szName) + 7 < ADRTEXTLEN)
{
lstrcat(szDisplay, szName);
lstrcat(szDisplay, "; ");
}
else
{
lstrcat(szDisplay, "...");
*pfFull = TRUE;
}

break;
}
}
}

//get rid of the "; " after the last recipient
if(lstrlen(szTo) > 0 && !fToFull)
szTo[lstrlen(szTo)-2] = 0;
if(lstrlen(szCC) > 0 && !fCCFull)
szCC[lstrlen(szCC)-2] = 0;

Edit_SetText(hwTo, szTo);
Edit_SetText(hwCC, szCC);

}


/// CBaseForm::DisplayMessage
//
// display the info from m_pval in the dialog
void CBaseForm::DisplayMessage(void)
{
char sz[256];

Assert(m_hwnd);
Assert(m_hwndDialog);

if(NULL == m_pval) return;

if (m_pval[irtSubject].ulPropTag == PR_SUBJECT)
{
SetDlgItemText(m_hwndDialog, ID_SUBJECT, m_pval[irtSubject].Value.LPSZ);
lstrcpyn(sz, m_pval[irtSubject].Value.LPSZ, 200);
sz[200] = 0;
lstrcat(sz, " - ");
lstrcat(sz, g_szWindowCaption);
SetWindowText(m_hwnd, sz);
}
else
{
SetWindowText(m_hwnd, g_szWindowCaption);
}

if (m_pval[irtBody].ulPropTag == PR_BODY)
SetDlgItemText(m_hwndDialog, ID_BODY, m_pval[irtBody].Value.LPSZ);

if(m_eFormType == eformRead)
{
if (m_pval[irtFrom].ulPropTag == PR_SENDER_NAME)
SetDlgItemText(m_hwndDialog, ID_FROM, m_pval[irtFrom].Value.LPSZ);

if (m_pval[irtTime].ulPropTag == PR_CLIENT_SUBMIT_TIME) {
FormatTime(&m_pval[irtTime].Value.ft, sz);
SetDlgItemText(m_hwndDialog, ID_SENT, sz);
}

if (m_pval[irtTo].ulPropTag == PR_DISPLAY_TO)
SetDlgItemText(m_hwndDialog, ID_TO, m_pval[irtTo].Value.LPSZ);

Edit_SetModify(GetDlgItem(m_hwndDialog, ID_BODY), FALSE);
}
else if (m_eFormType == eformSend)
{
UpdateRecipientsDisplay();

Edit_SetModify(GetDlgItem(m_hwndDialog, ID_BODY), FALSE);
Edit_SetModify(GetDlgItem(m_hwndDialog, ID_SUBJECT), FALSE);
Edit_SetModify(GetDlgItem(m_hwndDialog, ID_TO), FALSE);
Edit_SetModify(GetDlgItem(m_hwndDialog, ID_CC), FALSE);
}
else
{
Assert(FALSE);
}

}


/// CBaseForm::HrGetMsgDataFromUI
//
// save the message info from the dialog into the m_pval
HRESULT CBaseForm::HrGetMsgDataFromUI(HWND hDlg)
{

LONG cb = 0;
HRESULT hr = hrSuccess;


//have to call IsDirty() to make sure m_fDirty is current
if(!m_fDirty)
IsDirty();

if(m_eFormType == eformRead)
{
Assert(m_pval);

if(!m_fDirty)
return hrSuccess;

//everything is read-only except for the body
cb = GetWindowTextLength(GetDlgItem(hDlg, ID_BODY));
if(m_pval[irtBody].ulPropTag == PR_BODY &&
cb <= lstrlen(m_pval[irtBody].Value.LPSZ))
{
GetWindowText(GetDlgItem(hDlg, ID_BODY), m_pval[irtBody].Value.LPSZ, cb+1);
}
else
{
if(hr = MAPIAllocateMore(cb+1, m_pval, (LPVOID FAR *)&m_pval[irtBody].Value.LPSZ))
{
return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
}

GetWindowText(GetDlgItem(hDlg, ID_BODY), m_pval[irtBody].Value.LPSZ, cb+1);
}

m_pval[irtBody].ulPropTag = PR_BODY;
}
else if (m_eFormType == eformSend)
{
if(!m_fDirty && m_pval != NULL)
return hrSuccess;

if(NULL != m_pval)
{
MAPIFreeBuffer(m_pval);
m_pval = NULL;
}
if(MAPIAllocateBuffer(sizeof(SPropValue)* cpropMsg, (LPVOID FAR *) &m_pval))
{
return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
}

ZeroMemory(m_pval, sizeof(SPropValue)* cpropMsg);


m_pval[irtTime].ulPropTag = PR_NULL;
m_pval[irtFrom].ulPropTag = PR_NULL;

cb = GetWindowTextLength(GetDlgItem(hDlg, ID_SUBJECT)) + 1;
/*if(cb > 0)
{*/
if(MAPIAllocateMore(cb+1, m_pval, (LPVOID FAR *)&m_pval[irtSubject].Value.LPSZ))
{
return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
}

cb = GetWindowText(GetDlgItem(hDlg, ID_SUBJECT), m_pval[irtSubject].Value.LPSZ, cb+1);
m_pval[irtSubject].ulPropTag = PR_SUBJECT;
/*}
else
{ //no subject

m_pval[irtSubject].ulPropTag = PR_NULL;
} */

cb = GetWindowTextLength(GetDlgItem(hDlg, ID_BODY)) +1 ;
/*if(cb > 0)
{ */
if(MAPIAllocateMore(cb+1, m_pval, (LPVOID FAR *)&m_pval[irtBody].Value.LPSZ))
{
return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
}

cb = GetWindowText(GetDlgItem(hDlg, ID_BODY), m_pval[irtBody].Value.LPSZ, cb+1);
m_pval[irtBody].ulPropTag = PR_BODY;
/*}
else
{ //no body

m_pval[irtBody].ulPropTag = PR_NULL;
} */

cb = GetWindowTextLength(GetDlgItem(hDlg, ID_TO));
if(cb > 0)
{
if(hr = MAPIAllocateMore(cb+1, m_pval, (LPVOID FAR *)&m_pval[irtTo].Value.LPSZ))
{
return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
}

GetWindowText(GetDlgItem(hDlg, ID_TO), m_pval[irtTo].Value.LPSZ, cb+1);
m_pval[irtTo].ulPropTag = PR_DISPLAY_TO;
}
else
{ //no to

m_pval[irtTo].ulPropTag = PR_NULL;
}

cb = GetWindowTextLength(GetDlgItem(hDlg, ID_CC));
if(cb > 0)
{
if(hr = MAPIAllocateMore(cb+1, m_pval, (LPVOID FAR *)&m_pval[irtCc].Value.LPSZ))
{
return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
}

GetWindowText(GetDlgItem(hDlg, ID_CC), m_pval[irtCc].Value.LPSZ, cb+1);
m_pval[irtTo].ulPropTag = PR_DISPLAY_CC;
}
else
{ //no cc

m_pval[irtCc].ulPropTag = PR_NULL;
}
}
else
{
Assert(FALSE);
}
return hr;
}

/// CBaseForm::IsAddressed
//
//Does m_padrlist contain a recipient?
BOOL CBaseForm::IsAddressed(void)
{
Assert(m_eFormType == eformSend) ;

if(NULL == m_padrlist || m_padrlist->cEntries == 0)
return FALSE;

for(LPADRENTRY pae = m_padrlist->aEntries;
pae < m_padrlist->aEntries + m_padrlist->cEntries; ++pae)
{
if(pae->rgPropVals)
return TRUE;
}

return FALSE;
}

/// CBaseForm::ConfigMenu
//Enable/disable menu commands based on the values of m_ulSiteStatus
// and m_ulViewStatus
void CBaseForm::ConfigMenu(HMENU hMenu)
{
if(m_eFormType == eformRead)
{
EnableMenuItem(hMenu, IDC_MESSAGE_SAVE,
MF_BYCOMMAND|((m_ulSiteStatus & VCSTATUS_SAVE)? MF_ENABLED:MF_GRAYED));
EnableMenuItem(hMenu, IDC_MESSAGE_DELETE,
MF_BYCOMMAND| (!(m_ulViewStatus & VCSTATUS_READONLY) &&
(m_ulSiteStatus & VCSTATUS_DELETE) ? MF_ENABLED:MF_GRAYED));
EnableMenuItem(hMenu, IDC_MESSAGE_COPY,
MF_BYCOMMAND| (!(m_ulViewStatus & VCSTATUS_READONLY) &&
(m_ulSiteStatus & VCSTATUS_COPY) ? MF_ENABLED:MF_GRAYED));
EnableMenuItem(hMenu, IDC_MESSAGE_MOVE,
MF_BYCOMMAND| (!(m_ulViewStatus & VCSTATUS_READONLY) &&
(m_ulSiteStatus & VCSTATUS_MOVE) ? MF_ENABLED:MF_GRAYED));
EnableMenuItem(hMenu, IDC_VIEW_ITEMABOVE,
MF_BYCOMMAND|(m_ulViewStatus & VCSTATUS_PREV ? MF_ENABLED:MF_GRAYED));
EnableMenuItem(hMenu, IDC_VIEW_ITEMBELOW,
MF_BYCOMMAND|(m_ulViewStatus & VCSTATUS_NEXT ? MF_ENABLED:MF_GRAYED));
}

else
{
EnableMenuItem(hMenu, IDC_MESSAGE_SUBMIT,
MF_BYCOMMAND|((m_ulSiteStatus &VCSTATUS_SUBMIT) ? MF_ENABLED:MF_GRAYED));
EnableMenuItem(hMenu, IDC_MESSAGE_SAVE,
MF_BYCOMMAND|((m_ulSiteStatus & VCSTATUS_SAVE)? MF_ENABLED:MF_GRAYED));
EnableMenuItem(hMenu, IDC_MESSAGE_DELETE,
MF_BYCOMMAND| (!(m_ulViewStatus & VCSTATUS_READONLY) &&
(m_ulSiteStatus & VCSTATUS_DELETE) ? MF_ENABLED:MF_GRAYED));
EnableMenuItem(hMenu, IDC_MESSAGE_COPY,
MF_BYCOMMAND| (!(m_ulViewStatus & VCSTATUS_READONLY) &&
(m_ulSiteStatus & VCSTATUS_COPY) ? MF_ENABLED:MF_GRAYED));
EnableMenuItem(hMenu, IDC_MESSAGE_MOVE,
MF_BYCOMMAND| (!(m_ulViewStatus & VCSTATUS_READONLY) &&
(m_ulSiteStatus & VCSTATUS_MOVE) ? MF_ENABLED:MF_GRAYED));

}
}

///CBaseForm::HrReply
//
//
HRESULT CBaseForm::HrReply(eREPLYTYPE eReplyType, HWND hwndParent, LPCRECT prect)
{

//reply all is not implemented
Assert(eREPLY == eReplyType || eFORWARD == eReplyType);

HRESULT hr;
LONG cb;

char * szBody = NULL;
char * szSubject = NULL;
SPropValue val[3] = {0};

enum { eName, eAddrType, eEID, eRecipType, eDim};
SizedSPropTagArray(eDim, sptSender) =
{eDim, {PR_SENDER_NAME, PR_SENDER_ADDRTYPE,
PR_SENDER_ENTRYID, PR_NULL}};

LPSPropProblemArray pProblems = NULL;
LPMAPIFORM pfrmReply = NULL;
LPPERSISTMESSAGE ppermsg = NULL;
LPMAPIMESSAGESITE pmsgsite = NULL;
LPMAPIVIEWCONTEXT pviewctx = NULL;
LPMESSAGE pmsg = NULL;
ULONG cbNewConvIdx = 0;
LPBYTE lpbNewConvIdx = NULL;

Assert(m_pmsg);


hr = m_pClassFactory->CreateInstance(NULL, IID_IMAPIForm, (LPVOID FAR *) &pfrmReply);
if(hr)
{
m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
goto err;
}

hr = pfrmReply->QueryInterface(IID_IPersistMessage, (LPVOID *) &ppermsg);
if(hr)
{
m_lsterr.HrSetLastError(hr, pfrmReply);
goto err;
}

hr = m_pmsgsite->NewMessage(FALSE, NULL, ppermsg, &pmsg, &pmsgsite, &pviewctx);
if(hr)
{
m_lsterr.HrSetLastError(hr, m_pmsgsite);
goto err;
}

hr = m_pmsg->CopyTo(0, NULL, (LPSPropTagArray)&sptExcludedProps,
0, NULL, &IID_IMessage, pmsg, 0, &pProblems);
if(hr)
{
m_lsterr.HrSetLastError(hr, m_pmsg);
goto err;
}

if(pProblems)
{
DebugTraceProblems("SmplForm: CopyTo returned ...", pProblems);
// if any of the errors is other than MAPI_E_COMPUTED, fail
for(UINT ind = 0; ind < pProblems->cProblem; ++ind)
{
if(MAPI_E_COMPUTED != pProblems->aProblem[ind].scode)
{
hr = m_lsterr.HrSetLastError(
ResultFromScode(pProblems->aProblem[ind].scode));
MAPIFreeBuffer(pProblems);
pProblems = NULL;
goto err;

}
}
MAPIFreeBuffer(pProblems);
pProblems = NULL;
}

if(m_pval && m_pval[irtNormSubject].ulPropTag == PR_NORMALIZED_SUBJECT)
cb = lstrlen(m_pval[irtNormSubject].Value.LPSZ);
else
cb = 0;

if(hr = MAPIAllocateBuffer(cb+lstrlen(szRE_PREFIX)+1, (LPVOID FAR *) &szSubject))
{
m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
goto err;
}


*szSubject = '\0';

if(eREPLY == eReplyType)
{
lstrcat(szSubject, szRE_PREFIX);
}
else
{
lstrcat(szSubject, szFW_PREFIX);
}

if(cb > 0)
{
lstrcat(szSubject, m_pval[irtNormSubject].Value.LPSZ);
}

val[1].Value.lpszA = szSubject;
val[1].ulPropTag = PR_SUBJECT;

HrSaveToString(&szBody);
if(szBody)
{
val[0].Value.LPSZ = szBody;
val[0].ulPropTag = PR_BODY;
}
else
val[0].ulPropTag = PR_NULL;


/*
* Create a conversation index for the reply msg based on that of ours
*
*/
if(!ScAddConversationIndex(m_cbConvIdx, m_lpbConvIdx,
&cbNewConvIdx, &lpbNewConvIdx))
{
val[2].ulPropTag = PR_CONVERSATION_INDEX;
val[2].Value.bin.cb = cbNewConvIdx;
val[2].Value.bin.lpb = lpbNewConvIdx;
}
else
{
val[2].ulPropTag = PR_NULL;
}

hr = pmsg->SetProps(3, val, &pProblems);
MAPIFreeBuffer(lpbNewConvIdx);
lpbNewConvIdx = NULL;

MAPIFreeBuffer(szSubject);
szSubject = NULL;

if(!hr)
{
if(pProblems)
{
for(UINT ind = 0; ind < pProblems->cProblem; ++ind)
{
if(PR_BODY == pProblems->aProblem[ind].ulPropTag &&
MAPI_E_NOT_ENOUGH_MEMORY == pProblems->aProblem[ind].scode)
{
hr = HrStreamOutMsgBody(pmsg, szBody, &m_lsterr);
if(hr)
{
MAPIFreeBuffer(pProblems);
pProblems = NULL;
goto err;
}
break;
}
}
MAPIFreeBuffer(pProblems);
pProblems = NULL;
}
}
else
{
m_lsterr.HrSetLastError(hr, m_pmsg);
goto err;
}

// if it's a reply, set the addressee
if(eREPLY == eReplyType)
{
LPADRLIST pal = NULL;
ULONG cVal = 0;
LPSPropValue pval = NULL;

hr = MAPIAllocateBuffer(CbNewADRLIST(1), (LPVOID FAR *)&pal);
if(hr)
{
m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
goto err;
}
hr = m_pmsg->GetProps((LPSPropTagArray) &sptSender, 0, &cVal, &pval);
if(hr) //treat warnings as an error, 'cause the props we ask for are required
{
m_lsterr.HrSetLastError(hr, m_pmsg);
MAPIFreeBuffer(pal);
goto err;
}

Assert(cVal == eDim);

pval[eRecipType].ulPropTag = PR_RECIPIENT_TYPE;
pval[eRecipType].Value.l = MAPI_TO;

Assert(pval[eName].ulPropTag == PR_SENDER_NAME);
pval[eName].ulPropTag = PR_DISPLAY_NAME;

Assert(pval[eAddrType].ulPropTag == PR_SENDER_ADDRTYPE);
pval[eAddrType].ulPropTag = PR_ADDRTYPE;

Assert(pval[eEID].ulPropTag == PR_SENDER_ENTRYID);
pval[eEID].ulPropTag = PR_ENTRYID;

pal->aEntries[0].rgPropVals = pval;

pal->cEntries = 1;
pal->aEntries[0].cValues = eDim;

hr = pmsg->ModifyRecipients(0, pal);
FreePadrlist(pal); //this will also free pval
pal = NULL;
pval = NULL;
if(hr)
{
m_lsterr.HrSetLastError(hr, pmsg);
goto err;
}
}

hr = ppermsg->Load(pmsgsite, pmsg, 0, MSGFLAG_UNSENT );
if(hr)
{
m_lsterr.HrSetLastError(hr, ppermsg);
goto err;
}

hr = pfrmReply->DoVerb(EXCHIVERB_OPEN, pviewctx, (ULONG)hwndParent, prect);
if(hr)
{
m_lsterr.HrSetLastError(hr, pfrmReply);
pfrmReply->ShutdownForm(SAVEOPTS_NOSAVE);
goto err;
}

err:

UlRelease(pfrmReply);
UlRelease(ppermsg);
UlRelease(pmsgsite);
UlRelease(pviewctx);
UlRelease(pmsg);
delete [] szBody;

return hr;
}

/// CBaseForm::HrSaveToString
//
// The returned string has to be freed using delete
HRESULT CBaseForm::HrSaveToString(LPSTR * pszMessage)
{
Assert(m_pval);
Assert(pszMessage);

ostrstream ostrBody;
char *szMsg = NULL;

ostrBody << "\r\n-----------------------------";
ostrBody << "\r\nFrom:\t";
if(m_pval[irtFrom].ulPropTag == PR_SENDER_NAME)
{
ostrBody << m_pval[irtFrom].Value.LPSZ;
}
else
{
ostrBody << "Unknown";
}

if(m_pval[irtTime].ulPropTag == PR_CLIENT_SUBMIT_TIME)
{
char sz[30];
FormatTime(&m_pval[irtTime].Value.ft, sz);
ostrBody << "\r\nSent:\t";
ostrBody << sz;
}

if(m_pval[irtTo].ulPropTag == PR_DISPLAY_TO)
{
ostrBody << "\r\nTo:\t";
ostrBody << m_pval[irtTo].Value.LPSZ;
}

if(m_pval[irtSubject].ulPropTag == PR_SUBJECT)
{
ostrBody << "\r\nSubject:\t";
ostrBody << m_pval[irtSubject].Value.LPSZ;
}

ostrBody << "\r\n";
if(m_pval && m_pval[irtBody].ulPropTag == PR_BODY)
ostrBody << m_pval[irtBody].Value.LPSZ;

ostrBody << ends;

szMsg = ostrBody.str();

if(szMsg != NULL)
{
*pszMessage = szMsg;
return hrSuccess;
}
else
{
*pszMessage = NULL;
return m_lsterr.HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
}
}

/// CBaseForm::HrQuerySave
//
//
HRESULT CBaseForm::HrQuerySave (DWORD ulSaveOptions)
{
HRESULT hr = hrSuccess;
UINT ui;

//
// Check to see if we are marked as dirty. If we are clean then
// we can just return without doing any work
//

if(GetScode(IsDirty()) != S_OK)
return hrSuccess;

switch( ulSaveOptions )
{
default:
Assert(FALSE);

case SAVEOPTS_SAVEIFDIRTY:
break;

case SAVEOPTS_NOSAVE:
return hrSuccess;

case SAVEOPTS_PROMPTSAVE:
ui = ShowMessageBox (m_hwnd, "Would you like to save changes?",
g_szFormName, MB_ICONEXCLAMATION | MB_YESNOCANCEL);
switch (ui)
{
case IDYES:
break;

default:
Assert(FALSE);
case IDNO:
return hrSuccess;

case IDCANCEL:
return MAPI_E_USER_CANCEL;

}
}

hr = m_pmsgsite->SaveMessage();
if(hr)
m_lsterr.HrSetLastError(hr, m_pmsgsite);

return hr;
}

/// CBaseForm::DoDelete
//
// called only from our UI
void CBaseForm::DoDelete(void)
{
HRESULT hr;
RECT rect;

GetWindowRect(m_hwnd, &rect);

hr = m_pmsgsite->DeleteMessage(m_pviewctx, &rect);
if(HR_FAILED(hr))
{
m_lsterr.HrSetLastError(hr, m_pmsgsite);
ShowError();
}
if(NULL == m_pmsg)
{
ShutdownForm(SAVEOPTS_NOSAVE);
}
}

/// CBaseForm::DoSave
//
// called only from our UI
void CBaseForm::DoSave(void)
{
HRESULT hr;

hr = m_pmsgsite->SaveMessage();
if (FAILED(hr))
{
m_lsterr.HrSetLastError(hr, m_pmsgsite);

ShowError(); 
}
}

/// CBaseForm::DoSubmit
//
// called only from our UI
void CBaseForm::DoSubmit(void)
{
HRESULT hr;

if(!IsAddressed())
{
ShowMessageBox(m_hwndDialog, "No recipients", g_szFormName, MB_OK);
return;
}

hr = m_pmsgsite->SubmitMessage(0);
if (FAILED(hr))
{
m_lsterr.HrSetLastError(hr, m_pmsgsite);
ShowError();
}
else
{
m_viewnotify.OnSubmitted();
}

if (m_pmsg == NULL)
ShutdownForm(SAVEOPTS_NOSAVE);
}

/// CBaseForm::DoNext
//
// called only from our UI
void CBaseForm::DoNext(ULONG ulDir)
{
Assert(VCDIR_NEXT == ulDir || VCDIR_PREV == ulDir);

HRESULT hr;

hr = HrQuerySave(SAVEOPTS_PROMPTSAVE);
if(hr)
{
if(hr != MAPI_E_USER_CANCEL)
ShowError();
return;
}

RECT rect;
GetWindowRect(m_hwnd, &rect);

hr = ViewCtx()->ActivateNext(ulDir, &rect);
if(NULL == m_pmsg)
{
ShutdownForm(SAVEOPTS_NOSAVE);
}
}

/// CBaseForm::DoReply
//
// called only from our UI
void CBaseForm::DoReply(eREPLYTYPE eType)
{
HRESULT hr;

hr = HrQuerySave(SAVEOPTS_PROMPTSAVE);
if(hr)
{
if(hr != MAPI_E_USER_CANCEL)
ShowError();
return;
}

RECT rect;
GetWindowRect(m_hwnd, &rect);

int iOffset = GetSystemMetrics(SM_CYCAPTION);
OffsetRect(&rect, iOffset, iOffset);

hr = HrReply(eType, m_hwnd, &rect);
if(!hr)
{
ShutdownForm(SAVEOPTS_NOSAVE);
}
else
{
ShowError();
}
}

/// CBaseForm::DoCopy
//
// called only from our UI
void CBaseForm::DoCopy(void)
{
HRESULT hr;
LPMAPIFOLDER pfld = NULL;
LPMDB pmdb = NULL;

if(!FGetFoldChooser())
{
ShowMessageBox(m_hwnd, "Can't copy", g_szFormName, MB_OK | MB_ICONSTOP);
return;
}

Assert(m_lpfnHrPickFolder && m_hChsFldDll);

if (m_pses == NULL)
{
hr = m_pmsgsite->GetSession(&m_pses);
if(hr)
{
m_lsterr.HrSetLastError(hr, m_pmsgsite);
ShowError();
return;
}
}

BOOL fOldModalUp = g_FModalUp;

g_FModalUp = TRUE;

hr = (*m_lpfnHrPickFolder)(g_hinst, m_hwnd, m_pses, &pfld, &pmdb,
&m_cbCFDState, &m_pbCFDState);


g_FModalUp = fOldModalUp;

if(hr)
{
if(GetScode(hr) != MAPI_E_USER_CANCEL)
ShowMessageBox(m_hwnd, "Can't copy", g_szFormName, MB_OK | MB_ICONSTOP);

return;
}

Assert(m_pmsgsite);
Assert(pfld);
Assert(pmdb);

hr = m_pmsgsite->CopyMessage(pfld);
pfld->Release();
pmdb->Release();
if(hr)
{
m_lsterr.HrSetLastError(hr, m_pmsgsite);
ShowError();
return;
}

}


/// CBaseForm::DoMove
//
// called only from our UI
void CBaseForm::DoMove(void)
{
HRESULT hr;
LPMAPIFOLDER pfld = NULL;
LPMDB pmdb = NULL;

if(!FGetFoldChooser())
{
ShowMessageBox(m_hwnd, "Can't move", g_szFormName, MB_OK | MB_ICONSTOP);
return;
}

Assert(m_lpfnHrPickFolder && m_hChsFldDll);

if (m_pses == NULL)
{
hr = m_pmsgsite->GetSession(&m_pses);
if(hr)
{
m_lsterr.HrSetLastError(hr, m_pmsgsite);
ShowError();
return;
}
}


BOOL fOldModalUp = g_FModalUp;

g_FModalUp = TRUE;

hr = (*m_lpfnHrPickFolder)(g_hinst, m_hwnd, m_pses, &pfld, &pmdb,
//&m_cbCFDState, &m_pbCFDState);
NULL, NULL);

g_FModalUp = fOldModalUp;

if(hr)
{
if(GetScode(hr) != MAPI_E_USER_CANCEL)
ShowMessageBox(m_hwnd, "Can't move", g_szFormName, MB_OK | MB_ICONSTOP);

return;
}

Assert(m_pmsgsite);
Assert(pfld);
Assert(pmdb);

RECT rect;
GetWindowRect(m_hwnd, &rect);

hr = m_pmsgsite->MoveMessage(pfld, ViewCtx(), &rect);
pfld->Release();
pmdb->Release();
if(HR_FAILED(hr))
{
m_lsterr.HrSetLastError(hr, m_pmsgsite);
ShowError();
return;
}

if(NULL == m_pmsg)
{
ShutdownForm(SAVEOPTS_NOSAVE);
}


}

//wraper for MessageBox()
int CBaseForm::ShowMessageBox(HWND hwnd, LPCTSTR lpszText, LPCTSTR lpszTitle, UINT uiStyle)
{
int iret;
BOOL fOldModalUp = g_FMBoxUp;

g_FMBoxUp = TRUE;

iret = MessageBox(hwnd, lpszText, lpszTitle, uiStyle);

g_FMBoxUp = fOldModalUp;

return iret;
}

//wraper for m_lsterr.ShowError()
void CBaseForm::ShowError(void)
{
int iret;
BOOL fOldModalUp = g_FMBoxUp;

g_FMBoxUp = TRUE;

iret = m_lsterr.ShowError(m_hwnd);

g_FMBoxUp = fOldModalUp;

}

BOOL CBaseForm::FGetFoldChooser(void)
{
if(m_lpfnHrPickFolder)
return TRUE;

Assert(!m_hChsFldDll);

UINT uiErrMode = SetErrorMode(SEM_NOOPENFILEERRORBOX);

m_hChsFldDll = LoadLibrary(szChsFldDllName);

SetErrorMode(uiErrMode);

if(m_hChsFldDll)
{
if((m_lpfnHrPickFolder = (HRPICKFOLDER)GetProcAddress(m_hChsFldDll,
szChsFldFnName)))
{
return TRUE;
}

DebugTrace("smpfrm: GetProcAddress for %s failed", szChsFldFnName);

FreeLibrary(m_hChsFldDll);
m_hChsFldDll = NULL;
}
else
{
DebugTrace("smpfrm: failed to load choose folder dll\n");
}

return FALSE;
}

//
//if the body is to large for GetProps, have to use IStream
//
HRESULT HrStreamInMsgBody(LPMESSAGE pmsg, LPVOID pbase,
LPSTR * pszBody, CLastError * plasterror)
{
Assert(pmsg);
Assert(pszBody);
Assert(plasterror);

HRESULT hr;
LPSTREAM lpstreamBody = NULL;
STATSTG statstg;
LPSTR szRet = NULL;
ULONG cb = 0;

Assert(pszBody);
*pszBody = NULL;

hr = pmsg->OpenProperty(PR_BODY, &IID_IStream,
STGM_READ, 0, (LPUNKNOWN FAR *) &lpstreamBody);
if(S_OK != GetScode(hr))
{
plasterror->HrSetLastError(hr, pmsg);
goto err;
}

hr = lpstreamBody->Stat(&statstg, STATFLAG_NONAME);
if(S_OK != GetScode(hr))
{
plasterror->HrSetLastError(hr, lpstreamBody);
goto err;
}
Assert(statstg.cbSize.HighPart == 0);

//if p base is not null, link the new buffer to it
if(pbase)
{
if(MAPIAllocateMore(statstg.cbSize.LowPart + 1, pbase, (LPVOID FAR *) &szRet))
{
plasterror->HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
goto err;
}
}
else
{
if(MAPIAllocateBuffer(statstg.cbSize.LowPart + 1, (LPVOID FAR *) &szRet))
{
plasterror->HrSetLastError(ResultFromScode(E_OUTOFMEMORY));
goto err;
}
}

hr = lpstreamBody->Read(szRet, statstg.cbSize.LowPart, &cb);
if(S_OK != GetScode(hr))
{
plasterror->HrSetLastError(hr);
goto err;
}
szRet[statstg.cbSize.LowPart] = '\0';

err:
UlRelease(lpstreamBody);
lpstreamBody = NULL;
if(hr)
{
if(!pbase)
MAPIFreeBuffer(szRet);
szRet = NULL;

}

*pszBody = szRet;

return hr;
}
//
//if the body is to large for SetProps, have to use IStream
//
HRESULT HrStreamOutMsgBody(LPMESSAGE pmsg, LPSTR szBody, CLastError * plasterror)
{
Assert(pmsg);
Assert(szBody);
Assert(plasterror);

HRESULT hr;
LPSTREAM lpstreamBody = NULL;
ULONG cb = 0;

Assert(szBody);

hr = pmsg->OpenProperty(PR_BODY, &IID_IStream,
STGM_READWRITE, 0, (LPUNKNOWN FAR *) &lpstreamBody);
if(S_OK != GetScode(hr))
{
plasterror->HrSetLastError(hr, pmsg);
goto err;
}

hr = lpstreamBody->Write(szBody, lstrlen(szBody)+1, NULL);
if(hr)
plasterror->HrSetLastError(hr);

err:
UlRelease(lpstreamBody);
lpstreamBody = NULL;

return hr;
}

/*
* Formats a Win32 file time as a MAPI date/time string.
* NOTE: converts from GMT to local time.
*/
void FormatTime(FILETIME *pft, LPSTR szTime)
{
FILETIME ft;
SYSTEMTIME systime;

FileTimeToLocalFileTime(pft, &ft);
FileTimeToSystemTime(&ft, &systime);
wsprintf(szTime,
"%04.4d/%02.2d/%02.2d %02.2d:%02.2d",
systime.wYear, systime.wMonth, systime.wDay,
systime.wHour, systime.wMinute);
}

/// GetMsgAdrlist
// retrieves recipients adrlist of a message
HRESULT GetMsgAdrlist (LPMESSAGE pmsg, LPADRLIST * ppAdrList, CLastError * plasterror)
{
*ppAdrList = NULL;

LPMAPITABLE pTable = NULL;
HRESULT hr;

hr = pmsg->GetRecipientTable (0, &pTable);
if(!hr)
{
hr = HrQueryAllRows(pTable, NULL, NULL, NULL, 0, (LPSRowSet *)ppAdrList);
if(hr)
{
plasterror->HrSetLastError(hr, pTable);
}
}
else
{
plasterror->HrSetLastError(hr, pmsg);
}

pTable->Release();

return hr;
}