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; 
    }