//------------------------------------------------------------------------------
// File: SysEnumDlg.cpp
//
// Desc: DirectShow sample code - implementation of dialog for device
//       enumeration.
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------


#include "stdafx.h"
#include <atlbase.h>
#include "SysEnum.h"
#include "SysEnumDlg.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()

/////////////////////////////////////////////////////////////////////////////
// CSysEnumDlg dialog

CSysEnumDlg::CSysEnumDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CSysEnumDlg::IDD, pParent)
{
    //{{AFX_DATA_INIT(CSysEnumDlg)
    m_bShowAllCategories = FALSE;
    //}}AFX_DATA_INIT
    // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CSysEnumDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CSysEnumDlg)
    DDX_Control(pDX, IDC_STATIC_FILENAME, m_StrFilename);
    DDX_Control(pDX, IDC_STATIC_FILTERS, m_StrFilters);
    DDX_Control(pDX, IDC_STATIC_CLASSES, m_StrClasses);
    DDX_Control(pDX, IDC_LIST_FILTERS, m_FilterList);
    DDX_Control(pDX, IDC_LIST_DEVICES, m_DeviceList);
    DDX_Check(pDX, IDC_CHECK_SHOWALL, m_bShowAllCategories);
    //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CSysEnumDlg, CDialog)
    //{{AFX_MSG_MAP(CSysEnumDlg)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_LBN_SELCHANGE(IDC_LIST_DEVICES, OnSelchangeListDevices)
    ON_WM_CLOSE()
    ON_BN_CLICKED(IDC_CHECK_SHOWALL, OnCheckShowall)
    ON_LBN_SELCHANGE(IDC_LIST_FILTERS, OnSelchangeListFilters)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CSysEnumDlg message handlers

void CSysEnumDlg::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 CSysEnumDlg::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 CSysEnumDlg::OnQueryDragIcon()
{
    return (HCURSOR) m_hIcon;
}


BOOL CSysEnumDlg::OnInitDialog()
{
    HRESULT hr;

    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

    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

    // Instantiate the system device enumerator
    m_pSysDevEnum = NULL;

    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, 
                          CLSCTX_INPROC, IID_ICreateDevEnum, 
                          (void **)&m_pSysDevEnum);
    if FAILED(hr)
    {
        CoUninitialize();
        return FALSE;
    }

    // By default, only enumerate subset of categories listed in docs
    m_bShowAllCategories = FALSE;
    FillCategoryList();

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


void CSysEnumDlg::OnCheckShowall() 
{
    // Toggle category type and redraw the category list
    m_bShowAllCategories ^= 1;
    FillCategoryList();
    SetNumFilters(0);
}


void CSysEnumDlg::FillCategoryList(void)
{
    // Clear listboxes
    ClearDeviceList();
    ClearFilterList();

    if (m_bShowAllCategories)
    {
        // Emulate the behavior of GraphEdit by enumerating all 
        // categories in the system
        DisplayFullCategorySet();
    }
    else
    {
        // Fill the category list box with the categories to display,
        // using the names stored in the CATEGORY_INFO array.
        // See SysEnumDlg.H for a category description.
        for (int i=0; i < NUM_CATEGORIES; i++)
        {
            m_DeviceList.AddString(categories[i].szName);
        }

        // Update listbox title with number of classes
        SetNumClasses(NUM_CATEGORIES);
    }
}


void CSysEnumDlg::SetNumClasses(int nClasses)
{
    TCHAR szClasses[64];

    wsprintf(szClasses, TEXT("%s (%d found)\0"), STR_CLASSES, nClasses);
    m_StrClasses.SetWindowText(szClasses);
}


void CSysEnumDlg::SetNumFilters(int nFilters)
{
    TCHAR szFilters[64]={0};

    if (nFilters)
        wsprintf(szFilters, TEXT("%s (%d found)\0"), STR_FILTERS, nFilters);
    else
    {
        _tcsncpy(szFilters, STR_FILTERS, 63);
        szFilters[63] = 0;
    }

    m_StrFilters.SetWindowText(szFilters);
}


void CSysEnumDlg::DisplayFullCategorySet(void)
{
    USES_CONVERSION;

    HRESULT hr;
    IEnumMoniker *pEmCat = 0;
    ICreateDevEnum *pCreateDevEnum = NULL;
    int nClasses=0;

    // Create an enumerator
    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
                          IID_ICreateDevEnum, (void**)&pCreateDevEnum);
    ASSERT(SUCCEEDED(hr));
    if (FAILED(hr))
        return;

    // Use the meta-category that contains a list of all categories.
    // This emulates the behavior of GraphEdit.
    hr = pCreateDevEnum->CreateClassEnumerator(
                         CLSID_ActiveMovieCategories, &pEmCat, 0);
    ASSERT(SUCCEEDED(hr));

    if(hr == S_OK)
    {
        IMoniker *pMCat;
        ULONG cFetched;

        // Enumerate over every category
        while(hr = pEmCat->Next(1, &pMCat, &cFetched),
              hr == S_OK)
        {
            IPropertyBag *pPropBag;

            // Associate moniker with a file
            hr = pMCat->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
            if(SUCCEEDED(hr))
            {
                VARIANT varCatClsid;
                varCatClsid.vt = VT_BSTR;

                // Read CLSID string from property bag
                hr = pPropBag->Read(L"CLSID", &varCatClsid, 0);
                if(SUCCEEDED(hr))
                {
                    CLSID clsidCat;

                    if(CLSIDFromString(varCatClsid.bstrVal, &clsidCat) == S_OK)
                    {
                        // Use the guid if we can't get the name
                        WCHAR *wszCatName;
                        TCHAR szCatDesc[MAX_PATH];

                        VARIANT varCatName;
                        varCatName.vt = VT_BSTR;

                        // Read filter name
                        hr = pPropBag->Read(L"FriendlyName", &varCatName, 0);
                        if(SUCCEEDED(hr))
                            wszCatName = varCatName.bstrVal;
                        else
                            wszCatName = varCatClsid.bstrVal;

#ifndef UNICODE
                        WideCharToMultiByte(
                                CP_ACP, 0, wszCatName, -1,
                                szCatDesc, sizeof(szCatDesc), 0, 0);
#else
                        lstrcpy(szCatDesc, W2T(wszCatName));
#endif

                        if(SUCCEEDED(hr))
                            SysFreeString(varCatName.bstrVal);

                        // Add category name and CLSID to list box
                        AddFilterCategory(szCatDesc, &clsidCat);
                        nClasses++;
                    }

                    SysFreeString(varCatClsid.bstrVal);
                }

                pPropBag->Release();
            }
            else
            {
                break;
            }

            pMCat->Release();
        } // for loop

        pEmCat->Release();
    }

    pCreateDevEnum->Release();

    // Update listbox title with number of classes
    SetNumClasses(nClasses);
}


void CSysEnumDlg::AddFilterCategory(
    const TCHAR *szCatDesc,
    const GUID *pCatGuid)
{
    // Allocate a new CLSID, whose pointer will be stored in 
    // the listbox.  When the listbox is cleared, these will be deleted.
    CLSID *pclsid = new CLSID;
    if (pclsid && pCatGuid)
        *pclsid = *pCatGuid;

    // Add the category name and a pointer to its CLSID to the list box
    int nSuccess  = m_DeviceList.AddString(szCatDesc);
    int nIndexNew = m_DeviceList.FindStringExact(-1, szCatDesc);
    nSuccess = m_DeviceList.SetItemDataPtr(nIndexNew, pclsid);
}


void CSysEnumDlg::AddFilter(
    const TCHAR *szFilterName,
    const GUID *pCatGuid)
{
    // Allocate a new CLSID, whose pointer will be stored in 
    // the listbox.  When the listbox is cleared, these will be deleted.
    CLSID *pclsid = new CLSID;
    if (pclsid && pCatGuid)
        *pclsid = *pCatGuid;

    // Add the category name and a pointer to its CLSID to the list box
    int nSuccess  = m_FilterList.AddString(szFilterName);
    int nIndexNew = m_FilterList.FindStringExact(-1, szFilterName);
    nSuccess = m_FilterList.SetItemDataPtr(nIndexNew, pclsid);
}


void CSysEnumDlg::ClearDeviceList(void)
{
    CLSID *pStoredId = NULL;
    
    int nCount = m_DeviceList.GetCount();

    // Delete any CLSID pointers that were stored in the listbox item data
    for (int i=0; i < nCount; i++)
    {
        pStoredId = (CLSID *) m_DeviceList.GetItemDataPtr(i);
        if (pStoredId != 0)
        {
            delete pStoredId;
            pStoredId = NULL;
        }
    }

    // Clean up
    m_DeviceList.ResetContent();
    SetNumClasses(0);
}


void CSysEnumDlg::ClearFilterList(void)
{
    CLSID *pStoredId = NULL;
    
    int nCount = m_FilterList.GetCount();

    // Delete any CLSID pointers that were stored in the listbox item data
    for (int i=0; i < nCount; i++)
    {
        pStoredId = (CLSID *) m_FilterList.GetItemDataPtr(i);
        if (pStoredId != 0)
        {
            delete pStoredId;
            pStoredId = NULL;
        }
    }

    // Clean up
    m_FilterList.ResetContent();
    SetNumFilters(0);
    m_StrFilename.SetWindowText(TEXT("<No filter selected>"));
}


void CSysEnumDlg::OnSelchangeListDevices() 
{
    HRESULT hr;    
    IEnumMoniker *pEnumCat = NULL;

    // Get the currently selected category name
    int nItem = m_DeviceList.GetCurSel();
    const CLSID *clsid;
    
    if (m_bShowAllCategories)
    {
        // Read the CLSID pointer from the list box's item data
        clsid = (CLSID *) m_DeviceList.GetItemDataPtr(nItem);
    }
    else
    {
        // Read the CLSID pointer from our hard-coded array of
        // documented filter categories
        clsid = categories[nItem].pclsid;
    }

    // If the CLSID wasn't allocated earlier, then fail
    if (!clsid)
    {
        MessageBeep(0);
        return;
    }

    //
    // WARNING!
    //
    // Some third-party filters throw an exception (int 3) during enumeration
    // on Debug builds, often due to heap corruption in RtlFreeHeap().
    // Sometimes it is done intentionally to prevent reverse engineering.
    // 
    // This is not an issue on Release builds.
    //

    // Enumerate all filters of the selected category  
    hr = m_pSysDevEnum->CreateClassEnumerator(*clsid, &pEnumCat, 0);
    ASSERT(SUCCEEDED(hr));
    if FAILED(hr)
        return;

    // Enumerate all filters using the category enumerator
    hr = EnumFilters(pEnumCat);

    // Go ahead and select the first filter in the list, if it exists
    if (m_FilterList.GetCount() > 0)
    {
        m_FilterList.SetCurSel(0);
        OnSelchangeListFilters();
    }

    SAFE_RELEASE(pEnumCat);
}


HRESULT CSysEnumDlg::EnumFilters(IEnumMoniker *pEnumCat)
{
    HRESULT hr=S_OK;
    IMoniker *pMoniker;
    ULONG cFetched;
    VARIANT varName={0};
    int nFilters=0;

    // Clear the current filter list
    ClearFilterList();

    // If there are no filters of a requested type, show default string
    if (!pEnumCat)
    {
        m_FilterList.AddString(TEXT("<< No entries >>"));
        SetNumFilters(nFilters);
        return S_FALSE;
    }

    // Enumerate all items associated with the moniker
    while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
    {
        IPropertyBag *pPropBag;
        ASSERT(pMoniker);

        // Associate moniker with a file
        hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, 
                                    (void **)&pPropBag);
        ASSERT(SUCCEEDED(hr));
        ASSERT(pPropBag);
        if (FAILED(hr))
            continue;

        // Read filter name from property bag
        varName.vt = VT_BSTR;
        hr = pPropBag->Read(L"FriendlyName", &varName, 0);
        if (FAILED(hr))
            continue;

        // Get filter name (converting BSTR name to a CString)
        CString str(varName.bstrVal);
        SysFreeString(varName.bstrVal);
        nFilters++;

        // Read filter's CLSID from property bag.  This CLSID string will be
        // converted to a binary CLSID and passed to AddFilter(), which will
        // add the filter's name to the listbox and its CLSID to the listbox
        // item's DataPtr item.  When the user clicks on a filter name in
        // the listbox, we'll read the stored CLSID, convert it to a string,
        // and use it to find the filter's filename in the registry.
        VARIANT varFilterClsid;
        varFilterClsid.vt = VT_BSTR;

        // Read CLSID string from property bag
        hr = pPropBag->Read(L"CLSID", &varFilterClsid, 0);
        if(SUCCEEDED(hr))
        {
            CLSID clsidFilter;

            // Add filter name and CLSID to listbox
            if(CLSIDFromString(varFilterClsid.bstrVal, &clsidFilter) == S_OK)
            {
                AddFilter(str, &clsidFilter);
            }

            SysFreeString(varFilterClsid.bstrVal);
        }
       
        // Cleanup interfaces
        SAFE_RELEASE(pPropBag);
        SAFE_RELEASE(pMoniker);
    }

    // Update count of enumerated filters
    SetNumFilters(nFilters);
    return hr;
}


void CSysEnumDlg::OnSelchangeListFilters() 
{
    const CLSID *clsid;

    // Get the currently selected category name
    int nItem = m_FilterList.GetCurSel();
    
    // Read the CLSID pointer from the list box's item data
    clsid = (CLSID *) m_FilterList.GetItemDataPtr(nItem);

    // Find the filter filename in the registry (by CLSID)
    if (clsid != 0)
        ShowFilenameByCLSID(*clsid);
}


void CSysEnumDlg::ShowFilenameByCLSID(REFCLSID clsid)
{
    HRESULT hr;
    LPOLESTR strCLSID;

    // Convert binary CLSID to a readable version
    hr = StringFromCLSID(clsid, &strCLSID);
    if(SUCCEEDED(hr))
    {
        TCHAR szKey[512];
        CString strQuery(strCLSID);

        // Create key name for reading filename registry
        wsprintf(szKey, TEXT("Software\\Classes\\CLSID\\%s\\InprocServer32\0"),
                 (LPCTSTR) strQuery);

        // Free memory associated with strCLSID (allocated in StringFromCLSID)
        CoTaskMemFree(strCLSID);

        HKEY hkeyFilter=0;
        DWORD dwSize=MAX_PATH;
        BYTE pbFilename[MAX_PATH];
        int rc=0;

        // Open the CLSID key that contains information about the filter
        rc = RegOpenKey(HKEY_LOCAL_MACHINE, szKey, &hkeyFilter);
        if (rc == ERROR_SUCCESS)
        {
            rc = RegQueryValueEx(hkeyFilter, NULL,  // Read (Default) value
                                 NULL, NULL, pbFilename, &dwSize);

            if (rc == ERROR_SUCCESS)
            {
                TCHAR szFilename[MAX_PATH];
                wsprintf(szFilename, TEXT("%s\0"), pbFilename);
                m_StrFilename.SetWindowText(szFilename);
            }

            RegCloseKey(hkeyFilter);
        }
    }
}


void CSysEnumDlg::OnClose() 
{
    // Free any stored CLSID pointers (in listbox item data ptr area)
    ClearFilterList();
    ClearDeviceList();

    // Release system device enumerator and close COM
    SAFE_RELEASE(m_pSysDevEnum);
    CoUninitialize();
    
    CDialog::OnClose();
}



