//------------------------------------------------------------------------------
// File: app.cpp
//
// Desc: DirectShow sample code - application code for TxtPlayer sample
//
// Copyright (c) 1994 - 2001, Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------

#include <streams.h>
#include <atlbase.h>
#include <atlconv.cpp>
#include <mmreg.h>
#include <commctrl.h>

#include <stdarg.h>
#include <stdio.h>

#include "project.h"
#include "mpgcodec.h"


/* -------------------------------------------------------------------------
** Global variables that are initialized at run time and then stay constant.
** -------------------------------------------------------------------------
*/
HINSTANCE           hInst;
HICON               hIconVideoCd;
HWND                hwndApp;
HWND                g_hwndToolbar;
HWND                g_hwndStatusbar;
HWND                g_hwndTrackbar;
CMpegMovie          *pMpegMovie;
double              g_TrackBarScale = 1.0;
BOOL                g_bPlay = FALSE;
int                 dyToolbar, dyStatusbar, dyTrackbar;
int                 FrameStepCount;


/* -------------------------------------------------------------------------
** True Globals - these may change during execution of the program.
** -------------------------------------------------------------------------
*/
TCHAR               g_achFileName[MAX_PATH];
OPENFILENAME        ofn;
DWORD               g_State = VCD_NO_CD;
LONG                lMovieOrgX, lMovieOrgY;
int                 g_TimeFormat = IDM_TIME;

extern RECENTFILES  aRecentFiles[MAX_RECENT_FILES];
extern int          nRecentFiles;

char                g_szLine21Text[4 * 41];

const char  g_Line21Msg[] =
/*        10        20        30
 0123456789012345678901234567890123456789
*/
"Here is some random text used to        "
"demonstrate how Line21 can be rendered  "
"using the new Video Mixing Renderer.    "
"The font used to render the text is     "
"resized as the video destination        "
"rectangle changes.  This means that the "
"text is always shown with the highest   "
"possible image quality.  Also, you will "
"notice that the text is Alpha Blended   "
"with the underlying video, which is     "
"only possible using the new VMR.        ";


LRESULT CALLBACK AboutDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


/* -------------------------------------------------------------------------
** Constants
** -------------------------------------------------------------------------
*/
const TCHAR szClassName[] = TEXT("VMRTxtPlayer_CLASS");
const TCHAR g_szNULL[]    = TEXT("\0");
const TCHAR g_szEmpty[]   = TEXT("");
const TCHAR g_szMovieX[]  = TEXT("MovieOriginX");
const TCHAR g_szMovieY[]  = TEXT("MovieOriginY");

const int STR_MAX_STRING_LEN = 256;

const int   dxBitmap        = 16;
const int   dyBitmap        = 15;
const int   dxButtonSep     = 8;
const TCHAR g_chNULL        = TEXT('\0');

const TBBUTTON tbButtons[DEFAULT_TBAR_SIZE] = {
    { IDX_SEPARATOR,    1,                    0,               TBSTYLE_SEP           },
    { IDX_1,            IDM_MOVIE_PLAY,       TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, -1 },
    { IDX_2,            IDM_MOVIE_PAUSE,      TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, -1 },
    { IDX_3,            IDM_MOVIE_STOP,       TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, -1 },
    { IDX_SEPARATOR,    2,                    0,               TBSTYLE_SEP           },
    { IDX_4,            IDM_MOVIE_PREVTRACK,  TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, -1 },
    { IDX_5,            IDM_MOVIE_SKIP_BACK,  TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, -1 },
    { IDX_6,            IDM_MOVIE_SKIP_FORE,  TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, -1 },
    { IDX_SEPARATOR,    3,                    0,               TBSTYLE_SEP           },
    { IDX_12,           IDM_MOVIE_STEP,       TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, -1 }
};


/******************************Public*Routine******************************\
* WinMain
*
*
* Windows recognizes this function by name as the initial entry point
* for the program.  This function calls the application initialization
* routine, if no other instance of the program is running, and always
* calls the instance initialization routine.  It then executes a message
* retrieval and dispatch loop that is the top-level control structure
* for the remainder of execution.  The loop is terminated when a WM_QUIT
* message is received, at which time this function exits the application
* instance by returning the value passed by PostQuitMessage().
*
* If this function must abort before entering the message loop, it
* returns the conventional value NULL.
*
\**************************************************************************/
int PASCAL
WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLineOld,
    int nCmdShow
    )
{
    USES_CONVERSION;
    LPTSTR lpCmdLine = A2T(lpCmdLineOld);

    HRESULT hres = CoInitialize(NULL);
    if(hres == S_FALSE)
    {
        CoUninitialize();
        return FALSE;
    }

    if(!hPrevInstance)
    {
        if(!InitApplication(hInstance))
        {
            return FALSE;
        }
    }

    /*
    ** Perform initializations that apply to a specific instance
    */
    if(!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }

    /* Verify that the VMR is present on this system */
    if(!VerifyVMR())
        return FALSE;

    /* Look for options */
    while(lpCmdLine && (*lpCmdLine == '-' || *lpCmdLine == '/'))
    {
        if ((lpCmdLine[1] == 'P') || (lpCmdLine[1] == 'p'))
        {
            g_bPlay = TRUE;
            lpCmdLine += 2;
        }
        else
        {
            break;
        }
        while(lpCmdLine[0] == ' ')
        {
            lpCmdLine++;
        }
    }


    if(lpCmdLine != NULL && lstrlen(lpCmdLine) > 0)
    {
        ProcessOpen(lpCmdLine, g_bPlay);
        SetPlayButtonsEnableState();
    }

    /*
    ** Acquire and dispatch messages until a WM_QUIT message is received.
    */
    return DoMainLoop();
}


/*****************************Private*Routine******************************\
* DoMainLoop
*
* Process the main message loop
*
\**************************************************************************/
int
DoMainLoop(
    void
    )
{
    MSG         msg;
    HANDLE      ahObjects[1];   // handles that need to be waited on
    const int   cObjects = 1;   // no of objects that we are waiting on
    HACCEL      haccel = LoadAccelerators(hInst, MAKEINTRESOURCE(IDR_ACCELERATOR));

    //
    // Message loop lasts until we get a WM_QUIT message
    //
    for(;;)
    {
        if(pMpegMovie != NULL)
        {
            ahObjects[0] = pMpegMovie->GetMovieEventHandle();
        }
        else
        {
            ahObjects[0] = NULL;
        }

        if(ahObjects[0] == NULL)
        {
            WaitMessage();
        }
        else
        {
            //
            // wait for any message sent or posted to this queue
            // or for a graph notification
            //
            DWORD result;

            result = MsgWaitForMultipleObjects(cObjects, ahObjects, FALSE,
                INFINITE, QS_ALLINPUT);
            if(result != (WAIT_OBJECT_0 + cObjects))
            {
                if(result == WAIT_OBJECT_0)
                {
                    VideoCd_OnGraphNotify();
                }
                continue;
            }
        }

        //
        // When here, we either have a message or no event handle
        // has been created yet.
        //
        // read all of the messages in this next loop,
        // removing each message as we read it
        //
        while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if(msg.message == WM_QUIT)
            {
                return (int) msg.wParam;
            }

            if(!TranslateAccelerator(hwndApp, haccel, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    }

} // DoMainLoop


/*****************************Private*Routine******************************\
* InitApplication(HANDLE)
*
* This function is called at initialization time only if no other
* instances of the application are running.  This function performs
* initialization tasks that can be done once for any number of running
* instances.
*
* In this case, we initialize a window class by filling out a data
* structure of type WNDCLASS and calling the Windows RegisterClass()
* function.  Since all instances of this application use the same window
* class, we only need to do this when the first instance is initialized.
*
\**************************************************************************/
BOOL
InitApplication(
    HINSTANCE hInstance
    )
{
    WNDCLASS  wc;

    /*
    ** Fill in window class structure with parameters that describe the
    ** main window.
    */
    hIconVideoCd     = LoadIcon(hInstance, MAKEINTRESOURCE(IDR_VIDEOCD_ICON));

    wc.style         = CS_VREDRAW | CS_HREDRAW;
    wc.lpfnWndProc   = VideoCdWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = hIconVideoCd;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)NULL; // (COLOR_BTNFACE + 1);
    wc.lpszMenuName  = MAKEINTRESOURCE(IDR_MAIN_MENU);
    wc.lpszClassName = szClassName;

    /*
    ** Register the window class and return success/failure code.
    */
    return RegisterClass(&wc);
}


/*****************************Private*Routine******************************\
* InitInstance
*
*
* This function is called at initialization time for every instance of
* this application.  This function performs initialization tasks that
* cannot be shared by multiple instances.
*
* In this case, we save the instance handle in a static variable and
* create and display the main program window.
*
\**************************************************************************/
BOOL
InitInstance(
    HINSTANCE hInstance,
    int nCmdShow
    )
{
    HWND    hwnd;
    RECT    rc;
    POINT   pt;

    /*
    ** Save the instance handle in static variable, which will be used in
    ** many subsequence calls from this application to Windows.
    */
    hInst = hInstance;

    if(! LoadWindowPos(&rc))
    {
        rc.left = rc.top = 0;
        rc.bottom = rc.right = 400;
    }

    /*
    ** Create a main window for this application instance.
    */
    hwnd = CreateWindow(szClassName, IdStr(STR_APP_TITLE),
                        WS_THICKFRAME | WS_POPUP | WS_CAPTION  |
                        WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX |
                        WS_CLIPCHILDREN,
                        rc.left, rc.top,
                        rc.right - rc.left, rc.bottom - rc.top,
                        NULL, NULL, hInstance, NULL);

    /*
    ** If window could not be created, return "failure"
    */
    if(NULL == hwnd)
    {
        return FALSE;
    }

    hwndApp = hwnd;
    nRecentFiles = GetRecentFiles(nRecentFiles);

    pt.x = lMovieOrgX = ProfileIntIn(g_szMovieX, 0);
    pt.y = lMovieOrgY = ProfileIntIn(g_szMovieY, 0);

    // if we fail to get the working area (screen-tray), then assume
    // the screen is 640x480
    //
    if(!SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, FALSE))
    {
        rc.top = rc.left = 0;
        rc.right = 640;
        rc.bottom = 480;
    }

    if(!PtInRect(&rc, pt))
    {
        lMovieOrgX = lMovieOrgY = 0L;
    }

    /*
    ** Make the window visible; update its client area; and return "success"
    */
    SetPlayButtonsEnableState();
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    return TRUE;
}

/*****************************Private*Routine******************************\
* GetMoviePosition
*
* Place the movie in the center of the client window.  We do not stretch the
* the movie yet !
*
\**************************************************************************/
void
GetMoviePosition(
    HWND hwnd,
    long* xPos,
    long* yPos,
    long* pcx,
    long* pcy
    )
{
    RECT rc;
    GetClientRect(hwnd, &rc);

    rc.top += (dyToolbar + dyTrackbar);
    rc.bottom -= dyStatusbar;

    *xPos = rc.left;
    *yPos = rc.top;
    *pcx = rc.right - rc.left;
    *pcy = rc.bottom - rc.top;
}


/*****************************Private*Routine******************************\
* RepositionMovie
*
\**************************************************************************/
void
RepositionMovie(HWND hwnd)
{
    if(pMpegMovie)
    {
        // Allow the video to play for a short time (otherwise, VMR has no way 
        // to reallocate the DDraw surface) and [possibly] return to the point 
        // where we started.
        REFTIME rtPausedPosition = 0L;
        BOOL    bNeedToRewind = FALSE;
        BOOL    bStopped = FALSE;

        if( MOVIE_PLAYING != pMpegMovie->StatusMovie() )
        {
            if( MOVIE_STOPPED == pMpegMovie->StatusMovie() )
                bStopped = TRUE;

            rtPausedPosition = pMpegMovie->GetCurrentPosition();
            bNeedToRewind = TRUE;
            pMpegMovie->PlayMovie();
        }

        long xPos, yPos, cx, cy;
        GetMoviePosition(hwnd, &xPos, &yPos, &cx, &cy);
        pMpegMovie->PutMoviePosition(xPos, yPos, cx, cy);
    
        // Repaint the video window
        HDC hdcWin = GetDC(NULL);
        pMpegMovie->RepaintVideo(hwnd, hdcWin);
        ReleaseDC(hwnd, hdcWin);

        // Stop or pause the video to return to its previous state.
        // Seek to the previously displayed frame.
        if( bNeedToRewind )
        {
            if( bStopped )
            {
                pMpegMovie->StopMovie();
            }
            else
            {
                pMpegMovie->PauseMovie();
            }
            pMpegMovie->SeekToPosition( rtPausedPosition, TRUE);
        }
    }
}


/*****************************Private*Routine******************************\
* VideoCd_OnMove
*
\**************************************************************************/
void
VideoCd_OnMove(
    HWND hwnd,
    int x,
    int y
    )
{
    if(pMpegMovie)
    {
        if(pMpegMovie->GetStateMovie() != State_Running)
        {
            RepositionMovie(hwnd);
        }
        else
        {
            long xPos, yPos, cx, cy;

            // Reposition movie but don't invalidate the rect, since
            // the next video frame will handle the redraw.
            GetMoviePosition(hwnd, &xPos, &yPos, &cx, &cy);
            pMpegMovie->PutMoviePosition(xPos, yPos, cx, cy);
        }
    }
}


/******************************Public*Routine******************************\
* VideoCdWndProc
*
\**************************************************************************/
LRESULT CALLBACK
VideoCdWndProc(
    HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam
    )
{
    switch(message)
    {
        HANDLE_MSG(hwnd, WM_CREATE,            VideoCd_OnCreate);
        HANDLE_MSG(hwnd, WM_PAINT,             VideoCd_OnPaint);
        HANDLE_MSG(hwnd, WM_COMMAND,           VideoCd_OnCommand);
        HANDLE_MSG(hwnd, WM_CLOSE,             VideoCd_OnClose);
        HANDLE_MSG(hwnd, WM_QUERYENDSESSION,   VideoCd_OnQueryEndSession);
        HANDLE_MSG(hwnd, WM_DESTROY,           VideoCd_OnDestroy);
        HANDLE_MSG(hwnd, WM_SIZE,              VideoCd_OnSize);
        HANDLE_MSG(hwnd, WM_SYSCOLORCHANGE,    VideoCd_OnSysColorChange);
        HANDLE_MSG(hwnd, WM_MENUSELECT,        VideoCd_OnMenuSelect);
        HANDLE_MSG(hwnd, WM_INITMENUPOPUP,     VideoCd_OnInitMenuPopup);
        HANDLE_MSG(hwnd, WM_HSCROLL,           VideoCd_OnHScroll);
        HANDLE_MSG(hwnd, WM_TIMER,             VideoCd_OnTimer);
        HANDLE_MSG(hwnd, WM_NOTIFY,            VideoCd_OnNotify);
        HANDLE_MSG(hwnd, WM_DROPFILES,         VideoCd_OnDropFiles);
        HANDLE_MSG(hwnd, WM_MOVE,              VideoCd_OnMove);

        // Note: we do not use HANDLE_MSG here as we want to call
        // DefWindowProc after we have notifed the FilterGraph Resource Manager,
        // otherwise our window will not finish its activation process.

        case WM_ACTIVATE: VideoCd_OnActivate(hwnd, wParam, lParam);

            // IMPORTANT - let this drop through to DefWindowProc

        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
    }

    return 0L;
}


/*****************************Private*Routine******************************\
* VideoCd_OnCreate
*
\**************************************************************************/
BOOL
VideoCd_OnCreate(
    HWND hwnd,
    LPCREATESTRUCT lpCreateStruct
    )
{
    RECT rc;
    int Pane[2];

    InitCommonControls();

    /*
    ** Create the toolbar and statusbar.
    */
    g_hwndToolbar = CreateToolbarEx(hwnd,
        WS_VISIBLE | WS_CHILD |
        TBSTYLE_TOOLTIPS | CCS_NODIVIDER,
        ID_TOOLBAR, NUMBER_OF_BITMAPS,
        hInst, IDR_TOOLBAR, tbButtons,
        DEFAULT_TBAR_SIZE, dxBitmap, dyBitmap,
        dxBitmap, dyBitmap, sizeof(TBBUTTON));

    if(g_hwndToolbar == NULL)
    {
        return FALSE;
    }

    g_hwndStatusbar = CreateStatusWindow(WS_VISIBLE | WS_CHILD | CCS_BOTTOM,
        TEXT("Example Text"), hwnd, ID_STATUSBAR);

    GetWindowRect(g_hwndToolbar, &rc);
    dyToolbar = rc.bottom - rc.top;

    GetWindowRect(g_hwndStatusbar, &rc);
    dyStatusbar = rc.bottom - rc.top;
    dyTrackbar = 30;

    GetClientRect(hwnd, &rc);
    Pane[0] = (rc.right - rc.left) / 2 ;
    Pane[1] = -1;
    SendMessage(g_hwndStatusbar, SB_SETPARTS, 2, (LPARAM)Pane);


    g_hwndTrackbar = CreateWindowEx(0, TRACKBAR_CLASS, TEXT("Trackbar Control"),
        WS_CHILD | WS_VISIBLE |
        TBS_AUTOTICKS | TBS_ENABLESELRANGE,
        LEFT_MARGIN, dyToolbar - 1,
        (rc.right - rc.left) - (2* LEFT_MARGIN),
        dyTrackbar, hwnd, (HMENU)ID_TRACKBAR,
        hInst, NULL);

    SetDurationLength((REFTIME)0);
    SetCurrentPosition((REFTIME)0);
    SetTimer(hwnd, StatusTimer, 100, NULL);

    if(g_hwndStatusbar == NULL || g_hwndTrackbar == NULL)
    {
        return FALSE;
    }

    // accept filemanager WM_DROPFILES messages
    DragAcceptFiles(hwnd, TRUE);

    return TRUE;
}


/*****************************Private*Routine******************************\
* VideoCd_OnActivate
*
\**************************************************************************/

void
VideoCd_OnActivate(
    HWND hwnd,
    WPARAM wParam,
    LPARAM lParam
    )
{
    // If gaining focus, tell the renderer to set focus and 
    // repaint the video (which is necessary if playback is paused)
    if((UINT)LOWORD(wParam))
    {
        // we are being activated - tell the Filter graph (for Sound follows focus)
        if(pMpegMovie)
        {
            pMpegMovie->SetFocus();
            RepositionMovie(hwnd);
        }
    }
}


/*****************************Private*Routine******************************\
* VideoCd_OnHScroll
*
\**************************************************************************/
void
VideoCd_OnHScroll(
    HWND hwnd,
    HWND hwndCtl,
    UINT code,
    int pos
    )
{
    static BOOL fWasPlaying = FALSE;
    static BOOL fBeginScroll = FALSE;

    if(pMpegMovie == NULL)
        return;

    if(hwndCtl == g_hwndTrackbar)
    {
        REFTIME     rtCurrPos;
        REFTIME     rtTrackPos;
        REFTIME     rtDuration;

        pos = (int)SendMessage(g_hwndTrackbar, TBM_GETPOS, 0, 0);
        rtTrackPos = (REFTIME)pos * g_TrackBarScale;

        switch(code)
        {
            case TB_BOTTOM:
                rtDuration = pMpegMovie->GetDuration();
                rtCurrPos = pMpegMovie->GetCurrentPosition();
                VcdPlayerSeekCmd(rtDuration - rtCurrPos);
                SetCurrentPosition(pMpegMovie->GetCurrentPosition());
                break;

            case TB_TOP:
                rtCurrPos = pMpegMovie->GetCurrentPosition();
                VcdPlayerSeekCmd(-rtCurrPos);
                SetCurrentPosition(pMpegMovie->GetCurrentPosition());
                break;

            case TB_LINEDOWN:
                VcdPlayerSeekCmd(10.0);
                SetCurrentPosition(pMpegMovie->GetCurrentPosition());
                break;

            case TB_LINEUP:
                VcdPlayerSeekCmd(-10.0);
                SetCurrentPosition(pMpegMovie->GetCurrentPosition());
                break;

            case TB_ENDTRACK:
                fBeginScroll = FALSE;
                if(fWasPlaying)
                {
                    VcdPlayerPauseCmd();
                    fWasPlaying = FALSE;
                }
                break;

            case TB_THUMBTRACK:
                if(!fBeginScroll)
                {
                    fBeginScroll = TRUE;
                    fWasPlaying = (g_State & VCD_PLAYING);
                    if(fWasPlaying)
                    {
                        VcdPlayerPauseCmd();
                    }
                }

            // Fall through
            case TB_PAGEUP:
            case TB_PAGEDOWN:
                rtCurrPos = pMpegMovie->GetCurrentPosition();
                VcdPlayerSeekCmd(rtTrackPos - rtCurrPos);
                SetCurrentPosition(pMpegMovie->GetCurrentPosition());
                break;
        }
    }
}


/*****************************Private*Routine******************************\
* VideoCd_OnTimer
*
\**************************************************************************/
void
VideoCd_OnTimer(
    HWND hwnd,
    UINT id
    )
{
    if(pMpegMovie && pMpegMovie->StatusMovie() == MOVIE_PLAYING)
    {
        switch(id)
        {
            case StatusTimer:
            {
                REFTIME rt = pMpegMovie->GetCurrentPosition();
                TCHAR   szFmt[64];
                char    sz[64];
                long cx, cy;
                static int iCharCount = 0;
                static int iCharOffset = 40;

                pMpegMovie->GetNativeMovieSize(&cx, &cy);
#ifdef UNICODE
                wsprintfA(sz, "%ls", FormatRefTime(szFmt, rt));
#else
                wsprintfA(sz, "%s", FormatRefTime(szFmt, rt));
#endif
                //
                // Prepare the text array
                //
                memset(g_szLine21Text, ' ', sizeof(g_szLine21Text));
                g_szLine21Text[ 40] = '\n';
                g_szLine21Text[ 81] = '\n';
                g_szLine21Text[122] = '\n';
                g_szLine21Text[163] = '\0';

                // add time
                memcpy(g_szLine21Text, sz, lstrlenA(sz));


                char* lpL1 = &g_szLine21Text[ 41];
                char* lpL2 = &g_szLine21Text[ 82];
                char* lpL3 = &g_szLine21Text[123];

                if(iCharCount <= 40)
                {
                    memcpy(lpL3, g_Line21Msg, iCharCount);
                }
                else if(iCharCount <= 80)
                {
                    memcpy(lpL2, g_Line21Msg +  0,              40);
                    memcpy(lpL3, g_Line21Msg + 40, iCharCount - 40);
                }
                else if(iCharCount <= 120)
                {
                    memcpy(lpL1, g_Line21Msg +  0,              40);
                    memcpy(lpL2, g_Line21Msg + 40,              40);
                    memcpy(lpL3, g_Line21Msg + 80, iCharCount - 80);
                }
                else
                {
                    memcpy(lpL1, g_Line21Msg + iCharOffset +  0, 40);
                    memcpy(lpL2, g_Line21Msg + iCharOffset + 40, 40);
                    memcpy(lpL3, g_Line21Msg + iCharOffset + 80, iCharCount - (iCharOffset + 80));

                    if(iCharCount % 40 == 0)
                    {
                        iCharOffset += 40;
                    }
                }

                iCharCount++;

                if(iCharCount > (sizeof(g_Line21Msg) - 1))
                {
                    iCharCount = 1;
                    iCharOffset = 40;
                }

                pMpegMovie->SetAppText(g_szLine21Text);
                SetCurrentPosition(rt);
            }
            break;
        }
    }
}


/*****************************Private*Routine******************************\
* VideoCd_OnPaint
*
\**************************************************************************/
void
VideoCd_OnPaint(
    HWND hwnd
    )
{
    PAINTSTRUCT ps;
    HDC         hdc;
    RECT        rc1, rc2;

    /*
    ** Draw a frame around the movie playback area.
    */
    GetClientRect(hwnd, &rc2);

    hdc = BeginPaint(hwnd, &ps);

    if(pMpegMovie)
    {
        long xPos, yPos, cx, cy;
        GetMoviePosition(hwnd, &xPos, &yPos, &cx, &cy);
        SetRect(&rc1, xPos, yPos, xPos + cx, yPos + cy);

        HRGN rgnClient = CreateRectRgnIndirect(&rc2);
        HRGN rgnVideo  = CreateRectRgnIndirect(&rc1);
        CombineRgn(rgnClient, rgnClient, rgnVideo, RGN_DIFF);

        HBRUSH hbr = GetSysColorBrush(COLOR_BTNFACE);
        FillRgn(hdc, rgnClient, hbr);
        DeleteObject(hbr);
        DeleteObject(rgnClient);
        DeleteObject(rgnVideo);

        pMpegMovie->RepaintVideo(hwnd, hdc);
    }
    else
    {
        FillRect(hdc, &rc2, (HBRUSH)(COLOR_BTNFACE + 1));
    }

    EndPaint(hwnd, &ps);
}


/*****************************Private*Routine******************************\
* VideoCd_OnCommand
*
\**************************************************************************/
void
VideoCd_OnCommand(
    HWND hwnd,
    int id,
    HWND hwndCtl,
    UINT codeNotify
    )
{
    switch(id)
    {
        case IDM_FILE_OPEN:
            if(VcdPlayerOpenCmd())
                VcdPlayerPlayCmd();
            break;

        case IDM_FILE_CLOSE:
            VcdPlayerCloseCmd();
            QzFreeUnusedLibraries();
            break;

        case IDM_FILE_EXIT:
            PostMessage(hwnd, WM_CLOSE, 0, 0L);
            break;

        case IDM_MOVIE_PLAY:
            VcdPlayerPlayCmd();
            break;

        case IDM_MOVIE_STOP:
            VcdPlayerStopCmd();
            VcdPlayerRewindCmd();
            break;

        case IDM_MOVIE_PAUSE:
            VcdPlayerPauseCmd();
            break;

        case IDM_MOVIE_SKIP_FORE:
            VcdPlayerSeekCmd(1.0);
            break;

        case IDM_MOVIE_SKIP_BACK:
            VcdPlayerSeekCmd(-1.0);
            break;

        case IDM_MOVIE_PREVTRACK:
            if(pMpegMovie)
            {
                VcdPlayerSeekCmd(-pMpegMovie->GetCurrentPosition());
            }
            break;

        case IDM_MOVIE_STEP:
            VcdPlayerPauseCmd();
            VcdPlayerStepCmd();
            break;

        case IDM_TIME:
        case IDM_FRAME:
        case IDM_FIELD:
        case IDM_SAMPLE:
        case IDM_BYTES:
            if(pMpegMovie)
            {
                g_TimeFormat = VcdPlayerChangeTimeFormat(id);
            }
            break;

        case IDM_HELP_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX),
                hwnd,  (DLGPROC) AboutDlgProc);
            break;

        default:
            if(id > ID_RECENT_FILE_BASE
                && id <= (ID_RECENT_FILE_BASE + MAX_RECENT_FILES + 1))
            {
                ProcessOpen(aRecentFiles[id - ID_RECENT_FILE_BASE - 1]);
                VcdPlayerPlayCmd();
            }
            break;
    }

    SetPlayButtonsEnableState();
}


/******************************Public*Routine******************************\
* VideoCd_OnDestroy
*
\**************************************************************************/
void
VideoCd_OnDestroy(
    HWND hwnd
    )
{
    PostQuitMessage(0);
}


/******************************Public*Routine******************************\
* VideoCd_OnClose
*
\**************************************************************************/
void
VideoCd_OnClose(
    HWND hwnd
    )
{
    // stop accepting dropped filenames
    DragAcceptFiles(hwnd, FALSE);

    VcdPlayerCloseCmd();
    ProfileIntOut(g_szMovieX, lMovieOrgX);
    ProfileIntOut(g_szMovieY, lMovieOrgY);

    SaveWindowPos(hwnd);
    DestroyWindow(hwnd);
}

/******************************Public*Routine******************************\
* VideoCd_OnQueryEndSession
*
\**************************************************************************/
BOOL
VideoCd_OnQueryEndSession(
    HWND hwnd
    )
{
    SaveWindowPos(hwnd);
    return TRUE;
}


/******************************Public*Routine******************************\
* VideoCd_OnSize
*
\**************************************************************************/
void
VideoCd_OnSize(
    HWND hwnd,
    UINT state,
    int dx,
    int dy
    )
{
    if(IsWindow(g_hwndStatusbar))
    {
        int Pane[2] = {dx/2-8, -1};

        SendMessage(g_hwndStatusbar, WM_SIZE, 0, 0L);
        SendMessage(g_hwndStatusbar, SB_SETPARTS, 2, (LPARAM)Pane);
    }

    if(IsWindow(g_hwndTrackbar))
    {
        SetWindowPos(g_hwndTrackbar, HWND_TOP, LEFT_MARGIN, dyToolbar - 1,
            dx - (2 * LEFT_MARGIN), dyTrackbar, SWP_NOZORDER);
    }

    if(IsWindow(g_hwndToolbar))
    {
        SendMessage(g_hwndToolbar, WM_SIZE, 0, 0L);
    }

    RepositionMovie(hwnd);
}


/*****************************Private*Routine******************************\
* VideoCd_OnSysColorChange
*
\**************************************************************************/
void
VideoCd_OnSysColorChange(
    HWND hwnd
    )
{
    FORWARD_WM_SYSCOLORCHANGE(g_hwndToolbar, SendMessage);
    FORWARD_WM_SYSCOLORCHANGE(g_hwndStatusbar, SendMessage);
}


/*****************************Private*Routine******************************\
* VideoCd_OnInitMenuPopup
*
\**************************************************************************/
void
VideoCd_OnInitMenuPopup(
    HWND hwnd,
    HMENU hMenu,
    UINT item,
    BOOL fSystemMenu
    )
{
    UINT uFlags;

    switch(item)
    {
        case 0: // File menu
            if(g_State & (VCD_IN_USE | VCD_NO_CD | VCD_DATA_CD_LOADED))
            {
                uFlags = (MF_BYCOMMAND | MF_GRAYED);
            }
            else
            {
                uFlags = (MF_BYCOMMAND | MF_ENABLED);
            }
            EnableMenuItem(hMenu, IDM_FILE_CLOSE, uFlags);
            break;
    }
}


/*****************************Private*Routine******************************\
* VideoCd_OnGraphNotify
*
* This is where we get any notifications from the filter graph.
*
\**************************************************************************/
void
VideoCd_OnGraphNotify(
    void
    )
{
    long lEventCode;

    lEventCode = pMpegMovie->GetMovieEventCode();

    switch(lEventCode)
    {
        case EC_STEP_COMPLETE:
            g_State &= ~VCD_STEPPING;
            SetPlayButtonsEnableState();
            break;

        case EC_COMPLETE:
            VcdPlayerRewindCmd();
            break;

        case EC_USERABORT:
        case EC_ERRORABORT:
            VcdPlayerStopCmd();
            SetPlayButtonsEnableState();
            break;

        default:
            break;
    }
}


/*****************************Private*Routine******************************\
* VideoCd_OnNotify
*
* This is where we get the text for the little tooltips
*
\**************************************************************************/
LRESULT
VideoCd_OnNotify(
    HWND hwnd,
    int idFrom,
    NMHDR FAR* pnmhdr
    )
{
    switch(pnmhdr->code)
    {
        case TTN_NEEDTEXT:
            {
                LPTOOLTIPTEXT   lpTt;

                lpTt = (LPTOOLTIPTEXT)pnmhdr;
                LoadString(hInst, (UINT) lpTt->hdr.idFrom, lpTt->szText,
                    sizeof(lpTt->szText));
            }
            break;
    }

    return 0;
}


/*****************************Private*Routine******************************\
* VideoCd_OnMenuSelect
*
\**************************************************************************/
void
VideoCd_OnMenuSelect(
    HWND hwnd,
    HMENU hmenu,
    int item,
    HMENU hmenuPopup,
    UINT flags
    )
{
    TCHAR szString[STR_MAX_STRING_LEN + 1];

    /*
    ** Is it time to end the menu help ?
    */
    if((flags == 0xFFFFFFFF) && (hmenu == NULL))
    {
        SendMessage(g_hwndStatusbar, SB_SIMPLE, 0, 0L);
    }

    /*
    ** Do we have a separator, popup, or the system menu ?
    */
    else if(flags & MF_POPUP)
    {
        SendMessage(g_hwndStatusbar, SB_SIMPLE, 0, 0L);
    }
    else if(flags & MF_SYSMENU)
    {
        switch(item)
        {
            case SC_RESTORE:
                lstrcpy(szString, IdStr(STR_SYSMENU_RESTORE));
                break;

            case SC_MOVE:
                lstrcpy(szString, IdStr(STR_SYSMENU_MOVE));
                break;

            case SC_MINIMIZE:
                lstrcpy(szString, IdStr(STR_SYSMENU_MINIMIZE));
                break;

            case SC_MAXIMIZE:
                lstrcpy(szString, IdStr(STR_SYSMENU_MAXIMIZE));
                break;

            case SC_TASKLIST:
                lstrcpy(szString, IdStr(STR_SYSMENU_TASK_LIST));
                break;

            case SC_CLOSE:
                lstrcpy(szString, IdStr(STR_SYSMENU_CLOSE));
                break;
        }

        SendMessage(g_hwndStatusbar, SB_SETTEXT, SBT_NOBORDERS|255,
            (LPARAM)(LPTSTR)szString);
        SendMessage(g_hwndStatusbar, SB_SIMPLE, 1, 0L);
        UpdateWindow(g_hwndStatusbar);

    }

    /*
    ** Hopefully it's one of ours
    */
    else
    {
        if((flags & MF_SEPARATOR))
        {
            szString[0] = g_chNULL;
        }
        else
        {
            lstrcpy(szString, IdStr(item + MENU_STRING_BASE));

        }

        SendMessage(g_hwndStatusbar, SB_SETTEXT, SBT_NOBORDERS|255,
            (LPARAM)(LPTSTR)szString);
        SendMessage(g_hwndStatusbar, SB_SIMPLE, 1, 0L);
        UpdateWindow(g_hwndStatusbar);
    }
}

/*****************************Private*Routine******************************\
* VideoCd_OnDropFiles
*
* -- handle a file-manager drop of a filename to indicate a movie we should
*    open.
*
\**************************************************************************/
void
VideoCd_OnDropFiles(
    HWND hwnd,
    HDROP hdrop)
{
    // if there is more than one file, simply open the first one

    // find the length of the path (plus the null
    int cch = DragQueryFile(hdrop, 0, NULL, 0) + 1;
    TCHAR * pName = new TCHAR[cch];

    DragQueryFile(hdrop, 0, pName, cch);

    // open the file
    ProcessOpen(pName);

    // update the toolbar state
    SetPlayButtonsEnableState();

    // free up used resources
    delete [] pName;
    DragFinish(hdrop);
}


/******************************Public*Routine******************************\
* SetPlayButtonsEnableState
*
* Sets the play buttons enable state to match the state of the current
* cdrom device.  See below...
*
*
*                 VCD Player buttons enable state table
* -------------------------------------------------------------------
* ³E=Enabled D=Disabled      ³ Play ³ Pause ³ Eject ³ Stop  ³ Other ³
* -------------------------------------------------------------------
* ³Disk in use               ³  D   ³  D    ³  D    ³   D   ³   D   ³
* -------------------------------------------------------------------
* ³No video cd or data cdrom ³  D   ³  D    ³  E    ³   D   ³   D   ³
* -------------------------------------------------------------------
* ³Video cd (playing)        ³  D   ³  E    ³  E    ³   E   ³   E   ³
* -------------------------------------------------------------------
* ³Video cd (paused)         ³  E   ³  D    ³  E    ³   E   ³   E   ³
* -------------------------------------------------------------------
* ³Video cd (stopped)        ³  E   ³  D    ³  E    ³   D   ³   E   ³
* -------------------------------------------------------------------
*
*
\**************************************************************************/
void
SetPlayButtonsEnableState(
    void
    )
{
    BOOL    fEnable;
    BOOL    fVideoLoaded;

    /*
    ** Do we have a video cd loaded.
    */
    if(g_State & (VCD_NO_CD | VCD_DATA_CD_LOADED | VCD_IN_USE))
        fVideoLoaded = FALSE;
    else
        fVideoLoaded = TRUE;

    /*
    ** Do the play button
    */
    if(fVideoLoaded
        && ((g_State & VCD_STOPPED) || (g_State & VCD_PAUSED)))
        fEnable = TRUE;
    else
        fEnable = FALSE;

    SendMessage(g_hwndToolbar, TB_ENABLEBUTTON, IDM_MOVIE_PLAY, fEnable);

    /*
    ** Do the stop button
    */
    if(fVideoLoaded
        && ((g_State & VCD_PLAYING) || (g_State & VCD_PAUSED)))
        fEnable = TRUE;
    else
        fEnable = FALSE;

    SendMessage(g_hwndToolbar, TB_ENABLEBUTTON, IDM_MOVIE_STOP, fEnable);


    /*
    ** Do the pause button
    */
    if(fVideoLoaded && (g_State & VCD_PLAYING))
        fEnable = TRUE;
    else
        fEnable = FALSE;

    SendMessage(g_hwndToolbar, TB_ENABLEBUTTON, IDM_MOVIE_PAUSE, fEnable);


    fEnable = FALSE;
    if(fVideoLoaded && pMpegMovie->CanMovieFrameStep())
    {
        if(!(g_State & VCD_STEPPING))
            fEnable = TRUE;
    }
    SendMessage(g_hwndToolbar, TB_ENABLEBUTTON, IDM_MOVIE_STEP, fEnable);

    /*
    ** Do the seeking buttons
    */
    if((g_State & VCD_PAUSED) || (!fVideoLoaded))
        fEnable = FALSE;
    else
        fEnable = TRUE;

    SendMessage(g_hwndToolbar, TB_ENABLEBUTTON,
        IDM_MOVIE_SKIP_FORE, fEnable);

    SendMessage(g_hwndToolbar, TB_ENABLEBUTTON,
        IDM_MOVIE_SKIP_BACK, fEnable);

    SendMessage(g_hwndToolbar, TB_ENABLEBUTTON,
        IDM_MOVIE_PREVTRACK, fEnable);
}


/*****************************Private*Routine******************************\
* GetAdjustedClientRect
*
* Calculate the size of the client rect and then adjusts it to take into
* account the space taken by the toolbar and status bar.
*
\**************************************************************************/
void
GetAdjustedClientRect(
    RECT *prc
    )
{
    RECT    rcTool;

    GetClientRect(hwndApp, prc);

    GetWindowRect(g_hwndToolbar, &rcTool);
    prc->top += (rcTool.bottom - rcTool.top);

    GetWindowRect(g_hwndTrackbar, &rcTool);
    prc->top += (rcTool.bottom - rcTool.top);

    GetWindowRect(g_hwndStatusbar, &rcTool);
    prc->bottom -= (rcTool.bottom - rcTool.top);
}


/******************************Public*Routine******************************\
* IdStr
*
* Loads the given string resource ID into the passed storage.
*
\**************************************************************************/
LPCTSTR
IdStr(
    int idResource
    )
{
    static TCHAR    chBuffer[ STR_MAX_STRING_LEN ];

    if(LoadString(hInst, idResource, chBuffer, STR_MAX_STRING_LEN) == 0)
    {
        return g_szEmpty;
    }

    return chBuffer;
}


/*****************************Private*Routine******************************\
* SetDurationLength
*
* Updates pane 0 on the status bar
*
\**************************************************************************/
void
SetDurationLength(
    REFTIME rt
    )
{
    TCHAR   szFmt[64];
    TCHAR   sz[64];

    g_TrackBarScale = 1.0;
    while(rt / g_TrackBarScale > 30000)
    {
        g_TrackBarScale *= 10;
    }

    SendMessage(g_hwndTrackbar, TBM_SETRANGE, TRUE,
        MAKELONG(0, (WORD)(rt / g_TrackBarScale)));

    SendMessage(g_hwndTrackbar, TBM_SETTICFREQ, (WPARAM)((int)(rt / g_TrackBarScale) / 9), 0);
    SendMessage(g_hwndTrackbar, TBM_SETPAGESIZE, 0, (LPARAM)((int)(rt / g_TrackBarScale) / 9));

    wsprintf(sz, TEXT("Length: %s"), FormatRefTime(szFmt, rt));
    SendMessage(g_hwndStatusbar, SB_SETTEXT, 0, (LPARAM)sz);
}


/*****************************Private*Routine******************************\
* SetCurrentPosition
*
* Updates pane 1 on the status bar
*
\**************************************************************************/
void
SetCurrentPosition(
    REFTIME rt
    )
{
    TCHAR   szFmt[64];
    TCHAR   sz[64], szCurrent[64];

    SendMessage(g_hwndTrackbar, TBM_SETPOS, TRUE, (LPARAM)(rt / g_TrackBarScale));

    wsprintf(sz, TEXT("Elapsed: %s"), FormatRefTime(szFmt, rt));

    // Read the current status bar text
    SendMessage(g_hwndStatusbar, SB_GETTEXT, 1, (LPARAM)szCurrent);

    // If this time is different, update the status bar display
    if (_tcscmp(sz, szCurrent))
        SendMessage(g_hwndStatusbar, SB_SETTEXT, 1, (LPARAM)sz);
}


/*****************************Private*Routine******************************\
* FormatRefTime
*
* Formats the given RefTime into the passed in character buffer,
* returns a pointer to the character buffer.
*
\**************************************************************************/
TCHAR *
FormatRefTime(
    TCHAR *sz,
    REFTIME rt
    )
{
    // If we are not seeking in time then format differently

    if(pMpegMovie && pMpegMovie->GetTimeFormat() != TIME_FORMAT_MEDIA_TIME)
    {
        wsprintf(sz,TEXT("%s"),(LPCTSTR) CDisp((LONGLONG) rt,CDISP_DEC));
        return sz;
    }

    int hrs, mins, secs;

    rt += 0.49;

    hrs  =  (int)rt / 3600;
    mins = ((int)rt % 3600) / 60;
    secs = ((int)rt % 3600) % 60;

    wsprintf(sz, TEXT("%02d:%02d:%02d h:m:s"),hrs, mins, secs);

    return sz;
}


/*****************************Private*Routine******************************\
* AboutDlgProc
*
\**************************************************************************/
LRESULT CALLBACK AboutDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_INITDIALOG:
            return TRUE;

        case WM_COMMAND:
            if(wParam == IDOK)
            {
                EndDialog(hWnd, TRUE);
                return TRUE;
            }
            break;
    }
    return FALSE;
}

