/*
 *  @doc INTERNAL
 *
 *  @module EDIT.C - main part of CTxtEdit |
 *
 *      See also textserv.cpp (ITextServices and SendMessage interfaces)
 *      and tomDoc.cpp (ITextDocument interface)
 *
 *  Authors: <nl>
 *      Original RichEdit code: David R. Fulmer <nl>
 *      Christian Fortini, Murray Sargent, Alex Gounares, Rick Sailor,
 *      Jon Matousek
 *
 *  History: <nl>
 *      12/28/95 jonmat-Added support of Magellan mouse and smooth scrolling.
 *
 *  @devnote
 *      Be sure to set tabs at every four (4) columns.  In fact, don't even
 *      think of doing anything else!
 *
 *  Copyright (c) 1995-1998 Microsoft Corporation. All rights reserved.
 */

#include "_common.h"
#include "_edit.h"
#include "_dispprt.h"
#include "_dispml.h"
#include "_dispsl.h"
#include "_select.h"
#include "_text.h"
#include "_runptr.h"
#include "_font.h"
#include "_measure.h"
#include "_render.h"
#include "_m_undo.h"
#include "_antievt.h"
#include "_rtext.h"

#include "_uspi.h"
#include "_urlsup.h"

#ifdef LINESERVICES
#include "_ols.h"
#endif

#include "_txtbrk.h"
#include "_clasfyc.h"

#define CONTROL(_ch) (_ch - 'A' + 1)

ASSERTDATA

// This is not public because we don't really want folks using it.
// ITextServices is a private interface.
EXTERN_C const IID IID_ITextServices = { // 8d33f740-cf58-11ce-a89d-00aa006cadc5
    0x8d33f740,
    0xcf58,
    0x11ce,
    {0xa8, 0x9d, 0x00, 0xaa, 0x00, 0x6c, 0xad, 0xc5}
  };

// {13E670F4-1A5A-11cf-ABEB-00AA00B65EA1}
EXTERN_C const GUID IID_ITextHost =
{ 0x13e670f4, 0x1a5a, 0x11cf, { 0xab, 0xeb, 0x0, 0xaa, 0x0, 0xb6, 0x5e, 0xa1 } };

// {13E670F5-1A5A-11cf-ABEB-00AA00B65EA1}
EXTERN_C const GUID IID_ITextHost2 =
{ 0x13e670f5, 0x1a5a, 0x11cf, { 0xab, 0xeb, 0x0, 0xaa, 0x0, 0xb6, 0x5e, 0xa1 } };

// this is used internally do tell if a data object is one of our own.
EXTERN_C const GUID IID_IRichEditDO =
{ /* 21bc3b20-e5d5-11cf-93e1-00aa00b65ea1 */
    0x21bc3b20,
    0xe5d5,
    0x11cf,
    {0x93, 0xe1, 0x00, 0xaa, 0x00, 0xb6, 0x5e, 0xa1}
};

// Static data members
DWORD CTxtEdit::_dwTickDblClick;    // time of last double-click
POINT CTxtEdit::_ptDblClick;        // position of last double-click

//HCURSOR CTxtEdit::_hcurCross = 0; // We don't implement outline drag move
HCURSOR CTxtEdit::_hcurArrow = 0;
HCURSOR CTxtEdit::_hcurHand = 0;
HCURSOR CTxtEdit::_hcurIBeam = 0;
HCURSOR CTxtEdit::_hcurItalic = 0;
HCURSOR CTxtEdit::_hcurSelBar = 0;

const TCHAR szCRLF[]= TEXT("\r\n");
const TCHAR szCR[]  = TEXT("\r");

WORD    g_wFlags = 0;                   // Keyboard controlled flags

/*
 *  GetKbdFlags(vkey, dwFlags)
 *
 *  @func
 *      return bit mask (RSHIFT, LSHIFT, RCTRL, LCTRL, RALT, or LALT)
 *      corresponding to vkey = VK_SHIFT, VK_CONTROL, or VK_MENU and
 *      dwFlags
 *
 *  @rdesc
 *      Bit mask corresponding to vkey and dwFlags
 */
DWORD GetKbdFlags(
    WORD    vkey,       //@parm Virtual key code
    DWORD   dwFlags)    //@parm lparam of WM_KEYDOWN msg
{
    if(vkey == VK_SHIFT)
        return (LOBYTE(HIWORD(dwFlags)) == 0x36) ? RSHIFT : LSHIFT;

    if(vkey == VK_CONTROL)
        return (HIWORD(dwFlags) & KF_EXTENDED) ? RCTRL : LCTRL;

    Assert(vkey == VK_MENU);

    return (HIWORD(dwFlags) & KF_EXTENDED) ? RALT : LALT;
}

///////////////// CTxtEdit Creation, Initialization, Destruction ///////////////////////////////////////

/*
 *  CTxtEdit::CTxtEdit()
 *
 *  @mfunc
 *      constructor
 */
CTxtEdit::CTxtEdit(
    ITextHost2 *phost,
    IUnknown * punk)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CTxtEdit");

    _unk.Init();
    _punk = (punk) ? punk : &_unk;
    _ldte.Init(this);
    _phost    = phost;
    _cpAccelerator = -1;                    // Default to no accelerator

    // Initialize _iCF and _iPF to something bogus
    Set_iCF(-1);
    Set_iPF(-1);

    // Initialize local maximum text size to window default
    _cchTextMost = cInitTextMax;

    // This actually counts the number of active ped
    W32->AddRef();
}

/*
 *  CTxtEdit::~CTxtEdit()
 *
 *  @mfunc
 *      Destructor
 */
CTxtEdit::~CTxtEdit ()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::~CTxtEdit");

    Assert(!_fMButtonCapture);              // Need to properly transition
                                            //  Magellan mouse if asserts!
    _fSelfDestruct = TRUE;                  // Tell the Call Mgr not to
                                            //  call this any more
    // Flush clipboard first
    _ldte.FlushClipboard();

    if(_pDocInfo)                           // Do this before closing
    {                                       //  down internal structures
        CloseFile(TRUE);                    // Close any open file
        delete _pDocInfo;                   // Delete document info
        _pDocInfo = NULL;
    }

    if(_pdetecturl)
        delete _pdetecturl;

    if (_pbrk)
        delete _pbrk;

    if(_pobjmgr)
        delete _pobjmgr;

    // Release our reference to selection object
    if(_psel)
        _psel->Release();

    // Delete undo and redo managers
    if(_pundo)
        _pundo->Destroy();

    // Release message filter.
    // Note that the attached message filter must have released this document
    // Otherwise we will never get here.
    if (_pMsgFilter)
        _pMsgFilter->Release();

    if(_predo)
        _predo->Destroy();

    ReleaseFormats(Get_iCF(), Get_iPF());   // Release default formats

    delete _pdp;                            // Delete displays
    delete _pdpPrinter;
    _pdp = NULL;                            // Break any further attempts to
                                            //  use display

    if (_fHost2)
    {
        // We are in a windows host - need to deal with the shutdown
        // problem where the window can be destroyed before text
        // services is.
        if (!_fReleaseHost)
        {
            ((ITextHost2*)_phost)->TxFreeTextServicesNotification();
        }
        else
        {
            // Had to keep host alive so tell it we are done with it.
            _phost->Release();
        }
    }

    W32->Release();
}

/*
 *  CTxtEdit::Init (prcClient)
 *
 *  @mfunc
 *      Initializes this CTxtEdit. Called by CreateTextServices()
 *
 *  @rdesc
 *      Return TRUE if successful
 */

BOOL CTxtEdit::Init (
    const RECT *prcClient)      //@parm Client RECT
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::Init");

    CCharFormat         CF;
    DWORD               dwBits = 0;
    DWORD               dwMask;
    LONG                iCF, iPF;
    CParaFormat         PF;
    CCallMgr            callmgr(this);

    static BOOL fOnce = FALSE;
    if (!fOnce)
    {
        CLock lock;
        fOnce = TRUE;
        _fnpPropChg[ 0] = &CTxtEdit::OnRichEditChange;          // TXTBIT_RICHTEXT
        _fnpPropChg[ 1] = &CTxtEdit::OnTxMultiLineChange;       // TXTBIT_MULTILINE
        _fnpPropChg[ 2] = &CTxtEdit::OnTxReadOnlyChange;        // TXTBIT_READONLY
        _fnpPropChg[ 3] = &CTxtEdit::OnShowAccelerator;         // TXTBIT_SHOWACCELERATOR
        _fnpPropChg[ 4] = &CTxtEdit::OnUsePassword;             // TXTBIT_USEPASSWORD
        _fnpPropChg[ 5] = &CTxtEdit::OnTxHideSelectionChange;   // TXTBIT_HIDESELECTION
        _fnpPropChg[ 6] = &CTxtEdit::OnSaveSelection;           // TXTBIT_SAVESELECTION
        _fnpPropChg[ 7] = &CTxtEdit::OnAutoWordSel;             // TXTBIT_AUTOWORDSEL
        _fnpPropChg[ 8] = &CTxtEdit::OnTxVerticalChange;        // TXTBIT_VERTICAL
        _fnpPropChg[ 9] = &CTxtEdit::NeedViewUpdate;            // TXTBIT_SELECTIONBAR
        _fnpPropChg[10] = &CTxtEdit::OnWordWrapChange;          // TXTBIT_WORDWRAP
        _fnpPropChg[11] = &CTxtEdit::OnAllowBeep;               // TXTBIT_ALLOWBEEP
        _fnpPropChg[12] = &CTxtEdit::OnDisableDrag;             // TXTBIT_DISABLEDRAG
        _fnpPropChg[13] = &CTxtEdit::NeedViewUpdate;            // TXTBIT_VIEWINSETCHANGE
        _fnpPropChg[14] = &CTxtEdit::OnTxBackStyleChange;       // TXTBIT_BACKSTYLECHANGE
        _fnpPropChg[15] = &CTxtEdit::OnMaxLengthChange;         // TXTBIT_MAXLENGTHCHANGE
        _fnpPropChg[16] = &CTxtEdit::OnScrollChange;            // TXTBIT_SCROLLBARCHANGE
        _fnpPropChg[17] = &CTxtEdit::OnCharFormatChange;        // TXTBIT_CHARFORMATCHANGE
        _fnpPropChg[18] = &CTxtEdit::OnParaFormatChange;        // TXTBIT_PARAFORMATCHANGE
        _fnpPropChg[19] = &CTxtEdit::NeedViewUpdate;            // TXTBIT_EXTENTCHANGE
        _fnpPropChg[20] = &CTxtEdit::OnClientRectChange;        // TXTBIT_CLIENTRECTCHANGE
    }

    // Set up default CCharFormat and CParaFormat
    if (TxGetDefaultCharFormat(&CF, dwMask) != NOERROR ||
        TxGetDefaultParaFormat(&PF)         != NOERROR ||
        FAILED(GetCharFormatCache()->Cache(&CF, &iCF)) ||
        FAILED(GetParaFormatCache()->Cache(&PF, &iPF)))
    {
        return FALSE;
    }

    GetTabsCache()->Release(PF._iTabs);
    Set_iCF(iCF);                               // Save format indices
    Set_iPF(iPF);

    // Load mouse cursors (but only for first instance)
    if(!_hcurArrow)
    {
        _hcurArrow = LoadCursor(0, IDC_ARROW);
        if(!_hcurHand)
        {
            if (dwMajorVersion < 5)
                _hcurHand   = LoadCursor(hinstRE, MAKEINTRESOURCE(CUR_HAND));
            else
                _hcurHand   = LoadCursor(0, IDC_HAND);
        }
        if(!_hcurIBeam)                         // Load cursor
            _hcurIBeam  = LoadCursor(0, IDC_IBEAM);
        if(!_hcurItalic)
            _hcurItalic = LoadCursor(hinstRE, MAKEINTRESOURCE(CUR_ITALIC));
        if(!_hcurSelBar)
            _hcurSelBar = LoadCursor(hinstRE, MAKEINTRESOURCE(CUR_SELBAR));
    }

#ifdef DEBUG
    // The host is going to do some checking on richtext vs. plain text.
    _fRich = TRUE;
#endif // DEBUG

    if(_phost->TxGetPropertyBits (TXTBITS |     // Get host state flags
        TXTBIT_MULTILINE | TXTBIT_SHOWACCELERATOR,  //  that we cache or need
        &dwBits) != NOERROR)                        //  for display setup
    {
        return FALSE;
    }                                               // Cache bits defined by
    _dwFlags = dwBits & TXTBITS;                    //  TXTBITS mask

    if ((dwBits & TXTBIT_SHOWACCELERATOR) &&        // They want accelerator,
        FAILED(UpdateAccelerator()))                //  so let's get it
    {
        return FALSE;
    }

    _fTransparent = TxGetBackStyle() == TXTBACK_TRANSPARENT;
    if(dwBits & TXTBIT_MULTILINE)                   // Create and initialize
        _pdp = new CDisplayML(this);                //  display
    else
        _pdp = new CDisplaySL(this);
    Assert(_pdp);

    if(!_pdp || !_pdp->Init())
        return FALSE;

    _fUseUndo  = TRUE;
    _fAutoFont = TRUE;
    _fDualFont = TRUE;
    _f10DeferChangeNotify = 0;

    // Set whether we are in our host or not
    ITextHost2 *phost2;
    if(_phost->QueryInterface(IID_ITextHost2, (void **)&phost2) == NOERROR)
    {
        // We assume that ITextHost2 means this is our host
        phost2->Release();
        _fHost2 = TRUE;
    }
    else                                // Get maximum from our host
        _phost->TxGetMaxLength(&_cchTextMost);

    // Add EOP iff Rich Text
    if(IsRich())
    {
        // We should _not_ be in 10 compatibility mode yet.
        // If we transition into 1.0 mode, we'll add a CRLF
        // at the end of the document.
        SetRichDocEndEOP(0);
    }

    // Allow for win.ini control over use of line services
    if (W32->fUseLs())
    {
        OnSetTypographyOptions(TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY);
    }

    if (W32->GetDigitSubstitutionMode() != DIGITS_NOTIMPL)
        OrCharFlags(fDIGITSHAPE);       // digit substitution presents

    // Initialize the BiDi property
    // It is set to true if OS is BiDi (the system default LCID is a BiDi language)
    // or if the current keyboard code page is a BiDi code page
    // or if system.ini says we should do it.
    if (W32->OnBiDiOS() ||
        W32->IsBiDiCodePage(GetKeyboardCodePage(0xFFFFFFFF)) ||
        W32->fUseBiDi())
        OrCharFlags(fBIDI);

    _fAutoKeyboard = IsBiDi() && IsBiDiKbdInstalled();
    return TRUE;
}


///////////////////////////// CTxtEdit IUnknown ////////////////////////////////

/*
 *  CTxtEdit::QueryInterface (riid, ppv)
 *
 *  @mfunc
 *      IUnknown method
 *
 *  @rdesc
 *      HRESULT = (if success) ? NOERROR : E_NOINTERFACE
 *
 *  @devnote
 *      This interface is aggregated. See textserv.cpp for discussion.
 */
HRESULT CTxtEdit::QueryInterface(
    REFIID riid,
    void **ppv)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::QueryInterface");

    return _punk->QueryInterface(riid, ppv);
}

/*
 *  CTxtEdit::AddRef()
 *
 *  @mfunc
 *      IUnknown method
 *
 *  @rdesc
 *      ULONG - incremented reference count
 */
ULONG CTxtEdit::AddRef(void)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::AddRef");

    return _punk->AddRef();
}

/*
 *  CTxtEdit::Release()
 *
 *  @mfunc
 *      IUnknown method
 *
 *  @rdesc
 *      ULONG - decremented reference count
 */
ULONG CTxtEdit::Release(void)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::Release");

    return _punk->Release();
}

////////////////////////// Undo Management  //////////////////////////////

/*
 *  CTxtEdit::CreateUndoMgr (dwLim, flags)
 *
 *  @mfunc
 *      Creates an undo stack
 *
 *  @rdesc
 *      Ptr to new IUndoMgr
 */
IUndoMgr *CTxtEdit::CreateUndoMgr(
    DWORD   dwLim,          //@parm Size limit
    USFlags flags)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CreateUndoMgr");

    IUndoMgr *pmgr = NULL;

    if(_fUseUndo)
    {
        pmgr = new CUndoStack(this, dwLim, flags);
        if(!pmgr)
            return NULL;

        if(!pmgr->GetUndoLimit())
        {
            // The undo stack failed to initialize properly (probably
            // lack of memory). Trash it and return NULL.
            pmgr->Destroy();
            return NULL;
        }
        // We may be asked to create a new undo/redo manager
        // before we are completely done with initialization.
        // We need to clean up memory we have already allocated.
        if(flags & US_REDO)
        {
            if(_predo)
                _predo->Destroy();
            _predo = pmgr;
        }
        else
        {
            if(_pundo)
                _pundo->Destroy();
            _pundo = pmgr;
        }
    }
    return pmgr;
}

/*
 *  CTxtEdit::HandleUndoLimit (dwLim)
 *
 *  @mfunc
 *      Handles the EM_SETUNDOLIMIT message
 *
 *  @rdesc
 *      Actual limit to which things were set.
 */
LRESULT CTxtEdit::HandleSetUndoLimit(
    LONG Count)         //@parm Requested limit size
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::HandleSetUndoLimit");

    if (Count == tomSuspend ||              // This option really just
        Count == tomResume)                 //  suspends undo, i.e.,
    {                                       //  doesn't discard existing
        _fUseUndo = (Count == tomResume);   //  antievents
        return _pundo ? _pundo->GetUndoLimit() : 0;
    }

    if(Count < 0)
        Count = DEFAULT_UNDO_SIZE;

    if(!Count)
    {
        _fUseUndo = FALSE;
        if(_pundo)
        {
            _pundo->Destroy();
            _pundo = NULL;
        }
        if(_predo)
        {
            _predo->Destroy();
            _predo = NULL;
        }
    }
    else if(!_pundo)
    {
        _fUseUndo = TRUE;
        // Don't worry about return value; if it's NULL, we're
        // in the same boat as if the API wasn't called (so later
        // on, we might try to allocate the default).
        CreateUndoMgr(Count, US_UNDO);
    }
    else
    {
        Count = _pundo->SetUndoLimit(Count);

        // Setting the undo limit on the undo stack will return to
        // us the actual amount set.  Try to set the redo stack to
        // the same size.  If it can't go that big, too bad.
        if(_predo)
            _predo->SetUndoLimit(Count);
    }
    return Count;
}

/*
 *  CTxtEdit::HandleSetTextMode(mode)
 *
 *  @mfunc  handles setting the text mode
 *
 *  @rdesc  LRESULT; 0 (NOERROR) on success, OLE failure code on failure.
 *
 *  @devnote    the text mode does not have to be fully specified; it
 *          is sufficient to merely specify the specific desired behavior.
 *
 *          Note that the edit control must be completely empty for this
 *          routine to work.
 */
LRESULT CTxtEdit::HandleSetTextMode(
    DWORD mode)         //@parm the desired mode
{
    LRESULT lres = 0;

    // First off, we must be completely empty
    if (GetAdjustedTextLength() ||
        _pundo && _pundo->CanUndo() ||
        _predo && _predo->CanUndo())
    {
        return E_UNEXPECTED;
    }

    // These bits are considered one at a time; thus the absence of
    // any bits does _NOT_ imply any change in behavior.

    // TM_RICHTEXT && TM_PLAINTEXT are mutually exclusive; they cannot
    // be both set.  Same goes for TM_SINGLELEVELUNDO / TM_MULTILEVELUNDO
    // and TM_SINGLECODEPAGE / TM_MULTICODEPAGE
    if((mode & (TM_RICHTEXT | TM_PLAINTEXT)) == (TM_RICHTEXT | TM_PLAINTEXT) ||
       (mode & (TM_SINGLELEVELUNDO | TM_MULTILEVELUNDO)) ==
            (TM_SINGLELEVELUNDO | TM_MULTILEVELUNDO) ||
       (mode & (TM_SINGLECODEPAGE | TM_MULTICODEPAGE)) ==
            (TM_SINGLECODEPAGE | TM_MULTICODEPAGE))
    {
        lres = E_INVALIDARG;
    }
    else if((mode & TM_PLAINTEXT) && IsRich())
        lres = OnRichEditChange(FALSE);

    else if((mode & TM_RICHTEXT) && !IsRich())
        lres = OnRichEditChange(TRUE);

    if(!lres)
    {
        if(mode & TM_SINGLELEVELUNDO)
        {
            if(!_pundo)
                CreateUndoMgr(1, US_UNDO);

            if(_pundo)
            {
                // We can 'Enable' single level mode as many times
                // as we want, so no need to check for it before hand.
                lres = ((CUndoStack *)_pundo)->EnableSingleLevelMode();
            }
            else
                lres = E_OUTOFMEMORY;
        }
        else if(mode & TM_MULTILEVELUNDO)
        {
            // If there's no undo stack, no need to do anything,
            // we're already in multi-level mode
            if(_pundo && ((CUndoStack *)_pundo)->GetSingleLevelMode())
                ((CUndoStack *)_pundo)->DisableSingleLevelMode();
        }

        if(mode & TM_SINGLECODEPAGE)
            _fSingleCodePage = TRUE;

        else if(mode & TM_MULTICODEPAGE)
            _fSingleCodePage = FALSE;
    }

    // We don't want this marked modified after this operation to make us
    // work better in dialog boxes.
    _fModified = FALSE;

    return lres;
}


////////////////////////// Uniscribe Interface //////////////////////////////

/*
 *  GetUniscribe()
 *
 *  @mfunc
 *      returns a pointer to the Uniscribe interface object
 *
 *  @rdesc
 *      Ptr to Uniscribe interface
 */
extern BOOL g_fNoUniscribe;
CUniscribe* GetUniscribe()
{
    if (g_pusp)
        return g_pusp;

    if (g_fNoUniscribe)
        return NULL;

    //Attempt to create the Uniscribe object, but make sure the
    //OS is valid and that we can load the uniscribe DLL.
    int cScripts;
    //Find out if OS is valid, or if delay-load fails
    if (!IsSupportedOS() || FAILED(ScriptGetProperties(NULL, &cScripts)))
    {
        g_fNoUniscribe = TRUE;
        return NULL;
    }

    if (!g_pusp)
        g_pusp = new CUniscribe();

    AssertSz(g_pusp, "GetUniscribe(): Create Uniscribe object failed");
    return g_pusp;
}


////////////////////////// Notification Manager //////////////////////////////

/*
 *  CTxtEdit::GetNotifyMgr()
 *
 *  @mfunc
 *      returns a pointer to the notification manager (creating it if necessary)
 *
 *  @rdesc
 *      Ptr to notification manager
 */
CNotifyMgr *CTxtEdit::GetNotifyMgr()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetNotifyMgr");

    return &_nm;
}


////////////////////////// Object Manager ///////////////////////////////////

/*
 *  CTxtEdit::GetObjectMgr()
 *
 *  @mfunc
 *      returns a pointer to the object manager (creating if necessary)
 *
 *  @rdesc
 *      pointer to the object manager
 */
CObjectMgr *CTxtEdit::GetObjectMgr()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetObjectMgr");

    if(!_pobjmgr)
        _pobjmgr = new CObjectMgr();

    return _pobjmgr;
}


////////////////////////////// Properties - Selection ////////////////////////////////


LONG CTxtEdit::GetSelMin() const
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetSelMin");

    return _psel ? _psel->GetCpMin() : 0;
}

LONG CTxtEdit::GetSelMost() const
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetSelMost");

    return _psel ? _psel->GetCpMost() : 0;
}


////////////////////////////// Properties - Text //////////////////////////////////////

LONG CTxtEdit::GetTextRange(
    LONG    cpFirst,
    LONG    cch,
    TCHAR * pch)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetTextRange");

#ifdef DEBUG
    const LONG cchAsk = cch;
#endif
    CTxtPtr tp(this, cpFirst);
    LONG    cchAdj = GetAdjustedTextLength();

    if(--cch < 0 || cpFirst > cchAdj)
        return 0;

    cch = min(cch, cchAdj - cpFirst);
    if(cch > 0)
    {
        cch = tp.GetText(cch, pch);
        Assert(cch >= 0);
    }
    pch[cch] = TEXT('\0');

#ifdef DEBUG
    if(cch != cchAsk - 1)
        Tracef(TRCSEVINFO, "CTxtEdit::GetTextRange: only got %ld out of %ld", cch, cchAsk - 1);
#endif

    return cch;
}

/*
 *  CTxtEdit::GetTextEx (pgt, pch)
 *
 *  @mfunc
 *      Grabs text according to various params
 *
 *  @rdesc
 *      Count of bytes gotten
 */
LONG CTxtEdit::GetTextEx(
    GETTEXTEX *pgt,     //@parm Info on what to get
    TCHAR *    pch)     //@parm Where to put the text
{
    LONG    cb;
    LONG    cch;
    LONG    cchGet = GetAdjustedTextLength();
    TCHAR * pchUse = pch;
    CTxtPtr tp(this, 0);
    CTempWcharBuf twcb;

    if(pgt->flags & GT_SELECTION)           // Get selected text
    {
        LONG cpMin, cpMost;
        cch = GetSel()->GetRange(cpMin, cpMost);
        cchGet = min(cch, cchGet - cpMin);  // Don't include final EOP
        tp.SetCp(cpMin);
    }

    if(pgt->codepage == (unsigned)-1)   // Use default codepage
        pgt->codepage = GetDefaultCodePage(EM_GETTEXTEX);

    if(pgt->cb == (unsigned)-1)         // Client says its buffer is big enuf
    {
        pgt->cb = cchGet + 1;
        if(W32->IsFECodePage(pgt->codepage) || pgt->codepage == 1200)
            pgt->cb += cchGet;
        else if(pgt->codepage == CP_UTF8 && (_dwCharFlags & ~fASCII))
            pgt->cb *= (_dwCharFlags & fABOVEX7FF) ? 3 : 2;
    }

    // Allocate a big buffer; make sure that we have
    // enough room for lots of CRLFs if necessary
    if(pgt->flags & GT_USECRLF)
        cchGet *= 2;

    if(pgt->codepage != 1200)
    {
        // If UNICODE, copy straight to client's buffer;
        // else, copy to temp buffer and translate cases first
        pchUse = twcb.GetBuf(cchGet + 1);
        if (pch)
            *((char *) pch) = '\0';         // In case something fails
    }
    else                        // Be sure to leave room for NULL terminator
        cchGet = min(UINT(pgt->cb/2 - 1), (UINT)cchGet);

    // Now grab the text.
    if(pgt->flags & GT_USECRLF)
        cch = tp.GetPlainText(cchGet, pchUse, tomForward, FALSE);
    else
        cch = tp.GetText(cchGet, pchUse);

    pchUse[cch] = L'\0';

    // If we're just doing UNICODE, return number of chars written
    if(pgt->codepage == 1200)
        return cch;

    // Oops, gotta translate to ANSI.
    cb = WideCharToMultiByte(pgt->codepage, 0, pchUse, cch + 1, (char *)pch,
            pgt->cb, pgt->lpDefaultChar, pgt->lpUsedDefChar);

    // Don't count NULL terminator for compatibility with WM_GETTEXT.
    return (cb) ? cb - 1 : 0;
}

/*
 *  CTxtEdit::GetTextLengthEx (pgtl)
 *
 *  @mfunc
 *      Calculates text length in various ways.
 *
 *  @rdesc
 *      Text length calculated in various ways
 *
 *  @comm
 *      This function returns an API cp that may differ from the
 *      corresponding internal Unicode cp.
 */
LONG CTxtEdit::GetTextLengthEx(
    GETTEXTLENGTHEX *pgtl)  //@parm Info describing how to calculate length
{
    LONG    cchUnicode = GetAdjustedTextLength();
    LONG    cEOP = 0;
    DWORD   dwFlags = pgtl->flags;
    GETTEXTEX gt;

    if(pgtl->codepage == (unsigned)-1)
        pgtl->codepage = GetDefaultCodePage(EM_GETTEXTLENGTHEX);

    // Make sure the flags are defined appropriately
    if ((dwFlags & GTL_CLOSE)    && (dwFlags & GTL_PRECISE) ||
        (dwFlags & GTL_NUMCHARS) && (dwFlags & GTL_NUMBYTES))
    {
        TRACEWARNSZ("Invalid flags for EM_GETTEXTLENGTHEX");
        return E_INVALIDARG;
    }

    // Note in the following if statement, the second part of the
    // and clause will always be TRUE. At some point in the future
    // fUseCRLF and Get10Mode may become independent, in which case
    // the code below will automatically work without change.
	// NEW: 1.0 mode gets text as is, so don't add count for CRs.
	// (RichEdit 1.0 only inserts Enters as CRLFs; it doesn't "cleanse"
	// other text insertion strings)
	if((dwFlags & GTL_USECRLF) && !fUseCRLF() && !Get10Mode())
	{
		// Important facts for 1.0 mode (REMARK: this is out of date):
        //
        // (1) 1.0 mode implies that the text is stored with fUseCRLF true.
        // fUseCRLF means that the EOP mark can either be a CR or a
        // CRLF - see CTxtRange::CleanseAndReplaceRange for details.
        //
        // (2) 1.0 mode has an invariant that the count of text returned
        // by this call should be enough to hold all the text returned by
        // WM_GETTEXT.
        //
        // (3) The WM_GETEXT call for 1.0 mode will return a buffer in
        // which all EOPs that consist of a CR are replaced by CRLF.
        //
        // Therefore, for 1.0 mode, we must count all EOPs that consist
        // of only a CR and add addition return character to count the
        // LF that will be added into any WM_GETEXT buffer.

        // For 2.0 mode, the code is much easier, just count up all
        // CRs and bump count of each one by 1.

        CTxtPtr tp(this, 0);
        LONG    Results;

        while(tp.FindEOP(tomForward, &Results))
        {
            // If EOP consists of 1 char, add 1 since is returned by a CRLF.
            // If it consists of 2 chars, add 0, since it's a CRLF and is
            // returned as such.
            if(tp.GetCp() > cchUnicode)     // Don't add correction for
                break;                      //  final CR (if any)
            Results &= 3;
            if(Results)
                cEOP += 2 - Results;

            AssertSz(IN_RANGE(1, Results, 2) || !Results && tp.GetCp() == cchUnicode,
                "CTxtEdit::GetTextLengthEx: CRCRLF found in backing store");
        }
        cchUnicode += cEOP;
    }

	// If we're just looking for the number of characters or if it's an
	// 8-bit codepage in RE 1.0 mode, we've already got the count.
	if ((dwFlags & GTL_NUMCHARS) || !dwFlags ||
		Get10Mode() && Is8BitCodePage(pgtl->codepage))
	{
        return cchUnicode;
	}

    // Hmm, they're looking for number of bytes, but don't care about
    // precision, just multiply by two.  If neither PRECISE or CLOSE is
	// specified, default to CLOSE. Note if the codepage is UNICODE and
	// asking for number of bytes, we also just multiply by 2.
    if((dwFlags & GTL_CLOSE) || !(dwFlags & GTL_PRECISE) ||
        pgtl->codepage == 1200)
    {
        return cchUnicode *2;
    }

	// In order to get a precise answer, we need to convert (which is slow!).
    gt.cb = 0;
    gt.flags = (pgtl->flags & GT_USECRLF);
    gt.codepage = pgtl->codepage;
    gt.lpDefaultChar = NULL;
    gt.lpUsedDefChar = NULL;

    return GetTextEx(&gt, NULL);
}

/*
 *  CTxtEdit::GetDefaultCodePage (msg)
 *
 *  @mfunc
 *      Return codepage to use for converting the text in RichEdit20A text
 *      messages.
 *
 *  @rdesc
 *      Codepage to use for converting the text in RichEdit20A text messages.
 */
LONG CTxtEdit::GetDefaultCodePage(
    UINT msg)
{
    LONG CodePage = GetACP();

    // FUTURE: For backward compatibility in Office97, We always use ACP for all these
    // languages. Need review in the future when the world all moves to Unicode.
    if (W32->IsBiDiCodePage(CodePage) || CodePage == CP_THAI || CodePage == CP_VIETNAMESE ||
        W32->IsFECodePage(CodePage) || _fSingleCodePage || msg == EM_GETCHARFORMAT ||
        msg == EM_SETCHARFORMAT)
    {
        return CodePage;
    }

    if(Get10Mode())
        return GetCodePage(GetCharFormat(-1)->_bCharSet);

    return GetKeyboardCodePage();
}

//////////////////////////////  Properties - Formats  //////////////////////////////////

/*
 *  CTxtEdit::HandleStyle (pCFTarget, pCF, dwMask, dwMask2)
 *
 *  @mfunc
 *      If pCF specifies a style choice, initialize pCFTarget with the
 *      appropriate style, apply pCF, and return NOERROR.  Else return
 *      S_FALSE or an error
 *
 *  @rdesc
 *      HRESULT = (pCF specifies a style choice) ? NOERROR : S_FALSE or error code
 */
HRESULT CTxtEdit::HandleStyle(
    CCharFormat *pCFTarget,     //@parm Target CF to receive CF style content
    const CCharFormat *pCF,     //@parm Source CF that may specify a style
    DWORD        dwMask,        //@parm CHARFORMAT2 mask
    DWORD        dwMask2)       //@parm Second mask
{
    if(pCF->fSetStyle(dwMask, dwMask2))
    {
        // FUTURE: generalize to use client style if specified
        *pCFTarget = *GetCharFormat(-1);
        pCFTarget->ApplyDefaultStyle(pCF->_sStyle);
        return pCFTarget->Apply(pCF, dwMask, dwMask2);
    }
    return S_FALSE;
}

/*
 *  CTxtEdit::HandleStyle (pPFTarget, pPF)
 *
 *  @mfunc
 *      If pPF specifies a style choice, initialize pPFTarget with the
 *      appropriate style, apply pPF, and return NOERROR.  Else return
 *      S_FALSE or an error
 *
 *  @rdesc
 *      HRESULT = (pPF specifies a style choice) ? NOERROR : S_FALSE or error code
 */
HRESULT CTxtEdit::HandleStyle(
    CParaFormat *pPFTarget,     //@parm Target PF to receive PF style content
    const CParaFormat *pPF,     //@parm Source PF that may specify a style
    DWORD       dwMask)         //@parm Mask to use in setting CParaFormat
{
    if(pPF->fSetStyle(dwMask))
    {
        // FUTURE: generalize to use client style if specified
        *pPFTarget = *GetParaFormat(-1);
        pPFTarget->ApplyDefaultStyle(pPF->_sStyle);
        return pPFTarget->Apply(pPF, dwMask);
    }
    return S_FALSE;
}

//////////////////////////// Mouse Commands /////////////////////////////////


HRESULT CTxtEdit::OnTxLButtonDblClk(
    INT     x,          //@parm Mouse x coordinate
    INT     y,          //@parm Mouse y coordinate
    DWORD   dwFlags)    //@parm Mouse message wparam
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxLButtonDblClk");

    BOOL            fEnterParaSelMode = FALSE;
    HITTEST         Hit;
    CTxtSelection * psel = GetSel();
    const POINT     pt = {x, y};

    AssertSz(psel, "CTxtEdit::OnTxLButtonDblClk() - No selection object !");

    if (StopMagellanScroll())
        return S_OK;

    _dwTickDblClick = GetTickCount();
    _ptDblClick.x = x;
    _ptDblClick.y = y;

    TxUpdateWindow();       // Repaint window to show any exposed portions

    if(!_fFocus)
    {
        TxSetFocus();                   // Create and display caret
        return S_OK;
    }

    // Find out what the cursor is pointing at
    _pdp->CpFromPoint(pt, NULL, NULL, NULL, FALSE, &Hit);

    if(Hit == HT_Nothing)
        return S_OK;

    if(Hit == HT_OutlineSymbol)
    {
        CTxtRange rg(*psel);
        rg.ExpandOutline(0, FALSE);
        return S_OK;
    }

    if(Hit == HT_LeftOfText)
        fEnterParaSelMode = TRUE;

    _fWantDrag = FALSE;                 // just to be safe

    // If we are over a link, let the client have a chance to process
    // the message
    if(Hit == HT_Link && HandleLinkNotification(WM_LBUTTONDBLCLK, (WPARAM)dwFlags,
            MAKELPARAM(x, y)))
    {
        return S_OK;
    }

    if(dwFlags & MK_CONTROL)
        return S_OK;

    // Mark mouse down
    _fMouseDown = TRUE;

    if(_pobjmgr && _pobjmgr->HandleDoubleClick(this, pt, dwFlags))
    {
        // The object subsystem handled everything
        _fMouseDown = FALSE;
        return S_OK;
    }

    // Update the selection
    if(fEnterParaSelMode)
        psel->SelectUnit(pt, tomParagraph);
    else
        psel->SelectWord(pt);

    return S_OK;
}

HRESULT CTxtEdit::OnTxLButtonDown(
    INT     x,          //@parm Mouse x coordinate
    INT     y,          //@parm Mouse y coordinate
    DWORD   dwFlags)    //@parm Mouse message wparam
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxLButtonDown");

    BOOL        fEnterLineSelMode = FALSE;
    BOOL        fShift = dwFlags & MK_SHIFT;
    HITTEST     Hit;
    const POINT pt = {x, y};
    COleObject *pobj;
    BOOL        fMustThaw = FALSE;

    const BOOL fTripleClick = GetTickCount() < _dwTickDblClick + W32->GetDCT() &&
                abs(x - _ptDblClick.x) <= W32->GetCxDoubleClk() &&
                abs(y - _ptDblClick.y) <= W32->GetCyDoubleClk();

    if (StopMagellanScroll())
        return S_OK;

    // If click isn't inside view, just activate, don't select

    if(!_fFocus)                    // Sets focus if not already
    {
        // We may be removing an existing selection, so freeze
        // display to avoid flicker
        _pdp->Freeze();
        fMustThaw = TRUE;
        TxSetFocus();               // creates and displays caret
    }

    // Grab selection object
    CTxtSelection * const psel = GetSel();
    AssertSz(psel,"CTxtEdit::OnTxLButtonDown - No selection object !");

    // Find out what cursor is pointing at
    _pdp->CpFromPoint(pt, NULL, NULL, NULL, FALSE, &Hit);

    if(Hit == HT_LeftOfText)
    {
        // Shift click in sel bar treated as normal click
        if(!fShift)
        {
            // Control selbar click and triple selbar click
            // are select all
            if((dwFlags & MK_CONTROL) || fTripleClick)
            {
                psel->SelectAll();
                goto cancel_modes;
            }
            fEnterLineSelMode = TRUE;
            if(!GetAdjustedTextLength() && !_pdp->IsMultiLine())
            {
                const CParaFormat *pPF = psel->GetPF();
                // Can't see selected para mark when flushed right, so
                // leave selection as an insertion point
                if(pPF->_bAlignment == PFA_RIGHT && !pPF->IsRtlPara())
                    fEnterLineSelMode = FALSE;
            }
        }
    }
    else if(Hit == HT_Nothing)
        goto cancel_modes;

    else if(!fShift)
        psel->CancelModes();

    // Let client have a chance to handle this message if we are over a link
    if(Hit == HT_Link && HandleLinkNotification(WM_LBUTTONDOWN, (WPARAM)dwFlags,
            MAKELPARAM(x, y)))
    {
        goto cancel_modes;
    }

    _fMouseDown = TRUE;                     // Flag mouse down
    if(!fShift && _pobjmgr)
    {
        // Deactivate anybody active, etc.
        ClickStatus status = _pobjmgr->HandleClick(this, pt);
        if(status == CLICK_OBJSELECTED)
        {
            // The object subsystem will handle resizing.
            // if not a resize we will signal start of drag
            pobj = _pobjmgr->GetSingleSelect();

            // Because HandleClick returned true, pobj better be non-null.
            Assert(pobj);

            if (!pobj->HandleResize(pt))
                _fWantDrag = !_fDisableDrag;

            goto cancel_modes;
        }
        else if(status == CLICK_OBJDEACTIVATED)
            goto cancel_modes;
    }

    _fCapture = TRUE;                       // Capture the mouse
    TxSetCapture(TRUE);

    // Check for start of drag and drop
    if(!fTripleClick && !fShift && psel->PointInSel(pt, NULL, Hit)
        && !_fDisableDrag)
    {
        // Assume we want a drag. If we don't CmdLeftUp() needs
        //  this to be set anyway to change the selection
        _fWantDrag = TRUE;

        goto cancel_modes;
    }

    if(fShift)                              // Extend selection from current
    {                                       //  active end to click
        psel->InitClickForAutWordSel(pt);
        psel->ExtendSelection(pt);
    }
    else if(fEnterLineSelMode)              // Line selection mode: select line
        psel->SelectUnit(pt, tomLine);
    else if(fTripleClick || Hit == HT_OutlineSymbol) // paragraph selection mode
        psel->SelectUnit(pt, tomParagraph);
    else
    {
        if (Get10Mode())
            _f10DeferChangeNotify = 1;
        psel->SetCaret(pt);
        _mousePt = pt;
    }

    if(fMustThaw)
        _pdp->Thaw();

    return S_OK;

cancel_modes:
    psel->CancelModes();

    if(_fWantDrag)
    {
        TxSetTimer(RETID_DRAGDROP, W32->GetDragDelay());
        _mousePt = pt;
        _bMouseFlags = (BYTE)dwFlags;
        _fDragged = FALSE;
    }

    if(fMustThaw)
        _pdp->Thaw();

    return S_OK;
}

HRESULT CTxtEdit::OnTxLButtonUp(
    INT     x,              //@parm Mouse x coordinate
    INT     y,              //@parm Mouse y coordinate
    DWORD   dwFlags,        //@parm Mouse message wparam
    int     ffOptions)      //@parm Mouse options, see _edit.h for details
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxLButtonUp");

    CheckRemoveContinuousScroll();

    // Remove capture before test for mouse down since we wait till
    // we get the mouse button up message to release capture since Forms
    // wants it that way.
    if(_fCapture && (ffOptions & LB_RELEASECAPTURE))
    {
        TxSetCapture(FALSE);
        _fCapture = FALSE;
    }

    // we were delaying selection change.  So send it now...
    if (DelayChangeNotification() && (ffOptions & LB_FLUSHNOTIFY))
    {
        AssertSz(Get10Mode(), "Flag should only be set in 10 mode");
        _f10DeferChangeNotify = 0;
        GetCallMgr()->SetSelectionChanged();
    }

    if(!_fMouseDown)
    {
        // We noticed the mouse was no longer down earlier so we don't
        // need to do anything.
        return S_OK;
    }

    const BOOL fSetSel = !!_fWantDrag;
    const POINT pt = {x, y};

    // Cancel Auto Word Sel if on
    CTxtSelection * const psel = GetSel();
    AssertSz(psel,"CTxtEdit::OnLeftUp() - No selection object !");

    psel->CancelModes(TRUE);

    // Reset flags
    _fMouseDown = FALSE;
    _fWantDrag = FALSE;
    _fDragged = FALSE;
    TxKillTimer(RETID_DRAGDROP);
    if(IsInOutlineView())
        psel->Update(FALSE);

    // Let the client handle this message if we are over a
    // link area
    if(HandleLinkNotification(WM_LBUTTONUP, (WPARAM)dwFlags,
            MAKELPARAM(x, y)))
    {
        return NOERROR;
    }

    // If we were in drag & drop, put caret under mouse
    if(fSetSel)
    {
        CObjectMgr* pobjmgr = GetObjectMgr();

        // If we were on an object, don't deselect it by setting the caret
        if(pobjmgr && !pobjmgr->GetSingleSelect())
        {
            psel->SetCaret(pt, TRUE);
            if(!_fFocus)
                TxSetFocus();       // create and display caret
        }
    }
    return S_OK;
}

HRESULT CTxtEdit::OnTxRButtonUp(
    INT     x,          //@parm Mouse x coordinate
    INT     y,          //@parm Mouse y coordinate
    DWORD   dwFlags,    //@parm Mouse message wparam
    int     ffOptions)  //@parm option flag
{
    const POINT pt = {x, y};
    CTxtSelection * psel;
    SELCHANGE selchg;
    HMENU hmenu = NULL;
    IOleObject * poo = NULL;
    COleObject * pobj = NULL;
    IUnknown * pUnk = NULL;
    IRichEditOleCallback * precall = NULL;

    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxRButtonUp");

    // make sure we have the focus
    if(!_fFocus)
        TxSetFocus();

    if(_fWantDrag)
    {
        _fDragged = FALSE;
        _fWantDrag = FALSE;
        TxKillTimer(RETID_DRAGDROP);
    }

    // Grab selection object
    psel = GetSel();
    psel->SetSelectionInfo(&selchg);

    //We need a pointer to the first object, if any, in the selection.
    if(_pobjmgr)
    {
        //If the point is in the selection we need to find out if there
        //are any objects in the selection.  If the point is not in a
        //selection but it is on an object, we need to select the object.
        if(psel->PointInSel(pt, NULL) || (ffOptions & RB_FORCEINSEL))
        {
            pobj = _pobjmgr->GetFirstObjectInRange(selchg.chrg.cpMin,
                selchg.chrg.cpMost);
        }
        else
        {
            //Select the object
            if(_pobjmgr->HandleClick(this, pt) == CLICK_OBJSELECTED)
            {
                pobj = _pobjmgr->GetSingleSelect();
                // Because HandleClick returned true, pobj better be non-null.
                Assert(pobj!=NULL);
                //Refresh our information about the selection
                psel = GetSel();
                psel->SetSelectionInfo(&selchg);
            }
        }
        precall = _pobjmgr->GetRECallback();
    }

    if(pobj)
        pUnk = pobj->GetIUnknown();

    if(pUnk)
        pUnk->QueryInterface(IID_IOleObject, (void **)&poo);

    if(precall)
        precall->GetContextMenu(selchg.seltyp, poo, &selchg.chrg, &hmenu);

    if(hmenu)
    {
        HWND hwnd, hwndParent;
        POINT ptscr;

        if(TxGetWindow(&hwnd) == NOERROR)
        {
            if(!(ffOptions & RB_NOSELCHECK) && !psel->PointInSel(pt, NULL) &&
                !psel->GetCch() && !(ffOptions & RB_FORCEINSEL))
                psel->SetCaret(pt);
            ptscr.x = pt.x;
            ptscr.y = pt.y;
            ClientToScreen(hwnd, &ptscr);

            hwndParent = GetParent(hwnd);
            if(!hwndParent)
                hwndParent = hwnd;

            TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON,
                ptscr.x, ptscr.y, 0, hwndParent, NULL);
        }
        DestroyMenu(hmenu);
    }

    if(poo)
        poo->Release();

    return precall ? S_OK : S_FALSE;
}

HRESULT CTxtEdit::OnTxRButtonDown(
    INT     x,          //@parm Mouse x coordinate
    INT     y,          //@parm Mouse y coordinate
    DWORD   dwFlags)    //@parm Mouse message wparam
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxRButtonDown");

    if (StopMagellanScroll())
        return S_OK;

    CTxtSelection *psel = GetSel();
    const POINT pt = {x, y};

    psel->CancelModes();

    if(psel->PointInSel(pt, NULL) && !_fDisableDrag)
    {
        _fWantDrag = TRUE;

        TxSetTimer(RETID_DRAGDROP, W32->GetDragDelay());
        _mousePt = pt;
        _bMouseFlags = (BYTE)dwFlags;
        _fDragged = FALSE;
        return S_OK;
    }
    return S_FALSE;
}

HRESULT CTxtEdit::OnTxMouseMove(
    INT     x,          //@parm Mouse x coordinate
    INT     y,          //@parm Mouse y coordinate
    DWORD   dwFlags,    //@parm Mouse message wparam
    IUndoBuilder *publdr)
{
    int     dx, dy;
    BOOL    fLButtonDown = FALSE;
    BOOL    fRButtonDown = FALSE;
    DWORD   vkLButton, vkRButton;

    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxMouseMove");

    CTxtSelection * const psel = GetSel();

    if(!_fFocus)
        return S_OK;

    if(_fWantDrag || _fCapture)
    {
        LONG nDragMinDist = W32->GetDragMinDist() + 3;
        dx = _mousePt.x > x ? _mousePt.x - x : x - _mousePt.x;
        dy = _mousePt.y > y ? _mousePt.y - y : y - _mousePt.y;
        if(dx < nDragMinDist && dy < nDragMinDist)
        {
            _bMouseFlags = (BYTE)dwFlags;
            return S_OK;
        }
        _fDragged = _fWantDrag;
    }

    _mousePt.x = x;                                 // Remember for scrolling
    _mousePt.y = y;                                 //  speed, and dir calc.

    // RichEdit 1.0 allows the client to process mouse moves itself if
    // we are over a link (but _not_ doing drag drop).
    if(HandleLinkNotification(WM_MOUSEMOVE, 0, MAKELPARAM(x, y)))
        return NOERROR;

    // If we think the mouse is down and it really is then do special
    // processing.
    if(GetSystemMetrics(SM_SWAPBUTTON))
    {
        vkLButton = VK_RBUTTON;
        vkRButton = VK_LBUTTON;
    }
    else
    {
        vkLButton = VK_LBUTTON;
        vkRButton = VK_RBUTTON;
    }

    fLButtonDown = (GetAsyncKeyState(vkLButton) < 0);
    if(!fLButtonDown)
        fRButtonDown = (GetAsyncKeyState(vkRButton) < 0);

    if(fLButtonDown || fRButtonDown)
    {
        if(_fWantDrag
            && !_fUsePassword
            && !IsProtected(_fReadOnly ? WM_COPY : WM_CUT, dwFlags,
                    MAKELONG(x,y)))
        {
            TxKillTimer(RETID_DRAGDROP);
            _ldte.StartDrag(psel, publdr);
            // the mouse button may still be down, but drag drop is over
            // so we need to _think_ of it as up.
            _fMouseDown = FALSE;

            // similarly, OLE should have nuked the capture for us, but
            // just in case something failed, release the capture.
            TxSetCapture(FALSE);
            _fCapture = FALSE;
        }
        else if(_fMouseDown)
        {
            POINT   pt = _mousePt;

            // We think mouse is down and it is
            if(_ldte.fInDrag())
            {
                // Only do drag scrolling if a drag operation is in progress.
                _pdp->DragScroll(&pt);
            }

            AssertSz(psel,"CTxtEdit::OnMouseMove: No selection object !");
            psel->ExtendSelection(pt);              // Extend the selection

            CheckInstallContinuousScroll ();
        }
    }
    else if (!(GetAsyncKeyState(VK_MBUTTON) < 0) && !mouse.IsAutoScrolling())
    {
        // Make sure we aren't autoscrolling via intellimouse

        if(_fMButtonCapture)
            OnTxMButtonUp (x, y, dwFlags);

        if(_fMouseDown)
        {
            // Although we thought the mouse was down, at this moment it
            // clearly is not. Therefore, we pretend we got a mouse up
            // message and clear our state to get ourselves back in sync
            // with what is really happening.
            OnTxLButtonUp(x, y, dwFlags, LB_RELEASECAPTURE);
        }

    }

    // Either a drag was started or the mouse button was not down. In either
    // case, we want no longer to start a drag so we set the flag to false.
    _fWantDrag = FALSE;
    return S_OK;
}

/*
 *  OnTxMButtonDown (x, y, dwFlags)
 *
 *  @mfunc
 *      The user pressed the middle mouse button, setup to do
 *      continuous scrolls, which may in turn initiate a timer
 *      for smooth scrolling.
 *
 *  @rdesc
 *      HRESULT = S_OK
 */
HRESULT CTxtEdit::OnTxMButtonDown (
    INT     x,          //@parm Mouse x coordinate
    INT     y,          //@parm Mouse y coordinate
    DWORD   dwFlags)    //@parm Mouse message wparam
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxMButtonDown");

#if !defined(NOMAGELLAN)
    POINT   mDownPt = {x,y};

    if(!_fFocus)
        TxSetFocus();

    if(!StopMagellanScroll() && mouse.MagellanStartMButtonScroll(*this, mDownPt))
    {
        TxSetCapture(TRUE);

        _fCapture           = TRUE;                         // Capture the mouse
        _fMouseDown         = TRUE;
        _fMButtonCapture    = TRUE;
    }
#endif

    return S_OK;
}

/*
 *  CTxtEdit::OnTxMButtonUp (x, y, dwFlags)
 *
 *  @mfunc
 *      Remove timers and capture assoicated with a MButtonDown
 *      message.
 *
 *  @rdesc
 *      HRESULT = S_OK
 */
HRESULT CTxtEdit::OnTxMButtonUp (
    INT     x,          //@parm Mouse x coordinate
    INT     y,          //@parm Mouse y coordinate
    DWORD   dwFlags)    //@parm Mouse message wparam
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxMButtonUp");

#if !defined(NOMAGELLAN)
    if (mouse.ContinueMButtonScroll(x, y))
        return S_OK;

    StopMagellanScroll();

#else

    if(_fCapture)
        TxSetCapture(FALSE);

    _fCapture           = FALSE;
    _fMouseDown         = FALSE;
    _fMButtonCapture    = FALSE;

#endif

    return S_OK;
}


/*
 *  CTxtEdit::StopMagellanScroll()
 *
 *  @mfunc
 *      Stops the intellimouse autoscrolling and returns
 *      us back into a normal state
 *
 *  BOOL = TRUE if auto scrolling was turned off : FALSE
 *          Autoscrolling was never turned on
 */
 BOOL CTxtEdit::StopMagellanScroll ()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::StopMagellanScroll");

#if !defined(NOMAGELLAN)
    if (!mouse.IsAutoScrolling())
        return FALSE;

    mouse.MagellanEndMButtonScroll(*this);

    if(_fCapture)
        TxSetCapture(FALSE);

    _fCapture           = FALSE;
    _fMouseDown         = FALSE;
    _fMButtonCapture    = FALSE;
    return TRUE;
#else
    return FALSE;
#endif
}


/*
 *  CTxtEdit::CheckInstallContinuousScroll ()
 *
 *  @mfunc
 *      There are no events that inform the app on a regular
 *      basis that a mouse button is down. This timer notifies
 *      the app that the button is still down, so that scrolling can
 *      continue.
 */
void CTxtEdit::CheckInstallContinuousScroll ()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CheckInstallContinuousScroll");

    if(!_fContinuousScroll && TxSetTimer(RETID_AUTOSCROLL, cmsecScrollInterval))
        _fContinuousScroll = TRUE;
}

/*
 *  CTxtEdit::CheckRemoveContinuousScroll ()
 *
 *  @mfunc
 *      The middle mouse button, or drag button, is up
 *      remove the continuous scroll timer.
 */
void CTxtEdit::CheckRemoveContinuousScroll ()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CheckRemoveContinuousScroll");

    if(_fContinuousScroll)
    {
        TxKillTimer(RETID_AUTOSCROLL);
        _fContinuousScroll = FALSE;
    }
}

/*
 *  OnTxTimer(idTimer)
 *
 *  @mfunc
 *      Handle timers for doing background recalc and scrolling.
 *
 *  @rdesc
 *      HRESULT = (idTimer valid) ? S_OK : S_FALSE
 */
HRESULT CTxtEdit::OnTxTimer(
    UINT idTimer)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxTimer");

    switch (idTimer)
    {
        case RETID_BGND_RECALC:
            _pdp->StepBackgroundRecalc();
            break;

#if !defined(NOMAGELLAN)
        case RETID_MAGELLANTRACK:
            mouse.TrackUpdateMagellanMButtonDown(*this, _mousePt);
            break;
#endif
        case RETID_AUTOSCROLL:                      // Continuous scrolling.
            OnTxMouseMove(_mousePt.x, _mousePt.y,   // Do a select drag scroll.
                          0, NULL);
            break;

#if !defined(NOMAGELLAN)
        case RETID_SMOOTHSCROLL:                    // Smooth scrolling
            if(_fMButtonCapture)                    // HACK, only 1 timer!
            {                                       // delivered on Win95
                                                    // when things get busy.
                mouse.TrackUpdateMagellanMButtonDown(*this, _mousePt);
            }
            if(_pdp->IsSmoothVScolling())           // Test only because of
                _pdp->SmoothVScrollUpdate();        //  above HACK!!
        break;
#endif
        case RETID_DRAGDROP:
            TxKillTimer(RETID_DRAGDROP);
            if (_fWantDrag && _fDragged && !_fUsePassword &&
                !IsProtected(_fReadOnly ? WM_COPY : WM_CUT,
                             _bMouseFlags, MAKELONG(_mousePt.x,_mousePt.y)))
            {
                IUndoBuilder *  publdr;
                CGenUndoBuilder undobldr(this, UB_AUTOCOMMIT, &publdr);
                _ldte.StartDrag(GetSel(), publdr);
                _fWantDrag = FALSE;
                _fDragged = FALSE;
                TxSetCapture(FALSE);
                _fCapture = FALSE;
            }
            break;

        default:
            return S_FALSE;
    }
    return S_OK;
}


/////////////////////////// Keyboard Commands ////////////////////////////////

/*
 *  CTxtEdit::OnTxKeyDown(vkey, dwFlags, publdr)
 *
 *  @mfunc
 *      Handle WM_KEYDOWN message
 *
 *  @rdesc
 *      HRESULT with the following values:
 *
 *      S_OK                if key was understood and consumed
 *      S_MSG_KEY_IGNORED   if key was understood, but not consumed
 *      S_FALSE             if key was not understood or just looked at
 *                                  and in any event not consumed
 */
HRESULT CTxtEdit::OnTxKeyDown(
    WORD          vkey,     //@parm Virtual key code
    DWORD         dwFlags,  //@parm lparam of WM_KEYDOWN msg
    IUndoBuilder *publdr)   //@parm Undobuilder to receive antievents
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxKeyDown");

    if(IN_RANGE(VK_SHIFT, vkey, VK_MENU))
    {
        SetKeyboardFlag(GetKbdFlags(vkey, dwFlags));
        return S_FALSE;
    }

    BOOL  fAlt   = GetKeyboardFlag(ALT, VK_MENU);
    BOOL  fCtrl  = GetKeyboardFlag(CTRL, VK_CONTROL);
    BOOL  fShift = GetKeyboardFlag(SHIFT, VK_SHIFT);

    BOOL  fRet   = FALSE;           // Converted to HRESULT on return
    LONG  nDeadKey = 0;

    if(fCtrl & fShift)                      // Signal NonCtrl/Shift keydown
        SetKeyboardFlag(LETAFTERSHIFT);     //  while Ctrl&Shift are down

    // Handle Hebrew caps and LRM/RLM
    if (IsBiDi())
    {
        if (W32->IsBiDiCodePage(GetKeyboardCodePage(0xFFFFFFFF)))
        {
            _fHbrCaps = FALSE;
            if(IsRich() && W32->UsingHebrewKeyboard())
            {
                WORD wCapital = GetKeyState(VK_CAPITAL);
                _fHbrCaps = ((wCapital & 1) ^ fShift) &&
                            !(wCapital & 0x0080) &&
                            IN_RANGE('A', vkey, 'Z');
                if(_fHbrCaps)
                    W32->ActivateKeyboard(ANSI_INDEX);
            }
        }

        if(vkey == VK_BACK && fShift && W32->OnWin9x())
        {
            // Shift+Backspace generates a LRM | RLM on a BiDi keyboard.
            // Consequently, we must eat the Backspace lest it delete text.
            W32->_fLRMorRLM = 1;
            return S_OK;
        }
    }

    // If dragging or Alt key down, just look for ESCAPE. Note: if Alt key is
    // down, we should never come here (would generate WM_SYSKEYDOWN message).
    if(_fMouseDown)
    {
        if(vkey == VK_ESCAPE)
        {
            // Turn-off autoscroll.
            if (StopMagellanScroll())
                return S_OK;

            POINT pt;
            // Cancel drag select or drag & drop
            GetCursorPos(&pt);
            OnTxLButtonUp(pt.x, pt.y, 0, LB_RELEASECAPTURE | LB_FLUSHNOTIFY);
            return S_OK;
        }
        return OnTxSpecialKeyDown(vkey, dwFlags, publdr);
    }

    CTxtSelection * const psel = GetSel();
    AssertSz(psel,"CTxtEdit::OnKeyDown() - No selection object !");

    if(fCtrl)
    {
        if(OnTxSpecialKeyDown(vkey, dwFlags, publdr) == S_OK)
            return S_OK;

        if(fAlt)                        // This following code doesn't handle
            return S_FALSE;             //  use Ctrl+Alt, which happens for
                                        //  AltGr codes (no WM_SYSKEYDOWN)

        // Shift must not be pressed for these.
        if(!fShift)
        {
            switch(vkey)
            {
            case 'E':
            case 'J':
            case 'R':
            case 'L':
            {
                CParaFormat PF;
                if (vkey == 'E')
                    PF._bAlignment = PFA_CENTER;
                else if (vkey == 'J')
                    PF._bAlignment = PFA_FULL_INTERWORD;
                else if (vkey == 'R')
                    PF._bAlignment = PFA_RIGHT;
                else
                    PF._bAlignment = PFA_LEFT;

                psel->SetParaFormat(&PF, publdr, PFM_ALIGNMENT);
                break;
            }
            case '1':
            case '2':
            case '5':
            {
                CParaFormat PF;
                PF._bLineSpacingRule = tomLineSpaceMultiple;
                PF._dyLineSpacing = (vkey - '0') * 20;
                if (vkey == '5')
                    PF._dyLineSpacing = 30;

                psel->SetParaFormat(&PF, publdr, PFM_LINESPACING);
                break;
            }
            default:
                break;
            }
        }

        switch(vkey)
        {
        case VK_TAB:
            return OnTxChar(VK_TAB, dwFlags, publdr);

        case VK_CLEAR:
        case VK_NUMPAD5:
        case 'A':                       // Ctrl-A => pselect all
            psel->SelectAll();
            break;

        //Toggle Subscript
        case 187: // =
        {
            ITextFont *pfont;
            psel->GetFont(&pfont);
            if (pfont)
            {
                pfont->SetSubscript(tomToggle);
                pfont->Release();
            }
        }
        break;

        case 'C':                       // Ctrl-C => copy
CtrlC:      CutOrCopySelection(WM_COPY, 0, 0, NULL);
            break;

        case 'V':                       // Ctrl-V => paste
CtrlV:      if(IsntProtectedOrReadOnly(WM_PASTE, 0, 0))
            {
                PasteDataObjectToRange(NULL, (CTxtRange *)psel, 0, NULL,
                    publdr, PDOR_NONE);
            }
            break;

        case 'X':                       // Ctrl-X => cut
CtrlX:      CutOrCopySelection(WM_CUT, 0, 0, publdr);
            break;

        case 'Z':                       // Ctrl-Z => undo
            if (_pundo && !_fReadOnly && _fUseUndo)
                PopAndExecuteAntiEvent(_pundo, 0);
            break;

        case 'Y':                       // Ctrl-Y => redo
            if(_predo && !_fReadOnly && _fUseUndo)
                PopAndExecuteAntiEvent(_predo, 0);
            break;

#ifdef DEBUG
            void RicheditDebugCentral(void);
        case 191:
            RicheditDebugCentral();
            break;
#endif

#if defined(DOGFOOD)
        case '1':                       // Shift+Ctrl+1 => start Aimm
            // Activate AIMM by posting a message to RE (Shift+Ctrl+; for now)
            if (fShift && _fInOurHost)
            {
                HWND    hWnd;

                TxGetWindow( &hWnd );

                if (hWnd)
                    PostMessage(hWnd, EM_SETEDITSTYLE, SES_USEAIMM, SES_USEAIMM);
            }
            break;
#endif

        case VK_CONTROL:
            goto cont;

// English keyboard defines
#define VK_APOSTROPHE   0xDE
#define VK_GRAVE        0xC0
#define VK_SEMICOLON    0xBA
#define VK_COMMA        0xBC

        case VK_APOSTROPHE:
            if(fShift)
                g_wFlags ^= KF_SMARTQUOTES;
            else
                nDeadKey = ACCENT_ACUTE;
            break;

        case VK_GRAVE:
            nDeadKey = fShift ? ACCENT_TILDE : ACCENT_GRAVE;
            break;

        case VK_SEMICOLON:
            nDeadKey = ACCENT_UMLAUT;
            break;

        case '6':
            if(!fShift)
                goto cont;
            nDeadKey = ACCENT_CARET;
            break;

        case VK_COMMA:
            nDeadKey = ACCENT_CEDILLA;
            break;

        default:
            goto cont;
        }
        if(nDeadKey)
        {
            // Since deadkey choices vary a bit according to keyboard, we
            // only enable them for English. French, German, Italian, and
            // Spanish keyboards already have a fair amount of accent
            // capability.
            if(PRIMARYLANGID(GetKeyboardLayout(0)) == LANG_ENGLISH)
                SetDeadKey((WORD)nDeadKey);
            else goto cont;
        }
        return S_OK;
    }

cont:
    psel->SetExtend(fShift);

    switch(vkey)
    {
    case VK_BACK:
    case VK_F16:
        if(_fReadOnly)
        {
            Beep();
            fRet = TRUE;
        }
        else if(IsntProtectedOrReadOnly(WM_KEYDOWN, VK_BACK, dwFlags))
        {
            fRet = psel->Backspace(fCtrl, publdr);
        }
        break;

    case VK_INSERT:                             // Ins
        if(fShift)                              // Shift-Ins
            goto CtrlV;                         // Alias for Ctrl-V
        if(fCtrl)                               // Ctrl-Ins
            goto CtrlC;                         // Alias for Ctrl-C

        if(!_fReadOnly)                         // Ins
            _fOverstrike = !_fOverstrike;       // Toggle Ins/Ovr
        fRet = TRUE;
        break;

    case VK_LEFT:                               // Left arrow
    case VK_RIGHT:                              // Right arrow
        fRet = (vkey == VK_LEFT) ^ (psel->GetPF()->IsRtlPara() != 0)
             ? psel->Left (fCtrl)
             : psel->Right(fCtrl);
        break;

    case VK_UP:                                 // Up arrow
        fRet = psel->Up(fCtrl);
        break;

    case VK_DOWN:                               // Down arrow
        fRet = psel->Down(fCtrl);
        break;

    case VK_HOME:                               // Home
        fRet = psel->Home(fCtrl);
        break;

    case VK_END:                                // End
        fRet = psel->End(fCtrl);
        break;

    case VK_PRIOR:                              // PgUp
        // If SystemEditMode and control is single-line, do nothing
        if(!_fSystemEditMode || _pdp->IsMultiLine())
            fRet = psel->PageUp(fCtrl);
        break;

    case VK_NEXT:                               // PgDn
        // If SystemEditMode and control is single-line, do nothing
        if(!_fSystemEditMode || _pdp->IsMultiLine())
            fRet = psel->PageDown(fCtrl);
        break;

    case VK_DELETE:                             // Del
        if(fShift)                              // Shift-Del
            goto CtrlX;                         // Alias for Ctrl-X

        if(IsntProtectedOrReadOnly(WM_KEYDOWN, VK_DELETE, dwFlags))
            psel->Delete(fCtrl, publdr);
        fRet = TRUE;
        break;

    case CONTROL('J'):                          // Ctrl-Return gives Ctrl-J
    case VK_RETURN:                             //  (LF), treat it as return
        // If we are in 1.0 mode we need to handle <CR>'s on WM_CHAR
        if (!Get10Mode())
        {
            if(!_pdp->IsMultiLine())
            {
                Beep();
                return S_FALSE;
            }
            TxSetCursor(0, NULL);

            if(IsntProtectedOrReadOnly(WM_CHAR, VK_RETURN, dwFlags))
                psel->InsertEOP(publdr, (fShift && IsRich() ? VT : 0));

            fRet = TRUE;
        }
        break;

    default:
        return S_FALSE;
    }

    return fRet ? S_OK : S_MSG_KEY_IGNORED;
}

/*
 *  CTxtEdit::CutOrCopySelection(msg, wparam, lparam, publdr)
 *
 *  @mfunc
 *      Handle WM_COPY message and its keyboard hotkey aliases
 *
 *  @rdesc
 *      HRESULT
 */
HRESULT CTxtEdit::CutOrCopySelection(
    UINT   msg,             //@parm Message (WM_CUT or WM_COPY)
    WPARAM wparam,          //@parm Message wparam for protection check
    LPARAM lparam,          //@parm Message lparam for protection check
    IUndoBuilder *publdr)   //@parm Undobuilder to receive antievents
{
    Assert(msg == WM_CUT || msg == WM_COPY);

    if(!_fUsePassword && IsntProtectedOrReadOnly(msg, wparam, lparam))
    {
        CTxtSelection *psel = GetSel();
        psel->CheckTableSelection();
        return msg == WM_COPY
               ? _ldte.CopyRangeToClipboard((CTxtRange *)psel)
               : _ldte.CutRangeToClipboard((CTxtRange *)psel, publdr);
    }
    return NOERROR;
}

#define ENGLISH_UK	 MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_UK)
#define ENGLISH_EIRE MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_EIRE)

/*
 *  CTxtEdit::OnTxSpecialKeyDown(vkey, dwFlags, publdr)
 *
 *  @mfunc
 *      Handle WM_KEYDOWN message for outline mode
 *
 *  @rdesc
 *      HRESULT with the following values:
 *
 *      S_OK                if key was understood and consumed
 *      S_MSG_KEY_IGNORED   if key was understood, but not consumed
 *      S_FALSE             if key was not understood (and not consumed)
 */
HRESULT CTxtEdit::OnTxSpecialKeyDown(
    WORD          vkey,             //@parm Virtual key code
    DWORD         dwFlags,          //@parm lparam of WM_KEYDOWN msg
    IUndoBuilder *publdr)           //@parm Undobuilder to receive antievents
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxSpecialKeyDown");

    HRESULT hr = S_FALSE;                   // Key not understood yet
    DWORD   dwKbdFlags = GetKeyboardFlags();
    BOOL    fUpdateFormat = TRUE;

    if(!(dwKbdFlags & (CTRL | ALT)))        // All hot keys here have at
        return S_FALSE;                     //  least Ctrl or Alt

    CTxtSelection * const psel = GetSel();
    if(dwKbdFlags & ALT && dwKbdFlags & CTRL)
    {
        // AltGr generates LCTRL | RALT, so don't match hot keys with
        // that combination
        if(dwKbdFlags & LCTRL && dwKbdFlags & RALT)
            return S_FALSE;

//#if 0
        // First they say they want it, then they don't. Leave it ifdef'd out
        // for a bit in case they want it again
        if(vkey == 'E')
        {
			LANGID lid = LANGIDFROMLCID(GetKeyboardLayout(0));
			static const LANGID rgLangID[] =
			{
				ENGLISH_UK, ENGLISH_EIRE, LANG_POLISH, LANG_PORTUGUESE,
				LANG_HUNGARIAN, LANG_VIETNAMESE
			};
			for(LONG i = ARRAY_SIZE(rgLangID); i--; )
			{
				// Don't insert Euro if lid matches any LIDs or PLIDs in rgLangID
				if(lid == rgLangID[i] || PRIMARYLANGID(lid) == rgLangID[i])
					return S_FALSE;
			}
            if(psel->PutChar(EURO, _fOverstrike, publdr))
			{
				SetKeyboardFlag(HOTEURO);		// Setup flag to eat the next WM_CHAR w/ EURO
                hr = S_OK;
			}
        }
        else
//#endif
        if(dwKbdFlags & SHIFT)
            switch(vkey)
            {
#ifdef ENABLE_OUTLINEVIEW
            // FUTURE: OutlineView hot keys postponed (see below)
            case 'N':                       // Alt-Ctrl-N => Normal View
                hr = SetViewKind(VM_NORMAL);
                break;
            case 'O':                       // Alt-Ctrl-O => Outline View
                hr = SetViewKind(VM_OUTLINE);
                break;
#endif
            case VK_F12:                    // Alt-Ctrl-F12 (in case Alt-X taken)
                hr = psel->HexToUnicode(publdr);
                break;

    #if defined(DEBUG)
            case VK_F11:                    // Alt-Ctrl-F11
                if (W32->fDebugFont())
                    psel->DebugFont();
                break;
    #endif
            }
        return hr;
    }

    AssertSz(psel, "CTxtEdit::OnTxSpecialKeyDown() - No selection object !");
    CTxtRange rg(*psel);

    if(!IsRich() || !_pdp->IsMultiLine() || !(dwKbdFlags & SHIFT))
        return S_FALSE;

    if(dwKbdFlags & ALT)                            // Alt+Shift hot keys
    {
        // NB: Alt and Shift-Alt with _graphics_ characters generate a
        // WM_SYSCHAR, which see

#ifdef ENABLE_OUTLINEVIEW
        // FUTURE: These are Outline related hot keys.  We will postpone these features
        // since we have several bugs related to these hot keys
        // Bug 5687, 5689, & 5691
        switch(vkey)
        {
        case VK_LEFT:                               // Left arrow
        case VK_RIGHT:                              // Right arrow
            hr = rg.Promote(vkey == VK_LEFT ? 1 : -1, publdr);
            psel->Update_iFormat(-1);
            psel->Update(FALSE);
            break;

        case VK_UP:                                 // Up arrow
        case VK_DOWN:                               // Down arrow
            hr = MoveSelection(vkey == VK_UP ? -1 : 1, publdr);
            psel->Update(TRUE);
            break;
        }
#endif
        return hr;
    }

    Assert(dwKbdFlags & CTRL && dwKbdFlags & SHIFT);

    // Ctrl+Shift hot keys
    switch(vkey)
    {

#ifdef ENABLE_OUTLINEVIEW
    // FUTUTRE: These are Outline related hot keys.  We will postpone these features
    // since we have several bugs related to these hot keys
    // Bug 5687, 5689, & 5691
    case 'N':                       // Demote to Body
        hr = rg.Promote(0, publdr);
        break;
#endif

    //Toggle superscript
    case 187: // =
    {
        ITextFont *pfont;
        psel->GetFont(&pfont);
        if (pfont)
        {
            pfont->SetSuperscript(tomToggle);
            pfont->Release();
            hr = S_OK;
            fUpdateFormat = FALSE;
        }
        break;
    }

    case 'A':
    {
        ITextFont *pfont;
        psel->GetFont(&pfont);
        if (pfont)
        {
            pfont->SetAllCaps(tomToggle);
            pfont->Release();
            hr = S_OK;
            fUpdateFormat = FALSE;
        }
        break;
    }

    case 'L':                       // Fiddle bullet style
    {
        CParaFormat PF;
        DWORD dwMask = PFM_NUMBERING | PFM_OFFSET;

        PF._wNumbering = psel->GetPF()->_wNumbering + 1;
        PF._wNumbering %= tomListNumberAsUCRoman + 1;
        PF._dxOffset = 0;
        if(PF._wNumbering)
        {
            dwMask |= PFM_NUMBERINGSTYLE | PFM_NUMBERINGSTART;
            PF._wNumberingStyle = PFNS_PERIOD;
            PF._wNumberingStart = 1;
            PF._dxOffset = 360;
        }
        hr = psel->SetParaFormat(&PF, publdr, dwMask);
        break;
    }
#define VK_RANGLE   190
#define VK_LANGLE   188

    case VK_RANGLE:                 // '>' on US keyboards
    case VK_LANGLE:                 // '<' on US keyboards
        hr = OnSetFontSize(vkey == VK_RANGLE ? 1 : -1, publdr)
           ? S_OK : S_FALSE;
        fUpdateFormat = (hr == S_FALSE);
        break;
    }

    if(hr != S_FALSE)
    {
        if (fUpdateFormat)
            psel->Update_iFormat(-1);
        psel->Update(FALSE);
    }
    return hr;
}

/*
 *  CTxtEdit::OnTxChar (vkey, dwFlags, publdr)
 *
 *  @mfunc
 *      Handle WM_CHAR message
 *
 *  @rdesc
 *      HRESULT with the following values:
 *
 *      S_OK                if key was understood and consumed
 *      S_MSG_KEY_IGNORED   if key was understood, but not consumed
 *      S_FALSE             if key was not understood (and not consumed)
 */
HRESULT CTxtEdit::OnTxChar(
    WORD          vkey,     //@parm Translated key code
    DWORD         dwFlags,  //@parm lparam of WM_KEYDOWN msg
    IUndoBuilder *publdr)   //@parm Undobuilder to receive antievents
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxChar");

    // Reset Alt key state if needed
    if (!(HIWORD(dwFlags) & KF_ALTDOWN))
        ResetKeyboardFlag(ALT);

    DWORD dwFlagsPutChar = _fOverstrike | KBD_CHAR;
    if(GetKeyboardFlags() & ALTNUMPAD)
    {
        DWORD Number = GetKeyPadNumber();
        if(Number >= 256 || vkey >= 256)
            vkey = Number;
        ResetKeyboardFlag(ALTNUMPAD | ALT0);
        dwFlagsPutChar &= ~KBD_CHAR;        // Need font binding
    }

    if (_fMouseDown || vkey == VK_ESCAPE || // Ctrl-Backspace generates VK_F16
        vkey == VK_BACK || vkey==VK_F16)    // Eat it since we process it
    {                                       //  in WM_KEYDOWN
        return S_OK;
    }

    CTxtSelection * const psel = GetSel();
    AssertSz(psel,
        "CTxtEdit::OnChar() - No selection object !");
    psel->SetExtend(FALSE);                 // Shift doesn't mean extend for
                                            //  WM_CHAR
    if(_fReadOnly && vkey != 3)             // Don't allow input if read only,
    {                                       //  but allow copy (Ctrl-C)
        if(vkey >= ' ')
            Beep();
        return S_MSG_KEY_IGNORED;
    }

    if(vkey >= ' ' || vkey == VK_TAB)
    {
        TxSetCursor(0, NULL);
        if(IsntProtectedOrReadOnly(WM_CHAR, vkey, dwFlags))
        {
            LONG nDeadKey = GetDeadKey();
            if(nDeadKey)
            {
                LONG ch       = vkey | 0x20;        // Convert to lower case
                BOOL fShift   = vkey != ch;         //  (if ASCII letter)
                //                             a   b    c   d    e   f  g  h    i   j
                const static WORD chOff[] = {0xDF, 0, 0xE7, 0, 0xE7, 0, 0, 0, 0xEB, 0,
                //                      k  l  m    n     o   p  q  r  s  t    u
                                        0, 0, 0, 0xF1, 0xF1, 0, 0, 0, 0, 0, 0xF8};
                SetDeadKey(0);
                if(!IN_RANGE('a', ch, 'u'))         // Not relevant ASCII
                    return S_OK;                    //  letter

                vkey = chOff[ch - 'a'];             // Translate to base char
                if(!vkey)                           // No accents available
                    return S_OK;                    //  in current approach

                if(ch == 'n')
                {
                    if(nDeadKey != ACCENT_TILDE)
                        return S_OK;
                }
                else if(nDeadKey == ACCENT_CEDILLA)
                {
                    if(ch != 'c')
                        return S_OK;
                }
                else                                // aeiou
                {
                    vkey += (WORD)nDeadKey;
                    if (nDeadKey >= ACCENT_TILDE && // eiu with ~ or :
                        (vkey == 0xF0 || vkey & 8))
                    {
                        if(nDeadKey != ACCENT_UMLAUT)// Only have umlauts
                            return S_OK;
                        vkey--;
                    }
                }
                if(fShift)
                    vkey &= ~0x20;
            }

            // need to check if character is LRM | RLM character, if so
            // then convert vkey
            if (W32->_fLRMorRLM && IsBiDi() && IN_RANGE(0xFD, vkey, 0xFE))
            {
                vkey = LTRMARK + (vkey - 0xFD);
            }

            psel->PutChar((TCHAR)vkey, dwFlagsPutChar, publdr);
        }
    }
    else if (Get10Mode() && (vkey == VK_RETURN || vkey == CONTROL('J')))
    {
        // 1.0 handled <CR> on WM_CHAR

        // Just make sure we are entering text into a multiline control
        DWORD dwStyle;
        GetHost()->TxGetPropertyBits(TXTBIT_MULTILINE, &dwStyle);
        if(dwStyle & TXTBIT_MULTILINE)
        {
            TxSetCursor(0, NULL);
            if(IsntProtectedOrReadOnly(WM_CHAR, VK_RETURN, dwFlags))
                psel->InsertEOP(publdr);
        }
    }

    if(_fHbrCaps)
    {
         W32->ActivateKeyboard(HEBREW_INDEX);
         _fHbrCaps = FALSE;
    }
    return S_OK;
}

/*
 *  CTxtEdit::OnTxSysChar (vkey, dwFlags, publdr)
 *
 *  @mfunc
 *      Handle WM_SYSCHAR message
 *
 *  @rdesc
 *      HRESULT with the following values:
 *
 *      S_OK                if key was understood and consumed
 *      S_MSG_KEY_IGNORED   if key was understood, but not consumed
 *      S_FALSE             if key was not understood (and not consumed)
 */
HRESULT CTxtEdit::OnTxSysChar(
    WORD          vkey,     //@parm Translated key code
    DWORD         dwFlags,  //@parm lparam of WM_KEYDOWN msg
    IUndoBuilder *publdr)   //@parm Undobuilder to receive antievents
{
    if(!(HIWORD(dwFlags) & KF_ALTDOWN))
        return S_FALSE;

    BOOL    fWholeDoc = TRUE;
    HRESULT hr = S_FALSE;
    int     level = 0;
    CTxtSelection * const psel = GetSel();

    switch(vkey)
    {
    case VK_BACK:
        return S_OK;

    case 'x':
        hr = psel->HexToUnicode(publdr);
        break;

    case 'X':
        hr = psel->UnicodeToHex(publdr);
        break;

    case '+':
    case '-':
        level = vkey == VK_ADD ? 1 : -1;
        fWholeDoc = FALSE;
        /* Fall through */
    case 'A':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
        {
            CTxtRange rg(*psel);
            if(!level)
                level = vkey == 'A' ? 9 : vkey - '0';
            return rg.ExpandOutline(level, fWholeDoc);
        }
    }
    return hr;
}

HRESULT CTxtEdit::OnTxSysKeyDown(
    WORD          vkey,             //@parm Virtual key code
    DWORD         dwFlags,          //@parm lparam of WM_KEYDOWN msg
    IUndoBuilder *publdr)           //@parm Undobuilder to receive antievents
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxSysKeyDown");


    if(IN_RANGE(VK_SHIFT, vkey, VK_MENU))
    {
        SetKeyboardFlag(GetKbdFlags(vkey, dwFlags));
        SetKeyPadNumber(0);             // Init keypad number to 0
        return S_FALSE;
    }

    if (StopMagellanScroll())
        return S_FALSE;

    HRESULT hr = OnTxSpecialKeyDown(vkey, dwFlags, publdr);
    if(hr != S_FALSE)
        return hr;

    if(vkey == VK_BACK && (HIWORD(dwFlags) & KF_ALTDOWN))
    {
        if(_pundo && _pundo->CanUndo() && _fUseUndo)
        {
            if(PopAndExecuteAntiEvent(_pundo, 0) != NOERROR)
                hr = S_MSG_KEY_IGNORED;
        }
        else
            Beep();
    }
    else if(vkey == VK_F10 &&                   // F10
            !(HIWORD(dwFlags) & KF_REPEAT) &&   // Key previously up
            (GetKeyboardFlags() & SHIFT))       // Shift is down
    {
        HandleKbdContextMenu();
    }

    return hr;
}

/////////////////////////////// Other system events //////////////////////////////

HRESULT CTxtEdit::OnContextMenu(LPARAM lparam)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnContextMenu");

    POINT pt;

    pt.x = LOWORD(lparam);
    pt.y = HIWORD(lparam);

    if(TxScreenToClient(&pt))
        return OnTxRButtonUp(pt.x, pt.y, 0, RB_NOSELCHECK);

    return S_FALSE;
}

/*
 *  CTxtEdit::HandleKbdContextMenu ()
 *
 *  @mfunc  decides where to put the context menu on the basis of where the
 *          the selection is.  Useful for shift-F10 and VK_APPS, where
 *          we aren't given a location.
 */
void CTxtEdit::HandleKbdContextMenu()
{
    POINT   pt;
    RECT    rc;
    const CTxtSelection * const psel = GetSel();
    int RbOption = RB_DEFAULT;

    // Figure out where selection ends and put context menu near it
    if(_pdp->PointFromTp(*psel, NULL, FALSE, pt, NULL, TA_TOP) < 0)
        return;

    // Due to various factors, the result of PointFromTp doesn't land
    // in the selection in PointInSel. Therefore, we send in an override
    // here if the selection is non-degenerate and to force the result
    // and thus have the correct context menu appear.

    LONG cpMin;
    LONG cpMost;
    psel->GetRange(cpMin, cpMost);

    if (cpMin != cpMost)
    {
        RbOption = RB_FORCEINSEL;
    }

    // Make sure point is still within bounds of edit control
    _pdp->GetViewRect(rc);

    if (pt.x < rc.left)
        pt.x = rc.left;
    if (pt.x > rc.right - 2)
        pt.x = rc.right - 2;
    if (pt.y < rc.top)
        pt.y = rc.top;
    if (pt.y > rc.bottom - 2)
        pt.y = rc.bottom - 2;

    OnTxRButtonUp(pt.x, pt.y, 0, RbOption);
}


/////////////////////////////// Format Range Commands //////////////////////////////

/*
 *  CTxtEdit::OnFormatRange (pfr, prtcon, hdcMeasure,
 *                           xMeasurePerInch, yMeasurePerInch)
 *  @mfunc
 *      Format the range given by pfr
 *
 *  @comm
 *      This function inputs API cp's that may differ from the
 *      corresponding internal Unicode cp's.
 */
LRESULT CTxtEdit::OnFormatRange(
    FORMATRANGE * pfr,
    SPrintControl prtcon,
    BOOL          fSetupDC)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnFormatRange");

    LONG cpMin  = 0;
    LONG cpMost = 0;

    if(pfr)
    {
        cpMin  = GetCpFromAcp(pfr->chrg.cpMin);
        cpMost = GetCpFromAcp(pfr->chrg.cpMost);
    }
    // Even if there is 0 text, we want to print the control so that it will
    // fill the control with background color.
    // Use Adjusted Text Length.  Embedded objects using RichEdit will get the empty
    // document they expect and will create a default size document.
    if(!pfr || cpMin >= GetAdjustedTextLength() &&
        !prtcon._fPrintFromDraw)
    {   // We're done formatting, get rid of our printer's display context.
        delete _pdpPrinter;
        _pdpPrinter = NULL;

        return GetAcpFromCp(GetAdjustedTextLength());
    }

    LONG cpReturn = -1;
    BOOL fSetDCWorked = FALSE;


    // Fix MFC Print preview in mirrored control
    //
    // MFC CPreviewView sends us a mirrored rendering DC. We need to disable
    // this mirroring effect so our internal state remains consistent with user
    // action. We also need to disable mirrored window mode in CPreviewView
    // window. [wchao - 4/9/1999]
    //

    HDC  hdcLocal = pfr->hdc;
    DWORD dwLayout = GetLayout(hdcLocal);

    if (dwLayout & LAYOUT_RTL)
    {
        HWND hwndView = WindowFromDC(hdcLocal);

        if (hwndView)
        {
            DWORD   dwExStyleView = GetWindowLong(hwndView, GWL_EXSTYLE);

            if (dwExStyleView & WS_EX_LAYOUTRTL)
                SetWindowLong(hwndView, GWL_EXSTYLE, dwExStyleView & ~WS_EX_LAYOUTRTL);
        }

        SetLayout(hdcLocal, 0);
    }

    // First time in with this printer, set up a new display context.
    // IMPORTANT: proper completion of the printing process is required
    // to dispose of this context and begin a new context.
    // This is implicitly done by printing the last character, or
    // sending an EM_FORMATRANGE message with pfr equal to NULL.
    if(!_pdpPrinter)
    {
        _pdpPrinter = new CDisplayPrinter (this, hdcLocal,
                pfr->rc.right  - pfr->rc.left,  // x width  max
                pfr->rc.bottom - pfr->rc.top,   // y height max
                prtcon);

        _pdpPrinter->Init();

        _pdpPrinter->SetWordWrap(TRUE);
        // Future: (ricksa) This is a really yucky way to pass the draw info
        // to the printer but it was quick. We want to make this better.
        _pdpPrinter->ResetDrawInfo(_pdp);

        // Set temporary zoom factor (if there is one).
        _pdpPrinter->SetTempZoomDenominator(_pdp->GetTempZoomDenominator());
    }
    else
        _pdpPrinter->SetPrintDimensions(&pfr->rc);

	LONG dxpInch = 0, dypInch = 0;
    // We set the DC everytime because it could have changed.
    if(GetDeviceCaps(hdcLocal, TECHNOLOGY) != DT_METAFILE)
    {
        // This is not a metafile so do the normal thing
        fSetDCWorked = _pdpPrinter->SetDC(hdcLocal);
    }
    else
    {
        //Forms^3 draws using screen resolution, while OLE specifies HIMETRIC
        dxpInch = fInOurHost() ? 2540 : W32->GetXPerInchScreenDC();
        dypInch = fInOurHost() ? 2540 : W32->GetYPerInchScreenDC();

        if (!fSetupDC)
        {
            RECT rc;
            rc.left = MulDiv(pfr->rcPage.left, dxpInch, LX_PER_INCH);
            rc.right = MulDiv(pfr->rcPage.right, dxpInch, LX_PER_INCH);
            rc.top = MulDiv(pfr->rcPage.top, dypInch, LY_PER_INCH);
            rc.bottom = MulDiv(pfr->rcPage.bottom, dypInch, LY_PER_INCH);

            SetWindowOrgEx(hdcLocal, rc.left, rc.top, NULL);
            SetWindowExtEx(hdcLocal, rc.right, rc.bottom, NULL);
        }

        _pdpPrinter->SetMetafileDC(hdcLocal, dxpInch, dypInch);
        fSetDCWorked = TRUE;
    }

    if(fSetDCWorked)
    {
		//It is illogical to have the target device be the screen and the presentation
		//device be a HIMETRIC metafile.
		LONG dxpInchT = -1, dypInchT = -1;
		if (dxpInch && GetDeviceCaps(pfr->hdcTarget, TECHNOLOGY) == DT_RASDISPLAY)
		{
			dxpInchT = dxpInch;
			dypInchT = dypInch;
		}

        // We set this every time because it could have changed.
        if(_pdpPrinter->SetTargetDC(pfr->hdcTarget, dxpInchT, dypInchT))
        {

            // Format another, single page worth of text.
            cpReturn = _pdpPrinter->FormatRange(cpMin, cpMost, prtcon._fDoPrint);
            if(!prtcon._fPrintFromDraw)
            {
                // After formatting, we know where the bottom is. But we only
                // want to set this if we are writing a page rather than
                // displaying a control on the printer.
                pfr->rc.bottom = INT (pfr->rc.top + _pdpPrinter->DYtoLY(_pdpPrinter->GetHeight()));
            }

            // Remember this in case the host wishes to do its own banding.
            _pdpPrinter->SetPrintView(pfr->rc); // we need to save this for OnDisplayBand.
            _pdpPrinter->SetPrintPage(pfr->rcPage);

            // If we're asked to render, then render the entire page in one go.
            if(prtcon._fDoPrint && (cpReturn > 0 || prtcon._fPrintFromDraw))
            {
                OnDisplayBand(&pfr->rc, prtcon._fPrintFromDraw);

                // Note: we can no longer call OnDisplayBand without reformating.
                _pdpPrinter->Clear(AF_DELETEMEM);
            }
        }
    }

    return cpReturn > 0 ? GetAcpFromCp(cpReturn) : cpReturn;
}

BOOL CTxtEdit::OnDisplayBand(
    const RECT *prc,
    BOOL        fPrintFromDraw)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnDisplayBand");

    HDC     hdcPrinter;
    RECT    rc, rcPrint;

    // Make sure OnFormatRange was called and that it actually rendered something.
    if(!_pdpPrinter || !_pdpPrinter->Count())
        return FALSE;

    // Review (murrays): shouldn't the following use LRtoDR()? I.e.,
    // _pdpPrinter->LRtoDR(rc, *prc);
    // Proportionally map to printers extents.
    rc.left     = (INT) _pdpPrinter->LXtoDX(prc->left);
    rc.right    = (INT) _pdpPrinter->LXtoDX(prc->right);
    rc.top      = (INT) _pdpPrinter->LYtoDY(prc->top);
    rc.bottom   = (INT) _pdpPrinter->LYtoDY(prc->bottom);

    rcPrint         = _pdpPrinter->GetPrintView();
    rcPrint.left    = (INT) _pdpPrinter->LXtoDX(rcPrint.left);
    rcPrint.right   = (INT) _pdpPrinter->LXtoDX(rcPrint.right);
    rcPrint.top     = (INT) _pdpPrinter->LYtoDY(rcPrint.top);
    rcPrint.bottom  = (INT) _pdpPrinter->LYtoDY(rcPrint.bottom);

    // Get printer DC because we use it below.
    hdcPrinter = _pdpPrinter->GetDC();

    if(fPrintFromDraw)
    {
        // We need to take view inset into account
        _pdpPrinter->GetViewRect(rcPrint, &rcPrint);
    }

    // Render this band (if there's something to render)
    if(rc.top < rc.bottom)
        _pdpPrinter->Render(rcPrint, rc);

    return TRUE;
}

//////////////////////////////// Protected ranges //////////////////////////////////
/*
 *  CTxtEdit::IsProtected (msg, wparam, lparam)
 *
 *  @mfunc
 *      Find out if selection is protected
 *
 *  @rdesc
 *      TRUE iff 1) control is read-only or 2) selection is protected and
 *      parent query says to protect
 */
BOOL CTxtEdit::IsProtected(
    UINT    msg,        //@parm Message id
    WPARAM  wparam,     //@parm WPARAM from window's message
    LPARAM  lparam)     //@parm LPARAM from window's message
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::IsProtected");

    LONG iDirection = 0;
    CTxtSelection *psel = GetSel();

    if(!psel)
        return FALSE;

    // There are a few special cases to consider, namely backspacing
    // into a protected range, deleting into a protected range, and type
    // with overstrike into a protected range.
    if(msg == WM_KEYDOWN && (wparam == VK_BACK || wparam == VK_F16))
    {
        // Check for format behind selection, if we are trying to
        // backspace an insertion point.
        iDirection = -1;
    }
    else if(msg == WM_KEYDOWN && wparam == VK_DELETE ||
        _fOverstrike && msg == WM_CHAR)
    {
        iDirection = 1;
    }

    // HACK ALERT: we don't do fIsDBCS protection checking for EM_REPLACESEL,
    // EM_SETCHARFORMAT, or EM_SETPARAFORMAT.  Outlook uses these APIs
    // extensively and DBCS protection checking messes them up. N.B. the
    // following if statement assumes that IsProtected returns a tri-value.
    int iProt = psel->IsProtected(iDirection);
    if (iProt == CTxtRange::PROTECTED_YES && msg != EM_REPLACESEL &&
        msg != EM_SETCHARFORMAT && msg != EM_SETPARAFORMAT ||
        iProt == CTxtRange::PROTECTED_ASK && _dwEventMask & ENM_PROTECTED &&
        QueryUseProtection(psel, msg, wparam, lparam))
    {
        return TRUE;
    }
    return FALSE;
}

/*
 *  CTxtEdit::IsntProtectedOrReadOnly (msg, wparam, lparam)
 *
 *  @mfunc
 *      Find out if selection isn't protected or read only. If it is,
 *      ring bell.  For msg = WM_COPY, only protection is checked.
 *
 *  @rdesc
 *      TRUE iff 1) control isn't read-only and 2) selection either isn't
 *      protected or parent query says not to protect
 *
 *  @devnote    This function is useful for UI operations (like typing).
 */
BOOL CTxtEdit::IsntProtectedOrReadOnly(
    UINT   msg,     //@parm Message
    WPARAM wparam,  //@parm Corresponding wparam
    LPARAM lparam)  //@parm Corresponding lparam
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::IsProtectedOrReadOnly");

    if (!IsProtected(msg, wparam, lparam) &&
        (msg == WM_COPY || !_fReadOnly))    // WM_COPY only cares about
    {                                       //  protection
        return TRUE;
    }
    Beep();
    return FALSE;
}

/*
 *  CTxtEdit::IsProtectedRange (msg, wparam, lparam, prg)
 *
 *  @mfunc
 *      Find out if range prg is protected
 *
 *  @rdesc
 *      TRUE iff control is read-only or range is protected and parent
 *      query says to protect
 */
BOOL CTxtEdit::IsProtectedRange(
    UINT        msg,        //@parm Message id
    WPARAM      wparam,     //@parm WPARAM from window's message
    LPARAM      lparam,     //@parm LPARAM from window's message
    CTxtRange * prg)        //@parm Range to examine
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::IsProtectedRange");

    int iProt = prg->IsProtected(0);

    if (iProt == CTxtRange::PROTECTED_YES ||
        (iProt == CTxtRange::PROTECTED_ASK &&
         (_dwEventMask & ENM_PROTECTED) &&
         QueryUseProtection(prg, msg, wparam, lparam)))
    // N.B.  the preceding if statement assumes that IsProtected returns a tri-value
    {
        return TRUE;
    }
    return FALSE;
}

/*
 *  RegisterTypeLibrary
 *
 *  @mfunc
 *      Auxiliary function to ensure the type library is registered if Idispatch is used.
 */
void RegisterTypeLibrary( void )
{
    HRESULT  hRes = NOERROR;
    WCHAR    szModulePath[MAX_PATH];
    ITypeLib *pTypeLib = NULL;

    // Obtain the path to this module's executable file
    W32->GetModuleFileName( hinstRE, szModulePath, MAX_PATH );

    // Load and register the type library resource
    if (LoadRegTypeLib(LIBID_tom, 1, 0, LANG_NEUTRAL, &pTypeLib) != NOERROR)
    {
        hRes = W32->LoadTypeLibEx(szModulePath, REGKIND_REGISTER, &pTypeLib);
    }

    if(SUCCEEDED(hRes) && pTypeLib)
    {
        pTypeLib->Release();
    }
}

/////////////////////////////// Private IUnknown //////////////////////////////

HRESULT __stdcall CTxtEdit::CUnknown::QueryInterface(
    REFIID riid,
    void **ppvObj)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CUnknown::QueryInterface");

    CTxtEdit *ped = (CTxtEdit *)GETPPARENT(this, CTxtEdit, _unk);
    *ppvObj = NULL;

    if(IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_ITextServices))
        *ppvObj = (ITextServices *)ped;

    else if(IsEqualIID(riid, IID_IDispatch))
    {
        *ppvObj = (IDispatch *)ped;
        RegisterTypeLibrary();
    }

    else if(IsEqualIID(riid, IID_ITextDocument))
    {
        *ppvObj = (ITextDocument *)ped;
        RegisterTypeLibrary();
    }

    else if(IsEqualIID(riid, IID_ITextDocument2))
        *ppvObj = (ITextDocument2 *)ped;

    else if(IsEqualIID(riid, IID_IRichEditOle))
        *ppvObj = (IRichEditOle *)ped;

    else if(IsEqualIID(riid, IID_IRichEditOleCallback))
    {
        // NB!! Returning this pointer in our QI is
        // phenomenally bogus; it breaks fundamental COM
        // identity rules (granted, not many understand them!).
        // Anyway, RichEdit 1.0 did this, so we better.
        TRACEWARNSZ("Returning IRichEditOleCallback interface, COM "
            "identity rules broken!");

        *ppvObj = ped->GetRECallback();
    }

    if(*ppvObj)
    {
        ((IUnknown *) *ppvObj)->AddRef();
        return S_OK;
    }
    return E_NOINTERFACE;
}

ULONG __stdcall CTxtEdit::CUnknown::AddRef()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CUnknown::AddRef");

    return ++_cRefs;
}

ULONG __stdcall CTxtEdit::CUnknown::Release()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CUnknown::Release");

    // the call manager will take care of deleting our instance if appropriate.
    CTxtEdit *ped = GETPPARENT(this, CTxtEdit, _unk);
    CCallMgr callmgr(ped);

    ULONG culRefs = --_cRefs;

    if(culRefs == 0)
    {
        // Even though we don't delete ourselves now, dump the callback
        // if we have it.  This make implementation a bit easier on clients.

        if(ped->_pobjmgr)
            ped->_pobjmgr->SetRECallback(NULL);

        // Make sure our timers are gone
        ped->TxKillTimer(RETID_AUTOSCROLL);
        ped->TxKillTimer(RETID_DRAGDROP);
        ped->TxKillTimer(RETID_BGND_RECALC);
        ped->TxKillTimer(RETID_SMOOTHSCROLL);
        ped->TxKillTimer(RETID_MAGELLANTRACK);
    }
    return culRefs;
}

/*
 *  ValidateTextRange(pstrg)
 *
 *  @func
 *    Makes sure that an input text range structure makes sense.
 *
 *  @rdesc
 *    Size of the buffer required to accept copy of data or -1 if all the
 *    data in the control is requested.
 *
 *  @comm
 *    This is used both in this file and in the RichEditANSIWndProc
 */
LONG ValidateTextRange(
    TEXTRANGE *pstrg)       //@parm pointer to a text range structure
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "ValidateTextRange");

    // Validate that the input structure makes sense. In the first
    // place it must be big enough. Secondly, the values must sense.
    // Remember that if the cpMost field is -1 and the cpMin field
    // is 0 this means that the call wants the entire buffer.
    if (IsBadReadPtr(pstrg, sizeof(TEXTRANGE))  ||
        ((pstrg->chrg.cpMost < 1 || pstrg->chrg.cpMin < 0 ||
          pstrg->chrg.cpMost <= pstrg->chrg.cpMin) &&
         !(pstrg->chrg.cpMost == -1 && !pstrg->chrg.cpMin)))
    {
        // This isn't valid so tell the caller we didn't copy any data
        return 0;
    }
    // Calculate size of buffer that we need on return
    return pstrg->chrg.cpMost - pstrg->chrg.cpMin;
}


////////////////////////////////////  Selection  /////////////////////////////////////

CTxtSelection * CTxtEdit::GetSel()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetSel");

    if(!_psel)
    {
        // There is no selection object available so create it.
        _psel = new CTxtSelection(_pdp);
        if(_psel)
            _psel->AddRef();                    // Set reference count = 1
    }

    // It is caller's responsiblity to notice that an error occurred
    // in allocation of selection object.
    return _psel;
}

void CTxtEdit::DiscardSelection()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::DiscardSelection");

    if(_psel)
    {
        _psel->Release();
        if(_psel)
        {
            // The text services reference is not the last reference to the
            // selection. We could keep track of the fact that text services
            // has released its reference and when text services gets a
            // reference again, do the AddRef there so that if the last
            // reference went away while we were still inactive, the selection
            // object would go away. However, it is seriously doubtful that
            // such a case will be very common. Therefore, just do the simplest
            // thing and put our reference back.
            _psel->AddRef();
        }
    }
}

void CTxtEdit::GetSelRangeForRender(
    LONG *pcpSelMin,
    LONG *pcpSelMost)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetSelRangeForRender");

    // If we have no selection or we are not active and the selection
    // has been requested to be hidden, there is no selection so we
    // just return 0's.
    if(!_psel || (!_fInPlaceActive && _fHideSelection))
    {
        *pcpSelMin = 0;
        *pcpSelMost = 0;
        return;
    }

    // Otherwise return the state of the current selection.
    *pcpSelMin  = _psel->GetScrSelMin();
    *pcpSelMost = _psel->GetScrSelMost();
}

LRESULT CTxtEdit::OnGetSelText(
    TCHAR *psz)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetSelText");

    LONG cpMin  = GetSelMin();                  // length + 1 for the null
    LONG cpMost = GetSelMost();
    return GetTextRange(cpMin, cpMost - cpMin + 1, psz);
}

/*
 *  CTxtEdit::OnExGetSel (pcrSel)
 *
 *  @mfunc
 *      Get the current selection acpMin, acpMost packaged in a CHARRANGE.
 *
 *  @comm
 *      This function outputs API cp's that may differ from the
 *      corresponding internal Unicode cp's.
 */
void CTxtEdit::OnExGetSel(
    CHARRANGE *pcrSel)  //@parm Output parm to receive acpMin, acpMost
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnExGetSel");

    pcrSel->cpMin  = GetAcpFromCp(GetSelMin());
    pcrSel->cpMost = GetAcpFromCp(GetSelMost());
}

/*
 *  CTxtEdit::OnGetSel (pacpMin, pacpMost)
 *
 *  @mfunc
 *      Get the current selection acpMin, acpMost.
 *
 *  @rdesc
 *      LRESULT = acpMost > 65535L ? -1 : MAKELRESULT(acpMin, acpMost)
 *
 *  @comm
 *      This function outputs API cp's that may differ from the
 *      corresponding internal Unicode cp's.
 */
LRESULT CTxtEdit::OnGetSel(
    LONG *pacpMin,      //@parm Output parm to receive acpMin
    LONG *pacpMost)     //@parm Output parm to receive acpMost
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetSel");

    CHARRANGE crSel;

    OnExGetSel(&crSel);
    if(pacpMin)
        *pacpMin = crSel.cpMin;
    if(pacpMost)
        *pacpMost = crSel.cpMost;

    return (crSel.cpMost > 65535l)  ? (LRESULT) -1
                : MAKELRESULT((WORD) crSel.cpMin, (WORD) crSel.cpMost);
}

/*
 *  CTxtEdit::OnSetSel (acpMin, acpMost)
 *
 *  @mfunc
 *      Implements the EM_SETSEL message
 *
 *  Algorithm:
 *      There are three basic cases to handle
 *
 *      cpMin < 0,  cpMost ???      -- Collapse selection to insertion point
 *                                     at text end if cpMost < 0 and else at
 *                                     selection active end
 *      cpMin >= 0, cpMost < 0      -- select from cpMin to text end with
 *                                     active end at text end
 *
 *      cpMin >= 0, cpMost >= 0     -- Treat as cpMin, cpMost with active
 *                                     end at cpMost
 *
 *  @comm
 *      This function inputs API cp's that may differ from the
 *      corresponding internal Unicode cp's.
 */
LRESULT CTxtEdit::OnSetSel(
    LONG acpMin,        //@parm Input acpMin
    LONG acpMost)       //@parm Input acpMost
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetSel");

    // Since this is only called from the window proc, we are always active
    Assert(GetSel());

    CTxtSelection * const psel = GetSel();
    LONG cpMin, cpMost;

    if(acpMin < 0)
        cpMin = cpMost = (acpMost < 0) ? tomForward : psel->GetCp();
    else
    {
        cpMin  = GetCpFromAcp(acpMin);
        cpMost = (acpMost < 0) ? tomForward : GetCpFromAcp(acpMost);
    }
    if(Get10Mode() && cpMost < cpMin)   // In 10 mode, ensure
    {                                   //  cpMost >= cpMin.  In
        cpMin ^= cpMost;                //  SetSelection, we set active
        cpMost ^= cpMin;                //  end to cpMost, which can be
        cpMin ^= cpMost;                //  smaller than cpMin, in spite
    }                                   //  of its name.
    psel->SetSelection(cpMin, cpMost);
    return psel->GetCpMost();
}

///////////////////////////////  DROP FILES support  //////////////////////////////////////
#ifndef NODROPFILES

LRESULT CTxtEdit::InsertFromFile (
    LPCTSTR lpFile)
{
    REOBJECT        reobj;
    LPRICHEDITOLECALLBACK const precall = GetRECallback();
    HRESULT         hr = NOERROR;

    if(!precall)
        return E_NOINTERFACE;

    ZeroMemory(&reobj, sizeof(REOBJECT));
    reobj.cbStruct = sizeof(REOBJECT);

    // Get storage for the object from client
    hr = precall->GetNewStorage(&reobj.pstg);
    if(hr)
    {
        TRACEERRORSZ("GetNewStorage() failed.");
        goto err;
    }

    // Create an object site for new object
    hr = GetClientSite(&reobj.polesite);
    if(!reobj.polesite)
    {
        TRACEERRORSZ("GetClientSite() failed.");
        goto err;
    }

    hr = OleCreateLinkToFile(lpFile, IID_IOleObject, OLERENDER_DRAW,
                NULL, NULL, reobj.pstg, (LPVOID*)&reobj.poleobj);
    if(hr)
    {
        TRACEERRORSZ("Failure creating link object.");
        goto err;
    }

    reobj.cp = REO_CP_SELECTION;
    reobj.dvaspect = DVASPECT_CONTENT;

    //Get object clsid
    hr = reobj.poleobj->GetUserClassID(&reobj.clsid);
    if(hr)
    {
        TRACEERRORSZ("GetUserClassID() failed.");
        goto err;
    }

    // Let client know what we're up to
    hr = precall->QueryInsertObject(&reobj.clsid, reobj.pstg,
            REO_CP_SELECTION);
    if(hr != NOERROR)
    {
        TRACEERRORSZ("QueryInsertObject() failed.");
        goto err;
    }

    hr = reobj.poleobj->SetClientSite(reobj.polesite);
    if(hr)
    {
        TRACEERRORSZ("SetClientSite() failed.");
        goto err;
    }

    if(hr = InsertObject(&reobj))
    {
        TRACEERRORSZ("InsertObject() failed.");
    }

err:
    if(reobj.poleobj)
        reobj.poleobj->Release();

    if(reobj.polesite)
        reobj.polesite->Release();

    if(reobj.pstg)
        reobj.pstg->Release();

    return hr;
}

typedef void (WINAPI*DRAGFINISH)(HDROP);
typedef UINT (WINAPI*DRAGQUERYFILEA)(HDROP, UINT, LPSTR, UINT);
typedef UINT (WINAPI*DRAGQUERYFILEW)(HDROP, UINT, LPTSTR, UINT);
typedef BOOL (WINAPI*DRAGQUERYPOINT)(HDROP, LPPOINT);

LRESULT CTxtEdit::OnDropFiles(
    HANDLE hDropFiles)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnDropFiles");

    UINT    cFiles;
    UINT    iFile;
    char    szFile[MAX_PATH];
    WCHAR   wFile[MAX_PATH];
    POINT   ptDrop;
    CTxtSelection * const psel = GetSel();
    HMODULE     hDLL = NULL;
    DRAGFINISH      fnDragFinish;
    DRAGQUERYFILEA  fnDragQueryFileA;
    DRAGQUERYFILEW  fnDragQueryFileW;
    DRAGQUERYPOINT  fnDragQueryPoint;

    if (_fReadOnly)
        return 0;

    AssertSz((hDropFiles != NULL), "CTxtEdit::OnDropFiles invalid hDropFiles");

    // dynamic load Shell32

    hDLL = LoadLibrary (TEXT("Shell32.DLL"));
    if(hDLL)
    {
        fnDragFinish = (DRAGFINISH)GetProcAddress (hDLL, "DragFinish");
        fnDragQueryFileA = (DRAGQUERYFILEA)GetProcAddress (hDLL, "DragQueryFileA");
        fnDragQueryFileW = (DRAGQUERYFILEW)GetProcAddress (hDLL, "DragQueryFileW");
        fnDragQueryPoint = (DRAGQUERYPOINT)GetProcAddress (hDLL, "DragQueryPoint");
    }
    else
        return 0;

    if(!fnDragFinish || !fnDragQueryFileA || !fnDragQueryFileW || !fnDragQueryPoint)
    {
        AssertSz(FALSE, "Shell32 GetProcAddress failed");
        goto EXIT0;
    }

    (*fnDragQueryPoint) ((HDROP)hDropFiles, &ptDrop);
    if(W32->OnWin9x())
        cFiles = (*fnDragQueryFileA) ((HDROP)hDropFiles, (UINT)-1, NULL, 0);
    else
        cFiles = (*fnDragQueryFileW) ((HDROP)hDropFiles, (UINT)-1, NULL, 0);

    if(cFiles)
    {
        LONG        cp = 0;
        POINT       ptl = ptDrop;
        CRchTxtPtr  rtp(this);
        const CCharFormat   *pCF;

        if(_pdp->CpFromPoint(ptl, NULL, &rtp, NULL, FALSE) >= 0)
        {
            cp = rtp.GetCp();
            pCF = rtp.GetCF();
        }
        else
        {
            LONG iCF = psel->Get_iCF();
            cp = psel->GetCp();
            pCF = GetCharFormat(iCF);
            ReleaseFormats(iCF, -1);
        }

        // Notify user for dropfile
        if(_dwEventMask & ENM_DROPFILES)
        {
            ENDROPFILES endropfiles;

            endropfiles.hDrop = hDropFiles;
            endropfiles.cp = Get10Mode() ? GetAcpFromCp(cp) : cp;
            endropfiles.fProtected = !!(pCF->_dwEffects & CFE_PROTECTED);

            if(TxNotify(EN_DROPFILES, &endropfiles))
                goto EXIT;                  // Ignore drop file

            cp = Get10Mode() ? GetCpFromAcp(endropfiles.cp) : endropfiles.cp;   // Allow callback to update cp
        }
        psel->SetCp(cp);
    }

    for (iFile = 0;  iFile < cFiles; iFile++)
    {
        if(W32->OnWin9x())
        {
            (*fnDragQueryFileA) ((HDROP)hDropFiles, iFile, szFile, MAX_PATH);
            MultiByteToWideChar(CP_ACP, 0, szFile, -1,
                            wFile, MAX_PATH);
        }
        else
            (*fnDragQueryFileW) ((HDROP)hDropFiles, iFile, wFile, MAX_PATH);

        InsertFromFile (wFile);
    }

EXIT:
    (*fnDragFinish) ((HDROP)hDropFiles);

EXIT0:
    FreeLibrary (hDLL);
    return 0;
}

#else // NODROPFILES

LRESULT CTxtEdit::OnDropFiles(HANDLE hDropFiles)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnDropFiles");

    return 0;
}

#endif  // NODROPFILES


///////////////////////////////  Exposable methods  //////////////////////////////////////

/*
 *  CTxtEdit::TxCharFromPos (ppt, plres)
 *
 *  @mfunc
 *      Get the acp at the point *ppt.
 *
 *  @rdesc
 *      HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK :
 *                (CpFromPoint succeeded) ? S_OK : E_FAIL
 *  @comm
 *      This function outputs an API cp that may differ from the
 *      corresponding internal Unicode cp.
 */
HRESULT CTxtEdit::TxCharFromPos(
    LPPOINT  ppt,   //@parm Point to find the acp for
    LRESULT *plres) //@parm Output parm to receive the acp
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxCharFromPos");

    if(!fInplaceActive())
    {
        // We have no valid display rectangle if this object is not active
        *plres = -1;
        return OLE_E_INVALIDRECT;
    }
    *plres = _pdp->CpFromPoint(*ppt, NULL, NULL, NULL, FALSE);
    if(*plres == -1)
        return E_FAIL;

    *plres = GetAcpFromCp(*plres);
    return S_OK;
}

/*
 *  CTxtEdit::TxPosFromChar (acp, ppt)
 *
 *  @mfunc
 *      Get the point at acp.
 *
 *  @rdesc
 *      HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK :
 *                (PointFromTp succeeded) ? S_OK : E_FAIL
 *  @comm
 *      This function inputs an API cp that may differ from the
 *      corresponding internal Unicode cp.
 */
HRESULT CTxtEdit::TxPosFromChar(
    LONG    acp,        //@parm Input cp to get the point for
    POINT * ppt)        //@parm Output parm to receive the point
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxPosFromChar");

    if(!fInplaceActive())
        return OLE_E_INVALIDRECT;

    CRchTxtPtr rtp(this, GetCpFromAcp(acp));

    if(_pdp->PointFromTp(rtp, NULL, FALSE, *ppt, NULL, TA_TOP) < 0)
        return E_FAIL;

    return S_OK;
}

/*
 *  CTxtEdit::TxFindWordBreak (nFunction, acp, plres)
 *
 *  @mfunc
 *      Find word break or classify character at acp.
 *
 *  @rdesc
 *      HRESULT = plRet ? S_OK : E_INVALIDARG
 *
 *  @comm
 *      This function inputs and exports API cp's and cch's that may differ
 *      from the internal Unicode cp's and cch's.
 */
HRESULT CTxtEdit::TxFindWordBreak(
    INT      nFunction, //@parm Word break function
    LONG     acp,       //@parm Input cp
    LRESULT *plres)     //@parm cch moved to reach break
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxFindWordBreak");

    CTxtPtr tp(this, GetCpFromAcp(acp));        // This validates cp
    LONG    cpSave = tp.GetCp();                // Save starting value

    if(!plres)
        return E_INVALIDARG;

    *plres = tp.FindWordBreak(nFunction);

    // WB_CLASSIFY and WB_ISDELIMITER return values; others return offsets
    // this function returns values, so it converts when necessary
    if(nFunction != WB_CLASSIFY && nFunction != WB_ISDELIMITER)
        *plres = GetAcpFromCp(LONG(*plres + cpSave));

    return S_OK;
}

/*
 *  INT CTxtEdit::TxWordBreakProc (pch, ich, cb, action)
 *
 *  @func
 *      Default word break proc used in conjunction with FindWordBreak. ich
 *      is character offset (start position) in the buffer pch, which is cb
 *      bytes in length.  Possible action values are:
 *
 *  WB_CLASSIFY
 *      Returns char class and word break flags of char at start position.
 *
 *  WB_ISDELIMITER
 *      Returns TRUE iff char at start position is a delimeter.
 *
 *  WB_LEFT
 *      Finds nearest word beginning before start position using word breaks.
 *
 *  WB_LEFTBREAK
 *      Finds nearest word end before start position using word breaks.
 *      Used by CMeasurer::Measure()
 *
 *  WB_MOVEWORDLEFT
 *      Finds nearest word beginning before start position using class
 *      differences. This value is used during CTRL+LEFT key processing.
 *
 *  WB_MOVEWORDRIGHT
 *      Finds nearest word beginning after start position using class
 *      differences. This value is used during CTRL+RIGHT key processing.
 *
 *  WB_RIGHT
 *      Finds nearest word beginning after start position using word breaks.
 *      Used by CMeasurer::Measure()
 *
 *  WB_RIGHTBREAK
 *      Finds nearest word end after start position using word breaks.
 *
 *  @rdesc
 *      Character offset from start of buffer (pch) of the word break
 */
INT CTxtEdit::TxWordBreakProc(
    TCHAR * pch,        //@parm Char buffer
    INT     ich,        //@parm Char offset of _cp in buffer
    INT     cb,         //@parm Count of bytes in buffer
    INT     action,     //@parm Type of breaking action
    LONG    cpStart,    //@parm cp for first character in pch
    LONG    cp)         //@parm cp associated to ich
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxWordBreakProc");

    if (_pfnWB)
    {
        // Client overrode the wordbreak proc, delegate the call to it.
        if (!Get10Mode())
        {
            Assert(!_fExWordBreakProc);
            return _pfnWB(pch, ich, cb, action);
        }
        else
        {
            int ret = 0;
            char sz[256];
            char* pach = sz;
            if (cb >= 255)
                pach = new char [cb + 1];

            // this indicates if we have to adjust the pach because the api's for
            // EDITWORDBREAKPROCEX and EDITWORDBREAKPROC are different when looking to the left
            BOOL fAdjustPtr = _fExWordBreakProc && (action == WB_LEFT || action == WB_MOVEWORDLEFT || action == WB_LEFTBREAK);

            // RichEdit 1.0, create a buffer, translate ich and WCTMB
            // pch into the buffer.  Need codepage to use. Then get translate
            // return value. Translations are like GetCachFromCch() and
            // GetCchFromCach()
            if (_fExWordBreakProc)
            {
                Assert(ich == 0 || ich == 1 || ich == CchOfCb(cb));

                // We need to adjust the cp to the starting point of the buffer
                if (!fAdjustPtr)
                {
                    cpStart += ich;
                    pch += ich;
                    cb -= (2 * ich);
                }

                // initialize string w/ zero's so we can determine the length of the string for later
                memset(pach, 0, cb + 1);
            }

            int nLen = CchOfCb(cb);
            CRchTxtPtr rtp(this, cpStart);
            BYTE bCharSet = rtp.GetCF()->_bCharSet;
            if (WideCharToMultiByte(GetCodePage(bCharSet), 0, pch, nLen, pach, cb + 1, NULL, NULL))
            {
                // Documentation stipulates we need to point to the end of the string
                if (fAdjustPtr)
                    pach += strlen(pach);

                if (_fExWordBreakProc)
                    ret = ((EDITWORDBREAKPROCEX)_pfnWB)(pach, nLen, bCharSet, action);
                else
                {
                    ret = ((EDITWORDBREAKPROCA)_pfnWB)(pach, rtp.GetCachFromCch(ich), nLen, action);

                    // need to reset cp position because GetCachFromCch potentially moves the cp
                    if (ich)
                        rtp.SetCp(cpStart);
                }

                // For WB_ISDELIMITER and WB_CLASSIFY don't need to convert back
                // to ich because return value represents a BOOL
                if (action != WB_ISDELIMITER && action != WB_CLASSIFY)
                    ret = rtp.GetCchFromCach(ret);
            }

            // Delete any allocated memory
            if (pach != sz)
                delete [] pach;
            return ret;
        }
    }

    LONG    cchBuff = CchOfCb(cb);
    LONG    cch = cchBuff - ich;
    TCHAR   ch;
    WORD    cType3[MAX_CLASSIFY_CHARS];
    INT     kinsokuClassifications[MAX_CLASSIFY_CHARS];
    WORD *  pcType3;
    INT  *  pKinsoku1, *pKinsoku2;
    WORD *  pwRes;
    WORD    startType3 = 0;
    WORD    wb = 0;
    WORD    wClassifyData[MAX_CLASSIFY_CHARS];  // For batch classifying

    Assert(cchBuff < MAX_CLASSIFY_CHARS);
    Assert(ich >= 0 && ich < cchBuff);


    // Single character actions
    if ( action == WB_CLASSIFY )
    {
        // 1.0 COMPATABILITY - 1.0 returned 0 for apostrohpe's
        TCHAR ch = pch[ich];
        if (Get10Mode() && ( ch ==  0x0027 /*APOSTRPHE*/ ||
            ch == 0xFF07 /*FULLWIDTH APOSTROPHE*/))
        {
            return 0;
        }
        return ClassifyChar(ch);
    }

    if ( action == WB_ISDELIMITER )
        return !!(ClassifyChar(pch[ich]) & WBF_BREAKLINE);

    // Batch classify buffer for whitespace and kinsoku classes
    BatchClassify(pch, cchBuff, cType3, kinsokuClassifications, wClassifyData);

    if (_pbrk && cp > -1)
    {
		cp -= ich;

        for (LONG cbrk = cchBuff-1; cbrk >= 0; --cbrk)
        {
            if (cp + cbrk >= 0 && _pbrk->CanBreakCp(BRK_WORD, cp + cbrk))
            {
                // Mimic class open/close in Kinsoku classification.
                kinsokuClassifications[cbrk] = brkclsOpen;
                if (cbrk > 0)
				{
                    kinsokuClassifications[cbrk-1] = brkclsClose;
                    wClassifyData[cbrk-1] |= WBF_WORDBREAKAFTER;
				}
            }
        }
    }

    // Setup pointers
    pKinsoku2 = kinsokuClassifications + ich;       // Ptr to current  kinsoku
    pKinsoku1 = pKinsoku2 - 1;                      // Ptr to previous kinsoku

    if(!(action & 1))                               // WB_(MOVE)LEFTxxx
    {
        ich--;
        Assert(ich >= 0);
    }
    pwRes    = &wClassifyData[ich];
    pcType3  = &cType3[ich];                        // for ideographics

    switch(action)
    {
    case WB_LEFT:
        for(; ich >= 0 && *pwRes & WBF_BREAKLINE;   // Skip preceding line
            ich--, pwRes--)                         //  break chars
                ;                                   // Empty loop. Then fall
                                                    //  thru to WB_LEFTBREAK
    case WB_LEFTBREAK:
        for(; ich >= 0 && !CanBreak(*pKinsoku1, *pKinsoku2);
            ich--, pwRes--, pKinsoku1--, pKinsoku2--)
                ;                                   // Empty loop
        if(action == WB_LEFTBREAK)                  // Skip preceding line
        {                                           //  break chars
            for(; ich >= 0 && *pwRes & WBF_BREAKLINE;
                ich--, pwRes--)
                    ;                               // Empty loop
        }
        return ich + 1;

    case WB_MOVEWORDLEFT:
        for(; ich >= 0 && (*pwRes & WBF_CLASS) == 2;// Skip preceding blank
            ich--, pwRes--, pcType3--)              //  chars
                ;
        if(ich >= 0)                                // Save starting wRes and
        {                                           //  startType3
            wb = *pwRes--;                          // Really type1
            startType3 = *pcType3--;                // type3
            ich--;
        }
        // Skip to beginning of current word
        while(ich >= 0 && (*pwRes & WBF_CLASS) != 3 &&
            !(*pwRes & WBF_WORDBREAKAFTER) &&
            (IsSameClass(*pwRes, wb, *pcType3, startType3) ||
            !wb && ich && ((ch = pch[ich]) == '\'' || ch == RQUOTE)))
        {
            ich--, pwRes--, pcType3--;
        }
        return ich + 1;


    case WB_RIGHTBREAK:
        for(; cch > 0 && *pwRes & WBF_BREAKLINE;    // Skip any leading line
            cch--, pwRes++)                         //  break chars
                ;                                   // Empty loop
                                                    // Fall thru to WB_RIGHT
    case WB_RIGHT:
        // Skip to end of current word
        for(; cch > 0 && !CanBreak(*pKinsoku1, *pKinsoku2);
            cch--, pKinsoku1++, pKinsoku2++, pwRes++)
                ;
        if(action != WB_RIGHTBREAK)                 // Skip trailing line
        {                                           //  break chars
            for(; cch > 0 && *pwRes & WBF_BREAKLINE;
                cch--, pwRes++)
                    ;
        }
        return cchBuff - cch;

    case WB_MOVEWORDRIGHT:
        if(cch <= 0)                                // Nothing to do
            return ich;

        wb = *pwRes;                                // Save start wRes
        startType3 = *pcType3;                      //  and startType3

        // Skip to end of word
        if (startType3 & C3_IDEOGRAPH ||            // If ideographic or
            (*pwRes & WBF_CLASS) == 3)              //  tab/cell, just
        {
            cch--, pwRes++;                         //  skip one char
        }
        else while(cch > 0 &&
            !(*pwRes & WBF_WORDBREAKAFTER) &&
            (IsSameClass(*pwRes, wb, *pcType3, startType3) || !wb &&
             ((ch = pch[cchBuff - cch]) == '\'' || ch == RQUOTE)))
        {
            cch--, pwRes++, pcType3++;
        }

        for(; cch > 0 &&
            ((*pwRes & WBF_CLASS) == 2              // Skip trailing blank
            || (*pwRes & WBF_WORDBREAKAFTER)); 		// Skip Thai break after
            cch--, pwRes++)                         //  chars
                    ;
        return cchBuff - cch;
    }

    TRACEERRSZSC("CTxtEdit::TxWordBreakProc: unknown action", action);
    return ich;
}


/*
 *  CTxtEdit::TxFindText (flags, cpMin, cpMost, pch, pcpRet)
 *
 *  @mfunc
 *      Find text in direction specified by flags starting at cpMin if
 *      forward search (flags & FR_DOWN nonzero) and cpMost if backward
 *      search.
 *
 *  @rdesc
 *      HRESULT (success) ? NOERROR : S_FALSE
 *
 *  @comm
 *      Caller is responsible for setting cpMin to the appropriate end of
 *      the selection depending on which way the search is proceding.
 */
HRESULT CTxtEdit::TxFindText(
    DWORD       flags,   //@parm Specify FR_DOWN, FR_MATCHCASE, FR_WHOLEWORD
    LONG        cpStart, //@parm Find start cp
    LONG        cpLimit, //@parm Find limit cp
    const WCHAR*pch,     //@parm Null terminated string to search for
    LONG *      pcpMin,  //@parm Out parm to receive start of matched string
    LONG *      pcpMost) //@parm Out parm to receive end of matched string
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxFindText");

    if(Get10Mode())                         // RichEdit 1.0 only searches
    {
        flags |= FR_DOWN;                   //  forward
        if (cpLimit < -1)
            cpLimit = -1;
    }

    DWORD       cchText = GetTextLength();
    LONG        cchToFind;
    const BOOL  fSetCur = (cchText >= 4096);
    HCURSOR     hcur = NULL;                // Init to keep compiler happy

    Assert(pcpMin && pcpMost);

    // Validate parameters
    if(!pch || !(cchToFind = wcslen(pch)) || cpStart < 0 || cpLimit < -1)
        return E_INVALIDARG;                // Nothing to search for

    CTxtPtr tp(this, cpStart);

    if(fSetCur)                             // In case this takes a while...
        hcur = TxSetCursor(LoadCursor(0, IDC_WAIT), NULL);

    *pcpMin  = tp.FindText(cpLimit, flags, pch, cchToFind);
    *pcpMost = tp.GetCp();

    if(fSetCur)
        TxSetCursor(hcur, NULL);

    return *pcpMin >= 0 ? NOERROR : S_FALSE;;
}

/*
 *  CTxtEdit::TxGetLineCount (plres)
 *
 *  @mfunc
 *      Get the line count.
 *
 *  @rdesc
 *      HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK :
 *                (WaitForRecalc succeeded) ? S_OK : E_FAIL
 */
HRESULT CTxtEdit::TxGetLineCount(
    LRESULT *plres)     //@parm Output parm to receive line count
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetLineCount");

    AssertSz(plres, "CTxtEdit::TxGetLineCount invalid pcli");

    if(!fInplaceActive())
        return OLE_E_INVALIDRECT;

    if(!_pdp->WaitForRecalc(GetTextLength(), -1))
        return E_FAIL;

    *plres = _pdp->LineCount();
    Assert(*plres > 0);

    return S_OK;
}

/*
 *  CTxtEdit::TxLineFromCp (acp, plres)
 *
 *  @mfunc
 *      Get the line containing acp.
 *
 *  @rdesc
 *      HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK :
 *                (LineFromCp succeeded) ? S_OK : E_FAIL
 *  @comm
 *      This function inputs an API cp that may differ from the
 *      corresponding internal Unicode cp.
 */
HRESULT CTxtEdit::TxLineFromCp(
    LONG     acp,       //@parm Input cp
    LRESULT *plres)     //@parm Ouput parm to receive line number
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxLineFromCp");

    BOOL    fAtEnd = FALSE;
    LONG    cp = 0;

    AssertSz(plres, "CTxtEdit::TxLineFromCp invalid plres");

    if(!fInplaceActive())
    {
        AssertSz(*plres == 0,
            "CTxtEdit::TxLineFromCp error return lres not correct");
        return OLE_E_INVALIDRECT;
    }

    if(acp < 0)                                 // Validate cp
    {
        if(_psel)
        {
            cp = _psel->GetCpMin();
            fAtEnd = !_psel->GetCch() && _psel->CaretNotAtBOL();
        }
    }
    else
    {
        LONG cchText = GetTextLength();
        cp = GetCpFromAcp(acp);
        cp = min(cp, cchText);
    }

    *plres = _pdp->LineFromCp(cp, fAtEnd);

    HRESULT hr = *plres < 0 ? E_FAIL : S_OK;

    // Old messages expect 0 as a result of this call if there is an error.
    if(*plres == -1)
        *plres = 0;

    return hr;
}

/*
 *  CTxtEdit::TxLineLength (acp, plres)
 *
 *  @mfunc
 *      Get the line containing acp.
 *
 *  @rdesc
 *      HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK :
 *                (GetSel() succeeded) ? S_OK : E_FAIL
 *  @comm
 *      This function inputs an API cp and outputs an API cch that
 *      may differ from the corresponding internal Unicode cp and cch.
 */
HRESULT CTxtEdit::TxLineLength(
    LONG     acp,       //@parm Input cp
    LRESULT *plres)     //@parm Output parm to receive line length
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxLineLength");

    LONG cch = 0;
    LONG cp;

    AssertSz(plres,
            "CTxtEdit::TxLineLength Invalid plres parameter");

    if(!fInplaceActive())
        return OLE_E_INVALIDRECT;

    if(acp < 0)
    {
        if(!_psel)
            return E_FAIL;
        cch = _psel->LineLength(&cp);
    }
    else
    {
        cp = GetCpFromAcp(acp);
        if(cp <= GetAdjustedTextLength())
        {
            CLinePtr rp(_pdp);
            rp.RpSetCp(cp, FALSE);
            cp -= rp.GetIch();              // Goto start of line
            cch = rp.GetAdjustedLineLength();
        }
    }
    if(fCpMap())                            // Can be time consuming, so
    {                                       //  don't do it unless asked
        CRchTxtPtr rtp(this, cp);           //  for
        cch = rtp.GetCachFromCch(cch);
    }
    *plres = cch;
    return S_OK;
}

/*
 *  CTxtEdit::TxLineIndex (acp, plres)
 *
 *  @mfunc
 *      Get the line containing acp.
 *
 *  @rdesc
 *      HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK :
 *                (LineCount() && WaitForRecalcIli succeeded) ? S_OK : E_FAIL
 *  @comm
 *      This function outputs an API cp that may differ from the
 *      corresponding internal Unicode cp.
 */
HRESULT CTxtEdit::TxLineIndex(
    LONG     ili,       //@parm Line # to find acp for
    LRESULT *plres)     //@parm Output parm to receive acp
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxLineIndex");

    HRESULT hr;
    AssertSz(plres, "CTxtEdit::TxLineIndex invalid plres");

    *plres = -1;
    if(!fInplaceActive())
        return OLE_E_INVALIDRECT;

    if(ili == -1)
    {
        // Fetch line from the current cp.
        LRESULT lres;                   // For 64-bit compatibility
        hr = TxLineFromCp(-1, &lres);
        if(hr != NOERROR)
            return hr;
        ili = (LONG)lres;
    }

    // ili is a zero-based *index*, whereas count returns the total # of lines.
    // Therefore, we use >= for our comparisions.
    if(ili >= _pdp->LineCount() && !_pdp->WaitForRecalcIli(ili))
        return E_FAIL;

    *plres = GetAcpFromCp(_pdp->CpFromLine(ili, NULL));

    return S_OK;
}

///////////////////////////////////  Miscellaneous messages  ////////////////////////////////////

/*
 *  CTxtEdit::OnFindText (msg, flags, pftex)
 *
 *  @mfunc
 *      Find text.
 *
 *  @rdesc
 *      LRESULT = succeeded ? acpmin : -1
 *
 *  @comm
 *      This function inputs and exports API cp's that may differ
 *      from the internal Unicode cp's.
 */
LRESULT CTxtEdit::OnFindText(
    UINT        msg,
    DWORD       flags,
    FINDTEXTEX *pftex)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnFindText");

    LONG cpMin, cpMost;

    if(TxFindText(flags,
                  GetCpFromAcp(pftex->chrg.cpMin),
                  GetCpFromAcp(pftex->chrg.cpMost),
                  pftex->lpstrText, &cpMin, &cpMost) != S_OK)
    {
        if(msg == EM_FINDTEXTEX || msg == EM_FINDTEXTEXW)
        {
            pftex->chrgText.cpMin  = -1;
            pftex->chrgText.cpMost = -1;
        }
        return -1;
    }

    LONG acpMin  = GetAcpFromCp(cpMin);
    if(msg == EM_FINDTEXTEX || msg == EM_FINDTEXTEXW)   // We send a message
    {                                                   //  back to change
        pftex->chrgText.cpMin  = acpMin;                //  selection to this
        pftex->chrgText.cpMost = GetAcpFromCp(cpMost);
    }
    return (LRESULT)acpMin;
}

LRESULT CTxtEdit::OnGetWordBreakProc()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetWordBreakProc");

    return (LRESULT) _pfnWB;
}

// For plain-text instances, OnGetCharFormat(), OnGetParaFormat(),
// OnSetCharFormat(), and OnSetParaFormat() apply to whole story

LRESULT CTxtEdit::OnGetCharFormat(
    CHARFORMAT2 *pCF2,
    DWORD        dwFlags)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetCharFormat");

    UINT cb = pCF2->cbSize;
    UINT CodePage = 1200;

    if(!IsValidCharFormatW(pCF2))
    {
        if(!IsValidCharFormatA((CHARFORMATA *)pCF2))
            return 0;
        CodePage = GetDefaultCodePage(EM_GETCHARFORMAT);
    }

    if(cb == sizeof(CHARFORMATW) || cb == sizeof(CHARFORMATA))
        dwFlags |= CFM2_CHARFORMAT;             // Tell callees that only
                                                //  CHARFORMAT parms needed
    CCharFormat CF;
    DWORD dwMask = CFM_ALL2;

    if(dwFlags & SCF_SELECTION)
        dwMask = GetSel()->GetCharFormat(&CF, dwFlags);
    else
        CF = *GetCharFormat(-1);

    if(dwFlags & CFM2_CHARFORMAT)               // Maintain CHARFORMAT
    {                                           //  compatibility
        CF._dwEffects &= CFM_EFFECTS;
        dwMask        &= CFM_ALL;
    }

    CF.Get(pCF2, CodePage);
    pCF2->dwMask = dwMask;
    return (LRESULT)dwMask;
}

LRESULT CTxtEdit::OnGetParaFormat(
    PARAFORMAT2 *pPF2,
    DWORD        dwFlags)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetParaFormat");

    if(!IsValidParaFormat(pPF2))
        return 0;

    if(pPF2->cbSize == sizeof(PARAFORMAT))  // Tell callees that only
        dwFlags |= PFM_PARAFORMAT;          //  PARAFORMAT parms needed

    CParaFormat PF;
    DWORD       dwMask = GetSel()->GetParaFormat(&PF, dwFlags);

    if(dwFlags & PFM_PARAFORMAT)
        dwMask &= PFM_ALL;

    PF.Get(pPF2);
    pPF2->dwMask = dwMask;
    return (LRESULT)dwMask;
}

/*
 *  CTxtEdit::OnSetFontSize(yPoint, publdr)
 *
 *  @mfunc
 *      Set new font height by adding yPoint to current height
 *      and rounding according to the table in cfpf.cpp
 *
 *  @rdesc
 *      LRESULT nonzero if success
 */
LRESULT CTxtEdit::OnSetFontSize(
    LONG yPoint,
    IUndoBuilder *publdr)   //@parm Undobuilder to receive antievents
{
    // TODO: ? Return nonzero if we set a new font size for some text.

    CCharFormat CF;
    CF._yHeight = (SHORT)yPoint;

    return OnSetCharFormat(SCF_SELECTION, &CF, publdr, CFM_SIZE,
                           CFM2_CHARFORMAT | CFM2_USABLEFONT);
}

/*
 *  CTxtEdit::OnSetFont(hfont)
 *
 *  @mfunc
 *      Set new default font from hfont
 *
 *  @rdesc
 *      LRESULT nonzero if success
 */
LRESULT CTxtEdit::OnSetFont(
    HFONT hfont)            //@parm Handle of font to use for default
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetFont");

    CCharFormat CF;
    if(FAILED(CF.InitDefault(hfont)))
        return 0;

    DWORD dwMask2 = CFM2_CHARFORMAT;
    WPARAM wparam = SCF_ALL;

    if(!GetAdjustedTextLength())
    {
        dwMask2 = CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK;
        wparam = 0;
    }

    return !FAILED(OnSetCharFormat(wparam, &CF, NULL, CFM_ALL, dwMask2));
}

/*
 *  CTxtEdit::OnSetCharFormat(wparam, pCF, publdr, dwMask, dwMask2)
 *
 *  @mfunc
 *      Set new default CCharFormat
 *
 *  @rdesc
 *      LRESULT nonzero if success
 */
LRESULT CTxtEdit::OnSetCharFormat(
    WPARAM        wparam,   //@parm Selection flag
    CCharFormat * pCF,      //@parm CCharFormat to apply
    IUndoBuilder *publdr,   //@parm Undobuilder to receive antievents
    DWORD         dwMask,   //@parm CHARFORMAT2 mask
    DWORD         dwMask2)  //@parm Second mask
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetCharFormat");

    // This says that if there's a selection that's protected and the
    // parent window wants protection notifications and doesn't want
    // changes with a protected selection, then return 0.  This is more
    // stringent than RE 2.0, but it's more like 1.0.
    if (_psel && _psel->IsProtected(0) == CTxtRange::PROTECTED_ASK &&
        _dwEventMask & ENM_PROTECTED)
    {
        CHARFORMAT CF0;                 // Selection is protected, client
                                        //  wants protect notifications
        CF0.cbSize = sizeof(CHARFORMAT);//  and protected mask is on
        CF0.dwEffects = pCF->_dwEffects;// Concoct CHARFORMAT for query
        CF0.dwMask = dwMask;            // Maybe need more fields...
        if(QueryUseProtection(_psel, EM_SETCHARFORMAT, wparam, (LPARAM)&CF0))
            return 0;                   // No deal
    }

    BOOL fRet = TRUE;

    AssertSz(!_fSelChangeCharFormat || IsRich(),
        "Inconsistent _fSelChangeCharFormat flag");

    if ((wparam & SCF_ALL) ||
        !_fSelChangeCharFormat && _story.GetCFRuns() && !(wparam & SCF_SELECTION))
    {
        CTxtRange rg(this, 0, -GetTextLength());

        if(publdr)
            publdr->StopGroupTyping();

        if ((dwMask & (CFM_CHARSET | CFM_FACE)) == (CFM_CHARSET | CFM_FACE))
        {
            if(GetAdjustedTextLength())
            {
                dwMask2 |= CFM2_MATCHFONT;
                if (_fAutoFontSizeAdjust)
                {
                    dwMask2 |= CFM2_ADJUSTFONTSIZE;
                    if (fUseUIFont())
                        dwMask2 |= CFM2_UIFONT;
                }
            }
            else
                dwMask2 |= CFM2_NOCHARSETCHECK;
        }

        fRet = (rg.SetCharFormat(pCF, 0, publdr, dwMask, dwMask2) == NOERROR);

        // If we have an insertion point, apply format to it as well
        if (_psel && !_psel->GetCch() &&
            _psel->SetCharFormat(pCF, wparam, publdr, dwMask, dwMask2) != NOERROR)
        {
            fRet = FALSE;
        }
    }
    else if(wparam & SCF_SELECTION)
    {
        // Change selection character format unless protected
        if(!_psel || !IsRich())
            return 0;

        return _psel->SetCharFormat(pCF, wparam, publdr, dwMask, dwMask2)
                == NOERROR;
    }

    // Change default character format
    CCharFormat        CF;                  // Local CF to party on
    LONG               iCF;                 // Possible new CF index
    const CCharFormat *pCF1;                // Ptr to current default CF
    ICharFormatCache  *pICFCache = GetCharFormatCache();

    if(FAILED(pICFCache->Deref(Get_iCF(), &pCF1)))  // Get ptr to current
    {                                       //  default CCharFormat
        fRet = FALSE;
        goto Update;
    }
    CF = *pCF1;                             // Copy current default CF
    CF.Apply(pCF, dwMask, dwMask2);         // Modify copy
    if(FAILED(pICFCache->Cache(&CF, &iCF))) // Cache modified copy
    {
        fRet = FALSE;
        goto Update;
    }

#ifdef LINESERVICES
    if (g_pols)
        g_pols->DestroyLine(NULL);
#endif

    pICFCache->Release(Get_iCF());          // Release _iCF regardless
    Set_iCF(iCF);                           //  of whether _iCF = iCF,
                                            //  i.e., only 1 ref count
    if(_psel && !_psel->GetCch() && _psel->Get_iFormat() == -1)
        _psel->UpdateCaret(FALSE);

    if ((dwMask & (CFM_CHARSET | CFM_FACE)) == CFM_FACE &&
        !GetFontName(pCF->_iFont)[0] && GetFontName(CF._iFont)[0] &&
        IsBiDiCharSet(CF._bCharSet))
    {
        // Client requested font/charset be chosen for it according to thread
        // locale. If BiDi, then also set RTL para default
        CParaFormat PF;
        PF._wEffects = PFE_RTLPARA;
        OnSetParaFormat(SPF_SETDEFAULT, &PF, publdr, PFM_RTLPARA | PFM_PARAFORMAT);
    }

Update:
    // FUTURE (alexgo): this may be unnecessary if the display handles
    // updating more automatically.
    _pdp->UpdateView();
    return fRet;
}

/*
 *  CTxtEdit::OnSetParaFormat(wparam, pPF, publdr, dwMask)
 *
 *  @mfunc
 *      Set new default CParaFormat
 *
 *  @rdesc
 *      LRESULT nonzero if success
 */
LRESULT CTxtEdit::OnSetParaFormat(
    WPARAM       wparam,    //@parm wparam passed thru to IsProtected()
    CParaFormat *pPF,       //@parm CParaFormat to use
    IUndoBuilder *publdr,   //@parm Undobuilder to receive antievents
    DWORD        dwMask)    //@parm Mask to use
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetParaFormat");

    // If we're using context direction in the control, then we disallow
    // the paragraph direction property and the alignment property (unless
    // it's for center alignment).
    if(IsStrongContext(_nContextDir) || IsStrongContext(_nContextAlign))
    {
        Assert(!IsRich());
        if(dwMask & (PFM_RTLPARA | PFM_ALIGNMENT))
        {
            if (IsStrongContext(_nContextAlign) &&
                (pPF->_bAlignment == PFA_LEFT || pPF->_bAlignment == PFA_RIGHT))
            {
                dwMask &= ~PFM_ALIGNMENT;
            }
            if(IsStrongContext(_nContextDir))
                dwMask &= ~PFM_RTLPARA;
        }
    }
    BOOL fMatchKbdToPara = FALSE;

    if(dwMask & PFM_RTLPARA)
    {
        // In plain text allow DIR changes to change DIR and ALIGNMENT
        if(!IsRich())
        {
            // Clear all para masks, except for DIR and ALIGN
            dwMask &= (PFM_RTLPARA | PFM_ALIGNMENT);
            wparam |= SPF_SETDEFAULT;
        }
        if(_psel && _fFocus)
            fMatchKbdToPara = TRUE;
    }
    if(!(wparam & SPF_SETDEFAULT))
    {
        // If DEFAULT flag is specified, don't change selection
        if(!_psel || IsProtected(EM_SETPARAFORMAT, wparam, (LPARAM)pPF))
            return 0;

        LRESULT lres = NOERROR == (pPF->fSetStyle(dwMask)
             ? _psel->SetParaStyle(pPF, publdr, dwMask)
             : _psel->SetParaFormat(pPF, publdr, dwMask));

        // This is a bit funky, but basically, if the text is empty
        // then we also need to set the default paragraph format
        // (done in the code below).  Thus, if we hit a failure or
        // if the document is not empty, go ahead and return.
        // Otherwise, fall through to the default case.
        if(!lres || GetAdjustedTextLength())
        {
            if(fMatchKbdToPara)
                _psel->MatchKeyboardToPara();
            return lres;
        }
    }

    // No text in document or (wparam & SPF_SETDEFAULT): set default format

    LONG               iPF;                     // Possible new PF index
    CParaFormat        PF = *GetParaFormat(-1); // Local PF to party on
    IParaFormatCache  *pPFCache = GetParaFormatCache();

	PF.Apply(pPF, dwMask);						// Modify copy
	if(FAILED(pPFCache->Cache(&PF, &iPF)))		// Cache modified copy
		return 0;
	pPFCache->Release(Get_iPF());				// Release _iPF regardless of
	Set_iPF(iPF);								// Update default format index
	
	if(PF.IsRtlPara())		
		OrCharFlags(fBIDI, publdr);				// BiDi in backing store

	if(!IsRich() && dwMask & PFM_RTLPARA)		// Changing plain-text default PF
	{
		ItemizeDoc(publdr);						// causing re-itemize the whole doc.

        // (#6503) We cant undo the -1 format change in plaintext and that causes
        // many problems when we undo ReplaceRange event happening before the paragraph
        // switches. We better abandon the whole stack for now. (wchao)
        // -FUTURE- We should create an antievent for -1 PF change.
        ClearUndo(publdr);
    }
    _pdp->UpdateView();
    if (_psel)
        _psel->UpdateCaret(!Get10Mode() || _psel->IsCaretInView());
    if(fMatchKbdToPara)
        _psel->MatchKeyboardToPara();
    return TRUE;
}


////////////////////////////////  System notifications  ////////////////////////////////

LRESULT CTxtEdit::OnSetFocus()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetFocus");

    _fFocus = TRUE;

    // Update our idea of the current keyboard layout
    W32->RefreshKeyboardLayout();

    InitKeyboardFlags();

    if(!_psel)
        return 0;

    // _fMouseDown may sometimes be true.
    // This can happen when somebody steals our focus when we were doing
    // something with the mouse down--like processing a click. Thus, we'll
    // never get the MouseUpMessage.
    if(_fMouseDown)
    {
        TRACEWARNSZ("Getting the focus, yet we think the mouse is down");
    }
    _fMouseDown = FALSE;

    // BUG FIX #5369
    // Special case where we don't have a selection (or a caret). We need
    // to display something on focus so display a caret
    _psel->UpdateCaret(_fScrollCaretOnFocus, _psel->GetCch() == 0);
    _fScrollCaretOnFocus = FALSE;

    _psel->ShowSelection(TRUE);

    // if there is an in-place active object, we need to set the focus to
    // it. (in addition to the work that we do; this maintains compatibility
    // with RichEdit 1.0).
    if(_pobjmgr)
    {
        COleObject *pobj = _pobjmgr->GetInPlaceActiveObject();
        if(pobj)
        {
            IOleInPlaceObject *pipobj;

            if(pobj->GetIUnknown()->QueryInterface(IID_IOleInPlaceObject,
                    (void **)&pipobj) == NOERROR)
            {
                HWND hwnd;
                pipobj->GetWindow(&hwnd);

                if(hwnd)
                    SetFocus(hwnd);
                pipobj->Release();
            }
        }
    }

    TxNotify(EN_SETFOCUS, NULL);
    return 0;
}

LRESULT CTxtEdit::OnKillFocus()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnKillFocus");

    StopMagellanScroll();

    if(_pundo)
        _pundo->StopGroupTyping();

    if(_psel)
    {
        // Scroll back to beginning if necessary
        if (_fScrollCPOnKillFocus)
        {
            bool fHideSelectionLocal = _fHideSelection;

            // cannot hide Selection so cp=0 will be scroll into view.
            _fHideSelection = 0;
            OnSetSel(0, 0);
            _fHideSelection = fHideSelectionLocal;
        }

        _psel->DeleteCaretBitmap(TRUE); // Delete caret bitmap if one exists
        if(_fHideSelection)
            _psel->ShowSelection(FALSE);
    }

    _fFocus = FALSE;
    DestroyCaret();
    TxNotify(EN_KILLFOCUS, NULL);

    _fScrollCaretOnFocus = FALSE;       // Just to be safe, clear this
    return 0;
}


#if defined(DEBUG)
void CTxtEdit::OnDumpPed()
{
#ifndef NOPEDDUMP
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnDumpPed");

    char sz[256];
    CTxtSelection * const psel = GetSel();
    SELCHANGE selchg;

    psel->SetSelectionInfo(&selchg);

    wsprintfA(sz,
        "cchText = %ld      cchTextMost = %ld\r\n"
        "cpSelActive = %ld      cchSel = %ld\r\n"
        "wSelType = %x      # lines = %ld\r\n"
        "SysDefLCID = %lx   UserDefLCID = %lx",
        GetTextLength(),    TxGetMaxLength(),
        psel->GetCp(),  psel->GetCch(),
        selchg.seltyp,  _pdp->LineCount(),
        GetSystemDefaultLCID(), GetUserDefaultLCID()
    );
    Tracef(TRCSEVINFO, "%s", sz);
    MessageBoxA(0, sz, "ED", MB_OK);
#endif                  // NOPEDDUMP
}
#endif                  // DEBUG


///////////////////////////// Scrolling Commands //////////////////////////////////////

HRESULT CTxtEdit::TxHScroll(
    WORD wCode,
    int xPos)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxHScroll");

    if(!fInplaceActive())
        return OLE_E_INVALIDRECT;

    _pdp->HScroll(wCode, xPos);
    return S_OK;
}

LRESULT CTxtEdit::TxVScroll(
    WORD wCode,
    int yPos)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxVScroll");

    return _pdp->VScroll(wCode, yPos);
}

HRESULT CTxtEdit::TxLineScroll(
    LONG cli,
    LONG cch)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxLineScroll");

    // Currently cch does nothing in the following call, so we ignore
    // its translation from cach to cch (need to instantiate an rtp
    // for the current line
    _pdp->LineScroll(cli, cch);
    return S_OK;
}

void CTxtEdit::OnScrollCaret()
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnScrollCaret");

    if(_psel)
	{
		_psel->SetForceScrollCaret(TRUE);
        _psel->UpdateCaret(TRUE);
		_psel->SetForceScrollCaret(FALSE);
	}
}


///////////////////////////////// Editing messages /////////////////////////////////

void CTxtEdit::OnClear(
    IUndoBuilder *publdr)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnClear");

    if(!_psel || TxGetReadOnly())
    {
        Beep();
        return;
    }

    if(_psel->GetCch() && !IsProtected(WM_CLEAR, 0, 0))
    {
        _psel->StopGroupTyping();
        _psel->ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE);
    }
}

void CTxtEdit::Beep()
{
    if(_fAllowBeep)
        MessageBeep(0);
}


///////////////////////////////////  Miscellaneous  ///////////////////////////////////////////

/*
 *  CTxtEdit::ItemizeDoc(publdr, cchRange)
 *
 *  @mfunc
 *      Helper routine to itemize the cchRange size of document content
 *      called by various clients outside CTxtRange.
 */
void CTxtEdit::ItemizeDoc(
    IUndoBuilder *  publdr,
    LONG            cchRange)
{
    // If cchRange = -1, itemize the whole doc
    if (cchRange == -1)
        cchRange = GetTextLength();

	// We wouldnt itemize if the doc only contains a single EOP
	// because we dont want Check_rpPF being called when the -1
	// PF format hasnt been properly established.
	// This is kind of hack, should be removed in the future.
	//
    if(cchRange && GetAdjustedTextLength())
    {                                       // Only itemize if more than
        CTxtRange rg(this, 0, -cchRange);   //  final EOP
        rg.ItemizeRuns(publdr);
    }

#if 0
	// =FUTURE=
	//		Once we open SPF_SETDEFAULT to public. We shall incorporate this code.
	// Basically, one can change the default paragraph reading order at runtime. All
	// PF runs referencing to -1 PF format then need to be reassigned a new paragraph
	// level value and reitemized.(6-10-99, wchao)
	//
    if(cchRange > 0)
    {
        CTxtRange rg(this, 0, -cchRange);

		// -1 PF format may have changed.
		// We shall make sure that the level of each PF run match the reading order
		// before start itemization.
		//
		if (rg.Check_rpPF())
		{
			LONG	cchLeft = cchRange;
			LONG	cchAdvance = 0;
			LONG	cch;

			while (cchLeft > 0)
			{
				rg._rpPF.GetRun(0)->_level._value = rg.IsParaRTL() ? 1 : 0;

				cch = rg._rpPF.GetCchLeft();

				if (!rg._rpPF.NextRun())
					break;		// no more run

				cchAdvance += cch;
				cchLeft -= cch;
			}

			Assert (cchAdvance + cchLeft == cchRange);

			rg._rpPF.AdvanceCp(-cchAdvance);	// fly back to cp = 0
		}

		// Now we rerun itemization
        rg.ItemizeRuns(publdr);
    }
#endif
}

/*
 *  CTxtEdit::OrCharFlags(dwFlags, publdr)
 *
 *  @mfunc
 *      Or in new char flags and activate LineServices and Uniscribe
 *      if complex script chars occur.
 */
void CTxtEdit::OrCharFlags(
    DWORD dwFlags,
    IUndoBuilder* publdr)
{
    // REVIEW: Should we send a notification for LS turn on?
    // Convert dwFlags to new on flags
    dwFlags &= dwFlags ^ _dwCharFlags;
    if(dwFlags)
    {
        _dwCharFlags |= dwFlags;            // Update flags

        dwFlags &= fCOMPLEX_SCRIPT;

        if(dwFlags && (_dwCharFlags & fCOMPLEX_SCRIPT) == dwFlags)
        {
            // REVIEW: Need to check if Uniscribe and LineServices are available...
            OnSetTypographyOptions(TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY);
            ItemizeDoc();

            // FUTURE: (#6838) We cant undo operations before the first itemization.
            ClearUndo(publdr);
            _fAutoKeyboard = IsBiDi();
        }

        UINT    brk = 0;

        if (dwFlags & fNEEDWORDBREAK)
            brk += BRK_WORD;
        if (dwFlags & fNEEDCHARBREAK)
            brk += BRK_CLUSTER;
        if (brk)
        {
            CUniscribe* pusp = Getusp();

            if (!_pbrk && pusp && pusp->IsValid())
            {
                // First time detecting the script that needs word/cluster-breaker
                // (such as Thai, Indic, Lao etc.)
                _pbrk = new CTxtBreaker(this);
                Assert(_pbrk);
            }

            if (_pbrk && _pbrk->AddBreaker(brk))
            {
                // Sync up the breaking array(s)
                _pbrk->Refresh();
            }
        }
    }
}

/*
 *  CTxtEdit::OnSetTypographyOptions(wparam, lparam)
 *
 *  @mfunc
 *      If CTxtEdit isn't a password or accelerator control and wparam
 *      differs from _bTypography, update the latter and the view.
 *
 *  @rdesc
 *      HRESULT = S_OK
 */
HRESULT CTxtEdit::OnSetTypographyOptions(
    WPARAM wparam,      //@parm Typography flags
    LPARAM lparam)      //@parm Typography mask
{
    // Currently only TO_SIMPLELINEBREAK and TO_ADVANCEDTYPOGRAPHY are defined
    if(wparam & ~(TO_SIMPLELINEBREAK | TO_ADVANCEDTYPOGRAPHY))
        return E_INVALIDARG;

    DWORD dwTypography = _bTypography & ~lparam;    // Kill current flag values
    dwTypography |= wparam & lparam;                // Or in new values

    if(_cpAccelerator == -1 && _bTypography != (BYTE)dwTypography)
    {
        _bTypography = (BYTE)dwTypography;
        _pdp->InvalidateRecalc();
        TxInvalidateRect(NULL, FALSE);
    }
    return S_OK;
}

void CTxtEdit::TxGetViewInset(
    LPRECT prc,
    CDisplay *pdp) const
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetViewInset");

    // Get inset, which is in HIMETRIC
    RECT rcHiMetricViewInset;

    if(SUCCEEDED(_phost->TxGetViewInset(&rcHiMetricViewInset)))
    {
        LONG vileft   = rcHiMetricViewInset.left;
        LONG vitop    = rcHiMetricViewInset.top;
        LONG viright  = rcHiMetricViewInset.right;
        LONG vibottom = rcHiMetricViewInset.bottom;

        if(!pdp)                        // If no display is specified,
            pdp = _pdp;                 //  use main display

        AssertSz(pdp->IsValid(), "CTxtEdit::TxGetViewInset Device not valid");

        // Convert HIMETRIC to pixels
        prc->left   = vileft   ? pdp->HimetricXtoDX( vileft ) : 0;
        prc->top    = vitop    ? pdp->HimetricYtoDY(rcHiMetricViewInset.top) : 0;
        prc->right  = viright  ? pdp->HimetricXtoDX(rcHiMetricViewInset.right) : 0;
        prc->bottom = vibottom ? pdp->HimetricYtoDY(rcHiMetricViewInset.bottom) : 0;
    }
    else
    {
        // The call to the host failed. While this is highly improbable, we do
        // want to something reasonably sensible. Therefore, we will just pretend
        // there is no inset and continue.
        ZeroMemory(prc, sizeof(RECT));
    }
}

#if 0
// Interchange horizontal and vertical commands
WORD wConvScroll(WORD wparam)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "wConvScroll");

    switch(wparam)
    {
        case SB_BOTTOM:
            return SB_TOP;

        case SB_LINEDOWN:
            return SB_LINEUP;

        case SB_LINEUP:
            return SB_LINEDOWN;

        case SB_PAGEDOWN:
            return SB_PAGEUP;

        case SB_PAGEUP:
            return SB_PAGEDOWN;

        case SB_TOP:
            return SB_BOTTOM;

        default:
            return wparam;
    }
}
#endif

//
//  helper functions. FUTURE (alexgo) maybe we should get rid of
//  some of these
//

/*  FUTURE (murrays): Unless they are called a lot, the TxGetBit routines
    might be done more compactly as:

BOOL CTxtEdit::TxGetBit(
    DWORD dwMask)
{
    DWORD dwBits = 0;
    _phost->TxGetPropertyBits(dwMask, &dwBits);
    return dwBits != 0;
}

e.g., instead of TxGetSelectionBar(), we use TxGetBit(TXTBIT_SELECTIONBAR).
If they are called a lot (like TxGetSelectionBar()), the bits should probably
be cached, since that saves a bunch of cache misses incurred in going over to
the host.

*/

BOOL CTxtEdit::IsLeftScrollbar() const
{
    if(!_fHost2)
        return FALSE;

    DWORD dwStyle, dwExStyle;

    _phost->TxGetWindowStyles(&dwStyle, &dwExStyle);
    return dwExStyle & WS_EX_LEFTSCROLLBAR;
}

TXTBACKSTYLE CTxtEdit::TxGetBackStyle() const
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetBackStyle");

    TXTBACKSTYLE style = TXTBACK_OPAQUE;
    _phost->TxGetBackStyle(&style);
    return style;
}

BOOL CTxtEdit::TxGetAutoSize() const
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetAutoSize");

    return (_dwEventMask & ENM_REQUESTRESIZE);
}

BOOL CTxtEdit::TxGetAutoWordSel() const
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetAutoWordSel");

    DWORD dwBits = 0;
    _phost->TxGetPropertyBits(TXTBIT_AUTOWORDSEL, &dwBits);
    return dwBits != 0;
}

DWORD CTxtEdit::TxGetMaxLength() const
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetMaxLength");

    // Keep this a DWORD in case client uses a cpMost of 0xFFFFFFFF, which is
    // admittedly a little large, at least for 32-bit address spaces!
    // tomForward would be a more reasonable max length, altho it's also
    // probably larger than possible in a 32-bit address space.
    return _cchTextMost;
}

/*
 *  CTxtEdit::TxSetMaxToMaxText(LONG cExtra)
 *
 *  @mfunc
 *      Set new maximum text length based on length of text and possibly extra chars
 *      to accomodate.
 *
 */
void CTxtEdit::TxSetMaxToMaxText(LONG cExtra)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxSetMaxToMaxText");

    // See if we need to update the text max
    LONG cchRealLen = GetAdjustedTextLength() + cExtra;

    if(_fInOurHost && _cchTextMost < (DWORD)cchRealLen)
        _cchTextMost = cchRealLen;
}

TCHAR CTxtEdit::TxGetPasswordChar() const
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetPasswordChar");

    if(_fUsePassword)
    {
        TCHAR ch = L'*';
        _phost->TxGetPasswordChar(&ch);

        // We don't allow these characters as password chars
        if(ch < 32 || ch == WCH_EMBEDDING)
            return L'*';
        return ch;
    }
    return 0;
}

DWORD CTxtEdit::TxGetScrollBars() const
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetScrollBars");

    DWORD dwScroll;
    _phost->TxGetScrollBars(&dwScroll);
    return dwScroll;
}

LONG CTxtEdit::TxGetSelectionBarWidth() const
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetSelectionBarWidth");

    LONG lSelBarWidth;
    _phost->TxGetSelectionBarWidth(&lSelBarWidth);
    return lSelBarWidth;
}

BOOL CTxtEdit::TxGetWordWrap() const
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetWordWrap");

    DWORD dwBits = 0;
    _phost->TxGetPropertyBits(TXTBIT_WORDWRAP, &dwBits);
    return dwBits != 0;
}

BOOL CTxtEdit::TxGetSaveSelection() const
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetSaveSelection");

    DWORD dwBits = 0;
    _phost->TxGetPropertyBits(TXTBIT_SAVESELECTION, &dwBits);
    return dwBits != 0;
}

/*
 *  CTxtEdit::ClearUndo()
 *
 *  @mfunc  Clear all undo buffers
 */
void CTxtEdit::ClearUndo(
    IUndoBuilder *publdr)   //@parm the current undo context (may be NULL)
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::ClearUndo");

    if(_pundo)
        _pundo->ClearAll();

    if(_predo)
        _predo->ClearAll();

    if(publdr)
        publdr->Discard();
}

/////////////////////////////// ITextHost2 Extensions //////////////////////////////

/*
 *  CTxtEdit::TxIsDoubleClickPending ()
 *
 *  @mfunc  calls host via ITextHost2 to find out if double click is pending.
 *
 *  @rdesc  TRUE/FALSE
 */
BOOL CTxtEdit::TxIsDoubleClickPending()
{
    return _fHost2 ? _phost->TxIsDoubleClickPending() : FALSE;
}

/*
 *  CTxtEdit::TxGetWindow(phwnd)
 *
 *  @mfunc  calls host via ITextHost2 to get current window for this edit
 *          instance.  This is very helpful for OLE object support
 *
 *  @rdesc  HRESULT
 */
HRESULT CTxtEdit::TxGetWindow(
    HWND *phwnd)
{
    return _fHost2 ? _phost->TxGetWindow(phwnd) : E_NOINTERFACE;
}

/*
 *  CTxtEdit::TxSetForegroundWindow ()
 *
 *  @mfunc  calls host via ITextHost2 to make our window the foreground
 *          window. Used to support drag/drop.
 *
 *  @rdesc  HRESULT
 */
HRESULT CTxtEdit::TxSetForegroundWindow()
{
    return _fHost2 ? _phost->TxSetForegroundWindow() : E_NOINTERFACE;
}

/*
 *  CTxtEdit::TxGetPalette()
 *
 *  @mfunc  calls host via ITextHost2 to get current palette
 *
 *  @rdesc  HPALETTE
 */
HPALETTE CTxtEdit::TxGetPalette()
{
    return _fHost2 ? _phost->TxGetPalette() : NULL;
}

/*
 *  CTxtEdit::TxGetFEFlags(pFEFlags)
 *
 *  @mfunc  calls host via ITextHost2 to get current FE settings
 *
 *  @rdesc  HRESULT
 */
HRESULT CTxtEdit::TxGetFEFlags(
    LONG *pFEFlags)
{
    *pFEFlags = 0;                      // In case no ITextHost2 methods

    HRESULT hResult = _fHost2 ? _phost->TxGetFEFlags(pFEFlags) : E_NOINTERFACE;

    if (hResult == NOERROR && Get10Mode())
        *pFEFlags |= tomRE10Mode;

    return hResult;
}

//
//  Event Notification methods
//

/*
 *  CTxtEdit::TxNotify(iNotify, pv)
 *
 *  @mfunc  This function checks bit masks and sends notifications to the
 *          host.
 *
 *  @devnote    Callers should check to see if a special purpose notification
 *          method has already been provided.
 *
 *  @rdesc  S_OK, S_FALSE, or some error
 */
HRESULT CTxtEdit::TxNotify(
    DWORD iNotify,      //@parm Notification to send
    void *pv)           //@parm Data associated with notification
{
    // First, disallow notifications that we handle elsewhere
    Assert(iNotify != EN_SELCHANGE);    //see SetSelectionChanged
    Assert(iNotify != EN_ERRSPACE);     //see SetOutOfMemory
    Assert(iNotify != EN_CHANGE);       //see SetChangedEvent
    Assert(iNotify != EN_HSCROLL);      //see SendScrollEvent
    Assert(iNotify != EN_VSCROLL);      //see SendScrollEvent
    Assert(iNotify != EN_MAXTEXT);      //see SetMaxText
    Assert(iNotify != EN_MSGFILTER);    //this is handled specially
                                        // in TxSendMessage

    // Switch on the event to check masks.

    DWORD dwMask;
    switch(iNotify)
    {
        case EN_DROPFILES:
            dwMask = ENM_DROPFILES;
            goto Notify;

        case EN_PROTECTED:
            dwMask = ENM_PROTECTED;
            goto Notify;

        case EN_REQUESTRESIZE:
            dwMask = ENM_REQUESTRESIZE;
            goto Notify;

        case EN_PARAGRAPHEXPANDED:
            dwMask = ENM_PARAGRAPHEXPANDED;
            goto Notify;

        case EN_IMECHANGE:
            if (!Get10Mode())
                return S_FALSE;
            dwMask = ENM_IMECHANGE;
            goto Notify;

        case EN_UPDATE:
            if (!Get10Mode())
                break;
            dwMask = ENM_UPDATE;
            //FALL THROUGH CASE

        Notify:
            if(!(_dwEventMask & dwMask))
                return NOERROR;



    }

    return _phost->TxNotify(iNotify, pv);
}

/*
 *  CTxtEdit::SendScrollEvent(iNotify)
 *
 *  @mfunc  Sends scroll event if appropriate
 *
 *  @comm   Scroll events must be sent before any view updates have
 *          been requested and only if ENM_SCROLL is set.
 */
void CTxtEdit::SendScrollEvent(
    DWORD iNotify)      //@parm Notification to send
{
    Assert(iNotify == EN_HSCROLL || iNotify == EN_VSCROLL);

    // FUTURE (alexgo/ricksa).  The display code can't really
    // handle this assert yet.  Basically, we're trying to
    // say that scrollbar notifications have to happen
    // _before_ the window is updated.  When we do the
    // display rewrite, try to handle this better.

    // Assert(_fUpdateRequested == FALSE);

    if(_dwEventMask & ENM_SCROLL)
        _phost->TxNotify(iNotify, NULL);
}

/*
 *  CTxtEdit::HandleLinkNotification (msg, wparam, lparam, pfInLink)
 *
 *  @mfunc  Handles sending EN_LINK notifications.
 *
 *  @rdesc  TRUE if the EN_LINK message was sent and
 *          processed successfully.  Typically, that means the
 *          caller should stop whatever processing it was doing.
 */
BOOL CTxtEdit::HandleLinkNotification(
    UINT    msg,        //@parm msg prompting the link notification
    WPARAM  wparam,     //@parm wparam of the message
    LPARAM  lparam,     //@parm lparam of the message
    BOOL *  pfInLink)   //@parm if non-NULL, indicate if over a link
{
    if(pfInLink)
        *pfInLink = FALSE;

    if(!(_dwEventMask & ENM_LINK) || !_fInPlaceActive)
        return FALSE;

    HITTEST Hit;
    POINT   pt = {LOWORD(lparam), HIWORD(lparam)};

    if(msg == WM_SETCURSOR)
    {
        GetCursorPos(&pt);
        if(!_phost->TxScreenToClient(&pt))
            return FALSE;
    }

    LONG cp = _pdp->CpFromPoint(pt, NULL, NULL, NULL, FALSE, &Hit);

    if(Hit != HT_Link)                  // Not a hyperlink
        return FALSE;

    LONG      cpMin, cpMost;            // It's a hyperlink
    ENLINK    enlink;
    CTxtRange rg(this, cp, 0);

    ZeroMemory(&enlink, sizeof(enlink));
    if (fInOurHost())
    {
        GetWindow((LONG *) &enlink.nmhdr.hwndFrom);
        enlink.nmhdr.idFrom = GetWindowLong(enlink.nmhdr.hwndFrom, GWL_ID);
    }
    enlink.nmhdr.code = EN_LINK;

    if(pfInLink)
        *pfInLink = TRUE;
    rg.Expander(tomLink, TRUE, NULL, &cpMin, &cpMost);

    // Fill in ENLINK data structure for our EN_LINK
    // callback asking client what we should do
    enlink.msg = msg;
    enlink.wParam = wparam;
    enlink.lParam = lparam;
    enlink.chrg.cpMin  = GetAcpFromCp(cpMin);
    enlink.chrg.cpMost = GetAcpFromCp(cpMost);

    return _phost->TxNotify(EN_LINK, &enlink) == S_FALSE;
}

/*
 *  CTxtEdit::QueryUseProtection(prg, msg, wparam, lparam)
 *
 *  @mfunc  sends EN_PROTECTED to the host, asking if we should continue
 *  to honor the protection on a given range of characters
 *
 *  @rdesc  TRUE if protection should be honored, FALSE otherwise
 */
BOOL CTxtEdit::QueryUseProtection(
    CTxtRange *prg,     //@parm range to check for
    UINT    msg,        //@parm msg used
    WPARAM  wparam,     //@parm wparam of the msg
    LPARAM  lparam)     //@parm lparam of the msg
{
    LONG        cpMin, cpMost;
    ENPROTECTED enp;
    BOOL        fRet = FALSE;
    CCallMgr *  pcallmgr = GetCallMgr();

    Assert(_dwEventMask & ENM_PROTECTED);

    if( pcallmgr->GetInProtected() ||
        _fSuppressNotify)       // Don't ask host if we don't want to send notification
        return FALSE;

    pcallmgr->SetInProtected(TRUE);

    ZeroMemory(&enp, sizeof(ENPROTECTED));

    prg->GetRange(cpMin, cpMost);

    enp.msg = msg;
    enp.wParam = wparam;
    enp.lParam = lparam;
    enp.chrg.cpMin  = GetAcpFromCp(cpMin);
    enp.chrg.cpMost = GetAcpFromCp(cpMost);

    if(_phost->TxNotify(EN_PROTECTED, &enp) == S_FALSE)
        fRet = TRUE;

    pcallmgr->SetInProtected(FALSE);

    return fRet;
}


#ifdef DEBUG
//This is a debug api used to dump the document runs.
//If a pointer to the ped is passed, it is saved and
//used.  If NULL is passed, the previously saved ped
//pointer is used.  This allows the "context" to be
//setup by a function that has access to the ped and
//DumpDoc can be called lower down in a function that
//does not have access to the ped.
extern "C" {
void DumpStory(void *ped)
{
    static CTxtEdit *pedSave = (CTxtEdit *)ped;
    if(pedSave)
    {
        CTxtStory * pStory = pedSave->GetTxtStory();
        if(pStory)
            pStory->DbgDumpStory();

        CObjectMgr * pobjmgr = pedSave->GetObjectMgr();
        if(pobjmgr)
            pobjmgr->DbgDump();
    }
}
}
#endif

/*
 *  CTxtEdit::TxGetDefaultCharFormat (pCF)
 *
 *  @mfunc  helper function to retrieve character formats from the
 *          host.  Does relevant argument checking
 *
 *  @rdesc  HRESULT
 */
HRESULT CTxtEdit::TxGetDefaultCharFormat(
    CCharFormat *pCF,       //@parm Character format to fill in
    DWORD &      dwMask)    //@parm Mask supplied by host or default
{
    HRESULT hr = pCF->InitDefault(0);
    dwMask = CFM_ALL2;

    const CHARFORMAT2 *pCF2 = NULL;

    if (_phost->TxGetCharFormat((const CHARFORMAT **)&pCF2) != NOERROR ||
        !IsValidCharFormatW(pCF2))
    {
        return hr;
    }

    dwMask  = pCF2->dwMask;
    DWORD dwMask2 = 0;
    if(pCF2->cbSize == sizeof(CHARFORMAT))
    {
        // Suppress CHARFORMAT2 specifications (except for Forms^3 disabled)
        dwMask  &= fInOurHost() ? CFM_ALL : (CFM_ALL | CFM_DISABLED);
        dwMask2 = CFM2_CHARFORMAT;
    }

    CCharFormat CF;                         // Transfer external CHARFORMAT(2)
    CF.Set(pCF2, 1200);                     //  parms to internal CCharFormat
    return pCF->Apply(&CF, dwMask, dwMask2);
}

/*
 *  CTxtEdit::TxGetDefaultParaFormat (pPF)
 *
 *  @mfunc  helper function to retrieve  paragraph formats.  Does
 *          the relevant argument checking.
 *
 *  @rdesc  HRESULT
 */
HRESULT CTxtEdit::TxGetDefaultParaFormat(
    CParaFormat *pPF)       //@parm Paragraph format to fill in
{
    HRESULT hr = pPF->InitDefault(0);

    const PARAFORMAT2 *pPF2 = NULL;

    if (_phost->TxGetParaFormat((const PARAFORMAT **)&pPF2) != NOERROR ||
        !IsValidParaFormat(pPF2))
    {
        return hr;
    }

    DWORD dwMask  = pPF2->dwMask;
    if(pPF2->cbSize == sizeof(PARAFORMAT))  // Suppress all but PARAFORMAT
    {                                       //  specifications
        dwMask &= PFM_ALL;
        dwMask |= PFM_PARAFORMAT;           // Tell Apply() that PARAFORMAT
    }                                       //  was used

    CParaFormat PF;                         // Transfer external PARAFORMAT(2)
    PF.Set(pPF2);                           //  parms to internal CParaFormat
    return pPF->Apply(&PF, dwMask);         // Apply parms identified by dwMask
}


/*
 *  CTxtEdit::SetContextDirection(fUseKbd)
 *
 *  @mfunc
 *      Determine the paragraph direction and/or alignment based on the context
 *      rules (direction/alignment follows first strong character in the
 *      control) and apply this direction and/or alignment to the default
 *      format.
 *
 *  @comment
 *      Context direction only works for plain text controls. Note that
 *      this routine only switches the default CParaFormat to RTL para if it
 *      finds an RTL char. IsBiDi() will automatically be TRUE for this case,
 *      since each char is checked before entering the backing store.
 */
void CTxtEdit::SetContextDirection(
    BOOL fUseKbd)       //@parm Use keyboard to set context when CTX_NEUTRAL
{
    // It turns out that Forms^3 can send EM_SETBIDIOPTIONS even for non BiDi controls.
    // AssertSz(IsBiDi(), "CTxtEdit::SetContextDirection called for nonBiDi control");
    if(IsRich() || !IsBiDi() || _nContextDir == CTX_NONE && _nContextAlign == CTX_NONE)
        return;

    LONG    cch = GetTextLength();
    CTxtPtr tp(this, 0);
    TCHAR   ch = tp.GetChar();
    WORD    ctx = CTX_NEUTRAL;
    BOOL    fChanged = FALSE;

    // Find first strongly directional character
    while (cch && !IsStrongDirectional(MECharClass(ch)))
    {
        ch = tp.NextChar();
        cch--;
    }

    // Set new context based on first strong character
    if(cch)
        ctx = IsRTL(MECharClass(ch)) ? CTX_RTL : CTX_LTR;

    // Has context direction or alignment changed?
    if (_nContextDir   != CTX_NONE && _nContextDir   != ctx ||
        _nContextAlign != CTX_NONE && _nContextAlign != ctx)
    {
        // Start with current default CParaFormat
        CParaFormat PF = *GetParaFormat(-1);

        // If direction has changed...
        if(_nContextDir != CTX_NONE && _nContextDir != ctx)
        {
            if(ctx == CTX_LTR || ctx == CTX_RTL || fUseKbd)
            {
                if (ctx == CTX_RTL ||
                    ctx == CTX_NEUTRAL && W32->IsBiDiLcid(LOWORD(GetKeyboardLayout(0))))
                {
                    PF._wEffects |= PFE_RTLPARA;
                }
                else
                {
                    Assert(ctx == CTX_LTR || ctx == CTX_NEUTRAL);
                    PF._wEffects &= ~PFE_RTLPARA;
                }
                fChanged = TRUE;
            }
            _nContextDir = ctx;
        }

        // If the alignment has changed...
        if(_nContextAlign != CTX_NONE && _nContextAlign != ctx)
        {
            if(PF._bAlignment != PFA_CENTER)
            {
                if(ctx == CTX_LTR || ctx == CTX_RTL || fUseKbd)
                {
                    if (ctx == CTX_RTL ||
                        ctx == CTX_NEUTRAL && W32->IsBiDiLcid(LOWORD(GetKeyboardLayout(0))))
                    {
                        PF._bAlignment = PFA_RIGHT;
                    }
                    else
                    {
                        Assert(ctx == CTX_LTR || ctx == CTX_NEUTRAL);
                        PF._bAlignment = PFA_LEFT;
                    }
                }
            }
            _nContextAlign = ctx;
        }

        // Modify default CParaFormat
        IParaFormatCache *pPFCache = GetParaFormatCache();
        LONG iPF;

        if(SUCCEEDED(pPFCache->Cache(&PF, &iPF)))
        {
            pPFCache->Release(Get_iPF());   // Release _iPF regardless of
            Set_iPF(iPF);                   // Update default format index

            if (fChanged)
                ItemizeDoc(NULL);

            // Refresh display
            Assert(_pdp);
            if(!_pdp->IsPrinter())
            {
                _pdp->InvalidateRecalc();
                TxInvalidateRect(NULL, FALSE);
            }
        }
    }

    // Reset the first strong cp.
    _cpFirstStrong = tp.GetCp();

    Assert(_nContextDir != CTX_NONE || _nContextAlign != CTX_NONE);
}

/*
 *  CTxtEdit::GetAdjustedTextLength ()
 *
 *  @mfunc
 *      retrieve text length adjusted for the default end-of-document marker
 *
 *  @rdesc
 *      Text length without final EOP
 *
 *  @devnote
 *      For Word and RichEdit compatibility, we insert a CR or CRLF at the
 *      end of every new rich-text control.  This routine calculates the
 *      length of the document _without_ this final EOD marker.
 *
 *      For 1.0 compatibility, we insert a CRLF.  However, TOM (and Word)
 *      requires that we use a CR, from 2.0 on, we do that instead.
 */
LONG CTxtEdit::GetAdjustedTextLength()
{
    LONG cchAdjText = GetTextLength();

    Assert(!Get10Mode() || IsRich());       // No RE10 plain-text controls

    if(IsRich())
        cchAdjText -= fUseCRLF() ? 2 : 1;   // Subtract cch of final EOP

    return cchAdjText;
}

/*
 *  CTxtEdit::Set10Mode()
 *
 *  @mfunc
 *      Turns on the 1.0 compatibility mode bit.  If the control is
 *      rich text, it already has a default 'CR' at the end, which
 *      needs to turn into a CRLF for compatibility with RichEdit 1.0.
 *
 *  @devnote
 *      This function should only be called _immediately_ after
 *      creation of text services and before all other work.  There
 *      are Asserts to help ensure this.  Remark (murrays): why not
 *      allow the change provided the control is empty except for the
 *      final CR?
 *
 *      FUTURE: we might want to split _f10Mode into three flags:
 *      1) _fMapCps     // API cp's are MBCS and need conversion to Unicode
 *      2) _fCRLF       // Use CRLFs for EOPs instead of CRs
 *      3) _f10Mode     // All other RE 1.0 compatibility things
 *
 *      Category 3 includes 1) automatically using FR_DOWN in searches,
 *      2) ignoring direction in CDataTransferObj::EnumFormatEtc(),
 *      3) not resetting _fModified when switching to a new doc,
 */
void CTxtEdit::Set10Mode()
{
    CCallMgr    callmgr(this);
    _f10Mode = TRUE;

    // Make sure nothing important has happened to the control.
    // If these values are non-NULL, then somebody is probably trying
    // to put us into 1.0 mode after we've already done work as
    // a 2.0 control.
    Assert(GetTextLength() == cchCR);
    Assert(_psel == NULL);
    Assert(_fModified == NULL);

    SetRichDocEndEOP(cchCR);

    if(!_pundo)
        CreateUndoMgr(1, US_UNDO);

    if(_pundo)
        ((CUndoStack *)_pundo)->EnableSingleLevelMode();

    // Turn off dual font
    _fDualFont = FALSE;

    // Turn on auto sizing for NTFE systems
    if (OnWinNTFE())
        _fAutoFontSizeAdjust = TRUE;
}

/*
 *  CTxtEdit::SetRichDocEndEOP(cchToReplace)
 *
 *  @mfunc  Place automatic EOP at end of a rich text document.
 */
void CTxtEdit::SetRichDocEndEOP(
    LONG cchToReplace)
{
    CRchTxtPtr rtp(this, 0);

    // Assume this is a 2.0 Doc
    LONG cchEOP = cchCR;
    const WCHAR *pszEOP = szCR;

    if(_f10Mode)
    {
        // Reset update values for a 1.0 doc
        cchEOP = cchCRLF;
        pszEOP = szCRLF;
    }

    rtp.ReplaceRange(cchToReplace, cchEOP, pszEOP, NULL, -1);

    _fModified = FALSE;
    _fSaved = TRUE;
    GetCallMgr()->ClearChangeEvent();
}

/*
 *  CTxtEdit::PopAndExecuteAntiEvent(pundomgr, void *pAE)
 *
 *  @mfunc  Freeze display and execute anti-event
 *
 *  @rdesc  HRESULT from IUndoMgr::PopAndExecuteAntiEvent
 */
HRESULT CTxtEdit::PopAndExecuteAntiEvent(
    IUndoMgr *pundomgr, //@parm Undo manager to direct call to
    void  *pAE)         //@parm AntiEvent for undo manager
{
    HRESULT hr;
    // Let stack based classes clean up before restoring selection
    {
        CFreezeDisplay fd(_pdp);
        CSelPhaseAdjuster   selpa(this);

        hr = pundomgr->PopAndExecuteAntiEvent(pAE);
    }

    if(_psel)
    {
        // Once undo/redo has been executed, flush insertion point formatting
        _psel->Update_iFormat(-1);
        _psel->Update(TRUE);
    }
    return hr;
}

/*
 *  CTxtEdit::PasteDataObjectToRange(pdo, prg, cf, rps, publdr, dwFlags)
 *
 *  @mfunc  Freeze display and paste object
 *
 *  @rdesc  HRESULT from IDataTransferEngine::PasteDataObjectToRange
 */
HRESULT CTxtEdit::PasteDataObjectToRange(
    IDataObject *   pdo,
    CTxtRange *     prg,
    CLIPFORMAT      cf,
    REPASTESPECIAL *rps,
    IUndoBuilder *  publdr,
    DWORD           dwFlags)
{
    HRESULT hr = _ldte.PasteDataObjectToRange(pdo, prg, cf, rps, publdr,
        dwFlags);

    if(_psel)
        _psel->Update(TRUE);           // now update the caret

    return hr;
}

/*
 *  GetECDefaultHeightAndWidth (pts, hdc, lZoomNumerator, lZoomDenominator,
 *                  yPixelsPerInch, pxAveWidth, pxOverhang, pxUnderhang)
 *
 *  @mfunc  Helper for host to get ave char width and height for default
 *          character set for the control.
 *
 *  @rdesc  Height of default character set
 *
 *  @devnote:
 *          This really only s/b called by the window's host.
 */
LONG GetECDefaultHeightAndWidth(
    ITextServices *pts,         //@parm ITextServices to conver to CTxtEdit.
    HDC hdc,                    //@parm DC to use for retrieving the font.
    LONG lZoomNumerator,        //@parm Zoom numerator
    LONG lZoomDenominator,      //@parm Zoom denominator
    LONG yPixelsPerInch,        //@parm Pixels per inch for hdc
    LONG *pxAveWidth,           //@parm Optional ave width of character
    LONG *pxOverhang,           //@parm Optional overhang
    LONG *pxUnderhang)          //@parm Optional underhang
{
    CLock lock;                 // Uses global (shared) FontCache
    // Convert the text-edit ptr
    CTxtEdit *ped = (CTxtEdit *) pts;

    // Get the CCcs that has all the information we need
    yPixelsPerInch = MulDiv(yPixelsPerInch, lZoomNumerator, lZoomDenominator);
    CCcs *pccs = fc().GetCcs(ped->GetCharFormat(-1), yPixelsPerInch);

    if(!pccs)
        return 0;

    if(pxAveWidth)
        *pxAveWidth = pccs->_xAveCharWidth;

    if(pxOverhang)
        *pxOverhang = pccs->_xOverhang;     // Return overhang

    if(pxUnderhang)
        *pxUnderhang = pccs->_xUnderhang;   // Return underhang

    SHORT   yAdjustFE = pccs->AdjustFEHeight(!ped->fUseUIFont() && ped->_pdp->IsMultiLine());
    LONG yHeight = pccs->_yHeight + (yAdjustFE << 1);

    pccs->Release();                        // Release the CCcs
    return yHeight;
}

/*
 *  CTxtEdit::TxScrollWindowEx (dx, dy, lprcScroll, lprcClip, hrgnUpdate,
 *                                  lprcUpdate, fuScroll)
 *  @mfunc
 *      Request Text Host to scroll the content of the specified client area
 *
 *  @comm
 *      This method is only valid when the control is in-place active;
 *      calls while inactive may fail.
 */
void CTxtEdit::TxScrollWindowEx (
    INT     dx,             //@parm Amount of horizontal scrolling
    INT     dy,             //@parm Amount of vertical scrolling
    LPCRECT lprcScroll,     //@parm Scroll rectangle
    LPCRECT lprcClip,       //@parm Clip rectangle
    HRGN    hrgnUpdate,     //@parm Handle of update region
    LPRECT  lprcUpdate,     //@parm Update rectangle
    UINT    fuScroll)       //@parm Scrolling flags
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEEXTERN, "CTxtEdit::TxScrollWindowEx");

    if(_fInPlaceActive)
    {
#if !defined(NOMAGELLAN)
        CMagellanBMPStateWrap bmpOff(*this, NULL);
#endif

        _phost->TxScrollWindowEx(dx, dy, lprcScroll, lprcClip,
                        hrgnUpdate, lprcUpdate, fuScroll);

        // Tell all objects that they may need to update their position
        // RECTs if scrolling occurred.

        if(_pobjmgr)
        {
            RECT rcClient;

            if(!lprcScroll)
            {
                TxGetClientRect(&rcClient);
                lprcScroll = &rcClient;
            }
            _pobjmgr->ScrollObjects(dx, dy, lprcScroll);
        }
    }
}

/*
 *  CTxtEdit::GetAcpFromCp (cp)
 *
 *  @mfunc
 *      Get API cp (acp) from Unicode cp in this text instance. The API cp
 *      may be Unicode, in which case it equals cp, or MBCS, in which case
 *      it's greater than cp if any Unicode characters preceding cp convert
 *      to double-byte characters.  An MBCS cp is the BYTE index of a character
 *      relative to the start of the story, while a Unicode cp is the character
 *      index.  The values are the same if all charsets are represented by
 *      SBCS charsets, e.g., ASCII.  If all characters are represented by
 *      double-byte characters, then acp = 2*cp.
 *
 *  @rdesc
 *      MBCS Acp from Unicode cp in this text instance
 *
 *  @devnote
 *      This could be made more efficient by having the selection maintain
 *      the acp that corresponds to its _rpTX._cp, provided RE 1.0 mode is
 *      active.  Alternatively CTxtEdit could have a _prg that tracks this
 *      value, but at a higher cost (17 DWORDs instead of 1 per instance).
 *
 *      FUTURE: we might want to have a conversion-mode state instead of just
 *      _f10Mode, since some people might want to know use MBCS cp's even in
 *      RE 3.0.  If so, use the corresponding new state flag instead of
 *      Get10Mode() in the following.
 */
LONG CTxtEdit::GetAcpFromCp(
    LONG cp,                //@parm Unicode cp to convert to MBCS cp
    BOOL fPrecise)          //@parm fPrecise flag to get byte count for MBCS
{
    if(!(IsFE() && (fCpMap() || fPrecise))) // RE 2.0 and higher use char-count
        return cp;                          //  cp's, while RE 1.0 uses byte
                                            //  counts
                                            //  bPrecise is for Ansi Apps that want byte counts
                                            //  (e.g. Outlook Subject line)

    CRchTxtPtr rtp(this);                   // Start at cp = 0
    return rtp.GetCachFromCch(cp);
}

LONG CTxtEdit::GetCpFromAcp(
    LONG acp,               //@parm MBCS cp to convert to Unicode cp
    BOOL fPrecise)          //@parm fPrecise flag to get Unicode cp for MBCS
{
    if( acp == -1 || !(IsFE() && (fCpMap() || fPrecise)))
        return acp;

    CRchTxtPtr rtp(this);                   // Start at cp = 0
    return rtp.GetCchFromCach(acp);
}


/*
 *  CTxtEdit::GetViewKind (plres)
 *
 *  @mfunc
 *      get view mode
 *
 *  @rdesc
 *      HRESULT = (plres) ? NOERROR : E_INVALIDARG
 *
 *  @devnote
 *      This could be a TOM property method (along with SetViewMode())
 */
HRESULT CTxtEdit::GetViewKind(
    LRESULT *plres)     //@parm Out parm to receive view mode
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetViewKind");

    if(!plres)
        return E_INVALIDARG;

    *plres = IsInOutlineView() ? VM_OUTLINE : VM_NORMAL;
    return NOERROR;
}

/*
 *  CTxtEdit::SetViewKind (Value)
 *
 *  @mfunc
 *      Turn outline mode on or off
 *
 *  @rdesc
 *      HRESULT = IsRich() ? NOERROR : S_FALSE
 *
 *  @devnote
 *      This could be a TOM property method (along with GetViewMode())
 */
HRESULT CTxtEdit::SetViewKind(
    long Value)     //@parm Turn outline mode on/off for Value nonzero/zero
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::SetViewKind");

    if(!IsRich() || !_pdp->IsMultiLine())
        return S_FALSE;

    Value = (Value == VM_OUTLINE);          // Convert to 1/0
    if(_fOutlineView != Value)
    {
        HCURSOR hcur = TxSetCursor(LoadCursor(0, IDC_WAIT), NULL);
        CTxtSelection *psel = GetSel();

        _fOutlineView = (WORD)Value;
        if(!GetAdjustedTextLength())        // No text in control: in outline
        {                                   //  view, use Heading 1; in normal
            CParaFormat PF;                 //  view, use Normal style
            PF._sStyle = (SHORT)(IsInOutlineView()
                      ? STYLE_HEADING_1 : STYLE_NORMAL);
            psel->SetParaStyle(&PF, NULL, PFM_STYLE);
        }
        else
        {
            // There is text. Make sure there is paragraph formatting.
            _psel->Check_rpPF();
        }

        psel->CheckIfSelHasEOP(-1, 0);
        _pdp->UpdateView();
        psel->Update(TRUE);
        TxSetCursor(hcur, NULL);
    }
    return NOERROR;
}

/*
 *  CTxtEdit::GetViewScale (pValue)
 *
 *  @mfunc
 *      get view zoom scale in percent
 *
 *  @rdesc
 *      HRESULT = (pValue) ? NOERROR : E_INVALIDARG
 *
 *  @devnote
 *      This could be a TOM property method (along with SetViewScale())
 */
HRESULT CTxtEdit::GetViewScale(
    long *pValue)       //@parm Get % zoom factor
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetViewScale");

    if(!pValue)
        return E_INVALIDARG;

    *pValue = 100;
    if(GetZoomNumerator() && GetZoomDenominator())
        *pValue = (100*GetZoomNumerator())/GetZoomDenominator();

    return NOERROR;
}

/*
 *  CTxtEdit::SetViewScale (Value)
 *
 *  @mfunc
 *      Set zoom numerator equal to the scale percentage Value and
 *      zoom denominator equal to 100
 *
 *  @rdesc
 *      NOERROR
 *
 *  @devnote
 *      This could be a TOM property method (along with GetViewScale())
 */
HRESULT CTxtEdit::SetViewScale(
    long Value)     //@parm Set view scale factor
{
    TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::SetViewScale");

    if((unsigned)Value > 2000)
        return E_INVALIDARG;

    SetZoomNumerator(Value);
    SetZoomDenominator(100);
    return NOERROR;
}

/*
 *  CTxtEdit::UpdateOutline()
 *
 *  @mfunc
 *      Update selection and screen after ExpandOutline() operation
 *
 *  @comm
 *      This method is only valid when the control is in-place active;
 *      calls while inactive may fail.
 */
HRESULT CTxtEdit::UpdateOutline()
{
    Assert(IsInOutlineView());

    GetSel()->Update(FALSE);
    TxInvalidateRect(NULL, TRUE);
    return NOERROR;
}

/*
 *  CTxtEdit::MoveSelection(lparam, publdr)
 *
 *  @mfunc
 *      Move selected text up/down by the number of paragraphs given by
 *      LOWORD(lparam).
 *
 *  @rdesc
 *      TRUE iff movement occurred
 */
HRESULT CTxtEdit::MoveSelection (
    LPARAM lparam,          //@parm # paragraphs to move by
    IUndoBuilder *publdr)   //@parm undo builder to receive antievents
{
    TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtRange::MoveSelection");

    CFreezeDisplay  fd(_pdp);
    CTxtSelection * psel = GetSel();
    LONG            cch;
    LONG            cchSel = psel->GetCch();
    LONG            cpMin, cpMost;
    LONG            cpSel = psel->GetCp();
    IDataObject *   pdo = NULL;
    CTxtRange       rg(*psel);
    LONG            cpNext = 0;
    LONG            cpCur = 0;
    BOOL            fDeleteCR = FALSE;

    if(publdr)
        publdr->StopGroupTyping();

    rg.Expander(tomParagraph, TRUE, NULL, &cpMin, &cpMost);
    CPFRunPtr rp(rg);
    cch = rp.FindExpanded();            // Include subordinate paras
    if(cch < 0)
        cch = tomForward;
    rg.SetExtend(TRUE);
    rg.Advance(cch);
    cpMost = rg.GetCpMost();

    if(lparam > 0 && cpMost == GetTextLength())
    {
        Beep();                         // Already at end
        return S_FALSE;
    }

    HRESULT hr = _ldte.RangeToDataObject(&rg, SF_RTF, &pdo);
    if(hr != NOERROR)
        goto error;

    if(lparam > 0)
        psel->EndOf(tomParagraph, FALSE, NULL);
    else
        psel->StartOf(tomParagraph, FALSE, NULL);

    cpCur = psel->GetCp();
    hr = psel->Move(tomParagraph, lparam, NULL);
    if(psel->GetCp() == cpCur)
    {
        psel->Set(cpSel, cchSel);
        Beep();
        goto error;
    }

    // Since psel->Move() calls psel->Update(), the selection is forced
    // to be in noncollapsed text. Going backward, this might leave the
    // selection just before the EOP of a paragraph, instead of being at the
    // start of the paragraph where it should be.  Going forward it may have
    // tried to reach the EOD, but was adjusted backward. This case gets
    // a bit awkward...
    if(psel->GetCp() < cpCur)                   // Going backward: be sure
        psel->StartOf(tomParagraph, FALSE, NULL);//  end up at start of para

    else if(!psel->_rpTX.IsAfterEOP())          // Going forward and sel
    {                                           //  adjusted backward
        psel->SetExtend(FALSE);
        psel->Advance(tomForward);              // Go to final CR, insert a CR
        CTxtRange rgDel(*psel);                 //  use psel because UI
        rgDel.ReplaceRange(1, szCR, publdr, SELRR_REMEMBERRANGE);
        psel->Advance(1);
        fDeleteCR = TRUE;                       // Remember to delete it
    }

    cpCur = psel->GetCp();
    hr = _ldte.PasteDataObjectToRange(pdo, psel, 0, NULL,
                                      publdr, PDOR_NONE);
    if(hr != NOERROR)
        goto error;

    if(fDeleteCR)                               // Delete CR (final CR becomes
    {                                           //  CR for this para). Don't
        CTxtRange rgDel(*psel);                 //  use psel because UI
        Assert(rgDel._rpTX.IsAfterEOP());       //  restricts it's ability to
        rgDel.Delete(tomCharacter, -1, &cch);   //  delete
    }

    cpNext = psel->GetCp();
    psel->Set(cpCur, 0);
    psel->CheckOutlineLevel(publdr);
    psel->Set(cpNext, 0);
    psel->CheckOutlineLevel(publdr);

    // Now set selection anti-events. If selection preceded paste point,
    // subtract its length from redo position, since selection will get
    // deleted if we are doing a DRAGMOVE within this instance.
    cch = cpMost - cpMin;                       // cch of rg
    if(cpSel < cpCur)
        cpNext -= cch;

    psel->Set(psel->GetCp() + fDeleteCR, cch);  // Include final CR

    // rg.ReplaceRange won't delete final CR, so remember if it's included
    fDeleteCR = rg.GetCpMost() == GetTextLength();
    rg.ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE);

    if(fDeleteCR)                               // Needed to delete final CR
        rg.DeleteTerminatingEOP(publdr);        // Delete one immediately
                                                //  before it instead
    rg.CheckOutlineLevel(publdr);
    if(publdr)
    {
        HandleSelectionAEInfo(this, publdr, cpSel, cchSel, cpNext, cch,
                              SELAE_FORCEREPLACE);
    }
    hr = NOERROR;

error:
    if(pdo)
        pdo->Release();
    return hr;
}

/*
 *  CTxtEdit::SetReleaseHost
 *
 *  @mfunc  Handles notification that edit control must keep its
 *          reference to the host alive.
 *
 *  @rdesc  None.
 */
void CTxtEdit::SetReleaseHost()
{
    _phost->AddRef();
    _fReleaseHost = TRUE;
}

#if !defined(NOMAGELLAN)
/*
 *  CTxtEdit::HandleMouseWheel(wparam, lparam)
 *
 *  @mfunc  Handles scrolling as a result of rotating a mouse roller wheel.
 *
 *  @rdesc  LRESULT
 */
LRESULT CTxtEdit::HandleMouseWheel(
    WPARAM wparam,
    LPARAM lparam)
{
    // This bit of global state is OK
    static LONG gcWheelDelta = 0;
    short zdelta = (short)HIWORD(wparam);
    BOOL fScrollByPages = FALSE;

    // Cancel middle mouse scrolling if it's going.
    OnTxMButtonUp(0, 0, 0);

    // Handle zoom or data zoom
    if((wparam & MK_CONTROL) == MK_CONTROL)
    {
        // bug fix 5760
        // prevent zooming if control is NOT rich or
        // is a single line control
        if (!_pdp->IsMultiLine())
            return 0;

        LONG lViewScale;
        GetViewScale(&lViewScale);
        lViewScale += (zdelta/WHEEL_DELTA) * 10;    // 10% per click
        if(lViewScale <= 500 && lViewScale >= 10)   // Word's limits
        {
            SetViewScale(lViewScale);
            _pdp->UpdateView();
        }
        return 0;
    }

    if(wparam & (MK_SHIFT | MK_CONTROL))
        return 0;

    gcWheelDelta += zdelta;

    if(abs(gcWheelDelta) >= WHEEL_DELTA)
    {
        LONG cLineScroll = W32->GetRollerLineScrollCount();
        if(cLineScroll != -1)
            cLineScroll *= abs(gcWheelDelta)/WHEEL_DELTA;

        gcWheelDelta %= WHEEL_DELTA;

        // -1 means scroll by pages; so simply call page up/down.
        if(cLineScroll == -1)
        {
            fScrollByPages = TRUE;
            if(_pdp)
                _pdp->VScroll(zdelta < 0 ? SB_PAGEDOWN : SB_PAGEUP, 0);
        }
        else
        {
            mouse.MagellanRollScroll(_pdp, zdelta, cLineScroll,
                SMOOTH_ROLL_NUM, SMOOTH_ROLL_DENOM, TRUE);
        }

        // notify through the messagefilter that we scrolled
        if(_dwEventMask & ENM_SCROLLEVENTS)
        {
            MSGFILTER msgfltr;
            ZeroMemory(&msgfltr, sizeof(MSGFILTER));
            msgfltr.msg    = WM_VSCROLL;
            msgfltr.wParam = fScrollByPages ?
                                (zdelta < 0 ? SB_PAGEDOWN: SB_PAGEUP):
                                (zdelta < 0 ? SB_LINEDOWN: SB_LINEUP);

            // We don't check the result of this call --
            // it's not a message we received and we're not going to
            // process it any further
            _phost->TxNotify(EN_MSGFILTER, &msgfltr);
        }
        return TRUE;
    }
    return 0;
}
#endif