//
// 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.
//
/*
 *	@doc TOM
 *
 *	@module	tomsel.cpp - Implement the CTxtSelection Class |
 *	
 *		This module contains the TOM ITextSelection implementation for
 *		the selection object
 *
 *	History: <nl>
 *		5/24/95 - Alex Gounares: stubs <nl>
 *		8/95	- Murray Sargent: core implementation
 *
 *	@comm
 *		The "cursor-pad" functions (Left, Right, Up, Down, Home, End)
 *		are simple generalizations of the corresponding keystrokes and have
 *		to express the same UI.  Consequently they are typically not as
 *		efficient for moving the cursor around as ITextRange methods, which
 *		are designed for particular purposes.  This is especially true for
 *		counts larger than one.
 *
 *	@devnote
 *		All ITextSelection methods inherited from ITextRange are handled by
 *		the ITextRange methods, since they either don't affect the display of
 *		the selection (e.g., Get methods), or virtual methods are used that
 *		perform the appropriate updating of the selection on screen.
 *
 */

#include "_common.h"
#include "_select.h"
#include "_disp.h"
#include "_edit.h"

#define DEBUG_CLASSNAME CTxtSelection
#include "_invar.h"


//---------------------- CTxtSelection methods	------------------------------------

/*
 *	CTxtSelection::EndKey (Unit, Extend, pDelta)
 *
 *	@mfunc
 *		Act as UI End key, such that <p Extend> is TRUE corresponds to the
 *		Shift key being depressed and <p Unit> = start of line/document for
 *		Ctrl key not being/being depressed.  Returns *<p pDelta> = count of
 *		characters active end is moved forward, i.e., a number >= 0.
 *
 *	@rdesc
 *		HRESULT =  (invalid Unit) ? E_INVALIDARG :
 *				   (if change) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtSelection::EndKey (
	long  	Unit,			//@parm Unit to use
	long  	Extend,			//@parm Extend selection or go to IP
	long *	pDelta)			//@parm Out parm to receive count of chars moved
{
	TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtSelection::EndKey");

	return Homer(Unit, Extend, pDelta, &CTxtSelection::End);
}

/*
 *	CTxtSelection::GetFlags (pFlags)
 *
 *	@mfunc
 *		Set <p pFlags> = this text selection's flags
 *
 *	@rdesc
 *		HRESULT = (<p pFlags>) ? NOERROR : E_INVALIDARG
 */
STDMETHODIMP CTxtSelection::GetFlags(
	long * pFlags) 		//@parm Out parm to receive selection flags
{
	TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtSelection::GetFlags");

	if(!pFlags)
		return E_INVALIDARG;

	if(IsZombie())	
	{
		*pFlags = tomSelStartActive | tomSelReplace;
		return CO_E_RELEASED;
	}

	DWORD	dwFlags = _cch <= 0;			// Store tomSelStartActive value

	if(_fCaretNotAtBOL)
		dwFlags |= tomSelAtEOL;

	if(GetPed()->_fOverstrike)
		dwFlags |= tomSelOvertype;

	if(GetPed()->_fFocus)
		dwFlags |= tomSelActive;

	*pFlags = dwFlags | tomSelReplace;		// tomSelReplace isn't optional

	return NOERROR;
}

/*
 *	CTxtSelection::GetSelectionType (pType)
 *
 *	@mfunc
 *		Set *pType = type of this text selection
 *
 *	@rdesc
 *		HRESULT = <p pType> ? NOERROR : E_INVALIDARG
 */
STDMETHODIMP CTxtSelection::GetType(
	long * pType) 		//@parm Out parm to receive selection type
{
	TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtSelection::GetSelectionType");

	if(!pType)
		return E_INVALIDARG;

	*pType = (!_cch) ? tomSelectionIP :
			 (abs(_cch) == 1 && _rpTX.GetChar() == WCH_EMBEDDING)
			 ? tomSelectionInlineShape : tomSelectionNormal;

	return IsZombie() ? CO_E_RELEASED : NOERROR;
}

/*
 *	CTxtSelection::HomeKey (Unit, Extend, pDelta)
 *
 *	@mfunc
 *		Act as UI Home key, such that <p Extend> is TRUE corresponds to the
 *		Shift key being depressed and <p Unit> = start of line/document for
 *		Ctrl key not being/being depressed.  Returns *<p pDelta> = count of
 *		characters active end is moved forward, i.e., a number <= 0.
 *
 *	@rdesc
 *		HRESULT =  (invalid Unit) ? E_INVALIDARG :
 *				   (if change) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtSelection::HomeKey (
	long  	Unit,			//@parm Unit to use
	long  	Extend,			//@parm Extend selection or go to IP
	long *	pDelta)			//@parm Out parm to receive count of chars moved
{
	TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtSelection::HomeKey");
	
	return Homer(Unit, Extend, pDelta, &CTxtSelection::Home);
}

/*
 *	CTxtSelection::MoveDown (Unit, Count, Extend, pDelta)
 *
 *	@mfunc
 *		Act as UI Down arrow, such that <p Extend> is TRUE corresponds to the
 *		Shift key being depressed and <p Unit> = tomLine/tomParagraph for
 *		Ctrl key not being/being depressed. In addition, <p Unit> can equal
 *		tomWindow/tomWindowEnd for the Ctrl key not being/being depressed.
 *		This second pair emulates PgDn behavior.  The method returns
 *		*<p pDelta> = actual count of units moved.
 *
 *	@rdesc
 *		HRESULT = (if change) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtSelection::MoveDown (
	long  	Unit,			//@parm Unit to use
	long  	Count,			//@parm Number of Units to move
	long  	Extend,			//@parm Extend selection or go to IP
	long *	pDelta)			//@parm Out parm to receive actual count of
							//		Units moved
{
	TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtSelection::MoveDown");
 	return GeoMover(Unit, Count, Extend, pDelta, 3);
}

/*
 *	CTxtSelection::MoveLeft (Unit, Count, Extend, pDelta)
 *
 *	@mfunc
 *		Act as UI left arrow, such that <p Extend> is TRUE corresponds to the
 *		Shift key being depressed and <p Unit> = tomChar/tomWord for Ctrl key
 *		not	being/being	depressed.  Returns *<p pDelta> = actual count of
 *		units moved
 *
 *	@rdesc
 *		HRESULT = (if change) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtSelection::MoveLeft (
	long  	Unit,			//@parm Unit to use
	long  	Count,			//@parm Number of Units to move
	long  	Extend,			//@parm Extend selection or go to IP
	long *	pDelta)			//@parm Out parm to receive actual count of
							//		Units moved
{
	TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtSelection::MoveLeft");

	return GeoMover(Unit, Count, Extend, pDelta, 0);
}

/*
 *	CTxtSelection::MoveRight (Unit, Count, Extend, pDelta)
 *
 *	@mfunc
 *		Act as UI right arrow, such that <p Extend> is TRUE corresponds to the
 *		Shift key being depressed and <p Unit> = tomChar/tomWord for Ctrl key
 *		not	being/being	depressed.  Returns *<p pDelta> = actual count of
 *		units moved
 *
 *	@rdesc
 *		HRESULT = (if change) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtSelection::MoveRight (
	long  	Unit,			//@parm Unit to use
	long  	Count,			//@parm Number of Units to move
	long  	Extend,			//@parm Extend selection or go to IP
	long *	pDelta)			//@parm Out parm to receive actual count of
							//		Units moved
{
	TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtSelection::MoveRight");
	return GeoMover(Unit, Count, Extend, pDelta, 1);
}

/*
 *	CTxtSelection::MoveUp (Unit, Count, Extend, pDelta)
 *
 *	@mfunc
 *		Act as UI Up arrow, such that <p Extend> is TRUE corresponds to the
 *		Shift key being depressed and <p Unit> = tomLine/tomParagraph for
 *		Ctrl key not being/being depressed. In addition, <p Unit> can equal
 *		tomWindow/tomWindowEnd for the Ctrl key not being/being depressed.
 *		This second pair emulates PgUp behavior.  The method returns
 *		*<p pDelta> = actual count of units moved.
 *
 *	@rdesc
 *		HRESULT = (if change) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtSelection::MoveUp (
	long  	Unit,			//@parm Unit to use
	long  	Count,			//@parm Number of Units to move
	long  	Extend,			//@parm Extend selection or go to IP
	long *	pDelta)			//@parm Out parm to receive actual count of
							//		Units moved
{
	TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtSelection::MoveUp");

	return GeoMover(Unit, Count, Extend, pDelta, 2);
}

/*
 *	CTxtSelection::SetFlags (Flags)
 *
 *	@mfunc
 *		Set this text selection's flags = Flags
 *
 *	@rdesc
 *		HRESULT = NOERROR
 *
 *	@comm
 *		RichEdit ignores tomSelReplace since it's always on
 */
STDMETHODIMP CTxtSelection::SetFlags(
	long Flags) 			//@parm New flag values
{
	TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtSelection::SetFlags");

	if(IsZombie())	
		return CO_E_RELEASED;

	_fCaretNotAtBOL			= (Flags & tomSelAtEOL) != 0;
	GetPed()->_fOverstrike	= (Flags & tomSelOvertype) != 0;

	if(!(Flags & tomSelStartActive) ^ (_cch > 0))
		FlipRange();

	if((Flags & tomSelActive) && !GetPed()->_fFocus)
		GetPed()->TxSetFocus();

	return NOERROR;
}

/*
 *	CTxtRange::SetPoint (x, y, Extend)
 *
 *	@mfunc
 *		Select text at or up through (depending on <p Extend>) the point
 *		(<p x>, <p y>).
 *
 *	@rdesc
 *		HRESULT = NOERROR
 */
STDMETHODIMP CTxtSelection::SetPoint (
	long	x,			//@parm Horizontal coord of point to select
	long	y,			//@parm	Vertical   coord of point to select
	long 	Extend) 	//@parm Whether to extend selection to point
{
	TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtSelection::SelectPoint");

	if(IsZombie())	
		return CO_E_RELEASED;

	CCallMgr	callmgr(GetPed());
	POINT		pt = {x, y};

	if(Extend)
		ExtendSelection (pt);
	else
		SetCaret(pt, FALSE);		

	return NOERROR;
}

/*
 *	CTxtSelection::TypeText (bstr)
 *
 *	@mfunc
 *		Type the string given by bstr at this selection as if someone typed it.
 *		This is similar to the underlying ITextRange::SetText() method, but is
 *		sensitive to the Ins/Ovr key state.
 *
 *	@rdesc
 *		HRESULT = !<p bstr> ? E_INVALIDARG :
 *				  (whole string typed) ? NOERROR : S_FALSE
 *	@comm
 *		This is faster than sending chars via SendMessage(), but it's slower
 *		than using ITextRange::SetText()
 */
STDMETHODIMP CTxtSelection::TypeText (
	BSTR bstr)				//@parm String to type into this selection
{
	TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtSelection::TypeText");

	if(!bstr)
		return E_INVALIDARG;

	if(IsZombie())	
		return CO_E_RELEASED;

	CCallMgr callmgr(GetPed());

	if(!GetPed()->IsntProtectedOrReadOnly(WM_CHAR, 0, 0))
		return E_ACCESSDENIED;

	BOOL			fOver	= GetPed()->_fOverstrike;
	DWORD			iCount;
	OLECHAR *		pch		= bstr;
	CGenUndoBuilder undobldr(GetPed(), UB_AUTOCOMMIT );
	CFreezeDisplay	fd(_pdp);

	iCount = pSysStringLen(bstr);
	
	for ( ; iCount && PutChar(*pch++, fOver, &undobldr); iCount--)
	{
		// simulate one character input at a time
		undobldr.Done();
	}
	return iCount ? S_FALSE : NOERROR;
}


//--------------------- ITextSelection PRIVATE helper methods -----------------------------

/*
 *	@doc INTERNAL
 *
 *	CTxtSelection::GeoMover (Unit, Count, Extend, pDelta, iDir)
 *
 *	@mfunc
 *		Helper function to move active end <p Count> <p Unit>s geometrically
 *
 *		Extends range if <p Extend> is TRUE; else collapses range to Start if
 *		<p Count> <lt> 0 and to End if <p Count> <gt> 0.
 *
 *		Sets *<p pDelta> = count of Units moved
 *
 *		Used by ITextSelection::Left(), Right(), Up(), and Down()
 *
 *	@rdesc
 *		HRESULT = (if change) ? NOERROR : (if Unit supported) ? S_FALSE
 *			: E_NOTIMPL
 */
HRESULT CTxtSelection::GeoMover (
	long  		Unit,		//@parm Unit to use
	long  		Count,		//@parm Number of Units to move
	long 	 	Extend,		//@parm Extend selection or go to IP
	long *	  	pDelta,		//@parm Out parm to receive count of Units moved
	LONG	  	iDir)		//@parm Direction to move in if Count > 0
{
	TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CTxtSelection::GeoMover");

	if(pDelta)							// Default no movement
		*pDelta = 0;

	if(IsZombie())	
		return CO_E_RELEASED;

	CCallMgr callmgr(GetPed());
	LONG	 CountSave = Count;
	LONG	 cp;
	LONG	 cUnit;
	LONG	 iDefUnit = (iDir & 0xfe) == 2 ? tomLine : tomCharacter;
	BOOL	 fCollapse = !Extend && _cch;
	BOOL	 fCtrl	 = Unit != iDefUnit;

	_fExtend = Extend != 0;

	if(Count < 0)
	{
		Count = -Count;
		iDir ^= 1;
	}
	
	if(iDefUnit == tomLine)				// Up or Down
	{
		if(Unit == tomScreen)
		{
			iDir ^= 6;					// Convert Up/Down to PgUp/PgDn
			fCtrl = FALSE;
		}
		else if(Unit == tomWindow)		// Go to top/bottom of window
		{
			iDir ^= 6;					// Convert Up/Down to PgUp/PgDn
			Count = 1;					// Be sure Count = 1
		}								// Leave fCtrl = 1
		else if(fCtrl && Unit != tomParagraph)
			return E_INVALIDARG;
	}
	else if(fCtrl && Unit != tomWord)
		return E_INVALIDARG;

	for (cUnit = Count; Count; Count--)
	{
		cp = GetCp();					// Save cp for comparison
		switch(iDir)					// iDir bit 0 inc/dec for 1/0
		{								// iDir values are chosen contiguously
		case 0:							//  to encourage compiler to use a
			Left(fCtrl);				//  jump table
			break;

		case 1:							// tomCharacter/tomWord OK here
			Right(fCtrl);
			break;

		case 2:							// tomLine/tomParagraph OK here
			Up(fCtrl);
			break;

		case 3:							// tomLine/tomParagraph OK here
			Down(fCtrl);
			break;

		case 4:							// tomWindow/tomScreen OK here
			PageUp(fCtrl);
			break;

		case 5:							// tomWindow/tomScreen OK here
			PageDown(fCtrl);
		}
		if(cp == GetCp() && !fCollapse)	// Didn't move or collapse
			break;						//  so we're done
		fCollapse = FALSE;				// Collapse counts as a Unit
	}

	cUnit -= Count;						// Count of Units moved
	if(CountSave < 0)
		cUnit = -cUnit;					// Negative Counts get negative results

	if(pDelta)
		*pDelta = cUnit;

	return cUnit ? NOERROR : S_FALSE;
}

/*
 *	CTxtSelection::Homer (Unit, Extend, pDelta, pfn)
 *
 *	@mfunc
 *		Helper function to move active end Home or End depending on pfn
 *
 *		Extends range if <p Extend> is TRUE; else collapses range to Start if
 *		<p Count> <lt> 0 and to End if <p Count> <gt> 0.
 *
 *		Sets *<p pDelta> = count of chars moved	forward
 *
 *		Used by ITextSelection::Home(), End()
 *
 *	@rdesc
 *		HRESULT =  (invalid Unit) ? E_INVALIDARG :
 *				   (if change) ? NOERROR : S_FALSE
 */
HRESULT CTxtSelection::Homer (
	long  	Unit,			//@parm Unit to use
	long 	Extend,			//@parm Extend selection or go to IP
	long *	pDelta,			//@parm Out parm to receive count of Units moved
	BOOL	(CTxtSelection::*pfn)(BOOL))	//@parm Direction to move in
{
	TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CTxtSelection::Homer");

	if(pDelta)							// Default no movement
		*pDelta = 0;

	if(IsZombie())	
		return CO_E_RELEASED;

	if(Unit != tomLine && Unit != tomStory)
		return E_INVALIDARG;

	CCallMgr callmgr(GetPed());
	LONG	 cch = GetCp();

	_fExtend = Extend != 0;

	(this->*pfn)(Unit != tomLine);
	cch = GetCp() - cch;
	if(pDelta)
		*pDelta = cch;

	return cch ? NOERROR : S_FALSE;
}

