/*
 *  a d d r o b j . h
 *
 *
 *   Purpose:
 *      implements an Address object. An ole object representation for
 *      a resolved email address. Wraps a CAddress object
 *      also implements an IDataObj for drag-drop
 *
 *  Copyright (C) Microsoft Corp. 1993, 1994.
 *
 *  Owner: brettm
 *
 */

#include <pch.hxx>
#include <resource.h>
#include <addrobj.h>
#include <ourguid.h>    //addr object guid
#include <dragdrop.h>
#include <menuutil.h>
#include <wells.h>
#include <ipab.h>
#include <fonts.h>
#include <oleutil.h>
#include "menures.h"
#include "header.h"
#include "shlwapip.h"
#include "demand.h"

ASSERTDATA

/*
 * c o n s t a n t s
 */
enum
{
    iverbProperties=0,      //OLEIVERB_PRIMARY
    iverbAddToWAB=1,
    iverbFind=2,
    iverbBlockSender=3,
    iverbMax
};

#define CF_ADDROBJ              "Outlook Express Recipients"

/*
 * t y p e d e f s
 */

/*
 * m a c r o s
 */


/*
 * g l o b a l   d a t a 
 */

#pragma BEGIN_CODESPACE_DATA
static char szClipFormatAddrObj[] = CF_ADDROBJ;
#pragma END_CODESPACE_DATA

static FORMATETC rgformatetcADDROBJ[] =
{
    { 0, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL },
    { 0, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL },
    { 0, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }
};

enum 
{
    iFormatAddrObj=0, 
    iFormatText,
    iFormatUnicode,
    cformatetcADDROBJ
};

static BOOL     g_fInited=FALSE;


/*
 * p r o t o t y p e s
 */

HRESULT HrBuildSelectionWabal(HWND hwndRE, CHARRANGE *pchrg, LPWABAL *lplpWabal);
HRESULT HrBuildOneSelWabal(LPADRINFO lpAdrInfo, LPWABAL *lplpWabal);


/*
 * f u n c t i o n s
 */
BOOL FInitAddrObj(BOOL fInit)
{
    if(fInit&&g_fInited)
        return TRUE;

    if(!fInit)
    {
        //de-init stuff
        return TRUE;
    }

    // Register our clipboard formats
    rgformatetcADDROBJ[iFormatAddrObj].cfFormat =
                    (CLIPFORMAT) RegisterClipboardFormat(szClipFormatAddrObj);
    rgformatetcADDROBJ[iFormatText].cfFormat = CF_TEXT;
    rgformatetcADDROBJ[iFormatUnicode].cfFormat = CF_UNICODETEXT;
    
    return g_fInited=TRUE;
}




HRESULT CAddrObj::QueryInterface(REFIID riid, void **ppvObject)
{
    *ppvObject = NULL;   // set to NULL, in case we fail.

    if (IsEqualIID(riid, IID_IUnknown))
        *ppvObject = (void*)this;
    else if (IsEqualIID(riid, IID_IOleObject))
        *ppvObject = (void*)(IOleObject*)this;
    else if (IsEqualIID(riid, IID_IViewObject))
        *ppvObject = (void*)(IViewObject*)this;
    else if (IsEqualIID(riid, IID_IPersist))
        *ppvObject = (void*)(IPersist*)this;
    else if (IsEqualIID(riid, IID_IOleCommandTarget))
        *ppvObject = (void *)(IOleCommandTarget *)this;
    else
        return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

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

ULONG CAddrObj::Release(void)
{
    if (--m_cRef==0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

// IOleObject methods:
HRESULT CAddrObj::SetClientSite(LPOLECLIENTSITE pClientSite)
{
    CAddrWellCB             *pawcb=0;
    IRichEditOleCallback    *prole=0;
    IOleInPlaceSite         *pIPS;
    
    m_hwndEdit = NULL;    
    ReleaseObj(m_lpoleclientsite);
    m_lpoleclientsite=0;

    if (!pClientSite)
        return NOERROR;

    // we do this so when we d-d between notes, read/send etc. the triple inherits the
    // properies of it's callbacks pab and underlining.
    if(!pClientSite->QueryInterface(IID_IRichEditOleCallback, (LPVOID *)&prole))
    {
        pawcb=(CAddrWellCB *)prole;
        m_fUnderline=pawcb->m_fUnderline;
        ReleaseObj(prole);
    }
    
    if(!pClientSite->QueryInterface(IID_IOleInPlaceSite, (LPVOID *)&pIPS))
    {
        pIPS->GetWindow(&m_hwndEdit);
        pIPS->Release();
    }

    pClientSite->AddRef();
    m_lpoleclientsite = pClientSite;
    return NOERROR;

}

HRESULT CAddrObj::GetClientSite(LPOLECLIENTSITE * ppClientSite)
{
    *ppClientSite = m_lpoleclientsite;
    if(m_lpoleclientsite)
        m_lpoleclientsite->AddRef();
    return NOERROR;
}

HRESULT CAddrObj::SetHostNames(LPCOLESTR szContainerApp, LPCOLESTR szContainerObj)
{
    return NOERROR;
}

HRESULT CAddrObj::Close(DWORD dwSaveOption)
{
    return NOERROR;
}

HRESULT CAddrObj::SetMoniker(DWORD dwWhichMoniker, LPMONIKER pmk)
{
    return E_NOTIMPL;
}

HRESULT CAddrObj::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, LPMONIKER * ppmk)
{
    return E_NOTIMPL;
}

HRESULT CAddrObj::InitFromData(LPDATAOBJECT pDataObject, BOOL fCreation, DWORD dwReserved)
{
    return E_NOTIMPL;
}

HRESULT CAddrObj::GetClipboardData(DWORD dwReserved, LPDATAOBJECT * ppDataObject)
{
    HRESULT         hr;
    CAddrObjData    *pAddrObjData=0;
    LPWABAL         lpWabal=0;

    Assert(m_lpAdrInfo);

    hr=HrBuildOneSelWabal(m_lpAdrInfo, &lpWabal);
    if(FAILED(hr))
        goto cleanup;

    if(!(pAddrObjData = new CAddrObjData(lpWabal)))
    {
        hr=E_OUTOFMEMORY;
        goto cleanup;
    }

    hr=pAddrObjData->QueryInterface(IID_IDataObject, (LPVOID *)ppDataObject);

cleanup:
    ReleaseObj(lpWabal);
    ReleaseObj(pAddrObjData);
    return hr;
}


HRESULT CAddrObj::DoVerb(LONG iVerb, LPMSG lpmsg, LPOLECLIENTSITE pActiveSite, LONG lindex, HWND hwndParent, LPCRECT lprcPosRect)
{
    HRESULT         hr=0;
    HWND            hwndUI=GetParent(hwndParent);
    LPWAB           lpWab=0;
    int             idsErr=0;

    Assert(m_lpAdrInfo);

    hr=HrCreateWabObject(&lpWab);
    if(FAILED(hr))
        goto error;

    switch(iVerb)
    {
        case iverbBlockSender:
            // hack. send the wm_command to the note to avoid dupe-code, also the note knows
            // if it's news or mail so the correct block sender verb can be applied
            SendMessage(GetTopMostParent(hwndUI), WM_COMMAND, MAKELONG(ID_BLOCK_SENDER, 0), 0);
            break;

        case iverbProperties:
            hr=lpWab->HrDetails(hwndUI, &m_lpAdrInfo);
            if(FAILED(hr) && hr!=MAPI_E_USER_CANCEL)
                idsErr=idsErrAddrProps;
 
            m_padvisesink->OnViewChange(DVASPECT_CONTENT, -1);
            break;

        case iverbAddToWAB:
            if (m_lpAdrInfo->fDistList)
            {
                // $bug: 12298. don't try and add dist-lists
                idsErr=idsErrAddrDupe;
                hr = MAPI_E_COLLISION;
            }
            else
            {
                hr=lpWab->HrAddToWAB(hwndUI, m_lpAdrInfo);
                if(FAILED(hr) && hr!=MAPI_E_USER_CANCEL)
                {
                    if(hr==MAPI_E_COLLISION)
                        idsErr=idsErrAddrDupe;
                    else
                        idsErr=idsErrAddToWAB;
                }
            }
            break;

        case iverbFind:
            hr = lpWab->HrFind(hwndUI, m_lpAdrInfo->lpwszAddress);
            if(FAILED(hr))
                idsErr = idsErrFindWAB;
            break;


        default:
            hr=OLEOBJ_S_INVALIDVERB;
            break;
    }

    if(idsErr)
        AthMessageBoxW(hwndUI, MAKEINTRESOURCEW(idsAthenaMail), 
            MAKEINTRESOURCEW(idsErr), NULL, MB_OK);

error:
    ReleaseObj(lpWab);
    return hr;

}

HRESULT CAddrObj::EnumVerbs(LPENUMOLEVERB * ppEnumOleVerb)
{
    return E_NOTIMPL;
}

HRESULT CAddrObj::Update()
{
    return E_NOTIMPL;
}

HRESULT CAddrObj::IsUpToDate()
{
    return E_NOTIMPL;
}

HRESULT CAddrObj::GetUserClassID(CLSID * pClsid)
{
    *pClsid = CLSID_AddrObject;
    return NOERROR;
}


HRESULT CAddrObj::GetUserType(DWORD dwFormOfType, LPOLESTR *pszUserType)
{
    TCHAR szT[CCHMAX_STRINGRES];
    WCHAR szWT[CCHMAX_STRINGRES];
    
    AthLoadString((dwFormOfType == USERCLASSTYPE_APPNAME?idsAthena:idsRecipient),szT, 40);
    if (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szT, -1, szWT, 40))
        lstrcpyW(*pszUserType, szWT);
    else
        lstrcpyW(*pszUserType, NULL);

    *pszUserType = PszDupW(szWT);
    return(NOERROR);
}

HRESULT CAddrObj::SetExtent(DWORD dwDrawAspect, LPSIZEL psizel)
{
    // The object's size is fixed
    return E_FAIL;
}

HFONT CAddrObj::_GetFont()
{
    HFONT           hFont=NULL;
    
    // try and get the message-font from the header, if this fails, fall back to the normal font
    if (m_hwndEdit)
        hFont = (HFONT)SendMessage(GetParent(m_hwndEdit), WM_HEADER_GETFONT, m_lpAdrInfo->fDistList, 0);
    
    if (!hFont)
        hFont = HGetSystemFont(m_lpAdrInfo->fDistList?FNT_SYS_ICON_BOLD:FNT_SYS_ICON);
    
    return hFont;
}

HRESULT CAddrObj::OnFontChange()
{
    IAdviseSink     *pAS;

    if (m_lpoleclientsite &&
        m_lpoleclientsite->QueryInterface(IID_IAdviseSink, (LPVOID *)&pAS)==S_OK)
    {
        pAS->OnViewChange(DVASPECT_CONTENT, -1);
        pAS->Release();
    }
    return S_OK;
}

 

HRESULT CAddrObj::GetExtent(DWORD dwDrawAspect, LPSIZEL psizel)
{
    HFONT           hfontOld=NULL;
    HDC             hdc;
    TEXTMETRIC      tm;
    LPWSTR          pwszName;
    SIZE            size;
    SIZEL           sizel;
    DWORD           cch, i;
    int             cx = 0, chcx;

    if (m_hwndEdit)
        hdc = GetWindowDC(m_hwndEdit);
    else
        hdc = GetWindowDC(NULL);

    if(!hdc)
        return E_OUTOFMEMORY;
            
    Assert(m_lpAdrInfo);

    hfontOld = SelectFont(hdc, _GetFont());

    pwszName = m_lpAdrInfo->lpwszDisplay;
    Assert(pwszName);

    GetTextExtentPoint32AthW(hdc, pwszName, lstrlenW(pwszName), &size, DT_NOPREFIX);    
    GetTextMetrics(hdc, &tm);

    sizel.cx = size.cx;
#ifndef DBCS
    sizel.cy = size.cy - tm.tmDescent;      // Same height as normal line (RAID11516 was +1)
#else
    sizel.cy = tm.tmAscent + 2;
#endif
    XformSizeInPixelsToHimetric(hdc, &sizel, psizel);

    if (hfontOld)
        SelectFont(hdc, hfontOld);

    if (m_hwndEdit)
        ReleaseDC(m_hwndEdit, hdc);
    else
        ReleaseDC(NULL, hdc);

    return NOERROR;
}

HRESULT CAddrObj::Advise (LPADVISESINK pAdvSink, DWORD * pdwConnection)
{
    if (m_poleadviseholder)
        return m_poleadviseholder->Advise(pAdvSink, pdwConnection);
    else
        return OLE_E_ADVISENOTSUPPORTED;
}

HRESULT CAddrObj::Unadvise(DWORD dwConnection)
{
    if (m_poleadviseholder)
        return m_poleadviseholder->Unadvise(dwConnection);
    else
        return E_FAIL;
}

HRESULT CAddrObj::EnumAdvise(LPENUMSTATDATA * ppenumAdvise)
{
    if(m_poleadviseholder)
        return m_poleadviseholder->EnumAdvise(ppenumAdvise);
    else
        return OLE_E_ADVISENOTSUPPORTED;
}

HRESULT CAddrObj::GetMiscStatus(DWORD dwAspect, DWORD * pdwStatus)
{
    *pdwStatus = 0;
    return NOERROR;
}

HRESULT CAddrObj::SetColorScheme(LPLOGPALETTE lpLogpal)
{
    return E_NOTIMPL;
}


HRESULT CAddrObj::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT *pCmdText)
{
    if (pguidCmdGroup == NULL)
        return OLECMDERR_E_UNKNOWNGROUP;

    if (IsEqualGUID(CMDSETID_OutlookExpress, *pguidCmdGroup))
    {
        for (ULONG ul = 0; ul < cCmds; ul++)
        {
            rgCmds[ul].cmdf = 0;
            switch (rgCmds[ul].cmdID)
            {
                case ID_ADDROBJ_OLE_BLOCK_SENDER:
                    rgCmds[ul].cmdf = (m_lpAdrInfo->lRecipType == MAPI_ORIG) ? OLECMDF_ENABLED|OLECMDF_SUPPORTED : 0;
                    break;                    

                case ID_ADDROBJ_OLE_PROPERTIES:
                    rgCmds[ul].cmdf = OLECMDF_ENABLED|OLECMDF_SUPPORTED;
                    break;

                case ID_ADDROBJ_OLE_ADD_ADDRESS_BOOK:
                case ID_ADDROBJ_OLE_FIND:
                    rgCmds[ul].cmdf = OLECMDF_SUPPORTED;
                    if (m_lpAdrInfo->lpwszAddress)
                        rgCmds[ul].cmdf |= OLECMDF_ENABLED;
                    break;
            }
        }                
        return S_OK;
    }
    else
        return OLECMDERR_E_UNKNOWNGROUP;
}

HRESULT CAddrObj::Exec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdExecOpt, VARIANTARG *pvaIn,   VARIANTARG *pvaOut)
{
    // we should use DoVerb for this
    return E_NOTIMPL;
}



// IViewObject methods:
HRESULT CAddrObj::Draw(DWORD dwDrawAspect, LONG lindex, void * pvAspect, 
                                DVTARGETDEVICE * ptd, HDC hicTargetDev, HDC hdcDraw, 
                                LPCRECTL lprcBounds, LPCRECTL lprcWBounds,
                                BOOL (CALLBACK * pfnContinue)(ULONG_PTR), ULONG_PTR dwContinue)
{
    HFONT           hfontOld=NULL;
    RECT            rect;
    LPWSTR          pwszName;
    TEXTMETRIC      tm;
    HPEN            hPen,
                    hPenOld;
    COLORREF        hColor;
    Assert(m_lpAdrInfo);

    // Need to convert from RECTL to RECT
    rect.left = (INT) lprcBounds->left;
    rect.top = (INT) lprcBounds->top;
    rect.right = (INT) lprcBounds->right;
    rect.bottom = (INT) lprcBounds->bottom;

    hColor = GetSysColor(COLOR_WINDOWTEXT);
    if (m_fUnderline && m_lpAdrInfo->lpwszAddress == NULL && !m_lpAdrInfo->fDistList)
    {
        // if a compose-note, and there is no address then we show recipients with no email in red
        hColor = RGB(0xFF, 0, 0);
        if (GetSysColor(COLOR_WINDOW) == hColor)    // if background is RED, use WHITE
            hColor = RGB(0xFF, 0xFF, 0xFF);
    }

    hfontOld = SelectFont(hdcDraw, _GetFont());

    pwszName = m_lpAdrInfo->lpwszDisplay;
    SetTextAlign(hdcDraw, TA_BOTTOM);
    
    SetTextColor(hdcDraw, hColor);
    ExtTextOutWrapW(hdcDraw, rect.left, rect.bottom, 0, &rect, pwszName, lstrlenW(pwszName), NULL);

    if (hfontOld)
        SelectObject(hdcDraw, hfontOld);

    GetTextMetrics(hdcDraw, &tm);
    if (m_fUnderline)
    {
        // we want this underlined...
        hPen=CreatePen(PS_SOLID, 0, hColor);
        if(!hPen)
            return E_OUTOFMEMORY;
        
        hPenOld=(HPEN)SelectPen(hdcDraw, hPen);
        MoveToEx(hdcDraw, rect.left, rect.bottom - tm.tmDescent + 1, NULL);
        LineTo(hdcDraw, rect.right, rect.bottom - tm.tmDescent + 1);
        SelectPen(hdcDraw, hPenOld);
        DeleteObject(hPen);
    }
    return NOERROR;
}

HRESULT CAddrObj::GetColorSet(DWORD dwDrawAspect, 
                             LONG lindex, 
                             void *pvAspect, 
                             DVTARGETDEVICE *ptd, 
                             HDC hicTargetDev,
                             LPLOGPALETTE *ppColorSet)
{
    return E_NOTIMPL;
}

HRESULT CAddrObj::Freeze(DWORD dwDrawAspect, LONG lindex, void * pvAspect, DWORD * pdwFreeze)
{
    return E_NOTIMPL;
}

HRESULT CAddrObj::Unfreeze(DWORD dwFreeze)
{
    return E_NOTIMPL;
}

HRESULT CAddrObj::SetAdvise(DWORD aspects, DWORD advf, IAdviseSink * pAdvSnk)
{
    if(m_padvisesink)
        m_padvisesink->Release();

    if(pAdvSnk)
        pAdvSnk->AddRef();
    
    m_padvisesink = pAdvSnk;

    return NOERROR;
}

HRESULT CAddrObj::GetAdvise(DWORD * pAspects, DWORD * pAdvf, IAdviseSink ** ppAdvSnk)
{
    return E_NOTIMPL;
}



// IPersit methods:
HRESULT CAddrObj::GetClassID(CLSID *pClsID)
{
    *pClsID = CLSID_AddrObject;
    return NOERROR;
}



CAddrObj::CAddrObj()
{
    
    m_cRef=1;
    m_fUnderline=TRUE;
    m_lpoleclientsite = 0;
    m_pstg = 0;
    m_padvisesink = 0;
    CreateOleAdviseHolder(&m_poleadviseholder);
    // copy props
    m_lpAdrInfo = NULL;
}

HRESULT CAddrObj::HrSetAdrInfo(LPADRINFO lpAdrInfo)
{
    if(m_lpAdrInfo)
    {
        MemFree(m_lpAdrInfo);
        m_lpAdrInfo=0;
    }
    
    // has to have a valid address, or else be a distlist...
    Assert(lpAdrInfo->lpwszDisplay);
    Assert(lpAdrInfo->lpbEID);
    return HrDupeAddrInfo(lpAdrInfo, &m_lpAdrInfo);
}

HRESULT CAddrObj::HrGetAdrInfo(LPADRINFO *lplpAdrInfo)
{
    Assert(lplpAdrInfo);
    *lplpAdrInfo=m_lpAdrInfo;
    return NOERROR;
};


CAddrObj::~CAddrObj()
{
    ReleaseObj(m_lpoleclientsite);
    ReleaseObj(m_pstg);
    ReleaseObj(m_poleadviseholder);
    ReleaseObj(m_padvisesink);

    // this is our own copy, we must free it!
    if(m_lpAdrInfo)
        MemFree(m_lpAdrInfo);
}




/* 
 * I D a t a  O b j e c t  m e t h o d s:
 *
 * 
 *
 */
HRESULT CAddrObjData::GetData(FORMATETC * pformatetcIn, STGMEDIUM * pmedium)
{
    return HrGetDataHereOrThere(pformatetcIn, pmedium);
}

HRESULT CAddrObjData::GetDataHere(FORMATETC * pformatetc, STGMEDIUM *pmedium)
{
    return HrGetDataHereOrThere(pformatetc, pmedium);
}

HRESULT CAddrObjData::QueryGetData(FORMATETC * pformatetc )
{
    LONG        iformatetc;
    LPFORMATETC pformatetcT = rgformatetcADDROBJ;
    CLIPFORMAT  cfFormat    = pformatetc->cfFormat;
    DWORD       tymed       = pformatetc->tymed;

    for (iformatetc = 0; iformatetc < cformatetcADDROBJ;
                                ++iformatetc, ++pformatetcT)
    {
        // Stop searching if we have compatible formats and mediums
        if (pformatetcT->cfFormat == cfFormat &&
                    (pformatetcT->tymed & tymed))
            return NOERROR;
    }

    return ResultFromScode(S_FALSE);
}

HRESULT CAddrObjData::GetCanonicalFormatEtc(FORMATETC * pformatetcIn, FORMATETC * pFormatetcOut)
{
    return DATA_S_SAMEFORMATETC;
}

HRESULT CAddrObjData::SetData(FORMATETC * pformatetc, STGMEDIUM * pmedium, BOOL fRelease)
{
    return E_NOTIMPL;
}

HRESULT CAddrObjData::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC ** ppenumFormatEtc )
{
    return CreateEnumFormatEtc(this, cformatetcADDROBJ, NULL, rgformatetcADDROBJ, ppenumFormatEtc);
}

HRESULT CAddrObjData::DAdvise(FORMATETC * pformatetc, DWORD advf, IAdviseSink *pAdvSnk, DWORD * pdwConnection)
{
    return E_NOTIMPL;
}
HRESULT CAddrObjData::DUnadvise(DWORD dwConnection)
{
return E_NOTIMPL;
}

HRESULT CAddrObjData::EnumDAdvise(IEnumSTATDATA ** ppenumAdvise )
{
    return E_NOTIMPL;
}



HRESULT CAddrObjData::QueryInterface(REFIID riid, void **ppvObject)
{
    *ppvObject = NULL;   // set to NULL, in case we fail.

    if (IsEqualIID(riid, IID_IUnknown))
        *ppvObject = (void*)this;
    else if (IsEqualIID(riid, IID_IDataObject))
        *ppvObject = (void*)(IDataObject*)this;
    else
        return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

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

ULONG CAddrObjData::Release(void)
{
    if (--m_cRef==0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}



HRESULT CAddrObjData::HrGetDataHereOrThere(LPFORMATETC pformatetcIn, LPSTGMEDIUM pmedium)
{
    HRESULT         hr=NOERROR;
    BOOL            fFound;
    ADRINFO         adrInfo;
    ULONG           cb=0;
    LPBYTE          pDst, pBeg;
    int             cch = 0;
    
    Assert(m_lpWabal);
    
    if (pformatetcIn->cfFormat == rgformatetcADDROBJ[iFormatAddrObj].cfFormat)
    {
        pmedium->tymed = TYMED_HGLOBAL;
        pmedium->pUnkForRelease = NULL;
        return m_lpWabal->HrBuildHGlobal(&pmedium->hGlobal);
    }
    else if ((pformatetcIn->cfFormat != rgformatetcADDROBJ[iFormatText].cfFormat) &&
             (pformatetcIn->cfFormat != rgformatetcADDROBJ[iFormatUnicode].cfFormat))
        return DATA_E_FORMATETC;
        
    fFound=m_lpWabal->FGetFirst(&adrInfo);
    while(fFound)
    {
        Assert(adrInfo.lpwszDisplay);
        cb+=(lstrlenW(adrInfo.lpwszDisplay)+2)*sizeof(WCHAR);        // +2 for '; '
        if (adrInfo.lpwszAddress)
            cb+=(lstrlenW(adrInfo.lpwszAddress)+3)*sizeof(WCHAR);        // +3 for ' <>'
        fFound=m_lpWabal->FGetNext(&adrInfo);
    }
    cb+=sizeof(WCHAR);      // null term
    
    if  (pmedium->tymed == TYMED_NULL ||
        (pmedium->tymed == TYMED_HGLOBAL && pmedium->hGlobal == NULL))
    {
        // This is easy, we can quit right after copying stuff
        pmedium->tymed = TYMED_HGLOBAL;
        pmedium->hGlobal = GlobalAlloc(GMEM_SHARE|GMEM_MOVEABLE, cb);
        pmedium->pUnkForRelease = NULL;
    }
    else if (pmedium->tymed == TYMED_HGLOBAL && pmedium->hGlobal != NULL)
    {
        HGLOBAL hGlobal;
        // Caller wants us to fill his hGlobal
        // Realloc the destination to make sure there is enough room
        if (!(hGlobal = GlobalReAlloc(pmedium->hGlobal, cb, 0)))
        {
            hr = E_OUTOFMEMORY;
            goto Cleanup;
        }
        pmedium->hGlobal = hGlobal;
    }
    else
        goto Cleanup;
    
    if (!pmedium->hGlobal)
    {
        hr = E_OUTOFMEMORY;
        goto Cleanup;
    }
    
    cch = (cb/sizeof(WCHAR));
    pBeg = pDst = (LPBYTE)GlobalLock(pmedium->hGlobal);
    fFound=m_lpWabal->FGetFirst(&adrInfo);
    while(fFound)
    {
        cb = lstrlenW(adrInfo.lpwszDisplay) * sizeof(WCHAR);
        if (adrInfo.lpwszAddress)
        {
            cb += (lstrlenW(adrInfo.lpwszAddress) + 3) * sizeof(WCHAR);   //+3 " <>"
        }
        
        if (adrInfo.lpwszAddress)
            AthwsprintfW((LPWSTR)pDst, cch, L"%s <%s>", adrInfo.lpwszDisplay, adrInfo.lpwszAddress);
        else
            StrCpyW((LPWSTR)pDst, adrInfo.lpwszDisplay);

        pDst+=cb;
        
        fFound=m_lpWabal->FGetNext(&adrInfo);
        if(fFound)
        {
            // if more, add a '; '
            *((LPWSTR) pDst) = L';';
            pDst += sizeof(WCHAR);
            *((LPWSTR) pDst) = L' ';
            pDst += sizeof(WCHAR);
            *((LPWSTR) pDst) = L'\0';
        }                
    }

    //From MSDN:
    //   CF_TEXT Specifies the standard American National Standards Institute (ANSI) text format.
    //The string needs to be ANSI...convert it.
    if(pformatetcIn->cfFormat == CF_TEXT)
    {
        WCHAR *pwszDup;
        int cCopied;

        IF_NULLEXIT(pwszDup = StrDupW((LPWSTR)pBeg));
        cCopied = WideCharToMultiByte(CP_ACP, 0, pwszDup, lstrlenW(pwszDup), (LPTSTR)pBeg, cb, NULL, NULL);
        pBeg[cCopied] = '\0';
        MemFree(pwszDup);
    }

    GlobalUnlock(pmedium->hGlobal);
  
exit:    
Cleanup:
    return hr;
}


CAddrObjData::CAddrObjData(LPWABAL lpWabal)
{ 
    m_cRef=1;
    Assert(lpWabal);
    
    m_lpWabal=lpWabal;
    if(lpWabal)
        lpWabal->AddRef();
};


CAddrObjData::~CAddrObjData()
{ 
    ReleaseObj(m_lpWabal);
};

CAddrWellCB::CAddrWellCB(BOOL fUnderline, BOOL fHasAddrObjs)
{
    m_cRef=1;
    m_hwndEdit = 0;
    m_fUnderline = fUnderline;
    m_fHasAddrObjs=fHasAddrObjs;
}

CAddrWellCB::~CAddrWellCB()
{
}


BOOL CAddrWellCB::FInit(HWND hwndEdit)
{
    // make sure addrobj's DD guid's are registered
    if(!FInitAddrObj(TRUE))
        return FALSE;

    if(!IsWindow(hwndEdit))
        return FALSE;
    
    m_hwndEdit = hwndEdit;
    return TRUE;
}


HRESULT CAddrWellCB::QueryInterface(REFIID riid, LPVOID FAR * lplpObj)
{
    *lplpObj = NULL;   // set to NULL, in case we fail.

    if (IsEqualIID(riid, IID_IUnknown))
        *lplpObj = (void*)(IUnknown*)this;
    else if (IsEqualIID(riid, IID_IRichEditOleCallback))
        *lplpObj = (void*)(IRichEditOleCallback*)this;
    else
        return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

ULONG CAddrWellCB::AddRef()
{
    return ++m_cRef;
}

ULONG CAddrWellCB::Release()
{
    if (--m_cRef==0)
    {
        delete this;
        return 0;
    }
    else
        return m_cRef;
}

HRESULT CAddrWellCB::GetNewStorage (LPSTORAGE FAR * ppstg)
{
    if (*ppstg)
        ppstg=NULL;
    
    AssertSz(FALSE, "this code should not get hit for OE");
    return E_NOTIMPL;
}

HRESULT CAddrWellCB::GetInPlaceContext(   LPOLEINPLACEFRAME FAR * lplpFrame,
                                            LPOLEINPLACEUIWINDOW FAR * lplpDoc,
                                            LPOLEINPLACEFRAMEINFO lpFrameInfo)
{
    return E_NOTIMPL;
}

HRESULT CAddrWellCB::ShowContainerUI(BOOL fShow)
{
    return E_NOTIMPL;
}

HRESULT CAddrWellCB::QueryInsertObject(LPCLSID lpclsid, 
                                       LPSTORAGE lpstg,
                                       LONG cp)
{
    if(!m_fHasAddrObjs)
        return E_NOTIMPL;

    if (IsEqualIID(*lpclsid, CLSID_AddrObject))
        return NOERROR;
    else
        return E_FAIL;
}

HRESULT CAddrWellCB::DeleteObject(LPOLEOBJECT lpoleobj)
{
    return NOERROR;
}


HRESULT CAddrWellCB::QueryAcceptData(LPDATAOBJECT   pdataobj,
                                     CLIPFORMAT FAR *pcfFormat, 
                                     DWORD          reco,
                                     BOOL           fReally, 
                                     HGLOBAL        hMetaPict)
{
    HRESULT         hr;
    STGMEDIUM       stgmedium;
    LPWABAL         lpWabal=0;
    ADRINFO         adrInfo;
    
    if(!m_fHasAddrObjs)
    {
        // of we're a regular callback, take TEXTONLY
        *pcfFormat=CF_TEXT;
        return NOERROR;
    }
    
    if (!*pcfFormat)
    {
        // default to text
        *pcfFormat = CF_TEXT;
        
        // Cool, it's one of ours...
        if(pdataobj->QueryGetData(&rgformatetcADDROBJ[iFormatAddrObj])==NOERROR)
            *pcfFormat = rgformatetcADDROBJ[iFormatAddrObj].cfFormat;
    }
    else
    {
        if (*pcfFormat != rgformatetcADDROBJ[iFormatAddrObj].cfFormat
            && *pcfFormat != rgformatetcADDROBJ[iFormatText].cfFormat)
            return DATA_E_FORMATETC;
    }
    
    if (*pcfFormat==CF_TEXT)  // let the richedit take care of text
        return NOERROR;
    
    // If I'm read-only, return Success and Richedit won't do anything
    if (GetWindowLong(m_hwndEdit, GWL_STYLE) & ES_READONLY)
        return NOERROR;
    
    if (!fReally)   // return that we'll import it ourselves
        return ResultFromScode(S_FALSE);
    
    hr=pdataobj->GetData(&rgformatetcADDROBJ[iFormatAddrObj], &stgmedium);
    if(FAILED(hr))
        goto Cleanup;
    
    hr=HrCreateWabalObjectFromHGlobal(stgmedium.hGlobal, &lpWabal);
    if(FAILED(hr))
        goto Cleanup;
    
    if(lpWabal->FGetFirst(&adrInfo))
    {
        // don't add semi colon before first entry!
        HrAddRecipientToWell(m_hwndEdit, (LPADRINFO)&adrInfo);
        while(lpWabal->FGetNext(&adrInfo))
        {
            HdrSetRichEditText(m_hwndEdit, L"; ", TRUE);
            HrAddRecipientToWell(m_hwndEdit, (LPADRINFO)&adrInfo);
        }
    }
    
    // free the hglobal
    GlobalFree(stgmedium.hGlobal);
    ReleaseObj(lpWabal);
    hr = ResultFromScode(S_FALSE);
    
Cleanup:
    return hr;
}


HRESULT CAddrWellCB::ContextSensitiveHelp(BOOL fEnterMode)
{
    return E_NOTIMPL;
}

HRESULT CAddrWellCB::GetClipboardData(CHARRANGE FAR * pchrg,
                                        DWORD reco,
                                        LPDATAOBJECT FAR * ppdataobj)
{
    HRESULT         hr;
    CAddrObjData    *lpAddrObjData=0;
    LPWABAL         lpWabal=0;
    ULONG           uSelType;

    if(!m_fHasAddrObjs)
        return E_NOTIMPL;

    Assert(ppdataobj);
    *ppdataobj=0;

    // Need to prevent cut on read only
    if (reco == RECO_CUT &&
        (GetWindowStyle(m_hwndEdit)&ES_READONLY))
        return E_NOTIMPL;


    // if there is only text in the selection, let the richedit take care of it!
    uSelType= (ULONG) SendMessage(m_hwndEdit, EM_SELECTIONTYPE, 0, 0);
    if(!(uSelType&SEL_OBJECT))
        return E_NOTIMPL;

    hr=HrBuildSelectionWabal(m_hwndEdit, pchrg, &lpWabal);
    if(FAILED(hr))
        return E_FAIL;
    
    // this will gobble up the pal so I don't want to free it
    if(!(lpAddrObjData= new CAddrObjData(lpWabal)))
    {
        hr=E_OUTOFMEMORY;
        goto error;
    }
    
    hr=lpAddrObjData->QueryInterface(IID_IDataObject, (LPVOID *)ppdataobj);
    ReleaseObj(lpAddrObjData);

error:
    ReleaseObj(lpWabal);
    return hr;
}

HRESULT CAddrWellCB::GetDragDropEffect(BOOL fDrag, 
                                       DWORD grfKeyState,
                                       LPDWORD pdwEffect)
{
    if(!m_fHasAddrObjs)
        return E_NOTIMPL;

    if (fDrag)          // use the default
        return NOERROR;

    if (GetWindowLong(m_hwndEdit, GWL_STYLE) & ES_READONLY)
        *pdwEffect = DROPEFFECT_NONE;
    else
    {
        if ((grfKeyState & MK_CONTROL) || !(*pdwEffect & DROPEFFECT_MOVE))
            *pdwEffect = DROPEFFECT_COPY;
        else 
            *pdwEffect = DROPEFFECT_MOVE;
    }
    return NOERROR;
}

HRESULT CAddrWellCB::GetContextMenu(WORD            seltype, 
                                    LPOLEOBJECT     pOleObject,
                                    CHARRANGE FAR   *pchrg,
                                    HMENU FAR       *phMenu)
{

    HMENU               hMenu=0;
    DWORD               dwFlags=0;
    IOleCommandTarget   *pCmdTarget;
    OLECMD              rgCmds[] = {
                                        {ID_ADDROBJ_OLE_FIND, 0},
                                        {ID_ADDROBJ_OLE_ADD_ADDRESS_BOOK, 0},
                                        {ID_ADDROBJ_OLE_PROPERTIES, 0},
                                        {ID_ADDROBJ_OLE_BLOCK_SENDER, 0}};


    if (!(hMenu=LoadPopupMenu(IDR_ADDRESS_POPUP)))
        return E_OUTOFMEMORY;

    if (!m_fHasAddrObjs || pOleObject==NULL)
    {
        // if this RICHEDIT control does not care about ADDROBJECT or if there is
        // no addr object selected, then just return a 
        // regular cut|copy|paste menu
        
        RemoveMenu(hMenu, ID_ADDROBJ_OLE_ADD_ADDRESS_BOOK, MF_BYCOMMAND);
        RemoveMenu(hMenu, ID_ADDROBJ_OLE_FIND, MF_BYCOMMAND);
        RemoveMenu(hMenu, ID_ADDROBJ_OLE_BLOCK_SENDER, MF_BYCOMMAND);
        
        RemoveMenu(hMenu, ID_SEPARATOR_1, MF_BYCOMMAND);
        
        RemoveMenu(hMenu, ID_SEPARATOR_3, MF_BYCOMMAND);
        RemoveMenu(hMenu, ID_ADDROBJ_OLE_PROPERTIES, MF_BYCOMMAND);
        
    }
    else
    {
        // if we get this far, then we have an ole object, make sure it's one we know
        // about if we're an addrobj well
#ifdef DEBUG        
        AssertValidAddrObject(pOleObject);
#endif
        
        // try and see if the object can handle the commands
        if (pOleObject->QueryInterface(IID_IOleCommandTarget, (LPVOID *)&pCmdTarget)==S_OK)
        {
            if (pCmdTarget->QueryStatus(&CMDSETID_OutlookExpress, ARRAYSIZE(rgCmds), rgCmds, NULL)==S_OK)
            {
                for(ULONG ul=0; ul<sizeof(rgCmds)/sizeof(OLECMD); ul++)
                {
                    EnableMenuItem(hMenu, rgCmds[ul].cmdID, (rgCmds[ul].cmdf&OLECMDF_ENABLED ? MF_ENABLED: MF_GRAYED)|MF_BYCOMMAND);
                    if (!(rgCmds[ul].cmdf&OLECMDF_SUPPORTED))
                        RemoveMenu(hMenu, rgCmds[ul].cmdID, MF_BYCOMMAND);
                }
            }
            pCmdTarget->Release();
        }
        
        // if an Object has focus, then we show the commands before the separator
        // if there is an object in selction, remove SelectAll
        RemoveMenu(hMenu, ID_SEPARATOR_2, MF_BYCOMMAND);
        RemoveMenu(hMenu, ID_SELECT_ALL, MF_BYCOMMAND);
        
        if (SendMessage(m_hwndEdit, EM_SELECTIONTYPE, 0, 0) != SEL_OBJECT)
        {
            // multiple objects selected, let's grey out addrobj commands
            EnableMenuItem(hMenu, ID_ADDROBJ_OLE_PROPERTIES,        MF_GRAYED|MF_BYCOMMAND);
            EnableMenuItem(hMenu, ID_ADDROBJ_OLE_ADD_ADDRESS_BOOK,  MF_GRAYED|MF_BYCOMMAND);
            EnableMenuItem(hMenu, ID_ADDROBJ_OLE_FIND,              MF_GRAYED|MF_BYCOMMAND);
            EnableMenuItem(hMenu, ID_ADDROBJ_OLE_BLOCK_SENDER,      MF_GRAYED|MF_BYCOMMAND);
        } 
    }
    
    // if we are a readonly edit, then remove cut and paste
    if (FReadOnlyEdit(m_hwndEdit))
    {
        // remove cut and past if readonly
        RemoveMenu(hMenu, ID_CUT, MF_BYCOMMAND);
        RemoveMenu(hMenu, ID_PASTE, MF_BYCOMMAND);
    }
    
    MenuUtil_SetPopupDefault(hMenu, ID_ADDROBJ_OLE_PROPERTIES);
    
    GetEditDisableFlags(m_hwndEdit, &dwFlags);
    EnableDisableEditMenu(hMenu, dwFlags);
    *phMenu=hMenu;
    return S_OK;
}



#ifdef DEBUG        
void AssertValidAddrObject(LPUNKNOWN pUnk)
{    
    BOOL        fValid=FALSE;
    LPOLEOBJECT pOleObject;
    CLSID       clsid;

    Assert(pUnk);
    if(!pUnk->QueryInterface(IID_IOleObject, (LPVOID *)&pOleObject))
    {
        if((pOleObject->GetUserClassID(&clsid)==NOERROR) &&
            IsEqualCLSID(clsid, CLSID_AddrObject))
            fValid=TRUE;
        ReleaseObj(pOleObject);
    }

    AssertSz(fValid, "WHOA! This is not an AddrObject!");
}
#endif


HRESULT HrBuildSelectionAddrInfoList(HWND hwndRE, CHARRANGE *pchrg, LPADRINFOLIST *lplpAdrInfoList)
{

    return NOERROR;
}

HRESULT HrBuildOneSelWabal(LPADRINFO lpAdrInfo, LPWABAL *lplpWabal)
{
    LPWABAL lpWabal=0;
    HRESULT hr;

    if(!lplpWabal)
        return E_INVALIDARG;

    hr=HrCreateWabalObject(&lpWabal);
    if(FAILED(hr)) 
        goto error;

    hr=lpWabal->HrAddEntry(lpAdrInfo);
    if(FAILED(hr)) 
        goto error;

    *lplpWabal=lpWabal;
    lpWabal->AddRef();
    
error:
    ReleaseObj(lpWabal);        
    return hr;
}



#define iswhite(_ch)   (_ch==' ' || _ch=='\t' || _ch=='\n' || _ch=='\r')


/*
 *    ScBuildSelectionAdrlist
 *    
 *    Purpose:
 *        This function will add all the resolved and unresolved
 *        names from the selection in an edit control to an ADRLIST
 *    
 *    Parameters:
 *        ppal            pointer to pointer to ADRLIST
 *        hwndEdit        hwnd of the edit control
 *        pchrg            CHARRANGE of the selection
 *    
 *    Returns:
 *        sc
 */
HRESULT HrBuildSelectionWabal(HWND hwndRE, CHARRANGE *pchrg, LPWABAL *lplpWabal)
{
    ULONG               iOb,
                        cOb,
                        cb,
                        cchBuf = 0,
                        cchSel;
    LPRICHEDITOLE       preole;
    REOBJECT            reobj = {0};
    LPWABAL             lpWabal = NULL;
    HRESULT             hr;
    WCHAR               rgch[cchUnresolvedMax];
    LPWSTR              pbStart = NULL,
                        pbSel;
    BOOL                fTruncated = FALSE;
    PHCI                phci;
    
    Assert(pchrg);
    cchSel = pchrg->cpMax-pchrg->cpMin;

    // Add all the resolved names (stored as OLE objects) from
    // hwndEdit to the ADRLIST
    reobj.cbStruct = sizeof(REOBJECT);
    
    if(!MemAlloc((LPVOID *)&pbStart, (cchSel+1)*sizeof(WCHAR)))
    {
        hr = E_OUTOFMEMORY;
        goto Cleanup;
    }
    
    pbSel = pbStart;
    
    hr=HrCreateWabalObject(&lpWabal);
    if(FAILED(hr))
        goto Cleanup;
    
    // if we're building a Wabal, there MUST be some object in the selection!
    Assert((SendMessage(hwndRE, EM_SELECTIONTYPE, 0, 0)&SEL_OBJECT));
    
    phci = (HCI*)GetWindowLongPtr(hwndRE, GWLP_USERDATA);
    AssertSz(phci, "How did we get a richedit without a phci???");
    
    preole = phci->preole;
    AssertSz(preole, "How did we get a phci without a preole???");
    
    // count up the number of objects in the selction, and the number of
    // bytes in them...
    cOb = preole->GetObjectCount();
    for (iOb = 0; iOb < cOb; iOb++)
    {
        hr=preole->GetObject(iOb, &reobj, REO_GETOBJ_POLEOBJ);
        if(FAILED(hr))
            goto Cleanup;
        
        if (reobj.cp >= pchrg->cpMax) // out of the selrange...
            break;
        
        if (reobj.cp >= pchrg->cpMin)
        {
            LPPERSIST   ppersist = NULL;
            LPADRINFO   lpAdrInfo = 0;
            
            if (FAILED(hr = reobj.poleobj->QueryInterface(IID_IPersist, (LPVOID *)&ppersist)))
                goto Cleanup;
            
            hr = ((CAddrObj *)ppersist)->HrGetAdrInfo(&lpAdrInfo);
            lpWabal->HrAddEntry(lpAdrInfo);

            ReleaseObj(ppersist);
            if(FAILED(hr))
                goto Cleanup;
        }
        ReleaseObj(reobj.poleobj);
        reobj.poleobj = NULL;
    }
    
    // walk the unresolved text, and parse it up into unresolved names...
    cb = HdrGetRichEditText(hwndRE, pbSel, cchSel, TRUE) + 1;
    //$ BUG - broken for Unicode
    
    // The algorithm below will strip spaces off of the
    // beginning and end of each name
    while (cb--)
    {
        if (*pbSel == L'\t')
            *pbSel = L' ';
        
        if ((*pbSel == L'\0') || (*pbSel == L';') || (*pbSel == L'\r'))
        {
            if(cchBuf)
            {
                LPWSTR psz = rgch + cchBuf - 1;
                while(cchBuf > 0)
                {
                    // Exchange #10168.
                    if((*psz == L' ') || (*psz == L'\t'))
                    {
                        cchBuf--;
                        psz--;
                    }
                    else
                        break;
                }    
            }
            
            if (cchBuf)
            {
                rgch[cchBuf] = L'\0';
                lpWabal->HrAddUnresolved(rgch, (ULONG)-1);
                cchBuf = 0;
            }
        }
        else
        {
            if (((*pbSel != L' ') && (*pbSel != L'\n') && (*pbSel != L'\r')) || cchBuf > 0)
            {
                if (cchBuf < cchUnresolvedMax - 1)
                    rgch[cchBuf++] = *pbSel;
                else
                    fTruncated = TRUE;
            }
        }
        ++pbSel;
    }
    
    
    *lplpWabal=lpWabal;
    lpWabal->AddRef();
    
Cleanup:
    MemFree(pbStart);

    ReleaseObj(lpWabal);
    ReleaseObj(reobj.poleobj);
    return hr;
}