2025-11-28 00:35:46 +09:00

2166 lines
72 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright © Microsoft Corporation. All rights reserved
/******************************************************************************
* DictationPad.cpp
* This file contains the entry point for the DictationPad application
* and some of the definitions for methods of CDictationPad, the
* main object.
* dictpad_sapi.cpp contains the methods of CDictationPad that
* pertain to the SAPI interfaces used in this app.
******************************************************************************/
#include "stdafx.h"
#include <richedit.h>
#include <commctrl.h>
#include <commdlg.h>
#include "DictationPad.h"
// Stream names for saving and opening DictationPad files
#define DICTPAD_TEXT L"dictpad_text"
#define DICTPAD_RECORESULTS L"dictpad_recoresults"
// Define GUID for the ITextDocument Interface
// (not part of SDK headers or libs)
#ifdef DEFINE_GUID
#undef DEFINE_GUID
#endif
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
EXTERN_C const GUID CDECL name \
= { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
DEFINE_GUID(IID_ITextDocument,0x8CC497C0,0xA1DF,0x11CE,0x80,0x98,
0x00,0xAA,0x00,0x47,0xBE,0x5D);
/************************************************************************
* WinMain() *
*-----------*
* Description:
* Main entry point for the application
**************************************************************************/
int APIENTRY WinMain( __in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance,
__in_opt LPSTR lpCmdLine, __in int nCmdShow )
{
int iRet = 0;
HRESULT hr = CoInitialize( NULL );
if( SUCCEEDED( hr ) )
{
CDictationPad *pDictPad = new CDictationPad( hInstance );
if( pDictPad )
{
if ( pDictPad->Initialize( nCmdShow, lpCmdLine ) )
{
iRet = pDictPad->Run();
}
delete pDictPad;
}
CoUninitialize();
}
return iRet;
} /* WinMain */
/****************************************************************************
* CDictationPad::CDictationPad() *
*--------------------------------*
* Description:
* Constructor
*****************************************************************************/
CDictationPad::CDictationPad( HINSTANCE hInst ) :
m_hAccelTable( NULL ),
m_hInst( hInst ),
m_hRtfLib( NULL ),
m_hEdit( NULL ),
m_hToolBar( NULL ),
m_hStatusBar( NULL ),
m_hFont( NULL ),
m_hAltsButton( NULL ),
m_dwFlags( DP_SHARED_RECOGNIZER ),
m_ullDictInterest(
#ifdef _DEBUG
// DictationPad doesn't actually need SOUND_START
// or SOUND_END
SPFEI(SPEI_SOUND_START) | SPFEI(SPEI_SOUND_END) |
#endif
SPFEI(SPEI_PHRASE_START) | SPFEI(SPEI_RECOGNITION) |
SPFEI(SPEI_RECO_OTHER_CONTEXT) |
SPFEI(SPEI_FALSE_RECOGNITION) | SPFEI(SPEI_HYPOTHESIS) |
SPFEI(SPEI_INTERFERENCE) | SPFEI(SPEI_RECO_STATE_CHANGE) ),
m_ullCCInterest( SPFEI(SPEI_SOUND_START) | SPFEI(SPEI_SOUND_END) |
SPFEI(SPEI_PHRASE_START) | SPFEI(SPEI_RECOGNITION) |
SPFEI(SPEI_INTERFERENCE) | SPFEI(SPEI_RECO_STATE_CHANGE) ),
m_pCandidateList( NULL ),
m_pszFile( NULL )
{
// Zero out the various STRUCTs we are using
memset( (void *) &m_LastSelInfo, 0, sizeof( SELINFO ) );
memset( (void *) &m_CurSelInfo, 0, sizeof( SELINFO ) );
memset( (void *) &m_SpeakInfo, 0, sizeof( SPEAKINFO ) );
}
/****************************************************************************
* CDictationPad::~CDictationPad() *
*---------------------------------*
* Description:
* Destructor
*****************************************************************************/
CDictationPad::~CDictationPad()
{
// Release the richedit/TOM objects. These need to be released before
// the RTF library gets freed.
m_cpRichEdit = NULL;
m_cpTextDoc = NULL;
m_cpTextSel = NULL;
// Delete objects we have new-ed
if ( m_pTextRunList )
{
delete m_pTextRunList;
}
if ( m_pCandidateList )
{
delete m_pCandidateList;
}
if ( m_pRecoEventMgr )
{
delete m_pRecoEventMgr;
}
if( m_hFont )
{
::DeleteObject( m_hFont );
}
if( m_hRtfLib )
{
::FreeLibrary( m_hRtfLib );
}
}
/****************************************************************************
* CDictationPad::Initialize *
*---------------------------*
* Description:
* Set up the windows, as well as the RichEdit objects.
* Calls InitializeSAPIObjs, which in initializes the SAPI objects
* and sets up notification for SAPI events.
*****************************************************************************/
BOOL CDictationPad::Initialize( int nCmdShow, __in_opt LPSTR lpCmdLine )
{
TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];
BOOL bRet = false; // assume failure
// Initialize window strings
::LoadString( m_hInst, IDS_APP_TITLE, szTitle, MAX_LOADSTRING );
::LoadString( m_hInst, IDC_DICTPAD, szWindowClass, MAX_LOADSTRING );
// register our window class
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = m_hInst;
wcex.hIcon = ::LoadIcon(m_hInst, (LPCTSTR)IDI_DICTPAD);
wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = (LPCTSTR)IDC_DICTPAD;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = ::LoadIcon( wcex.hInstance, (LPCTSTR)IDI_SMALL );
ATOM atomRet = ::RegisterClassEx( &wcex );
HRESULT hr = E_FAIL;
if( atomRet )
{
// Set up the text run list and the reco event manager
m_pTextRunList = new CTextRunList();
m_pRecoEventMgr = new CRecoEventMgr( m_hInst );
if ( !m_pTextRunList || !m_pRecoEventMgr )
{
return false;
}
// Load DLL for Rich Edit 3.0
m_hRtfLib = LoadLibrary( _T("RICHED20.DLL") );
// Create the main application window
m_hClient = ::CreateWindow( szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, m_hInst, this );
// The richedit window (m_hEdit) will have been created by the WM_CREATE
// message-handling in the WNDPROC for the main window
if( m_hClient && m_hEdit )
{
// Set interest in various events from the richedit control
::SendMessage( m_hEdit, EM_SETEVENTMASK, 0,
ENM_KEYEVENTS | ENM_MOUSEEVENTS | ENM_CHANGE | ENM_UPDATE | ENM_SELCHANGE | ENM_SCROLL );
// Show the window if it is supposed to be shown upon startup
::ShowWindow( m_hClient, nCmdShow );
// Update the window so that the window is drawn without having to
// wait for all of the SAPI initialization
::UpdateWindow( m_hClient );
// Accelerators
m_hAccelTable = ::LoadAccelerators( m_hInst, (LPCTSTR)IDC_DICTPAD );
// Get the TOM objects (an ITextDocument and an ITextSelection)
// from the richedit control
::SendMessage( m_hEdit, EM_GETOLEINTERFACE, 0, (LPARAM)(LPVOID FAR *)&m_cpRichEdit );
if ( !m_cpRichEdit )
{
::MessageBox( m_hClient, _T("Error getting the RichEdit interface"), NULL, MB_OK );
return false;
}
hr = m_cpRichEdit->QueryInterface( IID_ITextDocument, (void**)&m_cpTextDoc );
if( SUCCEEDED( hr ) )
{
// Hand the pointer to the ITextDocument for this document to the
// data members that need that pointer.
m_pTextRunList->SetTextDoc( m_cpTextDoc );
hr = m_cpTextDoc->GetSelection( &m_cpTextSel );
}
else
{
::MessageBox( m_hClient, _T("Error getting ITextDocument"), NULL, MB_OK );
}
if ( SUCCEEDED( hr ) )
{
// Hand the pointer to the TOM objects for this document to the
// data members that need that pointer.
m_pRecoEventMgr->SetTextSel( m_cpTextSel );
m_pCandidateList->SetTextSel( m_cpTextSel );
}
else
{
::MessageBox( m_hClient, _T("Error getting ITextSelection"), NULL, MB_OK );
}
// Do the initialization of SAPI objects
if ( SUCCEEDED( hr ) )
{
hr = InitializeSAPIObjs();
}
}
}
if ( FAILED( hr ) )
{
// Bail, since some initialization did not happen correctly
::MessageBox( m_hClient, _T("Cannot initialize DictationPad; exiting."), NULL, MB_OK );
::DestroyWindow( m_hClient );
}
if ( SUCCEEDED( hr ) && lpCmdLine && *lpCmdLine )
{
// lpInitialFileName will come surrounded by quotation marks,
// which we need to strip off
CA2T pszFileName(lpCmdLine);
if ( !pszFileName )
{
return FALSE;
}
TCHAR *pchBeginQuote = _tcschr( pszFileName, _T('\"') );
if ( pchBeginQuote )
{
// Skip to the character after the first quote
pchBeginQuote++;
TCHAR *pchEndQuote = _tcschr( pchBeginQuote, _T('\"') );
if ( pchEndQuote )
{
// Cut it off at the end quote
*pchEndQuote = 0;
}
}
else
{
// If no quotes, take the whole string
pchBeginQuote = pszFileName;
}
hr = DoFileOpen( pchBeginQuote );
}
return SUCCEEDED( hr );
} /* CDictationPad::Initialize */
/****************************************************************************************
* CDictationPad::Run() *
*----------------------*
* Description:
* Contains the message loop for the application
*****************************************************************************************/
int CDictationPad::Run()
{
// Main message loop:
MSG msg;
while( ::GetMessage( &msg, NULL, 0, 0 ) )
{
if( !::TranslateAccelerator( m_hClient, m_hAccelTable, &msg ) )
{
::TranslateMessage( &msg );
::DispatchMessage( &msg );
}
}
return (int) msg.wParam;
} /* CDictationPad::Run */
/*****************************************************************************************
* CDictationPad::WndProc() *
*--------------------------*
* Description:
* Main message handler for DictationPad.
******************************************************************************************/
LRESULT CALLBACK CDictationPad::WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
int wmId, wmEvent;
HRESULT hr;
HMENU hMenu;
// The CDictationPad object was set to be the USERDATA in the window long
// associated with this window when WM_CREATE was called
CDictationPad * pThis = (CDictationPad *)(LONG_PTR)::GetWindowLongPtr( hWnd, GWLP_USERDATA );
switch( message )
{
case WM_CREATE :
{
// store pointer to the object that created this window
pThis = (CDictationPad *)(((LPCREATESTRUCT)lParam)->lpCreateParams);
_ASSERTE(pThis);
if ( pThis == NULL )
{
break;
}
::SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) pThis );
// This call draws the toolbar buttons, gets the richedit control, etc.
if ( !( pThis->InitializeWindow( hWnd ) ) )
{
::DestroyWindow( hWnd );
break;
}
::SetFocus( pThis->m_hEdit );
// Indicate whether whole words are being selected
hMenu = ::GetMenu( hWnd );
::CheckMenuItem( hMenu, IDM_WHOLE_WORDS,
(pThis->m_dwFlags & DP_WHOLE_WORD_SEL) ? MF_CHECKED : MF_UNCHECKED );
// Set the appropriate menu info for the type of engine
::CheckMenuItem( hMenu, IDM_SHAREDENGINE,
(pThis->m_dwFlags & DP_SHARED_RECOGNIZER) ? MF_CHECKED : MF_UNCHECKED );
//--- get us into dictation mode
::SendMessage( hWnd, WM_COMMAND, IDM_DICTATION_MODE, 0 );
break;
}
case WM_SIZE:
{
// If the frame changes size, change the size of the edit control,
// taking the toolbar & status bar into consideration
RECT r;
::GetWindowRect( pThis->m_hToolBar, &r );
LONG lToolBarHeight = r.bottom - r.top;
::GetWindowRect( pThis->m_hStatusBar, &r );
LONG lStatusBarHeight = r.bottom - r.top;
::SetWindowPos( pThis->m_hToolBar, hWnd, 0, 0, LOWORD(lParam),
lToolBarHeight, SWP_NOMOVE | SWP_NOZORDER );
::SetWindowPos( pThis->m_hEdit, hWnd, 0, 0, LOWORD(lParam),
HIWORD(lParam) - lToolBarHeight - lStatusBarHeight,
SWP_NOMOVE | SWP_NOZORDER );
::SetWindowPos( pThis->m_hStatusBar, hWnd, 0,
HIWORD(lParam) - lStatusBarHeight, LOWORD(lParam), lStatusBarHeight,
SWP_NOZORDER );
break;
}
case WM_COMMAND:
// If the SR engine is currently cranking, then save this message for later
if ( pThis->m_pRecoEventMgr->IsProcessingPhrase() )
{
// The message will get handled later, when the result comes back
pThis->m_pRecoEventMgr->QueueCommand( hWnd, message, wParam, lParam );
break;
}
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Look for notifications from the edit control about gaining and losing
// input focus
if ( ((HWND) lParam) == pThis->m_hEdit )
{
switch ( wmEvent )
{
case EN_KILLFOCUS:
if ( pThis->m_dwFlags & DP_DICTATION_MODE )
{
// Turn off the "mic"
pThis->SetGrammarState( false );
}
break;
case EN_SETFOCUS:
if ( pThis->m_dwFlags & DP_DICTATION_MODE )
{
// Restore the "mic" to its state when we last lost focus
pThis->SetGrammarState( pThis->m_dwFlags & DP_MICROPHONE_ON );
}
// Trigger an update so that the alternates button appears
// if appropriate
pThis->m_hAltsButton = pThis->m_pCandidateList->Update(
pThis->m_pTextRunList );
break;
}
break;
}
// Handle menu item selections
switch (wmId)
{
//----------------------
// FILE
//----------------------
case ID_FILE_NEW:
{
hr = pThis->DoFileNew();
if ( FAILED( hr ) )
{
MessageBoxFromResource( hWnd, IDS_CANNOTFILENEW, NULL, MB_ICONEXCLAMATION );
::DestroyWindow( hWnd );
}
break;
}
case ID_FILE_OPEN:
{
hr = pThis->DoFileOpen( NULL );
if ( FAILED( hr ) )
{
MessageBoxFromResource( hWnd, IDS_CANNOTOPEN, NULL, MB_ICONEXCLAMATION );
// Open a new file instead
::SendMessage( pThis->m_hClient, WM_COMMAND, ID_FILE_NEW, 0 );
}
break;
}
case ID_FILE_SAVE:
case ID_FILE_SAVEAS:
{
if ( (ID_FILE_SAVE == wmId) && pThis->m_pszFile )
{
// The file already has a name.
// Just save under the existing name
hr = pThis->DoFileSave();
}
else
{
// Need to get a name
hr = pThis->DoFileSaveAs();
}
if (FAILED(hr))
{
TCHAR pszCaptionText[ MAX_LOADSTRING ];
::LoadString( pThis->m_hInst, IDS_CANNOTSAVE,
pszCaptionText, MAX_LOADSTRING );
if (STG_E_ACCESSDENIED == hr)
{
// The user has a readonly file open
MessageBoxFromResource( hWnd, IDS_ACCESSDENIED, pszCaptionText,
MB_ICONEXCLAMATION );
// Ask user to save under a different name
::SendMessage( pThis->m_hClient, WM_COMMAND, ID_FILE_SAVEAS, 0 );
}
else
{
MessageBoxFromResource( hWnd, IDS_ERRORSAVING, pszCaptionText,
MB_ICONEXCLAMATION );
}
}
// Return 1 only if we saved the file
return (S_OK == hr);
break;
}
case IDM_EXIT:
::SendMessage( hWnd, WM_CLOSE, 0, 0 );
break;
case ID_EDIT_CUT:
// Cut the selected text to the clipboard
pThis->m_cpTextSel->Cut( NULL );
break;
case ID_EDIT_COPY:
// Copy the selected text to the clipboard
pThis->m_cpTextSel->Copy( NULL );
break;
case ID_EDIT_PASTE:
// Paste the text from the clipboard into the document
// This flag will be used when the selection changed is processed.
// It will indicate that there is new text present, even if the
// length of the text in the document has not changed.
pThis->m_dwFlags |= DP_JUST_PASTED_TEXT;
pThis->m_cpTextSel->Paste( NULL, 0 );
pThis->m_dwFlags &= ~DP_JUST_PASTED_TEXT;
break;
case IDM_FONT:
{
LOGFONT lf;
CHOOSEFONT cf;
ZeroMemory( &cf, sizeof(cf) );
cf.lStructSize = sizeof(cf);
cf.lpLogFont = &lf;
cf.Flags = CF_SCREENFONTS;
if( ::ChooseFont( &cf ) ) // Display the choose font dialog
{
::DeleteObject( (HGDIOBJ)pThis->m_hFont );
pThis->m_hFont = ::CreateFontIndirect( &lf );
::SendMessage( pThis->m_hEdit, WM_SETFONT, (WPARAM)pThis->m_hFont, MAKELPARAM(true, 0) );
::SetFocus( pThis->m_hEdit );
}
}
break;
case IDM_DICTATION_MODE:
case IDM_COMMAND_MODE:
// Choose between dictation mode and command mode
hr = pThis->SetMode( IDM_DICTATION_MODE == wmId );
if (FAILED(hr))
{
MessageBoxFromResource( hWnd, IDS_CANNOTSWITCHMODES, NULL,
MB_ICONEXCLAMATION );
}
break;
case IDM_VOICE_TRAINING:
{
// Brings up the SR-engine-specific user training UI
pThis->m_cpRecoEngine->DisplayUI(hWnd, NULL, SPDUI_UserTraining, NULL, 0);
}
break;
case IDM_MICROPHONE_SETUP:
{
// Brings up the SR-engine-specific mic training UI
pThis->m_cpRecoEngine->DisplayUI(hWnd, NULL, SPDUI_MicTraining, NULL, 0);
}
break;
case IDM_ADDREMOVEWORDS:
// Brings up the SR-engine-specific Add/Remove Words UI
pThis->RunAddDeleteUI();
break;
case IDM_MODE_TOGGLE:
// Toggles between dictation mode and command mode
if( pThis->m_dwFlags & DP_DICTATION_MODE )
{
::SendMessage( hWnd, WM_COMMAND, IDM_COMMAND_MODE, 0 );
}
else
{
::SendMessage( hWnd, WM_COMMAND, IDM_DICTATION_MODE, 0 );
}
break;
case IDM_WHOLE_WORDS:
// Toggles between selecting whole words and normal selection
hMenu = ::GetMenu( hWnd );
if( pThis->m_dwFlags & DP_WHOLE_WORD_SEL )
{
pThis->m_dwFlags &= ~DP_WHOLE_WORD_SEL;
::CheckMenuItem( hMenu, IDM_WHOLE_WORDS, MF_UNCHECKED );
}
else
{
pThis->m_dwFlags |= DP_WHOLE_WORD_SEL;
::CheckMenuItem( hMenu, IDM_WHOLE_WORDS, MF_CHECKED );
}
break;
case IDM_SHAREDENGINE:
// Toggles between inproc reco engine and shared reco engine
hMenu = ::GetMenu( hWnd );
if ( pThis->m_dwFlags & DP_SHARED_RECOGNIZER )
{
pThis->m_dwFlags &= ~DP_SHARED_RECOGNIZER;
::CheckMenuItem( hMenu, IDM_SHAREDENGINE, MF_UNCHECKED );
}
else
{
pThis->m_dwFlags |= DP_SHARED_RECOGNIZER;
::CheckMenuItem( hMenu, IDM_SHAREDENGINE, MF_CHECKED );
}
// When the reco engine changes, all of the SAPI objects need
// to be unplugged and reinitialized.
if ( FAILED(pThis->InitializeSAPIObjs()) )
{
// New SAPI objects couldn't get set up: need to bail
::DestroyWindow( hWnd );
}
break;
case IDM_PLAY:
// if we're already speaking then stop
if( pThis->m_dwFlags & DP_IS_SPEAKING )
{
pThis->EndSpeaking();
}
else
{
pThis->DoPlay();
}
break;
case IDM_MIC_TOGGLE:
{
DWORD dwOldFlags = pThis->m_dwFlags;
if ( !(pThis->m_dwFlags & DP_GRAMMARS_ACTIVE) )
{
// If the user sees the microphone button not pressed and tries
// to press it, the grammars should be activated regardless
// of what the state is
pThis->m_dwFlags |= DP_MICROPHONE_ON;
}
else
{
// Flip the microphone flag
pThis->m_dwFlags ^= DP_MICROPHONE_ON;
}
if ( pThis->m_dwFlags & DP_MICROPHONE_ON )
{
// Since the grammars are active, we want the dictation
// window to have the input focus
::SetFocus( pThis->m_hEdit );
}
// Set the microphone accordingly
hr = pThis->SetGrammarState( pThis->m_dwFlags & DP_MICROPHONE_ON );
if ( FAILED( hr ) )
{
// Couldn't do this: switch the microphone flag back
pThis->m_dwFlags = dwOldFlags;
}
break;
}
case IDM_ABOUT:
::DialogBox( pThis->m_hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About );
break;
default:
return ::DefWindowProc( hWnd, message, wParam, lParam );
}
break;
case WM_KILLFOCUS:
OutputDebugString( _T("Kill focus\r\n") );
if ( pThis->m_dwFlags & DP_DICTATION_MODE )
{
// No focus, no microphone
pThis->SetGrammarState( false );
}
break;
case WM_CLOSE:
if ( S_OK == pThis->DoFileClose() )
{
::DestroyWindow( hWnd );
}
break;
case WM_DESTROY:
::PostQuitMessage( 0 );
break;
// The following three window messages were defined by the call to
// ISpNotifyTranslator::InitWindowMessage() in
// CDictationPad::InitSAPICallback (see dictpad_sapi.cpp) to be
// the messages this window receives whenever SAPI wants to
// notify us of one of the events in which we said we were interested
case WM_DICTRECOEVENT:
{
// Something happened with the dictation recognition context
bool fSuccess = pThis->SRDictEventHandler();
// We expect this to succeed; otherwise exit
_ASSERTE( fSuccess );
if ( !fSuccess )
{
MessageBoxFromResource( hWnd, IDS_UPDATEERROR,
NULL, MB_ICONEXCLAMATION );
::SendMessage( hWnd, WM_CLOSE, 0, 0 );
}
break;
}
case WM_CCRECOEVENT:
// Something happened with the command-and-control reco context
pThis->SRCCEventHandler();
break;
case WM_TTSEVENT:
// Some TTS event happened
pThis->TTSEventHandler();
break;
// The following two messages have to do with "updating", which is
// DictationPad's way of dealing with selection changes in the richedit
// control (which is how DictationPad knows that the text in the document
// has changed
case WM_STOPUPDATE:
pThis->m_dwFlags |= DP_SKIP_UPDATE;
break;
case WM_STARTUPDATE:
pThis->m_dwFlags &= ~DP_SKIP_UPDATE;
break;
case WM_UPDATEALTSBUTTON:
pThis->m_hAltsButton = pThis->m_pCandidateList->Update( pThis->m_pTextRunList );
break;
// Notifications from the richedit control
case WM_NOTIFY:
// Set the tooltips if that is what this notification is about
pThis->SetTooltipText( lParam );
switch ( ((LPNMHDR)lParam)->code)
{
case EN_MSGFILTER:
{
MSGFILTER *pMsgFilter = (MSGFILTER*)lParam;
pThis->ProcessMsgFilter( pMsgFilter );
break;
}
case EN_SELCHANGE:
// Selection has changed; DictationPad needs to update
// its state
hr = pThis->ProcessSelChange( (SELCHANGE *) lParam );
// We expect this to succeed; otherwise exit
_ASSERTE( SUCCEEDED(hr) );
if ( FAILED( hr ) )
{
MessageBoxFromResource( hWnd, IDS_UPDATEERROR,
NULL, MB_ICONEXCLAMATION );
::SendMessage( hWnd, WM_CLOSE, 0, 0 );
}
break;
}
break;
default:
return ::DefWindowProc( hWnd, message, wParam, lParam );
break;
}
return 0;
} /* CDictationPad::WndProc */
/****************************************************************************************
* CDictationPad::InitializeWindow() *
*-----------------------------------*
* Description:
* Sets up and creates the application window, along with the toolbar,
* status bar, and richedit control.
* In order for the alternates list UI to trap window messages from
* the richedit control, we subclass the richedit control so that it
* actually uses the WndProc in candidatelist.cpp
* Return:
* TRUE iff successful
*****************************************************************************************/
BOOL CDictationPad::InitializeWindow( HWND hWnd )
{
// Create a toolbar
m_hToolBar = ::CreateToolbarEx( hWnd, WS_CHILD | WS_BORDER | WS_VISIBLE | TBSTYLE_TOOLTIPS,
IDR_TOOLBAR, 5, m_hInst, IDR_TOOLBAR, NULL, 0, 0, 0, 0, 0, sizeof(TBBUTTON) );
if ( !m_hToolBar )
{
return FALSE;
}
// add the buttons
TBBUTTON tbButtons[5];
tbButtons[0].iBitmap = 0;
tbButtons[0].idCommand = IDM_DICTATION_MODE;
tbButtons[0].fsState = TBSTATE_ENABLED;
tbButtons[0].fsStyle = TBSTYLE_BUTTON;
tbButtons[0].dwData = 0;
tbButtons[0].iString = 0;
tbButtons[1].iBitmap = 1;
tbButtons[1].idCommand = IDM_COMMAND_MODE;
tbButtons[1].fsState = TBSTATE_ENABLED;
tbButtons[1].fsStyle = TBSTYLE_BUTTON;
tbButtons[1].dwData = 0;
tbButtons[1].iString = 0;
tbButtons[2].iBitmap = 0;
tbButtons[2].idCommand = 0;
tbButtons[2].fsState = TBSTATE_ENABLED;
tbButtons[2].fsStyle = TBSTYLE_SEP;
tbButtons[2].dwData = 0;
tbButtons[2].iString = 0;
tbButtons[3].iBitmap = 2;
tbButtons[3].idCommand = IDM_PLAY;
tbButtons[3].fsState = TBSTATE_ENABLED;
tbButtons[3].fsStyle = TBSTYLE_BUTTON;
tbButtons[3].dwData = 0;
tbButtons[3].iString = 0;
tbButtons[4].iBitmap = 3;
tbButtons[4].idCommand = IDM_MIC_TOGGLE;
tbButtons[4].fsState = TBSTATE_ENABLED;
tbButtons[4].fsStyle = TBSTYLE_BUTTON;
tbButtons[4].dwData = 0;
tbButtons[4].iString = 0;
::SendMessage( m_hToolBar, TB_ADDBUTTONS, 5, (LONG_PTR) tbButtons );
::SendMessage( m_hToolBar, TB_AUTOSIZE, 0, 0 );
// Create a status bar
m_hStatusBar = ::CreateStatusWindow( WS_CHILD | WS_VISIBLE, NULL, hWnd, IDC_STATUSBAR );
if ( !m_hStatusBar )
{
return FALSE;
}
// Set up the candidate list UI manager.
// This will register a window class to which our richedit control should belong
m_pCandidateList = new CCandidateList( hWnd, *m_pRecoEventMgr );
if ( !m_pCandidateList )
{
return FALSE;
}
// Create a rich edit control from the candidate UI object's registered parent class
// The richedit control should fill the client area, taking the toolbar & status bar
// into consideration
POINT ptToolBar;
RECT r;
LONG lToolBarHeight;
LONG lStatusBarHeight;
::GetWindowRect( m_hToolBar, &r );
lToolBarHeight = r.bottom - r.top;
ptToolBar.x = r.left;
ptToolBar.y = r.bottom;
::ScreenToClient( hWnd, &ptToolBar );
::GetWindowRect( m_hStatusBar, &r );
lStatusBarHeight = r.bottom - r.top;
::GetClientRect (hWnd, &r);
// Get the modified richedit window class registered by m_pCandidateList
const WNDCLASS *pwcEditWindowClass = m_pCandidateList->GetParentClass();
if ( pwcEditWindowClass )
{
// Create the modified richedit control.
// We pass the candidate UI manager in as lParam
m_hEdit = ::CreateWindowEx( WS_EX_CLIENTEDGE,
pwcEditWindowClass->lpszClassName,
_T(""),
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE |
WS_VSCROLL | ES_AUTOVSCROLL | ES_NOHIDESEL,
0,
ptToolBar.y, r.right,
( r.bottom - ( lToolBarHeight + lStatusBarHeight ) ),
hWnd,
(HMENU)1,
m_hInst,
(LPVOID) m_pCandidateList );
}
if ( !m_hEdit )
{
return FALSE;
}
return TRUE;
} /* CDictationPad::InitializeWindow() */
/****************************************************************************************
* CDictationPad::SetTooltipText() *
*---------------------------------*
* Description:
* Puts the appropriate tooltip text into the LTOOLTIPTEXT struct pointed to
* by lParam.
******************************************************************************************/
void CDictationPad::SetTooltipText( LPARAM lParam )
{
LPTOOLTIPTEXT pToolTipText;
static TCHAR szBuffer[64];
pToolTipText = (LPTOOLTIPTEXT)lParam;
if( pToolTipText->hdr.code == TTN_NEEDTEXT )
{
::LoadString( m_hInst, (UINT) pToolTipText->hdr.idFrom, szBuffer, _countof(szBuffer) );
pToolTipText->lpszText = szBuffer;
}
} /* CDictationPad::SetTooltipText */
/***************************************************************************************
* CDictationPad::UpdateList *
*---------------------------*
* Description:
* Updates the TextRunList by inserting a TextRun that has the given range.
* This is called when the RichEdit control notifies us of some change to the
* text. The node added to the TextRunList is to be a TextRun -- not a
* DictationRun -- since the case of dictated text is handled separately in
* ProcessDictation()
* Return:
* Return value of CTextRunList::Insert()
****************************************************************************************/
HRESULT CDictationPad::UpdateList( long lStart, long lEnd )
{
_ASSERTE( m_pTextRunList );
if ( m_pTextRunList == NULL )
{
return E_UNEXPECTED;
}
// Create a new CTextRun for the text in the range lStart->lEnd
CTextRun *pTextRun = new CTextRun();
if ( !pTextRun )
{
return E_OUTOFMEMORY;
}
// Create a range for that text.
CComPtr<ITextRange> pTextRange;
HRESULT hr = m_cpTextDoc->Range( lStart, lEnd, &pTextRange );
if ( FAILED(hr) )
{
return hr;
}
// Hand the range to the pTextRun
pTextRun->SetTextRange( pTextRange );
// Any text entering the TextRunList should be the normal font.
// This is necessary in the case of typing and hypotheses occurring
// in the same place at the same time in the text.
CComPtr<ITextFont> cpFont;
hr = pTextRange->GetFont( &cpFont );
if ( SUCCEEDED( hr ) )
{
cpFont->SetForeColor( tomAutoColor );
pTextRange->SetFont( cpFont );
}
// Insert the new CTextRun into the TextRunList
hr = m_pTextRunList->Insert( pTextRun );
// The alternate UI needs to be notified of this change
if ( SUCCEEDED( hr ) && m_pCandidateList )
{
m_hAltsButton = m_pCandidateList->Update( m_pTextRunList );
}
return hr;
} /* CDictationPad::UpdateList */
/****************************************************************************************
* CDictationPad::ProcessMsgFilter() *
*-----------------------------------*
* Description:
* Called whenever one of the events that we set in our event mask
* for the edit window happened
******************************************************************************************/
void CDictationPad::ProcessMsgFilter( MSGFILTER *pMsgFilter )
{
switch( pMsgFilter->msg )
{
case WM_CHAR:
{
switch( pMsgFilter->wParam )
{
case VK_BACK:
if( m_dwFlags & DP_WHOLE_WORD_SEL )
{
// Delete whole words at a time
// Use the information on the last selection
// to see whether a selected range was being deleted
// or whether the selection was an IP
long lStart = m_LastSelInfo.selchange.chrg.cpMin;
long lEnd = m_LastSelInfo.selchange.chrg.cpMax;
if ( lStart == lEnd )
{
// Select and then delete the current word
m_cpTextSel->MoveStart( tomWord, -1, NULL );
m_cpTextSel->MoveEnd( tomWord, 1, NULL );
m_cpTextSel->Delete(tomWord, 0, NULL);
}
}
break;
case VK_ESCAPE:
if ( m_dwFlags & DP_IS_SPEAKING )
{
// Esc stops a playback
EndSpeaking();
}
break;
default:
break;
}
break;
}
case WM_LBUTTONDOWN:
{
if( m_dwFlags & DP_WHOLE_WORD_SEL )
{
// Mouse capture will expand the text selection to
// include whole words when the left button is back up
::SetCapture( m_hClient );
}
else if ( m_dwFlags & DP_IS_SPEAKING )
{
// Clicking anywhere in the document should kill the speak
EndSpeaking();
}
break;
}
case WM_LBUTTONUP:
{
if( m_dwFlags & DP_WHOLE_WORD_SEL )
{
// Get the selection, expanded to whole words
::ReleaseCapture();
m_cpTextSel->Expand( tomWord, NULL );
}
break;
}
default:
break;
}
} /* CDictationPad::ProcessMsgFilter */
/****************************************************************************************
* CDictationPad::ProcessSelChange() *
*-----------------------------------*
* Description:
* Called whenever there is an EN_SELCHANGE notification. Updates the state
* info and processes new non-dictated text.
* New text is detected by comparing the current state of the document
* against the state when this function was last called.
* Return:
* S_OK
* E_POINTER
* return value of CDictationPad::UpdateList()
******************************************************************************************/
HRESULT CDictationPad::ProcessSelChange( SELCHANGE *pSelChange )
{
_ASSERTE( m_cpTextSel );
if ( !pSelChange )
{
return E_POINTER;
}
// Store the previous selection info into m_LastSelInfo
// and the previous text length into lLastTextLen
m_LastSelInfo = m_CurSelInfo;
const long lLastTextLen = m_LastSelInfo.lTextLen;
// Get the current selection info and document length
m_CurSelInfo.selchange = *pSelChange;
long lCurrentTextLen;
GETTEXTLENGTHEX gtl;
gtl.flags = GTL_PRECISE | GTL_NUMCHARS;
gtl.codepage = CP_ACP;
lCurrentTextLen = (long) ::SendMessage( m_hEdit,
EM_GETTEXTLENGTHEX, (WPARAM) &gtl, 0 );
m_CurSelInfo.lTextLen = lCurrentTextLen;
// These variables are here for the purpose of clarity
const SELCHANGE lastSelChange = m_LastSelInfo.selchange;
const SELCHANGE curSelChange = m_CurSelInfo.selchange;
// Move the alternates button to the new location
if ( !( m_dwFlags & DP_SKIP_UPDATE ) )
{
m_hAltsButton = m_pCandidateList->Update( m_pTextRunList );
}
// The DP_SKIP_UPDATE flag is set when DictationPad is elsewhere making
// changes to the document (or moving around the selection)
// but handling the changes itself.
if (( m_dwFlags & DP_SKIP_UPDATE ) || ( m_dwFlags & DP_IS_SPEAKING ))
{
// Nothing needs to be done
return S_OK;
}
// Tell the recoevent manager if the selection is being
// moved around, because it is possible that if an utterance
// is currently being processed that we may want it to appear
// here.
if ( lCurrentTextLen == lLastTextLen )
{
m_dwFlags |= DP_SKIP_UPDATE;
HRESULT hr = m_pRecoEventMgr->SelNotify( *m_cpTextSel );
m_dwFlags &= ~DP_SKIP_UPDATE;
if ( FAILED( hr ) )
{
return hr;
}
}
// Find out if this selection is editable (it is not if there is
// hypothesis text here)
CComPtr<ITextRange> cpNextEditableRange;
if ( !(m_pRecoEventMgr->IsEditable( m_cpTextSel, &cpNextEditableRange )) )
{
// Move the selection to an IP at the next point that can be edited
long lStart;
long lEnd;
if ( cpNextEditableRange )
{
cpNextEditableRange->GetStart( &lStart );
cpNextEditableRange->GetEnd( &lEnd );
}
else
{
// This is an error: means IsEditable() ran out of memory
// (see recomgr.cpp)
lStart = lEnd = m_pTextRunList->GetTailEnd();
}
::SendMessage( m_hEdit, EM_SETSEL, lStart, lEnd );
// We can return now, since there is no way that text could have
// been added to a non-editable range
return S_OK;
}
// The following works backwards to determine what must have happened since
// the last time this function was called and whether there is new text
// to deal with (or text that has been deleted).
// Consider all combinations of {nondegenerate, degenerate} among the current
// selection and the last selection
HRESULT hr = E_FAIL;
if ( lastSelChange.chrg.cpMin == lastSelChange.chrg.cpMax )
{
// Last selection was degenerate (an IP).
if ( lCurrentTextLen < lLastTextLen )
{
// Something was deleted; insert the current (degenerate) range
long lStart;
long lEnd;
lStart = lEnd = curSelChange.chrg.cpMax;
hr = UpdateList( lStart, lEnd );
}
else if ( lCurrentTextLen > m_LastSelInfo.lTextLen )
{
// Something has been inserted. The start of the new text
// is the start of the old text selection,
// and the end of the new text is the end of the current
// text selection
long lStart = lastSelChange.chrg.cpMin;;
long lEnd = curSelChange.chrg.cpMax;
hr = UpdateList( lStart, lEnd );
}
else
{
// If the text length has not changed, then no text was changed
hr = S_OK;
}
}
else
{
// Last selection was not degenerate.
// The new text starts at the start of the last selection and ends at the
// end of the current selection (think about what happens when you
// highlight some text and type over it)
long lStart = lastSelChange.chrg.cpMin;
long lEnd = curSelChange.chrg.cpMax;
// If the text length has stayed the same, unless we just did a paste,
// there is no new text to deal with
if (( lLastTextLen == lCurrentTextLen ) && !(m_dwFlags & DP_JUST_PASTED_TEXT) )
{
return S_OK;
}
// Something changed, so we need to update
hr = UpdateList( lStart, lEnd );
}
return hr;
} /* CDictationPad::ProcessSelChange */
/***************************************************************************************
* CDictationPad::DoPlay *
*-----------------------*
* Description:
* Does a playback according to the following rules.
* If the selection is an IP:
* If it's at the end of the document, speak the entire document
* Otherwise speak from the IP until the end of the document.
* Otherwise speak the selection.
* Return:
* S_OK
* S_FALSE if there is nothing to speak
* Return value of CDictationRun::StartSpeaking()
* Return value of CTextRunList::Speak()
****************************************************************************************/
HRESULT CDictationPad::DoPlay()
{
_ASSERTE( m_cpTextSel && m_pTextRunList );
if ( !m_cpTextSel || !m_pTextRunList )
{
return E_UNEXPECTED;
}
if ( m_pTextRunList->GetTailEnd() == 0 )
{
// Nothing to speak
return S_FALSE;
}
// Save the info on the selection so that we can restore it after
// tracking the spoken text.
// This will occur in CDictationPad::EndSpeaking()
long lSpeakStart = 0;
long lSpeakEnd = 0;
m_cpTextSel->GetStart( &lSpeakStart );
m_cpTextSel->GetEnd( &lSpeakEnd );
m_SpeakInfo.lSelStart = lSpeakStart;
m_SpeakInfo.lSelEnd = lSpeakEnd;
if( lSpeakStart == lSpeakEnd ) // denotes insertion point
{
if ( lSpeakStart >= m_pTextRunList->GetTailEnd() )
{
// IP is at the end of the document:
// Speak from beginning to end
lSpeakStart = 0;
}
// Else lSpeakStart is already correct
// Speak all the way to the end
lSpeakEnd = m_pTextRunList->GetTailEnd();
}
// This will deactivate the grammars and set things up for
// a playback
HRESULT hr = StartSpeaking( lSpeakStart, lSpeakEnd );
if ( S_OK != hr )
{
EndSpeaking();
return hr;
}
// Do the playback.
// If we are in the middle of a word or a dictated phrase element,
// the endpoints of the speak will be expanded
hr = m_pTextRunList->Speak( *m_cpVoice, &lSpeakStart, &lSpeakEnd );
if (S_OK != hr)
{
// Something went wrong, or there was nothing to speak; bail out
EndSpeaking();
}
else
{
// If the start and end have been tweaked by m_pTextRunList->Speak(),
// then adjust them so that text tracking looks right
_ASSERTE( m_SpeakInfo.pSpeakRange );
if ( m_SpeakInfo.pSpeakRange == NULL )
{
return E_UNEXPECTED;
}
m_SpeakInfo.pSpeakRange->SetStart( lSpeakStart );
m_SpeakInfo.pSpeakRange->SetEnd( lSpeakEnd );
}
return hr;
} /* CDictationPad::DoPlay */
/***************************************************************************************
* CDictationPad::StartSpeaking *
*------------------------------*
* Description:
* Called when a playback is about to begin.
* Initializes the speak info.
* Return:
* S_OK
* Return value of ITextDocument::Range()
* Return value of CDictationPad::SetGrammarState()
* Return value of ITextRange::Collapse()
****************************************************************************************/
HRESULT CDictationPad::StartSpeaking( long lStartSpeakRange, long lEndSpeakRange )
{
// Stop being interested in SR events for the time being
SetSREventInterest( false );
// Cancel any current IME stuff
HIMC himc = ::ImmGetContext( m_hClient );
::ImmNotifyIME( himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0 );
// Hide the alternates UI for the duration of the speak
m_pCandidateList->ShowButton( false );
// Tell the candidate list UI to turn off character input
m_pCandidateList->StartPlayback();
// Set the range for the playback state info
_ASSERTE( !m_SpeakInfo.pSpeakRange );
HRESULT hr = m_cpTextDoc->Range( lStartSpeakRange, lEndSpeakRange,
&(m_SpeakInfo.pSpeakRange) );
if ( FAILED( hr ) )
{
return hr;
}
// Set the flag and the toolbar buttons
m_dwFlags |= DP_IS_SPEAKING;
::SendMessage( m_hToolBar, TB_SETSTATE, IDM_PLAY,
MAKELONG( TBSTATE_ENABLED | TBSTATE_PRESSED, 0 ) );
::SendMessage( m_hToolBar, TB_SETSTATE, IDM_MIC_TOGGLE,
MAKELONG( 0, 0 ) );
// Disable all the menu items
HMENU hMenu = ::GetMenu( m_hClient );
int nItems = ::GetMenuItemCount( hMenu );
for ( int i=0; i < nItems; i++ )
{
::EnableMenuItem( hMenu, i, MF_BYPOSITION | MF_GRAYED );
}
::DrawMenuBar( m_hClient );
// The focus will remain in the edit window.
// If the user types anything, it will just appear at the beginning
// of the spoken text.
// Deactivate the grammars
hr = SetGrammarState( false );
if ( FAILED( hr ) )
{
return hr;
}
// Set the speak info
m_SpeakInfo.ulCurrentStream = 1;
m_SpeakInfo.pCurrentNode =
m_pTextRunList->Find( lStartSpeakRange );
_ASSERTE( !(lStartSpeakRange) || m_SpeakInfo.pCurrentNode );
if ( lStartSpeakRange && !m_SpeakInfo.pCurrentNode )
{
return E_UNEXPECTED;
}
// Move the selection to an IP at the beginning of the selection for the
// time being (so that the highlighting can still be seen if the selection is
// nondegenerate.
m_cpTextSel->Collapse( tomStart );
return S_OK;
} /* CDictationPad::StartSpeaking */
/***************************************************************************************
* CDictationPad::EndSpeaking *
*----------------------------*
* Description:
* Called when a playback has ended (whether it has
* ended on its own or by the user interrupting it.
* Restores the selection to what it was before
* the playback started
****************************************************************************************/
void CDictationPad::EndSpeaking()
{
// This forces the voice to stop speaking
m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, NULL );
// Bring back the candidate list UI
m_pCandidateList->ShowButton( true );
// Set the flag and the toolbar buttons
m_dwFlags &= ~DP_IS_SPEAKING;
::SendMessage( m_hToolBar, TB_SETSTATE, IDM_PLAY,
MAKELONG( TBSTATE_ENABLED, 0 ) );
::SendMessage( m_hToolBar, TB_SETSTATE, IDM_MIC_TOGGLE,
MAKELONG( TBSTATE_ENABLED, 0 ) );
// Re-enable all the menu items
HMENU hMenu = ::GetMenu( m_hClient );
int nItems = ::GetMenuItemCount( hMenu );
for ( int i=0; i < nItems; i++ )
{
::EnableMenuItem( hMenu, i, MF_BYPOSITION | MF_ENABLED );
}
::DrawMenuBar( m_hClient );
// Restore the default background color
CComPtr<ITextFont> cpFont;
HRESULT hr = m_SpeakInfo.pSpeakRange->GetFont( &cpFont );
if ( SUCCEEDED( hr ) )
{
cpFont->SetBackColor( tomAutoColor );
m_SpeakInfo.pSpeakRange->SetFont( cpFont );
}
// Restore the selection to what it was before the playback started
m_cpTextSel->SetRange( m_SpeakInfo.lSelStart,
m_SpeakInfo.lSelEnd );
// If the selection is no longer visible, then scroll it into view
POINT pt;
hr = m_cpTextSel->GetPoint( tomEnd | TA_BOTTOM | TA_RIGHT,
&(pt.x), &(pt.y) );
if ( hr == S_FALSE )
{
// Out of view
m_cpTextSel->ScrollIntoView( tomStart );
}
// There is no current speaking node
m_SpeakInfo.pCurrentNode = NULL;
// Release the current speaking range
m_SpeakInfo.pSpeakRange->Release();
m_SpeakInfo.pSpeakRange = NULL;
// Restore the input focus and place the candidate button in the
// appropriate place
::SetFocus( m_hEdit );
m_hAltsButton = m_pCandidateList->Update( m_pTextRunList );
// Restore the mic if it was on before the playback started
SetGrammarState( m_dwFlags & DP_MICROPHONE_ON );
// Re-allow character input
m_pCandidateList->EndPlayback();
// Become interested in SR events again
SetSREventInterest( true );
} /* CDictationPad::EndSpeaking */
/***************************************************************************************
* CDictationPad::DoFileNew *
*--------------------------*
* Description:
* Opens a new file, closing any file that's already open.
* Return:
* S_OK
* S_FALSE if the user cancelled
* E_OUTOFMEMORY
* Return value of CDictationPad::DoFileClose()
* Return value of ITextDocument::New()
****************************************************************************************/
HRESULT CDictationPad::DoFileNew()
{
HRESULT hr = DoFileClose();
if ( S_FALSE == hr )
{
return S_FALSE;
}
if ( S_OK == hr )
{
// DoFileClose() better have deleted m_pTextRunList
// and m_pszFile
_ASSERTE( !m_pTextRunList && !m_pszFile );
// Notify m_cpTextDoc that this is a new file,
// and create a new CTextRunList off of m_cpTextDoc.
// Note that we do not want the current TextRunList updated, since
// we threw it away
m_dwFlags |= DP_SKIP_UPDATE;
hr = m_cpTextDoc->New();
m_pTextRunList = new CTextRunList();
m_dwFlags &= ~DP_SKIP_UPDATE;
if ( !m_pTextRunList )
{
return E_OUTOFMEMORY;
}
}
// The TextRunList now uses a different ITextDocument
if ( SUCCEEDED( hr ) )
{
m_pTextRunList->SetTextDoc( m_cpTextDoc );
}
return hr;
} /* CDictationPad::DoFileNew */
/***************************************************************************************
* CDictationPad::DoFileOpen *
*---------------------------*
* Description:
* Opens the file whose full path is lpFileName, or if lpFileName is NULL
* gets the name by using the GetOpenFileName common control.
* Attempts to open the file in the DictationPad format
* (an IStorage with two IStreams, one for text, and one with the
* serialized TextRunList).
* Failing that, opens the file as text.
* Return:
* S_OK
* S_FALSE if the user cancelled
* E_FAIL if the GetOpenFileName() function fails
* Return value of CDictationPad::DoFileClose()
* Return value of ITextDocument::Open()
****************************************************************************************/
HRESULT CDictationPad::DoFileOpen( __in_opt LPTSTR lpFileName )
{
if ( !m_hClient )
{
return E_FAIL;
}
// Stop listening for dictation, and do not start listening
// again unless the user explicitly asks to do so
if ( m_dwFlags & DP_MICROPHONE_ON )
{
::SendMessage( m_hClient, WM_COMMAND, IDM_MIC_TOGGLE, 0 );
}
// Close whatever file is currently open
HRESULT hr;
if ( (hr = DoFileClose()) != S_OK )
{
// User cancelled
return hr;
}
if ( lpFileName )
{
m_pszFile = _tcsdup( lpFileName );
}
else
{
// Start an "Open" dialog box
TCHAR pszFileName[ MAX_PATH ];
*pszFileName = 0;
OPENFILENAME ofn;
size_t ofnsize = (BYTE*)&ofn.lpTemplateName + sizeof(ofn.lpTemplateName) - (BYTE*)&ofn;
ZeroMemory( &ofn, ofnsize);
ofn.lStructSize = (DWORD)ofnsize;
ofn.hwndOwner = m_hClient;
ofn.hInstance = m_hInst;
ofn.lpstrFilter = _T("DictationPad Files (*.dpd;*.txt)\0*.dpd;*.txt\0All Files (*.*)\0*.*\0\0");
ofn.lpstrCustomFilter = NULL;
ofn.nMaxCustFilter = 0;
ofn.nFilterIndex = 0;
ofn.lpstrFile = pszFileName;
ofn.nMaxFile = MAX_PATH;
ofn.nMaxFileTitle = MAX_PATH;
ofn.lpstrFileTitle = NULL;
ofn.lpstrInitialDir = NULL;
ofn.lpstrTitle = NULL;
ofn.Flags = OFN_CREATEPROMPT;
ofn.lpstrDefExt = _T("dpd");
BOOL fSuccess = ::GetOpenFileName( &ofn );
if ( !fSuccess )
{
// Check what caused fSuccess to be false
DWORD dwErr = ::CommDlgExtendedError();
if ( 0 == dwErr )
{
// User cancelled the open
// If there isn't a file currently open, then put up a new file
if ( !m_pszFile )
{
hr = DoFileNew();
}
if ( FAILED( hr ) )
{
return hr;
}
else
{
return S_FALSE;
}
}
else
{
// Error saving
return E_FAIL;
}
}
// Store the file name
_ASSERTE( !m_pszFile );
m_pszFile = _tcsdup( ofn.lpstrFile );
}
if ( !m_pszFile )
{
return E_OUTOFMEMORY;
}
// Open the new file into this document
// We do not want to trigger updates for introducing this new text,
// since we will take care of constructing the CTextRunList
// for this file below.
m_dwFlags |= DP_SKIP_UPDATE;
// Attempt to open an IStorage from that file
IStorage *pStorage = NULL;
hr = ::StgOpenStorage( CT2W(m_pszFile), NULL, STGM_READ | STGM_TRANSACTED,
NULL, 0, &pStorage );
// Attempt to open an IStream from the IStorage
IStream *pStream = NULL;
if ( SUCCEEDED( hr ) && pStorage )
{
// Open an IStream off that storage
hr = pStorage->OpenStream( DICTPAD_TEXT, 0, STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
0, &pStream );
}
// Read in from that IStream
if ( SUCCEEDED( hr ) && pStream )
{
// Set up a callback function (defined below) that will accept
// the text from the IStream and put it into the document
EDITSTREAM es;
es.dwCookie = (DWORD_PTR) pStream;
es.pfnCallback = (EDITSTREAMCALLBACK) EditStreamCallbackReadIn;
::SendMessage( m_hEdit, EM_STREAMIN, SF_RTF, (LPARAM) &es );
// Release the stream
pStream->Release();
pStream = NULL;
}
else
{
// Reading in the text in from the IStorage failed; just open this file as text
CComVariant var;
BSTR bstrFile = ::SysAllocString( CT2W( m_pszFile ) );
var = bstrFile;
hr = m_cpTextDoc->Open( &var, tomReadOnly | tomOpenAlways, 0 );
::SysFreeString( bstrFile );
}
// Create a TextRunList
_ASSERTE( !m_pTextRunList );
m_pTextRunList = new CTextRunList();
m_pTextRunList->SetTextDoc( m_cpTextDoc );
if ( SUCCEEDED( hr ) && pStorage )
{
// We will be here if we could open the DICTPAD_TEXT stream
// from pStorage
// Open the IStream with the serialized result objects
hr = pStorage->OpenStream( DICTPAD_RECORESULTS, 0,
STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
0, &pStream );
}
if ( SUCCEEDED( hr ) && pStream )
{
// Recreate the TextRunList using this stream
hr = m_pTextRunList->Deserialize( pStream, m_cpDictRecoCtxt );
}
//else
if ( FAILED( hr ) || !pStream )
{
// All of the text in this document will be treated as typed
// text
hr = m_pTextRunList->CreateSimpleList();
}
// We need to know how long the text run list is starting out
m_CurSelInfo.lTextLen = m_pTextRunList->GetTailEnd();
if ( pStream )
{
pStream->Release();
}
if ( pStorage )
{
pStorage->Release();
}
// Turn updates back on
m_dwFlags &= ~DP_SKIP_UPDATE;
// Set the saved flag in m_cpTextDoc to true to indicate that
// there are no outstanding changes
m_cpTextDoc->SetSaved( tomTrue );
return hr;
} /* CDictationPad::DoFileOpen */
/***************************************************************************************
* CDictationPad::DoFileSave *
*---------------------------*
* Description:
* Saves the file in the DictationPad format, unless fTextOnly is set,
* in which case the file is just saved as text
* Return:
* S_OK
* E_FAIL if there is no file name to save to
* E_OUTOFMEMORY
* Return value of ITextDocument::Save()
* Return value of StgCreateDocfile()
* Return value of IStorage::CreateStream()
* Return value of IStream::Commit()
* Return value of CTextRunList::Serialize()
* Return value of IStorage::Commit()
****************************************************************************************/
HRESULT CDictationPad::DoFileSave( bool fTextOnly )
{
if ( !m_pszFile )
{
return E_FAIL;
}
// Stop listening for dictation, and do not start listening
// again unless the user explicitly asks to do so
if ( m_dwFlags & DP_MICROPHONE_ON )
{
::SendMessage( m_hClient, WM_COMMAND, IDM_MIC_TOGGLE, 0 );
}
if ( fTextOnly )
{
// Just do a simple save as text
// Get the file name into a VARIANT for ITextDocument::Save()
BSTR bstrFile = ::SysAllocString( CT2W( m_pszFile ) );
if ( !bstrFile )
{
return E_OUTOFMEMORY;
}
CComVariant var = bstrFile;
HRESULT hr = m_cpTextDoc->Save( &var, tomCreateAlways | tomText, 0 );
::SysFreeString( bstrFile );
if ( SUCCEEDED( hr ) )
{
m_cpTextDoc->SetSaved( tomTrue );
}
return hr;
}
// Associate an IStorage with this file
IStorage *pStorage = NULL;
HRESULT hr = ::StgCreateDocfile( CT2W(m_pszFile),
STGM_CREATE | STGM_READWRITE | STGM_TRANSACTED,
0, &pStorage );
// Create a stream for the text of the document in the storage object
IStream *pStream = NULL;
if ( SUCCEEDED( hr ) )
{
hr = pStorage->CreateStream( DICTPAD_TEXT,
STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStream );
}
if ( SUCCEEDED( hr ) )
{
// Write the text out to pStream.
// EditStreamCallbackWriteOut() is a callback function (defined below)
// that will suck the text into pStream
EDITSTREAM es;
es.dwCookie = (DWORD_PTR) pStream;
es.pfnCallback = (EDITSTREAMCALLBACK) EditStreamCallbackWriteOut;
::SendMessage( m_hEdit, EM_STREAMOUT, SF_RTF, (LPARAM) &es );
// Commit the changes to the IStream
hr = pStream->Commit( STGC_DEFAULT );
}
if ( pStream )
{
pStream->Release();
pStream = NULL;
}
if ( SUCCEEDED( hr ) )
{
// The text document has been saved (if this doesn't succeed then
// we will just be prompted to save next time, even if we didn't make
// any changes; this is not serious.
m_cpTextDoc->SetSaved( tomTrue );
}
// Serialize the TextRunList into a separate stream
_ASSERTE( m_pTextRunList );
if ( m_pTextRunList == NULL )
{
return E_UNEXPECTED;
}
if ( SUCCEEDED(hr) )
{
hr = pStorage->CreateStream( DICTPAD_RECORESULTS,
STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStream );
}
if ( SUCCEEDED( hr ) )
{
hr = m_pTextRunList->Serialize( pStream, m_cpDictRecoCtxt );
}
if ( SUCCEEDED( hr ) )
{
hr = pStream->Commit( STGC_DEFAULT );
}
if ( pStream )
{
pStream->Release();
}
// Commit the changes to the IStorage
if ( SUCCEEDED( hr ) )
{
hr = pStorage->Commit( STGC_DEFAULT );
}
if ( pStorage )
{
pStorage->Release();
}
return hr;
} /* CDictationPad::DoFileSave */
/***************************************************************************************
* CDictationPad::DoFileSaveAs *
*-----------------------------*
* Description:
* Saves the file, using the GetSaveFileName common control.
* Return:
* S_OK
* S_FALSE if user cancelled save
* E_FAIL if no client window or if unsuccessful at obtaining the file name
* Return value of CDictationPad::DoFileSave()
****************************************************************************************/
HRESULT CDictationPad::DoFileSaveAs()
{
if ( !m_hClient )
{
return E_FAIL;
}
// Stop listening for dictation, and do not start listening
// again unless the user explicitly asks to do so
if ( m_dwFlags & DP_MICROPHONE_ON )
{
::SendMessage( m_hClient, WM_COMMAND, IDM_MIC_TOGGLE, 0 );
}
// Launch the Save As... common control
TCHAR pszFileName[ MAX_PATH ];
*pszFileName = 0;
OPENFILENAME ofn;
size_t ofnsize = (BYTE*)&ofn.lpTemplateName + sizeof(ofn.lpTemplateName) - (BYTE*)&ofn;
ZeroMemory( &ofn, ofnsize);
ofn.lStructSize = (DWORD)ofnsize;
ofn.hwndOwner = m_hClient;
ofn.hInstance = m_hInst;
ofn.lpstrFilter =
_T("DictationPad Files (*.dpd)\0*.dpd\0Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0\0");
ofn.lpstrCustomFilter = NULL;
ofn.nMaxCustFilter = 0;
ofn.nFilterIndex = 0;
ofn.lpstrFile = pszFileName;
ofn.nMaxFile = MAX_PATH;
ofn.nMaxFileTitle = MAX_PATH;
ofn.lpstrFileTitle = NULL;
ofn.lpstrInitialDir = NULL;
ofn.lpstrTitle = NULL;
ofn.Flags = OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT;
ofn.lpstrDefExt = _T("dpd");
BOOL fSuccess = ::GetSaveFileName( &ofn );
if ( fSuccess )
{
// Get the name of the file to save to
if ( m_pszFile )
{
// If there is already a file name in use, ditch its name
free( m_pszFile );
}
m_pszFile = _tcsdup( ofn.lpstrFile );
// If the filter index is 2, that means the user wants a ".txt" extension
return DoFileSave( 2 == ofn.nFilterIndex );
}
else
{
// Check what caused fSuccess to be false
DWORD dwErr = ::CommDlgExtendedError();
if ( 0 == dwErr )
{
// User cancelled the save
return S_FALSE;
}
else
{
// Error saving
return E_FAIL;
}
}
} /* CDictationPad::DoFileSaveAs */
/***************************************************************************************
* CDictationPad::DoFileClose *
*----------------------------*
* Description:
* If there is currently an open file, closes the file (checking to see if
* it needs saving).
* Always frees the file name string if successful
* Return:
* S_OK
* S_FALSE if the user cancelled the close
* E_FAIL if there is no associated window
****************************************************************************************/
HRESULT CDictationPad::DoFileClose()
{
if ( !m_hClient )
{
return E_FAIL;
}
if ( m_pCandidateList )
{
m_pCandidateList->ShowButton( false );
}
// Have unsaved changes been made?
long lSaved = tomFalse;
m_cpTextDoc->GetSaved( &lSaved );
// If there is something to save, then confirm the close with the user
if ( (lSaved != tomTrue) && m_pTextRunList && (m_pTextRunList->GetTailEnd() > 0) )
{
// Find out whether the user wants to save the current file
TCHAR pszCaption[ MAX_LOADSTRING ];
::LoadString( m_hInst, IDS_APP_TITLE, pszCaption, MAX_LOADSTRING );
int iResult = MessageBoxFromResource( m_hClient, IDS_CONFIRMCLOSE, pszCaption,
MB_YESNOCANCEL | MB_ICONEXCLAMATION );
if ( IDCANCEL == iResult )
{
return S_FALSE;
}
else if ( IDYES == iResult )
{
// Save the file before closing it.
LRESULT iSaved = ::SendMessage( m_hClient, WM_COMMAND, ID_FILE_SAVE, 0 );
if ( !iSaved )
{
// Save cancelled or did not work
return S_FALSE;
}
}
else
{
// The user said don't save.
// If we opened the file via ITextDocument::Open(), TOM will save
// the file anyways, unless we set the saved flag, which we do here
m_cpTextDoc->SetSaved( tomTrue );
}
}
// Free the memory associated with this file
if ( m_pszFile )
{
free( m_pszFile );
m_pszFile = NULL;
}
if ( m_pTextRunList )
{
delete m_pTextRunList;
m_pTextRunList = NULL;
}
return S_OK;
} /* CDictationPad::DoFileClose */
/*****************************************************************************************
* EditStreamCallbackReadIn() *
*----------------------------*
* Description:
* Repeatedly called by RichEdit when an EM_STREAMIN message is sent.
* dwCookie is the IStream from which we will be reading
* pbBuff is the buffer into which we will put the bytes from the IStream
* cb is the number of bytes we are requested to read, and *pcb is the
* number of bytes actually read.
* This function ceases to be called when it returns a nonzero value or sets
* *pcb to 0.
* Return values:
* 0 if successful
* -1 if error
*******************************************************************************************/
DWORD CALLBACK EditStreamCallbackReadIn( DWORD_PTR dwCookie, LPBYTE pbBuff, ULONG cb, ULONG *pcb )
{
if ( !dwCookie || !pbBuff || !pcb )
{
// Error: invalid stream
return -1;
}
IStream *pStream = (IStream *) dwCookie;
HRESULT hr = pStream->Read( pbBuff, cb, pcb );
if ( SUCCEEDED( hr ) )
{
return 0;
}
else
{
return -1;
}
}
/*****************************************************************************************
* EditStreamCallbackWriteOut() *
*------------------------------*
* Description:
* Repeatedly called by RichEdit when an EM_STREAMOUT message is sent.
* dwCookie is the IStream to which we will be writing
* pbBuff is the buffer from which we will get the bytes to write to the IStream
* cb is the number of bytes we are requested to write, and *pcb is the
* number of bytes actually written.
* This function ceases to be called when it returns a nonzero value or sets
* *pcb to 0.
* Return values:
* 0 if successful
* -1 if error
*******************************************************************************************/
DWORD CALLBACK EditStreamCallbackWriteOut( DWORD_PTR dwCookie, LPBYTE pbBuff, ULONG cb, ULONG *pcb )
{
if ( !dwCookie || !pbBuff || !pcb )
{
// Error: invalid stream
return -1;
}
IStream *pStream = (IStream *) dwCookie;
HRESULT hr = pStream->Write( pbBuff, cb, pcb );
if ( SUCCEEDED( hr ) )
{
return 0;
}
else
{
return -1;
}
}
/*****************************************************************************************
* MessageBoxFromResource() *
*--------------------------*
* Description:
* Calls a message box with text loaded from the resource with ID uID.
* Return values:
* error value of LoadString() or return value of MessageBox()
*******************************************************************************************/
int MessageBoxFromResource( HWND hWnd, UINT uID, LPCTSTR lpCaption, UINT uType )
{
TCHAR pszMessageBoxText[ MAX_LOADSTRING ];
int iRet = ::LoadString( (HINSTANCE)(LONG_PTR) ::GetWindowLongPtr( hWnd, GWLP_HINSTANCE ),
uID, pszMessageBoxText, MAX_LOADSTRING );
if ( iRet )
{
iRet = ::MessageBox( hWnd, pszMessageBoxText, lpCaption, uType );
}
return iRet;
} /* MessageBoxFromResource */
/*****************************************************************************************
* About() *
*---------*
* Description:
* Message handler for the "About" box.
******************************************************************************************/
LRESULT CALLBACK About( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
switch( message )
{
case WM_COMMAND:
{
WORD wId = LOWORD(wParam);
switch( wId )
{
case IDOK:
case IDCANCEL:
EndDialog( hDlg, LOWORD(wParam) );
return TRUE;
}
break;
}
}
return FALSE;
} /* About */