//------------------------------------------------------------------------------
// File: JukeboxDlg.cpp
//
// Desc: DirectShow sample code - implementation of CJukeboxDlg class.
//
// Copyright (c) 1998-2001 Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------

#include "stdafx.h"
#include <atlbase.h>
#include "Jukebox.h"
#include "JukeboxDlg.h"
#include "playvideo.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()

/////////////////////////////////////////////////////////////////////////////
// CJukeboxDlg dialog

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

void CJukeboxDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CJukeboxDlg)
	DDX_Control(pDX, IDC_STATIC_POSITION, m_StrPosition);
	DDX_Control(pDX, IDC_SLIDER, m_Seekbar);
	DDX_Control(pDX, IDC_STATIC_IMAGESIZE, m_StrImageSize);
	DDX_Control(pDX, IDC_STATIC_DURATION, m_StrDuration);
	DDX_Control(pDX, IDC_EDIT_MEDIADIR, m_EditMediaDir);
	DDX_Control(pDX, IDC_SPIN_FILES, m_SpinFiles);
	DDX_Control(pDX, IDC_BUTTON_FRAMESTEP, m_ButtonFrameStep);
	DDX_Control(pDX, IDC_LIST_EVENTS, m_ListEvents);
	DDX_Control(pDX, IDC_CHECK_EVENTS, m_CheckEvents);
	DDX_Control(pDX, IDC_BUTTON_PROPPAGE, m_ButtonProperties);
	DDX_Control(pDX, IDC_STATUS_DIRECTORY, m_StrMediaPath);
	DDX_Control(pDX, IDC_CHECK_MUTE, m_CheckMute);
	DDX_Control(pDX, IDC_BUTTON_STOP, m_ButtonStop);
	DDX_Control(pDX, IDC_BUTTON_PLAY, m_ButtonPlay);
	DDX_Control(pDX, IDC_BUTTON_PAUSE, m_ButtonPause);
	DDX_Control(pDX, IDC_CHECK_PLAYTHROUGH, m_CheckPlaythrough);
	DDX_Control(pDX, IDC_CHECK_LOOP, m_CheckLoop);
	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_STATUS, m_Status);
	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(CJukeboxDlg, CDialog)
	//{{AFX_MSG_MAP(CJukeboxDlg)
	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_BN_CLICKED(IDC_BUTTON_PAUSE, OnPause)
	ON_BN_CLICKED(IDC_BUTTON_PLAY, OnPlay)
	ON_BN_CLICKED(IDC_BUTTON_STOP, OnStop)
	ON_BN_CLICKED(IDC_CHECK_MUTE, OnCheckMute)
	ON_BN_CLICKED(IDC_CHECK_LOOP, OnCheckLoop)
	ON_BN_CLICKED(IDC_CHECK_PLAYTHROUGH, OnCheckPlaythrough)
	ON_LBN_SELCHANGE(IDC_LIST_FILTERS, OnSelchangeListFilters)
	ON_LBN_DBLCLK(IDC_LIST_FILTERS, OnDblclkListFilters)
	ON_BN_CLICKED(IDC_BUTTON_PROPPAGE, OnButtonProppage)
	ON_BN_CLICKED(IDC_CHECK_EVENTS, OnCheckEvents)
	ON_BN_CLICKED(IDC_BUTTON_FRAMESTEP, OnButtonFramestep)
	ON_BN_CLICKED(IDC_BUTTON_CLEAR_EVENTS, OnButtonClearEvents)
	ON_LBN_DBLCLK(IDC_LIST_FILES, OnDblclkListFiles)
	ON_NOTIFY(UDN_DELTAPOS, IDC_SPIN_FILES, OnDeltaposSpinFiles)
	ON_BN_CLICKED(IDC_BUTTON_SET_MEDIADIR, OnButtonSetMediadir)
	ON_WM_TIMER()
	ON_BN_CLICKED(IDC_BUTTON_GRAPHEDIT, OnButtonGraphedit)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CJukeboxDlg message handlers

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


/////////////////////////////////////////////////////////////////////////////
// CJukeboxDlg DirectShow code and message handlers


BOOL CJukeboxDlg::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
	
    // Initialize COM
    CoInitialize(NULL);
	
    // 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;
    }

    // IMPORTANT
    // 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.
    //
    // Neglecting to set this style can lead to situations when the video
    // is erased and replaced with black (or the default color of the 
    // bounding rectangle).
    m_Screen.ModifyStyle(0, WS_CLIPCHILDREN);

    // Propagate the files list and select the first item
    InitMediaDirectory();
   
    // Initialize seeking trackbar range
    m_Seekbar.SetRange(0, 100, TRUE);
    m_Seekbar.SetTicFreq(10);
    
    return TRUE;  // return TRUE  unless you set the focus to a control
}


void CJukeboxDlg::InitMediaDirectory(void)
{
    // 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.
    TCHAR szDir[MAX_PATH];
    LONG lResult=0;

    if (theApp.m_lpCmdLine[0] == L'\0')
    {
        lResult = GetDXMediaPath(szDir);

        // If the DirectX SDK is not installed, use the Windows media
        // directory instead.
        if (lResult != 0)
        {
            GetWindowsDirectory(szDir, MAX_PATH);
            _tcscat(szDir, _T("\\Media\\") );
        }
    }
    else
        _tcscpy(szDir, theApp.m_lpCmdLine);

    TCHAR szPathMsg[MAX_PATH];
    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 CJukeboxDlg::GetDXMediaPath(TCHAR *szPath)
{
    HKEY  hKey;
    DWORD dwType, dwSize = MAX_PATH;

    // 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;

    lResult = RegQueryValueEx( hKey, _T("DX81SDK Samples Path"), NULL,
                              &dwType, (BYTE*)szPath, &dwSize );
    RegCloseKey( hKey );

    if( ERROR_SUCCESS != lResult )
        return -1;

    _tcscat( szPath, _T("\\Media\\") );
    return 0;
}


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

    m_ListFiles.ResetContent();

    ::SetCurrentDirectory(pszRootDir);
    Say(TEXT("Building file list..."));

    // 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);
    }
    Say(TEXT("File list complete."));

    // Update list box title with number of items added
    int nItems  = m_ListFiles.GetCount();
    TCHAR szTitle[64];
    wsprintf(szTitle, TEXT("Media files (%d found)"), nItems);
    m_StrFileList.SetWindowText(szTitle);
    
    // 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 CJukeboxDlg::OnClose() 
{
    // Release DirectShow interfaces
    StopMedia();
    FreeDirectShow();

    // Release COM
    CoUninitialize();

	CDialog::OnClose();
}

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

	CDialog::OnDestroy();
}


void CJukeboxDlg::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);

    // Remember current play state to restart playback
    int nCurrentState = g_psCurrent;

    // First release any existing interfaces
    ResetDirectShow();

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

    // Load the selected media file
    hr = PrepareMedia(szFilename);
    if (FAILED(hr))
    {
        // Error - disable play button and give feedback
        Say(TEXT("File failed to render!"));
        m_ButtonPlay.EnableWindow(FALSE);
        MessageBeep(0);
        return;
    }
    else
    {
        m_ButtonPlay.EnableWindow(TRUE);
    }

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

    // Set up the seeking trackbar and read capabilities
    ConfigureSeekbar();

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

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

    // See if the renderer supports frame stepping on this file.
    // Enable/disable frame stepping button accordingly
    m_ButtonFrameStep.EnableWindow(CanStep());

    // If the user has asked to mute audio then we
    // need to mute this new clip before continuing.
    if (g_bGlobalMute)
        MuteAudio();

    // If we were running when the user changed selection,
    // start running the newly selected clip
    if (nCurrentState == State_Running)
    {
        OnPlay();
    }
    else
    {
        // Cue the first video frame
        OnStop();
    }
}


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

    Say(TEXT("Loading..."));

    // Allow DirectShow to create the FilterGraph for this media file
    hr = 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.  If this is an audio-only or MIDI file, 
    // then put_MessageDrain will fail.
    hr = pVW->put_MessageDrain((OAHWND) m_hWnd);
    if (FAILED(hr))
    {
        g_bAudioOnly = TRUE;
    }

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

    // Configure the video window
    if (!g_bAudioOnly)
    {
        // We'll manually set the video to be visible
        hr = pVW->put_Visible(OAFALSE);

        hr = pVW->put_WindowStyle(WS_CHILD);
        hr = pVW->put_Owner((OAHWND) m_Screen.GetSafeHwnd());

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

        // Make the video window visible within the screen window.
        // If this is an audio-only file, then there won't be a video interface.
        hr = pVW->put_Visible(OATRUE);
        hr = pVW->SetWindowForeground(-1);
    }

    Say(TEXT("Ready"));
    return hr;
}


//
//  Displays a text string in a status line near the bottom of the dialog
//
void CJukeboxDlg::Say(LPTSTR szText)
{
    m_Status.SetWindowText(szText);
}

void CJukeboxDlg::OnPause() 
{  
    if (g_psCurrent == State_Paused)
    {
        RunMedia();
        StartSeekTimer();
        Say(TEXT("Running"));
    }
    else
    {
        StopSeekTimer();
        PauseMedia();
        Say(TEXT("PAUSED"));
    }
}

void CJukeboxDlg::OnPlay() 
{
  	RunMedia();
    StartSeekTimer();
    Say(TEXT("Running"));
}

void CJukeboxDlg::ShowState()
{
    HRESULT hr;

    OAFilterState fs;
    hr = pMC->GetState(500, &fs);
    if (FAILED(hr))
    {
        RetailOutput(TEXT("Failed to read graph state!  hr=0x%x\r\n"), hr);
        return;
    }

    // Show debug output for current media state
    switch (fs)
    {
        case State_Stopped:
            RetailOutput(TEXT("State_Stopped\r\n"));
            break;
        case State_Paused:
            RetailOutput(TEXT("State_Paused\r\n"));
            break;
        case State_Running:
            RetailOutput(TEXT("State_Running\r\n"));
            break;
    }
}

void CJukeboxDlg::OnStop() 
{
    HRESULT hr;

    // Stop playback immediately with IMediaControl::Stop().
    StopSeekTimer();
    StopMedia();

    // Wait for the stop to propagate to all filters
    OAFilterState fs;
    hr = pMC->GetState(500, &fs);
    if (FAILED(hr))
    {
        RetailOutput(TEXT("Failed to read graph state!  hr=0x%x\r\n"), hr);
    }

    // Reset to beginning of media clip
    LONGLONG pos=0;
    hr = pMS->SetPositions(&pos, AM_SEEKING_AbsolutePositioning ,
                           NULL, AM_SEEKING_NoPositioning);
    if (FAILED(hr))
    {
        RetailOutput(TEXT("Failed to seek to beginning of media!  hr=0x%x\r\n"), hr);
    }
  
    // Display the first frame of the media clip, if it contains video.
    // StopWhenReady() pauses all filters internally (which allows the video
    // renderer to queue and display the first video frame), after which
    // it sets the filters to the stopped state.  This enables easy preview
    // of the video's poster frame.
    hr = pMC->StopWhenReady();
    if (FAILED(hr))
    {
        RetailOutput(TEXT("Failed in StopWhenReady!  hr=0x%x\r\n"), hr);
    }

    Say(TEXT("Stopped"));

    // Reset slider bar and position label back to zero
    ReadMediaPosition();
}

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

    g_bAudioOnly = FALSE;

    // Zero interfaces (sanity check)
    pVW = NULL;
    pBV = NULL;

    JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&pGB));
    JIF(pGB->QueryInterface(IID_IMediaControl,  (void **)&pMC));
    JIF(pGB->QueryInterface(IID_IMediaSeeking,  (void **)&pMS));
    JIF(pGB->QueryInterface(IID_IBasicVideo,    (void **)&pBV));
    JIF(pGB->QueryInterface(IID_IVideoWindow,   (void **)&pVW));
    JIF(pGB->QueryInterface(IID_IMediaEventEx,  (void **)&pME));

    return S_OK;

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

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

    StopSeekTimer();
    StopMedia();

    // Disable event callbacks
    if (pME)
        hr = pME->SetNotifyWindow((OAHWND)NULL, 0, 0);

    // 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(pVW)
    {
        hr = pVW->put_Visible(OAFALSE);
        hr = pVW->put_Owner(NULL);
    }

    SAFE_RELEASE(pMC);
    SAFE_RELEASE(pMS);
    SAFE_RELEASE(pVW);
    SAFE_RELEASE(pBV);
    SAFE_RELEASE(pME);
    SAFE_RELEASE(pGB);

    return hr;
}

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

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

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

    if ((g_bAudioOnly) || (!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 = 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 CJukeboxDlg::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_HSCROLL:
            HandleTrackbar(LOWORD(wParam));
            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;

        case WM_CLOSE:
            FreeDirectShow();
            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 (pVW)
    {
        pVW->NotifyOwnerMessage((LONG_PTR) m_hWnd, message, wParam, lParam);
    }

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


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

	// Since we may have a scenario where we're shutting down the application,
	// but events are still being generated, make sure that the event
	// interface is still valid before using it.  It's possible that
	// the interface could be freed during shutdown but later referenced in
	// this callback before the app completely exits.
	if (!pME)
		return S_OK;
    
    while(SUCCEEDED(pME->GetEvent(&evCode, (LONG_PTR *) &evParam1, 
                    (LONG_PTR *) &evParam2, 0)))
    {
        // Spin through the events
        hr = pME->FreeEventParams(evCode, evParam1, evParam2);

        if(EC_COMPLETE == evCode)
        {
            // If looping, reset to beginning and continue playing
            if (g_bLooping)
            {
                LONGLONG pos=0;

                // Reset to first frame of movie
                hr = pMS->SetPositions(&pos, AM_SEEKING_AbsolutePositioning ,
                                       NULL, AM_SEEKING_NoPositioning);
                if (FAILED(hr))
                {
                    // Some custom filters (like the Windows CE MIDI filter) 
                    // may not implement seeking interfaces (IMediaSeeking)
                    // to allow seeking to the start.  In that case, just stop 
                    // and restart for the same effect.  This should not be
                    // necessary in most cases.
                    StopMedia();
                    RunMedia();
                }
            }
            else if (g_bPlayThrough)
            {
                // Tell the app to select the next file in the list
                PostMessage(WM_NEXTFILE, 0, 0);
            }
            else
            {
                // Stop playback and display first frame of movie
                OnStop();
            }
        }

        //  If requested, display DirectShow events received
        if (g_bDisplayEvents)
        {
            DisplayECEvent(evCode, evParam1, evParam2);
        }
    }

    return hr;
}

void CJukeboxDlg::OnCheckMute() 
{
    // Remember global mute status for next file.  When you destroy a
    // filtergraph, you lose all of its audio settings.  Therefore, when
    // we create the next graph, we will mute the audio before running
    // the graph if this global variable is set.
    g_bGlobalMute ^= 1; 

    if (g_bGlobalMute)
        MuteAudio();
    else
        ResumeAudio();
}

void CJukeboxDlg::OnCheckLoop() 
{
    g_bLooping ^= 1;

    // Looping and play-through are mutually exclusive
    if ((g_bLooping) && (g_bPlayThrough))
    {
        // Disable play-through and uncheck button
        g_bPlayThrough = 0;
        m_CheckPlaythrough.SetCheck(0);
    }
}

void CJukeboxDlg::OnCheckPlaythrough() 
{
    g_bPlayThrough ^= 1;	

    // Looping and play-through are mutually exclusive
    if ((g_bPlayThrough) && (g_bLooping) )
    {
        // Disable play-through and uncheck button
        g_bLooping = 0;
        m_CheckLoop.SetCheck(0);
    }
}

void CJukeboxDlg::OnCheckEvents() 
{
    g_bDisplayEvents ^= 1;	
}

void CJukeboxDlg::OnButtonClearEvents() 
{
    m_ListEvents.ResetContent();
}

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

    // Make sure that we're not playing media
    OnStop();

    // 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 = _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 CJukeboxDlg::OnDblclkListFiles() 
{
    // Because it might take time to render the file and display
    // its first frame, it's better to post a message that tells
    // the app to play the selected file when ready.
    PostMessage(WM_PLAYFILE, 0, 0L);
}

void CJukeboxDlg::PlaySelectedFile() 
{
    OnPlay();
}

void CJukeboxDlg::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 CJukeboxDlg::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 CJukeboxDlg::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 CJukeboxDlg::EnumFilters (void) 
{
    HRESULT hr;
    IEnumFilters *pEnum = NULL;
    IBaseFilter *pFilter = NULL;
    ULONG cFetched;

    // Clear filters list box
    m_ListFilters.ResetContent();
    
    // Get filter enumerator
    hr = 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
            USES_CONVERSION;

            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 *CJukeboxDlg::FindFilterFromName(LPTSTR szNameToFind)
{
    USES_CONVERSION;

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

    // Get filter enumerator
    hr = 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 CJukeboxDlg::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 CJukeboxDlg::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 CJukeboxDlg::DisplayImageInfo(void)
{
    HRESULT hr;
    long lWidth, lHeight;
    
    // If this file has no video component, clear the text field
    if ((!pBV) || (g_bAudioOnly))
    {
        m_StrImageSize.SetWindowText(TEXT("\0"));
        return FALSE;
    }

    hr = 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 CJukeboxDlg::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("File date: %02d/%02d/%d\0"), 
                 time.GetMonth(), time.GetDay(), time.GetYear());
        m_StrFileDate.SetWindowText(szScrap);
    }

    CloseHandle(hFile);

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

    return TRUE;
}


HRESULT CJukeboxDlg::DisplayFileDuration(void)
{
    HRESULT hr;

    if (!pMS)
        return E_NOINTERFACE;

    // Initialize the display in case we can't read the duration
    m_StrDuration.SetWindowText(TEXT("<00:00.000>"));

    // Is media time supported for this file?
    if (S_OK != pMS->IsFormatSupported(&TIME_FORMAT_MEDIA_TIME))
        return E_NOINTERFACE;

    // Read the time format to restore later
    GUID guidOriginalFormat;
    hr = pMS->GetTimeFormat(&guidOriginalFormat);
    if (FAILED(hr))
        return hr;

    // Ensure media time format for easy display
    hr = pMS->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME);
    if (FAILED(hr))
        return hr;

    // Read the file's duration
    LONGLONG llDuration;
    hr = pMS->GetDuration(&llDuration);
    if (FAILED(hr))
        return hr;

    // Return to the original format
    if (guidOriginalFormat != TIME_FORMAT_MEDIA_TIME)
    {
        hr = pMS->SetTimeFormat(&guidOriginalFormat);
        if (FAILED(hr))
            return hr;
    }

    // Convert the LONGLONG duration into human-readable format
    unsigned long nTotalMS = (unsigned long) llDuration / 10000; // 100ns -> ms
    int nMS = nTotalMS % 1000;
    int nSeconds = nTotalMS / 1000;
    int nMinutes = nSeconds / 60;
    nSeconds %= 60;

    // Update the display
    TCHAR szDuration[24];
    wsprintf(szDuration, _T("%02dm:%02d.%03ds\0"), nMinutes, nSeconds, nMS);
    m_StrDuration.SetWindowText(szDuration);

    return hr;
}


BOOL CJukeboxDlg::SupportsPropertyPage(IBaseFilter *pFilter) 
{
    HRESULT hr;
    ISpecifyPropertyPages *pSpecify;

    // 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 CJukeboxDlg::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 CJukeboxDlg::OnDblclkListFilters() 
{
    OnButtonProppage();
}


void CJukeboxDlg::DisplayECEvent(long lEventCode, long lParam1, long lParam2)
{
    static TCHAR szMsg[256];
    BOOL bMatch = TRUE;

#define HANDLE_EC(c)                              \
    case c:                                       \
        wsprintf(szMsg, TEXT("%s\0"), TEXT(#c));  \
        break;

    switch (lEventCode)
    {
        HANDLE_EC(EC_ACTIVATE);
        HANDLE_EC(EC_BUFFERING_DATA);
        HANDLE_EC(EC_CLOCK_CHANGED);
        HANDLE_EC(EC_COMPLETE);
        HANDLE_EC(EC_DEVICE_LOST);
        HANDLE_EC(EC_DISPLAY_CHANGED);
        HANDLE_EC(EC_END_OF_SEGMENT);
        HANDLE_EC(EC_ERROR_STILLPLAYING);
        HANDLE_EC(EC_ERRORABORT);
        HANDLE_EC(EC_EXTDEVICE_MODE_CHANGE);
        HANDLE_EC(EC_FULLSCREEN_LOST);
        HANDLE_EC(EC_GRAPH_CHANGED);
        HANDLE_EC(EC_LENGTH_CHANGED);
        HANDLE_EC(EC_NEED_RESTART);
        HANDLE_EC(EC_NOTIFY_WINDOW);
        HANDLE_EC(EC_OLE_EVENT);
        HANDLE_EC(EC_OPENING_FILE);
        HANDLE_EC(EC_PALETTE_CHANGED);
        HANDLE_EC(EC_PAUSED);
        HANDLE_EC(EC_QUALITY_CHANGE);
        HANDLE_EC(EC_REPAINT);
        HANDLE_EC(EC_SEGMENT_STARTED);
        HANDLE_EC(EC_SHUTTING_DOWN);
        HANDLE_EC(EC_SNDDEV_IN_ERROR);
        HANDLE_EC(EC_SNDDEV_OUT_ERROR);
        HANDLE_EC(EC_STARVATION);
        HANDLE_EC(EC_STEP_COMPLETE);
        HANDLE_EC(EC_STREAM_CONTROL_STARTED);
        HANDLE_EC(EC_STREAM_CONTROL_STOPPED);
        HANDLE_EC(EC_STREAM_ERROR_STILLPLAYING);
        HANDLE_EC(EC_STREAM_ERROR_STOPPED);
        HANDLE_EC(EC_TIMECODE_AVAILABLE);
        HANDLE_EC(EC_USERABORT);
        HANDLE_EC(EC_VIDEO_SIZE_CHANGED);
        HANDLE_EC(EC_WINDOW_DESTROYED);

    default:
        bMatch = FALSE;
        RetailOutput(TEXT("  Received unknown event code (0x%x)\r\n"), lEventCode);
        break;
    }

    // If a recognized event was found, add its name to the events list box
    if (bMatch)
        m_ListEvents.AddString(szMsg);
}


void CJukeboxDlg::OnButtonFramestep() 
{
    StepFrame();
}


//
// Some hardware decoders and video renderers support stepping media
// frame by frame with the IVideoFrameStep interface.  See the interface
// documentation for more details on frame stepping.
//
BOOL CJukeboxDlg::CanStep(void)
{
    HRESULT hr;
    IVideoFrameStep* pFS;

    hr = pGB->QueryInterface(__uuidof(IVideoFrameStep), (PVOID *)&pFS);
    if (FAILED(hr))
        return FALSE;

    // Check if this decoder can step
    hr = pFS->CanStep(0L, NULL); 

    pFS->Release();

    if (hr == S_OK)
        return TRUE;
    else
        return FALSE;
}

HRESULT CJukeboxDlg::StepFrame(void)
{
    // Get the Frame Stepping Interface
    HRESULT hr;
    IVideoFrameStep* pFS;

    hr = pGB->QueryInterface(__uuidof(IVideoFrameStep), (PVOID *)&pFS);
    if (FAILED(hr))
        return hr;

    // The graph must be paused for frame stepping to work
    if (g_psCurrent != State_Paused)
        OnPause();

    // Step one frame
    hr = pFS->Step(1, NULL); 
    pFS->Release();

    // Since the media position has changed slightly, update the
    // slider bar and position label.
    ReadMediaPosition();
    return hr;
}


void CJukeboxDlg::ConfigureSeekbar()
{
    // Disable seekbar for new file and reset tracker/position label
    m_Seekbar.SetPos(0);
    m_StrPosition.SetWindowText(TEXT("Position: 00m:00s\0"));
    g_rtTotalTime=0;

    // If we can't read the file's duration, disable the seek bar
    if (pMS && SUCCEEDED(pMS->GetDuration(&g_rtTotalTime)))
        m_Seekbar.EnableWindow(TRUE);
    else
        m_Seekbar.EnableWindow(FALSE);
}

void CJukeboxDlg::StartSeekTimer() 
{
    // Cancel any pending timer event
    StopSeekTimer();

    // Create a new timer
    g_wTimerID = SetTimer(TIMERID, TICKLEN, NULL);
}

void CJukeboxDlg::StopSeekTimer() 
{
    // Cancel the timer
    if(g_wTimerID)        
    {                
        KillTimer(g_wTimerID);
        g_wTimerID = 0;
    }
}

void CJukeboxDlg::OnTimer(UINT nIDEvent) 
{
    ReadMediaPosition();

	CDialog::OnTimer(nIDEvent);
}

void CJukeboxDlg::ReadMediaPosition()
{
    HRESULT hr;
    REFERENCE_TIME rtNow;

    // Read the current stream position
    hr = pMS->GetCurrentPosition(&rtNow);
    if (FAILED(hr))
        return;

    // Convert position into a percentage value and update slider position
    if (g_rtTotalTime != 0)
    {
        long lTick = (long)((rtNow * 100) / g_rtTotalTime);
        m_Seekbar.SetPos(lTick);
    }
    else
        m_Seekbar.SetPos(0);
	
    // Update the 'current position' string on the main dialog
    UpdatePosition(rtNow);
}

void CJukeboxDlg::UpdatePosition(REFERENCE_TIME rtNow) 
{
    HRESULT hr;

    // If no reference time was passed in, read the current position
    if (rtNow == 0)
    {
        // Read the current stream position
        hr = pMS->GetCurrentPosition(&rtNow);
        if (FAILED(hr))
            return;
    }

    // Convert the LONGLONG duration into human-readable format
    unsigned long nTotalMS = (unsigned long) rtNow / 10000; // 100ns -> ms
    int nSeconds = nTotalMS / 1000;
    int nMinutes = nSeconds / 60;
    nSeconds %= 60;

    // Update the display
    TCHAR szPosition[24], szCurrentString[24];
    wsprintf(szPosition, _T("Position: %02dm:%02ds\0"), nMinutes, nSeconds);

    // Read current string and compare to the new string.  To prevent flicker,
    // don't update this label unless the string has changed.
    m_StrPosition.GetWindowText(szCurrentString, 24);

    if (_tcscmp(szCurrentString, szPosition))
        m_StrPosition.SetWindowText(szPosition);
}

void CJukeboxDlg::HandleTrackbar(WPARAM wReq)
{
    HRESULT hr;
    static OAFilterState state;
    static BOOL bStartOfScroll = TRUE;

    // If the file is not seekable, the trackbar is disabled. 
    DWORD dwPosition = m_Seekbar.GetPos();

    // Pause when the scroll action begins.
    if (bStartOfScroll) 
    {       
        hr = pMC->GetState(10, &state);
        bStartOfScroll = FALSE;
        hr = pMC->Pause();
    }
    
    // Update the position continuously.
    REFERENCE_TIME rtNew = (g_rtTotalTime * dwPosition) / 100;

    hr = pMS->SetPositions(&rtNew, AM_SEEKING_AbsolutePositioning,
                           NULL,   AM_SEEKING_NoPositioning);

    // Restore the state at the end.
    if (wReq == TB_ENDTRACK)
    {
        if (state == State_Stopped)
            hr = pMC->Stop();
        else if (state == State_Running) 
            hr = pMC->Run();

        bStartOfScroll = TRUE;
    }

    // Update the 'current position' string on the main dialog.
    UpdatePosition(rtNew);
}

LONG CJukeboxDlg::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 CJukeboxDlg::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)
    {
        wsprintf(szPath, TEXT("%s\0"), 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 CJukeboxDlg::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);
}
