// speakn.cpp : Defines the class behaviors for the SpeakN application.
//
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and Microsoft
// QuickHelp and/or WinHelp documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.
//

#include "stdafx.h"

#include "speakn.h"
#include <mmsystem.h>

/////////////////////////////////////////////////////////////////////////////
// Sound helpers

static void PlaySound(LPCSTR lpszSound)
{
	HRSRC hRes; // resource handle to wave file
	HGLOBAL hData;
	BOOL bOk = FALSE;
	if ((hRes = ::FindResource(AfxGetResourceHandle(), lpszSound,
	  "sound")) != NULL &&
	  (hData = ::LoadResource(AfxGetResourceHandle(), hRes)) != NULL)
	{
		// found the resource, play it
		bOk = sndPlaySound((LPCSTR)::LockResource(hData),
			SND_MEMORY|SND_SYNC|SND_NODEFAULT);
		FreeResource(hData);
	}
	if (!bOk)
	{
		static BOOL bReported = FALSE;
		if (!bReported)
		{
			AfxMessageBox("Cannot play sound.\n"
				"This demo works much better with a sound card.");
			bReported = TRUE;       // once please
		}
	}
}

inline static void PlaySound(UINT nIDS)
	{ PlaySound(MAKEINTRESOURCE(nIDS)); }

/////////////////////////////////////////////////////////////////////////////
// CSpeakNDlg

CSpeakNDlg::CSpeakNDlg(BOOL bNoPen)
	: CDialog(bNoPen ? IDD_NOPENDIALOG : CSpeakNDlg::IDD)
{
	m_bNoPen = bNoPen;
	m_lpszNextQuestion = NULL;
	m_bNoAnswerCheck = FALSE;
}

BEGIN_MESSAGE_MAP(CSpeakNDlg, CDialog)
	//{{AFX_MSG_MAP(CSpeakNDlg)
	ON_COMMAND(IDC_REPLAY_SOUND, OnReplaySound)
	ON_COMMAND(IDC_GIVE_UP, OnGiveUp)
	ON_COMMAND(IDC_PICTURE, OnReplaySound)
	ON_EN_CHANGE(IDC_INPUT_EDIT, OnUpdateStatus)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

BOOL CSpeakNDlg::LoadLesson(LPCSTR lpLessonName)
{
	// load lesson from resource
	HRSRC hRes; // resource handle to lesson data
	HGLOBAL hData;
	if ((hRes = ::FindResource(AfxGetResourceHandle(), lpLessonName,
	  "lesson")) == NULL ||
	  (hData = ::LoadResource(AfxGetResourceHandle(), hRes)) == NULL)
		return FALSE;
	m_lpszNextQuestion = (LPCSTR)::LockResource(hData);
	return TRUE;
}

BOOL CSpeakNDlg::OnInitDialog()
{
	ASSERT(m_targetWord.IsEmpty());     // not started yet

	// set the font of the prompt text to something bigger
	LOGFONT logfont;
	memset(&logfont, 0, sizeof(logfont));
	logfont.lfHeight = 40;
	logfont.lfWeight = FW_BOLD;
	strcpy(logfont.lfFaceName, "Arial");        // TrueType font
	VERIFY(m_biggerFont.CreateFontIndirect(&logfont));
	PromptText().SetFont(&m_biggerFont);
	InputEdit().SetFont(&m_biggerFont);

	// load the bitmaps for bitmap buttons
	VERIFY(m_replayButton.AutoLoad(IDC_REPLAY_SOUND, this));
	InputEdit().ShowWindow(FALSE);      // start with input disabled

	// load initial picture
	VERIFY(m_pictureButton.SubclassDlgItem(IDC_PICTURE, this));
	VERIFY(m_pictureButton.LoadBitmaps("intro", NULL, NULL));

	// Make the dialog visible, and update
	ShowWindow(TRUE);       // SHOW_OPENWINDOW
	UpdateWindow();

	PlaySound(IDSOUND_WELCOME);
	AdvanceLesson();
	return FALSE;       // focus already set
}

void CSpeakNDlg::OnReplaySound()
{
	InputEdit().SetFocus();
	PlaySound(m_targetWord);
}

void CSpeakNDlg::OnOK()
{
	// check results
	CString result;
	InputEdit().GetWindowText(result);
	if (result != m_targetWord)
	{
		PlaySound(IDSOUND_INCORRECT);
		AfxMessageBox("Please try again");
		return;
	}
	PlaySound(IDSOUND_CORRECT);
	AdvanceLesson();
}

void CSpeakNDlg::OnGiveUp()
{
	PlaySound(IDSOUND_GIVEUP);
	SetAnswerText(m_targetWord);        // show answer
	OnReplaySound();
	AdvanceLesson();
}

void CSpeakNDlg::SetAnswerText(const char* psz)
{
	// setting the window text for an edit control will cause EN_CHANGE
	//  control notifications, so we lock them out while setting the
	//  text programmatically
	ASSERT(!m_bNoAnswerCheck);
	m_bNoAnswerCheck = TRUE;
	InputEdit().SetWindowText(psz);
	m_bNoAnswerCheck = FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Advancing to the next lesson

void CSpeakNDlg::AdvanceLesson()
{
	if (*m_lpszNextQuestion == '\0')
	{
		// out of questions
		PlaySound(IDSOUND_GOODBYE);
		EndDialog(IDOK);
		return;
	}
	// lesson resource points to 1 keyword string per lesson
	m_targetWord = m_lpszNextQuestion;
	m_lpszNextQuestion += m_targetWord.GetLength() + 1;
	m_targetWord.MakeUpper();       // just in case
	int nBoxes = m_targetWord.GetLength();

	PlaySound(IDSOUND_QUESTION);

	// draw the picture (bitmap with the same name as the target)
	if (!m_pictureButton.LoadBitmaps(m_targetWord))
	{
		AfxMessageBox("Picture unavailable");
		VERIFY(m_pictureButton.LoadBitmaps("intro", NULL, NULL));
			// go back to the initial bitmap
	}
	m_pictureButton.Invalidate(TRUE);

	SetAnswerText("");
	if (m_bNoPen)
	{
		InputEdit().ShowWindow(TRUE);
	}
	else
	{
		// InputEdit() is a CBEdit if PenWindows installed
		CBEdit* pEdit = (CBEdit*)&InputEdit();

		m_pictureButton.UpdateWindow();     // draw the picture now

		// adjust boxed edit item to be centered and the right size
		CRect rect;         // get edit control size
		pEdit->GetWindowRect(&rect);
		ScreenToClient(&rect);                  // in parent coordinate
		int xMid = (rect.left + rect.right) / 2;
		RC rc;              // get pen input info
		VERIFY(pEdit->GetRC(&rc));
		TEXTMETRIC tm;      // get size of font
		{
			CClientDC dc(this);
			CFont* pOldFont = dc.SelectObject(&m_biggerFont);
			dc.GetTextMetrics(&tm);
			dc.SelectObject(pOldFont);
		}

		// set input guides to match big font
		const int xGap = 8;
		const int yGap = 8;
		rc.guide.cHorzBox = nBoxes;
		rc.guide.cxBox = tm.tmMaxCharWidth + tm.tmMaxCharWidth / 3; // 4/3
		rc.guide.cyBox = tm.tmHeight;
		rc.guide.cyBase = tm.tmAscent + yGap;               // uppercase
		int cxEdit = rc.guide.cxBox * nBoxes + yGap;        // extra room for sides
		rect.left = xMid - cxEdit / 2;
		rect.right = xMid + cxEdit / 2;
		rect.bottom = rect.top + tm.tmHeight + yGap;        // extra space
		pEdit->MoveWindow(rect);
		VERIFY(pEdit->SetRC(&rc));
		pEdit->Invalidate(TRUE);
	}
	InputEdit().ShowWindow(TRUE);

	OnUpdateStatus();               // set appropriate face
	OnReplaySound();                // ask question
}

/////////////////////////////////////////////////////////////////////////////
// Happy face status indicator

void CSpeakNDlg::OnUpdateStatus()
{
	CString result;
	InputEdit().GetWindowText(result);

	UINT nIDI = IDI_FACE_NEUTRAL;       // default
	if (result == m_targetWord)
		nIDI = IDI_FACE_HAPPIER;        // exact match
	else if (result.IsEmpty())
		nIDI = IDI_FACE_NEUTRAL;        // not started yet
	else if (result[0] == m_targetWord[0])
		nIDI = IDI_FACE_HAPPY;          // first letter correct
	else
		nIDI = IDI_FACE_SAD;            // not even close
	HICON hNew = ::LoadIcon(AfxGetResourceHandle(), MAKEINTRESOURCE(nIDI));
	ASSERT(hNew != NULL);
	::DestroyIcon(StatusFace().SetIcon(hNew));
	UpdateWindow();                 // draw everything now

	if (m_bNoAnswerCheck)
		return;     // don't update

	if (nIDI == IDI_FACE_HAPPIER)
	{
		// exact match - automatic advance
		OnReplaySound();
		PlaySound(IDSOUND_CORRECT);
		AdvanceLesson();
	}
}

/////////////////////////////////////////////////////////////////////////////
// CSpeakNApp

BOOL CSpeakNApp::InitInstance()
{
	SetDialogBkColor();     // grey look

	BOOL bNoPen = FALSE;
	// Must have PenWindows installed
	if (GetSystemMetrics(SM_PENWINDOWS) == NULL)
	{
		AfxMessageBox("Microsoft Windows for Pen Computing not present.\n"
			"This demo works much better with a pen input device.");
		bNoPen = TRUE;  // no pen-aware controls
	}

	// Creates a simple dialog and do it
	CSpeakNDlg mainDlg(bNoPen);
	if (!mainDlg.LoadLesson("SAMPLE1"))
		return FALSE;
	m_pMainWnd = &mainDlg;
	mainDlg.DoModal();

	// that's all, quit app
	::PostQuitMessage(0);
	return TRUE;
}

CSpeakNApp theApp;

/////////////////////////////////////////////////////////////////////////////
