//------------------------------------------------------------------------------
// File: StillViewDlg.cpp
//
// Desc: DirectShow sample code - implementation of CStillViewDlg class.
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------

#include "stdafx.h"
#include <atlbase.h>
#include "StillView.h"
#include "StillViewDlg.h"
#include "mediatypes.h"

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

/////////////////////////////////////////////////////////////////////////////
// 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()

/////////////////////////////////////////////////////////////////////////////
// CStillViewDlg dialog

CStillViewDlg::CStillViewDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CStillViewDlg::IDD, pParent), m_pGB(0), m_pMC(0), m_pBV(0), m_pVW(0), m_pME(0)
{
    //{{AFX_DATA_INIT(CStillViewDlg)
    //}}AFX_DATA_INIT
    // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CStillViewDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CStillViewDlg)
    DDX_Control(pDX, IDC_STATIC_FILETIME, m_StrFileTime);
    DDX_Control(pDX, IDC_STATIC_IMAGESIZE, m_StrImageSize);
    DDX_Control(pDX, IDC_EDIT_MEDIADIR, m_EditMediaDir);
    DDX_Control(pDX, IDC_SPIN_FILES, m_SpinFiles);
    DDX_Control(pDX, IDC_BUTTON_PROPPAGE, m_ButtonProperties);
    DDX_Control(pDX, IDC_STATUS_DIRECTORY, m_StrMediaPath);
    DDX_Control(pDX, IDC_STATIC_FILEDATE, m_StrFileDate);
    DDX_Control(pDX, IDC_STATIC_FILESIZE, m_StrFileSize);
    DDX_Control(pDX, IDC_LIST_PINS_OUTPUT, m_ListPinsOutput);
    DDX_Control(pDX, IDC_LIST_PINS_INPUT, m_ListPinsInput);
    DDX_Control(pDX, IDC_STATIC_FILELIST, m_StrFileList);
    DDX_Control(pDX, IDC_MOVIE_SCREEN, m_Screen);
    DDX_Control(pDX, IDC_LIST_FILTERS, m_ListFilters);
    DDX_Control(pDX, IDC_LIST_FILES, m_ListFiles);
    //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CStillViewDlg, CDialog)
    //{{AFX_MSG_MAP(CStillViewDlg)
    ON_WM_ERASEBKGND()
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_WM_CLOSE()
    ON_WM_DESTROY()
    ON_LBN_SELCHANGE(IDC_LIST_FILES, OnSelectFile)
    ON_LBN_SELCHANGE(IDC_LIST_FILTERS, OnSelchangeListFilters)
    ON_LBN_DBLCLK(IDC_LIST_FILTERS, OnDblclkListFilters)
    ON_BN_CLICKED(IDC_BUTTON_PROPPAGE, OnButtonProppage)
    ON_NOTIFY(UDN_DELTAPOS, IDC_SPIN_FILES, OnDeltaposSpinFiles)
    ON_BN_CLICKED(IDC_BUTTON_SET_MEDIADIR, OnButtonSetMediadir)
    ON_BN_CLICKED(IDC_BUTTON_GRAPHEDIT, OnButtonGraphedit)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CStillViewDlg message handlers

void CStillViewDlg::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 CStillViewDlg::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();
    }
}

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


/////////////////////////////////////////////////////////////////////////////
// CStillViewDlg DirectShow code and message handlers


BOOL CStillViewDlg::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
    
    ////////////////////////////////////////////////////////////////////////
    //
    //  DirectShow-specific initialization code

    // Initialize COM
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    
    // Initialize DirectShow and query for needed interfaces
    HRESULT hr = InitDirectShow();
    if(FAILED(hr))
    {
        RetailOutput(TEXT("Failed to initialize DirectShow!  hr=0x%x\r\n"), hr);
        return FALSE;
    }

    // Since we're embedding video in a child window of a dialog,
    // we must set the WS_CLIPCHILDREN style to prevent the bounding
    // rectangle from drawing over our video frames.
    m_Screen.ModifyStyle(0, WS_CLIPCHILDREN);

    // Propagate the files list and select the first item
    InitMediaDirectory();
   
    return TRUE;  // return TRUE  unless you set the focus to a control
}


void CStillViewDlg::InitMediaDirectory(void)
{
    TCHAR szDir[MAX_PATH]={0};
    LONG lResult=0;

    // Fill the media file list, starting with the directory passed
    // on the command line.  If no directory is passed, then read the
    // default media path for the DirectX SDK.
    if (theApp.m_lpCmdLine[0] == L'\0')
    {
        // If the media path is found, it will be returned in szDir
        lResult = GetDXMediaPath(szDir, sizeof(szDir));

        // If the DirectX SDK is not installed, use the Windows media
        // directory instead.
        if (lResult != 0)
        {
            if (GetWindowsDirectory(szDir, MAX_PATH))
            {
                if ((_tcslen(szDir) + STR_MEDIA_FOLDER_SIZE) < MAX_PATH)
                    _tcscat(szDir, STR_MEDIA_FOLDER );
            }
            else
            {
                szDir[0] = TEXT('\\');  // Default to root directory
                szDir[1] = TEXT('\0');
            }
        }
    }
    else
    {
        _tcsncpy(szDir, theApp.m_lpCmdLine, NUMELMS(szDir));
        szDir[MAX_PATH-1] = 0;      // Ensure null-termination
    }

    TCHAR szPathMsg[MAX_PATH + 64];
    wsprintf(szPathMsg, TEXT("Media directory: %s\0"), szDir);
    m_StrMediaPath.SetWindowText(szPathMsg);

    // Save current directory name
    wsprintf(m_szCurrentDir, TEXT("%s\0"), szDir);

    m_EditMediaDir.SetLimitText(MAX_PATH);
    m_EditMediaDir.SetWindowText(szDir);

    // Find all files in this directory and fill the list box
    FillFileList(szDir);
}


LONG CStillViewDlg::GetDXMediaPath(TCHAR *szPath, DWORD dwPathSize)
{
    HKEY  hKey=0;
    DWORD dwType=0, dwValueSize=dwPathSize;

    // Open the appropriate registry key
    LONG lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                                _T("Software\\Microsoft\\DirectX SDK"),
                                0, KEY_READ, &hKey );
    if( ERROR_SUCCESS != lResult )
        return -1;

    // Start with DirectX9 SDK
    lResult = RegQueryValueEx( hKey, _T("DX9SDK Samples Path"), NULL,
                              &dwType, (BYTE*)szPath, &dwValueSize );

    if( ERROR_SUCCESS != lResult )
    {
        // Next try DirectX 8.1 SDK
        lResult = RegQueryValueEx( hKey, _T("DX81SDK Samples Path"), NULL,
                                  &dwType, (BYTE*)szPath, &dwValueSize );
        if( ERROR_SUCCESS != lResult )
        {
            // Finally, try DirectX 8.0 SDK
            lResult = RegQueryValueEx( hKey, _T("DX8SDK Samples Path"), NULL,
                                      &dwType, (BYTE*)szPath, &dwValueSize );
            if( ERROR_SUCCESS != lResult )
            {
                RegCloseKey( hKey );
                return -1;
            }
        }
    }

    RegCloseKey( hKey );

    // Now that we have the location of the installed SDK samples path,
    // append the name of the folder where media files are stored.
    // Make sure that the szPath string has enough space to hold the
    // full SDK samples path plus the "\Media\" string.
    if ((_tcslen(szPath) + STR_MEDIA_FOLDER_SIZE) < dwPathSize )
    {
        _tcscat( szPath, STR_MEDIA_FOLDER );
        return 0;
    }
    else
        return -1;
}


void CStillViewDlg::OnButtonSetMediadir() 
{
    TCHAR szEditPath[MAX_PATH];
    DWORD dwAttr;

    // Read the string in the media directory edit box.
    m_EditMediaDir.GetWindowText(szEditPath, MAX_PATH);

    // Is this a valid directory name?
    dwAttr = GetFileAttributes(szEditPath);
    if ((dwAttr == (DWORD) -1) || (! (dwAttr & FILE_ATTRIBUTE_DIRECTORY)))
    {
        MessageBox(TEXT("Please enter a valid directory name."), TEXT("Media error"));
        return;
    }

    // User has specified a valid media directory.  
    // Update the current path string.
    TCHAR szPathMsg[MAX_PATH];
    wsprintf(szPathMsg, TEXT("Media directory: %s\0"), szEditPath);
    m_StrMediaPath.SetWindowText(szPathMsg);

    // Save current directory name.  Append the trailing '\' to match
    // the string created by GetDXSDKMediaPath() (if not present)
    int nLength = (int) _tcslen(szEditPath);
    if (szEditPath[nLength - 1] != TEXT('\\'))
        wsprintf(m_szCurrentDir, TEXT("%s\\\0"), szEditPath);

    // Propagate the files list and select the first item
    FillFileList(szEditPath);
}


void CStillViewDlg::FillFileList(LPTSTR pszRootDir)
{
    UINT attr = 0;

    // Clear current file list
    m_ListFiles.ResetContent();

    // Clear filter/pin/event information listboxes
    m_ListFilters.ResetContent();
    m_ListPinsInput.ResetContent();
    m_ListPinsOutput.ResetContent();

    ::SetCurrentDirectory(pszRootDir);

    // Add all of our known supported media types to the file list.
    // Add files of each type in order.
    for (int i=0; i < NUM_MEDIA_TYPES; i++)
    {
        m_ListFiles.Dir(attr, TypeInfo[i].pszType);
    }

    // Update list box title with number of items added
    int nItems  = m_ListFiles.GetCount();
    TCHAR szTitle[64];
    wsprintf(szTitle, TEXT("Media files (%d found)\0"), nItems);
    m_StrFileList.SetWindowText(szTitle);

    // If there are no messages, inform the user
    if (nItems == 0)
        MessageBox(TEXT("This is a valid directory, but it does not contain any ")
                   TEXT("media files expected by this application (BMP, GIF, JPG, TGA)."),
                   TEXT("No still image files!"));
    
    // Automatically select the first file in the list once
    // the dialog is displayed.
    PostMessage(WM_FIRSTFILE, 0, 0L);
    m_nCurrentFileSelection = -1;     // No selection yet
}


void CStillViewDlg::OnClose() 
{
    // Release DirectShow interfaces
    FreeDirectShow();

    // Release COM
    CoUninitialize();

    CDialog::OnClose();
}


void CStillViewDlg::OnDestroy() 
{
    FreeDirectShow();   

    CDialog::OnDestroy();   
}


void CStillViewDlg::OnSelectFile() 
{
    HRESULT hr;
    TCHAR szFilename[MAX_PATH];

    // If this is the currently selected file, do nothing
    int nItem = m_ListFiles.GetCurSel();
    if (nItem == m_nCurrentFileSelection)
        return;

    // Remember the current selection to speed double-click processing
    m_nCurrentFileSelection = nItem;

    // Read file name from list box
    m_ListFiles.GetText(nItem, szFilename);

    // First release any existing interfaces
    ResetDirectShow();

    // Clear filter/pin/event information listboxes
    m_ListFilters.ResetContent();
    m_ListPinsInput.ResetContent();
    m_ListPinsOutput.ResetContent();

    // Load the selected media file
    hr = PrepareMedia(szFilename);
    if (FAILED(hr))
    {
        MessageBeep(0);
        return;
    }

    // Display useful information about this file
    DisplayFileInfo(szFilename);
    DisplayImageInfo();

    // Enumerate and display filters in graph
    hr = EnumFilters();

    // Select the first filter in the list to display pin info
    m_ListFilters.SetCurSel(0);
    OnSelchangeListFilters();
}


HRESULT CStillViewDlg::PrepareMedia(LPTSTR lpszMovie)
{
    USES_CONVERSION;
    HRESULT hr = S_OK;

    // Allow DirectShow to create the FilterGraph for this media file
    hr = m_pGB->RenderFile(T2W(lpszMovie), NULL);
    if (FAILED(hr)) {
        RetailOutput(TEXT("*** Failed(%08lx) in RenderFile(%s)!\r\n"),
                 hr, lpszMovie);
        return hr;
    }

    // Set the message drain of the video window to point to our main
    // application window.
    hr = m_pVW->put_MessageDrain((OAHWND) m_hWnd);

    // Have the graph signal event via window callbacks
    hr = m_pME->SetNotifyWindow((OAHWND)m_hWnd, WM_GRAPHNOTIFY, 0);

    // Place video window within the bounding rectangle
    CenterVideo();

    // Finish configuring video window
    hr = m_pVW->put_WindowStyle(WS_CHILD);
    hr = m_pVW->put_Owner((OAHWND) m_Screen.GetSafeHwnd());
    hr = m_pVW->SetWindowForeground(-1);

    // Display the media file's first frame, which is the only frame
    // for a still image.
    hr = m_pMC->Pause();

    return hr;
}


HRESULT CStillViewDlg::InitDirectShow(void)
{
    HRESULT hr = S_OK;

    // Zero interfaces (sanity check)
    m_pVW = NULL;
    m_pBV = NULL;

    JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, 
                        (void **)&m_pGB));
    JIF(m_pGB->QueryInterface(IID_IMediaControl,  (void **)&m_pMC));
    JIF(m_pGB->QueryInterface(IID_IBasicVideo,    (void **)&m_pBV));
    JIF(m_pGB->QueryInterface(IID_IVideoWindow,   (void **)&m_pVW));
    JIF(m_pGB->QueryInterface(IID_IMediaEventEx,  (void **)&m_pME));

    return S_OK;

CLEANUP:
    FreeDirectShow();
    return(hr);
}


HRESULT CStillViewDlg::FreeDirectShow(void)
{
    HRESULT hr=S_OK;

    // Hide video window and remove owner.  This is not necessary here,
    // since we are about to destroy the filter graph, but it is included
    // for demonstration purposes.  Remember to hide the video window and
    // clear its owner when destroying a window that plays video.
    if(m_pVW)
    {
        hr = m_pVW->put_Visible(OAFALSE);
        hr = m_pVW->put_Owner(NULL);
    }

    SAFE_RELEASE(m_pMC);
    SAFE_RELEASE(m_pVW);
    SAFE_RELEASE(m_pBV);
    SAFE_RELEASE(m_pME);
    SAFE_RELEASE(m_pGB);

    return hr;
}


void CStillViewDlg::ResetDirectShow(void)
{
    // Destroy the current filter graph its filters.
    FreeDirectShow();

    // Reinitialize graph builder and query for interfaces
    InitDirectShow();
}


void CStillViewDlg::CenterVideo(void)
{
    LONG width, height;
    HRESULT hr;

    if (!m_pVW)
        return;

    // Read coordinates of video container window
    RECT rc;
    m_Screen.GetClientRect(&rc);
    width =  rc.right - rc.left;
    height = rc.bottom - rc.top;

    // Ignore the video's original size and stretch to fit bounding rectangle
    hr = m_pVW->SetWindowPosition(rc.left, rc.top, width, height);
    if (FAILED(hr))
    {
        RetailOutput(TEXT("Failed to set window position!  hr=0x%x\r\n"), hr);
        return;
    }
}


LRESULT CStillViewDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{
    // Field notifications from the DirectShow filter graph manager
    // and those posted by the application
    switch (message)
    {
        case WM_GRAPHNOTIFY:
            HandleGraphEvent();
            break;

        case WM_PLAYFILE:
            PlaySelectedFile();
            break;

        case WM_NEXTFILE:
            PlayNextFile();
            break;

        case WM_PREVIOUSFILE:
            PlayPreviousFile();
            break;

        case WM_FIRSTFILE:
            // Select the first item in the list
            m_ListFiles.SetCurSel(0);
            OnSelectFile();
            break;
    }

    // Pass along this message to the video window, which exists as a child
    // of the m_Screen window.  This method should be used by windows that 
    // make a renderer window a child window. It forwards significant messages 
    // to the child window that the child window would not otherwise receive. 
    if (m_pVW)
    {
        m_pVW->NotifyOwnerMessage((LONG_PTR) m_hWnd, message, wParam, lParam);
    }

    return CDialog::WindowProc(message, wParam, lParam);
}


HRESULT CStillViewDlg::HandleGraphEvent(void)
{
    LONG evCode, evParam1, evParam2;
    HRESULT hr=S_OK;

    while(SUCCEEDED(m_pME->GetEvent(&evCode, (LONG_PTR *) &evParam1, 
                                 (LONG_PTR *) &evParam2, 0)))
    {
        // Free memory associated with this event
        hr = m_pME->FreeEventParams(evCode, evParam1, evParam2);
    }

    return hr;
}


void CStillViewDlg::PlaySelectedFile() 
{
    OnSelectFile();
}


void CStillViewDlg::OnDeltaposSpinFiles(NMHDR* pNMHDR, LRESULT* pResult) 
{
    NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;

    if (pNMUpDown->iDelta > 0)
        PostMessage(WM_NEXTFILE, 0, 0L);
    else
        PostMessage(WM_PREVIOUSFILE, 0, 0L);

    *pResult = 0;
}


void CStillViewDlg::PlayNextFile(void)
{
    int nItems  = m_ListFiles.GetCount();
    
    // Return if the list is empty
    if (!nItems)
        return;
        
    int nCurSel = m_ListFiles.GetCurSel();
    int nNewSel = (nCurSel + 1) % nItems;

    // Select the next item in the list, wrapping to top if needed
    m_ListFiles.SetCurSel(nNewSel);
    OnSelectFile();
}


void CStillViewDlg::PlayPreviousFile(void)
{
    int nItems  = m_ListFiles.GetCount();

    // Return if the list is empty
    if (!nItems)
        return;
        
    int nCurSel = m_ListFiles.GetCurSel();
    int nNewSel = nCurSel - 1;

    // If moved off top of list, select last item in list
    if (nNewSel < 0)
        nNewSel = nItems - 1;

    // Select the next item in the list, wrapping to top if needed
    m_ListFiles.SetCurSel(nNewSel);
    OnSelectFile();
}


HRESULT CStillViewDlg::EnumFilters (void) 
{
    USES_CONVERSION;

    HRESULT hr;
    IEnumFilters *pEnum = NULL;
    IBaseFilter *pFilter = NULL;
    ULONG cFetched;

    // Clear filters list box
    m_ListFilters.ResetContent();
    
    // Get filter enumerator
    hr = m_pGB->EnumFilters(&pEnum);
    if (FAILED(hr))
    {
        m_ListFilters.AddString(TEXT("<ERROR>"));
        return hr;
    }

    // Enumerate all filters in the graph
    while(pEnum->Next(1, &pFilter, &cFetched) == S_OK)
    {
        FILTER_INFO FilterInfo;
        TCHAR szName[256];
        
        hr = pFilter->QueryFilterInfo(&FilterInfo);
        if (FAILED(hr))
        {
            m_ListFilters.AddString(TEXT("<ERROR>"));
        }
        else
        {
            // Add the filter name to the filters listbox
            lstrcpy(szName, W2T(FilterInfo.achName));
            m_ListFilters.AddString(szName);

            FilterInfo.pGraph->Release();
        }       
        pFilter->Release();
    }
    pEnum->Release();

    return hr;
}


//
// The GraphBuilder interface provides a FindFilterByName() method,
// which provides similar functionality to the method below.
// This local method is provided for educational purposes.
//
IBaseFilter *CStillViewDlg::FindFilterFromName(LPTSTR szNameToFind)
{
    USES_CONVERSION;

    HRESULT hr;
    IEnumFilters *pEnum = NULL;
    IBaseFilter *pFilter = NULL;
    ULONG cFetched;
    BOOL bFound = FALSE;

    // Get filter enumerator
    hr = m_pGB->EnumFilters(&pEnum);
    if (FAILED(hr))
        return NULL;

    // Enumerate all filters in the graph
    while((pEnum->Next(1, &pFilter, &cFetched) == S_OK) && (!bFound))
    {
        FILTER_INFO FilterInfo;
        TCHAR szName[256];
        
        hr = pFilter->QueryFilterInfo(&FilterInfo);
        if (FAILED(hr))
        {
            pFilter->Release();
            pEnum->Release();
            return NULL;
        }

        // Compare this filter's name with the one we want
        lstrcpy(szName, W2T(FilterInfo.achName));
        if (! lstrcmp(szName, szNameToFind))
        {
            bFound = TRUE;
        }

        FilterInfo.pGraph->Release();

        // If we found the right filter, don't release its interface.
        // The caller will use it and release it later.
        if (!bFound)
            pFilter->Release();
        else
            break;
    }
    pEnum->Release();

    return (bFound ? pFilter : NULL);
}


HRESULT CStillViewDlg::EnumPins(IBaseFilter *pFilter, PIN_DIRECTION PinDir,
                              CListBox& Listbox)
{
    HRESULT hr;
    IEnumPins  *pEnum = NULL;
    IPin *pPin = NULL;

    // Clear the specified listbox (input or output)
    Listbox.ResetContent();

    // Get pin enumerator
    hr = pFilter->EnumPins(&pEnum);
    if (FAILED(hr))
    {
        Listbox.AddString(TEXT("<ERROR>"));
        return hr;
    }

    // Enumerate all pins on this filter
    while(pEnum->Next(1, &pPin, 0) == S_OK)
    {
        PIN_DIRECTION PinDirThis;

        hr = pPin->QueryDirection(&PinDirThis);
        if (FAILED(hr))
        {
            Listbox.AddString(TEXT("<ERROR>"));
            pPin->Release();
            continue;
        }

        // Does the pin's direction match the requested direction?
        if (PinDir == PinDirThis)
        {
            PIN_INFO pininfo={0};

            // Direction matches, so add pin name to listbox
            hr = pPin->QueryPinInfo(&pininfo);
            if (SUCCEEDED(hr))
            {
                CString str(pininfo.achName);
                Listbox.AddString(str);
            }

            // The pininfo structure contains a reference to an IBaseFilter,
            // so you must release its reference to prevent resource a leak.
            pininfo.pFilter->Release();
        }
        pPin->Release();
    }
    pEnum->Release();

    return hr;
}


void CStillViewDlg::OnSelchangeListFilters() 
{
    HRESULT hr;
    IBaseFilter *pFilter = NULL;
    TCHAR szNameToFind[128];

    // Read the current filter name from the list box
    int nCurSel = m_ListFilters.GetCurSel();
    m_ListFilters.GetText(nCurSel, szNameToFind);

    // Read the current list box name and find it in the graph
    pFilter = FindFilterFromName(szNameToFind);
    if (!pFilter)
        return;

    // Now that we have filter information, enumerate pins by direction
    // and add their names to the appropriate listboxes
    hr = EnumPins(pFilter, PINDIR_INPUT,  m_ListPinsInput);
    hr = EnumPins(pFilter, PINDIR_OUTPUT, m_ListPinsOutput);

    // Find out if this filter supports a property page
    if (SupportsPropertyPage(pFilter))
        m_ButtonProperties.EnableWindow(TRUE);
    else
        m_ButtonProperties.EnableWindow(FALSE);
    
    // Must release the filter interface returned from FindFilterByName()
    pFilter->Release();
}


BOOL CStillViewDlg::DisplayImageInfo(void)
{
    HRESULT hr;
    long lWidth, lHeight;
    
    if (!m_pBV)
        return FALSE;

    hr = m_pBV->GetVideoSize(&lWidth, &lHeight);
    if (SUCCEEDED(hr))
    {
        TCHAR szSize[64];
        wsprintf(szSize, TEXT("Image size: %d x %d\0"), lWidth, lHeight);
        m_StrImageSize.SetWindowText(szSize);
    }

    return TRUE;
}


BOOL CStillViewDlg::DisplayFileInfo(LPTSTR szFile)
{
    HANDLE hFile;
    LONGLONG llSize=0;
    DWORD dwSizeLow=0, dwSizeHigh=0;
    TCHAR szScrap[64];

    // Open the specified file to read size and date information
    hFile = CreateFile(szFile,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,
                       (DWORD) 0, NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        RetailOutput(TEXT("*** Failed(0x%x) to open file (to read size)!\r\n"),
                     GetLastError());
        return FALSE;
    }

    dwSizeLow = GetFileSize(hFile, &dwSizeHigh);
    if ((dwSizeLow == 0xFFFFFFFF) && (GetLastError() != NO_ERROR))
    {
        RetailOutput(TEXT("*** Error(0x%x) reading file size!\r\n"),
                     GetLastError());
        CloseHandle(hFile);
        return FALSE;
    }

    // Large files will also use the upper DWORD to report size.
    // Add them together for the true size if necessary.
    if (dwSizeHigh)
        llSize = (dwSizeHigh << 16) + dwSizeLow;
    else
        llSize = dwSizeLow;

    // Read date information
    BY_HANDLE_FILE_INFORMATION fi;
    if (GetFileInformationByHandle(hFile, &fi))
    {
        CTime time(fi.ftLastWriteTime);

        wsprintf(szScrap, TEXT("Date: %02d/%02d/%d\0"), 
                 time.GetMonth(), time.GetDay(), time.GetYear());
        m_StrFileDate.SetWindowText(szScrap);

        // Set time.  Note that 12am-12:59am will show as "00" instead of 12.
        wsprintf(szScrap, TEXT("Time: %02d:%02d %s\0"), 
                (time.GetHour() % 12), time.GetMinute(), 
                ((time.GetHour() > 12) ? TEXT("pm\0") : TEXT("am\0")));
        m_StrFileTime.SetWindowText(szScrap);
    }

    CloseHandle(hFile);

    // Update size/date windows
    wsprintf(szScrap, TEXT("Size: %d bytes\0"), dwSizeLow);
    m_StrFileSize.SetWindowText(szScrap);

    return TRUE;
}


BOOL CStillViewDlg::SupportsPropertyPage(IBaseFilter *pFilter) 
{
    HRESULT hr;
    TCHAR szNameToFind[128];
    ISpecifyPropertyPages *pSpecify;

    // Read the current filter name from the list box
    int nCurSel = m_ListFilters.GetCurSel();
    m_ListFilters.GetText(nCurSel, szNameToFind);

    // Discover if this filter contains a property page
    hr = pFilter->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpecify);
    if (SUCCEEDED(hr)) 
    {
        pSpecify->Release();
        return TRUE;
    }
    else
        return FALSE;
}


void CStillViewDlg::OnButtonProppage() 
{
    HRESULT hr;
    IBaseFilter *pFilter = NULL;
    TCHAR szNameToFind[128];
    ISpecifyPropertyPages *pSpecify;

    // Read the current filter name from the list box
    int nCurSel = m_ListFilters.GetCurSel();
    m_ListFilters.GetText(nCurSel, szNameToFind);

    // Read the current list box name and find it in the graph
    pFilter = FindFilterFromName(szNameToFind);
    if (!pFilter)
        return;

    // Discover if this filter contains a property page
    hr = pFilter->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpecify);
    if (SUCCEEDED(hr)) 
    {
        do 
        {
            FILTER_INFO FilterInfo;
            hr = pFilter->QueryFilterInfo(&FilterInfo);
            if (FAILED(hr))
                break;

            CAUUID caGUID;
            hr = pSpecify->GetPages(&caGUID);
            if (FAILED(hr))
                break;

            pSpecify->Release();
        
            // Display the filter's property page
            OleCreatePropertyFrame(
                m_hWnd,                 // Parent window
                0,                      // x (Reserved)
                0,                      // y (Reserved)
                FilterInfo.achName,     // Caption for the dialog box
                1,                      // Number of filters
                (IUnknown **)&pFilter,  // Pointer to the filter 
                caGUID.cElems,          // Number of property pages
                caGUID.pElems,          // Pointer to property page CLSIDs
                0,                      // Locale identifier
                0,                      // Reserved
                NULL                    // Reserved
            );
            CoTaskMemFree(caGUID.pElems);
            FilterInfo.pGraph->Release(); 

        } while(0);
    }

    pFilter->Release();
}


void CStillViewDlg::OnDblclkListFilters() 
{
    OnButtonProppage();
}


void RetailOutput(TCHAR *szFormat, ...)
{
    TCHAR szBuffer[1024];  // Large buffer for long filenames or URLs
    const size_t NUMCHARS = sizeof(szBuffer) / sizeof(szBuffer[0]);
    const int LASTCHAR = NUMCHARS - 1;

    // Format the input string
    va_list pArgs;
    va_start(pArgs, szFormat);

    // Use a bounded buffer size to prevent buffer overruns.  Limit count to
    // character size minus one to allow for a NULL terminating character.
    _vsntprintf(szBuffer, NUMCHARS - 1, szFormat, pArgs);
    va_end(pArgs);

    // Ensure that the formatted string is NULL-terminated
    szBuffer[LASTCHAR] = TEXT('\0');

    OutputDebugString(szBuffer);
}


LONG CStillViewDlg::GetGraphEditPath(TCHAR *szPath)
{
    HKEY  hKey;
    DWORD dwType, dwSize = MAX_PATH;

    // Open the appropriate registry key
    LONG lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                                _T("Software\\Microsoft\\Shared Tools\\Graphedit"),
                                0, KEY_READ, &hKey );
    if( ERROR_SUCCESS != lResult )
        return -1;

    // Read the full path (including .exe name) for the GraphEdit tool
    lResult = RegQueryValueEx( hKey, _T("Path"), NULL,
                              &dwType, (BYTE*)szPath, &dwSize );
    RegCloseKey( hKey );

    if( ERROR_SUCCESS != lResult )
        return -1;

    return 0;
}


void CStillViewDlg::OnButtonGraphedit() 
{
    TCHAR szFilename[128], szFile[MAX_PATH];

    // Read file name from list box
    int nItem = m_ListFiles.GetCurSel();
    m_ListFiles.GetText(nItem, szFilename);

    // Build the full file name with path.  The path will already have
    // a '\' appended to the end.
    wsprintf(szFile, TEXT("%s%s\0"), m_szCurrentDir, szFilename);

    // Launch GraphEdit for the selected file
    //    
    // First look for a registry key containing its full path
    TCHAR szPath[MAX_PATH];

    LONG lResult = GetGraphEditPath(szPath);

    // If the DirectX SDK is not installed, just look for GraphEdit
    // anywhere in the system path.
    if (lResult != 0)
    {
        _tcscpy(szPath, TEXT("graphedt\0"));
    }

    // Lauch GraphEdit using either the full tool path or just its name                               
    HINSTANCE rc = ShellExecute(m_hWnd, TEXT("open\0"), szPath, 
                                szFile, NULL, SW_SHOWNORMAL);

    if (rc < (HINSTANCE) 32)
    {
        // Failed to start the app
        if ((rc == (HINSTANCE) ERROR_FILE_NOT_FOUND) || 
            (rc == (HINSTANCE) ERROR_PATH_NOT_FOUND))
        {
            MessageBox(TEXT("Couldn't find the GraphEdit application.\r\n\r\n")
                       TEXT("Please copy graphedt.exe to a directory on your path."), 
                       TEXT("Can't find GraphEdit"));   
        }
    }
}


BOOL CStillViewDlg::OnEraseBkgnd(CDC *pDC)
{
    // Intercept background erasing for the movie window, since the
    // video renderer will keep the screen painted.  Without this code,
    // your video window might get painted over with gray (the default
    // background brush) when it is obscured by another window and redrawn.
    CRect rc;

    // Get the bounding rectangle for the movie screen
    m_Screen.GetWindowRect(&rc);
    ScreenToClient(&rc);

    // Exclude the clipping region occupied by our movie screen
    pDC->ExcludeClipRect(&rc);
    
    // Erase the remainder of the dialog as usual
    return CDialog::OnEraseBkgnd(pDC);
}


