//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
/*
 *
 *	@doc	INTERNAL
 *
 *	@module	CALLMGR.CPP		CCallMgr implementation |
 *
 *	Purpose:  The call manager controls various aspects of
 *		a client call chain, including re-entrancy management,
 *		undo contexts, and change notifications.
 *
 *	Author:	<nl>
 *		alexgo 2/8/96
 *
 *	See the documentation in reimplem.doc for a detailed explanation
 *	of how all this stuff works.
 *
 */

#include "_common.h"
#include "_edit.h"
#include "_m_undo.h"
#include "_callmgr.h"
#include "_select.h"
#include "_disp.h"
#include "_ime.h"

ASSERTDATA

/*
 *	CCallMgr::SetChangeEvent
 *
 *	@mfunc	informs the callmgr that some data in the document 
 *			changed.  The fType parameter describes the actual change
 *
 *	@rdesc	void
 */
void CCallMgr::SetChangeEvent(
	CHANGETYPE fType)		//@parm the type of change (e.g. text, etc)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetChangeEvent");

	// if another callmgr exists higher up the chain, then 
	// delegate the call to it
	if( _pPrevcallmgr )
	{
		Assert(_fChange == FALSE);
		Assert(_fTextChanged == FALSE);
		_pPrevcallmgr->SetChangeEvent(fType);
	}
	else
	{
		_fChange = TRUE;
		_ped->_fModified = TRUE;
		_ped->_fSaved = FALSE;
		_fTextChanged = !!(fType & CN_TEXTCHANGED);
	}
}

/*
 *	CCallmgr::ClearChangeEvent
 *
 *	@mfunc	If a change happened, then clear the change event bit. 
 *			This allows callers to make changes to the edit control
 *			_without_ having a notifcation fire.  Sometimes, this
 *			is necessary for backwards compatibility.
 *
 *	@devnote	This is a very dangerous method to use.  If _fChange
 *			is set, it may represent more than 1 change; in other words,
 *			other changes than the one that should be ignored.  However,
 *			for all existing uses of this method, earlier changes are
 *			irrelevant.
 */
void CCallMgr::ClearChangeEvent()
{
	if( _pPrevcallmgr )
	{
		Assert(_fChange == FALSE);
		Assert(_fTextChanged == FALSE);
		_pPrevcallmgr->ClearChangeEvent();
	}
	else
	{
		_fChange = FALSE;
		_fTextChanged = FALSE;
		// caller is responsible for setting _fModifed
	}
}

/*
 *	CCallMgr::SetNewUndo
 *
 *	@mfunc	Informs the notification code that a new undo action has
 *			been added to the undo stack
 *
 *	@rdesc	void
 */
void CCallMgr::SetNewUndo()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetNewUndo");

	// we should only ever do this once per call
	Assert(_fNewUndo == FALSE);

	if( _pPrevcallmgr )
	{
		_pPrevcallmgr->SetNewUndo();
	}
	else
	{
		_fNewUndo = TRUE;
	}
}

/*
 *		
 *	CCallMgr::SetNewRedo
 *
 *	@mfunc	Informs the notification code that a new redo action has
 *			been added to the redo stack.
 *
 *	@rdesc	void
 */
void CCallMgr::SetNewRedo()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetNewRedo");

	// we should only ever do this once per call.
	Assert(_fNewRedo == FALSE);

	if( _pPrevcallmgr )
	{
		_pPrevcallmgr->SetNewRedo();
	}
	else
	{
		_fNewRedo = TRUE;
	}
}

/*
 *	CCallMgr::SetMaxText
 *
 *	@mfunc	Informs the notification code that the max text limit has
 *			been reached.
 *
 *	@rdesc	void
 */
 void CCallMgr::SetMaxText()
 {
     TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetMaxText");

	// if there is a call context higher on the stack, delegate to it.

	if( _pPrevcallmgr )
	{
		Assert(_fMaxText == 0);
		_pPrevcallmgr->SetMaxText();
	}
	else
	{
		_fMaxText = TRUE;
	}
}

/*
 *	CCallMgr::SetSelectionChanged
 *
 *	@mfunc	Informs the notification code that the selection has
 *			changed
 *
 *	@rdesc	void
 */
void CCallMgr::SetSelectionChanged()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetSelectionChanged");

	// if there is a call context higher on the stack, delegate to it.

	if( _pPrevcallmgr )
	{
		Assert(_fSelChanged == 0);
		_pPrevcallmgr->SetSelectionChanged();
	}
	else
	{
		_fSelChanged = TRUE;
	}
}

/*
 *	CCallMgr::SetOutOfMemory()
 *
 *	@mfunc	Informs the notification code that we were unable to allocate
 *			enough memory.
 *
 *	@rdesc	void
 */
void CCallMgr::SetOutOfMemory()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetOutOfMemory");

	// if there is a call context higher on the stack, delegate to it.

	if( _pPrevcallmgr )
	{
		Assert(_fOutOfMemory == 0);
		_pPrevcallmgr->SetOutOfMemory();
	}
	else
	{
		_fOutOfMemory = TRUE;
	}
}

/*
 *	CCallMgr::SetInProtected
 *
 *	@mfunc	Indicates that we are currently processing an EN_PROTECTED
 *			notification
 *
 *	@rdesc	void
 */
void CCallMgr::SetInProtected(BOOL flag)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetInProtected");

	if( _pPrevcallmgr )
	{
		_pPrevcallmgr->SetInProtected(flag);
	}
	else
	{
		_fInProtected = flag;
	}
}

/*
 *	CCallMgr:GetInProtected
 *
 *	@mfunc	retrieves the InProtected flag, whether or not we are currently
 *			processing an EN_PROTECTED notification
 *
 *	@rdesc	void
 */
BOOL CCallMgr::GetInProtected()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::GetInProtected");

	if( _pPrevcallmgr )
	{
		return _pPrevcallmgr->GetInProtected();
	}
	else
	{
		return _fInProtected;
	}
}	

/*
 *	CCallMgr::RegisterComponent
 *
 *	@mfunc	Registers a subsystem component implementing IReEntrantComponent.
 *			This enables this call manager to inform those objects about
 *			relevant changes in our re-entrancy status.
 *
 *	@rdesc	void
 *
 */
void CCallMgr::RegisterComponent(
	IReEntrantComponent *pcomp,	//@parm The component to register
	CompName name)				//@parm The name for the component
{
	pcomp->_idName = name;
	pcomp->_pnext = _pcomplist;
	_pcomplist = pcomp;
}

/*
 *	CCallMgr::RevokeComponent
 *
 *	@mfunc	Removes a subsystem component from the list of components.  The
 *			component must have been previously registered with _this_
 *			call context.
 *
 *	@rdesc	void
 */
void CCallMgr::RevokeComponent(
	IReEntrantComponent *pcomp)	//@parm The component to remove
{
	IReEntrantComponent *plist, **ppprev;
	plist = _pcomplist;
	ppprev = &_pcomplist;

	while( plist != NULL )
	{
		if( plist == pcomp )
		{
			*ppprev = plist->_pnext;
			break;
		}
		ppprev = &(plist->_pnext);
		plist = plist->_pnext;
	} 
}

/*
 *	CCallMgr::GetComponent
 *
 *	@mfunc	Retrieves the earliest instance of a registered sub-component.
 *
 *	@rdesc	A pointer to the component, if one has been registered.  NULL
 *			otherwise.
 */
IReEntrantComponent *CCallMgr::GetComponent(
	CompName name)				//@parm the subsystem to look for
{
	IReEntrantComponent *plist = _pcomplist;

	while( plist != NULL )
	{
		if( plist->_idName == name )
		{
			return plist;
		}
		plist = plist->_pnext;
	}

	// hmm, didn't find anything.  Try contexts higher up, if we're
	// the top context, then just return NULL.

	if( _pPrevcallmgr )
	{
		return _pPrevcallmgr->GetComponent(name);
	}
	return NULL;
}


/*
 *	CCallMgr::CCallMgr
 *
 *	@mfunc	Constructor
 *
 *	@rdesc	void
 */
CCallMgr::CCallMgr(CTxtEdit *ped)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::");

	// set everthing to NULL
	ZeroMemory(this, sizeof(CCallMgr));

	if(ped)								// If ped is NULL, a zombie has
	{									//  been entered
		_ped = ped;
		_pPrevcallmgr = ped->_pcallmgr;
		ped->_pcallmgr = this;
		NotifyEnterContext();
	}
}

/*
 *	CCallMgr::~CCallMgr
 *
 *	@mfunc	Destructor.  If appropriate, we will fire any cached
 *			notifications and cause the edit object to be destroyed.
 *
 *	@rdesc	void
 */
CCallMgr::~CCallMgr()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::");

	if(IsZombie())					// No reentrancy with Zombies
		return;

	if( _pPrevcallmgr )
	{
		// we don't allow these flags to be set in re-entrant call
		// states
		Assert(_fMaxText == FALSE);
		Assert(_fSelChanged == FALSE);
		Assert(_fTextChanged == FALSE);
		Assert(_fChange == FALSE);
		Assert(_fNewRedo == FALSE);
		Assert(_fNewUndo == FALSE);
		Assert(_fOutOfMemory == FALSE);

		// set the ped to the next level of the call state
		_ped->_pcallmgr = _pPrevcallmgr;
	
		return;
	}

	// we're the top level. Note that we explicity do not
	// have an overall guard for cases where we are re-entered
	// while firing these notifications.  This is necessary for
	// better 1.0 compatibility and for Forms^3, which wants
	// to 'guard' their implementation of ITextHost::TxNotify and
	// ignore any notifications that happen while they are 
	// processing our notifications.  Make sense?

	_ped->_pcallmgr = NULL;

	// Process our internal notifications
	if(_ped->_fUpdateSelection)
	{	
		CTxtSelection *psel = _ped->GetSel();

		_ped->_fUpdateSelection = FALSE;

		if(psel && !_ped->_pdp->IsFrozen() && !_fOutOfMemory )
		{
			// this may cause an out of memory, so set things
			// up for that
			CCallMgr callmgr(_ped);
			psel->Update(FALSE);
		}
	}

	// Now fire any external notifications that may be necessary
	if( _fChange || _fSelChanged || _fMaxText || _fOutOfMemory )
	{
		SendAllNotifications();
	}

	// finally, we should check to see if we should delete the 
	// CTxtEdit instance.

	if( _ped->_unk._cRefs == 0 && !_ped->_fSelfDestruct)
	{
		delete _ped;
	}
}

//
//	PRIVATE methods
//

/*
 *	CCallMgr::SendAllNotifications
 *
 *	@mfunc	sends notifications for any cached notification bits.
 *
 *	@rdesc	void
 */
void CCallMgr::SendAllNotifications()
{
	ITextHost *phost = _ped->GetHost();
	CHANGENOTIFY	cn;

    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::");

	//
	// COMPATIBILITY ISSUE: The ordering of these events _may_
	// be an issue.  I've attempted to preserve the ordering
	// that the original code would use, but we have ~many~ more
	// control paths, so it's difficult.
	//
	if(	_fMaxText )
	{			
		phost->TxNotify(EN_MAXTEXT, NULL);

	}
	
	if( _fSelChanged )
	{ 		
		if( (_ped->_dwEventMask & ENM_SELCHANGE) 
			&& !(_ped->IsIMEComposition() && _ped->_ime->HoldNotify()) )
		{
			SELCHANGE selchg;

			ZeroMemory(&selchg, sizeof(SELCHANGE));

			_ped->GetSel()->SetSelectionInfo(&selchg);

			phost->TxNotify(EN_SELCHANGE, &selchg);
		}
	}

	if( _fOutOfMemory )
	{
		_fNewUndo = 0;
		_fNewRedo = 0;
		_ped->ClearUndo(NULL);
		_ped->_pdp->InvalidateRecalc();
		phost->TxNotify(EN_ERRSPACE, NULL);

	}

	if( _fChange )
	{
		if( (_ped->_dwEventMask & ENM_CHANGE) 
			&& !(_ped->IsIMEComposition() && _ped->_ime->HoldNotify())  )
		{
			cn.dwChangeType = 0;
			cn.dwUndoCookie = 0;
			
			if( _fNewUndo )
			{
				Assert(_ped->_pundo);
				cn.dwChangeType |= CN_NEWUNDO;
				cn.dwUndoCookie = _ped->_pundo->GetTopAECookie();

			}
			else if( _fNewRedo )
			{
				Assert(_ped->_predo);
				cn.dwChangeType |= CN_NEWREDO;
				cn.dwUndoCookie = _ped->_predo->GetTopAECookie();
			}

			if( _fTextChanged )
			{
				cn.dwChangeType |= CN_TEXTCHANGED;
			}
			phost->TxNotify(EN_CHANGE, &cn);
		}
	}
}

/*
 *	CCallMgr::NotifyEnterContext
 *
 *	@mfunc	Notify any registered components that a new context
 *			has been entered.
 *
 *	@rdesc	void
 */
void CCallMgr::NotifyEnterContext()
{
	IReEntrantComponent *pcomp = _pcomplist;

	while( pcomp )
	{
		pcomp->OnEnterContext();
		pcomp = pcomp->_pnext;
	}

	if( _pPrevcallmgr )
	{
		_pPrevcallmgr->NotifyEnterContext();
	}
}