/*
 *  @doc TOM
 *
 *  @module TOMRANGE.CPP - Implement the CTxtRange Class |
 *  
 *      This module contains the implementation of the TOM ITextRange
 *      interface on the CTxtRange object
 *
 *  History: <nl>
 *      5/24/95 - Alex Gounares: stubs created <nl>
 *      8/95    - MurrayS: main implementation <nl>
 *      11/95   - MurrayS: upgrade to TOM spec of 12/10/95 <nl>
 *      5/96    - MurrayS: added zombie protection
 *
 *  @comm
 *      All ITextRange methods return HRESULTs.  If the method can move a
 *      range cp, the HRESULT is NOERROR if movement occurs and S_FALSE if
 *      no movement occurs.  These methods usually take a <p pDelta> argument
 *      that returns the count of characters or Units actually moved.  If this
 *      parameter is NULL, E_INVALIDARG is returned.  Other return values
 *      include E_NOTIMPL, e.g., for Unit values not implemented,
 *      E_OUTOFMEMORY, e.g., when allocations fail, and CO_E_RELEASED, when
 *      the CTxtEdit (_ped) to which the range is attached has been deleted.
 *
 *      For more complete documentation, please see tom.doc
 *
 *  @devnote
 *      All ptr parameters must be validated before use and all entry points
 *      need to check whether this range is a zombie.  These checks are
 *      done in one of three places: 1) immediately on entry to a function,
 *      2) immediately on entry to a helper function (e.g., private Mover()
 *      for the move methods), or 3) before storing the out value.
 *      Alternative 3) is used for optional return values, such as pDelta
 *      and pB.
 *
 *      To achieve a simple, efficient inheritance model, CTxtSelection
 *      inherits ITextSelection through CTxtRange.  Otherwise we'd have a
 *      diamond inheritance, since ITextSelection itself inherits from
 *      ITextRange. Diamond inheritance creates two copies of the multiply
 *      inherited class unless that class is inherited virtually. Virtual
 *      inheritance uses run-time base-offset tables and is slower and
 *      bigger.  To avoid such a mess, we include the extra ITextSelection
 *      methods in CTxtRange, with the intention that they'll never be called
 *      and therefore they return E_NOTIMPL. This is overridden for
 *      ITextSelection objects
 *
 *  @future
 *      1) Finder match ^p, etc.
 *      2) Fast GetEffects() method. Would speed up the myriad IsProtected()
 *         calls and be useful for getting other effects as well.
 *      3) Fast copies/pastes of RichEdit binary format. This can be done by
 *         creating a method to copy a range to a new CTxtStory and a method
 *         to insert a CTxtStory.
 *      4) Delayed rendering
 *
 *  Copyright (c) 1995-1998, Microsoft Corporation. All rights reserved.
 */

#include "_common.h"
#include "_select.h"
#include "_edit.h"
#include "_line.h"
#include "_frunptr.h"
#include "_tomfmt.h"
#include "_disp.h"
#include "_objmgr.h"
#include "_callmgr.h"
#include "_measure.h"

ASSERTDATA

#define DEBUG_CLASSNAME CTxtRange
#include "_invar.h"

HRESULT QueryInterface (REFIID riid, REFIID riid1, IUnknown *punk,
                        void **ppv, BOOL fZombie);


//----------------- CTxtRange (ITextRange) PUBLIC methods ----------------------------------

//----------------------- CTxtRange IUnknown Methods -------------------------------------

/*
 *  CTxtRange::QueryInterface (riid, ppv)
 *
 *  @mfunc
 *      IUnknown method
 *
 *  @rdesc
 *      HRESULT = (!ppv) ? E_INVALIDARG :
 *                (interface found) ? NOERROR : E_NOINTERFACE
 */
STDMETHODIMP CTxtRange::QueryInterface (
    REFIID  riid,           //@parm Reference to requested interface ID
    void ** ppv)            //@parm Out parm to receive interface ptr
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::QueryInterface");

    REFIID riid1 = _fSel && IsEqualIID(riid, IID_ITextSelection)
                 ? IID_ITextSelection : IID_ITextRange;
#ifndef PEGASUS
    return ::QueryInterface(riid, riid1, this, ppv, IsZombie());
#else
    return 0;
#endif
}

/*
 *  CTxtRange::AddRef()
 *
 *  @mfunc
 *      IUnknown method
 *
 *  @rdesc
 *      ULONG - incremented reference count
 */
STDMETHODIMP_(ULONG) CTxtRange::AddRef()
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::AddRef");

    return ++_cRefs;
}

/*
 *  CTxtRange::Release()
 *
 *  @mfunc
 *      IUnknown method
 *
 *  @rdesc
 *      ULONG - decremented reference count
 */
STDMETHODIMP_(ULONG) CTxtRange::Release()
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Release");

    _cRefs--;

    if(!_cRefs)
    {
        delete this;
        return 0;
    }

    Assert(_cRefs > 0);
    return _cRefs;
}


//------------------------ CTxtRange IDispatch Methods -------------------------------------

/*
 *  CTxtRange::GetTypeInfoCount(pcTypeInfo)
 *
 *  @mfunc
 *      Get the number of TYPEINFO elements (1)
 *
 *  @rdesc
 *      HRESULT
 */
STDMETHODIMP CTxtRange::GetTypeInfoCount (
    UINT * pcTypeInfo)          //@parm Out parm to receive type-info count
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetTypeInfoCount");

    if(!pcTypeInfo)
        return E_INVALIDARG;

    *pcTypeInfo = 1;
    return NOERROR;
}

/*
 *  CTxtRange::GetTypeInfo(iTypeInfo, lcid, ppTypeInfo)
 *
 *  @mfunc
 *      Return ptr to type information object for ITextSelection interface
 *
 *  @rdesc
 *      HRESULT
 */
STDMETHODIMP CTxtRange::GetTypeInfo (
    UINT        iTypeInfo,      //@parm Index of type info to return
    LCID        lcid,           //@parm Local ID of type info
    ITypeInfo **ppTypeInfo)     //@parm Out parm to receive type info
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetTypeInfo");

    return ::GetTypeInfo(iTypeInfo, g_pTypeInfoSel, ppTypeInfo);
}

/*
 *  CTxtRange::GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid)
 *
 *  @mfunc
 *      Get DISPIDs for methods in the ITextSelection, ITextRange, ITextFont,
 *      and ITextPara interfaces
 *
 *  @rdesc
 *      HRESULT
 *
 *  @devnote
 *      If the ITextFont and ITextPara ever offer more methods than exposed
 *      in their type libraries, the code should delegate to the corresponding
 *      GetIDsOfNames. The current code only gets DISPIDs for the methods in
 *      type libraries, thereby not having to instantiate the objects.
 */
STDMETHODIMP CTxtRange::GetIDsOfNames (
    REFIID      riid,           //@parm Interface ID to interpret names for
    OLECHAR **  rgszNames,      //@parm Array of names to be mapped
    UINT        cNames,         //@parm Count of names to be mapped
    LCID        lcid,           //@parm Local ID to use for interpretation
    DISPID *    rgdispid)       //@parm Out parm to receive name mappings
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetIDsOfNames");

    HRESULT hr = GetTypeInfoPtrs();             // Ensure TypeInfo ptrs are OK
    if(hr != NOERROR)
        return hr;
        
    if(g_pTypeInfoSel->GetIDsOfNames(rgszNames, cNames, rgdispid) == NOERROR)
        return NOERROR;

    if(g_pTypeInfoFont->GetIDsOfNames(rgszNames, cNames, rgdispid) == NOERROR)
        return NOERROR;

    return g_pTypeInfoPara->GetIDsOfNames(rgszNames, cNames, rgdispid);
}

/*
 *  CTxtRange::Invoke(dispidMember, riid, lcid, wFlags, pdispparams,
 *                    pvarResult, pexcepinfo, puArgError)
 *  @mfunc
 *      Invoke methods for the ITextRange and ITextSelection objects, as
 *      well as for ITextFont and ITextPara interfaces on those objects.
 *
 *  @rdesc
 *      HRESULT
 */
STDMETHODIMP CTxtRange::Invoke (
    DISPID      dispidMember,   //@parm Identifies member function
    REFIID      riid,           //@parm Pointer to interface ID
    LCID        lcid,           //@parm Locale ID for interpretation
    USHORT      wFlags,         //@parm Flags describing context of call
    DISPPARAMS *pdispparams,    //@parm Ptr to method arguments
    VARIANT *   pvarResult,     //@parm Out parm for result (if not NULL)
    EXCEPINFO * pexcepinfo,     //@parm Out parm for exception info
    UINT *      puArgError)     //@parm Out parm for error
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Invoke");

    HRESULT hr = GetTypeInfoPtrs();         // Ensure TypeInfo ptrs are OK
    if(hr != NOERROR)
        return hr;
        
    if(IsZombie())
        return CO_E_RELEASED;

    IDispatch * pDispatch;
    ITypeInfo * pTypeInfo;

    if((DWORD)dispidMember <= 0x2ff)        // Include default (0), selection,
    {                                       //  and range DISPIDs
        pTypeInfo = g_pTypeInfoSel;
        pDispatch = this;
        AddRef();                           // Compensate for Release() below
    }
    else if((DWORD)dispidMember <= 0x3ff)   // 0x300 to 0x3ff: DISPIDs
    {                                       //  reserved for ITextFont
        pTypeInfo = g_pTypeInfoFont;
        hr = GetFont((ITextFont**)&pDispatch);
    }
    else if((DWORD)dispidMember <= 0x4ff)   // 0x400 to 0x4ff: DISPIDs
    {                                       //  reserved for ITextPara
        pTypeInfo = g_pTypeInfoPara;
        hr = GetPara((ITextPara **)&pDispatch);
    }
    else                                    // dispidMember is negative or
        return DISP_E_MEMBERNOTFOUND;       //  > 0x4ff, i.e., not TOM

    if(hr != NOERROR)                       // Couldn't instantiate ITextFont
        return hr;                          //  or ITextPara

    hr = pTypeInfo->Invoke(pDispatch, dispidMember, wFlags,
                             pdispparams, pvarResult, pexcepinfo, puArgError);
#ifndef PEGASUS
    pDispatch->Release();
#endif
    return hr;
}


//----------------------- ITextRange Methods/Properties ------------------------

/*
 *  CTxtRange::CanEdit (pB)
 *
 *  @mfunc
 *      Set *<p pB> = tomTrue iff this range can be edited and
 *      pB isn't NULL
 *
 *  @rdesc
 *      HRESULT = (can edit) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::CanEdit (
    long * pB)          //@parm Out parm to receive boolean value
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::CanEdit");

    if(IsZombie())
        return CO_E_RELEASED;

    CCallMgr    callmgr(GetPed());
    return IsTrue(!WriteAccessDenied(), pB);
}

/*
 *  CTxtRange::CanPaste (pVar, long Format, pB)
 *
 *  @mfunc
 *      Set *<p pB> = tomTrue iff the data object <p pVar>->punkVal can be
 *      pasted into this range and pB isn't NULL.  If <p pVar> is NULL,
 *      use the clipboard instead.
 *
 *  @rdesc
 *      HRESULT = (can paste) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::CanPaste (
    VARIANT *   pVar,       //@parm Data object to paste
    long        Format,     //@parm Desired clipboard format
    long *      pB)         //@parm Out parm to receive boolean value
{
#ifndef PEGASUS
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::CanPaste");

    if(IsZombie())
        return CO_E_RELEASED;

    CCallMgr        callmgr(GetPed());
    HRESULT         hr;
    IDataObject *   pdo = NULL;             // Default clipboard

    if(pVar && pVar->vt == VT_UNKNOWN)
        pVar->punkVal->QueryInterface(IID_IDataObject, (void **)&pdo);

    hr = IsTrue(!WriteAccessDenied() &&
                (GetPed()->GetDTE()->CanPaste(pdo, (CLIPFORMAT)Format,
                 RECO_PASTE)), pB);
    if(pdo)
        pdo->Release();

    return hr;
#else
    return 0;
#endif
}

/*
 *  ITextRange::ChangeCase (long Type)
 *
 *  @mfunc
 *      Change the case of letters in this range according to Type:
 *
 *      tomSentenceCase = 0: capitalize first letter of each sentence
 *      tomLowerCase    = 1: change all letters to lower case
 *      tomUpperCase    = 2: change all letters to upper case
 *      tomTitleCase    = 3: capitalize the first letter of each word
 *      tomToggleCase   = 4: toggle the case of each letter
 *  
 *  @rdesc
 *      HRESULT = (WriteAccessDenied) ? E_ACCESSDENIED :
 *                (if change) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::ChangeCase (
    long Type)      //@parm Type of case change. Default value: tomLower
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::ChangeCase");

    if(IsZombie())
        return CO_E_RELEASED;

    CCallMgr callmgr(GetPed());

    if(WriteAccessDenied())
        return E_ACCESSDENIED;

    IUndoBuilder *  publdr;
    CGenUndoBuilder undobldr(GetPed(), UB_AUTOCOMMIT, &publdr);
    LONG            cpMin, cpMax;
    LONG            cch = GetRange(cpMin, cpMax);
    CRchTxtPtr      rtp(*this);

    undobldr.StopGroupTyping();

    rtp.SetCp(cpMin);
    return (rtp.ChangeCase(cch, Type, publdr)) ? NOERROR : S_FALSE;
}

/*
 *  CTxtRange::Collapse (bStart)
 *
 *  @mfunc
 *      Collapse this range into a degenerate point either at the
 *      the start (<p bStart> is nonzero or the end (<p bStart> = 0)
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::Collapse (
    long bStart)            //@parm Flag specifying end to collapse at
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Collapse");

    if(IsZombie())
        return CO_E_RELEASED;

    CCallMgr callmgr(GetPed());

    if(!_cch)                           // Already collapsed
        return S_FALSE;                 // Signal that no change occurred
        
    Collapser(bStart);
    Update(TRUE);                       // Update selection
    return NOERROR;                     // Signal that change occurred
}

/*
 *  CTxtRange::Copy (pVar)
 *
 *  @mfunc
 *      Copy the plain and/or rich text to a data object and return the
 *      object ptr in <p pVar>.  If <p pVar> is null, copy to the clipboard.
 *
 *  @rdesc
 *      HRESULT = (if success) ? NOERROR : E_OUTOFMEMORY
 */
STDMETHODIMP CTxtRange::Copy (
    VARIANT * pVar)             //@parm Out parm for data object
{
#ifndef PEGASUS
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Copy");

    if(IsZombie())
        return CO_E_RELEASED;

    CLightDTEngine * pldte = &GetPed()->_ldte;

    if(pVar && pVar->vt == (VT_UNKNOWN | VT_BYREF))
    {
        return pldte->RangeToDataObject(this, SF_TEXT | SF_RTF,
                                    (IDataObject **)pVar->ppunkVal);
    }
    return pldte->CopyRangeToClipboard(this);
#else
    return 0;
#endif
}

/*
 *  CTxtRange::Cut (pVar)
 *
 *  @mfunc
 *      Cut the plain and/or rich text to a data object and return the
 *      object ptr in <p pVar>.  If <p pVar> is null,
 *      cut to the clipboard.
 *
 *  @rdesc
 *      HRESULT = (if success) ? NOERROR : E_OUTOFMEMORY
 */
STDMETHODIMP CTxtRange::Cut (
    VARIANT * pVar)     //@parm Out parm for data object
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Cut");

    if(IsZombie())
        return CO_E_RELEASED;

    CCallMgr callmgr(GetPed());

    if(WriteAccessDenied())
        return E_ACCESSDENIED;

    HRESULT hr = Copy(pVar);

    Replacer(0, NULL);
    return hr;
}

/*
 *  CTxtRange::Delete (Unit, Count, pDelta)
 *
 *  @mfunc
 *      If this range is nondegenerate, delete it along with |Count| - 1 Units
 *      in the direction specified by the sign of Count.  If this range is
 *      degenerate, delete Count Units.
 *
 *  @rdesc
 *      HRESULT = (WriteAccessDenied) ? E_ACCESSDENIED :
 *                (all requested Units deleted) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::Delete (
    long    Unit,           //@parm Unit to use
    long    Count,          //@parm Number of chars to delete
    long *  pDelta)         //@parm Out parm to receive count of units deleted
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Delete");

    if(pDelta)
        *pDelta = 0;
    
    if(IsZombie())
        return CO_E_RELEASED;

    CCallMgr callmgr(GetPed());

    if(WriteAccessDenied())
        return E_ACCESSDENIED;

    LONG    cchSave = _cch;                 // Remember initial count
    LONG    cchText = GetAdjustedTextLength();
    LONG    CountOrg = Count;
    LONG    cpMin, cpMost;
    LONG    cUnit = 0;                      // Default no Units
    MOVES   Mode = (Count >= 0) ? MOVE_END : MOVE_START;

    GetRange(cpMin, cpMost);
    if(cpMost > cchText)                    // Can't delete final CR. To get
    {                                       //  *pDelta right, handle here
        Set(cpMin, cpMin - cchText);        // Backup before CR & set active
        if(Count > 0)                       //  end at cpMin since there's
        {                                   //  nothing to delete forward
            Count = 0;
            if(!_cch)                       // Only collapsed selection of
                Mode = MOVE_IP;             //  final CR: set up nothing
        }                                   //  deleted (MOVE_IP = 0)
    }
    if(Count)
    {
        if((_cch ^ Mode) < 0)               // Be sure active end is in
            FlipRange();                    //  deletion direction
        if(cchSave)                         // Deleting nondegenerate range
            Count -= Mode;                  //  counts as one unit
        if(Mover(Unit, Count, &cUnit, Mode) // Try to expand range for
            == E_INVALIDARG)                //  remaining Count Units
        {
            if(pDelta)
                *pDelta = 0;
            return E_INVALIDARG;
        }
        if(GetCp() > cchText && cUnit > 0)  // Range includes final CR, which
        {                                   //  cannot be deleted. Reduce
            if(Unit == tomCharacter)        //  counts for some Units
                cUnit -= GetTextLength() - cchText;
            else if(Unit == tomWord)
                cUnit--;                    // An EOP qualifies as a tomWord
        }
    }

    if(cchSave)                             // Deleting nondegenerate range
        cUnit += Mode;                      //  counts as a Unit

    if(pDelta)
        *pDelta = cUnit;

    if(_cch)                                // Mover() may have changed _cch
    {                                       
        IUndoBuilder *  publdr;
        CGenUndoBuilder undobldr(GetPed(), UB_AUTOCOMMIT, &publdr);

        if (publdr)
        {
            publdr->StopGroupTyping();
            publdr->SetNameID(UID_DELETE);
        }

        // FUTURE (murrays): the cchSave case should set up undo to
        // restore the original range, not the extended range resulting
        // when |CountOrg| > 1.  This could be done using two calls to
        // ReplaceRange(), one to delete the original range and one to
        // delete the rest
        SELRR selrr = !_fSel || cchSave ? SELRR_REMEMBERRANGE :
                      CountOrg > 0      ? SELRR_REMEMBERCPMIN :
                                          SELRR_REMEMBERENDIP;

        ReplaceRange(0, NULL, publdr, selrr);

        if (cUnit == CountOrg ||            // Delete(Unit,0,0)
            cUnit == 1 && !CountOrg)        //  deletes one "Unit", namely
        {                                   //  what's selected
            return NOERROR;                 // Signal everything deleted as
        }                                   //  requested
    }
    else if(cchSave)                        // Collapsed selection of final CR
    {                                       //  but didn't delete anything
        Update(TRUE);                       // Selection highlighting changed
    }
    return S_FALSE;                         // Less deleted than requested
}

/*
 *  CTxtRange::EndOf (Unit, Extend, pDelta)
 *
 *  @mfunc
 *      Move this range end(s) to end of the first overlapping Unit in
 *      the range.
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if Unit supported) ? S_FALSE : E_NOTIMPL
 */
STDMETHODIMP CTxtRange::EndOf (
    long    Unit,           //@parm Unit to use
    long    Extend,         //@parm If true, leave other end alone
    long *  pDelta)         //@parm Count of chars that End is moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::EndOf");

    CCallMgr    callmgr(GetPed());
    LONG        cpMost;

    HRESULT hr = Expander (Unit, Extend, pDelta, NULL, &cpMost);
    if(hr == NOERROR)
        Update(TRUE);                   // Update selection

    return hr;
}

/*
 *  CTxtRange::Expand (Unit, pDelta)
 *
 *  @mfunc
 *      Expand this range so that any partial Units it contains are
 *      completely contained.  If this range consists of one or more full
 *      Units, no change is made.  If this range is an insertion point at
 *      the beginning or within a Unit, Expand() expands this range to include
 *      that Unit.  If this range is an insertion point at the end of the
 *      story, Expand() tries to set this range to include the last Unit in
 *      the story.  The active end is always cpMost except for the last case.
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if Unit supported) ? S_FALSE : E_NOTIMPL
 */
STDMETHODIMP CTxtRange::Expand (
    long    Unit,           //@parm Unit to expand range to
    long *  pDelta)         //@parm Out parm to receive count of chars added
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Expand");

    CCallMgr callmgr(GetPed());
    LONG     cpMin, cpMost;

    HRESULT hr = Expander (Unit, TRUE, pDelta, &cpMin, &cpMost);
    if(hr == NOERROR)
        Update(TRUE);                   // Update selection

    return hr;
}

/*
 *  CTxtRange::FindText (bstr, cch, Flags, pLength)
 *
 *  @mfunc
 *      If this range isn't an insertion point already, convert it into an
 *      insertion point at its End if <p cch> <gt> 0 and at its Start if
 *      <p cch> <lt> 0.  Then search up to <p cch> characters of the range
 *      looking for the string <p bstr> subject to the compare flags
 *      <p Flags>.  If <p cch> <gt> 0, the search is forward and if <p cch>
 *      <lt> 0 the search is backward.  If the string is found, the range
 *      limits are changed to be those of the matched string and *<p pLength>
 *      is set equal to the length of the string. If the string isn't found,
 *      the range remains unchanged and *<p pLength> is set equal to 0.
 *
 *  @rdesc
 *      HRESULT = (if <p bstr> found) ? NOERROR : S_FALSE
 *
 *  @devnote
 *      Argument validation of the three Find methods is done by the helper
 *      function CTxtRange::Finder(bstr, cch, dwFlags, pDelta, fExtend, fFlip)
 */
STDMETHODIMP CTxtRange::FindText (
    BSTR    bstr,       //@parm String to find
    long    Count,      //@parm Max count of chars to search
    long    Flags,      //@parm Flags governing compares
    long *  pDelta)     //@parm Out parm to receive count of chars moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::FindText");

    return Finder(bstr, Count, Flags, pDelta, MOVE_IP);
}

/*
 *  CTxtRange::FindTextEnd (bstr, cch, Flags, pLength)
 *
 *  @mfunc
 *      Starting from this range's End, search up to <p cch> characters
 *      looking for the string <p bstr> subject to the compare flags
 *      <p Flags>.  If <p cch> <gt> 0, the search is forward and if <p cch>
 *      <lt> 0 the search is backward.  If the string is found, the range
 *      limits are changed to be those of the matched string and *<p pLength>
 *      is set equal to the length of the string. If the string isn't found,
 *      the range remains unchanged and *<p pLength> is set equal to 0.
 *
 *  @rdesc
 *      HRESULT = (if <p bstr> found) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::FindTextEnd (
    BSTR    bstr,       //@parm String to find
    long    Count,      //@parm Max count of chars to search
    long    Flags,      //@parm Flags governing compares
    long *  pDelta)     //@parm Out parm to receive count of chars moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::FindTextEnd");

    return Finder(bstr, Count, Flags, pDelta, MOVE_END);
}

/*
 *  CTxtRange::FindTextStart (bstr, cch, Flags, pDelta)
 *
 *  @mfunc
 *      Starting from this range's Start, search up to <p cch> characters
 *      looking for the string <p bstr> subject to the compare flags
 *      <p Flags>.  If <p cch> <gt> 0, the search is forward and if <p cch>
 *      <lt> 0 the search is backward.  If the string is found, the range
 *      limits are changed to be those of the matched string and *<p pLength>
 *      is set equal to the length of the string. If the string isn't found,
 *      the range remains unchanged and *<p pLength> is set equal to 0.
 *
 *  @rdesc
 *      HRESULT = (if <p bstr> found) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::FindTextStart (
    BSTR    bstr,       //@parm String to find
    long    Count,      //@parm Max count of chars to search
    long    Flags,      //@parm Flags governing compares
    long *  pDelta)     //@parm Out parm to receive count of chars moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::FindTextStart");

    return Finder(bstr, Count, Flags, pDelta, MOVE_START);
}

/*
 *  CTxtRange::GetChar (pChar)
 *
 *  @mfunc
 *      Set *<p pChar> equal to the character at cpFirst
 *
 *  @rdesc
 *      HRESULT = (<p pChar>) NOERROR ? E_INVALIDARG
 *
 *  @devnote
 *      This method is very handy for walking a range character by character
 *      from the Start. Accordingly, it's desirable that the active end
 *      is at the Start. We set this up for a range, since the API doesn't
 *      care which range end is active.  But we can't do this for a selection,
 *      since the selection API depends on the active end.
 */
STDMETHODIMP CTxtRange::GetChar (
    long * pChar)           //@parm Out parm for char
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetChar");

    HRESULT hr = GetLong(0, pChar);
    if(hr != NOERROR)
        return hr;

    if(_cch > 0)                            // Active end at cpMost (End)
    {
        if(_fSel)
        {
            CTxtPtr tp(_rpTX);              // For selection, can't change
            tp.AdvanceCp(-_cch);            //  active end
            *pChar = (long)(tp.GetChar());
            return NOERROR;
        }
        FlipRange();                        // For range, it's more efficient
    }                                       //  to work from cpFirst and API
    *(DWORD *)pChar = _rpTX.GetChar();      //  doesn't expose RichEdit active
                                            //  end
    return NOERROR;
}

/*
 *  CTxtRange::GetDuplicate (ppRange)
 *
 *  @mfunc
 *      Get a clone of this range object.  For example, you may want to
 *      create an insertion point to traverse a range, so you clone the
 *      range and then set the clone's cpLim equal to its cpFirst. A range
 *      is characterized by its cpFirst, cpLim, and the story it belongs to.
 *
 *  @rdesc
 *      HRESULT = (if success) ? NOERROR :
 *                (<p ppRange>) ? E_OUTOFMEMORY : E_INVALIDARG
 *
 *  @comm
 *      Even if this range is a selection, the clone returned is still only
 *      a range.
 */
STDMETHODIMP CTxtRange::GetDuplicate (
    ITextRange ** ppRange)      //@parm Out parm to receive duplicate of range
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetDuplicate");

    HRESULT hr = GetLong(NULL, (LONG *)ppRange);
    if(hr != NOERROR)
        return hr;

    ITextRange *prg = new CTxtRange(*this);
    if(prg)
    {
        *ppRange = prg;
        prg->AddRef();
        return NOERROR;
    }
    return E_OUTOFMEMORY;
}

/*
 *  ITextRange::GetEmbeddedObject (ppV)
 *
 *  @mfunc
 *      Property get method that gets a ptr to the object at cpFirst
 *
 *  @rdesc
 *      HRESULT = (!ppV) ? E_INVALIDARG :
 *                (object found) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::GetEmbeddedObject (
    IUnknown **ppV)         //@parm Out parm to receive embedded object
{
    HRESULT hr = GetLong(NULL, (LONG *)ppV);
    if(hr != NOERROR)
        return hr;
    
    if(GetObjectCount())
    {
        COleObject *pobj = GetPed()->_pobjmgr->GetObjectFromCp(GetCpMin());

        if(pobj && (*ppV = pobj->GetIUnknown()) != NULL)
        {
            (*ppV)->AddRef();
            return NOERROR;
        }
    }
    return S_FALSE;
}

/*
 *  CTxtRange::GetEnd (pcpLim)
 *
 *  @mfunc
 *      Get this range's End (cpMost) cp
 *
 *  @rdesc
 *      HRESULT = (<p pcpLim>) ? NOERROR : E_INVALIDARG
 */
STDMETHODIMP CTxtRange::GetEnd (
    long * pcpLim)          //@parm Out parm to receive End cp value
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetEnd");

    return GetLong(GetCpMost(), pcpLim);
}

/*
 *  CTxtRange::GetFont (ppFont)
 *
 *  @mfunc
 *      Get an ITextFont object with the character attributes of this range     
 *
 *  @rdesc
 *      HRESULT = <p ppFont> ? NOERROR : E_INVALIDARG
 */
STDMETHODIMP CTxtRange::GetFont (
    ITextFont ** ppFont)    //@parm Out parm to receive ITextFont object
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetFont");

    HRESULT hr = GetLong(NULL, (LONG *)ppFont);
    if(hr != NOERROR)
        return hr;

    *ppFont = (ITextFont *) new CTxtFont(this);
    return *ppFont ? NOERROR : E_OUTOFMEMORY;
}

/*
 *  CTxtRange::GetFormattedText (ppRange)
 *
 *  @mfunc
 *      Retrieves an ITextRange with this range's formatted text.
 *      If <p ppRange> is NULL, the clipboard is the target.
 *
 *  @rdesc
 *      HRESULT = (if success)  ? NOERROR :
 *                (<p ppRange>) ? E_OUTOFMEMORY : E_INVALIDARG
 */
STDMETHODIMP CTxtRange::GetFormattedText (
    ITextRange ** ppRange)      //@parm Out parm to receive formatted text
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetFormattedText");

    return GetDuplicate(ppRange);
}

/*
 *  CTxtRange::GetIndex (Unit, pIndex)
 *
 *  @mfunc
 *      Set *<p pIndex> equal to the Unit number at this range's cpFirst
 *
 *  @rdesc
 *      HRESULT = (!<p pIndex>) ? E_INVALIDARG :
 *                (Unit not implemented) ? E_NOTIMPL :
 *                (Unit available) ? NOERROR : S_FALSE
 *  @future
 *      implement tomWindow?
 */
STDMETHODIMP CTxtRange::GetIndex (
    long    Unit,           //@parm Unit to index
    long *  pIndex)         //@parm Out parm to receive index value
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetIndex");

    HRESULT hr = GetLong(0, pIndex);
    if(hr != NOERROR)
        return hr;

    LONG      cp;
    LONG      cUnit = tomBackward;
    CTxtRange rg(*this);

    hr = rg.Expander(Unit, FALSE, NULL,         // Go to Start of Unit; else
                             &cp, NULL);        //  UnitCounter gives 1 extra
    if(FAILED(hr))
        return hr;                              // Unit not recognized

    LONG cch = rg.UnitCounter(Unit, cUnit, 0);
    
    if(cch == tomForward)                       // UnitCounter() doesn't know
        return E_NOTIMPL;                       //  Unit

    if(cch == tomBackward)                      // Unit not in story
        return S_FALSE;

    *pIndex = -cUnit + 1;                       // Make count positive and
                                                //  1-based
    return NOERROR;
}

/*
 *  CTxtRange::GetPara (ppPara)
 *
 *  @mfunc
 *      Get an ITextPara object with the paragraph attributes of this range     
 *
 *  @rdesc
 *      HRESULT = <p ppPara> ? NOERROR : E_INVALIDARG
 */
STDMETHODIMP CTxtRange::GetPara (
    ITextPara ** ppPara)    //@parm Out parm to receive ITextPara object
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetPara");

    HRESULT hr = GetLong(NULL, (LONG *)ppPara);
    if(hr != NOERROR)
        return hr;

    *ppPara = (ITextPara *) new CTxtPara(this);
    return *ppPara ? NOERROR : E_OUTOFMEMORY;
}

/*
 *  CTxtRange::GetPoint (px, py, Type)
 *
 *  @mfunc
 *      Get point for selection Start or End and intraline position
 *      as determined by <p Type>.
 *
 *  @rdesc
 *      HRESULT = (!<p px> or !<p py>) ? E_INVALIDARG :
 *                (if success) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::GetPoint (
    long    Type,       //@parm Type of point
    long *  px,         //@parm Out parm for x coordinate
    long *  py)         //@parm Out parm for y coordinate
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtSelection::GetPoint");

    if(!px || !py)
        return E_INVALIDARG;

    *px = *py = 0;

    if(IsZombie())
        return CO_E_RELEASED;

    LONG        ili;
    BOOL        fAtEnd = _cch > 0;              // Default value for IP
    POINT       pt;
    CRchTxtPtr  rtp(*this);                     // Default active end
    CTxtEdit *  ped = GetPed();
    CDisplay *  pdp = ped->_pdp;                // Save indirections

    if(!pdp || !ped->fInplaceActive())
        return E_FAIL;                          // No display or not active
                                                // then we can do nothing.
    if(fAtEnd ^ !(Type & tomStart))             // Move tp to active end
        rtp.Advance(-_cch);

    ili = pdp->PointFromTp(rtp, NULL, fAtEnd, pt, NULL, Type & 0x1f);

    RECT rcView;                                // Verify return value makes
                                                // sense since PointFromTp
    pdp->GetViewRect(rcView, NULL);             // may return values outside
                                                // client rect
    rcView.bottom++;                            // Enlarge Rect to include
    rcView.right++;                             // bottom and right edges
    if(ili >= 0 && PtInRect(&rcView, pt))       // Function succeeded
    {
        // Caller wants screen coordinates?
        if ( !(Type & tomClientCoord) )
            ped->TxClientToScreen(&pt); 

        *px = pt.x;
        *py = pt.y;
        return NOERROR;
    }
    return S_FALSE;                             // Function failed
}

/*
 *  CTxtRange::GetStart (pcpFirst)
 *
 *  @mfunc
 *      Get this range's Start (cpMin) cp
 *
 *  @rdesc
 *      HRESULT = (<p pcpFirst>) ? NOERROR : E_INVALIDARG
 */
STDMETHODIMP CTxtRange::GetStart (
    long * pcpFirst)        //@parm Out parm to receive Start cp value
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetStart");

    return GetLong(GetCpMin(), pcpFirst);
}

/*
 *  CTxtRange::GetStoryLength (pcch)
 *
 *  @mfunc
 *      Set *<p pcch> = count of chars in this range's story
 *
 *  @rdesc
 *      HRESULT = (<p pcch>) ? NOERROR : E_INVALIDARG
 */
STDMETHODIMP CTxtRange::GetStoryLength (
    long * pcch)        //@parm Out parm to get length of this range's story
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetStoryLength");

    return GetLong(GetTextLength(), pcch);
}

/*
 *  ITextRange::GetStoryType (pValue)
 *
 *  @mfunc
 *      Property get method that gets the type of this range's
 *      story.
 *
 *  @rdesc
 *      HRESULT = (pValue) NOERROR ? E_INVALIDARG
 */
STDMETHODIMP CTxtRange::GetStoryType (
    long *pValue)       //@parm Out parm to get type of this range's story
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetStoryType");

    return GetLong(tomUnknownStory, pValue);
}

/*
 *  CTxtRange::GetText (pbstr)
 *
 *  @mfunc
 *      Get plain text in this range. The Text property is the default
 *      property for ITextRange.
 *
 *  @rdesc
 *      HRESULT = (if success) ? NOERROR :
 *                (!<p pbstr>) ? E_INVALIDARG : E_OUTOFMEMORY
 */
STDMETHODIMP CTxtRange::GetText (
    BSTR * pbstr)               //@parm Out parm to receive bstr
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetText");

    HRESULT hr = GetLong(NULL, (LONG *)pbstr);
    if(hr != NOERROR)
        return hr;

    if(!GetCch())
        return NOERROR;
        
    LONG cpMin, cpMost;
    LONG cch = GetRange(cpMin, cpMost);

    *pbstr = SysAllocStringLen(NULL, cch);
    if(!*pbstr)
        return E_OUTOFMEMORY;

    CTxtPtr tp(_rpTX);
    tp.SetCp(cpMin);
    tp.GetText( cch, (TCHAR*) * pbstr );
    return NOERROR;
}

/*
 *  CTxtRange::InRange (pRange, pB)
 *
 *  @mfunc
 *      Returns *<p pB> = tomTrue iff this range points within or at the same
 *      text as <p pRange> does
 *
 *  @rdesc
 *      HRESULT = (within range) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::InRange (
    ITextRange * pRange,        //@parm ITextRange to compare with
    long *       pB)            //@parm Out parm for comparison result
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::InRange");

    return IsTrue(Comparer(pRange), pB);
}


/*
 *  CTxtRange::InStory (pRange, pB)
 *
 *  @mfunc
 *      Return *pB = tomTrue iff this range's story is the same as
 *      <p pRange>'s
 *
 *  @rdesc
 *      HRESULT = (in story) ? NOERROR : S_FALSE
 *
 *  @future
 *      If RichEdit acquires the ability to have multiple stories and
 *      therefore ranges get a _story member, then compare that member
 *      instead of calling _rpTX.SameRuns().
 */
STDMETHODIMP CTxtRange::InStory (
    ITextRange *pRange,     //@parm ITextRange to query for private interface
    long *      pB)         //@parm Out parm to receive tomBool result
{
#ifndef PEGASUS
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::InStory");

    return IsTrue(IsSameVtables(this, pRange) &&        // Same vtables,
        _rpTX.SameRuns(&((CTxtRange *)pRange)->_rpTX),  //  same Runs
        pB);
#else
    return 0;
#endif
}

/*
 *  CTxtRange::IsEqual (pRange, pB)
 *
 *  @mfunc
 *      Returns *<p pB> = tomTrue iff this range points at the same text (cp's
 *      and story) as <p pRange> does and pB isn't NULL.
 *
 *  @rdesc
 *      HRESULT = (equal objects) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::IsEqual (
    ITextRange * pRange,        //@parm ITextRange to compare with
    long *       pB)            //@parm Out parm for comparison result
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::IsEqual");

    return IsTrue(Comparer(pRange) < 0, pB);
}

/*
 *  CTxtRange::Move (Unit, Count, pDelta)
 *
 *  @mfunc
 *      Move end(s) <p Count> <p Unit>'s, returning *<p pDelta> = # units
 *      actually moved. In general, this method converts a range into an
 *      insertion point if it isn't already, and moves the insertion point.
 *      If <p Count> <gt> 0, motion is forward toward the end of the story;
 *      if <p Count> <lt> 0, motion is backward toward the beginning.
 *      <p Count> = 0 moves cpFirst to the beginning of the <p Unit>
 *      containing cpFirst.
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if Unit supported) ? S_FALSE : E_NOTIMPL
 *  @devnote
 *      Argument validation of the three Move methods is done by the helper
 *      function CTxtRange::Mover(Unit, Count, pDelta, Mode)
 */
STDMETHODIMP CTxtRange::Move (
    long    Unit,           //@parm Unit to use
    long    Count,          //@parm Number of Units to move
    long *  pDelta)         //@parm Out parm to receive actual count of
                            //      Units end is moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Move");
    CCallMgr    callmgr(GetPed());

    return Mover(Unit, Count, pDelta, MOVE_IP);
}

/*
 *  CTxtRange::MoveEnd (Unit, Count, pDelta)
 *
 *  @mfunc
 *      Move End end <p Count> <p Unit>'s, returning *<p pDelta> = # units
 *      actually moved
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if Unit supported) ? S_FALSE : E_NOTIMPL
 */
STDMETHODIMP CTxtRange::MoveEnd (
    long    Unit,           //@parm Unit to use
    long    Count,          //@parm Number of Units to move
    long *  pDelta)         //@parm Out parm to receive actual count of
                            //      Units end is moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::MoveEnd");
    CCallMgr    callmgr(GetPed());

    return Mover(Unit, Count, pDelta, MOVE_END);
}

/*
 *  CTxtRange::MoveEndUntil (Cset, Count, pDelta)
 *
 *  @mfunc
 *      Move the End just past all contiguous characters that are not found
 *      in the set of characters specified by the VARIANT <p Cset> parameter.
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if <p Cset> valid) ? S_FALSE : E_INVALIDARG
 */
STDMETHODIMP CTxtRange::MoveEndUntil (
    VARIANT * Cset,             //@parm Character match set to use
    long      Count,            //@parm Max number of characters to move past
    long *    pDelta)           //@parm Out parm to receive actual count of
                                //      characters end is moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::MoveEndUntil");

    return Matcher(Cset, Count, pDelta, MOVE_END, MATCH_UNTIL);
}

/*
 *  CTxtRange::MoveEndWhile (Cset, Count, pDelta)
 *
 *  @mfunc
 *      Move the End just past all contiguous characters that are found in
 *      the set of characters specified by the VARIANT <p Cset> parameter.
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if <p Cset> valid) ? S_FALSE : E_INVALIDARG
 */
STDMETHODIMP CTxtRange::MoveEndWhile (
    VARIANT * Cset,             //@parm Character match set to use
    long      Count,            //@parm Max number of characters to move past
    long *    pDelta)           //@parm Out parm to receive actual count of
                                //      characters end is moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::MoveEndWhile");

    return Matcher(Cset, Count, pDelta, MOVE_END, MATCH_WHILE);
}

/*
 *  CTxtRange::MoveStart (Unit, Count, pDelta)
 *
 *  @mfunc
 *      Move Start end <p Count> <p Unit>'s, returning *<p pDelta> = # units
 *      actually moved
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if Unit supported) ? S_FALSE : E_NOTIMPL
 */
STDMETHODIMP CTxtRange::MoveStart (
    long    Unit,           //@parm Unit to use
    long    Count,          //@parm Number of Units to move
    long *  pDelta)         //@parm Out parm to receive actual count of
                            //      Units end is moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::MoveStart");
    CCallMgr    callmgr(GetPed());

    return Mover(Unit, Count, pDelta, MOVE_START);
}

/*
 *  CTxtRange::MoveStartUntil (Cset, Count, pDelta)
 *
 *  @mfunc
 *      Move the Start just past all contiguous characters that are not found
 *      in the set of characters specified by the VARIANT <p Cset> parameter.
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if <p Cset> valid) ? S_FALSE : E_INVALIDARG
 */
STDMETHODIMP CTxtRange::MoveStartUntil (
    VARIANT * Cset,             //@parm Character match set to use
    long      Count,            //@parm Max number of characters to move past
    long *    pDelta)           //@parm Out parm to receive actual count of
                                //      characters end is moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::MoveStartUntil");

    return Matcher(Cset, Count, pDelta, MOVE_START, MATCH_UNTIL);
}

/*
 *  CTxtRange::MoveStartWhile (Cset, Count, pDelta)
 *
 *  @mfunc
 *      Move the Start just past all contiguous characters that are found in
 *      the set of characters specified by the VARIANT <p Cset> parameter.
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if <p Cset> valid) ? S_FALSE : E_INVALIDARG
 */
STDMETHODIMP CTxtRange::MoveStartWhile (
    VARIANT * Cset,             //@parm Character match set to use
    long      Count,            //@parm Max number of characters to move past
    long *    pDelta)           //@parm Out parm to receive actual count of
                                //      characters end is moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::MoveStartWhile");

    return Matcher(Cset, Count, pDelta, MOVE_START, MATCH_WHILE);
}

/*
 *  CTxtRange::MoveUntil (Cset,Count, pDelta)
 *
 *  @mfunc
 *      Convert this range into an insertion point if it isn't already,
 *      and keep moving the insertion point until encountering any
 *      character in the set of characters specified by the VARIANT cset
 *      parameter.
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if <p Cset> valid) ? S_FALSE : E_INVALIDARG
 */
STDMETHODIMP CTxtRange::MoveUntil (
    VARIANT * Cset,             //@parm Character match set to use
    long      Count,            //@parm Max number of characters to move past
    long *    pDelta)           //@parm Out parm to receive actual count of
                                //      characters end is moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::MoveUntil");
                            
    return Matcher(Cset, Count, pDelta, MOVE_IP, MATCH_UNTIL);
}

/*
 *  CTxtRange::MoveWhile (Cset, Count, pDelta)
 *
 *  @mfunc
 *      Convert this range into an insertion point if it isn't already,
 *      and keep moving the insertion point so long as (while) the
 *      characters past by are found in set of characters specified by
 *      the VARIANT cset parameter.  Such a contiguous set of characters
 *      is known as a span of characters.  The magnitude of the <p Count>
 *      parameter gives the maximum number of characters to move past and
 *      the sign of <p Count> specifies the direction to move in.
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if <p Cset> valid) ? S_FALSE : E_INVALIDARG
 *
 *  @devnote
 *      Argument validation of the MoveWhile and MoveUntil methods is done by
 *      the helper CTxtRange::Matcher (Cset, Count, pDelta, fExtend, fSpan)
 */
STDMETHODIMP CTxtRange::MoveWhile (
    VARIANT * Cset,             //@parm Character match set to use
    long      Count,            //@parm Max number of characters to move past
    long *    pDelta)           //@parm Out parm to receive actual count of
                                //      characters end is moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::MoveWhile");
                            
    return Matcher(Cset, Count, pDelta, MOVE_IP, MATCH_WHILE);
}

/*
 *  CTxtRange::Paste (pVar, ClipboardFormat)
 *
 *  @mfunc
 *      Paste the data object <p pVar> into this range.  If
 *      <p pVar> is null, paste from the clipboard.
 *
 *  @rdesc
 *      HRESULT = (WriteAccessDenied) ? E_ACCESSDENIED :
 *                (if success) ? NOERROR : E_OUTOFMEMORY
 */
STDMETHODIMP CTxtRange::Paste (
    VARIANT *pVar,              //@parm Data object to paste
    long     ClipboardFormat)   //@parm Desired clipboard format
{
#ifndef PEGASUS
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Paste");

    if(IsZombie())
        return CO_E_RELEASED;

    CCallMgr        callmgr(GetPed());
    HRESULT         hr;
    IDataObject *   pdo = NULL;         // Default clipboard
    IUndoBuilder *  publdr;
    CGenUndoBuilder undobldr(GetPed(), UB_AUTOCOMMIT, &publdr);

    if(WriteAccessDenied())
        return E_ACCESSDENIED;

    if(pVar)
        if (pVar->vt == VT_UNKNOWN)
            pVar->punkVal->QueryInterface(IID_IDataObject, (void **)&pdo);
        else if (pVar->vt == (VT_UNKNOWN | VT_BYREF))
            pdo = (IDataObject *)(*pVar->ppunkVal);

    hr = GetPed()->PasteDataObjectToRange (pdo, this,
        (WORD)ClipboardFormat, NULL, publdr, PDOR_NONE);

    if(pdo && pVar->vt == VT_UNKNOWN)
        pdo->Release();
    Update(TRUE);                       // Update selection
    return hr;
#else
    return 0;
#endif
}

/*
 *  ITextRange::ScrollIntoView(long Code)
 *
 *  @mfunc
 *      Method that scrolls this range into view according to the
 *      code Code defined below.
 *
 *  @rdesc
 *      HRESULT = (if success) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::ScrollIntoView (
    long Code)          //@parm Scroll code
{
#ifndef PEGASUS
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::ScrollIntoView");

    // Check for invalid bits
    if (Code & ~(tomStart + tomEnd + TA_LEFT + TA_TOP + TA_BOTTOM +
                TA_CENTER + TA_STARTOFLINE + TA_ENDOFLINE + TA_LOGICAL))
        return E_INVALIDARG;

    // Validate parameter
    long lCode = tomEnd;
    if (Code & tomStart)
        lCode = tomStart;

    Code &= ~tomStart;

    if(IsZombie())
        return CO_E_RELEASED;

    // Get local copy of ped to save some indirections.
    CTxtEdit *ped = GetPed();

    if (!ped->fInplaceActive())
    {
        // If the control is not active, we can't get the information
        // because no one knows what our client rect is.
        return E_FAIL;
    }

    // Get a local copy of display to save some indirections.
    CDisplay *pdp = ped->_pdp;

    if (pdp->IsFrozen())
    {
        return E_FAIL;
    }

    LONG cpStart;
    LONG cpForEnd;

    GetRange(cpStart, cpForEnd);

    // Get the view rectangle so we can compute the absolute x/y
    RECT rcView;
    pdp->GetViewRect(rcView, NULL);

    // Set up tp for PointFromTp call
    CRchTxtPtr rtp(*this);

    if(_cch > 0)
        rtp.Advance(-_cch);

    // Values used for making returned point locations absolute since
    // PointFromTp adjusts the point returned to be relative to the
    // display.
    const LONG xScrollAdj = pdp->GetXScroll() - rcView.left;
    const LONG yScrollAdj = pdp->GetYScroll() - rcView.top;

    // Get the left/top for the start
    BOOL     taMask = Code & TA_STARTOFLINE; //set beginning of line flag
    BOOL     fAtEnd = _cch ? TRUE : !rtp._rpTX.IsAfterEOP();
    POINT    ptStart;
    CLinePtr rpStart(pdp);
    LONG     iliStart = pdp->PointFromTp(rtp, NULL, _cch ? FALSE : fAtEnd,
                                         ptStart, &rpStart, (lCode == tomStart && Code) ?  Code :
                                         TA_TOP + TA_LEFT);
    ptStart.x += xScrollAdj;
    ptStart.y += yScrollAdj;

    // Get the right/bottom for the end
    rtp.SetCp(cpForEnd);

    POINT    ptEnd;
    CLinePtr rpEnd(pdp);
    LONG     iliEnd = pdp->PointFromTp(rtp, NULL, fAtEnd, ptEnd, &rpEnd,
                                       (lCode == tomEnd && Code) ?  Code :
                                       TA_BOTTOM + TA_RIGHT);
    ptEnd.x += xScrollAdj;
    ptEnd.y += yScrollAdj;

    //
    // Calculate the yScroll
    //

    // The basic idea is to display both the start and the end if possible. But
    // if it is not possible then display the requested end based on the input
    // parameter.

    LONG yHeightView = pdp->GetViewHeight();
    LONG yScroll;

    if (tomStart == lCode)
    {
        // Scroll the Start cp to the top of the view
        yScroll = ptStart.y;
    }
    else
    {
        // Scroll the End cp to the bottom of the view
        yScroll = ptEnd.y - yHeightView;
    }

    //
    // Calculate the X Scroll
    //

    // Default scroll to beginning of the line
    LONG xScroll = 0;

    // Make view local to save a number of indirections
    LONG xWidthView = pdp->GetViewWidth();

    if (iliStart == iliEnd)
    {
        // Entire selection is on the same line so we want to display as
        // much of it as is possible.
        LONG xWidthSel = ptEnd.x - ptStart.x;

        if (xWidthSel > xWidthView)
        {
            // Selection length is greater than display width
            if (tomStart == lCode)
            {
                // Show Start requested - just start from beginning
                // of selection
                xScroll = ptStart.x;
            }
            else
            {
                // Show end requested - show as much of selection as
                // possible, ending with last character in the
                // selection.
                xScroll = ptEnd.x - xWidthView;
            }
        }
        else if (xWidthSel < 0)
        {
            xWidthSel = -xWidthSel;
            if (xWidthSel > xWidthView)
            {
                if (tomStart == lCode)
                {
                    // Show Requested Start;
                    xScroll = max(0, ptStart.x - xWidthView);       
                }
                else
                {
                    xScroll = max(0, ptEnd.x - xWidthView);
                }
            }
            else if (ptEnd.x > xWidthView || ptStart.x > xWidthView)
            {
                // Check mask if position is outside the boundaries
                if (taMask)
                    xScroll = ptStart.x - xWidthView;
                else
                    xScroll = ptEnd.x - xWidthView;
            }       
        }
        else if (ptEnd.x > xWidthView || ptStart.x > xWidthView)
        {
            // Check mask if position is outside the boundaries
            if (taMask)
                xScroll = ptStart.x - xWidthView;
            else
                xScroll = ptEnd.x - xWidthView;
        }
    }   
    else
    {
        // Multiline selection. Display as much as possible of the requested
        // end's line.

        // Calc width of line
        LONG xWidthLine = (tomStart == lCode)
            ? rpStart->_xWidth + rpStart->_xLeft
            : rpEnd->_xWidth + rpEnd->_xLeft;


        // If line width is less than or equal to view, start at
        // 0 otherwise we need to adjust starting position to
        // show as much of the requested end's selection line
        // as possible.
        if(xWidthLine > xWidthView)
        {
            if(tomStart == lCode)
            {
                // Start end to be displayed

                if(xWidthLine - ptStart.x > xWidthView)
                {
                    // Selection is bigger than view, so start at beginning
                    // and display as much as possible.
                    xScroll = ptStart.x;
                }
                else
                {
                    // Remember that this is a multiline selection so the
                    // selection on this line goes from ptStart.x to the
                    // end of line. Since the selection width is less than
                    // the width of the view, we just back up the width
                    // of view to show the entire selection.
                    xScroll = xWidthLine - xWidthView;
                }
            }
            else
            {
                // Show the end of the selection. In the multiline case,
                // this goes from the beginning of the line to End. So
                // we only have to adjust if the End is beyond the view.
                if(ptEnd.x > xWidthView)
                {
                    // End beyond the view. Show as much as possible
                    // of the selection.
                    xScroll = ptEnd.x - xWidthView;
                }
            }
        }
    }

    // Do the scroll
    pdp->ScrollView(xScroll, yScroll, FALSE, FALSE);

    return S_OK;
#else
    return 0;
#endif
}

/*
 *  CTxtRange::Select ()
 *
 *  @mfunc
 *      Copy this range's cp's and story ptr to the active selection.
 *
 *  @rdesc
 *      HRESULT = (if selection exists) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::Select ()
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Select");

    if(IsZombie())
        return CO_E_RELEASED;

    CTxtSelection *pSel = GetPed()->GetSel();
    if(pSel)
    {
        LONG cpMin, cpMost;
        GetRange(cpMin, cpMost);
        pSel->SetRange(cpMin, cpMost);
        return NOERROR;
    }
    return S_FALSE;
}

/*
 *  CTxtRange::SetChar (Char)
 *
 *  @mfunc
 *      Set char at cpFirst = <p Char>
 *
 *  @rdesc
 *      HRESULT = (WriteAccessDenied) ? E_ACCESSDENIED :
 *                (char stored) ? NOERROR : S_FALSE
 *
 *  @devnote
 *      Special cases could be much faster, e.g., just overtype the plain-
 *      text backing store unless at EOD or EOR.  Code below uses a cloned
 *      range to handle all cases easily and preserve undo capability.
 */
STDMETHODIMP CTxtRange::SetChar (
    long Char)              //@parm New value for char at cpFirst
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::SetChar");
    
    if(IsZombie())
        return CO_E_RELEASED;

    CTxtEdit *      ped = GetPed();
    CCallMgr        callmgr(ped);
    TCHAR           ch = (TCHAR)Char;           // Avoid endian problems
    CTxtRange       rg(*this);
    IUndoBuilder *  publdr;
    CGenUndoBuilder undobldr(ped, UB_AUTOCOMMIT, &publdr);
    CFreezeDisplay  fd(ped->_pdp);

    if(WriteAccessDenied())
        return E_ACCESSDENIED;

    if(!ped->_pdp->IsMultiLine() && IsEOP(Char))// EOPs are not allowed in
        return FALSE;                           //  single-line edit controls

    undobldr.StopGroupTyping();

    rg.Collapser(tomStart);                     // Collapse at cpMin
    rg.SetExtend(TRUE);                         // Setup to select
    rg.Advance(1);                              // Try to select char at IP
    ped->OrCharFlags(GetCharFlags(ch), publdr);
    if(rg.ReplaceRange(1, &ch, publdr, SELRR_REMEMBERRANGE))
    {
        Update(TRUE);                           // Update selection
        return NOERROR;
    }
    return S_FALSE;
}

/*
 *  CTxtRange::SetEnd (cp)
 *
 *  @mfunc
 *      Set this range's End cp
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR : S_FALSE
 *
 *  @comm
 *      Note that setting this range's cpMost to <p cp> also sets cpMin to
 *      <p cp> if <p cp> < cpMin.
 */
STDMETHODIMP CTxtRange::SetEnd (
    long cp)                            //@parm Desired new End cp
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::SetEnd");

    if(IsZombie())
        return CO_E_RELEASED;

    LONG cpMin = GetCpMin();

    ValidateCp(cp);
    return SetRange(min(cpMin, cp), cp);        // Active end is End
}

/*
 *  CTxtRange::SetFont (pFont)
 *
 *  @mfunc
 *      Set this range's character attributes to those given by <p pFont>.
 *      This method is a "character format painter".
 *
 *  @rdesc
 *      HRESULT = (!pFont) ? E_INVALIDARG :
 *                (if success) ? NOERROR :
 *                (protected) ? E_ACCESSDENIED : E_OUTOFMEMORY
 */
STDMETHODIMP CTxtRange::SetFont (
    ITextFont * pFont)  //@parm Font object with desired character formatting
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::SetFont");

    if(!pFont)
        return E_INVALIDARG;
    
    if(IsZombie())
        return CO_E_RELEASED;

    ITextFont *pFontApply = (ITextFont *) new CTxtFont(this);

    if(!pFontApply)
        return E_OUTOFMEMORY;

    HRESULT hr;
    if(*(LONG *)pFontApply == *(LONG *)pFont)       // If same vtable, use
        hr = CharFormatSetter(&((CTxtFont *)pFont)->_CF, //  its copy
                    ((CTxtFont *)pFont)->_dwMask);
    else                                            // Else copy
        hr = pFontApply->SetDuplicate(pFont);       //  to clone and apply

    pFontApply->Release();
    return hr;
}

/*
 *  CTxtRange::SetFormattedText (pRange)
 *
 *  @mfunc
 *      Replace this range's text with formatted text given by <p pRange>.
 *      If <p pRange> is NULL, paste from the clipboard.
 *
 *  @rdesc
 *      HRESULT = (WriteAccessDenied) ? E_ACCESSDENIED :
 *                (if success) ? NOERROR : E_OUTOFMEMORY
 *
 *  @FUTURE
 *      Do this more efficiently if pRange points at a RichEdit range. This
 *      would also help with RichEdit D&D to RichEdit targets
 */
STDMETHODIMP CTxtRange::SetFormattedText (
    ITextRange * pRange)        //@parm Formatted text to replace this
                                // range's text
{
#ifndef PEGASUS
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::SetFormattedText");

    if(IsZombie())
        return CO_E_RELEASED;

    CCallMgr    callmgr(GetPed());
    LONG        cpMin = GetCpMin();
    HRESULT     hr;
    IUnknown *  pdo = NULL;
    VARIANT     vr;

    if(!pRange)
        return NOERROR;                 // Nothing to paste

    if(WriteAccessDenied())
        return E_ACCESSDENIED;

    VariantInit(&vr);
    vr.vt = VT_UNKNOWN | VT_BYREF;
    vr.ppunkVal = &pdo;

    hr = pRange->Copy(&vr);
    if(hr == NOERROR)
    {
        hr = Paste(&vr, 0);
        pdo->Release();                 // Release the data object
        _cch = GetCp() - cpMin;         // Select the new text
    }
    return hr;
#else
    return 0;
#endif
}

/*
 *  CTxtRange::SetIndex (Unit, Index, Extend)
 *
 *  @mfunc
 *      If <p Extend> is zero, convert this range into an insertion point
 *      at the start of the <p Index>th <p Unit> in the current story. If
 *      <p Extend> is nonzero, set this range to consist of this unit. The
 *      start of the story corresponds to <p Index> = 0 for all units.
 *
 *      Positive indices are 1-based and index relative to the beginning of
 *      the story.  Negative indices are -1-based and index relative to the
 *      end of the story.  So an index of 1 refers to the first Unit in the
 *      story and an index of -1 refers to the last Unit in the story.
 *
 *  @rdesc
 *      HRESULT = (invalid index) ? E_INVALIDARG :
 *                (Unit not supported) ? E_NOTIMPL :
 *                (change) ? NOERROR : S_FALSE
 *
 *  @devnote
 *      Currently moves out <p Index> <p Unit>s from the start of the story.
 *      Might be faster to move from current position, but would need to know
 *      the current index.
 */
STDMETHODIMP CTxtRange::SetIndex (
    long    Unit,           //@parm Unit to index
    long    Index,          //@parm Index value to use
    long    Extend)         //@parm if nonzero, set range to <p Unit>
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::SetIndex");
    
    if(IsZombie())
        return CO_E_RELEASED;

    if(!Index)
        return E_INVALIDARG;

    CCallMgr    callmgr(GetPed());

    LONG      cchText = GetTextLength();
    CTxtRange rg(GetPed());                     // Create IP at cp = 0

    if(Index > 0)                               // Index going forward First
        Index--;                                //  Unit is at start of story
    else                                        // Index from end of story
        rg.Set(cchText, cchText);               //  selecting whole story

    LONG    cUnit;
    HRESULT hr = rg.Mover(Unit, Index, &cUnit, MOVE_END);
    if(FAILED(hr))
        return hr;

    if(Index != cUnit || rg.GetCp() == cchText) // No such index in story
        return E_INVALIDARG;

    rg._cch = 0;                                // Collapse at active end
                                                //  namely at cpMost
    LONG cpMin, cpMost;
    if(Extend)                                  // Select Index'th Unit
        rg.Expander(Unit, TRUE, NULL, &cpMin, &cpMost);

    if(Set(rg.GetCp(), rg._cch))                // Something changed
    {
        Update(TRUE);
        return NOERROR;
    }
    return S_FALSE;
}

/*
 *  CTxtRange::SetPara (pPara)
 *
 *  @mfunc
 *      Set this range's paragraph attributes to those given by <p pPara>
 *      This method is a "Paragraph format painter".
 *
 *  @rdesc
 *      HRESULT = (!pPara) ? E_INVALIDARG :
 *                (if success) ? NOERROR :
 *                (protected) ? E_ACCESSDENIED : E_OUTOFMEMORY
 */
STDMETHODIMP CTxtRange::SetPara (
    ITextPara * pPara)      //@parm Paragraph object with desired paragraph
{                           //      formatting
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::SetPara");

    if(!pPara)
        return E_INVALIDARG;

    if(IsZombie())
        return CO_E_RELEASED;

    ITextPara * pParaApply = (ITextPara *) new CTxtPara(this);

    if(!pParaApply)
        return E_OUTOFMEMORY;

    HRESULT hr;

    if(*(LONG *)pParaApply == *(LONG *)pPara)       // If same vtable, use
    {                                               //  its _PF
        hr = ParaFormatSetter(&((CTxtPara *)pPara)->_PF,
                    ((CTxtPara *)pPara)->_dwMask);
    }
    else                                            // Else copy
       hr = pParaApply->SetDuplicate(pPara);        //  to clone and apply

    pParaApply->Release();
    return hr;
}

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

    if(IsZombie())
        return CO_E_RELEASED;

    // Copy the ped locally once to save some indirections
    CTxtEdit *ped = GetPed();
    CCallMgr  callmgr(ped);

    if(Type != tomStart && Type != tomEnd)
        return E_INVALIDARG;

    if(!ped->fInplaceActive())
    {
        // If we aren't inplace active we can't get a DC to
        // calculate the cp.
        return OLE_E_NOT_INPLACEACTIVE;
    }

    // Convert (x, y) from screen coordinates to client coordinates
    POINT pt = {x, y};
    // Caller specifies screen coordinates?
    if ( !(Type & tomClientCoord) )
        if(!ped->TxScreenToClient(&pt))
            return E_FAIL;          // It is unexpected for this to happen

    // Get cp for (x, y)
    LONG cpSel = ped->_pdp->CpFromPoint(pt, NULL, NULL, NULL, TRUE);
    if(cpSel == -1)
        return E_FAIL;          // It is highly unexpected for this to fail

    // Extend range as requested
    LONG cchForSel = 0;
    if(Extend)
    {
        LONG cpMin, cpMost;
        GetRange(cpMin, cpMost);
        if(Type == tomStart)
            cchForSel = cpSel - cpMin;
        else
            cchForSel = cpSel - cpMost;
    }

    // Update range
    Set(cpSel, cchForSel);
    return S_OK;
}

/*
 *  CTxtRange::SetRange (cp1, cp2)
 *
 *  @mfunc
 *      Set this range's ends
 *
 *  @rdesc
 *      HRESULT = (cp1 > cp2) ? E_INVALIDARG
 *              : (if change) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::SetRange (
    long cp1,       //@parm Char position for Start end
    long cp2)       //@parm Char position for End end
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::SetRange");

    if(IsZombie())
        return CO_E_RELEASED;

    CCallMgr    callmgr(GetPed());
    LONG cpMin, cpMost;                 // Save starting cp's for
                                        //  change determination
    GetRange(cpMin, cpMost);
    ValidateCp(cp1);
    ValidateCp(cp2);

    Set(cp2, cp2 - cp1);
    GetRange(cp1, cp2);                 // See if either range end changed
    if(cp1 != cpMin || cp2 != cpMost)   //  (independent of active end)
    {
        Update(TRUE);                   // Update selection
        return NOERROR;
    }
    return S_FALSE;
}

/*
 *  CTxtRange::SetStart (cp)
 *
 *  @mfunc
 *      Set this range's Start cp
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR : S_FALSE
 *
 *  @comm
 *      Note that setting this range's cpMin to <p cp> also sets cpMost to
 *      <p cp> if <p cp> > cpMost.
 */
STDMETHODIMP CTxtRange::SetStart (
    long cp)                            //@parm Desired new Start cp
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::SetStart");
    
    if(IsZombie())
        return CO_E_RELEASED;

    LONG cpMost = GetCpMost();

    ValidateCp(cp);
    return SetRange(max(cpMost, cp), cp);       // Active end is Start
}

/*
 *  CTxtRange::SetText (bstr)
 *
 *  @mfunc
 *      Replace text in this range by that given by <p bstr>.  If <p bstr>
 *      is NULL, delete text in range.
 *
 *  @rdesc
 *      HRESULT = (WriteAccessDenied) ? E_ACCESSDENIED :
 *                (if success) ? NOERROR : S_FALSE
 */
STDMETHODIMP CTxtRange::SetText (
    BSTR bstr)          //@parm Text to replace text in this range by
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::SetText");

    if(IsZombie())
        return CO_E_RELEASED;

    CCallMgr callmgr(GetPed());

    if(WriteAccessDenied())
        return E_ACCESSDENIED;

    LONG cchNew = bstr ? SysStringLen(bstr) : 0;
    _cch = Replacer(cchNew, (TCHAR *)bstr, RR_ITMZ_UNICODEBIDI);    // Select the new text

    _TEST_INVARIANT_

    GetPed()->TxSetMaxToMaxText();

    return _cch == cchNew ? NOERROR : S_FALSE;
}

/*
 *  CTxtRange::StartOf (Unit, Extend, pDelta)
 *
 *  @mfunc
 *      Move this range end(s) to start of the first overlapping Unit in
 *      the range.
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if <p Unit> valid) ? S_FALSE : E_INVALIDARG
 */
STDMETHODIMP CTxtRange::StartOf (
    long    Unit,           //@parm Unit to use
    long    Extend,         //@parm If true, leave other end alone
    long *  pDelta)         //@parm Out parm to get count of chars that
                            //      StartOf moved
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::StartOf");

    CCallMgr callmgr(GetPed());
    LONG     cpMin;
    HRESULT  hr = Expander (Unit, Extend, pDelta, &cpMin, NULL);

    if(hr == NOERROR)
        Update(TRUE);                   // Update selection

    return hr;
}


//---------------------- CTxtRange ITextSelection stubs -----------------------------

// Dummy CTxtRange routines to simplify CTxtSelection inheritance hierarchy

STDMETHODIMP CTxtRange::GetFlags (long * pFlags)
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetFlags");
    return E_NOTIMPL;
}

STDMETHODIMP CTxtRange::SetFlags (long Flags)
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::SetFlags");
    return E_NOTIMPL;
}

STDMETHODIMP CTxtRange::GetType (long * pType)
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::GetType");
    return E_NOTIMPL;
}

STDMETHODIMP CTxtRange::MoveLeft (long Unit, long Count, long Extend, long * pDelta)
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Left");
    return E_NOTIMPL;
}

STDMETHODIMP CTxtRange::MoveRight (long Unit, long Count, long Extend, long * pDelta)
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Right");
    return E_NOTIMPL;
}

STDMETHODIMP CTxtRange::MoveUp (long Unit, long Count, long Extend, long * pDelta)
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Up");
    return E_NOTIMPL;
}

STDMETHODIMP CTxtRange::MoveDown (long Unit, long Count, long Extend, long * pDelta)
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::Down");
    return E_NOTIMPL;
}

STDMETHODIMP CTxtRange::HomeKey (long Unit, long Extend, long * pDelta)
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::HomeKey");
    return E_NOTIMPL;
}

STDMETHODIMP CTxtRange::EndKey (long Unit, long Extend, long * pDelta)
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::EndKey");
    return E_NOTIMPL;
}

STDMETHODIMP CTxtRange::TypeText (BSTR bstr)
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEEXTERN, "CTxtRange::TypeText");
    return E_NOTIMPL;
}


//--------------------- ITextRange Private Helper Methods -----------------------------

/*
 *  @doc INTERNAL
 *
 *  CTxtRange::Collapser (bStart)
 *
 *  @mfunc
 *      Internal routine to collapse this range into a degenerate point
 *      either at the the start (<p bStart> is nonzero or the end
 *      (<p bStart> = 0)
 */
void CTxtRange::Collapser (
    long bStart)            //@parm Flag specifying end to collapse at
{
    TRACEBEGIN(TRCSUBSYSRANG, TRCSCOPEEXTERN, "CTxtRange::Collapser");

    if(bStart)                          // Collapse to Start
    {
        if(_cch > 0)
            FlipRange();                // Move active end to range Start
    }
    else                                // Collapse to End
    {
        if(_cch < 0)
            FlipRange();                // Move active end to range End

        const LONG cchText = GetAdjustedTextLength();

        if(GetCp() > cchText)           // IP can't follow final CR
            Set(cchText, 0);            //  so move it before
    }
    if(_cch)
        _fMoveBack = bStart != 0;
    _cch = 0;                           // Collapse this range
    _fSelHasEOP = FALSE;                // Insertion points don't have
    _fSelHasCell = FALSE;               //  EOPs or Cells

    if(_fSel)                           // Notify if selection changed
        GetPed()->GetCallMgr()->SetSelectionChanged();

    Update_iFormat(-1);                 // Make sure format is up to date
}

/*
 *  CTxtRange::Comparer(pRange)
 *
 *  @mfunc
 *      helper function for CTxtRange::InRange() and IsEqual()
 *
 *  @rdesc
 *      0 if not same story or if this range isn't contained by <p pRange>;
 *      -1 if ranges are equal; 1 if this range wholely contained in
 *      <p pRange>.
 *
 *  @comm
 *      Note that if this range is degenerate and *pRange is nondegenerate,
 *      this range is not included in *pRange if it's located at pRange's
 *      End position.
 */
LONG CTxtRange::Comparer (
    ITextRange * pRange)        //@parm ITextRange to compare with
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CTxtRange::Comparer");

    LONG    cpMin, cpMost;
    LONG    Start, End;

    if(InStory(pRange, NULL) != NOERROR)    // If this range doesn't point at
        return 0;                           //  same story as pRange's,
                                            //  return 0
    GetRange(cpMin, cpMost);                // Get this range's cp's
    pRange->GetStart(&Start);               // Get pRange's cp's
    pRange->GetEnd(&End);
    if(cpMin == Start && cpMost == End)     // Exact match
        return -1;
    return cpMin >= Start && cpMost <= End && cpMin < End;
}

/*
 *  CTxtRange::Expander (Unit, fExtend, pDelta, pcpMin, pcpMost)
 *
 *  @mfunc
 *      Helper function that expands this range so that partial Units it
 *      contains are completely contained according to the out parameters
 *      pcpMin and pcpMost.  If pcpMin is not NULL, the range is expanded to
 *      the beginning of the Unit.  Similarly, if pcpMost is not NULL, the
 *      range is expanded to the end of the Unit. <p pDelta> is an out
 *      parameter that receives the number of chars added to the range.
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if Unit valid) ? S_FALSE : E_INVALIDARG
 *
 *  @devnote
 *      Used by ITextRange::Expand(), StartOf(), and EndOf(). Both pcpMin and
 *      pcpMost are nonNULL for Expand().  pcpMin is NULL for EndOf() and
 *      pcpMost is NULL for StartOf().
 *
 *  @future
 *      Discontiguous Units. Expander should expand only to end of Unit,
 *      rather than to start of next Unit.
 */
HRESULT CTxtRange::Expander (
    long    Unit,       //@parm Unit to expand range to
    BOOL    fExtend,    //@parm Expand this range if TRUE
    LONG *  pDelta,     //@parm Out parm that receives chars added
    LONG *  pcpMin,     //@parm Out parm that receives new cpMin
    LONG *  pcpMost)    //@parm Out parm that receives new cpMost
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CTxtRange::Expander");
    
    if(IsZombie())
        return CO_E_RELEASED;

    LONG    cch = 0;                        // Default no chars added
    LONG    cchRange;
    LONG    cchAdjustedText = GetAdjustedTextLength();
    LONG    cchText = GetTextLength();
    LONG    cp;
    LONG    cpMin, cpMost;
    BOOL    fUnitFound = TRUE;              // Most Units can be found
    LONG    cchCollapse;
    CDisplay *pdp;                          //  but tomObject maybe not

    GetRange(cpMin, cpMost);                // Save starting cp's
    if(pcpMin)                              // Default no change
    {
        *pcpMin = cpMin;
        AssertSz(!pcpMost || fExtend,
            "CTxtRange::Expander should extend if both pcpMin and pcpMost != 0");
    }
    if(pcpMost)
        *pcpMost = cpMost;
    if(pDelta)
        *pDelta = 0;

    if(Unit < 0)
    {
        // Valid attribute Units are high bit plus any combo of CFE_xxx.
        // CFE_REVISED is most significant value currently defined.
        if(Unit & ~(2*CFM_REVISED - 1 + 0x80000000))
            return E_NOTIMPL;
        FindAttributes(pcpMin, pcpMost, Unit);
    }
    else
    {
        switch(Unit)                        // Calculate new cp's
        {
        case tomObject:
            fUnitFound = FindObject(pcpMin, pcpMost);
            break;

        case tomCharacter:
            if (pcpMost && cpMin == cpMost &&// EndOf/Expand insertion point
                cpMost < cchText &&         //  with at least 1 more char
                (!cpMost || pcpMin))        //  at beginning of story or
            {                               //  Expand(), then
                (*pcpMost)++;               //  expand by one char
            }
            break;

        case tomCharFormat:
            _rpCF.FindRun (pcpMin, pcpMost, cpMin, _cch, cchText);
            break;

        case tomParaFormat:
            _rpPF.FindRun (pcpMin, pcpMost, cpMin, _cch, cchText);
            break;

        case tomWord:
            FindWord (pcpMin, pcpMost, FW_INCLUDE_TRAILING_WHITESPACE);
            break;

        case tomSentence:
            FindSentence (pcpMin, pcpMost);
            break;

        case tomCell:
            FindCell (pcpMin, pcpMost);
            break;

        case tomRow:
            _rpPF.AdjustForward();
            if(InTable())                   // In our simple table model,
                goto para;                  //  each row is a single para
            break;                          

        case tomLine:
            pdp = GetPed()->_pdp;
            if(pdp)                         // If this story has a display
            {                               //  use line array
                CLinePtr rp(pdp);
                cp = GetCp();
                pdp->WaitForRecalc(cp, -1);
                rp.RpSetCp(cp, FALSE);
                rp.FindRun (pcpMin, pcpMost, cpMin, _cch, cchText);
                break;
            }                               // Else fall thru to tomPara

        case tomParagraph:
    para:   FindParagraph(pcpMin, pcpMost);
            break;

        case tomWindow:
            fUnitFound = FindVisibleRange(pcpMin, pcpMost);
            break;

        case tomStory:
            if(pcpMin)
                *pcpMin = 0;
            if(pcpMost)
                *pcpMost = cchText;
            break;

        default:
            return E_NOTIMPL;
        }
    }
    if(!fUnitFound)
        return S_FALSE;

    cchCollapse = !fExtend && _cch;         // Collapse counts as a char
                                            // Note: Expand() has fExtend = 0
    if(pcpMin)
    {
        cch = cpMin - *pcpMin;              // Default positive cch for Expand
        cpMin = *pcpMin;
    }

    if(pcpMost)                             // EndOf() and Expand()
    {
        if(!fExtend)                        // Will be IP if not already
        {
            if(cpMost > cchAdjustedText)    // If we collapse (EndOf only),
                cchCollapse = -cchCollapse; //  it'll be before the final CR
            else
                *pcpMost = min(*pcpMost, cchAdjustedText);
        }
        cch += *pcpMost - cpMost;
        cp = cpMost = *pcpMost;
    }
    else                                    // StartOf()
    {
        cch = -cch;                         // Invert count
        cp = cpMin;                         // Active end at cpMin
        cchCollapse = -cchCollapse;         // Backward collapses count as -1
    }

    cch += cchCollapse;                     // Collapse counts as a char
    if(cch)                                 // One or both ends changed
    {
        cchRange = cpMost - cpMin;          // cch for EndOf() and Expand()
        if(!pcpMost)                        // Make negative for StartOf()
            cchRange = -cchRange;
        if(!fExtend)                        // We're not expanding (EndOf()
            cchRange = 0;                   //  or StartOf() call)
        if(Set(cp, cchRange))               // Set active end and signed cch
        {                                   // Something changed
            if(pDelta)                      // Report cch if caller cares
                *pDelta = cch;
            return NOERROR;
        }
    }
    
    return S_FALSE;                         // Report Unit found but no change
}

/*
 *  CTxtRange::Finder (bstr, Count, dwFlags, pDelta, Mode)
 *
 *  @mfunc
 *      Helper find function that moves active end up to <p cch> characters
 *      subject to compare flags <p Flags> and the <p Mode>, which has the
 *      following possible values:
 *
 *      1:  set this range's cpMost = cpMost of matched string
 *      0:  set this range's cp's equal to those of matched string
 *      -1: set this range's cpMin = cpMin of matched string
 *
 *      Return *<p pDelta> = # characters past.
 *
 *  @rdesc
 *      HRESULT = (if <p bstr> found) ? NOERROR : S_FALSE
 *
 *  @devnote
 *      Used by ITextRange::FindText(), FindTextStart() and FindTextEnd()
 */
HRESULT CTxtRange::Finder (
    BSTR    bstr,       //@parm String to find
    long    Count,      //@parm Max count of chars to search
    long    Flags,      //@parm Flags governing compares
    LONG *  pDelta,     //@parm Out parm to receive count of chars moved
    MOVES   Mode)       //@parm Governs setting of range wrt matched string
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CTxtRange::Finder");

    if(!bstr)
        return S_FALSE;

    if(IsZombie())
        return CO_E_RELEASED;

    CCallMgr    callmgr(GetPed());

    LONG        cpMin, cpMost;
    LONG        cch = GetRange(cpMin, cpMost);  // Get this range's cp's
    LONG        cchBstr = SysStringLen(bstr);
    LONG        cchSave = _cch;
    LONG        cp, cpMatch, cpSave;
    LONG        cpStart = cpMost;               // Default Start cp to range
    CRchTxtPtr  rtp(*this);                     //  End

    if(Mode == MOVE_IP)                         // FindText(): Count = 0 is
    {                                           //  treated specially: if IP,
        if(!Count)                              //  compare string at IP; else
            Count = cch ? cch : cchBstr;        //  confine search to range
        if(Count > 0)                           // Forward searches start from
            cpStart = cpMin;                    //  beginning of range
    }
    else                                        // FindTextStart() or
    {                                           //   FindTextEnd()
        if(!Count)                              // Compare string at IP; else
            Count = cch ? -Mode*cch : cchBstr;  //  confine search to range
        if(Mode < 0)                            // Find from Start
            cpStart = cpMin;
    }

    cpSave = cpStart;                           // Save starting cp
    cp = cpStart + Count;                       // cp = limiting cp. Can be on
    cp = max(cp, 0);                            //  either side of cpStart
    Flags &= ~FR_DOWN;                          // Default search backward
    if(Count >= 0)                              // It's forward, so set
        Flags |= FR_DOWN;                       //  downward search bit

find:
    rtp.SetCp(cpStart);                         // Move to start of search
    cpMatch = rtp.FindText(cp, Flags, bstr, cchBstr);
    if (Mode == MOVE_IP && cpMatch == cpMin &&  // Ordinary Find matched
        rtp.GetCp() == cpMost)                  //  current range
    {
        Assert(cpStart == cpSave);              // (Can't loop twice)
        cpStart += Count > 0 ? 1 : -1;          // Move over one char
        goto find;                              //  and try again
    }

    if(cpMatch < 0)                             // Match failed
    {
        if(pDelta)                              // Return match string length
            *pDelta = 0;                        //  = 0
        return S_FALSE;                         // Signal no match
    }


    // Match succeeded: set new cp and cch for range, update selection (if
    // this range is a selection), send notifications, and return NOERROR

    cp = rtp.GetCp();                           // cp = cpMost of match string
    if(pDelta)                                  // Return match string length
        *pDelta = cchBstr;                      //  if caller wants to know

    cch = cp - cpMatch;                         // Default to select matched
                                                //  string (for MOVE_IP)
    if(Mode != MOVE_IP)                         // MOVE_START or MOVE_END
    {
        if(Mode == MOVE_START)                  // MOVE_START moves to start
            cp = cpMatch;                       //  of matched string
        cch = cp - cpSave;                      // Distance end moved
        if(!cchSave && (Mode ^ cch) < 0)        // If crossed ends of initial
            cch = 0;                            //  IP, use an IP
        else if(cchSave)                        // Initially nondegenerate
        {                                       //  range
            if((cchSave ^ Mode) < 0)            // If wrong end is active,
                cchSave = -cchSave;             //  fake a FlipRange to get
            cch += cchSave;                     //  new length
            if((cch ^ cchSave) < 0)             // If ends would cross,
                cch = 0;                        //  convert to insertion point
        }
    }
    if ((cp != GetCp() || cch != _cch)          // Active end and/or length of
        && Set(cp, cch))                        //  range changed
    {                                           // Use the new values
        Update(TRUE);                           // Update selection
    }
    return NOERROR;
}

/*
 *  CTxtRange::Matcher (Cset, Count, pDelta, fExtend, Match)
 *
 *  @mfunc
 *      Helper function to move active end up to <p cch> characters past
 *      all contiguous characters that are (<p Match> ? in : not in) the cset
 *      *<p pvar>.  If <p fExtend>, extend the range to include the characters
 *      past by. Return *<p pDelta> = # characters past by.
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if <p Cset> valid) ? S_FALSE : E_INVALIDARG
 */
HRESULT CTxtRange::Matcher (
    VARIANT *   Cset,       //@parm Character match set
    long        Count,      //@parm Max cch to match
    long *      pDelta,     //@parm Out parm for cch moved
    MOVES       Mode,       //@parm MOVE_START (-1), MOVE_IP (0), MOVE_END (1)
    MATCHES     Match)      //@parm MATCH_WHILE spans Cset; else break on Cset
{
#ifndef PEGASUS
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CTxtRange::Matcher");

    // This (and other) code assumes the following conditions:
    Assert(MOVE_START == -1 && MOVE_IP == 0 && MOVE_END == 1);
    Assert(MATCH_UNTIL == 0 && MATCH_WHILE == 1);
    Assert(sizeof(WORD) == 2);                      // 16-bit WORDs

    if(!Cset)
        return E_INVALIDARG;

    if(IsZombie())
        return CO_E_RELEASED;

    CCallMgr    callmgr(GetPed());
    LONG    cch;                                    // For cch moved
    TCHAR   ch;                                     // Current char
    LONG    count = Count;                          // Count down variable
    LONG    cpSave;                                 // To save initial cp
    WORD    ctype;                                  // CT_TYPEx info for ch
    long    Delta;                                  // Value for *pDelta
    BOOL    fInCset;                                // TRUE iff ch in Cset
    UINT    i, j;                                   // Handy indices
    LONG    iDir = (Count > 0) ? 1 : -1;            // Count increment
    long    lVal = Cset->lVal;
    TCHAR * pch;                                    // Used to walk BSTR Cset
    CTxtPtr tp(_rpTX);                              // tp to walk text with
    LONG    vt = Cset->vt;

    if(pDelta)                                      // Default neither motion
        *pDelta = 0;                                //  nor match

    if (Mode == MOVE_IP && (_cch ^ Count) < 0 ||    // Wrong active end:
        Mode != MOVE_IP && (_cch ^ Mode)  < 0)
    {
        tp.AdvanceCp(-_cch);                        //  go to other end
    }
    cpSave = tp.GetCp();                            // Save cp for checks

    if(Count > 0)                                   // If matching forward,
    {                                               //  get current char
        ch = tp.GetChar();
        count--;                                    // One less char to match
    }
    else                                            // If matching backward,
        ch = tp.NextCharCount(count);               //  start at previous char
                                                    
    if(!ch)                                         // At one end or other, so
        return S_FALSE;                             //  signal no match


    // Process built-in and explicit character sets
    if(vt & VT_BYREF)                               // VB passes VT_BYREF
    {                                               //  unless args are
        lVal = *Cset->plVal;                        //  enclosed in ()'s
        vt &= ~ VT_BYREF;
    }

    if(vt == VT_I2)                                 // Should be VT_I4, but
        lVal &= 0xffff;                             //  facilitate common cases

    // Built-in char set: either Unicode range or CT_CTYPEx
    if(vt == VT_I4 || vt == VT_I2)
    {
        i = lVal & 0xffff;                          // First code or CT mask
        j = lVal >> 16;                             // Size of range
        if(lVal < 0)                                // Unicode range Cset
        {                                           //  (sign bit is set)
            j &= 0x7fff;                            // Kill sign bit
            while (((BOOL)Match ^ (ch - i > j)) &&      // ch in range or not
                   (ch = tp.NextCharCount(count)))  // Another char available
                   ;                                // Note: count is passed
        }                                           //  by reference
        else                                        // CT_CTYPEx Cset
        {                                           // CT_CTYPEx is given by
            if(!j)                                  //  upper WORD of lVal
                j = CT_CTYPE1;                      // 0 defaults to CT_CTYPE1
            do
            {
                ctype = 0;                          // For each char, get
                                                    //  string type info
                W32->GetStringTypeEx(0, j, &ch, 1, &ctype);

                // Loop (up to |Count| - 1 times) as long as the characters
                // encountered are in the Cset (Match = MATCH_WHILE (=1)),
                // or as long as they are not  (Match = MATCH_UNTIL (=0)).

                fInCset = (j == CT_CTYPE2)          // CT_CTYPE2 values are
                        ? (ctype == i)              //  mutually exclusive;
                        : (ctype & i) != 0;         //  others can be combos

            } while ((Match ^ fInCset) == 0 &&
                     (ch = tp.NextCharCount(count)) != 0);
        }                                           // End of built-in Csets
    }                                               // End of Cset VT_I4

    // Explicit char set given by chars in Cset->bstrVal
    else if (Cset->vt == VT_BSTR)
    {
        //REVIEW (keithcu) What is going on here?
        if((DWORD_PTR)Cset->bstrVal < 0xfffff)      // Don't get fooled by
            return E_INVALIDARG;                    //  invalid vt values
        j = SysStringLen(Cset->bstrVal);
        do
        {                                           // Set i = 0 if ch isn't
            pch = Cset->bstrVal;                //  in set; this stops
            for(i = j;                              //  movement
                i && (ch != *pch++);                
                i--) ;
        
        // If we are doing a MATCH_WHILE routine then we only
        // continue while i > 0 becuase this indicates that we
        // found the char at the current cp in the CSet.  If
        // we were doing a MATCH_UNTIL then we should quit when
        // i != 0 becuase the current char was in the CSet.
        } while((Match == (i ? MATCH_WHILE : MATCH_UNTIL)) &&
            (ch = tp.NextCharCount(count)));        // Break if no more chars
    }                                               //  or ch not in set
    else
        return E_INVALIDARG;

    /* If MoveWhile, leave tp immediately after last matched char going
     * forward and at that char going backward (helps to think of tp
     * pointing in between chars).  If MoveUntil, leave tp at the char
     * going forward and just after that char going backward.
     *
     * E.g.: the code
     *
     *      r.MoveUntil   (C1_DIGIT, tomForward, NULL)
     *      r.MoveEndWhile(C1_DIGIT, tomForward, NULL)
     *
     * breaks at the first digit and selects the number going forward.
     * Similarly
     *
     *      r.MoveUntil     (C1_DIGIT, tomBackward, NULL)
     *      r.MoveStartWhile(C1_DIGIT, tomBackward, NULL)
     *
     * selects the number going backward.
     */
    count = (Match == MATCH_WHILE && !ch)           // If MoveWhile, move past
          ? iDir : 0;                               //  last matched char
    if(Count < 0)
        count++;
    tp.AdvanceCp(count);

    Delta = cch = 0;                                // Suppress motion unless
    if(Match == MATCH_WHILE || ch)                  //  match occurred
    {
        Delta = cch = tp.GetCp() - cpSave;          // Calculate distance moved
        if(Match == MATCH_UNTIL)                    // For MoveUntil methods,
            Delta += iDir;                          //  match counts as a char
    }

    if(pDelta)                                      // Report motion to caller
        *pDelta = Delta;                            //  if it wants to know

    // Handle cases for which range is changed
    if(cch || (Delta && _cch && Mode == MOVE_IP))
    {
        if (Mode == MOVE_IP ||                      // If move IP or asked to
            !_cch && (Mode ^ Count) < 0)            //  cross ends of initial
        {                                           //  IP, use an IP
            cch = 0;
        }
        else if(_cch)                               // Initially nondegenerate
        {                                           //  range
            if((_cch ^ Mode) < 0)                   // If wrong end is active,
                _cch = -_cch;                       //  fake a FlipRange (will
            cch += _cch;                            //  set cp shortly)
            if((cch ^ _cch) < 0)                    // If ends crossed, convert
                cch = 0;                            //  to insertion point
        }
        if(Set(tp.GetCp(), cch))                    // Set new range cp and cch
        {
            Update(TRUE);                           // Update selection
            return NOERROR;                         // Signal match occurred
        }
        return S_FALSE;
    }

    // No change in range. Return NOERROR iff match occurred for MOVE_UNTIL
    return Delta ? NOERROR : S_FALSE;
#else
    return 0;
#endif
}

/*
 *  CTxtRange::Mover (Unit, Count, pDelta, Mode)
 *
 *  @mfunc
 *      Helper function to move end(s) <p Count> <p Unit>s, which end(s)
 *      depending on Mode = MOVE_IP, MOVE_START, and MOVE_END.  Collapsing
 *      the range by using MOVE_IP counts as a Unit.
 *
 *      Extends range from End if <p Mode> = MOVE_END and from Start if
 *      <p Mode> = MOVE_START; else (MOVE_IP) it 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 ITextRange::Delete(), Move(), MoveStart(), MoveEnd(),
 *      and SetIndex()
 *
 *  @rdesc
 *      HRESULT = (if change) ? NOERROR :
 *                (if <p Unit> valid) ? S_FALSE : E_INVALIDARG
 */
HRESULT CTxtRange::Mover (
    long    Unit,       //@parm Unit to use for moving active end
    long    Count,      //@parm Count of units to move active end
    long *  pDelta,     //@parm Out parm for count of units moved
    MOVES   Mode)       //@parm MOVE_START (-1), MOVE_IP (0), MOVE_END (1)
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CTxtRange::Mover");

    if(pDelta)
        *pDelta = 0;                            // Default no units moved

    if(IsZombie())
        return CO_E_RELEASED;

    LONG      cch;
    LONG      cchAdj = GetAdjustedTextLength();
    LONG      cchMax = 0;                       // Default full story limits
    LONG      cp;
    LONG      cpMost = GetCpMost();
    LONG      cUnitCollapse = 0;
    HRESULT   hr = NOERROR;
    CTxtRange rg(*this);                        // Use a copy to look around

    if(pDelta)
        *pDelta = 0;                            // Default no units moved

    if(_cch && Count)                           // Nondegenerate range
    {
        if(Mode == MOVE_IP)                     // Insertion point: will
        {                                       //  collapse range if Unit is
            if((Count ^ rg._cch) < 0)           //  defined. Go to correct end
                rg.FlipRange();
            if(Count > 0)
            {
                if(cpMost > cchAdj)
                {
                    cUnitCollapse = -1;         // Collapse before final CR
                    Count = 0;                  // No more motion
                }
                else
                {   //               Extend pDelta pcpMin pcpMost
                    hr = rg.Expander(Unit, FALSE, NULL, NULL, &cp);
                    cUnitCollapse = 1;          // Collapse counts as a Unit
                    Count--;                    // One less Unit to count
                }
            }
            else
            {
                hr = rg.Expander(Unit, FALSE, NULL, &cp, NULL);
                cUnitCollapse = -1;
                Count++;
            }
            if(FAILED(hr))
                return hr;
        }
        else if((Mode ^ rg._cch) < 0)           // MOVE_START or MOVE_END
            rg.FlipRange();                     // Go to Start or End
    }

    if(Count > 0 && Mode != MOVE_END)           // Moving IP or Start forward
    {
        cchMax = cchAdj - rg.GetCp();           // Can't pass final CR
        if(cchMax <= 0)                         // Already at or past it
        {                                       // Only count comes from
            Count = cUnitCollapse;              //  possible collapse
            cp = cchAdj;                        // Put active end at cchAdj
            cch = (Mode == MOVE_START && cpMost > cchAdj)
                ? cp - cpMost : 0;
            goto set;
        }
    }

    cch = rg.UnitCounter(Unit, Count, cchMax);  // Count off Count Units

    if(cch == tomForward)                       // Unit not implemented
        return E_NOTIMPL;
    
    if(cch == tomBackward)                      // Unit not available, e.g.,
        return S_FALSE;                         //  tomObject and no objects

    Count += cUnitCollapse;                     // Add a Unit if collapse
    if(!Count)                                  // Nothing changed, so quit
        return S_FALSE;

    if (Mode == MOVE_IP ||                      // MOVE_IP or
        !_cch && (Mode ^ Count) < 0)            //  initial IP end cross
    {
        cch = 0;                                // New range is degenerate
    }
    else if(_cch)                               // MOVE_START or MOVE_END
    {                                           //  with nondegenerate range
        if((_cch ^ Mode) < 0)                   // Make _cch correspond to end
            _cch = -_cch;                       //  that moved
        cch += _cch;                            // Possible new range length
        if((cch ^ _cch) < 0)                    // Nondegenerate end cross
            cch = 0;                            // Use IP
    }
    cp = rg.GetCp();

set:
    if(Set(cp, cch))                            // Attempt to set new range
    {                                           // Something changed
        if(pDelta)                              // Report count of units
            *pDelta = Count;                    //  advanced
        Update(TRUE);                           // Update selection
        return NOERROR;
    }
    return S_FALSE;
}

/*
 *
 *  CTxtRange::Replacer (cchNew, *pch)
 *  
 *  @mfunc
 *      Replace this range's using CHARFORMAT _iFormat and updating other
 *      text runs as needed.
 *
 *      Same as CTxtRange::CleanseAndReplaceRange(cchNew, *pch, publdr),
 *      except creates its own undo builder.
 *  
 *  @rdesc
 *      cch of text actually pasted
 *  
 *  @devnote
 *      moves this text pointer to end of replaced text and
 *      may move text block and formatting arrays
 */
LONG CTxtRange::Replacer (
    LONG            cchNew,     //@parm Length of replacement text
    TCHAR const *   pch,        //@parm Replacement text
    DWORD           dwFlags)    //@parm ReplaceRange flags
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CTxtRange::Replacer");

    IUndoBuilder *  publdr;
    CGenUndoBuilder undobldr(GetPed(), UB_AUTOCOMMIT, &publdr);

    undobldr.StopGroupTyping();

    // Note: we don't check the limit on text here. Right now, this
    // is only called by Delete and SetText so this is OK. However,
    // we might want to reinvestigate this latter if this is called
    // by anything else.
    return CleanseAndReplaceRange(cchNew, pch, FALSE, publdr, NULL, NULL, dwFlags);
}

/*
 *  CTxtRange::CharFormatSetter (pCF)
 *
 *  @mfunc
 *      Helper function that's the same as CTxtRange::SetCharFormat(), but
 *      adds undo building, and notification.
 *
 *  @rdesc
 *      HRESULT = (if success) ? NOERROR : S_FALSE
 *                (protected) ? E_ACCESSDENIED : E_OUTOFMEMORY
 */
HRESULT CTxtRange::CharFormatSetter (
    const CCharFormat *pCF, //@parm CCharFormat to fill with results
    DWORD         dwMask)   //@parm CHARFORMAT2 mask
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CTxtRange::CharFormatSetter");

    if(IsZombie())
        return CO_E_RELEASED;

    CTxtEdit *ped = GetPed();
    CCallMgr        callmgr(ped);
    IUndoBuilder *  publdr;
    CGenUndoBuilder undobldr(ped, UB_AUTOCOMMIT, &publdr);

    if(WriteAccessDenied())
        return E_ACCESSDENIED;

    undobldr.StopGroupTyping();
    return SetCharFormat(pCF, FALSE, publdr, dwMask, 0);
}

/*
 *  CTxtRange::ParaFormatSetter (pPF, dwMask)
 *
 *  @mfunc
 *      Helper function that's the same as CTxtRange::SetParaFormat(), but
 *      adds protection checking, undo building, and notification.
 *
 *  @rdesc
 *      HRESULT = (if success) ? NOERROR : S_FALSE
 *                (protected) ? E_ACCESSDENIED : E_OUTOFMEMORY
 */
HRESULT CTxtRange::ParaFormatSetter (
    const CParaFormat *pPF, //@parm CParaFormat to fill with results
    DWORD           dwMask) //@parm Mask to use
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CTxtRange::ParaFormatSetter");

    if(IsZombie())
        return CO_E_RELEASED;

    CTxtEdit *ped = GetPed();
    CCallMgr        callmgr(ped);
    IUndoBuilder *  publdr;
    CGenUndoBuilder undobldr(ped, UB_AUTOCOMMIT, &publdr);

    if(WriteAccessDenied())
        return E_ACCESSDENIED;

    undobldr.StopGroupTyping();
    // In RichEdit 3.0, we don't support setting tables
    if(GetPF()->InTable())
        dwMask &= ~PFM_TABSTOPS;
    return SetParaFormat(pPF, publdr, dwMask & ~PFM_TABLE);
}

/*
 *  CTxtRange::WriteAccessDenied()
 *
 *  @mfunc
 *      Returns TRUE iff at least part of the range is protected and the
 *      owner chooses to enforce it
 *
 *  @rdesc
 *      TRUE iff write access to range is denied
 */
BOOL CTxtRange::WriteAccessDenied ()
{
    TRACEBEGIN(TRCSUBSYSTOM, TRCSCOPEINTERN, "CTxtRange::WriteAccessDenied");

    int       iProt;
    CTxtEdit *ped = GetPed();

    if (ped && ped->TxGetReadOnly() ||
        ((iProt = IsProtected(-1)) == PROTECTED_YES ||
        (iProt == PROTECTED_ASK && ped->IsProtectionCheckingEnabled() &&
         ped->QueryUseProtection(this, 0, 0, 0))))
    // N.B.  the preceding if statement assumes that IsProtected returns a tri-value
    {
        return TRUE;
    }

    return FALSE;
}

/*
 *  CTxtRange::IsTrue (f, pB)
 *
 *  @mfunc
 *      Return *<p pB> = tomTrue iff <p f> is nonzero and pB isn't NULL
 *
 *  @rdesc
 *      HRESULT = (f) ? NOERROR : S_FALSE
 */
HRESULT CTxtRange::IsTrue(BOOL f, long *pB)
{
    if(pB)
        *pB = tomFalse;
    
    if(IsZombie())
        return CO_E_RELEASED;

    if(f)
    {
        if(pB)
            *pB = tomTrue;
        return NOERROR;
    }

    return S_FALSE;
}

/*
 *  CTxtRange::GetLong (lValue, pLong)
 *
 *  @mfunc
 *      Return *pLong = lValue provided pLong isn't NULL and this range
 *      isn't a zombie
 *
 *  @rdesc
 *      HRESULT = (zombie) ? CO_E_RELEASED :
 *                (pLong) ? NOERROR : E_INVALIDARG
 */
HRESULT CTxtRange::GetLong (
    LONG lValue,        //@parm Long value to return
    long *pLong)        //@parm Out parm to receive long value
{
    if(IsZombie())
        return CO_E_RELEASED;   
    
    _TEST_INVARIANT_

    if(!pLong)
        return E_INVALIDARG;

    *pLong = lValue;



    return NOERROR;
}

/*
 *  IsSameVtables (punk1, punk2)
 *
 *  @mfunc
 *      Returns true if punk1 has same vtable as punk2
 *
 *  @rdesc
 *      TRUE iff punk1 has same vtable as punk2
 */
BOOL IsSameVtables(IUnknown *punk1, IUnknown *punk2)
{
    return punk1 && punk2 && *(long *)punk1 == *(long *)punk2;
}

/*
 *  FPPTS_TO_TWIPS (x)
 *
 *  @mfunc
 *      Returns 20*x, i.e., the number of twips corresponding to
 *      x given in floating-point points.  The value is rounded.
 *
 *  @rdesc
 *      x converted to twips
 */
long FPPTS_TO_TWIPS(
    float x)
{
    return 20*x + ((x >= 0) ? 0.5 : -0.5);
}
