//------------------------------------------------------------------------------
// File: DvdSample.cpp
//
// Desc: DVD Playback sample app using IDvdGraphBuilder,
//       IDvdInfo2, and IDvdControl2 interfaces.
//
//       This contains all Windows-related "plumbing" for the application
//
// Copyright (c) 1999-2001 Microsoft Corporation. All rights reserved.
//------------------------------------------------------------------------------

#include <windows.h>
#include <commdlg.h>
#include <commctrl.h>

#include <dshow.h>
#include "resource.h"

#include "DvdCore.h"
#include "Dialogs.h"
#include "DvdSample.h"




//------------------------------------------------------------------------------
// Global data
//------------------------------------------------------------------------------

const PTSTR APPNAME = TEXT("DVDSample");
CApp g_App;




//------------------------------------------------------------------------------
// Type Definitions
//------------------------------------------------------------------------------

typedef UINT (CALLBACK* PFNDLL_STES)(UINT);




//------------------------------------------------------------------------------
// Name: WinMain()
// Desc: This method is the standard Windows program entry point.
//------------------------------------------------------------------------------

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     PSTR      pCmdLine,
                     int       nCmdShow)
{    
    DbgInitialise(hInstance);
    g_App.SetAppValues(hInstance, APPNAME, IDS_APP_TITLE) ;
    
    // Perform application initialization:
    if (! g_App.InitInstance(nCmdShow) ) 
    {
        DbgTerminate();
        return (FALSE) ;
    }
    
    HACCEL hAccelTable ;
    hAccelTable = LoadAccelerators(hInstance, g_App.GetAppName()) ;
    
    // Main message loop:
    MSG msg ;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (! TranslateAccelerator(msg.hwnd, hAccelTable, &msg) )
        {
            TranslateMessage(&msg) ;
            DispatchMessage(&msg) ;
        }
    }
    
    DbgTerminate();
    return (msg.wParam) ;
}


//------------------------------------------------------------------------------
// Name: CApp::CApp()
// Desc: This is the constructor for CApp.  It sets default values and disables 
//       power saving.
//------------------------------------------------------------------------------

CApp::CApp() : m_hInstance(NULL), 
               m_hWnd(NULL), 
               m_bCaptionsOn(false), 
               m_pDvdCore(NULL),
               m_bFullScreenOn(false),
               m_pParentLevels(NULL),
               m_pLangLookup(NULL),
               m_hwndToolBar(0),
               m_dwProhibitedTime(0)
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::CApp()"))) ;
    ZeroMemory(m_szAppTitle, sizeof(m_szAppTitle)) ;

    ZeroMemory(m_szAppName, sizeof(m_szAppName)) ;

    m_pParentLevels = new CParentLevels();
    m_pLangLookup = new CDVDLanguages();

    // notify the power manager that the display is in use so it won't go to sleep
	// SetThreadExecutionState() is a Win98/Win2k API.
	// The following code lets it fail gracefully on Win95

	PFNDLL_STES pfn;
	pfn = (PFNDLL_STES) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")),
		"SetThreadExecutionState");
	if (pfn)
	{
		pfn(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
	}
}


//------------------------------------------------------------------------------
// Name: CApp::~CApp()
// Desc: This is the destructor for CApp.
//------------------------------------------------------------------------------

CApp::~CApp()
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::~CApp()"))) ;

    delete m_pParentLevels;
    delete m_pLangLookup;
    if (m_pDvdCore)
    {
        delete m_pDvdCore;
        m_pDvdCore = NULL;
    }

	// notify the power manager that the display is no longer in use and it can go to sleep
	// SetThreadExecutionState() is a Win98/Win2k API.
	// The following code lets it fail gracefully on Win95

	PFNDLL_STES pfn;
	pfn = (PFNDLL_STES) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")),
		"SetThreadExecutionState");
	if (pfn)
	{
		pfn(ES_CONTINUOUS);
	}
}


//------------------------------------------------------------------------------
// Name: CApp::SetAppValues()
// Desc: This method sets the basic application values like szAppName and hInstance
//------------------------------------------------------------------------------

void CApp::SetAppValues(HINSTANCE hInst, PTSTR szAppName, int iAppTitleResId)
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::SetAppValues()"))) ;
    
    // The Windows stuff
    m_hInstance = hInst ;
    lstrcpy(m_szAppName, APPNAME) ;
    LoadString(m_hInstance, IDS_APP_TITLE, m_szAppTitle, 100) ;
}


//------------------------------------------------------------------------------
// Name: CApp::InitInstance()
// Desc: This method registers and creates our window and toolbar.
//------------------------------------------------------------------------------

bool CApp::InitInstance(int nCmdShow)
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::InitInstance()"))) ;
        
    // Win32 will always set hPrevInstance to NULL, So lets check
    // things a little closer. This is because we only want a single
    // version of this app to run at a time
    m_hWnd = FindWindow (m_szAppName, m_szAppTitle) ;
    if (m_hWnd) 
    {
        // We found another instance of ourself. Lets use that one:
        if (IsIconic(m_hWnd)) 
        {
            ShowWindow(m_hWnd, SW_RESTORE);
        }
        SetForegroundWindow(m_hWnd);
        
        // If this app actually had any methodality, we would
        // also want to communicate any action that our 'twin'
        // should now perform based on how the user tried to
        // execute us.
        return false;
    }
    
    // Register the app main window class
    WNDCLASSEX  wc ;
    wc.cbSize        = sizeof(wc) ;
    wc.style         = CS_HREDRAW | CS_VREDRAW ;
    wc.lpfnWndProc   = (WNDPROC) WndProc ;
    wc.cbClsExtra    = 0 ;
    wc.cbWndExtra    = 0 ;
    wc.hInstance     = m_hInstance ;
    wc.hIcon         = LoadIcon(m_hInstance, MAKEINTRESOURCE(IDI_APPICON));
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW) ;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1) ;
    wc.lpszMenuName  = TEXT("DvdSample_Menu");
    wc.lpszClassName = m_szAppName ;
    wc.hIconSm       = NULL ;

    if (0 == RegisterClassEx(&wc))
    {
        DbgLog((LOG_ERROR, 0, 
            TEXT("ERROR: RegisterClassEx() for app class failed (Error %ld)"), 
            GetLastError())) ;
        return false ;
    }

    // Determine where to put the Application Window
    RECT rDesktop;
    SystemParametersInfo(SPI_GETWORKAREA, NULL, &rDesktop, NULL);

    // Create an instance of the window we just registered
    // locate it at the bottom of the screen (bottom of screen - height of player)
    m_hWnd = CreateWindowEx(0, m_szAppName, m_szAppTitle, WS_OVERLAPPEDWINDOW, //& ~WS_THICKFRAME,
        160, rDesktop.bottom - 150, 300, 150, NULL, NULL, m_hInstance, NULL);
    if (!m_hWnd) 
    {
        DbgLog((LOG_ERROR, 0, 
            TEXT("ERROR: CreateWindowEx() failed (Error %ld)"), 
            GetLastError())) ;
        return false ;
    }

    // We now create the toolbar
    INITCOMMONCONTROLSEX cc;
    cc.dwSize = sizeof(INITCOMMONCONTROLSEX);
    cc.dwICC = ICC_BAR_CLASSES; // register only the toolbar control
    InitCommonControlsEx(&cc);
    
    TBBUTTON tbb[] = 
    {
        0, ID_PLAYBACK_PREVIOUSCHAPTER, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0,
        1, ID_PLAYBACK_REWIND,          TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0,
        2, ID_PLAYBACK_PAUSE,           TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0,
        3, ID_PLAYBACK_PLAY,            TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0,
        4, ID_PLAYBACK_STOP,            TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0,
        5, ID_PLAYBACK_FASTFORWARD,     TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0,
        6, ID_PLAYBACK_NEXTCHAPTER,     TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0,
        9, 0,                           TBSTATE_ENABLED, TBSTYLE_SEP,    0, 0, 0, 0,     
        7, ID_PLAYBACK_MENUROOT,        TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0,
        8, ID_OPTIONS_FULLSCREEN,       TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0,
        9, ID_PLAYBACK_STEPFORWARD,     TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, 0
    };

    m_hwndToolBar = CreateToolbarEx(m_hWnd, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | CCS_TOP
        | TBSTYLE_TOOLTIPS, 1, 10, m_hInstance, IDR_TOOLBAR1, tbb, 11, 0, 0, 0, 0,
        sizeof(TBBUTTON));
    if (!m_hwndToolBar) 
    {
        DbgLog((LOG_ERROR, 0, 
            TEXT("ERROR: CreateToolbarEx() failed (Error %ld)"), 
            GetLastError())) ;
        return false ;
    }

    // we now set up the dvd playback class
    m_pDvdCore = new CDvdCore(m_hInstance, this);
    if (!m_pDvdCore->Init())
    {
        DbgLog((LOG_ERROR, 0, TEXT("ERROR: CDvdCore::Init() failed"))) ;
        return false;
    }

    m_pDvdCore->SetVideoWindowTitle(TEXT("DvdSample Video Window"));

    // and finally, we make the window visible
    ShowWindow(m_hWnd, nCmdShow);
    UpdateWindow(m_hWnd) ;
        
    return true;
}


//------------------------------------------------------------------------------
// Name: WndProc()
// Desc: This method is our main window procedure.  It parses the messages
//       and farms out the work to other procedures.
//------------------------------------------------------------------------------

LRESULT CApp::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HDC  hDC ;
    static PAINTSTRUCT ps ;
    
    switch (message) {
        
    case WM_INITMENUPOPUP:
//        UpdateMenuState() ;
        break ;
        
    case WM_PAINT:
        hDC = BeginPaint(hWnd, &ps) ;
        ASSERT(hDC) ;
        g_App.DrawStatus(hDC);
        EndPaint(hWnd, &ps) ;
        break ;
        
    case WM_KEYUP:
        g_App.KeyProc(wParam, lParam) ;
        break ;
        
    case WM_COMMAND:
        g_App.MenuProc(hWnd, wParam, lParam) ;
        break;
        
    case WM_NOTIFY:
        return g_App.ToolTipProc(hWnd, message, wParam, lParam);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    case WM_SIZE:
        // we do this to cause the toolbar to resize correctly
        SendMessage(g_App.m_hwndToolBar, WM_SIZE, wParam, lParam);
        return DefWindowProc(hWnd, message, wParam, lParam);
        break;
        
    default:
        return (DefWindowProc(hWnd, message, wParam, lParam));
    }
    
    return 0 ; // let Windows know we handled the message

}


//------------------------------------------------------------------------------
// Name: ToolTipProc()
// Desc: This method is a MessageProc for our toolbar tooltips.
//       This checks whether the WM_NOTIFY message is really a tooltip.
//       If it is, we compare it to the buttons on the toolbar and send the right text back.
//       Note that older code would use TTN_NEEDTEXT instead of TTN_GETDISPINFO.
//------------------------------------------------------------------------------

LRESULT CApp::ToolTipProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::ToolTipProc()"))) ;

    LPNMTTDISPINFO TText;
    TText = (LPNMTTDISPINFO) lParam;
    if (TTN_GETDISPINFO == TText->hdr.code)
    {
        switch (TText->hdr.idFrom)
        {
            case ID_PLAYBACK_PREVIOUSCHAPTER:
                TText->lpszText = TEXT("Previous Chapter");
                break;
            case ID_PLAYBACK_REWIND:
                TText->lpszText = TEXT("Rewind");
                break;
            case ID_PLAYBACK_PAUSE:
                TText->lpszText = TEXT("Pause");
                break;
            case ID_PLAYBACK_PLAY:
                TText->lpszText = TEXT("Play");
                break;
            case ID_PLAYBACK_STOP:
                TText->lpszText = TEXT("Stop");
                break;
            case ID_PLAYBACK_FASTFORWARD:
                TText->lpszText = TEXT("FastForward");
                break;
            case ID_PLAYBACK_NEXTCHAPTER:
                TText->lpszText = TEXT("Next Chapter");
                break;
            case ID_PLAYBACK_MENUROOT:
                TText->lpszText = TEXT("Root Menu / Resume");
                break;
            case ID_OPTIONS_FULLSCREEN:
                TText->lpszText = TEXT("Full Screen");
                break;
            case ID_PLAYBACK_STEPFORWARD:
                TText->lpszText = TEXT("Step Forward");
                break;
        }
        return 0;
    }
    else
        return DefWindowProc(hWnd, message, wParam, lParam); // it wasn't a tooltip message
}


//------------------------------------------------------------------------------
// Name: CApp::MenuProc()
// Desc: This method handles all of the menu messages for our application.  It is
//       passed these messages by the main message proc.
//------------------------------------------------------------------------------

LRESULT CApp::MenuProc(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::MenuProc()"))) ;

    HMENU hMenu = GetMenu(hWnd) ;

    //Parse the menu selections:
    switch (LOWORD(wParam)) 
    {
    case ID_PLAYBACK_PLAY:
        m_pDvdCore->Play();
        break;

    case ID_PLAYBACK_PREVIOUSCHAPTER:
        m_pDvdCore->PrevChapter();
        break;

    case ID_PLAYBACK_REWIND:
        m_pDvdCore->Rewind();
        break;

    case ID_PLAYBACK_PAUSE:
        m_pDvdCore->Pause();
        break;

    case ID_PLAYBACK_STOP:
        m_pDvdCore->Stop();
        break;

    case ID_PLAYBACK_FASTFORWARD:
        m_pDvdCore->FastForward();
        break;

    case ID_PLAYBACK_NEXTCHAPTER:
        m_pDvdCore->NextChapter();
        break;

    case ID_PLAYBACK_MENUROOT:
        m_pDvdCore->RootMenu();
        break;

    case ID_OPTIONS_FULLSCREEN:
        {
            m_pDvdCore->ToggleFullScreen();
        }
       break;

    case ID_PLAYBACK_TITLEMENU:
        m_pDvdCore->TitleMenu();
        break;

    case ID_FILE_EXIT:
        DestroyWindow(m_hWnd);
        break;

    case ID_OPTIONS_CLOSEDCAPTION:
        if (false == m_bCaptionsOn) // turn them on
        {
            HMENU hSubMenu = GetSubMenu(hMenu, 2); // Options is the 3rd menu starting with 0
            CheckMenuItem(hSubMenu, ID_OPTIONS_CLOSEDCAPTION, MF_BYCOMMAND | MF_CHECKED);
            if (m_pDvdCore->EnableCaptions(true))
                m_bCaptionsOn = true;
        }
        else // turn them off
        {
            HMENU hSubMenu = GetSubMenu(hMenu, 2); // Options is the 3rd menu starting with 0
            CheckMenuItem(hSubMenu, ID_OPTIONS_CLOSEDCAPTION, MF_BYCOMMAND | MF_UNCHECKED);
            if (m_pDvdCore->EnableCaptions(false))
                m_bCaptionsOn = false;
        }
       break;

    case ID_OPTIONS_SAVEBOOKMARK:
        m_pDvdCore->SaveBookmark();
        break;

    case ID_OPTIONS_RESTOREBOOKMARK:
        m_pDvdCore->RestoreBookmark();
        break;

    case ID_OPTIONS_PARENTALLEVEL:
        if (Nav_Stopped != m_pDvdCore->GetState() && Graph_Stopped2 != m_pDvdCore->GetState())
            // can't change parental level except in stop state
        {
            MessageBox(m_hWnd, 
                TEXT("Can't change parental control level during playback. Please stop (twice) first."), 
                TEXT("Error"), MB_OK | MB_ICONINFORMATION) ;
            break ;
        }
        DialogBox(m_hInstance, MAKEINTRESOURCE(IDD_PARENTLEVELS), m_hWnd,
            reinterpret_cast<DLGPROC>(SelectParentalLevel));

        break;

    case ID_HELP_ABOUTDVDSAMPLE:
        {
            CAboutDlg aDlg(m_hInstance, m_hWnd);
            aDlg.DoModal();
        }
        break;

    case ID_OPTIONS_SUBPICTURE:
        {
            CSPLangDlg aDlg(m_hInstance, m_hWnd);
            aDlg.DoModal();
        }
        break;

    case ID_OPTIONS_AUDIOLANGUAGE:
        {
            CAudioLangDlg aDlg(m_hInstance, m_hWnd);
            aDlg.DoModal();
        }
        break;

    case ID_OPTIONS_ANGLE:
        {
            CAngleDlg aDlg(m_hInstance, m_hWnd);
            aDlg.DoModal();
        }
        break;

    case ID_FILE_SELECTDISC:
        OnSelectDisc();
        break;

    case ID_PLAYBACK_STEPFORWARD:
        m_pDvdCore->FrameStep();
        break;

    case ID_PLAYBACK_GOTO_CHAPTER:
        {
            CChapterDlg aDlg(m_hInstance, m_hWnd);
            if (false == aDlg.DoModal())
                break;
            m_pDvdCore->PlayChapter(aDlg.GetChapter());
        }
        break;
        
    case ID_PLAYBACK_GOTO_TITLE:
        {
            CTitleDlg aDlg(m_hInstance, m_hWnd);
            if (false == aDlg.DoModal())
                break;
            m_pDvdCore->PlayChapterInTitle(aDlg.GetTitle(), aDlg.GetChapter());
        }
        break;

    case ID_PLAYBACK_GOTO_TIME:
        {
            CTimeDlg aDlg(m_hInstance, m_hWnd);

            DVD_HMSF_TIMECODE time = m_pDvdCore->GetTime();
            aDlg.SetTime(time);

            if (false == aDlg.DoModal())
                break;
            m_pDvdCore->PlayTime(aDlg.GetTime());
        }
        break;

    case ID_OPTIONS_GETDISCTEXT:
        m_pDvdCore->GetDvdText();
        break;

    case ID_OPTIONS_GETAUDIOATTRIBUTES:
        m_pDvdCore->GetAudioAttributes();
        break;

    case ID_OPTIONS_GETVIDEOATTRIBUTES:
        m_pDvdCore->GetVideoAttributes();
        break;

    case ID_OPTIONS_GETSUBPICTUREATTRIBUTES:
        m_pDvdCore->GetSPAttributes();
        break;

    case ID_OPTIONS_SETKARAOKEMIXING:
        {
            CKaraokeDlg aDlg(m_hInstance, m_hWnd);
            aDlg.DoModal(); // all work happens in the dialog code
        }
        break;

    default:
        break;
    }

    return 0;
}


//------------------------------------------------------------------------------
// Name: CApp::KeyProc()
// Desc: This method will process all key presses sent to our application window.
//       At present it passes all of the keys along to the DvdCore but this is where
//       you would implement shortcut keys, etc.
//------------------------------------------------------------------------------

LRESULT CApp::KeyProc(WPARAM wParam, LPARAM lParam)
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::KeyProc()"))) ;

    switch (wParam)
    {
    case VK_ESCAPE: // exit full screen
    case VK_RETURN: // activate the currently selected button
    case VK_LEFT: // select the left button
    case VK_RIGHT: // select the right button
    case VK_UP: // select the upper button
    case VK_DOWN: // select the lower button
        return m_pDvdCore->OnKeyEvent(wParam, lParam); // pass these keys on to the Core.
    }
    return DefWindowProc(m_hWnd, WM_KEYUP, wParam, lParam);
}


//------------------------------------------------------------------------------
// Name: CApp::SelectParentalLevel()
// Desc: This method is the MessageProc for the Parental Level Dialog.  It is used
//       to allow the user to select the parental level setting.
//
//       The Dialog wrapper class is not used to demonstrate an alternate way to 
//       handle dialogs.
//------------------------------------------------------------------------------

BOOL CALLBACK CApp::SelectParentalLevel(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::SelectParentalLevel()"))) ;

    switch (message) 
    {
    case WM_INITDIALOG:
        g_App.MakeParentLevelList(hDlg, IDC_LEVEL_LIST) ;
        return TRUE;
        
    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDOK:
            {
                LONG  lLevel ;
                lLevel = SendDlgItemMessage(hDlg, IDC_LEVEL_LIST, LB_GETCURSEL, 
                    static_cast<WPARAM>(0), static_cast<LPARAM>(0) );
                if (CB_ERR == lLevel)
                    DbgLog((LOG_ERROR, 1, TEXT("WARNING: Couldn't get selected parental control level (Error %d)"), lLevel)) ;
                else
                    g_App.m_pDvdCore->SetParentalLevel(g_App.m_pParentLevels->GetValue(lLevel)) ;
            }
            
            // Now fall through to just end the dialog
            
        case IDCANCEL:
            EndDialog(hDlg, TRUE);
            return TRUE;
        }
        break;
        
        default:
            break ;
    }
    
    return FALSE;
}



//------------------------------------------------------------------------------
// Name: CApp::UpdateStatus()
// Desc: This method is notified every time that status of the player changes
//       (Time, title, Chapter, etc.).  This invalidates the client rectangle to force
//       a redraw of the screen.
//------------------------------------------------------------------------------

void CApp::UpdateStatus(void)
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::UpdateStatus()"))) ;

    RECT Rect;
    if (FALSE != GetClientRect(m_hWnd, &Rect))
    {
        Rect.top += 30; // so we don't redraw the toolbar - 30 is just a rough number
        InvalidateRect(m_hWnd, &Rect, TRUE);
    }
    else
    {
        DWORD res = GetLastError();
        DbgLog((LOG_ERROR, 1, TEXT("GetClientRect failed: %#x"), res)) ;
    }
}


//------------------------------------------------------------------------------
// Name: CApp::DrawStatus()
// Desc: This method draws our status test (time, title, chapter) on the screen
//------------------------------------------------------------------------------

void CApp::DrawStatus(HDC hDC)
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::DrawStatus()"))) ;

    TCHAR location[25];
    wsprintf(location, TEXT("Title: %-6uChapter: %u"), m_pDvdCore->GetTitle(), 
        m_pDvdCore->GetChapter());
    TextOut(hDC, 10, 50, location, lstrlen(location));

    TCHAR time[15];
    wsprintf(time, TEXT("Time: %02d:%02d:%02d"), m_pDvdCore->GetTime().bHours, 
        m_pDvdCore->GetTime().bMinutes, m_pDvdCore->GetTime().bSeconds);
    TextOut(hDC, 10, 65, time, lstrlen(time));

    if (timeGetTime() <= (m_dwProhibitedTime + 5000)) // if less than 5 seconds has passed
    {
        SetTextColor(hDC, RGB(255, 0, 0));
        TextOut(hDC, 180, 80, TEXT("Prohibited!"), 11);
    }
}



//------------------------------------------------------------------------------
// Name: CApp::OnSelectDisc()
// Desc: This method brings up a common file dialog to ask the user to find the
//       dvd disc they wish to watch.  We look for the video_ts.ifo file.
//------------------------------------------------------------------------------

bool CApp::OnSelectDisc()
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::OnSelectDisc()"))) ;

    OPENFILENAME  ofn ; // structure used by the common file dialog
    TCHAR         szFileName[MAX_PATH] ;
    
    // Init the filename buffer with *.ifo
    lstrcpy(szFileName, TEXT("*.ifo")) ;
    
    ZeroMemory(&ofn, sizeof(OPENFILENAME)) ;
    ofn.lStructSize = sizeof(OPENFILENAME) ;
    ofn.hwndOwner = m_hWnd ;
    ofn.lpstrFilter = TEXT("IFO Files\0*.ifo\0All Files\0*.*\0") ;
    ofn.nFilterIndex = 1 ;
    ofn.lpstrFile = szFileName ;
    ofn.nMaxFile = sizeof(szFileName) ;
    ofn.lpstrFileTitle = NULL ;
    ofn.lpstrTitle = TEXT("Select DVD-Video Volume") ;
    ofn.nMaxFileTitle = 0 ;
    ofn.lpstrInitialDir = NULL ;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY ;
    
    // GetOpenFileName will bring up the common file dialog in open mode
    if (GetOpenFileName(&ofn)) // user specified a file
    {      
        return m_pDvdCore->SetDirectory(szFileName);
    }
    
    // Either failed or user hit Esc.
    DWORD dw = CommDlgExtendedError() ;
    DbgLog((LOG_TRACE, 3, TEXT("GetOpenFileName() cancelled/failed with error %lu"), dw)) ;

    return false ; // DVD-Video volume not changed
}


//------------------------------------------------------------------------------
// Name: CApp::Prohibited()
// Desc: This method is called by the DVDCore whenever an operation is attempted that
//       is prohibited by UOP.  We set a time value and then invalidate the rectangle.
//------------------------------------------------------------------------------

void CApp::Prohibited()
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::Prohibited()"))) ;

    m_dwProhibitedTime = timeGetTime();

    RECT Rect;
    if (FALSE != GetClientRect(m_hWnd, &Rect))
    {
        Rect.top += 30; // so we don't redraw the toolbar - 30 is just a rough number
        InvalidateRect(m_hWnd, &Rect, TRUE);
    }
    else
    {
        DWORD res = GetLastError();
        DbgLog((LOG_ERROR, 1, TEXT("GetClientRect failed: %#x"), res)) ;
    }
}


//------------------------------------------------------------------------------
// Name: CApp::Exit()
// Desc: This method is part of IDvdCallBack.  It is called by the DVDCore whenever 
//       the playback window si closed.  We then close down the application. 
//------------------------------------------------------------------------------

void CApp::Exit()
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::Exit()"))) ;
    PostQuitMessage(0);
}


//------------------------------------------------------------------------------
// Name: CApp::GetAppWindow()
// Desc: This method is a member of IDVDCallback.  It returns the location and size
//       of the player window to the application.
//------------------------------------------------------------------------------

RECT CApp::GetAppWindow()
{
    RECT rWindow;
    GetWindowRect(m_hWnd, &rWindow);
    return rWindow;
}


//------------------------------------------------------------------------------
// Name: CApp::MakeParentLevelList()
// Desc: This method creates a list of parental levels and adds them to the
//       parental level dialog box's listbox.
//------------------------------------------------------------------------------

int CApp::MakeParentLevelList(HWND hDlg, int iListID)
{
    DbgLog((LOG_TRACE, 5, TEXT("CApp::MakeParentalLevelList(0x%lx, %d)"),
        hDlg, iListID)) ;
    
    int      iLevels = m_pParentLevels->GetCount() ;
    int      iResult ;
    
    // add all defined parental levels
    for (int i = 0 ; i < iLevels ; i++)
    {
        // Add to the listbox now
        iResult = SendDlgItemMessage(hDlg, iListID, LB_ADDSTRING, (WPARAM) 0, 
            (LPARAM)(LPVOID) m_pParentLevels->GetName(i)) ;
        if (LB_ERR == iResult || LB_ERRSPACE == iResult)
        {
            DbgLog((LOG_ERROR, 1, 
                TEXT("Error (%d) adding parental level '%s'(%d) to list"), 
                iResult, m_pParentLevels->GetName(i), i)) ;
        }
    }
    
    if (iLevels > 0)
    {
        iResult = SendDlgItemMessage(hDlg, iListID, LB_SETCURSEL, 
            (WPARAM) m_pDvdCore->GetParentalLevel(), (LPARAM) 0) ;
        if (LB_ERR == iResult)
        {
            DbgLog((LOG_ERROR, 1, 
                TEXT("WARNING: Couldn't set %ld as selected parent level (Error %d)"),
                m_ulParentCtrlLevel, iResult)) ;
        }
    }
    return iLevels ;
}


//------------------------------------------------------------------------------
// Name: CDVDLanguages::CDVDLanguages()
// Desc: This is the constructor.  It sets up our lookup table
//       Only 10 languages have been used here as a sample.
//       The list can be extended to include any language listed in ISO 639.
//------------------------------------------------------------------------------
CDVDLanguages::CDVDLanguages()
{
    DbgLog((LOG_TRACE, 5, TEXT("CDVDLanguages::CDVDLanguages()"))) ;

    m_lcidCodes[0] = 0x0407 ;    m_apszLangNames[0] = TEXT("German") ;
    m_lcidCodes[1] = 0x0409 ;    m_apszLangNames[1] = TEXT("English") ;
    m_lcidCodes[2] = 0x040a ;    m_apszLangNames[2] = TEXT("Spanish") ;
    m_lcidCodes[3] = 0x040c ;    m_apszLangNames[3] = TEXT("French") ;
    m_lcidCodes[4] = 0x0411 ;    m_apszLangNames[4] = TEXT("Japanese") ;
    m_lcidCodes[5] = 0x0412 ;    m_apszLangNames[5] = TEXT("Korean") ;
    m_lcidCodes[6] = 0x0413 ;    m_apszLangNames[6] = TEXT("Dutch") ;
    m_lcidCodes[7] = 0x0816 ;    m_apszLangNames[7] = TEXT("Portuguese") ;
    m_lcidCodes[8] = 0x041d ;    m_apszLangNames[8] = TEXT("Swedish") ;
    m_lcidCodes[9] = 0x0804 ;    m_apszLangNames[9] = TEXT("Chinese") ;
}


//------------------------------------------------------------------------------
// Name: CDVDLanguages::GetLangString()
// Desc: This method is our lookup function.  It takes an LCID language code
//       and returns an English string language name.
//------------------------------------------------------------------------------

bool CDVDLanguages::GetLangString(LCID LangCode, PTSTR pszLang, int iMaxLen)
{
    DbgLog((LOG_TRACE, 5, TEXT("CDVDLanguages::GetLangString()"))) ;

    for (int i = 0 ; i < 10 ; i++)
    {
        if (LangCode == m_lcidCodes[i])  // match!!
        {
            if (iMaxLen < lstrlen(m_apszLangNames[i]))
            {
                lstrcpy(pszLang, m_apszLangNames[i]) ;
                return true ;  // got a match
            }
            // let it fall through if that fails
        }
    }
    if (iMaxLen < lstrlen(TEXT("Unknown")))
    {
        lstrcpy(pszLang, TEXT("Unknown")) ;
    }
    return false ;  // didn't get a match
}


//------------------------------------------------------------------------------
// Name: CParentLevels::CParentLevels()
// Desc: This is the constructor for CParentLevels.  It can be modified to change
//       the parental levels shown to the user.
//       Remember that the mapping of parental levels to MPAA ratings shown here
//       is only valid for the U.S. and Canada.  In other countries, the parental
//       levels will have different specific meanings, although parental level 1
//       is always the least restrictive level and level 8 is the most restrictive.
//------------------------------------------------------------------------------

CParentLevels::CParentLevels()
{
    DbgLog((LOG_TRACE, 5, TEXT("CParentLevels::CParentLevels()"))) ;

    m_iCount = LEVELS ;
    m_alpszNames[0] = TEXT(" G ") ;         m_aiValues[0] = 1 ;
    m_alpszNames[1] = TEXT(" PG ") ;        m_aiValues[1] = 3 ;
    m_alpszNames[2] = TEXT(" PG-13 ") ;     m_aiValues[2] = 4 ;
    m_alpszNames[3] = TEXT(" R ") ;         m_aiValues[3] = 6 ;
    m_alpszNames[4] = TEXT(" NC-17 ") ;     m_aiValues[4] = 7 ;
    m_alpszNames[5] = TEXT(" Not Rated ") ; m_aiValues[5] = 8 ;
}



