//------------------------------------------------------------------------------
// File: StillCapDlg.cpp
//
// Desc: DirectShow sample code - implementation of callback and dialog
//       objects for StillCap application.
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------


#include "stdafx.h"
#include "StillCap.h"
#include "StillCapDlg.h"
#include "..\..\common\dshowutil.cpp"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


// An application can advertise the existence of its filter graph
// by registering the graph with a global Running Object Table (ROT).
// The GraphEdit application can detect and remotely view the running
// filter graph, allowing you to 'spy' on the graph with GraphEdit.
//
// To enable registration in this sample, define REGISTER_FILTERGRAPH.
//
#ifdef DEBUG
#define REGISTER_FILTERGRAPH
#endif

// Constants
#define WM_CAPTURE_BITMAP   WM_APP + 1

// Global data
BOOL g_bOneShot=FALSE;
DWORD g_dwGraphRegister=0;  // For running object table
HWND g_hwnd=0;

// Structures
typedef struct _callbackinfo 
{
    double dblSampleTime;
    long lBufferSize;
    BYTE *pBuffer;
    BITMAPINFOHEADER bih;

} CALLBACKINFO;

CALLBACKINFO cb={0};


// Note: this object is a SEMI-COM object, and can only be created statically.
// We use this little semi-com object to handle the sample-grab-callback,
// since the callback must provide a COM interface. We could have had an interface
// where you provided a function-call callback, but that's really messy, so we
// did it this way. You can put anything you want into this C++ object, even
// a pointer to a CDialog. Be aware of multi-thread issues though.
//
class CSampleGrabberCB : public ISampleGrabberCB 
{
public:
    // these will get set by the main thread below. We need to
    // know this in order to write out the bmp
    long lWidth;
    long lHeight;
    CStillCapDlg * pOwner;
    TCHAR m_szCapDir[MAX_PATH]; // the directory we want to capture to
    TCHAR m_szSnappedName[MAX_PATH];
    BOOL bFileWritten;

    CSampleGrabberCB( )
    {
        pOwner = NULL;
        ZeroMemory(m_szCapDir, sizeof(m_szCapDir));
        ZeroMemory(m_szSnappedName, sizeof(m_szSnappedName));
        bFileWritten = FALSE;
    }   

    // fake out any COM ref counting
    //
    STDMETHODIMP_(ULONG) AddRef() { return 2; }
    STDMETHODIMP_(ULONG) Release() { return 1; }

    // fake out any COM QI'ing
    //
    STDMETHODIMP QueryInterface(REFIID riid, void ** ppv)
    {
        if( riid == IID_ISampleGrabberCB || riid == IID_IUnknown ) 
        {
            *ppv = (void *) static_cast<ISampleGrabberCB*> ( this );
            return NOERROR;
        }    
        return E_NOINTERFACE;
    }

    // we don't implement this interface for this example
    //
    STDMETHODIMP SampleCB( double SampleTime, IMediaSample * pSample )
    {
        return 0;
    }

    // The sample grabber is calling us back on its deliver thread.
    // This is NOT the main app thread!
    //
    //           !!!!! WARNING WARNING WARNING !!!!!
    //
    // On Windows 9x systems, you are not allowed to call most of the 
    // Windows API functions in this callback.  Why not?  Because the
    // video renderer might hold the global Win16 lock so that the video
    // surface can be locked while you copy its data.  This is not an
    // issue on Windows 2000, but is a limitation on Win95,98,98SE, and ME.
    // Calling a 16-bit legacy function could lock the system, because 
    // it would wait forever for the Win16 lock, which would be forever
    // held by the video renderer.
    //
    // As a workaround, copy the bitmap data during the callback,
    // post a message to our app, and write the data later.
    //
    STDMETHODIMP BufferCB( double dblSampleTime, BYTE * pBuffer, long lBufferSize )
    {
        // this flag will get set to true in order to take a picture
        //
        if( !g_bOneShot )
            return 0;

        if (!pBuffer)
            return E_POINTER;

        if( cb.lBufferSize < lBufferSize )
        {
            delete [] cb.pBuffer;
            cb.pBuffer = NULL;
            cb.lBufferSize = 0;
        }

        // Since we can't access Windows API functions in this callback, just
        // copy the bitmap data to a global structure for later reference.
        cb.dblSampleTime = dblSampleTime;

        // If we haven't yet allocated the data buffer, do it now.
        // Just allocate what we need to store the new bitmap.
        if (!cb.pBuffer)
        {
            cb.pBuffer = new BYTE[lBufferSize];
            cb.lBufferSize = lBufferSize;
        }

        if( !cb.pBuffer )
        {
            cb.lBufferSize = 0;
            return E_OUTOFMEMORY;
        }

        // Copy the bitmap data into our global buffer
        memcpy(cb.pBuffer, pBuffer, lBufferSize);

        // Post a message to our application, telling it to come back
        // and write the saved data to a bitmap file on the user's disk.
        PostMessage(g_hwnd, WM_CAPTURE_BITMAP, 0, 0L);
        return 0;
    }

    // This function will be called whenever a captured still needs to be
    // displayed in the preview window.  It is called initially within
    // CopyBitmap() to display the captured still, but it is also called
    // whenever the main dialog needs to repaint and when we transition
    // from video capture mode back into still capture mode.
    //
    BOOL DisplayCapturedBits(BYTE *pBuffer, BITMAPINFOHEADER *pbih)
    {
        // If we haven't yet snapped a still, return
        if (!bFileWritten || !pOwner || !pBuffer)
            return FALSE;

        // put bits into the preview window with StretchDIBits
        //
        HWND hwndStill = NULL;
        pOwner->GetDlgItem( IDC_STILL, &hwndStill );

        RECT rc;
        ::GetWindowRect( hwndStill, &rc );
        long lStillWidth = rc.right - rc.left;
        long lStillHeight = rc.bottom - rc.top;
        
        HDC hdcStill = GetDC( hwndStill );
        PAINTSTRUCT ps;
        BeginPaint(hwndStill, &ps);

        SetStretchBltMode(hdcStill, COLORONCOLOR);
        StretchDIBits( 
                    hdcStill, 0, 0, 
                    lStillWidth, lStillHeight, 
                    0, 0, lWidth, lHeight, 
                    pBuffer, 
                    (BITMAPINFO*) pbih, 
                    DIB_RGB_COLORS, 
                    SRCCOPY );

        EndPaint(hwndStill, &ps);
        ReleaseDC( hwndStill, hdcStill );    

        return TRUE;
    }

    // This is the implementation function that writes the captured video
    // data onto a bitmap on the user's disk.
    //
    BOOL CopyBitmap( double dblSampleTime, BYTE * pBuffer, long lBufferSize )
    {
        if( !g_bOneShot || !pBuffer )
            return 0;

        // we only take one at a time
        //
        g_bOneShot = FALSE;

        // figure out where to capture to
        //
        TCHAR m_ShortName[MAX_PATH];

        wsprintf( m_szSnappedName, TEXT("%sStillCap%4.4ld.bmp\0"), 
                  m_szCapDir, pOwner->m_nCapTimes );

        wsprintf( m_ShortName, TEXT("StillCap%4.4ld.bmp\0"), 
                  pOwner->m_nCapTimes );

        // increment bitmap number if user requested it
        // otherwise, we'll reuse the filename next time
        if( pOwner->IsDlgButtonChecked( IDC_AUTOBUMP ) )
            pOwner->m_nCapTimes++;

        // write out a BMP file
        //
        HANDLE hf = CreateFile(
            m_szSnappedName, GENERIC_WRITE, FILE_SHARE_READ, NULL,
            CREATE_ALWAYS, NULL, NULL );

        if( hf == INVALID_HANDLE_VALUE )
            return 0;

        // write out the file header
        //
        BITMAPFILEHEADER bfh;
        memset( &bfh, 0, sizeof( bfh ) );
        bfh.bfType = 'MB';
        bfh.bfSize = sizeof( bfh ) + lBufferSize + sizeof( BITMAPINFOHEADER );
        bfh.bfOffBits = sizeof( BITMAPINFOHEADER ) + sizeof( BITMAPFILEHEADER );

        DWORD dwWritten = 0;
        WriteFile( hf, &bfh, sizeof( bfh ), &dwWritten, NULL );

        // and the bitmap format
        //
        BITMAPINFOHEADER bih;
        memset( &bih, 0, sizeof( bih ) );
        bih.biSize = sizeof( bih );
        bih.biWidth = lWidth;
        bih.biHeight = lHeight;
        bih.biPlanes = 1;
        bih.biBitCount = 24;

        dwWritten = 0;
        WriteFile( hf, &bih, sizeof( bih ), &dwWritten, NULL );

        // and the bits themselves
        //
        dwWritten = 0;
        WriteFile( hf, pBuffer, lBufferSize, &dwWritten, NULL );
        CloseHandle( hf );
        bFileWritten = TRUE;

        // Display the bitmap bits on the dialog's preview window
        DisplayCapturedBits(pBuffer, &bih);

        // Save bitmap header for later use when repainting the window
        memcpy(&(cb.bih), &bih, sizeof(bih));        

        // show where it captured
        //
        pOwner->SetDlgItemText( IDC_SNAPNAME, m_ShortName );

        // Enable the 'View Still' button
        HWND hwndButton = NULL;
        pOwner->GetDlgItem( IDC_BUTTON_VIEWSTILL, &hwndButton );
        ::EnableWindow(hwndButton, TRUE);

        // play a snap sound
        if (pOwner->IsDlgButtonChecked(IDC_PLAYSOUND))
        {
            PlaySnapSound();
        }

        return 0;
    }


    void PlaySnapSound(void)
    {
        TCHAR szSound[MAX_PATH];
        const TCHAR szFileToPlay[] = { TEXT("\\media\\click.wav\0") };
        int nSpaceAllowed = MAX_PATH - NUMELMS(szFileToPlay);
        int nSpaceUsed=0;
        
        nSpaceUsed = GetWindowsDirectory(szSound, nSpaceAllowed);
        if (nSpaceUsed && nSpaceUsed <= nSpaceAllowed)
        {
            _tcscat( szSound, szFileToPlay );
            sndPlaySound(szSound, SND_ASYNC);
        }           
    }
};


//////////////////////////////////////////////////////////////////
//
// This semi-COM object will receive sample callbacks for us
//
//////////////////////////////////////////////////////////////////

CSampleGrabberCB mCB;


/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
    CAboutDlg();

// Dialog Data
    //{{AFX_DATA(CAboutDlg)
    enum { IDD = IDD_ABOUTBOX };
    //}}AFX_DATA

    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CAboutDlg)
    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
    //}}AFX_VIRTUAL

// Implementation
protected:
    //{{AFX_MSG(CAboutDlg)
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
    //{{AFX_DATA_INIT(CAboutDlg)
    //}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CAboutDlg)
    //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
    //{{AFX_MSG_MAP(CAboutDlg)
        // No message handlers
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CStillCapDlg dialog

CStillCapDlg::CStillCapDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CStillCapDlg::IDD, pParent)
{
    //{{AFX_DATA_INIT(CStillCapDlg)
        // NOTE: the ClassWizard will add member initialization here
    //}}AFX_DATA_INIT
    // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CStillCapDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CStillCapDlg)
    DDX_Control(pDX, IDC_STATUS, m_StrStatus);
    DDX_Control(pDX, IDC_STILL, m_StillScreen);
    DDX_Control(pDX, IDC_PREVIEW, m_PreviewScreen);
    //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CStillCapDlg, CDialog)
    //{{AFX_MSG_MAP(CStillCapDlg)
    ON_WM_PAINT()
    ON_WM_SYSCOMMAND()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_SNAP, OnSnap)
    ON_BN_CLICKED(IDC_CAPSTILLS, OnCapstills)
    ON_BN_CLICKED(IDC_CAPVID, OnCapvid)
    ON_BN_CLICKED(IDC_BUTTON_RESET, OnButtonReset)
    ON_BN_CLICKED(IDC_BUTTON_VIEWSTILL, OnButtonViewstill)
    ON_WM_CLOSE()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CStillCapDlg message handlers

void CStillCapDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialog::OnSysCommand(nID, lParam);
    }
}


// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.
void CStillCapDlg::OnPaint() 
{
    if (IsIconic())
    {
        CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialog::OnPaint();

        // Update the bitmap preview window, if we have
        // already captured bitmap data
        mCB.DisplayCapturedBits(cb.pBuffer, &(cb.bih));
    }
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CStillCapDlg::OnQueryDragIcon()
{
    return (HCURSOR) m_hIcon;
}

BOOL CStillCapDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // Add "About..." menu item to system menu.

    // IDM_ABOUTBOX must be in the system command range.
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != NULL)
    {
        CString strAboutMenu;
        strAboutMenu.LoadString(IDS_ABOUTBOX);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }

    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE);         // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon
    
    //
    // StillCap-specific initialization
    //
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

    // Default to this capture directory
    //
    SetDlgItemText( IDC_CAPDIR, TEXT("c:\\\0") );

    // Default to capturing stills
    //
    CheckDlgButton( IDC_CAPSTILLS, 1 );
    m_bCapStills = true;
    m_nCapState = 0;
    m_nCapTimes = 0;
    g_hwnd = GetSafeHwnd();

    // start up the still image capture graph
    //
    HRESULT hr = InitStillGraph( );
    // Don't display an error message box on failure, because InitStillGraph()
    // will already have displayed a specific error message

    // Modify the window style of the capture and still windows
    // to prevent excessive repainting
    m_PreviewScreen.ModifyStyle(0, WS_CLIPCHILDREN);
    m_StillScreen.ModifyStyle(0, WS_CLIPCHILDREN);

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


void CStillCapDlg::ClearGraphs( )
{
    // Destroy capture graph
    if( m_pGraph )
    {
        // have to wait for the graphs to stop first
        //
        CComQIPtr< IMediaControl, &IID_IMediaControl > pControl = m_pGraph;
        if( pControl ) 
            pControl->Stop( );

        // make the window go away before we release graph
        // or we'll leak memory/resources
        // 
        CComQIPtr< IVideoWindow, &IID_IVideoWindow > pWindow = m_pGraph;
        if( pWindow )
        {
            pWindow->put_Visible( OAFALSE );
            pWindow->put_Owner( NULL );
        }

#ifdef REGISTER_FILTERGRAPH
        // Remove filter graph from the running object table   
        if (g_dwGraphRegister)
            RemoveGraphFromRot(g_dwGraphRegister);
#endif

        m_pGraph.Release( );
        m_pGrabber.Release( );
    }

    // Destroy playback graph, if it exists
    if( m_pPlayGraph )
    {
        CComQIPtr< IMediaControl, &IID_IMediaControl > pControl = m_pPlayGraph;
        if( pControl ) 
            pControl->Stop( );

        CComQIPtr< IVideoWindow, &IID_IVideoWindow > pWindow = m_pPlayGraph;
        if( pWindow )
        {
            pWindow->put_Visible( OAFALSE );
            pWindow->put_Owner( NULL );
        }

        m_pPlayGraph.Release( );
    }
}


HRESULT CStillCapDlg::InitStillGraph( )
{
    HRESULT hr;

    // create a filter graph
    //
    hr = m_pGraph.CoCreateInstance( CLSID_FilterGraph );
    if( !m_pGraph )
    {
        Error( TEXT("Could not create filter graph") );
        return E_FAIL;
    }

    // get whatever capture device exists
    //
    CComPtr< IBaseFilter > pCap;
    GetDefaultCapDevice( &pCap );
    if( !pCap )
    {
        Error( TEXT("No video capture device was detected on your system.\r\n\r\n")
               TEXT("This sample requires a functional video capture device, such\r\n")
               TEXT("as a USB web camera.  Video capture will be disabled.") );
        return E_FAIL;
    }

    // add the capture filter to the graph
    //
    hr = m_pGraph->AddFilter( pCap, L"Cap" );
    if( FAILED( hr ) )
    {
        Error( TEXT("Could not put capture device in graph"));
        return E_FAIL;
    }

    // create a sample grabber
    //
    hr = m_pGrabber.CoCreateInstance( CLSID_SampleGrabber );
    if( !m_pGrabber )
    {
        Error( TEXT("Could not create SampleGrabber (is qedit.dll registered?)"));
        return hr;
    }
    CComQIPtr< IBaseFilter, &IID_IBaseFilter > pGrabBase( m_pGrabber );

    // force it to connect to video, 24 bit
    //
    CMediaType VideoType;
    VideoType.SetType( &MEDIATYPE_Video );
    VideoType.SetSubtype( &MEDIASUBTYPE_RGB24 );
    hr = m_pGrabber->SetMediaType( &VideoType ); // shouldn't fail
    if( FAILED( hr ) )
    {
        Error( TEXT("Could not set media type"));
        return hr;
    }

    // add the grabber to the graph
    //
    hr = m_pGraph->AddFilter( pGrabBase, L"Grabber" );
    if( FAILED( hr ) )
    {
        Error( TEXT("Could not put sample grabber in graph"));
        return hr;
    }

    // build the graph
    CComPtr<ICaptureGraphBuilder2> pCGB2;
    hr = pCGB2.CoCreateInstance (CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC);
    if (FAILED( hr ))
    {
        Error(TEXT ("Can't get a ICaptureGraphBuilder2 reference"));
        return hr;
    }

    hr = pCGB2->SetFiltergraph( m_pGraph );
    if (FAILED( hr ))
    {
        Error(TEXT("SetGraph failed"));
        return hr;
    }


    // If there is a VP pin present on the video device, then put the 
    // renderer on CLSID_NullRenderer
    CComPtr<IPin> pVPPin;
    hr = pCGB2->FindPin(
                        pCap, 
                        PINDIR_OUTPUT, 
                        &PIN_CATEGORY_VIDEOPORT, 
                        NULL, 
                        FALSE, 
                        0, 
                        &pVPPin);


    // If there is a VP pin, put the renderer on NULL Renderer
    CComPtr<IBaseFilter> pRenderer;
    if (S_OK == hr)
    {   
        hr = pRenderer.CoCreateInstance(CLSID_NullRenderer);    
        if (S_OK != hr)
        {
            Error(TEXT("Unable to make a NULL renderer"));
            return S_OK;
        }
        hr = m_pGraph->AddFilter(pRenderer, L"NULL renderer");
        if (FAILED (hr))
        {
            Error(TEXT("Can't add the filter to graph"));
            return hr;
        }
    }

    hr = pCGB2->RenderStream(
                            &PIN_CATEGORY_PREVIEW,
                            &MEDIATYPE_Interleaved, 
                            pCap, 
                            pGrabBase, 
                            pRenderer);
    if (FAILED (hr))
    {
        // try to render preview pin
        hr = pCGB2->RenderStream( 
                                &PIN_CATEGORY_PREVIEW, 
                                &MEDIATYPE_Video,
                                pCap, 
                                pGrabBase, 
                                pRenderer);

        // try to render capture pin
        if( FAILED( hr ) )
        {
            hr = pCGB2->RenderStream( 
                                    &PIN_CATEGORY_CAPTURE, 
                                    &MEDIATYPE_Video,
                                    pCap, 
                                    pGrabBase, 
                                    pRenderer);
        }
    }

    if( FAILED( hr ) )
    {
        Error( TEXT("Can't build the graph") );
        return hr;
    }

    // ask for the connection media type so we know how big
    // it is, so we can write out bitmaps
    //
    AM_MEDIA_TYPE mt;
    hr = m_pGrabber->GetConnectedMediaType( &mt );
    if ( FAILED( hr) )
    {
        Error( TEXT("Could not read the connected media type"));
        return hr;
    }
    
    VIDEOINFOHEADER * vih = (VIDEOINFOHEADER*) mt.pbFormat;
    mCB.pOwner = this;
    mCB.lWidth  = vih->bmiHeader.biWidth;
    mCB.lHeight = vih->bmiHeader.biHeight;
    FreeMediaType( mt );

    // don't buffer the samples as they pass through
    //
    hr = m_pGrabber->SetBufferSamples( FALSE );

    // only grab one at a time, stop stream after
    // grabbing one sample
    //
    hr = m_pGrabber->SetOneShot( FALSE );

    // set the callback, so we can grab the one sample
    //
    hr = m_pGrabber->SetCallback( &mCB, 1 );

    // find the video window and stuff it in our window
    //
    CComQIPtr< IVideoWindow, &IID_IVideoWindow > pWindow = m_pGraph;
    if( !pWindow )
    {
        Error( TEXT("Could not get video window interface"));
        return E_FAIL;
    }

    // set up the preview window to be in our dialog
    // instead of floating popup
    //
    HWND hwndPreview = NULL;
    GetDlgItem( IDC_PREVIEW, &hwndPreview );
    RECT rc;
    ::GetWindowRect( hwndPreview, &rc );

    hr = pWindow->put_Owner( (OAHWND) hwndPreview );
    hr = pWindow->put_Left( 0 );
    hr = pWindow->put_Top( 0 );
    hr = pWindow->put_Width( rc.right - rc.left );
    hr = pWindow->put_Height( rc.bottom - rc.top );
    hr = pWindow->put_WindowStyle( WS_CHILD | WS_CLIPSIBLINGS );
    hr = pWindow->put_Visible( OATRUE );
    
    // Add our graph to the running object table, which will allow
    // the GraphEdit application to "spy" on our graph
#ifdef REGISTER_FILTERGRAPH
    hr = AddGraphToRot(m_pGraph, &g_dwGraphRegister);
    if (FAILED(hr))
    {
        Error(TEXT("Failed to register filter graph with ROT!"));
        g_dwGraphRegister = 0;
    }
#endif

    // run the graph
    //
    CComQIPtr< IMediaControl, &IID_IMediaControl > pControl = m_pGraph;
    hr = pControl->Run( );
    if( FAILED( hr ) )
    {
        Error( TEXT("Could not run graph"));
        return hr;
    }

    UpdateStatus(_T("Previewing Live Video"));
    return 0;
}


HRESULT CStillCapDlg::InitCaptureGraph( TCHAR * pFilename )
{
    HRESULT hr;

    // make a filter graph
    //
    m_pGraph.CoCreateInstance( CLSID_FilterGraph );
    if( !m_pGraph )
    {
        Error(TEXT("Could not create filter graph"));
        return E_FAIL;
    }

    // get whatever capture device exists
    //
    CComPtr< IBaseFilter > pCap;
    GetDefaultCapDevice( &pCap );
    if( !pCap )
    {
        Error( TEXT("No video capture device was detected on your system.\r\n\r\n")
               TEXT("This sample requires a functional video capture device, such\r\n")
               TEXT("as a USB web camera.  Video capture will be disabled.") );
        return E_FAIL;
    }

    // add the capture filter to the graph
    //
    hr = m_pGraph->AddFilter( pCap, L"Cap" );
    if( FAILED( hr ) )
    {
        Error( TEXT("Could not put capture device in graph"));
        return hr;
    }

    // make a capture builder graph (for connecting help)
    //
    CComPtr< ICaptureGraphBuilder2 > pBuilder;
    hr = pBuilder.CoCreateInstance( CLSID_CaptureGraphBuilder2 );
    if( !pBuilder )
    {
        Error( TEXT("Could not create capture graph builder2"));
        return hr;
    }

    hr = pBuilder->SetFiltergraph( m_pGraph );
    if( FAILED( hr ) )
    {
        Error( TEXT("Could not set filtergraph on capture graph builder2"));
        return hr;
    }

    CComPtr< IBaseFilter > pMux;
    CComPtr< IFileSinkFilter > pSink;
    USES_CONVERSION;

    hr = pBuilder->SetOutputFileName( &MEDIASUBTYPE_Avi,  T2W( pFilename ), 
                                      &pMux, &pSink );
    if( FAILED( hr ) )
    {
        Error( TEXT("Could not create/hookup mux and writer"));
        return hr;
    }

    hr = pBuilder->RenderStream( &PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video,
                                 pCap, NULL,pMux );
    if( FAILED( hr ) )
    {
        Error( TEXT("Could not connect capture pin"));
        return hr;
    }

    hr = pBuilder->RenderStream( &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,
                                 pCap, NULL, NULL );
    if( FAILED( hr ) )
    {
        Error( TEXT("Could not render capture pin"));
        return hr;
    }
    if( hr == VFW_S_NOPREVIEWPIN )
    {
        // preview was faked up using the capture pin, so we can't
        // turn capture on and off at will.
        hr = 0;
    }

    // find the video window and stuff it in our window
    //
    CComQIPtr< IVideoWindow, &IID_IVideoWindow > pWindow = m_pGraph;
    if( !pWindow )
    {
        Error( TEXT("Could not get video window interface"));
        return hr;
    }

    // set up the preview window to be in our dialog
    // instead of floating popup
    //
    HWND hwndPreview = NULL;
    GetDlgItem( IDC_PREVIEW, &hwndPreview );
    RECT rc;
    ::GetWindowRect( hwndPreview, &rc );

    hr = pWindow->put_Owner( (OAHWND) hwndPreview );
    hr = pWindow->put_Left( 0 );
    hr = pWindow->put_Top( 0 );
    hr = pWindow->put_Width( rc.right - rc.left );
    hr = pWindow->put_Height( rc.bottom - rc.top );
    hr = pWindow->put_Visible( OATRUE );
    hr = pWindow->put_WindowStyle( WS_CHILD | WS_CLIPSIBLINGS );
    
    // run the graph
    //
    CComQIPtr< IMediaControl, &IID_IMediaControl > pControl = m_pGraph;
    hr = pControl->Run( );
    if( FAILED( hr ) )
    {
        Error( TEXT("Could not run graph"));
        return hr;
    }

    UpdateStatus(_T("Capturing Video To Disk"));
    return 0;
}


HRESULT CStillCapDlg::InitPlaybackGraph( TCHAR * pFilename )
{
    USES_CONVERSION;

    m_pPlayGraph.CoCreateInstance( CLSID_FilterGraph );

    HRESULT hr = m_pPlayGraph->RenderFile( T2W( pFilename ), NULL );
    if (FAILED(hr))
        return hr;

    // find the video window and stuff it in our window
    //
    CComQIPtr< IVideoWindow, &IID_IVideoWindow > pWindow = m_pPlayGraph;
    if( !pWindow )
    {
        Error( TEXT("Could not get video window interface"));
        return E_FAIL;
    }

    // set up the preview window to be in our dialog
    // instead of floating popup
    //
    HWND hwndPreview = NULL;
    GetDlgItem( IDC_STILL, &hwndPreview );
    RECT rc;
    ::GetWindowRect( hwndPreview, &rc );

    hr = pWindow->put_Owner( (OAHWND) hwndPreview );
    hr = pWindow->put_Left( 0 );
    hr = pWindow->put_Top( 0 );
    hr = pWindow->put_Width( rc.right - rc.left );
    hr = pWindow->put_Height( rc.bottom - rc.top );
    hr = pWindow->put_Visible( OATRUE );
    hr = pWindow->put_WindowStyle( WS_CHILD | WS_CLIPSIBLINGS );

    CComQIPtr< IMediaControl, &IID_IMediaControl > pControl;
    pControl = m_pPlayGraph;

    // Play back the recorded video
    hr = pControl->Run( );
    UpdateStatus(_T("Playing Back Recorded Video"));
    return hr;
}


void CStillCapDlg::GetDefaultCapDevice( IBaseFilter ** ppCap )
{
    HRESULT hr;

    ASSERT(ppCap);
    if (!ppCap)
        return;

    *ppCap = NULL;

    // create an enumerator
    //
    CComPtr< ICreateDevEnum > pCreateDevEnum;
    pCreateDevEnum.CoCreateInstance( CLSID_SystemDeviceEnum );

    ASSERT(pCreateDevEnum);
    if( !pCreateDevEnum )
        return;

    // enumerate video capture devices
    //
    CComPtr< IEnumMoniker > pEm;
    pCreateDevEnum->CreateClassEnumerator( CLSID_VideoInputDeviceCategory, &pEm, 0 );

    ASSERT(pEm);
    if( !pEm )
        return;

    pEm->Reset( );
 
    // go through and find first video capture device
    //
    while( 1 )
    {
        ULONG ulFetched = 0;
        CComPtr< IMoniker > pM;

        hr = pEm->Next( 1, &pM, &ulFetched );
        if( hr != S_OK )
            break;

        // get the property bag interface from the moniker
        //
        CComPtr< IPropertyBag > pBag;
        hr = pM->BindToStorage( 0, 0, IID_IPropertyBag, (void**) &pBag );
        if( hr != S_OK )
            continue;

        // ask for the english-readable name
        //
        CComVariant var;
        var.vt = VT_BSTR;
        hr = pBag->Read( L"FriendlyName", &var, NULL );
        if( hr != S_OK )
            continue;

        // set it in our UI
        //
        USES_CONVERSION;
        SetDlgItemText( IDC_CAPOBJ, W2T( var.bstrVal ) );

        // ask for the actual filter
        //
        hr = pM->BindToObject( 0, 0, IID_IBaseFilter, (void**) ppCap );
        if( *ppCap )
            break;
    }

    return;
}


void CStillCapDlg::OnSnap() 
{
    CString CapDir;
    GetDlgItemText( IDC_CAPDIR, CapDir );

    // Determine AVI filename
    TCHAR szFilename[MAX_PATH], szFile[MAX_PATH];
    wsprintf( szFilename, TEXT("%sStillCap%04d.avi\0"), (LPCTSTR) CapDir, m_nCapTimes );
    wsprintf( szFile, TEXT("StillCap%04d.avi\0"), m_nCapTimes );

    // Snap a still picture?
    if( m_bCapStills )
    {
        _tcsncpy( mCB.m_szCapDir, CapDir, NUMELMS(mCB.m_szCapDir) );
        g_bOneShot = TRUE;
    }

    // Start capturing video
    else
    {
        if( m_nCapState == 0 )
        {
            if( IsDlgButtonChecked( IDC_AUTOBUMP ) )
                m_nCapTimes++;
        }

        // start capturing, show playing button
        //
        if( m_nCapState == 0 )
        {
            ClearGraphs( );
            InitCaptureGraph( szFilename );
            SetDlgItemText( IDC_SNAP, TEXT("&Start Playback"));
            m_nCapState = 1;
        }
        else if( m_nCapState == 1 )
        {
            // show us where it captured to
            //
            SetDlgItemText( IDC_SNAPNAME, szFile );

            ClearGraphs( );
            InitPlaybackGraph( szFilename );
            SetDlgItemText( IDC_SNAP, TEXT("&Start Capture"));
            m_nCapState = 0;
        }
    }
}


BOOL CStillCapDlg::DestroyWindow() 
{
    ClearGraphs( );

    return CDialog::DestroyWindow();
}


void CStillCapDlg::Error( TCHAR * pText )
{
    GetDlgItem( IDC_SNAP )->EnableWindow( FALSE );
    ::MessageBox( NULL, pText, TEXT("Error!"), MB_OK | MB_TASKMODAL | MB_SETFOREGROUND );
}


void CStillCapDlg::OnCapstills() 
{
    if( m_bCapStills )
        return;

    SetDlgItemText( IDC_SNAP, TEXT("&Snap Still"));
    m_bCapStills = true;

    ClearGraphs( );
    InitStillGraph( );

    // Update the bitmap preview window, if we have
    // already captured bitmap data
    mCB.DisplayCapturedBits(cb.pBuffer, &(cb.bih));
}


void CStillCapDlg::OnCapvid() 
{
    if( !m_bCapStills )
        return;

    ClearGraphs( );
    m_bCapStills = false;
    m_nCapState = 0;

    // use OnSnap to set the UI state and the graphs
    //
    OnSnap( );
}


void CStillCapDlg::OnButtonReset() 
{
    // Reset bitmap counter to reset at zero
    m_nCapTimes = 0;
}


void CStillCapDlg::OnButtonViewstill() 
{
    // Open the bitmap with the system-default application
    ShellExecute(this->GetSafeHwnd(), TEXT("open\0"), mCB.m_szSnappedName, 
                 NULL, NULL, SW_SHOWNORMAL);
}


void CStillCapDlg::UpdateStatus(TCHAR *szStatus)
{
    m_StrStatus.SetWindowText(szStatus);
}


LRESULT CStillCapDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{
    // Field the message posted by our SampleGrabber callback function.
    if (message == WM_CAPTURE_BITMAP)
        mCB.CopyBitmap(cb.dblSampleTime, cb.pBuffer, cb.lBufferSize);        
    
    return CDialog::WindowProc(message, wParam, lParam);
}


void CStillCapDlg::OnClose() 
{
    // Free the memory allocated for our bitmap data buffer
    if (cb.pBuffer != 0)
    {
        delete cb.pBuffer;
        cb.pBuffer = 0;
    }
        
    CDialog::OnClose();
}
