// idataobj.cpp
//
// Implementation file for the IDataObject interface viewer.
//

// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.
//
// Revisions:
//  August 7, 1993  cek     First implementation.
//  February 19, 1994 cek   Removed XRT stuff.  Fixed minor bugs.
//  April 9, 1994 cek       Pulled from XRTView and genericized again.
//
#include "stdafx.h"
#include "iview.h" 
#include "util.h"
#include "idataobj.h"  
#include "iadvsink.h"

static HFONT      g_hFont ;
static HFONT      g_hFontBold ;
static UINT        g_cyFont ;

static WNDPROC     g_pfnEdit = NULL ;

#define I_NORMAL        0x0000
#define I_LABEL         0x0001
#define I_COLUMNHEAD    0x0002
#define I_COLUMNENTRY   0x0003

typedef struct FAR tagITEMDATA
{                           
    UINT            nLevel ;            
    UINT            uiType ;
    int             cColumns ;
    LPCOLUMNSTRUCT  rgCol ;
    LPVOID          lpData ;
    
} ITEMDATA, *PITEMDATA, FAR*LPITEMDATA ;    

extern "C"
LRESULT EXPORT CALLBACK fnEditSubclass( HWND hwnd, UINT uiMsg, WPARAM wP, LPARAM lP ) ;

LPCTSTR MyGetClipboardFormatName( UINT cf ) ;

/////////////////////////////////////////////////////////////////////////////
// CIDataObjectDlg dialog
//
CIDataObjectDlg::CIDataObjectDlg( HWND hwnd, LPDATAOBJECT lpDO, REFIID /*riid*/, LPTSTR lpszName )
{
    m_lbFmtEtc = NULL ;
    m_edtAdvise = NULL ;
    m_MetaFile.hMF = NULL ;
    
    m_hWndParent = hwnd ;
    m_lpDO = lpDO ;
    m_lpszName = lpszName ;

    m_pSink = NULL ;
    m_dwConn = 0 ;
    m_advf = ADVF_PRIMEFIRST ;

    m_fetc.cfFormat = CF_TEXT ;
    m_fetc.dwAspect = DVASPECT_CONTENT ;
    m_fetc.ptd = NULL ;
    m_fetc.tymed = TYMED_HGLOBAL ;
    m_fetc.lindex = - 1 ;  
    
    m_fDoOnGetDataPosted = FALSE ;
    
    m_cchOutput = 0 ;
    m_cLinesOutput = 0 ;
}      

CIDataObjectDlg::~CIDataObjectDlg()
{   
    if (m_MetaFile.hMF != NULL)
        DeleteMetaFile( m_MetaFile.hMF ) ;
}

int CIDataObjectDlg::DoModal( void )
{
    if (-1 == DialogBoxParam( GetModuleHandle(_T("IVIEWERS.DLL")), MAKEINTRESOURCE( IDD_IDATAOBJDLG ), m_hWndParent, (DLGPROC)fnIDataObjectDlg, (LONG)this ))
    {
        DWORD dw = GetLastError() ;
        ErrorMessage( "Could not open dialog box", dw ) ;
        return -1 ;
    }
    else
        return 0 ;
}

extern "C"
BOOL EXPORT CALLBACK fnIDataObjectDlg( HWND hDlg, UINT uiMsg, WPARAM wParam, LPARAM lParam )
{
    CIDataObjectDlg * pIDOD =(CIDataObjectDlg *)GetWindowLong( hDlg, DWL_USER ) ;
    
    switch (uiMsg)
    {
        case WM_INITDIALOG: 
            pIDOD=(CIDataObjectDlg *)lParam ;            
            if (pIDOD==NULL)
            {
                EndDialog( hDlg, 0 ) ;
                return TRUE ;
            }
            SetWindowLong( hDlg, DWL_USER, (LONG)pIDOD ) ;
            pIDOD->m_hDlg = hDlg ;
            return pIDOD->OnInitDialog() ;
        break ;
        
        case WM_DESTROY: 
            if (pIDOD)
                pIDOD->OnDestroy() ;
        break ;
        
        case WM_SIZE: 
            if (pIDOD)
                pIDOD->OnSize( (UINT)wParam, LOWORD( lParam ), HIWORD( lParam ) ) ; 
        break ;
        
        case WM_COMMAND:
        {
            WORD wNotifyCode = HIWORD(wParam);  
            WORD wID = LOWORD(wParam);         
            HWND hwndCtl = (HWND) lParam;      
            
            switch (wID)
            {
                case IDCANCEL:
                    EndDialog( hDlg, IDCANCEL ) ;
                break ;
                
                case IDC_DOGETDATA:
                    pIDOD->OnDoGetData() ;
                break ;
                
                case IDC_SETUPADVISE:
                    if (pIDOD->m_dwConn != 0)
                        pIDOD->OnKillAdvise() ;
                    else
                        pIDOD->OnSetupAdvise() ;
                break ;
                
                case IDC_CLEAROUTPUT:
                    if (pIDOD)
                    {
                        pIDOD->m_cchOutput = 0 ;
                        pIDOD->m_cLinesOutput = 0 ;
                        if (pIDOD->m_MetaFile.hMF != NULL)
                        {
                            DeleteMetaFile( pIDOD->m_MetaFile.hMF ) ;
                            pIDOD->m_MetaFile.hMF = NULL ;
                        }
                        SetWindowText( pIDOD->m_edtAdvise, _T("") ) ;
                        InvalidateRect( pIDOD->m_edtAdvise, NULL, TRUE ) ;
                        UpdateWindow( pIDOD->m_edtAdvise ) ;
                    }
                break ;
                
                case IDC_FORMATETC:
                    if (wNotifyCode == LBN_SELCHANGE)
                        pIDOD->OnSelChangeFormatEtc() ;
                break ;
                
                case IDC_UPDATEDISPLAY:
                    pIDOD->m_fUpdateDisplay = Button_GetCheck( pIDOD->m_chkUpdateDisplay ) ;
                break ;
                
                case IDC_PRIMEFIRST:
                    if (Button_GetCheck( pIDOD->m_chkPrimeFirst ))
                        pIDOD->m_advf = ADVF_PRIMEFIRST ;
                    else
                        pIDOD->m_advf = ADVF_NODATA ;
                break ;
                       
                case IDC_ADVISEDATA:
                    if (wNotifyCode == EN_ERRSPACE)
                    {
                        #ifdef _DEBUG
                        OutputDebugString(_T("Output Edit Control reports EN_ERRSPACE\r\n")) ;
                        #endif
                    }
                break ;
            }
        }
        break ;
        
        case WM_DRAWITEM:
            if (pIDOD)
                pIDOD->OnDrawItem( wParam, (LPDRAWITEMSTRUCT)lParam ) ;
        break ;
        
        case WM_MEASUREITEM:
            if (pIDOD)
                pIDOD->OnMeasureItem( wParam, (LPMEASUREITEMSTRUCT)lParam ) ;
        break ;
        
        case WM_OUTPUTBUFFERHASDATA:
            if (pIDOD)
                pIDOD->OnOutputBufferHasData() ;
        break ;
        
        default:
           return FALSE ; 
    }    
    return TRUE ;
}


/////////////////////////////////////////////////////////////////////////////
// CIDataObjectDlg message handlers

BOOL CIDataObjectDlg::OnInitDialog()
{       
    m_btnDoGetData = GetDlgItem( m_hDlg, IDC_DOGETDATA ) ;
    m_btnSetupAdvise = GetDlgItem( m_hDlg, IDC_SETUPADVISE ) ;
    
    m_lbGetData = GetDlgItem( m_hDlg, IDC_GETDATA ) ;
    m_lbFmtEtc = GetDlgItem( m_hDlg, IDC_FORMATETC ) ;
    
    m_edtAdvise = GetDlgItem( m_hDlg, IDC_ADVISEDATA ) ;
    
    // Sublcass the edit so we can override it's WM_PAINT and
    // draw a metafile if we want to...
    if (g_pfnEdit == NULL)
        g_pfnEdit = SubclassWindow( m_edtAdvise, fnEditSubclass) ;

    m_chkUpdateDisplay = GetDlgItem( m_hDlg, IDC_UPDATEDISPLAY );
    m_fUpdateDisplay = TRUE ;
    Button_SetCheck( m_chkUpdateDisplay, m_fUpdateDisplay ) ;

    m_chkPrimeFirst = GetDlgItem( m_hDlg, IDC_PRIMEFIRST );
    Button_SetCheck( m_chkPrimeFirst, m_advf == ADVF_PRIMEFIRST ) ;

    //m_chkDump = GetDlgItem( m_hDlg, IDC_DUMPTOFILE ) ;
    //EnableWindow( m_chkDump, FALSE ) ;

    TEXTMETRIC  tm ;   
    HDC hdc = GetDC(NULL);
    g_hFont = CreateFont( -8, 0, 0, 0, 0, 0, 
                0, 0, ANSI_CHARSET, 0, 0, 0,
                FF_SWISS, _T("MS Sans Serif") ) ;
    g_hFontBold = CreateFont( -8, 0, 0, 0, 0, FW_BOLD, 
                0, 0, ANSI_CHARSET, 0, 0, 0,
                FF_SWISS, _T("MS Sans Serif") ) ;
    HFONT hFont = (HFONT)SelectObject( hdc, g_hFont ) ;
    GetTextMetrics( hdc, &tm ) ;
    SelectObject( hdc, hFont ) ;
    ReleaseDC( NULL, hdc ) ;
    g_cyFont = tm.tmHeight + tm.tmExternalLeading ;                   
    
    SetWindowFont( m_lbFmtEtc, g_hFont, TRUE ) ;
    SetWindowFont( m_lbGetData, g_hFont, TRUE ) ;
    SetWindowFont( m_edtAdvise, g_hFont, TRUE ) ;

    DoIDataObject( 0, (LPDATAOBJECT)m_lpDO ) ;   
    
    DlgCenter( m_hDlg, m_hWndParent, FALSE ) ;    
    RECT rc ;
    GetWindowRect( m_hDlg, &rc ) ;
    SetWindowPos( m_hDlg, NULL, rc.left, rc.top, rc.right - rc.left + 1,
                  rc.bottom - rc.top +1, SWP_NOMOVE|SWP_NOZORDER | SWP_NOACTIVATE ) ;

    //SetWindowText( m_hDlg, m_lpszName ) ;

    return TRUE;  // return TRUE  unless you set the focus to a control
}

void CIDataObjectDlg::OnDestroy()
{   
    OnKillAdvise() ;     

    if (g_pfnEdit != NULL && m_edtAdvise && IsWindow(m_edtAdvise))
    {
        SubclassWindow(m_edtAdvise, g_pfnEdit) ;
        g_pfnEdit = NULL ;
    }
    
    int c = ListBox_GetCount( m_lbFmtEtc ) ;
    for (int i = 0 ; i < c ; i++)
    {
        LPITEMDATA lpID = (LPITEMDATA)ListBox_GetItemData( m_lbFmtEtc, i ) ;
        if (lpID)
        {
            if (lpID->uiType == I_COLUMNHEAD)
                delete lpID->rgCol ;
                
            if (lpID->lpData != NULL)
                delete (LPFORMATETC)lpID->lpData ;
            delete lpID ;
        }        
    }

    if (g_hFont)
        DeleteObject( g_hFont );

    if (g_hFontBold)
        DeleteObject (g_hFontBold) ;
}

void CIDataObjectDlg::OnSize(UINT, int cx, int cy)
{
    if (m_edtAdvise && !IsWindow( m_edtAdvise ) )
        return ;

    RECT    rc ;                    
    RECT    rcWnd ;
    GetClientRect( m_hDlg, &rcWnd ) ; 
    GetWindowRect( m_edtAdvise, &rc ) ;    
    MapWindowPoints( NULL, m_hDlg, (POINT *)&rc, 2 ) ;
    SetWindowPos( m_edtAdvise, NULL, -1, rc.top, cx+2, cy - rc.top + 1,
                SWP_NOZORDER | SWP_NOACTIVATE ) ;
}


void CIDataObjectDlg::OnDrawItem(int, LPDRAWITEMSTRUCT lpDIS )
{ 
    if (lpDIS->itemID == LB_ERR)
        return ;

    LPITEMDATA      lpID ;
    COLORREF        rgbBack ;
    RECT            rcFocus ;
    BOOL            fSelected ;
    int             x, y, cy ;
    TCHAR           szItem[128] ;
    HFONT           hFont ;

    lpID = (LPITEMDATA)lpDIS->itemData ;
    rcFocus = lpDIS->rcItem ;    
    ListBox_GetText( m_lbFmtEtc, lpDIS->itemID, szItem ) ;
    
    if (fSelected = (lpDIS->itemState & ODS_SELECTED) ? TRUE : FALSE)
    {
        SetTextColor( lpDIS->hDC, GetSysColor( COLOR_HIGHLIGHTTEXT ) ) ;
        SetBkColor( lpDIS->hDC, rgbBack = GetSysColor( COLOR_HIGHLIGHT ) ) ;
    }
    else
    {
        SetTextColor( lpDIS->hDC, GetSysColor( COLOR_WINDOWTEXT ) ) ;
        SetBkColor( lpDIS->hDC, rgbBack = GetSysColor( COLOR_WINDOW ) ) ;
    }
    
    // if we are loosing focus, remove the focus rect before
    // drawing.
    //
    if ((lpDIS->itemAction) & (ODA_FOCUS))
        if (!((lpDIS->itemState) & (ODS_FOCUS)))
            DrawFocusRect( lpDIS->hDC, &rcFocus ) ;
    
    y = lpDIS->rcItem.top ;
    x = lpDIS->rcItem.left ;

    int cxChar = GetTextMetricsCorrectly( lpDIS->hDC, NULL ) ;
        
    if (lpID && (lpID->uiType == I_COLUMNHEAD || lpID->uiType == I_LABEL))
        hFont = (HFONT)SelectObject( lpDIS->hDC, g_hFontBold ) ;

    cy = (rcFocus.bottom - rcFocus.top - g_cyFont) / 2 ;
    
    ExtTextOut( lpDIS->hDC, x+2, y + cy, ETO_OPAQUE, &lpDIS->rcItem, NULL, 0, NULL ) ;
    if (lpID)
        ColumnTextOut( lpDIS->hDC, x + 2 + ((cxChar*3) * lpID->nLevel), y + cy, szItem, lpID->cColumns, lpID->rgCol ) ;

    if (lpID && lpID->uiType == I_COLUMNHEAD )
    {
        COLORREF    rgb ;
        RECT        rc = rcFocus ;
        rgb = SetBkColor( lpDIS->hDC, GetTextColor( lpDIS->hDC ) ) ;  
        rc.top = rc.bottom - 1 ; 
        rc.left = x + 2 + ((cxChar*3) * lpID->nLevel) ;
        ExtTextOut( lpDIS->hDC, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL ) ;
        SetBkColor( lpDIS->hDC, rgb ) ;  
    }

    if (lpID && (lpID->uiType == I_COLUMNHEAD || lpID->uiType == I_LABEL))
        (HFONT)SelectObject( lpDIS->hDC, hFont ) ;

    // if we are gaining focus draw the focus rect after drawing
    // the text.
    if ((lpDIS->itemAction) & (ODA_FOCUS))
        if ((lpDIS->itemState) & (ODS_FOCUS))
             DrawFocusRect( lpDIS->hDC, &rcFocus ) ;
    
    if (fSelected)
    {
        SetTextColor( lpDIS->hDC, GetSysColor( COLOR_WINDOWTEXT ) ) ;
        SetBkColor( lpDIS->hDC, GetSysColor( COLOR_WINDOW ) ) ;
    }

}

void CIDataObjectDlg::OnMeasureItem(int, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
    lpMeasureItemStruct->itemHeight = g_cyFont ;
}

void CIDataObjectDlg::OnDblClkFormatEtc()
{
}

void CIDataObjectDlg::OnSelChangeFormatEtc()
{
    
    if (m_MetaFile.hMF != NULL)
    {
        DeleteMetaFile( m_MetaFile.hMF ) ;
        m_MetaFile.hMF = NULL ;
    }
    InvalidateRect( m_edtAdvise, NULL, TRUE ) ;
    SetWindowText( m_edtAdvise, _T("") ) ;

    int i = ListBox_GetCurSel( m_lbFmtEtc ) ;
    if (i != LB_ERR)
    {
        LPITEMDATA lpid = (LPITEMDATA)ListBox_GetItemData( m_lbFmtEtc, i ) ;
        if (lpid && lpid->lpData)
        {
            m_fetc = *(LPFORMATETC)lpid->lpData ;
            return ;
        }
    }
    
    m_fetc.cfFormat = CF_TEXT ;
    m_fetc.dwAspect = DVASPECT_CONTENT ;
    m_fetc.ptd = NULL ;
    m_fetc.tymed = TYMED_HGLOBAL ;
    m_fetc.lindex = - 1 ;
}


void CIDataObjectDlg::OnDoGetData()
{
    HRESULT     hr ;
    STGMEDIUM   stm ;
    TCHAR       szBuf[256] ;
    
    if (m_lpDO == NULL)
    {   
        ListBox_AddString( m_lbGetData, _T( "IDataObject viewer internal error: m_lpDO == NULL" ) ) ;
        return ;
    }    

    wsprintf( szBuf, _T( "Calling GetData for %s" ), (LPCTSTR)MyGetClipboardFormatName( m_fetc.cfFormat ) ) ;
    ListBox_AddString( m_lbGetData, szBuf ) ;
    
    hr = m_lpDO->QueryGetData( &m_fetc ) ;
    if (FAILED( hr ))
    {
        wsprintf( szBuf, _T( "QueryGetData() failed:  %s" ), 
                    (LPTSTR)HRtoString( hr ) ) ;
        ListBox_AddString( m_lbGetData, szBuf ) ;                    
        return ;
    }

    stm.tymed = m_fetc.tymed ;    
    hr = m_lpDO->GetData( &m_fetc, &stm ) ;
    if (FAILED( hr ))
    {
        wsprintf( szBuf, _T( "GetData() failed:  %s" ), 
                    (LPTSTR)HRtoString( hr ) ) ;
        ListBox_AddString( m_lbGetData, szBuf ) ;                    
        return ;
    }
 
    if (FAILED( hr = GotData( &m_fetc, &stm ) ))
    {
        wsprintf( szBuf, _T( "IDataDlg::GotData() failed:  %s" ), 
                    (LPTSTR)HRtoString( hr ) ) ;
        ListBox_AddString( m_lbGetData, szBuf ) ;                    
        return ;
    }   
    
    // If we're warm linked, then don't let the queue overfill.  Allow only one
    // message at a time.  This is similar to the way Excel handles it's DDE Links.
    //
    m_fDoOnGetDataPosted = FALSE ;
}


HRESULT CIDataObjectDlg::GotData( LPFORMATETC lpfetc, LPSTGMEDIUM lpstm )
{
    HRESULT hr = NOERROR ;
    BOOL    fHandled = FALSE ;

    if ((lpfetc->cfFormat == CF_TEXT  || lpfetc->cfFormat == CF_UNICODETEXT) 
            && lpstm->tymed & TYMED_HGLOBAL)
    {     
        LPTSTR lpsz = (LPTSTR)GlobalLock( lpstm->hGlobal ) ;
        if (lpsz == NULL)
        {
            ListBox_AddString( m_lbGetData, _T( "hGlobal returned by GetData is NULL!" ) ) ;                    
            ReleaseStgMedium( lpstm );
            return ResultFromScode( E_FAIL ) ;
        }
        
        if (m_fUpdateDisplay == TRUE)
		{
        #ifndef _UNICODE
            if (lpfetc->cfFormat == CF_UNICODETEXT)
            {
                UINT n = wcslen( (LPWSTR)lpsz ) ;
                TCHAR* sztemp = new TCHAR[n+1] ;
                wcstombs( sztemp, (LPWSTR)lpsz, n ) ;
                WriteToOutput( sztemp ) ;
                delete [] sztemp ;
            }
            else
                WriteToOutput( lpsz ) ;
        #else
            if (lpfetc->cfFormat == CF_TEXT)
            {
                UINT n = _mbstrlen( (LPCSTR)lpsz ) ;
                TCHAR* sztemp = new TCHAR[n+1] ;
                mbstowcs( sztemp, (LPSTR)lpsz, n ) ;
                WriteToOutput( sztemp ) ;
                delete [] sztemp ;
            }
            else
                WriteToOutput( lpsz ) ;
        #endif
        }
            
        GlobalUnlock( lpstm->hGlobal ) ;

        fHandled = TRUE ;
    }

    if (lpfetc->cfFormat == CF_METAFILEPICT && lpstm->tymed & TYMED_MFPICT)
    {
        LPMETAFILEPICT pMF = (LPMETAFILEPICT)GlobalLock(lpstm->hGlobal) ;
        if (pMF)
        {
            if (m_MetaFile.hMF)
            {
                DeleteMetaFile( m_MetaFile.hMF ) ;
                m_MetaFile.hMF = NULL ;
            }
            
            if (m_fUpdateDisplay)
            {                
                m_MetaFile = *pMF ;
                m_MetaFile.hMF = CopyMetaFile( pMF->hMF, NULL ) ;
                InvalidateRect( m_edtAdvise, NULL, FALSE ) ;
            }
        }
        fHandled = TRUE ;
    }
    
    m_cOnDataChanges++ ;
        
    ReleaseStgMedium( lpstm );

    if (fHandled == FALSE)
    {
        ListBox_AddString( m_lbGetData, _T( "The IDataObject viewer does not know how to decode this FORMATETC" ) ) ;                    
    }
    
    return  hr;
}

void CIDataObjectDlg::OnSetupAdvise()
{
    TCHAR szBuf[128] ;
    OnKillAdvise() ;
    
    m_pSink = new CImpIAdviseSink( this ) ;

    if (m_pSink == NULL)
    {
        ListBox_AddString( m_lbGetData, _T( "CImpIAdviseSink constructor failed" ) ) ;                    
        return ;
    }
    
    SetWindowText( m_btnSetupAdvise, _T( "&Kill Advise" ) ) ;

    wsprintf( szBuf, _T( "Advise started with a cfFormat of %s" ), 
                (LPCTSTR)MyGetClipboardFormatName( m_fetc.cfFormat ) ) ;
    ListBox_AddString( m_lbGetData, szBuf ) ;    

    m_dwTime = GetTickCount() ;
    m_cOnDataChanges = 0 ;

    HRESULT hr = m_lpDO->DAdvise( &m_fetc, m_advf, m_pSink, &m_dwConn ) ;        
    if (FAILED( hr ))
    {
        wsprintf( szBuf, _T( "DAdvise() failed:  %s" ), (LPTSTR)HRtoString( hr ) ) ;
        ListBox_AddString( m_lbGetData, szBuf ) ;                    
        SetWindowText( m_btnSetupAdvise, _T( "&DAdvise" ) ) ;
        return ;
    }

}

void CIDataObjectDlg::OnKillAdvise()
{
    if (m_dwConn != 0)
    {       
        TCHAR szBuf[128] ;                           
        DWORD dwTime = max( 1000, GetTickCount() - m_dwTime ) ;    // avoid div by zero

        m_lpDO->DUnadvise( m_dwConn ) ;
        m_dwConn = 0 ;
        wsprintf( szBuf, _T( "  Advise Killed after %lu milliseconds, and %lu OnDataChanges;" ), dwTime, m_cOnDataChanges ) ;
        ListBox_AddString( m_lbGetData, szBuf ) ;
        wsprintf( szBuf, _T( "  That's %lu.%lu OnDataChange calls per second." ), 
                        m_cOnDataChanges / (dwTime / 1000L),
                        m_cOnDataChanges % (dwTime / 1000L) ) ;
        ListBox_AddString( m_lbGetData, szBuf ) ;
    }
        
    if (m_pSink != NULL)
    {
        m_pSink->Release() ;
        m_pSink = NULL ;
    }
    
    SetWindowText( m_btnSetupAdvise, _T( "&DAdvise" ) ) ;
}

// DoIDataObject
//
// This is where we start doing IDataObject dirty work.
// This function calls pIDataObject->EnumFormatEtc twice, once
// each for DATADIR_GET and DATADIR_SET, to get a pIEnumFORMATETC.
//
// We then call DoIEnumFormatEtc() with this pointer to actually
// perform the enumaration.
//  
BOOL CIDataObjectDlg::DoIDataObject( UINT nLevel, LPDATAOBJECT pIDataObject )        
{
    LPENUMFORMATETC pEFE = NULL ;
    HRESULT     hr1 ;
    
    hr1 = pIDataObject->EnumFormatEtc( DATADIR_GET, &pEFE ) ;
    if (SUCCEEDED( hr1 ))
    {
        if (pEFE == NULL)
        {
            AfxMessageBox(_T("IDataObject::EnumFormatEtc returned success, but returned IEnumFORMATETC pointer is NULL.")) ;
            AddItem( nLevel, _T( "<<DATA_DIRGET failed>>" ), NULL, I_LABEL ) ;
            return FALSE ;
        }

        DoIEnumFormatEtc( nLevel, pEFE ) ;
        pEFE->Release() ;
    }
    else
        AddItem( nLevel, _T( "<<DATA_DIRGET failed>>" ), NULL, I_LABEL ) ;

    if (SUCCEEDED(hr1))    
    {
        ListBox_SetCurSel( m_lbFmtEtc, 0 ) ;
        OnSelChangeFormatEtc() ;        
    }
    return TRUE ;
}

// This member takes a pointer to an IEnumFORMATETC and enumerates
// the FORMATETC structures avaialbe, inserting them into our
// listbox.
//  
BOOL CIDataObjectDlg::DoIEnumFormatEtc( UINT nLevel, LPENUMFORMATETC pIFormatEtc )        
{   
    LPCOLUMNSTRUCT  rgCol = new COLUMNSTRUCT[5] ;
    UINT    cxChar ;
    HDC     hdc  = GetDC( NULL) ;
                        
    // We use the "ColumnTextOut" code in util.c to create nice
    // columns in our list box. 
    //
    HFONT hFont = (HFONT)SelectObject( hdc, g_hFontBold ) ;                        
    cxChar = GetTextMetricsCorrectly( hdc, NULL ) ;
    rgCol[0].nLeft = 0 ;
    rgCol[0].nRight = cxChar * lstrlen( _T( "CF_METAFILEPICT_" ) ) ;
    rgCol[0].uiFlags = DT_LEFT ;
        
    rgCol[1].nLeft = rgCol[0].nRight + cxChar ;
    rgCol[1].nRight = rgCol[1].nLeft + cxChar * lstrlen( _T( "0000:0000_" ) ) ;
    rgCol[1].uiFlags = DT_LEFT ;

    rgCol[2].nLeft = rgCol[1].nRight + cxChar ;
    rgCol[2].nRight = rgCol[2].nLeft + cxChar * lstrlen( _T( "THUMBNAIL_" ) ) ;
    rgCol[2].uiFlags = DT_LEFT ;

    rgCol[3].nLeft = rgCol[2].nRight + cxChar ;
    rgCol[3].nRight = rgCol[3].nLeft + cxChar * lstrlen( _T( "0000:0000_" ) ) ;
    rgCol[3].uiFlags = DT_LEFT ;

    rgCol[4].nLeft = rgCol[3].nRight + cxChar ;
    rgCol[4].nRight = rgCol[4].nLeft + cxChar * lstrlen( _T( "_HGLOBAL_" ) ) ;
    rgCol[4].uiFlags = DT_LEFT ;
        
    (HFONT)SelectObject( hdc, hFont ) ;                        
    ReleaseDC( NULL, hdc ) ;

    AddItem( nLevel, _T( "cfFormat\tptd\tdwAspect\tlindex\ttymed" ), NULL, I_COLUMNHEAD, 5, rgCol ) ;
    LPFORMATETC     pfetc ;
    FORMATETC       fetc ;
    TCHAR           sz[512] ;
    ULONG           ulFE ; // number returned

    while (pIFormatEtc->Next( 1, &fetc, &ulFE ) == S_OK)
    {
        // now insert into list
        pfetc = new FORMATETC ;
        *pfetc = fetc ;
        lstrcpy( sz, MyGetClipboardFormatName( (UINT)fetc.cfFormat ) ) ;
        lstrcat( sz, _T("\t") ) ;

        TCHAR szTemp[32] ;
        wsprintf( szTemp, _T( "%04lX:%04lX\t" ), (DWORD)HIWORD( (DWORD)fetc.ptd ), (DWORD)LOWORD( (DWORD)fetc.ptd ) ) ;
        lstrcat( sz, szTemp ) ;
                    
        switch( fetc.dwAspect )
        {
            case DVASPECT_CONTENT: lstrcat( sz, _T( "CONTENT\t" ) ) ; break ;          
            case DVASPECT_THUMBNAIL: lstrcat( sz, _T( "THUMBNAIL\t" ) ) ; break ;
            case DVASPECT_ICON: lstrcat( sz, _T( "ICON\t" ) ) ; break ;
            case DVASPECT_DOCPRINT: lstrcat( sz, _T( "DOCPRINT\t" ) ) ; break ;
            default:  
                wsprintf( szTemp, _T( "%08lX\t" ), (DWORD)fetc.dwAspect ) ;
                lstrcat( sz, szTemp ) ; 
            break ;
        }
                    
        wsprintf( szTemp, _T( "%04lX:%04lX\t" ), (DWORD)HIWORD( fetc.lindex ), (DWORD)LOWORD( fetc.lindex ) ) ;
        lstrcat( sz, szTemp ) ;
                    
        switch( fetc.tymed )
        {
            case TYMED_HGLOBAL: lstrcat( sz, _T( "HGLOBAL\t" ) ) ; break ;       
            case TYMED_FILE: lstrcat( sz, _T( "FILE\t" ) ) ; break ;
            case TYMED_ISTREAM: lstrcat( sz, _T( "ISTREAM\t" ) ) ; break ;
            case TYMED_ISTORAGE: lstrcat( sz, _T( "ISTORAGE\t" ) ) ; break ;
            case TYMED_GDI: lstrcat( sz, _T( "GDI\t" ) ) ; break ;
            case TYMED_MFPICT: lstrcat( sz, _T( "MFPICT\t" ) ) ; break ;
            case TYMED_NULL: lstrcat( sz, _T( "NULL\t" ) ) ; break ;
            default: 
                wsprintf( szTemp, _T( "%08lX\t" ), (DWORD)fetc.tymed ) ;
                lstrcat( sz, szTemp ) ; 
            break ;
        }

        AddItem( nLevel, sz, 
                 pfetc, I_COLUMNENTRY, 5, rgCol ) ;
    }
    
    return TRUE ;
}

LPCTSTR MyGetClipboardFormatName( UINT cf )
{
    static TCHAR sz[256] ;
    switch( cf )
    {

        case CF_UNICODETEXT: lstrcpy( sz, _T( "CF_UNICODETEXT" ) ) ; break ;         
        case CF_ENHMETAFILE: lstrcpy( sz, _T( "CF_ENHMETAFILE" ) ) ; break ;  
        case CF_TEXT: lstrcpy( sz, _T( "CF_TEXT" ) ) ; break ;         
        case CF_BITMAP: lstrcpy( sz, _T( "CF_BITMAP" ) ) ; break ;
        case CF_METAFILEPICT: lstrcpy( sz, _T( "CF_METAFILEPICT" ) ) ; break ;  
        case CF_SYLK: lstrcpy( sz, _T( "CF_SYLK" ) ) ; break ;
        case CF_DIF: lstrcpy( sz, _T( "CF_DIF" ) ) ; break ;           
        case CF_TIFF: lstrcpy( sz, _T( "CF_TIFF" ) ) ; break ;          
        case CF_OEMTEXT: lstrcpy( sz, _T( "CF_OEMTEXT" ) ) ; break ;       
        case CF_DIB: lstrcpy( sz, _T( "CF_DIB" ) ) ; break ;           
        case CF_PALETTE: lstrcpy( sz, _T( "CF_PALETTE" ) ) ; break ;      
        case CF_PENDATA: lstrcpy( sz, _T( "CF_PENDATA" ) ) ; break ;         
        case CF_RIFF: lstrcpy( sz, _T( "CF_RIFF" ) ) ; break ;             
        case CF_WAVE: lstrcpy( sz, _T( "CF_WAVE" ) ) ; break ;            
        case 0: lstrcpy( sz, _T( "\"Wildcard Advise\"" ) ) ; break ;
        default:
            if (!GetClipboardFormatName( (UINT)cf, sz, 254 ))
                wsprintf( sz, _T( "%#08lX" ), (DWORD)cf ) ;
        break ;
    }

    return sz ;
}


UINT DBStrCpy(LPTSTR pszDst, LPCTSTR pszSrc, int far* pcLines) ;

BOOL  CIDataObjectDlg::WriteToOutput( LPTSTR lpsz ) 
{      
    UINT cch;
    int cLines;

    cch = DBStrCpy(NULL, lpsz, &cLines);

    if (cch > (CCHOUTPUTMAX - m_cchOutput))
    {
        MessageBeep(0);
        return FALSE;
    }

    DBStrCpy(&m_szOutput[m_cchOutput], lpsz, NULL);

    m_cchOutput += cch;
    m_cLinesOutput += cLines;

    // If buffer was originally empty, wake up the main loop.
    //
    if (m_cchOutput == cch)
        PostMessage( m_hDlg, WM_OUTPUTBUFFERHASDATA, 0, 0L);

    return TRUE;
}

// This function performs a string copy (or string length if pszDst is NULL)
// It also ensures the destination string is properly formatted for the
// edit controls: line terminators must be CR followed by LF, in that orderc.
// If pcLines != NULL, returns number of lines in the string in *pcLines
//
// (pulled from the dbwin sample)
//
UINT DBStrCpy(LPTSTR pszDst, LPCTSTR pszSrc, int far* pcLines)
{
    TCHAR ch;
    UINT cch = 0;
    int cLines = 0;

    while (ch = *pszSrc++)
    {
        // If we find a CR or LF, then store a CR/LF in
        // the output string.
        //
        if (ch == 0x0d || ch == 0x0a)
        {
            cch += 2;
            cLines++;
            if (pszDst)
            {
                *pszDst++ = 0x0d;
                *pszDst++ = 0x0a;
            }

            // Skip any other CRs or LFs we find.
            //
            while ((ch = *pszSrc) && (ch == 0x0d || ch == 0x0a))
                ++pszSrc;
        }
        else
        {
            cch++;
            if (pszDst)
                *pszDst++ = ch;
        }
    }
    if (pszDst)
        *pszDst = 0;

    if (pcLines)
        *pcLines = cLines;

    return cch;
}

// Whenever something happens that causes output, it calls the WriteToOutput function
// which puts the data into a buffer (m_szOutput) and posts a message.  This is
// the message handler.  
//
void CIDataObjectDlg::OnOutputBufferHasData()
{
    if (m_szOutput != NULL && m_cchOutput != 0)
    {
        int cLines = Edit_GetLineCount(m_edtAdvise) + m_cLinesOutput ;
         
        if (cLines > CLINESMAX)
        {
            SetWindowRedraw( m_edtAdvise, FALSE ) ;
            // If the total # of lines is greater than our arbitrary max lines
            // remove some from the top.
            //
            Edit_SetSel( m_edtAdvise, 0, Edit_LineIndex( m_edtAdvise, cLines - CLINESMAX));
            Edit_ReplaceSel( m_edtAdvise, _T(""));
        }

        // Put current output buffer at the end of the edit control
        //
        Edit_SetSel( m_edtAdvise, (UINT)-1, (UINT)-1 )  ;   // select the end
        Edit_ReplaceSel( m_edtAdvise, m_szOutput ) ;
        Edit_SetSel( m_edtAdvise, (UINT)-1, (UINT)-1 )  ;   // select the end
        
        if (cLines > CLINESMAX)
            SetWindowRedraw( m_edtAdvise, TRUE ) ;
            
        // Reset the output buffer
        m_cLinesOutput = 0 ;
        m_cchOutput = 0 ;
    }
}


// AddItem does a ListBox_AddString() plus a ListBox_SetItemData().  The
// item data that is set tells our owner-draw code what 'level' this
// item is at (indentation) and whether it is a label (bold), column
// head (column info is stored), or column entry (no column info is
// stored, but previous column head is used in WM_DRAWITEM).
//
int CIDataObjectDlg::AddItem( UINT nLevel, LPTSTR sz, LPVOID lpData, UINT uiType, int cColumns, LPCOLUMNSTRUCT  rgCol )
{    
    int i ;
    LPITEMDATA      lpID ;

    i = ListBox_AddString( m_lbFmtEtc,  sz ) ;
    lpID = new ITEMDATA ;        
    lpID->uiType = uiType ;
    lpID->cColumns = cColumns ;
    lpID->rgCol = rgCol ; 
    lpID->nLevel = nLevel ;
    lpID->lpData = lpData ;
    ListBox_SetItemData( m_lbFmtEtc, i, (DWORD)lpID ) ;
    return i ;
}

int CIDataObjectDlg::AddItem( UINT nLevel, LPTSTR sz, LPVOID lpData, UINT uiType )
{    
    return AddItem( nLevel, sz, lpData, uiType, 0, NULL ) ;
}

int CIDataObjectDlg::AddItem( UINT nLevel, LPTSTR sz, LPVOID lpData )
{    
    return AddItem( nLevel, sz, lpData, I_NORMAL, 0, NULL ) ;
}

extern "C"
LRESULT EXPORT CALLBACK fnEditSubclass( HWND hwnd, UINT uiMsg, WPARAM wP, LPARAM lP )
{   
    if (uiMsg == WM_PAINT)
    {
        HWND hwndParent = GetParent(hwnd) ;
        CIDataObjectDlg * pIDOD =(CIDataObjectDlg *)GetWindowLong( hwndParent, DWL_USER ) ;
        if (pIDOD && pIDOD->m_MetaFile.hMF != NULL)
        {
            PAINTSTRUCT ps ;
            BeginPaint( hwnd, &ps ) ;

/*            
            if (pIDOD->m_MetaFile.mm == MM_ISOTROPIC || pIDOD->m_MetaFile.mm == MM_ANISOTROPIC)
            {    
                if (pIDOD->m_MetaFile.xExt != 0 && pIDOD->m_MetaFile.yExt != 0)
                {
                    
                }
            }
            else
            {
                SetMapMode( ps.hdc, MM_ANISOTROPIC ) ;
                SetWindowOrg( ps.hdc, 0, 0 ) ;
                SetWindowExt( ps.hdc, pIDOD->m_MetaFile.xExt, pIDOD->m_MetaFile.yExt ) ;
                SetViewportExt( ps.hdc, pIDOD->m_MetaFile.xExt, pIDOD->m_MetaFile.yExt ) ;
            }
*/            
            PlayMetaFile( ps.hdc, pIDOD->m_MetaFile.hMF ) ;

            EndPaint( hwnd, &ps ) ;
            return 0L ;
        }
    }
    
    return CallWindowProc( g_pfnEdit, hwnd, uiMsg, wP, lP ) ;
}

/****************************************************************
 *
 *  Description: 
 *
 *    The ColumntTextOut function writes a character string at
 *    the specified location, using tabs to identify column breaks.  Each
 *    column is aligned according to the array of COLUMNSTRUCTs.
 *
 *    A COLUMNSTRUCT looks like this:
 *
 *    {
 *       int   nLeft ;       // starting x position of the column
 *       int   nRight ;      // ending x position of the column
 *       UINT  uiFlags ;      // format flags
 *    }
 *
 *    If a column has another column to the left of it, it's nLeft member
 *    is ignored.
 *
 *    uiFlags can be any of the following:
 *
 *          #define DT_LEFT      0x0000
 *          #define DT_CENTER    0x0001
 *          #define DT_RIGHT     0x0002
 *
 *  Comments:
 *
 ****************************************************************/
void WINAPI ColumnTextOut( HDC hdc, int nX, int nY, LPTSTR lpszIN,
                            int cColumns, LPCOLUMNSTRUCT rgColumns )
{

#define ETOFLAGS ETO_CLIPPED

    COLUMNSTRUCT   CS ;              // local copy for speed 
    RECT           rc ;              // current cell rect 
    int            cIteration = 0 ;  // what column 
    LPTSTR          lpNext ;          // next string (current is lpsz) 
    int            cch ;             // count of chars in current string 
    SIZE           size ;            // extent of current string 
    int            dx ;              // column width 
    
    rc.left = nX ;
    rc.top = nY ;
    rc.right = 0 ;
    
    if (rgColumns == NULL || cColumns <= 1)
    {                                                                      
        int Tab = 15 ;
        TabbedTextOut( hdc, nX, nY, lpszIN, lstrlen(lpszIN), 1, &Tab, nX ) ;
        return ;
    }

    // For each sub string (bracketed by a tab)
    //
    LPTSTR lpsz = lpszIN ;
    while (*lpsz)
    {
        if (cIteration >= cColumns)
            return ;
    
        for (cch = 0, lpNext = lpsz ;
             *lpNext != '\t' && *lpNext ;
             lpNext++, cch++)
            ;
    
        CS = rgColumns[cIteration] ;
    
        // If it's the leftmost column use
        //
        if (cIteration == 0)
        {
            rc.left = nX + CS.nLeft ;
            rc.right = nX + CS.nRight ;
        }
        else
            rc.right = nX + CS.nRight ;
        
        GetTextExtentPoint( hdc, lpsz, cch, &size ) ;
        rc.bottom = rc.top + size.cy ;
    
        // If the width of the column is 0 do nothing
        //
        if ((dx = (rc.right - rc.left)) > 0)
        {
            switch(CS.uiFlags)
            {
                case DT_CENTER:
                    ExtTextOut( hdc, rc.left + ((dx - size.cx) / (int)2),
                    rc.top, ETOFLAGS, &rc, lpsz, cch, NULL ) ;
                break ;
                
                case DT_RIGHT:
                    // If it's right justified then make the left border 0
                    //
                    //rc.left = nX + rgColumns[0].nLeft ;
                    ExtTextOut( hdc, rc.left + (dx - size.cx),
                    rc.top, ETOFLAGS, &rc, lpsz, cch, NULL ) ;
                break ;
                
                case DT_LEFT:
                default:
                    ExtTextOut( hdc, rc.left, rc.top, ETOFLAGS, &rc, lpsz, cch, NULL ) ;
                break ;
            }
        }
        rc.left = rc.right ;
        cIteration++ ;
        lpsz = lpNext + 1 ;
    }
    
} // ColumnTextOut()  


/*************************************************************************
 *  void WINAPI
 *  DlgCenter( HWND hwndCenter, HWND hwndWithin, BOOL fClient )
 *
 *  Description:
 *
 *    Centers a window within another window.
 *
 *  Type/Parameter
 *          Description
 *
 *    HWND  hwndCenter
 *          Window to center.  This does not have to be a child of
 *          hwndWithin.
 *    
 *    HWND  hwndWithin
 *          Window to center the above window within.  Can be NULL.
 *
 *    BOOL  fClient
 *          If TRUE the window is centered within the available client
 *          area.  Otherwise it's centered within the entire window area.
 * 
 *  Comments:
 *
 *************************************************************************/
void WINAPI
DlgCenter( HWND hwndCenter, HWND hwndWithin, BOOL fClient )
{
   RECT rcWithin ;
   RECT rcCenter ;
   int   x, y ;
   int   dxCenter, dyCenter ;
   int   dxScreen, dyScreen ;

   dxScreen = GetSystemMetrics( SM_CXSCREEN ) ;
   dyScreen = GetSystemMetrics( SM_CYSCREEN ) ;

   if (!IsWindow(hwndCenter))
      return ;

   if (hwndWithin && !IsWindow(hwndWithin))
      return ;

   if (hwndWithin == NULL)
   {
      rcWithin.left = rcWithin.top = 0 ;
      rcWithin.right = dxScreen ;
      rcWithin.bottom = dyScreen ;
   }
   else
   {
      if (fClient)
      {
         /*
          * First get screen coords of rectangle we're going to be
          * centered within.
          */
         GetClientRect( hwndWithin, (LPRECT)&rcWithin ) ;
         ClientToScreen( hwndWithin, (LPPOINT)&rcWithin.left ) ;
         ClientToScreen( hwndWithin, (LPPOINT)&rcWithin.right ) ;
      }
      else
         GetWindowRect( hwndWithin, (LPRECT)&rcWithin ) ;
   }

   GetWindowRect( hwndCenter, (LPRECT)&rcCenter ) ;
   dxCenter = rcCenter.right - rcCenter.left ;
   dyCenter = rcCenter.bottom - rcCenter.top ;

   /*
    * Now we have both the within and center rects in screen coords.
    *
    * SetWindowPos behaves differently for Non child windows
    * than for child windows.  For popups it's coordinates
    * are relative to the upper left corner of the screen.  For
    * children it's coords are relative to the upper left corner
    * of the client area of the parent.
    */
   x = ((rcWithin.right - rcWithin.left) - dxCenter) / 2 ;
   y = ((rcWithin.bottom - rcWithin.top) - dyCenter) / 2 ;

   if (hwndWithin == GetParent( hwndCenter ) &&
       !(GetWindowLong( hwndCenter, GWL_STYLE ) & WS_CHILD ))
   {
      x += rcWithin.left ;
      y += rcWithin.top ;

      if (x + dxCenter > dxScreen )
         x = dxScreen - dxCenter ;

      if (y + dyCenter > dyScreen )
         y = dyScreen - dyCenter ;

      SetWindowPos( hwndCenter, NULL,
                    max(x,0),
                    max(y,0),
                    0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER ) ;

      return ;
   }

   SetWindowPos( hwndCenter, NULL,
                 max(x,0),
                 max(y,0),
                 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER ) ;

   return ;

}/* DlgCenter() */

/****************************************************************
 *  int WINAPI
 *    GetTextMetricsCorrectly( HDC hDC, LPTEXTMETRIC lpTextMetrics )
 *
 *  Description: 
 *    
 *    This function gets the textmetrics of the font currently
 *    selected into the hDC.  It returns the average char width as
 *    the return value.
 *
 *    This function computes the average character width correctly
 *    by using GetTextExtent() on the string "abc...xzyABC...XYZ"
 *    which works out much better for proportional fonts.
 *
 *    Note that this function returns the same TEXTMETRIC
 *    values that GetTextMetrics() does, it simply has a different
 *    return value.
 *
 *  Comments:
 *
 ****************************************************************/
int WINAPI
   GetTextMetricsCorrectly( HDC hDC, LPTEXTMETRIC lpTM )
{
   int    nAveWidth ;
   TCHAR  rgchAlphabet[52] ;
   int  i ;
   SIZE size ;

   // Get the TM of the current font.  Note that GetTextMetrics
   // gets the incorrect AveCharWidth value for proportional fonts.
   // This is the whole poshort in this exercise.
   //
   if (lpTM)
    GetTextMetrics(hDC, lpTM);

   // If it's not a variable pitch font GetTextMetrics was correct
   // so just return.
   //
   if (lpTM && !(lpTM->tmPitchAndFamily & FIXED_PITCH))  
      return lpTM->tmAveCharWidth ;
   else
   {
      for ( i = 0 ; i <= 25 ; i++)
         rgchAlphabet[i] = (TCHAR)(i+(int)'a') ;

      for ( i = 0 ; i<=25 ; i++)
         rgchAlphabet[i+25] = (TCHAR)(i+(int)'A') ;

      GetTextExtentPoint( hDC, (LPTSTR)rgchAlphabet, 52, &size ) ;
      nAveWidth = size.cx / 26 ;
      nAveWidth = (nAveWidth + 1) / 2 ;
   }

   // Return the calculated average char width
   //
   return nAveWidth ;

} /* GetTextMetricsCorrectly()  */
 

