EVENTS.CPP

/* 
* EVENTS.CPP
* Patron Chapter 24
*
* Implementation of the Events dialog box, the CEventMap class,
* and the events IDispatch on a tenant control site.
*
* Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
*
* Kraig Brockschmidt, Microsoft
* Internet : kraigb@microsoft.com
* Compuserve: >INTERNET:kraigb@microsoft.com
*/


#include "patron.h"


/*
* EventsDlgProc
*
* Purpose:
* Dialog procedure for the dialog in which the user can assign
* actions to events.
*/

BOOL APIENTRY EventsDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
, LPARAM lParam)
{
PCEventMap pEM=NULL;
HWND hList;
UINT i, iEvent;

COMMANDPARAMS(wID, wCode, hWndMsg);

#ifdef WIN32
pEM=(PCEventMap)GetProp(hDlg, PROP_POINTER);
#else
WORD w1, w2;

w1=(WORD)GetProp(hDlg, PROP_SELECTOR);
w2=(WORD)GetProp(hDlg, PROP_OFFSET);

pEM=(PCEventMap)MAKELP(w1, w2);
#endif

switch (iMsg)
{
case WM_INITDIALOG:
pEM=(PCEventMap)lParam;

#ifdef WIN32
SetProp(hDlg, PROP_POINTER, (HANDLE)pEM);
#else
SetProp(hDlg, PROP_SELECTOR, (HANDLE)SELECTOROF(pEM));
SetProp(hDlg, PROP_OFFSET, (HANDLE)OFFSETOF(pEM));
#endif

/*
* Fill the listbox with events and select the first
* one. The selection will cause an LBN_SELCHANGE
* notification which will set the appropriate
* radiobutton for the action.
*/

hList=GetDlgItem(hDlg, IDC_EVENTLIST);

for (i=0; i < pEM->m_cEvents; i++)
{
//Add the name of the event to the list
#ifdef WIN32ANSI
char szTemp[40];

WideCharToMultiByte(CP_ACP, 0
, pEM->m_pEventMap[i].bstrName, -1, szTemp
, 40, NULL, NULL);
iEvent=(UINT)SendMessage(hList, LB_ADDSTRING, 0
, (LONG)(LPSTR)szTemp);
#else
iEvent=(UINT)SendMessage(hList, LB_ADDSTRING, 0
, (LONG)(LPSTR)pEM->m_pEventMap[i].bstrName);
#endif

if (LB_ERR!=iEvent)
{
//Give that item a pointer to the map data
SendMessage(hList, LB_SETITEMDATA, iEvent
, (LONG)&pEM->m_pEventMap[i]);
}
}

//Set the initial action for the first item
SendMessage(hList, LB_SETCURSEL, 0, 0L);
CheckAction(hDlg, hList);
return TRUE;

case WM_COMMAND:
hList=GetDlgItem(hDlg, IDC_EVENTLIST);

switch (wID)
{
case IDOK:
EndDialog(hDlg, 1);
break;

case ID_TEST:
TestSelection(hList);
break;

case IDC_EVENTLIST:
//Update the radiobuttons
if (LBN_SELCHANGE==wCode)
CheckAction(hDlg, hWndMsg);

//Double-click, same as hitting Test button
if (LBN_DBLCLK==wCode)
TestSelection(GetDlgItem(hDlg, IDC_EVENTLIST));
break;

case IDC_BEEPNONE:
case IDC_BEEPDEFAULT:
case IDC_BEEPEXCLAMATION:
case IDC_BEEPASTERISK:
case IDC_BEEPHAND:
case IDC_BEEPQUESTION:
UpdateAction(hList, wID);
break;
}
return TRUE;
}

return FALSE;
}



/*
* CheckAction
*
* Purpose:
* Sets the appropriate radiobutton for the current listbox
* selection
*
* Parameters:
* hDlg HWND of the dialog.
* hList HWND of the event list.
*
* Return Value:
* None
*/

void CheckAction(HWND hDlg, HWND hList)
{
UINT i, idControl;
PEVENTMAP pMap;

i=(UINT)SendMessage(hList, LB_GETCURSEL, 0, 0L);
pMap=(PEVENTMAP)SendMessage(hList, LB_GETITEMDATA, i, 0L);

if (LB_ERR==(LONG)pMap)
return;

//Map the action to the button
switch (pMap->iAction)
{
case ACTION_NONE: idControl=IDC_BEEPNONE; break;
case ACTION_BEEPDEFAULT: idControl=IDC_BEEPDEFAULT; break;
case ACTION_BEEPASTERISK: idControl=IDC_BEEPASTERISK; break;
case ACTION_BEEPEXCLAMATION: idControl=IDC_BEEPEXCLAMATION; break;
case ACTION_BEEPHAND: idControl=IDC_BEEPHAND; break;
case ACTION_BEEPQUESTION: idControl=IDC_BEEPQUESTION; break;
default: idControl=IDC_BEEPNONE; break;
}

CheckRadioButton(hDlg, IDC_BEEPNONE, IDC_BEEPQUESTION, idControl);
return;
}



/*
* UpdateAction
*
* Purpose:
* Sets the appropriate action in the event map for the
* selected radiobutton.
*
* Parameters:
* hList HWND of the event list.
* idControl UINT identifier of the selected action control
*
* Return Value:
* None
*/

void UpdateAction(HWND hList, UINT idControl)
{
UINT i;
PEVENTMAP pMap;

i=(UINT)SendMessage(hList, LB_GETCURSEL, 0, 0L);
pMap=(PEVENTMAP)SendMessage(hList, LB_GETITEMDATA, i, 0L);

if (LB_ERR==(LONG)pMap)
return;

//Map the button to the action
switch (idControl)
{
case IDC_BEEPNONE: pMap->iAction=ACTION_NONE; break;
case IDC_BEEPDEFAULT: pMap->iAction=ACTION_BEEPDEFAULT; break;
case IDC_BEEPASTERISK: pMap->iAction=ACTION_BEEPASTERISK; break;
case IDC_BEEPEXCLAMATION: pMap->iAction=ACTION_BEEPEXCLAMATION; break;
case IDC_BEEPHAND: pMap->iAction=ACTION_BEEPHAND; break;
case IDC_BEEPQUESTION: pMap->iAction=ACTION_BEEPQUESTION; break;
default: pMap->iAction=ACTION_NONE; break;
}

return;
}





/*
* TestSelection
*
* Purpose:
* Executes the action associated with the currently selected
* event.
*
* Parameters:
* hList HWND of the event list.
*
* Return Value:
* None
*/

void TestSelection(HWND hList)
{
UINT i;
PEVENTMAP pMap;

i=(UINT)SendMessage(hList, LB_GETCURSEL, 0, 0L);
pMap=(PEVENTMAP)SendMessage(hList, LB_GETITEMDATA, i, 0L);

//Event values corresond to MessageBeep values.
if (LB_ERR!=(LONG)pMap)
MessageBeep(pMap->iAction);

return;
}




//CEventMap implementations


/*
* CEventMap::CEventMap
* CEventMap::~CEventMap
*
* Parameters (Constructor):
* pITypeInfo LPTYPEINFO from which to read event names.
*/

CEventMap::CEventMap(LPTYPEINFO pITypeInfo)
{
m_cEvents=0;
m_pITypeInfo=pITypeInfo;

if (NULL!=m_pITypeInfo)
m_pITypeInfo->AddRef();

m_pEventMap=NULL;
return;
}




CEventMap::~CEventMap(void)
{
if (NULL!=m_pITypeInfo)
m_pITypeInfo->Release();

if (NULL!=m_pEventMap)
{
UINT i;

//Be sure to clean up allocated BSTRs
for (i=0; i < m_cEvents; i++)
SysFreeString(m_pEventMap[i].bstrName);

delete [] m_pEventMap;
}

return;
}



/*
* CEventMap::Init
*
* Purpose:
* Initializes the event map with any operation prone to failure.
* If this function fails, the caller should delete this object
* immediately as it is unusable.
*
* Parameters:
* None
*
* Return Value:
* BOOL TRUE if initialization succeeded, FALSE otherwise
*/

BOOL CEventMap::Init(void)
{
LPTYPEATTR pTA;
UINT i;

if (NULL==m_pITypeInfo)
return FALSE;

if (FAILED(m_pITypeInfo->GetTypeAttr(&pTA)))
return FALSE;

m_cEvents=pTA->cFuncs;
m_pITypeInfo->ReleaseTypeAttr(pTA);

m_pEventMap=new EVENTMAP[m_cEvents];

if (NULL==m_pEventMap)
{
m_cEvents=0;
return FALSE;
}

for (i=0; i < m_cEvents; i++)
{
LPFUNCDESC pFD;

m_pEventMap[i].id=0;
m_pEventMap[i].bstrName=NULL;
m_pEventMap[i].iAction=ACTION_NONE;

/*
* The only piece of information we want from for each
* event is the function name using ITypeInfo::GetNames.
*
* A more sophisticated container will probably save
* more information about each event here (such as
* parameter names and so forth) or access it dynamically
* when the end user wants to write code for events.
*/

if (SUCCEEDED(m_pITypeInfo->GetFuncDesc(i, &pFD)))
{
UINT cNames;
HRESULT hr;

/*
* Since we only want the function name, we ask
* ITypeInfo::GetNames for only one function and pass
* the address of our one BSTR to it. If we wanted all
* the names from ITypeInfo, then we'd allocate an
* array of BSTRs with "new BSTR[pFD->cParams+1]"
* and pass pFD->cParams+1 to GetNames below instead
* of just 1. In either case, GetNames allocates
* the string and stores the pointer to it in our
* variable.
*/

m_pEventMap[i].id=pFD->memid;

hr=m_pITypeInfo->GetNames(pFD->memid
, &m_pEventMap[i].bstrName, 1, &cNames);

m_pITypeInfo->ReleaseFuncDesc(pFD);
}
}

return TRUE;
}




/*
* CEventMap::Set
*
* Purpose:
* Sets the event mapping of a specific ID to a given action.
* To clear an event, call this function with the ID and
* ACTION_NONE.
*
* Parameters:
* id DISPID of the event ID.
* iAction EVENTACTION to assign.
*
* Return Value:
* BOOL TRUE if the assignment happened, FALSE otherwise.
*/

BOOL CEventMap::Set(DISPID id, EVENTACTION iAction)
{
BOOL fRet=FALSE;

if (NULL!=m_pEventMap)
{
UINT i;

for (i=0; i < m_cEvents; i++)
{
if (m_pEventMap[i].id==id)
{
m_pEventMap[i].iAction=iAction;
fRet=TRUE;
}
}
}

return fRet;
}




/*
* CEventMap::Get
*
* Purpose:
* Retrieves the event assignment for a given ID.
*
* Parameters:
* id DISPID of the event ID.
*
* Return Value:
* EVENTACTION The action assigned to this ID. ACTION_NONE
* if the ID is invalid.
*/

EVENTACTION CEventMap::Get(DISPID id)
{
EVENTACTION iAction=ACTION_NONE;

if (NULL!=m_pEventMap)
{
UINT i;

//Scan the list looking for the event
for (i=0; i < m_cEvents; i++)
{
if (m_pEventMap[i].id==id)
{
iAction=m_pEventMap[i].iAction;
break;
}
}
}

return iAction;
}




/*
* CEventMap::Serialize
* CEventMap::Deserialize
*
* Purpose:
* Writes or reads the mappings from DISPID to actions
* into or from a stream.
*
* Parameters:
* pIStream LPSTREAM into which to write or from which to
* read.
*
* Return Value:
* None
*/

void CEventMap::Serialize(LPSTREAM pIStream)
{
EVENTMAP emTemp;
ULONG cbWrite=sizeof(DISPID)+sizeof(EVENTACTION);

if (NULL==pIStream)
return;

/*
* Loop through all the IDs and write the ID and the action
* mapping only. We don't need the event name because that
* will be retrieved when the control is again loaded.
*
* Writing these pieces of info means writing the first
* so many bytes of each EVENTMAP structure, ignoring the
* BSTR of the name.
*/

if (NULL!=m_pEventMap)
{
UINT i;

for (i=0; i < m_cEvents; i++)
pIStream->Write(&m_pEventMap[i], cbWrite, NULL);
}

/*
* Finish off by writing a terminating EVENTMAP structure
* where the action is ACTION_TAILING which only have
* meaning here. The ID is ignored in this tail.
*/

emTemp.id=0;
emTemp.iAction=ACTION_TAILING;
pIStream->Write(&emTemp, cbWrite, NULL);

return;
}



void CEventMap::Deserialize(LPSTREAM pIStream)
{
if (NULL==pIStream)
return;

/*
* When reading back the event mappings we have to be
* careful: the control's event set might have changed
* in the meantime so some events may no longer exist and
* there may be new events. Therefore we read each mapping
* one at a time (until we hit the tailing map) and find
* the ID in the current memory event map. When we find
* a match we update the action in memory.
*/

if (NULL==m_pEventMap)
return;

while (TRUE)
{
ULONG cbRead=sizeof(DISPID)+sizeof(EVENTACTION);
HRESULT hr;
EVENTMAP em;

hr=pIStream->Read(&em, cbRead, NULL);

//Failure to read means a stream problem, to abort
if (FAILED(hr))
break;

//If we hit the tail, we're done
if (ACTION_TAILING==em.iAction)
break;

//Assign the action to the ID, if it exists
Set(em.id, em.iAction);
}

return;
}





//Events IDispatch

/*
* CDispatchEvents::CDispatchEvents
* CDispatchEvents::~CDispatchEvents
*
* Parameters (Constructor):
* pTen PCTenant of the tenant we're in.
*/

CDispatchEvents::CDispatchEvents(PCTenant pTen)
{
m_cRef=0;
m_pTen=pTen;
return;
}

CDispatchEvents::~CDispatchEvents(void)
{
return;
}




/*
* CDispatchEvents::QueryInterface
* CDispatchEvents::AddRef
* CDispatchEvents::Release
*
* Purpose:
* IUnknown members for CDispatchEvents object.
*/

STDMETHODIMP CDispatchEvents::QueryInterface(REFIID riid, PPVOID ppv)
{
*ppv=NULL;

if (IID_IUnknown==riid || IID_IDispatch==riid
|| m_pTen->m_iidEvents==riid)
*ppv=this;

if (NULL!=*ppv)
{
((LPUNKNOWN)*ppv)->AddRef();
return NOERROR;
}

return ResultFromScode(E_NOINTERFACE);
}

STDMETHODIMP_(ULONG) CDispatchEvents::AddRef(void)
{
return ++m_cRef;
}

STDMETHODIMP_(ULONG) CDispatchEvents::Release(void)
{
if (0!=--m_cRef)
return m_cRef;

delete this;
return 0;
}





/*
* CDispatchEvents::GetTypeInfoCount
* CDispatchEvents::GetTypeInfo
* CDispatchEvents::GetIDsOfNames
*
* Purpose:
* These type-information functions are not implemented. The
* only caller of this interface is a control which is the source
* of the type information itself. A control will not have a
* need to call these functions.
*
* Return Value:
* HRESULT E_NOTIMPL in all cases.
*/

STDMETHODIMP CDispatchEvents::GetTypeInfoCount(UINT *pctInfo)
{
*pctInfo=NULL;
return ResultFromScode(E_NOTIMPL);
}

STDMETHODIMP CDispatchEvents::GetTypeInfo(UINT itinfo
, LCID lcid, ITypeInfo **pptInfo)
{
*pptInfo=NULL;
return ResultFromScode(E_NOTIMPL);
}

STDMETHODIMP CDispatchEvents::GetIDsOfNames(REFIID riid
, OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID)
{
*rgszNames=NULL;
*rgDispID=NULL;
return ResultFromScode(E_NOTIMPL);
}




/*
* CDispatchEvents::Invoke
*
* Purpose:
* Notifies the container of the event in the control. In this
* container we look in the event mapping for this particular
* site and execute the appropriate action recorded in that
* mapping. If there is no event handler set up, then nothing
* happens.
*
* Parameters:
* dispIDMember DISPID of the method or property of interest.
* riid REFIID reserved, must be NULL.
* lcid LCID of the locale.
* wFlags USHORT describing the context of the invocation.
* pDispParams DISPPARAMS * to the array of arguments.
* pVarResult VARIANT * in which to store the result. Is
* NULL if the caller is not interested.
* pExcepInfo EXCEPINFO * to exception information.
* puArgErr UINT * in which to store the index of an
* invalid parameter if DISP_E_TYPEMISMATCH
* is returned.
*
* Return Value:
* HRESULT NOERROR or a general error code.
*/


STDMETHODIMP CDispatchEvents::Invoke(DISPID dispIDMember, REFIID riid
, LCID lcid, unsigned short wFlags, DISPPARAMS * pDispParams
, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
HRESULT hr;
VARIANT varResult;
EVENTACTION iAction;
UINT i;
PEVENTMAP pEM;

ODSlu("Events IDispatch called with ID=%lu", dispIDMember);

if (IID_NULL!=riid)
return ResultFromScode(E_INVALIDARG);

/*
* We ignore lcid in this function. A multilingual application
* might use it to determine the meaning of certain parameters
* or perhaps as an indication of how to format data like
* time, date, and currency or any other language or locale-
* sensitive data.
*/

/*
* Variable handling: we don't actually do anything with any
* of the variables from the control's events, so we don't have
* any VARIANTARG variables to initialize.
*/

/*
* We don't handle the return value of any events if
* events have them. We should, however, initialize an
* empty return value just so it's not garbage.
*/
if(NULL==pVarResult)
pVarResult=&varResult;

VariantInit(pVarResult);
V_VT(pVarResult)=VT_EMPTY;


//Only method calls are valid.
if (!(DISPATCH_METHOD & wFlags))
return ResultFromScode(DISP_E_MEMBERNOTFOUND);

/*
* Process the event by looking for dispIDMember in the
* list maintained in the tenant that maps event IDs to
* actions. If we find the ID, then we execute the action,
* otherwise we do nothing.
*
* Control containers that allow more sophisticated programming
* for events would do something on the same order but process
* parameters and call user-implemented functions instead of
* something simple like MessageBeep.
*/

iAction=ACTION_NONE;
pEM=m_pTen->m_pEventMap->m_pEventMap;

for (i=0; i < m_pTen->m_pEventMap->m_cEvents; i++)
{
if (dispIDMember==pEM[i].id)
{
iAction=pEM[i].iAction;
break;
}
}

if (ACTION_NONE==iAction)
hr=ResultFromScode(DISP_E_MEMBERNOTFOUND);
else
{
MessageBeep((UINT)iAction);
hr=NOERROR;
}

return hr;
}