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

1612 lines
49 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
/******************************************************************************
* TextRunList.cpp
* This module contains the implementation details of the CTextRunList
* class which is responsible for maintaining the collection of all the
* text runs in the DictationPad program as a doubly linked list.
******************************************************************************/
#include "stdafx.h"
#include "TextRunList.h"
/**********************************************************************
* CTextRunList::CTextRunList *
*----------------------------*
* Description:
* Constructor.
**********************************************************************/
CTextRunList::CTextRunList( ITextDocument *pTextDoc ) :
m_pHead( NULL ),
m_pTail( NULL ),
m_pCurrent( NULL ),
m_cpTextDoc( pTextDoc )
{
} /* CTextRunList::CTextRunList */
/**********************************************************************
* CTextRunList::~CTextRunList *
*-----------------------------*
* Description:
* Destructor
* Deletes everything in the list.
**********************************************************************/
CTextRunList::~CTextRunList()
{
DeleteAllNodes();
} /* CTextRunList::~CTextRunList */
/**********************************************************************
* CTextRunList::CreateSimpleList *
*--------------------------------*
* Description:
* Creates a simple list consisting of a single TextRun
* made up of the entire text of the document.
* Return:
* S_OK
* E_OUTOFMEMORY
* Return value of ITextDocument::Range()
**********************************************************************/
HRESULT CTextRunList::CreateSimpleList()
{
_ASSERTE( !m_pHead && m_cpTextDoc );
if ( m_pHead || !m_cpTextDoc )
{
return E_UNEXPECTED;
}
CComPtr<ITextRange> cpDocRange;
HRESULT hr = m_cpTextDoc->Range( 0, 0, &cpDocRange );
if ( SUCCEEDED( hr ) )
{
// Keep moving the end of the range until it reaches the end of
// the document in order to get a range with the text of
// the entire document
long lDelta;
do
{
cpDocRange->MoveEnd( tomWord, 100, &lDelta );
} while ( lDelta );
// Create a single CTextRun with this range
CTextRun *pTextRun = new CTextRun();
if ( !pTextRun )
{
return E_OUTOFMEMORY;
}
pTextRun->SetTextRange( cpDocRange );
pTextRun->IncrementCount();
// Create a single node with that CTextRun
PTEXTRUNNODE pNode = new TEXTRUNNODE;
if ( !pNode )
{
delete pTextRun;
return E_OUTOFMEMORY;
}
pNode->pTextRun = pTextRun;
pNode->pNext = pNode->pPrev = NULL;
// That node is the only node in the list
m_pHead = m_pTail = m_pCurrent = pNode;
}
return hr;
} /* CTextRunList::CreateSimpleList */
/**********************************************************************
* CTextRunList::Insert *
*----------------------*
* Description:
* Places pTextRun in the list wherever it should go.
* Tries to use pCurrent as a hint.
* Inserting degenerate runs is OK.
* Return:
* S_OK
* E_POINTER
* E_OUTOFMEMORY
* Return value of CTextRunList::AddHead()
* Return value of CTextRunList::MoveCurrentTo()
* Return value of CTextRunList::InsertAfter()
* Return value of CTextRunList::MergeIn()
***********************************************************************/
HRESULT CTextRunList::Insert( CTextRun *pTextRun )
{
if ( !pTextRun )
{
return E_POINTER;
}
// Increment the refcount on the text run
pTextRun->IncrementCount();
// Create a new node with this CTextRun
PTEXTRUNNODE pNode = new TEXTRUNNODE;
if ( !pNode )
{
return E_OUTOFMEMORY;
}
pNode->pTextRun = pTextRun;
// This is the node that is going to be added
m_pNodeToInsert = pNode;
// Insert the node into the list
HRESULT hr;
if (( !m_pHead ) || ( pNode->pTextRun->GetStart() == 0 ))
{
// There's nothing in the list already, or this run starts at zero,
// so add it as the head
hr = AddHead( pNode );
}
else
{
// Move the m_pCurrent pointer to the node that will directly precede this run.
// Nodes will be split here if necessary
hr = MoveCurrentTo( pNode->pTextRun->GetStart() );
// Insert the node after where m_pCurrent now is
if ( SUCCEEDED( hr ) )
{
hr = InsertAfter( m_pCurrent, pNode );
}
}
if ( FAILED( hr ) )
{
return hr;
}
// If we replaced or deleted some entire blocks,
// then we need to delete those nodes here
PTEXTRUNNODE p = pNode->pNext;
PTEXTRUNNODE pNextNode;
PTEXTRUNNODE pPrevNode;
while ( p )
{
pNextNode = p->pNext;
if ( p->pTextRun->IsDegenerate() ||
( p->pTextRun->GetEnd() <= pNode->pTextRun->GetEnd() ))
{
// This node has a degenerate run or a run that ends earlier than ours:
// remove it and keep going
RemoveNode( p );
p = pNextNode;
}
else
{
// Stop at the first non-deletable run
p = NULL;
}
}
// ... And look backwards for the same thing
p = pNode->pPrev;
while ( p )
{
pPrevNode = p->pPrev;
if (p->pTextRun->IsDegenerate() ||
( p->pTextRun->GetStart() >= pNode->pTextRun->GetStart() ))
{
// This node has a degenerate run or a run that starts later than ours:
// remove it and keep going
RemoveNode( p );
p = pPrevNode;
}
else
{
// Stop at the first non-deletable run
p = NULL;
}
}
// Adjust the text ranges of the previous and next nodes
// (This is necessary because the text is actually added before this node
// makes it in, so the TOM has already adjusted the previous and next nodes' ranges to
// cover this new text)
if ( pNode->pNext )
{
pNode->pNext->pTextRun->SetStart( pNode->pTextRun->GetEnd() );
}
if ( pNode->pPrev )
{
pNode->pPrev->pTextRun->SetEnd( pNode->pTextRun->GetStart() );
}
// Now merge this new run in with the neighbors, if possible.
// If pNode is degenerate (or becomes degenerate as a result of merging,
// it will get deleted.
bool fMadeItIn = false;
hr = MergeIn( pNode, &fMadeItIn );
if ( SUCCEEDED( hr ) )
{
return fMadeItIn ? S_OK : S_FALSE;
}
else
{
return hr;
}
} /* CTextRunList::Insert */
/******************************************************************************
* CTextRunList::Speak *
*---------------------*
* Description:
* Starts speaking at the position *plStartSpeaking and ends at
* *plEndSpeaking, unless *plEndSpeaking is -1, in which case it
* speaks until the end of the document has been reached.
* Adjusts *plStartSpeaking and *plEndSpeaking to reflect the
* start and endpoints at which we actually will be speaking
* since they will have to be expanded if they fall in the middle
* of words or phrase elements.
*
* Return:
* S_OK
* S_FALSE if there was nothing to speak
* E_POINTER
* E_INVALIDARG if the speaking limits are not within range
* Return value of CTextRun::Speak()
******************************************************************************/
HRESULT CTextRunList::Speak( ISpVoice &rVoice,
long *plStartSpeaking,
long *plEndSpeaking )
{
_ASSERTE( GetTailEnd() != 0 );
// Check arguments
if ( !plStartSpeaking || !plEndSpeaking )
{
return E_POINTER;
}
if ((( *plEndSpeaking >= 0 ) && ( *plEndSpeaking < *plStartSpeaking )) ||
(*plStartSpeaking < 0) )
{
return E_INVALIDARG;
}
if ( *plEndSpeaking == *plStartSpeaking )
{
// nothing to speak
return S_FALSE;
}
// Get p to the right spot for starting
PTEXTRUNNODE p;
p = Find( *plStartSpeaking );
// If the beginning was not found (i.e. it is at the end), then speak the entire TextRunList.
if ( !p )
{
*plStartSpeaking = 0;
*plEndSpeaking = GetTailEnd();
return Speak( rVoice, plStartSpeaking, plEndSpeaking );
}
if ( p->pTextRun->WithinRange( *plEndSpeaking ) )
{
// This block is the only one that needs speaking, since both the start and the end
// limits for speaking are found within this run.
// CTextRun::Speak() will put the appropriate values in *plStartSpeaking and
// *plEndSpeaking.
return p->pTextRun->Speak( rVoice, plStartSpeaking, plEndSpeaking );
}
// Speak from *plStartSpeaking to the end of the first block
long lFirstBlockEnd = -1;
HRESULT hr = p->pTextRun->Speak( rVoice, plStartSpeaking, &lFirstBlockEnd );
// Spin through the list until the final node is reached or until the end of the
// TextRunList has been reached
for ( p = p->pNext;
SUCCEEDED(hr) && p &&
(( *plEndSpeaking < 0 ) || ( p->pTextRun->GetEnd() < *plEndSpeaking ));
p = p->pNext )
{
hr = p->pTextRun->Speak( rVoice );
}
// Stop here if something has gone wrong
if (FAILED(hr))
{
return hr;
}
// If we were not supposed to speak to the end of the document,
// speak from the start of the final node until the end limit.
if ( p && (*plEndSpeaking >= 0) )
{
if ( p->pTextRun->GetStart() < *plEndSpeaking )
{
// There is something left to speak in the final block,
// so speak from the beginning to *plEndSpeaking
long lFinalBlockStart = -1;
hr = p->pTextRun->Speak( rVoice, &lFinalBlockStart, plEndSpeaking );
}
else
{
// There is nothing to speak in the final block:
// The end of the speak is the start of the final block
*plEndSpeaking = p->pTextRun->GetStart();
}
}
else
{
// The end of the speak is the end of the document
*plEndSpeaking = GetTailEnd();
}
return hr;
} /* CTextRunList::Speak */
/*****************************************************************************
* CTextRunList::Serialize *
*-------------------------*
* Description:
* Serializes the information in the TextRunList and writes
* it to pStream.
* Return:
* S_OK
* E_POINTER: pStream or pRecoCtxt is NULL
* Return value of CTextRun::Serialize()
* Return value of IStream::Write()
******************************************************************************/
HRESULT CTextRunList::Serialize( IStream *pStream, ISpRecoContext *pRecoCtxt )
{
if ( !pStream || !pRecoCtxt )
{
return E_POINTER;
}
// Walk the list, serializing each CTextRun
PTEXTRUNNODE pNode;
HRESULT hr = S_OK;
for ( pNode = m_pHead; pNode; pNode = pNode->pNext )
{
// Serialize the node
hr = pNode->pTextRun->Serialize( pStream, pRecoCtxt );
if ( FAILED( hr ) )
{
return hr;
}
}
// Write an "end-of-list" header to the stream
RUNHEADER endHdr;
ULONG cb;
endHdr.lStart = endHdr.lEnd = -1;
hr = pStream->Write( &endHdr, sizeof( RUNHEADER ), &cb );
return hr;
} /* CTextRunList::Serialize */
/*****************************************************************************
* CTextRunList::Deserialize *
*---------------------------*
* Description:
* Uses an IStream to recreate a previously-serialized TextRunList
* Return:
* S_OK
* E_POINTER: pStream or pRecoCtxt is NULL
* E_FAIL: TextRunList was not empty to begin with or the associated
* ITextDocument is invalid or the TextRunList contains
* invalid offsets
* E_OUTOFMEMORY
* Return value of IStream::Read()
* Return value of ISpRecoContext::DeserializeResult()
* Return value of ITextDocument::Range()
* Return value of CTextRun::SetTextRange()
******************************************************************************/
HRESULT CTextRunList::Deserialize( IStream *pStream, ISpRecoContext *pRecoCtxt )
{
if ( !pStream || !pRecoCtxt )
{
return E_POINTER;
}
if ( !m_cpTextDoc )
{
return E_FAIL;
}
// Since we're going to be creating this CTextRunList from a stream,
// clear out any nodes that are already there
if ( m_pHead )
{
DeleteAllNodes();
}
// Read in the first header; the end will be indicated by a header
// with lStart < 0
RUNHEADER runHdr;
ULONG cbRead = 0;
HRESULT hr = pStream->Read( &runHdr, sizeof( RUNHEADER ), &cbRead );
// Keep reading until the end-of-stream RUNHEADER is encountered
// (that RUNHEADER will have -1 as its lStart)
long lNextPosToRead = 0; // To check consistency of serialized list
while ( SUCCEEDED( hr ) && ( cbRead > 0 ) && ( runHdr.lStart >= 0 ) )
{
// Consistency check: Make sure this run starts where the
// last run left off
if ( lNextPosToRead == runHdr.lStart )
{
// Good, update next position
lNextPosToRead = runHdr.lEnd;
}
else
{
hr = E_UNEXPECTED;
}
CTextRun *pNewRun = NULL;
if ( runHdr.bResultFollows )
{
// This run is a serialized CDictationRun
// Serialized CDictationRuns are preceded by a DICTHEADER
DICTHEADER dictHdr;
if ( SUCCEEDED( hr ) )
{
hr = pStream->Read( &dictHdr, sizeof( DICTHEADER ), &cbRead );
}
/*
if ( FAILED( hr ) )
{
return hr;
}
*/
// Allocate the appropriate amount of space for the serialized
// result object
void *pv = NULL;
if ( SUCCEEDED( hr ) )
{
pv = ::CoTaskMemAlloc( dictHdr.cbSize );
if ( !pv )
{
hr = E_OUTOFMEMORY;
}
}
// Read in the serialized result object
if ( SUCCEEDED( hr ) )
{
hr = pStream->Read( pv, dictHdr.cbSize, &cbRead );
}
SPSERIALIZEDRESULT *pResultBlob = static_cast<SPSERIALIZEDRESULT *> (pv);
CComPtr<ISpRecoResult> cpResult;
if ( SUCCEEDED( hr ) )
{
hr = pRecoCtxt->DeserializeResult( pResultBlob, &cpResult );
}
if ( pv )
{
::CoTaskMemFree( pv );
}
/*
if ( FAILED( hr ) )
{
return hr;
}
*/
// Create a CDictationRun with which to associate the phrase blob
if ( SUCCEEDED( hr ) )
{
pNewRun = new CDictationRun;
if ( !pNewRun )
{
hr = E_OUTOFMEMORY;
}
}
// Initialize it with the deserialized result object
if ( SUCCEEDED( hr ) )
{
hr = ((CDictationRun *) pNewRun)->Initialize(
*cpResult, &dictHdr );
if ( FAILED( hr ) )
{
// Punt and make it a TextRun
delete pNewRun;
pNewRun = new CTextRun();
if ( pNewRun )
{
// Proceed with a simple CTextRun
hr = S_OK;
}
}
}
}
else
{
// The RUNHEADER has indicated that this run is not a CDictationRun,
// so create a CTextRun
pNewRun = new CTextRun();
}
if ( !pNewRun )
{
hr = E_OUTOFMEMORY;
}
else
{
pNewRun->IncrementCount();
}
// Create a text range for this run as specified by the RUNHEADER
CComPtr<ITextRange> cpRange;
if ( SUCCEEDED( hr ) )
{
hr = m_cpTextDoc->Range( runHdr.lStart, runHdr.lEnd, &cpRange );
}
// Set the run to use that text range
if (SUCCEEDED( hr ) )
{
hr = pNewRun->SetTextRange( cpRange );
}
// Put the node on the list and continue
if ( pNewRun )
{
// Link in the new node at the tail
PTEXTRUNNODE pNewNode = new TEXTRUNNODE;
pNewNode->pTextRun = pNewRun;
AddTail( pNewNode );
}
if ( SUCCEEDED( hr ) )
{
// Read in the next header
hr = pStream->Read( &runHdr, sizeof( RUNHEADER ), &cbRead );
}
}
if ( FAILED( hr ) )
{
// Roll back: Delete all of the nodes that were created
DeleteAllNodes();
}
// Start out with the "hint" pointer pointing to the head
m_pCurrent = m_pHead;
return hr;
} /* CTextRunList::Deserialize */
/*****************************************************************************
* CTextRunList::IsConsumeLeadingSpaces *
*--------------------------------------*
* Description:
* Sets *pfConsumeLeadingSpaces to true iff whatever starts at lPos
* has a display attribute to consume leading spaces
* Return:
* E_POINTER
* E_INVALIDARG if lPos is out of range
* Return value of CTextRun::IsConsumeLeadingSpaces()
******************************************************************************/
HRESULT CTextRunList::IsConsumeLeadingSpaces( long lPos,
bool *pfConsumeLeadingSpaces )
{
if ( !pfConsumeLeadingSpaces )
{
return E_POINTER;
}
// Find the node containing this position
PTEXTRUNNODE pNode = Find( lPos );
if ( !pNode )
{
// we were given an out-of-range position
return E_INVALIDARG;
}
_ASSERTE( pNode->pTextRun );
if ( !pNode->pTextRun )
{
return E_UNEXPECTED;
}
return pNode->pTextRun->IsConsumeLeadingSpaces( lPos,
pfConsumeLeadingSpaces );
} /* CTextRunList::ConsumeLeadingSpaces */
/*****************************************************************************
* CTextRunList::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
* Return:
* E_POINTER
* E_INVALIDARG if lPos is out of range
* Return value of CTextRun::HowManySpacesAfter()
******************************************************************************/
HRESULT CTextRunList::HowManySpacesAfter( long lPos, UINT *puiSpaces )
{
if ( !puiSpaces )
{
return E_POINTER;
}
// Find the node containing this position
PTEXTRUNNODE pNode = Find( lPos );
if ( !pNode )
{
// we were given an out-of-range position
return E_INVALIDARG;
}
_ASSERTE( pNode->pTextRun );
if ( !pNode->pTextRun )
{
return E_UNEXPECTED;
}
return pNode->pTextRun->HowManySpacesAfter( lPos, puiSpaces );
} /* CTextRunList::HowManySpacesAfter */
/*****************************************************************************
* CTextRunList::GetTailEnd *
*--------------------------*
* Description:
* Returns the end position of the tail. This gives us the length of
* the document
******************************************************************************/
long CTextRunList::GetTailEnd()
{
if ( m_pTail )
{
return m_pTail->pTextRun->GetEnd();
}
else
{
return 0;
}
} /* CTextRunList::GetTailEnd */
/*****************************************************************************
* CTextRunList::Find *
*--------------------*
* Description: Returns a pointer to the node containing position lDest.
* Return:
* NULL if lDest is out of bounds
* Pointer to a node with cpMin <= lDest < cpMax
* OR a pointer to the tail if lDest is the last position in the list
******************************************************************************/
PTEXTRUNNODE CTextRunList::Find( long lDest )
{
if ( !m_pHead || ( lDest < 0 ) || ( lDest > GetTailEnd() ))
{
return NULL;
}
// Try to use m_pCurrent as a hint
if ( m_pCurrent &&
( m_pCurrent->pTextRun->GetStart() <= lDest ) &&
( lDest < m_pCurrent->pTextRun->GetEnd()) )
{
return m_pCurrent;
}
// Check for the start
if ( lDest == 0 )
{
return m_pHead;
}
// Check for the end
if ( lDest == GetTailEnd() )
{
return m_pTail;
}
// Find whose start is closest to lDest: the head, the tail, or m_pCurrent.
// Note that the distance from the start (head) is just lDest, since the
// start position is always 0.
long lDistFromEnd = labs( lDest - m_pTail->pTextRun->GetStart() );
long lDistFromCurrent;
if ( m_pCurrent )
{
lDistFromCurrent = labs( lDest - m_pCurrent->pTextRun->GetStart() );
}
else
{
// m_pCurrent isn't pointing anywhere.
// "Sabotage" lDistFromCurrent so it will never beat searching from the start
lDistFromCurrent = lDest + 1;
}
bool bSearchForward;
PTEXTRUNNODE pStartSearch;
if (( lDistFromCurrent < lDest ) &&
( lDistFromCurrent < lDistFromEnd ))
{
// m_pCurrent is closer than both the head and the tail
// Search from m_pCurrent
pStartSearch = m_pCurrent;
}
else
{
if ( lDest < lDistFromEnd )
{
// Head is closer than tail
pStartSearch = m_pHead;
}
else
{
pStartSearch = m_pTail;
}
}
bSearchForward = (lDest >= pStartSearch->pTextRun->GetStart());
// Walk either forwards or backwards from the closest reference node
// looking for a node that meets cpMin <= lDest < cpMax
PTEXTRUNNODE p;
if ( bSearchForward )
{
// Forward search
for ( p = pStartSearch;
p && (p->pTextRun->GetEnd() <= lDest);
p = p->pNext )
;
_ASSERTE( p );
if ( p->pTextRun->GetStart() > lDest )
{
// lDest is in a gap preceding node p
_ASSERTE( false );
p->pTextRun->SetStart( lDest );
}
}
else
{
// Backwards search
for ( p = pStartSearch;
p && (p->pTextRun->GetStart() > lDest);
p = p->pPrev )
;
_ASSERTE( p );
if ( p->pTextRun->GetEnd() <= lDest )
{
// lDest is in a gap following node p
_ASSERTE( false );
p->pTextRun->SetEnd( lDest );
}
}
return p;
} /* CTextRunList::Find */
/*****************************************************************************
* CTextRunList::MoveCurrentTo *
*-----------------------------*
* Description:
* Moves the m_pCurrent to the TextRun node directly preceding
* the specified location (so that the next time we try to
* insert a text run, hopefully we can insert right after
* m_pCurrent.
* If lDest falls in the middle of a text run, splits that text
* run.
* First tries using its old value as a hint.
* Postcondition: The end of m_pCurrent == lDest
* Return:
* S_OK
* E_INVALIDARG if lDest is out of the range of the document
******************************************************************************/
HRESULT CTextRunList::MoveCurrentTo( LONG lDest )
{
if ( m_pCurrent && (lDest == m_pCurrent->pTextRun->GetEnd()) )
{
// All set, nothing more to do; m_pCurrent was already right
return S_OK;
}
// If lDest is 0, then set m_pCurrent to NULL (since there is no node preceding the
// head
if ( !lDest )
{
m_pCurrent = NULL;
return S_OK;
}
// Find() will return pNode such that cpMin <= lDest < cpMax
PTEXTRUNNODE pNode = Find( lDest );
if ( !pNode )
{
// lDest was too big, so it wasn't found
return E_INVALIDARG;
}
// Make sure lDest is somewhere in pNode's range
_ASSERTE( pNode->pTextRun->WithinRange( lDest ) || (lDest == pNode->pTextRun->GetEnd()) );
HRESULT hr = S_OK;
if ( pNode->pTextRun->GetStart() == lDest )
{
// The node we found begins exactly at lDest, so no splitting is necessary;
// we just need to have m_pCurrent point to the previous node
m_pCurrent = pNode->pPrev;
}
else
{
// lDest occurs in the middle of pNode's range, so we need to split pNode
hr = SplitNode( pNode );
if ( SUCCEEDED( hr ) )
{
// pNode still starts at the same place, except now it ends at the start
// of m_pNodeToInsert (see CTextRunList::Insert())
m_pCurrent = pNode;
}
}
return hr;
} /* CTextRunList::MoveCurrentTo */
/******************************************************************************
* CTextRunList::SplitNode *
*-------------------------*
* Description:
* Called BEFORE m_pNodeToInsert is inserted into the list, in
* order to split up pNode to accommodate the new node.
*
* Splits the given node to form a node that ends at the start position
* of m_pNodeToInsert and a node that starts at the end position of
* m_pNodeToInsert.
*
* Creates a new node to bridge the gap if necessary.
* See CTextRun::Split() and CDictationRun::Split()
* Return:
* S_OK
* E_POINTER if pNode is NULL
* E_INVALIDARG if lCursorPos does not fall in the range of pNode
* E_OUTOFMEMORY
* Return value of CTextRun::Split()
* Return value of CTextRunList::InsertAfter()
******************************************************************************/
HRESULT CTextRunList::SplitNode( PTEXTRUNNODE pNode )
{
if ( !pNode )
{
return E_POINTER;
}
_ASSERTE( m_pNodeToInsert );
if ( !m_pNodeToInsert )
{
return E_UNEXPECTED;
}
if ( m_pNodeToInsert->pTextRun->GetStart() == pNode->pTextRun->GetEnd() )
{
// No need to split, since the new node is at the end of this node
return S_OK;
}
if ( !(pNode->pTextRun->WithinRange( m_pNodeToInsert->pTextRun->GetStart() )) )
{
// Start of m_pNodeToInsert is out of bounds
return E_INVALIDARG;
}
CTextRun *pLatterRun = NULL;
// Remember the start and end of m_pNodeToInsert
const long lStart = m_pNodeToInsert->pTextRun->GetStart();
const long lEnd = m_pNodeToInsert->pTextRun->GetEnd();
// Call CTextRun::Split(). This might change the split locations lNewStart and lNewEnd
// and will set pLatterRun to a non-NULL value if the split actually did occur.
long lNewStart = lStart;
long lNewEnd = lEnd;
HRESULT hr = pNode->pTextRun->Split( &lNewStart,
&lNewEnd, m_cpTextDoc, &pLatterRun );
_ASSERTE( SUCCEEDED( hr ) );
if ( FAILED( hr ) )
{
return hr;
}
// Adjust the range on m_pNodeToInsert if it has changed
if (( lStart != lNewStart ) || ( lEnd != lNewEnd ))
{
m_pNodeToInsert->pTextRun->SetStart( lNewStart );
m_pNodeToInsert->pTextRun->SetEnd( lNewEnd );
}
if ( pLatterRun )
{
// A split did occur
// Create the new node and insert it right after pNode
PTEXTRUNNODE pNewNode = new TEXTRUNNODE;
if ( !pNewNode )
{
return E_OUTOFMEMORY;
}
pNewNode->pTextRun = pLatterRun;
pNewNode->pTextRun->IncrementCount();
pNewNode->pNext = pNewNode->pPrev = NULL;
hr = InsertAfter( pNode, pNewNode );
if ( FAILED( hr ) )
{
return hr;
}
// InsertAfter() would have moved m_pCurrent to the latter run (the
// one associated with pNewNode). But m_pCurrent should stay at pNode.
m_pCurrent = pNode;
}
return S_OK;
} /* CTextRunList::SplitNode */
/******************************************************************************
* CTextRunList::MergeIn *
*-----------------------*
* Description:
* For new DictationRuns, passes them along to the separate merging
* method for DictationRuns below.
* handled by a separate merging method below).
*
* First tries to merge with each neighbor if it is a non-dictated
* node.
*
* Next tries to merge with dictation nodes on either side by
* temporarily including the node in the dictation node's range
* and seeing if it is a match for any of the missing phrase elements
* in that run. The neighboring dictation runs are allowed to
* appropriate whatever portion of its range that they can.
*
* Return:
* S_OK
* E_POINTER
* Return value of CTextRunList::MergeInDictRun()
******************************************************************************/
HRESULT CTextRunList::MergeIn( PTEXTRUNNODE pNode, bool *pfNodeMadeItIn )
{
if ( !pfNodeMadeItIn )
{
return E_POINTER;
}
*pfNodeMadeItIn = false;
if ( pNode->pTextRun->IsDict() )
{
// Dictation runs are handled by a separate merging method.
// Assuming CTextRunList::MergeInDictRun() went off successfully,
// the run will have made it onto the list
HRESULT hr = MergeInDictRun( pNode );
if ( SUCCEEDED( hr ) )
{
*pfNodeMadeItIn = true;
}
return hr;
}
// This run is not a CDictationRun
// Do the easy merge with the neighbors if they are text nodes
// (that is, two adjacent non-dictated runs can always be merged)
MERGERESULT mr;
HRESULT hr;
if ( pNode->pPrev && !(pNode->pPrev->pTextRun->IsDict()) )
{
mr = pNode->pTextRun->Concatenate( pNode->pPrev->pTextRun, false );
// Both nodes are nondictated text, so this better have worked
_ASSERTE( mr == E_FULLMERGE );
_ASSERTE( pNode->pPrev->pTextRun->IsDegenerate() );
if (( mr != E_FULLMERGE ) || !pNode->pPrev->pTextRun->IsDegenerate() )
{
return E_UNEXPECTED;
}
// Prev node can now be removed
hr = RemoveNode( pNode->pPrev );
if ( FAILED( hr ) )
{
_ASSERTE( false );
return E_UNEXPECTED;
}
}
if ( pNode->pNext && !(pNode->pNext->pTextRun->IsDict()) )
{
mr = pNode->pTextRun->Concatenate( pNode->pNext->pTextRun, true );
// Both nodes are nondictated text, so this better have worked
_ASSERTE( mr == E_FULLMERGE );
_ASSERTE( pNode->pNext->pTextRun->IsDegenerate() );
if (( mr != E_FULLMERGE ) || !pNode->pNext->pTextRun->IsDegenerate() )
{
return E_UNEXPECTED;
}
// Next node can now be removed
hr = RemoveNode( pNode->pNext );
if ( FAILED( hr ) )
{
_ASSERTE( false );
return E_UNEXPECTED;
}
}
// pNode should be surrounded by dictation nodes now, since there should never be
// two consecutive text nodes
// The concatenation for pNext must happen before that for pPrev.
// That is because it is a lot easier to include trailing spaces before the
// next word in a range than it is to include preceding spaces.
// Thus by concatenating onto pPrev LAST we are doing the forward Concatenate second,
// which is more effective in gobbling up pNode's range (our goal is to get
// pNode's range as small as possible.
if ( pNode->pNext )
{
_ASSERTE( pNode->pNext->pTextRun->IsDict() );
_ASSERTE( pNode->pNext->pTextRun->GetStart() == pNode->pTextRun->GetEnd() );
// This concatenation may change the end of pNode's text range or
// the start of pNext's text range
pNode->pNext->pTextRun->Concatenate( pNode->pTextRun, false );
}
if ( pNode->pPrev )
{
_ASSERTE( pNode->pPrev->pTextRun->IsDict() );
_ASSERTE( pNode->pPrev->pTextRun->GetEnd() == pNode->pTextRun->GetStart() );
// This concatenation may change the end of pPrev's text range or
// the start of pNode's text range
pNode->pPrev->pTextRun->Concatenate( pNode->pTextRun, true );
}
// If pNode now has nothing left, then remove it and try to concatenate its previous and
// next neighbors together
if ( pNode->pTextRun->IsDegenerate() )
{
// Hang on to the prev and next nodes
PTEXTRUNNODE pPrev = pNode->pPrev;
PTEXTRUNNODE pNext = pNode->pNext;
// Remove pNode
hr = RemoveNode( pNode );
_ASSERTE( SUCCEEDED(hr) );
if ( FAILED( hr ) )
{
return E_UNEXPECTED;
}
// Concatenate pPrev and pNext. If the pNext node is completely consumed, then
// remove it as well
if ( pPrev && pNext )
{
_ASSERTE( pPrev->pTextRun->GetEnd() == pNext->pTextRun->GetStart() );
pPrev->pTextRun->Concatenate( pNext->pTextRun, true );
if ( pNext->pTextRun->IsDegenerate() )
{
// pNext was entirely subsumed by pPrev, so remove it
hr = RemoveNode( pNext );
_ASSERTE( SUCCEEDED(hr) );
if ( FAILED( hr ) )
{
return E_UNEXPECTED;
}
}
}
// Do not set *pfNodeMadeItIn to true, since the node did not make it in
return S_OK;
}
else
{
// If we are here, the node made it onto the list successfully
*pfNodeMadeItIn = true;
return S_OK;
}
} /* CTextRunList::MergeIn */
/******************************************************************************
* CTextRunList::MergeInDictRun *
*------------------------------*
* Description:
* Merges in a dictation run.
* Since it is a new dictation run, it is complete and its range
* will not change. If either neighbor is a dictation run,
* we need to check if the phrase elements are correct
* (since, of course, the new dictation run may be splitting up
* already-existing dictation runs).
* If not, we may need to insert text runs on either side.
* Return:
* S_OK
* E_OUTOFMEMORY
* Return value of CTextRun::CorrectPhraseEltsAndRange()
* Return value of ITextDocument::Range()
* Return value of CTextRun::SetTextRange()
* Return value of CTextRunList::InsertAfter()
* Return value of CTextRunList::MergeIn()
*******************************************************************************/
HRESULT CTextRunList::MergeInDictRun( PTEXTRUNNODE pNode )
{
HRESULT hr;
PTEXTRUNNODE pNewTextRunNode = NULL;
CTextRun *pNewTextRun = NULL;
// Make sure that the previous node still has accurate information
if ( pNode->pPrev )
{
PTEXTRUNNODE pPrev = pNode->pPrev;
// Ensure that the previous node's phrase elements reflect reality (it
// may be a newly-broken dictation run.)
// The range of pPrev will be adjusted so that only full phrase elements
// are included in pPrev's range
hr = pPrev->pTextRun->CorrectPhraseEltsAndRange( true );
if ( FAILED( hr ) )
{
return hr;
}
if ( pPrev->pTextRun->GetEnd() < pNode->pTextRun->GetStart() )
{
// There is a gap between the neighboring run and this run.
// The gap contains a fragment of a phrase element, which will
// now be downgraded to the status of non-dictated text.
// A new text run must be created to cover that area
pNewTextRun = new CTextRun();
if ( !pNewTextRun )
{
return E_OUTOFMEMORY;
}
pNewTextRun->IncrementCount();
// That new run will cover the gap
CComPtr<ITextRange> pPrevTextRange;
hr = m_cpTextDoc->Range( pPrev->pTextRun->GetEnd(),
pNode->pTextRun->GetStart(), &pPrevTextRange );
if ( SUCCEEDED( hr ) )
{
hr = pNewTextRun->SetTextRange( pPrevTextRange );
}
if ( FAILED( hr ) )
{
return hr;
}
// Make a new TEXTRUNNODE for the list
pNewTextRunNode = new TEXTRUNNODE;
if ( !pNewTextRunNode )
{
return E_OUTOFMEMORY;
}
pNewTextRunNode->pNext = pNewTextRunNode->pPrev = NULL;
pNewTextRunNode->pTextRun = pNewTextRun;
// Put it right where the gap is
hr = InsertAfter( pPrev, pNewTextRunNode );
if ( FAILED( hr ) )
{
return hr;
}
}
if ( pPrev->pTextRun->IsDegenerate() )
{
// pPrev now has no complete phrase elements,
// so get rid of it
hr = RemoveNode( pPrev );
_ASSERTE( SUCCEEDED(hr) );
if ( FAILED( hr ) )
{
return hr;
}
}
if ( pNewTextRunNode )
{
// We are adding a CTextRun to bridge the gap between pPrev and pNode
// Merge in the fragment of the phrase element as text
bool fMadeItIn;
hr = MergeIn( pNewTextRunNode, &fMadeItIn );
if ( FAILED( hr ) )
{
return hr;
}
// Reset pNewTextRunNode NULL so this will work for processing pNext
pNewTextRunNode = NULL;
}
}
// Make sure that the next node still has accurate information
if ( pNode->pNext )
{
PTEXTRUNNODE pNext = pNode->pNext;
// Ensure that the next node's phrase elements reflect reality (it
// may be a newly-broken dictation run.
// The range of pPrev will be adjusted so that only full phrase elements
// are included in pNext's range
hr = pNext->pTextRun->CorrectPhraseEltsAndRange( false );
if ( FAILED( hr ) )
{
return hr;
}
if ( pNext->pTextRun->GetStart() > pNode->pTextRun->GetEnd() )
{
// There is a gap between the neighboring run and this run.
// The gap contains a fragment of a phrase element, which will
// now be downgraded to the status of non-dictated text.
// A new text run must be created to cover that area
pNewTextRun = new CTextRun();
if ( !pNewTextRun )
{
return E_OUTOFMEMORY;
}
pNewTextRun->IncrementCount();
// That new run will cover the gap
CComPtr<ITextRange> pNextTextRange;
hr = m_cpTextDoc->Range( pNode->pTextRun->GetEnd(),
pNext->pTextRun->GetStart(), &pNextTextRange );
if ( SUCCEEDED( hr ) )
{
hr = pNewTextRun->SetTextRange( pNextTextRange );
}
if ( FAILED( hr ) )
{
return hr;
}
// Make a new TEXTRUNNODE for the list
pNewTextRunNode = new TEXTRUNNODE;
if ( !pNewTextRunNode )
{
return E_OUTOFMEMORY;
}
pNewTextRunNode->pNext = pNewTextRunNode->pPrev = NULL;
pNewTextRunNode->pTextRun = pNewTextRun;
// Put it right where the gap is
hr = InsertAfter( pNode, pNewTextRunNode );
if ( FAILED( hr ) )
{
return hr;
}
}
if ( pNext->pTextRun->IsDegenerate() )
{
// pNext now has no complete phrase elements,
// so get rid of it
hr = RemoveNode( pNext );
_ASSERTE( SUCCEEDED(hr) );
if ( FAILED( hr ) )
{
return hr;
}
}
if ( pNewTextRunNode )
{
// We are adding a CTextRun to bridge the gap between pNode and pNext
// Merge in the fragment of the phrase element as text
bool fMadeItIn;
hr = MergeIn( pNewTextRunNode, &fMadeItIn );
if ( FAILED( hr ) )
{
return hr;
}
}
}
// If we got here, then no errors were encountered
return S_OK;
} /*CTextRunList::MergeInDictRun */
/**********************************************************************
* CTextRunList::InsertAfter *
*---------------------------*
* Description:
* Inserts pNodeToInsert in the list after pCurrent.
* If pCurrent is NULL, adds it onto the head.
*
* Return:
* E_POINTER if pNodeToInsert is NULL, otherwise S_OK.
**********************************************************************/
HRESULT CTextRunList::InsertAfter( PTEXTRUNNODE pCurrent,
PTEXTRUNNODE pNodeToInsert )
{
HRESULT hr;
if( !pNodeToInsert )
{
hr = E_POINTER;
}
else if ( !pCurrent )
{
hr = AddHead( pNodeToInsert );
}
else
{
// check if pCurrent is the tail of the list
if( pCurrent == m_pTail )
{
hr = AddTail( pNodeToInsert );
}
else
{
if( pCurrent->pNext == NULL )
{
hr = E_UNEXPECTED; // only the tail can have a NULL pNext
}
else
{
// Link in the new node
PTEXTRUNNODE pLast = pCurrent->pNext;
pCurrent->pNext = pNodeToInsert;
pLast->pPrev = pNodeToInsert;
pNodeToInsert->pPrev = pCurrent;
pNodeToInsert->pNext = pLast;
// Update m_pCurrent
m_pCurrent = pNodeToInsert;
hr = S_OK;
}
}
}
return hr;
} /* CTextRunList::InsertAfter */
/**********************************************************************
* CTextRunList::RemoveNode *
*--------------------------*
* Description:
* Removes a node from the list.
* Decrements the text run reference counts.
*
* Return:
* E_POINTER if pNode is NULL, otherwise S_OK.
**********************************************************************/
HRESULT CTextRunList::RemoveNode( PTEXTRUNNODE pNode )
{
HRESULT hr;
if( !pNode )
{
hr = E_POINTER;
}
else
{
if( pNode == m_pHead )
{
RemoveHead();
}
else if( pNode == m_pTail )
{
RemoveTail();
}
else
{
// Link the two neighbors together
PTEXTRUNNODE pLeft = pNode->pPrev;
PTEXTRUNNODE pRight = pNode->pNext;
pLeft->pNext = pRight;
pRight->pPrev = pLeft;
// Update current node if we're removing it
if( pNode == m_pCurrent )
{
m_pCurrent = pLeft;
}
// Decrement the refcount of this pNode's TextRun, then delete it
pNode->pTextRun->DecrementCount();
delete pNode;
}
hr = S_OK;
}
return hr;
} /* CTextRunList::RemoveNode */
/**********************************************************************
* CTextRunList::AddHead *
*-----------------------*
* Description:
* Adds a node to the head of the list. Sets the current
* pointer to the head.
*
* Return:
* E_POINTER if pHead is NULL, otherwise S_OK.
**********************************************************************/
HRESULT CTextRunList::AddHead( PTEXTRUNNODE pHead )
{
HRESULT hr;
if( !pHead )
{
hr = E_POINTER;
}
else
{
// Check for the first item in the list
if( !m_pHead )
{
// This will be the only item on the list
_ASSERTE( m_pTail == NULL );
pHead->pNext = pHead->pPrev = NULL;
m_pHead = m_pTail = m_pCurrent = pHead;
}
else
{
// Put this node ahead of the existing head
m_pHead->pPrev = pHead;
pHead->pNext = m_pHead;
pHead->pPrev = NULL;
m_pHead = pHead;
m_pCurrent = pHead;
}
hr = S_OK;
}
return hr;
} /* CTextRunList::AddHead */
/**********************************************************************
* CTextRunList::AddTail *
*-----------------------*
* Description:
* Adds a node to the tail of the list. Sets the current
* pointer to the tail.
*
* Return:
* E_POINTER if pTail is NULL, otherwise S_OK.
**********************************************************************/
HRESULT CTextRunList::AddTail( PTEXTRUNNODE pTail )
{
HRESULT hr;
if( !pTail )
{
hr = E_POINTER;
}
else
{
// Check for the last item in the list
if( !m_pTail )
{
// This will be the only item on the list
_ASSERTE( m_pHead == NULL );
pTail->pNext = pTail->pPrev = NULL;
m_pHead = m_pTail = pTail;
}
else
{
// Put this node after of the existing tail
m_pTail->pNext = pTail;
pTail->pPrev = m_pTail;
pTail->pNext = NULL;
m_pTail = pTail;
m_pCurrent = pTail;
}
hr = S_OK;
}
return hr;
} /* CTextRunList::AddTail */
/**********************************************************************
* CTextRunList::RemoveHead *
*--------------------------*
* Description:
* Removes the head node from the list and decrements the
* text run's reference count.
**********************************************************************/
void CTextRunList::RemoveHead()
{
_ASSERTE( m_pHead );
if( m_pHead )
{
if( m_pCurrent == m_pHead )
{
// m_pCurrent would usually fall back one, but here
// we are removing the head
m_pCurrent = NULL;
}
m_pHead->pTextRun->DecrementCount();
// Update m_pHead
PTEXTRUNNODE pNewHead = m_pHead->pNext;
delete m_pHead;
m_pHead = pNewHead;
// If we have deleted the only element, then make the tail NULL too
if ( !m_pHead )
{
m_pTail = NULL;
}
else
{
// Head should have no previous node
m_pHead->pPrev = NULL;
}
}
} /* CTextRunList::RemoveHead */
/**********************************************************************
* CTextRunList::RemoveTail *
*--------------------------*
* Description:
* Removes the tail node from the list and decrements the
* text run's reference count.
**********************************************************************/
void CTextRunList::RemoveTail()
{
if( m_pTail )
{
if( m_pCurrent == m_pTail )
{
m_pCurrent = m_pTail->pPrev;
}
m_pTail->pTextRun->DecrementCount();
// Update m_pTail
PTEXTRUNNODE pNewTail = m_pTail->pPrev;
delete m_pTail;
m_pTail = pNewTail;
// If we have deleted the last element, then make the head NULL too
if ( !m_pTail )
{
m_pHead = NULL;
}
else
{
// Tail should have no next node
m_pTail->pNext = NULL;
}
}
} /* CTextRunList::RemoveTail */
/*****************************************************************************
* CTextRunList::DeleteAllNodes *
*------------------------------*
* Description:
* Deletes a doubly linked list for text run objects
* and decrements the text run reference counts.
******************************************************************************/
void CTextRunList::DeleteAllNodes()
{
// Spin through our list, deleting as we go
while( m_pHead )
{
m_pCurrent = m_pHead;
m_pHead = m_pHead->pNext;
// Decrement a count on the TextRun associated with m_pCurrent
// This should delete the TextRun when its refcount hits 0.
m_pCurrent->pTextRun->DecrementCount();
delete m_pCurrent;
}
// The list is now empty
m_pHead = m_pTail = m_pCurrent = NULL;
} /* CTextRunList::DeleteAllNodes */