564 lines
16 KiB
C++
564 lines
16 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
|
|
|
|
/******************************************************************************
|
|
* textrun.cpp
|
|
* Implementation details for the CTextRun object which is our base class
|
|
* for tracking all the text (be it dictated or typed) in our product.
|
|
******************************************************************************/
|
|
#include "stdafx.h"
|
|
#include "textrun.h"
|
|
|
|
/**********************************************************************
|
|
* CTextRun::CTextRun *
|
|
*--------------------*
|
|
* Description:
|
|
* CTextRun constructor
|
|
**********************************************************************/
|
|
CTextRun::CTextRun() : m_cpTextRange( NULL ),
|
|
m_dwRefCount( NULL )
|
|
{
|
|
} /* CTextRun::CTextRun */
|
|
|
|
|
|
/**********************************************************************
|
|
* CTextRun::~CTextRun *
|
|
*---------------------*
|
|
* Description:
|
|
* CTextRun destructor
|
|
**********************************************************************/
|
|
CTextRun::~CTextRun()
|
|
{
|
|
} /* CTextRun::~CTextRun */
|
|
|
|
/**********************************************************************
|
|
* CTextRun::IncrementCount *
|
|
*--------------------------*
|
|
* Description:
|
|
* Increments a reference count on this text run.
|
|
*
|
|
* Return:
|
|
* The current count
|
|
**********************************************************************/
|
|
DWORD CTextRun::IncrementCount()
|
|
{
|
|
m_dwRefCount++;
|
|
return m_dwRefCount;
|
|
} /* CTextRun::IncrementCount */
|
|
|
|
/**********************************************************************
|
|
* CTextRun::DecrementCount *
|
|
*--------------------------*
|
|
* Description:
|
|
* Decrements a reference count on this text run. If the
|
|
* count drops to zero, the text run is deleted.
|
|
*
|
|
* Return:
|
|
* The current count
|
|
**********************************************************************/
|
|
DWORD CTextRun::DecrementCount()
|
|
{
|
|
_ASSERTE( m_dwRefCount );
|
|
|
|
DWORD dwRefCount = --m_dwRefCount;
|
|
if( !m_dwRefCount )
|
|
{
|
|
delete this;
|
|
}
|
|
return dwRefCount;
|
|
} /* CTextRun::DecrementCount */
|
|
|
|
/******************************************************************************
|
|
* CTextRun::SetTextRange *
|
|
*------------------------*
|
|
* Description:
|
|
* Stores the text range interface pointer for this run
|
|
* Return:
|
|
* S_OK
|
|
* E_POINTER if pTextRange is NULL
|
|
******************************************************************************/
|
|
HRESULT CTextRun::SetTextRange( ITextRange *pTextRange )
|
|
{
|
|
if ( !pTextRange )
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
m_cpTextRange = pTextRange;
|
|
|
|
return S_OK;
|
|
} /* CTextRun::SetTextRange */
|
|
|
|
/**********************************************************************
|
|
* CTextRun::Split *
|
|
*-----------------*
|
|
* Description:
|
|
* Splits up a TextRun so that this text run now ends at lFirstEnd
|
|
* and the second text run begins at lSecondBegin.
|
|
* "This" will now be a shorter range (it will end sooner),
|
|
* and *ppTextRun will point to the new text run
|
|
* (space will be allocated here for *ppTextRun)
|
|
* to be inserted in the list.
|
|
*
|
|
* Return:
|
|
* S_OK
|
|
* E_INVALIDARG
|
|
* E_OUTOFMEMORY
|
|
* Return value of ITextDocument::Range()
|
|
**********************************************************************/
|
|
HRESULT CTextRun::Split( long *plFirstEnd, long *plSecondBegin,
|
|
ITextDocument *cpTextDoc,
|
|
CTextRun **ppTextRun )
|
|
{
|
|
if ( !plFirstEnd || !plSecondBegin || !cpTextDoc || !ppTextRun )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
_ASSERTE( m_cpTextRange );
|
|
if ( !m_cpTextRange )
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// These values won't be changing, since this run has no associated
|
|
// RecoResult.
|
|
// We can chop this block right at these positions.
|
|
long lFirstEnd = *plFirstEnd;
|
|
long lSecondBegin = *plSecondBegin;
|
|
|
|
if ( !WithinRange( lFirstEnd ) || (lFirstEnd > lSecondBegin) )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if ( (GetStart() == lSecondBegin) || (GetEnd() == lFirstEnd) || (lSecondBegin > GetEnd()) )
|
|
{
|
|
// Don't need to do anything, since we are asking for both of the
|
|
// cuts to be made on already-existing TextRun boundaries
|
|
*ppTextRun = NULL;
|
|
return S_OK;
|
|
}
|
|
|
|
*ppTextRun = new CTextRun();
|
|
if ( !(*ppTextRun) )
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
// The latter range will start at lSecondBegin and end where "this" ended
|
|
long lEnd = GetEnd();
|
|
CComPtr<ITextRange> pLatterRange;
|
|
HRESULT hr = cpTextDoc->Range( lSecondBegin, lEnd, &pLatterRange );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
(*ppTextRun)->SetTextRange( pLatterRange );
|
|
|
|
// Adjust the end of "this"'s range; it will end at lFirstEnd
|
|
m_cpTextRange->SetEnd( lFirstEnd );
|
|
|
|
return S_OK;
|
|
|
|
} /* CTextRun::Split */
|
|
|
|
|
|
/**********************************************************************
|
|
* CTextRun::Concatenate *
|
|
*-----------------------*
|
|
* Description:
|
|
* If possible, concatenates pNext (pPrev if fConcatAfter is false)
|
|
* onto the end of this.
|
|
* Another CTextRun can always be concatenated on, unless it
|
|
* contains dictation.
|
|
*
|
|
* Return:
|
|
* E_NOMERGE if could not be merged (because pTextRun is dictation)
|
|
* E_FULLMERGE
|
|
**********************************************************************/
|
|
MERGERESULT CTextRun::Concatenate( CTextRun *pTextRun, bool fConcatAfter )
|
|
{
|
|
if ( !pTextRun || !m_cpTextRange )
|
|
{
|
|
return E_NOMERGE;
|
|
}
|
|
|
|
// Check for compatibility: In this case, neither mergee can be
|
|
// a dict run
|
|
if ( IsDict() || pTextRun->IsDict() )
|
|
{
|
|
return E_NOMERGE;
|
|
}
|
|
|
|
// lNewBound will be the new end (resp. start) of the run, if the
|
|
// concatenation is successful
|
|
long lNewBound;
|
|
|
|
// Concatenation is possible iff one run ends exactly where the other
|
|
// begins.
|
|
// If concatenation is possible, do it.
|
|
if ( fConcatAfter )
|
|
{
|
|
// Will be concatenating pTextRun onto the end of this
|
|
if ( GetEnd() != pTextRun->GetStart() )
|
|
{
|
|
// They are not consecutive runs
|
|
return E_NOMERGE;
|
|
}
|
|
|
|
// lNewBound will be the new end of the run, if the
|
|
// concatenation is successful
|
|
lNewBound = pTextRun->GetEnd();
|
|
|
|
// Swallow up pTextRun by setting our end to its end
|
|
SetEnd( lNewBound );
|
|
|
|
// Make pTextRun degenerate
|
|
pTextRun->SetStart( lNewBound );
|
|
|
|
}
|
|
else
|
|
{
|
|
// Will be concatenating pTextRun onto the beginning of this
|
|
if ( GetStart() != pTextRun->GetEnd() )
|
|
{
|
|
return E_NOMERGE;
|
|
}
|
|
|
|
// lNewBound will be the new start of the run, if the
|
|
// concatenation is successful
|
|
lNewBound = pTextRun->GetStart();
|
|
|
|
// Swallow up pTextRun by setting our start to its start
|
|
SetStart( lNewBound );
|
|
|
|
// Make pTextRun degenerate
|
|
pTextRun->SetEnd( lNewBound );
|
|
}
|
|
|
|
return E_FULLMERGE;
|
|
} /* CTextRun::Concatenate */
|
|
|
|
/**********************************************************************
|
|
* CTextRun::Speak *
|
|
*-----------------*
|
|
* Description:
|
|
* Speaks the text associated with this CTextRun using TTS.
|
|
*
|
|
* Return:
|
|
* S_OK
|
|
* Return value of ITextRange::GetText()
|
|
* Return value of ISpVoice::Speak()
|
|
**********************************************************************/
|
|
HRESULT CTextRun::Speak( ISpVoice &rVoice )
|
|
{
|
|
_ASSERTE( m_cpTextRange );
|
|
if ( !m_cpTextRange )
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// Get the text and speak it.
|
|
BSTR bstrText;
|
|
HRESULT hr = m_cpTextRange->GetText( &bstrText );
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
hr = rVoice.Speak( bstrText, SPF_ASYNC, NULL );
|
|
|
|
::SysFreeString( bstrText );
|
|
}
|
|
return hr;
|
|
} /* CTextRun::Speak */
|
|
|
|
/**********************************************************************
|
|
* CTextRun::Speak() *
|
|
*-------------------*
|
|
* Description:
|
|
* Uses *plStart and *plEnd to find the nearest start and
|
|
* endpoints for speaking (to the nearest word).
|
|
* Returns these values in plStart and plEnd.
|
|
* Speaks the text associated with this CTextRun from *plStart
|
|
* to *plEnd.
|
|
*
|
|
* If *plStart is not within the range, then
|
|
* start at the beginning. If lEnd is not within range, then
|
|
* end at the end.
|
|
*
|
|
* Return:
|
|
* S_OK
|
|
* E_POINTER
|
|
* Return value of CTextRun::Speak()
|
|
**********************************************************************/
|
|
HRESULT CTextRun::Speak( ISpVoice &rVoice,
|
|
long *plStart,
|
|
long *plEnd )
|
|
{
|
|
if ( !plStart || !plEnd )
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
_ASSERTE( m_cpTextRange );
|
|
if ( !m_cpTextRange )
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// Save the old range
|
|
long lOldStart = GetStart();
|
|
long lOldEnd = GetEnd();
|
|
|
|
// Out of range start or end means we start speaking from the start
|
|
// or end (resp.) of the text range.
|
|
|
|
if ( WithinRange( *plStart ) )
|
|
{
|
|
// The start needs to be moved
|
|
SetStart( *plStart );
|
|
}
|
|
else
|
|
{
|
|
*plStart = GetStart();
|
|
}
|
|
|
|
if ( WithinRange( *plEnd ) )
|
|
{
|
|
// The end needs to be moved
|
|
SetEnd( *plEnd );
|
|
}
|
|
else
|
|
{
|
|
*plEnd = GetEnd();
|
|
}
|
|
|
|
// Expand to include whole words
|
|
m_cpTextRange->Expand( tomWord, NULL );
|
|
|
|
// Get the new start and end so that we can pass them back
|
|
m_cpTextRange->GetStart( plStart );
|
|
m_cpTextRange->GetEnd( plEnd );
|
|
|
|
// We should never speak past the end of this run, even if expanding to include
|
|
// whole words caused extra text to be included
|
|
// (e.g. if you typed "This is a sentence" and dictated some text that
|
|
// consumed leading spaces right afterwards)
|
|
*plStart = __max( *plStart, lOldStart );
|
|
*plEnd = __min( *plEnd, lOldEnd );
|
|
SetStart( *plStart );
|
|
SetEnd( *plEnd );
|
|
|
|
// Pass to the CTextRun::Speak() that speaks an entire run
|
|
HRESULT hr = Speak( rVoice );
|
|
|
|
// Restore the old range limits
|
|
SetStart( lOldStart );
|
|
SetEnd( lOldEnd );
|
|
|
|
return hr;
|
|
} /* CTextRun::Speak */
|
|
|
|
/**********************************************************************
|
|
* CTextRun::Serialize *
|
|
*---------------------*
|
|
* Description:
|
|
* Serializes the text for this run.
|
|
* Simply writes a small header that contains the start
|
|
* and end indices of this run and indicates that
|
|
* no phrase blob follows.
|
|
* Return:
|
|
* S_OK
|
|
* E_INVALIDARG
|
|
* E_UNEXPECTED
|
|
* Return value of ISequentialStream::Write()
|
|
**********************************************************************/
|
|
HRESULT CTextRun::Serialize( IStream *pStream, ISpRecoContext *pRecoCtxt )
|
|
{
|
|
if ( !pStream )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
_ASSERTE( m_cpTextRange );
|
|
if ( !m_cpTextRange )
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
RUNHEADER runHdr;
|
|
runHdr.lStart = GetStart();
|
|
runHdr.lEnd = GetEnd();
|
|
runHdr.bResultFollows = false;
|
|
|
|
// Write the header to the stream
|
|
ULONG cbWritten;
|
|
HRESULT hr = pStream->Write( &runHdr, sizeof( RUNHEADER ), &cbWritten );
|
|
|
|
return hr;
|
|
} /* CTextRun::Serialize */
|
|
|
|
/**********************************************************************
|
|
* CTextRun::IsConsumeLeadingSpaces *
|
|
*----------------------------------*
|
|
* Description:
|
|
* Sets *pfConsumeLeadingSpaces to true iff leading spaces before
|
|
* lPos would need to be consumed.
|
|
* For a text run this value will always be false, since
|
|
* typed text has no "display attributes"
|
|
* Return:
|
|
* S_OK
|
|
* E_POINTER
|
|
* E_INVALIDARG if lPos is out of range
|
|
**********************************************************************/
|
|
HRESULT CTextRun::IsConsumeLeadingSpaces( const long lPos,
|
|
bool *pfConsumeLeadingSpaces )
|
|
{
|
|
if ( !pfConsumeLeadingSpaces )
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
if ( !WithinRange( lPos ) )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
*pfConsumeLeadingSpaces = false;
|
|
|
|
return S_OK;
|
|
} /* CTextRun::IsConsumeLeadingSpaces */
|
|
|
|
/**********************************************************************
|
|
* CTextRun::HowManySpacesAfter *
|
|
*------------------------------*
|
|
* Description:
|
|
* Returns the number of spaces that would need to precede text
|
|
* if text were to be inserted at position lPos.
|
|
* This number is in the out param puiSpaces.
|
|
* For a text run this value will always be zero, since
|
|
* typed text has no "display attributes"
|
|
* Return:
|
|
* S_OK
|
|
* E_POINTER
|
|
* E_INVALIDARG if lPos is out of range
|
|
**********************************************************************/
|
|
HRESULT CTextRun::HowManySpacesAfter( const long lPos, UINT *puiSpaces )
|
|
{
|
|
if ( !puiSpaces )
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
if ( !WithinRange( lPos ) )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
*puiSpaces = 0;
|
|
|
|
return S_OK;
|
|
} /* CTextRun::HowManySpacesAfter */
|
|
|
|
/**********************************************************************
|
|
* CTextRun::GetStart *
|
|
*--------------------*
|
|
* Description:
|
|
* Returns the start of the associated TextRange, -1 in case
|
|
* of error.
|
|
**********************************************************************/
|
|
long CTextRun::GetStart()
|
|
{
|
|
if ( !m_cpTextRange )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
long lRet;
|
|
HRESULT hr = m_cpTextRange->GetStart( &lRet );
|
|
|
|
return (SUCCEEDED( hr )) ? lRet : -1;
|
|
} /* CTextRun::GetStart */
|
|
|
|
/**********************************************************************
|
|
* CTextRun::SetStart *
|
|
*--------------------*
|
|
* Description:
|
|
* Sets the start of the associated TextRange.
|
|
**********************************************************************/
|
|
void CTextRun::SetStart( long lStart )
|
|
{
|
|
m_cpTextRange->SetStart( lStart );
|
|
} /* CTextRun::SetStart */
|
|
|
|
/**********************************************************************
|
|
* CTextRun::GetEnd *
|
|
*------------------*
|
|
* Description:
|
|
* Returns the end of the associated TextRange, -1 in case
|
|
* of error.
|
|
**********************************************************************/
|
|
long CTextRun::GetEnd()
|
|
{
|
|
if ( !m_cpTextRange )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
long lRet;
|
|
HRESULT hr = m_cpTextRange->GetEnd( &lRet );
|
|
|
|
return (SUCCEEDED( hr )) ? lRet : -1;
|
|
} /* CTextRun::GetEnd */
|
|
|
|
/**********************************************************************
|
|
* CTextRun::SetEnd *
|
|
*------------------*
|
|
* Description:
|
|
* Sets the end of the associated TextRange
|
|
**********************************************************************/
|
|
void CTextRun::SetEnd( long lEnd )
|
|
{
|
|
m_cpTextRange->SetEnd( lEnd );
|
|
} /* CTextRun::SetEnd */
|
|
|
|
|
|
/*********************************************************************
|
|
* CTextRun::WithinRange *
|
|
*-----------------------*
|
|
* Description:
|
|
* Determines whether lCursorPos falls within the range of
|
|
* this CTextRun.
|
|
* Return:
|
|
* true iff RunStart <= lCursorPos < RunEnd
|
|
**********************************************************************/
|
|
bool CTextRun::WithinRange( long lCursorPos )
|
|
{
|
|
if ( !m_cpTextRange )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
long lStart= -1;
|
|
long lEnd= -1;
|
|
|
|
HRESULT hr = m_cpTextRange->GetStart( &lStart );
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpTextRange->GetEnd( &lEnd );
|
|
}
|
|
|
|
return (SUCCEEDED( hr ) && ( lCursorPos >= lStart ) && ( lCursorPos <= lEnd ));
|
|
} /* CTextRun::WithinRange */
|
|
|
|
/*********************************************************************
|
|
* CTextRun::IsDegenerate *
|
|
*------------------------*
|
|
* Description:
|
|
* Determines whether "this" has a degenerate range associated
|
|
* with it.
|
|
* Return:
|
|
* true iff start == end
|
|
**********************************************************************/
|
|
bool CTextRun::IsDegenerate()
|
|
{
|
|
return ( GetStart() == GetEnd() );
|
|
} /* CTextRun::IsDegenerate */ |