/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */


#ifdef EDITOR

#include "editor.h"
#include "prefapi.h"
#include "intl_csi.h"

#define CONTENT_TYPE "Content-Type"

// Maximum length of "positional text" - see GetPositionalText(). 
#ifdef XP_WIN16
const int32 MAX_POSITIONAL_TEXT = 64000;
#else
const int32 MAX_POSITIONAL_TEXT =0x7FFFFFF0;
#endif

// Maximum length of text that can be pasted.
#ifdef XP_WIN16
#define MAX_PASTE_SIZE 32760
#else
#define MAX_PASTE_SIZE (1<<30)
#endif

#include "prefapi.h"

#define GET_COL TRUE
#define GET_ROW FALSE

#if defined( DEBUG_shannon )
int32	gEditorReflow = TRUE;
#endif

EDT_CharacterData *CEditBuffer::m_pCopyStyleCharacterData = 0;

//-----------------------------------------------------------------------------
// CEditBuffer
//-----------------------------------------------------------------------------

CEditElement* CEditBuffer::CreateElement(PA_Tag* pTag, CEditElement *pParent){
    CEditElement *pElement;
    // this is a HACK.  We don't understand the tag but we want
    //  to make sure that we can re-gurgitate it to layout.
    //  Save the parameter data so we can spew it back out.
    char *locked_buff;

    PA_LOCK(locked_buff, char *, pTag->data);
    if( locked_buff && *locked_buff != '>'){
        pElement = new CEditElement(pParent,pTag->type,locked_buff);
    }
    else {
        pElement = new CEditElement(pParent,pTag->type);
    }
    PA_UNLOCK(pTag->data);
    return pElement;
}

CParseState* CEditBuffer::GetParseState() {
    if ( m_parseStateStack.IsEmpty() ) {
        PushParseState();
    }
    return m_parseStateStack.Top();
}


/////////////////////////////////////////////
//EDITBUFFER/////////////////////////////////
/////////////////////////////////////////////

XP_Bool CEditBuffer::m_bEdtBufPrefInitialized=FALSE;
XP_Bool CEditBuffer::m_bMoveCursor; //no initial value should be depended on
//these variables are set by the pref_registercallback function "PrefCallback";


//callback from a change in the preferences. we must then reinitialize these prefs
int CEditBuffer::PrefCallback(const char *, void *)//static
{
  m_bEdtBufPrefInitialized=FALSE;
  CEditBuffer::InitializePrefs();//static
  return TRUE;
}


void CEditBuffer::InitializePrefs()//static
{
  if (!m_bEdtBufPrefInitialized) //this is ok if this is allready initialized, 
  {                              //this means that someone has changed 
                                 //them and we need to reset them 
    PREF_RegisterCallback("editor", CEditBuffer::PrefCallback, NULL);//no instance data, only setting statics.  we are all of the same mind here
    PREF_GetBoolPref("editor.page_updown_move_cursor",&m_bMoveCursor);
  }
  m_bEdtBufPrefInitialized=TRUE;
}



void CEditBuffer::PushParseState(){
    XP_Bool bInBody = FALSE;
    if ( ! m_parseStateStack.IsEmpty() ) {
        bInBody = m_parseStateStack.Top()->InBody();
    }
    CParseState* state = new CParseState();
    state->Reset();
    if ( bInBody ) {
        state->StartBody(); // If we were in the body before, we're still in the body.
    }
    m_parseStateStack.Push(state);
}

void CEditBuffer::PopParseState(){
    if ( m_parseStateStack.IsEmpty() ) {
        XP_ASSERT(FALSE);
    }
    else {
        CParseState* state = m_parseStateStack.Pop();
        delete state;
    }
}

void CEditBuffer::ResetParseStateStack(){
    while ( ! m_parseStateStack.IsEmpty() ) {
        PopParseState();
    }
}

//
// stolen from layout
//
#define DEF_TAB_WIDTH       8

#define REMOVECHAR(p) XP_MEMMOVE( p, p+1, XP_STRLEN(p) );

PRIVATE
void edt_ExpandTab(char *p, intn n){
    if( n != 1 ){
        XP_MEMMOVE( p+n, p+1, XP_STRLEN(p));
    }
    XP_MEMSET( p, ' ', n);
}


//
// Parse the text into a series of tags.  Each tag is at most one line in
//  length.
//
intn CEditBuffer::NormalizePreformatText(pa_DocData *pData, PA_Tag* pTag,
            intn status ){
    char *pText;
    intn retVal = OK_CONTINUE;
    m_bInPreformat = TRUE;

    PA_LOCK(pText, char *, pTag->data);


    //
    // Calculate how big a buffer we are going to need to normalize this
    //  preformatted text.
    //
    int iTabCount = 0;
    int iLen = 0;
    int iNeedLen;
    int iSpace;

    while( *pText ){
        if( *pText == TAB ){
            iTabCount++;
        }
        pText++;
        iLen++;
    }

    PA_UNLOCK(pTag->data);
    iNeedLen = iLen + iTabCount * DEF_TAB_WIDTH+1;

    pTag->data =  (PA_Block) PA_REALLOC( pTag->data, iNeedLen );
    pTag->data_len = iNeedLen;
    PA_LOCK(pText, char *, pTag->data);

    // LTNOTE: total hack, but make sure the buffer is 0 terminated...  Probably has
    //  bad internation ramifications.. Oh Well...
    // JHPNOTE: I'll say this is a total hack. We used to write at [data_len], which
    // smashed beyond allocated memory. As far as I can see there's no need to write here,
    // since the string is known to be zero terminated now, and all the operations
    // below keep the string zero terminated. Never the less, in the interest of keeping
    // tradition alive (and because I don't want to take the chance of introducing a
    // bug, I'll leave the correct, but unnescessary, code in place.

    pText[iNeedLen-1] = 0;
    pText[iLen] = 0;

    char* pBuf = pText;
    //
    // Now actually Normalize the preformatted text
    //
    while( *pText ){
        switch( *pText ){
        // Look for CF LF, if you see it, just treat it as LF
        case CR:
            if( pText[1] == LF ){
                REMOVECHAR(pText);
                break;
            }
            else {
                *pText = LF;
            }

        case LF:
            pText++;
            m_preformatLinePos = 0;
            break;

        case VTAB:
        case FF:
            REMOVECHAR(pText);
            break;

        case TAB:
            iSpace = DEF_TAB_WIDTH - (m_preformatLinePos % DEF_TAB_WIDTH);

            edt_ExpandTab( pText, iSpace );
            pText += iSpace;
            m_preformatLinePos += iSpace;
            break;


        default:
            pText++;
            m_preformatLinePos++;
        }
    }

    XP_Bool bBreak = FALSE;
    while( *pBuf && retVal == OK_CONTINUE ){
        pText = pBuf;
        bBreak = FALSE;

        // scan for end of line.
        while( *pText && *pText != '\n' ){
            pText++;
        }


        PA_Tag *pNewTag = XP_NEW( PA_Tag );
        XP_BZERO( pNewTag, sizeof( PA_Tag ) );
        pNewTag->type = P_TEXT;

        char save = *pText;
        *pText = 0;
        edt_SetTagData(pNewTag, pBuf );
        *pText = save;

        // include the new line
        if( *pText == '\n' ){
            pText++;
            bBreak = TRUE;
        }

        pBuf = pText;
        retVal = EDT_ProcessTag( pData, pNewTag, status );
        if( bBreak ){
            pNewTag = XP_NEW( PA_Tag );
            XP_BZERO( pNewTag, sizeof( PA_Tag ) );
            pNewTag->type = P_LINEBREAK;
            retVal = EDT_ProcessTag( pData, pNewTag, status );
        }
    }

    PA_UNLOCK(pTag->data);
    m_bInPreformat = FALSE;
    return ( retVal == OK_CONTINUE) ? OK_IGNORE : retVal ;
}


static char *anchorHrefParams[] = {
    PARAM_HREF,
    0
};

static char *bodyParams[] = {
    PARAM_BACKGROUND,
    PARAM_BGCOLOR,
    PARAM_TEXT,
    PARAM_LINK,
    PARAM_ALINK,
    PARAM_VLINK,
    PARAM_NOSAVE,
    0
};

PRIVATE
void NormalizeEOLsInString(char* pStr){
    int state = 0;
    char* pOut = pStr;
    for(;; ) {
        char c = *pStr++;
        if ( c == '\0' ) break;

        int charClass = 0;
        if ( c == '\r' ) charClass = 1;
        else if ( c == '\n') charClass = 2;
        switch ( state ) {
        case 0: /* Normal */
            switch ( charClass ) {
            case 0: /* Normal char */
                *pOut++ = c;
                break;
            case 1: /* CR */
                state = 1;
                *pOut++ = '\n';
                break;
            case 2: /* LF */
                state = 2;
                *pOut++ = '\n';
                break;
            }
            break;
        case 1: /* Just saw a CR */
            switch ( charClass ) {
            case 0: /* Normal char */
                *pOut++ = c;
                state = 0;
                break;
            case 1: /* CR */
                *pOut++ = '\n';
                break;
            case 2: /* LF */
                state = 0;
                /* Swallow it, back to normal */
                break;
            }
            break;
        case 2: /* Just saw a LF */
            switch ( charClass ) {
            case 0: /* Normal char */
                *pOut++ = c;
                state = 0;
                break;
            case 1: /* CR */
                /* Swallow it, back to normal */
                state = 0;
                break;
            case 2: /* LF */
                *pOut++ = '\n';
                break;
            }
            break;
        }
    }
    *pOut = '\0';
}


PRIVATE
void NormalizeEOLsInTag(PA_Tag* pTag){
    // Convert the end-of-lines into \n characters.
    if( pTag->data ){
        char* pStr;
        PA_LOCK(pStr, char*, pTag->data);
        NormalizeEOLsInString(pStr);
        pTag->data_len = strlen( pStr );
        PA_UNLOCK( pTag->data );
    }
}

PRIVATE
XP_Bool IsComment(PA_Tag* pTag){
    // Is this a comment tag?
    XP_Bool result = FALSE;
    if ( pTag->type == P_UNKNOWN ) {
        if( pTag->data ){
            char* pStr;
            const char* kComment = "!";
            unsigned int commentLen = XP_STRLEN(kComment);
            PA_LOCK(pStr, char*, pTag->data);
            if ( pStr && XP_STRLEN(pStr) >= commentLen && XP_STRNCASECMP(pStr, kComment, commentLen) == 0 ) {
                result = TRUE;
            }
            PA_UNLOCK( pTag->data );
        }
    }
    return result;
}

PRIVATE
XP_Bool IsEmptyText(PA_Tag* pTag){
    // Is this empty white-space text?
    // Takes advantage of white-space being
    // one byte in all character sets,
    // and non-white-space characters always
    // start with a non-white-space byte.
    XP_Bool result = FALSE;
    if ( pTag->type == P_TEXT ) {
        result = TRUE;
        if( pTag->data ){
            char* pStr;
            PA_LOCK(pStr, char*, pTag->data);
            if ( pStr ) {
                while ( *pStr ) {
                    if ( ! XP_IS_SPACE(*pStr) ) {
                        result = FALSE;
                        break;
                    }
                    pStr++;
                }
            }
            PA_UNLOCK( pTag->data );
        }
    }
    return result;
}


PRIVATE
XP_Bool IsDocTypeTag(PA_Tag* pTag){
    // Is this a DocType tag?
    XP_Bool result = FALSE;
    if ( pTag->type == P_UNKNOWN ) {
        if( pTag->data ){
            char* pStr;
            const char* kDocType = "!DOCTYPE";
            unsigned int docTypeLen = XP_STRLEN(kDocType);
            PA_LOCK(pStr, char*, pTag->data);
            if ( XP_STRLEN(pStr) >= docTypeLen && XP_STRNCASECMP(pStr, kDocType, docTypeLen) == 0 ) {
                result = TRUE;
            }
            PA_UNLOCK( pTag->data );
        }
    }
    return result;
}

// To do - combine with HandleSelectionComment

XP_Bool CEditBuffer::IsSelectionComment(PA_Tag* pTag){
    XP_Bool result = FALSE;
    if ( pTag && pTag->type == P_UNKNOWN) {
        if ( pTag->data ){
            char* kStart = "!-- " EDT_SELECTION_START_COMMENT;
            char* kEnd = "!-- " EDT_SELECTION_END_COMMENT;
            char* pStr;
            PA_LOCK(pStr, char*, pTag->data);
            XP_Bool bStartComment = XP_STRLEN(pStr) >= XP_STRLEN(kStart) &&
                XP_STRNCASECMP(pStr, kStart, XP_STRLEN(kStart)) == 0;
            XP_Bool bEndComment = XP_STRLEN(pStr) >= XP_STRLEN(kEnd) &&
                XP_STRNCASECMP(pStr, kEnd,  XP_STRLEN(kEnd)) == 0;
            if ( bStartComment || bEndComment ) {
                result = TRUE;
            }
            PA_UNLOCK( pTag->data );
        }
    }
    return result;
}

XP_Bool CEditBuffer::HandleSelectionComment(PA_Tag* pTag, CEditElement*& pElement, intn& retval){
    XP_Bool result = FALSE;
    if ( pTag->type == P_UNKNOWN) {
        if ( pTag->data ){
            char* kStart = "!-- " EDT_SELECTION_START_COMMENT;
            char* kEnd = "!-- " EDT_SELECTION_END_COMMENT;
            char* kStartPlus = "!-- " EDT_SELECTION_START_COMMENT "+";
            char* kEndPlus = "!-- " EDT_SELECTION_END_COMMENT "+";
            char* pStr;
            PA_LOCK(pStr, char*, pTag->data);
            XP_Bool bStartComment = XP_STRLEN(pStr) >= XP_STRLEN(kStart) &&
                XP_STRNCASECMP(pStr, kStart, XP_STRLEN(kStart)) == 0;
            XP_Bool bEndComment = XP_STRLEN(pStr) >= XP_STRLEN(kEnd) &&
                XP_STRNCASECMP(pStr, kEnd,  XP_STRLEN(kEnd)) == 0;
            if ( bStartComment || bEndComment )
            {
                result = TRUE;
                pElement = NULL;
                if ( bStartComment && ! m_pStartSelectionAnchor ) {
                    pElement = m_pStartSelectionAnchor = new CEditInternalAnchorElement(m_pCreationCursor);
                    m_bStartSelectionStickyAfter = XP_STRLEN(pStr) >= XP_STRLEN(kStartPlus) &&
                        XP_STRNCASECMP(pStr, kStartPlus, XP_STRLEN(kStartPlus)) == 0;
                }
                else if ( bEndComment && ! m_pEndSelectionAnchor ) {
                    pElement = m_pEndSelectionAnchor = new CEditInternalAnchorElement(m_pCreationCursor);
                    m_bEndSelectionStickyAfter = XP_STRLEN(pStr) >= XP_STRLEN(kEndPlus) &&
                        XP_STRNCASECMP(pStr, kEndPlus, XP_STRLEN(kEndPlus)) == 0;
                }
                else {
                    retval = OK_IGNORE;
                }
            }
            PA_UNLOCK( pTag->data );
        }
    }

    if ( pElement ) {
        m_pCreationCursor = pElement->GetParent();
    }
    return result;
}

XP_Bool CEditBuffer::ShouldAutoStartBody(PA_Tag* pTag, int16 csid){
    CParseState* pParseState = GetParseState();
    if ( pParseState->InBody() ) {
        return FALSE;
    }
    if ( pParseState->m_inJavaScript ||
        pParseState->m_bInTitle) {
        return FALSE;
    }
    if (IsSelectionComment(pTag)){
        return TRUE;
    }
    if ( ! BitSet( edt_setAutoStartBody, pTag->type ) ) {
        return FALSE;
    }
    if ( pTag->type != P_TEXT ) {
        return TRUE;
    }
    XP_Bool result = FALSE;
    // Pure white space doesn't start a body.
    // This code takes advantage of the fact that, in all supported
    // character sets, white-space characters are single byte.
    if( pTag->data ){
        char* pStr;
        PA_LOCK(pStr, char*, pTag->data);
        if ( pStr ) {
            while ( *pStr ) {
                if (! XP_IS_SPACE(*pStr) ) {
                    result = TRUE;
                    break;
                }
                pStr = INTL_NextChar(csid, pStr);
            }
        }
        PA_UNLOCK( pTag->data );
    }
    return result;
}

intn CEditBuffer::ParseTag(pa_DocData *pData, PA_Tag* pTag, intn status){
    intn retVal = OK_CONTINUE;

    // Examine the tag  before it's modified or replaced.
    XP_Bool bRecordTag = ! IsComment(pTag) && ! IsEmptyText(pTag);
    TagType iTagType = pTag->type;
    XP_Bool bTagIsEnd = (XP_Bool) pTag->is_end;

    NormalizeEOLsInTag(pTag);

    /* P_STRIKE is a synonym for P_STRIKEOUT. Since pre-3.0 versions of
     * Navigator don't recognize P_STRIKE ( "<S>" ), we switch it to
     * P_STRIKEOUT ( "<STRIKE>" ). Also, it would complicate the
     * editor internals to track two versions of the same tag.
     * We test here so that we catch both the beginning and end tag.
     */
    if ( pTag->type == P_STRIKE ) {
        pTag->type = P_STRIKEOUT;
    }

    /* Many HTML pages don't include BODY tags. The first appearance of a
     * body-only tag is our signal to start a body.
     */
    if ( ShouldAutoStartBody(pTag, GetRAMCharSetID()) ) {
        GetParseState()->StartBody();
    }

    if( pTag->is_end && !BitSet( edt_setUnsupported, pTag->type ) &&
            pTag->type != P_UNKNOWN ){
        retVal = ParseEndTag(pTag);
    }
    else {
        retVal = ParseOpenTag(pData, pTag, status);
    }

    // Maintain the m_iLastTagType
    if ( bRecordTag ) {
        m_iLastTagType = iTagType;
        m_bLastTagIsEnd = bTagIsEnd;
    }

    return retVal;
}

intn CEditBuffer::ParseEndTag(PA_Tag* pTag){
    intn retVal = OK_CONTINUE;
 
    // If this is an end tag, match backwardly to the appropriate tag.
    //
    CEditElement *pPop = m_pCreationCursor;
    if( pTag->type == P_HEAD ){
        WriteClosingScriptTag();
        // RecordTag(pTag, TRUE);
        return retVal;
    }
    if( pTag->type == P_BODY ){
        // We ignore </BODY> in the Mail Compose window because we have
        // to deal with nested HTML documents.
        // We don't ignore it in the Page Compose window because
        // we want to preserve <FRAMESET>s and other tags that appear
        // after the </BODY>.
        if ( !IsComposeWindow() ) {
            GetParseState()->EndBody();
        }
        return retVal;
    }
    if( pTag->type == P_TITLE ){
        GetParseState()->m_bInTitle = FALSE;
        return retVal;
    }
    // libparse is nice enough to only call us with end tags if they
    // match the actual brute tag that we're in. So we
    // don't have to check if the end SCRIPT or STYLE tag matches the
    // corresponding start tag. (Other tags are passed as two text
    // tags, one for the opening '<', and the other for the rest of
    // the tag.)
    if( GetParseState()->m_inJavaScript &&
        pTag->type == GetParseState()->m_inJavaScript ){
        WriteClosingScriptTag();
        if ( GetParseState()->InBody() ){
            PopParseState();
        }
        return retVal;
    }
    if( BitSet( edt_setTextContainer, pTag->type ) || BitSet( edt_setList, pTag->type ) ){
        GetParseState()->bLastWasSpace = TRUE;
    }

    while( pPop && pTag->type != pPop->GetType() ){
        pPop = pPop->GetParent();
    }

    // if this is character formatting, pop the stack.
    if( pTag->type == P_CENTER || pTag->type == P_DIVISION ){
        if( !GetParseState()->m_formatAlignStack.IsEmpty() ){
            GetParseState()->m_formatAlignStack.Pop();
        }
        // If we are closing alignment, it forces a line break (or new
        //  paragraph)
        if( m_pCreationCursor->IsContainer() ){
            m_pCreationCursor = m_pCreationCursor->GetParent();
        }
        GetParseState()->bLastWasSpace = TRUE;

        // And for divisions, pop the division information.
        if( pTag->type == P_DIVISION && pPop && pTag->type == pPop->GetType() && pPop->GetParent() != 0 ) {
            m_pCreationCursor = pPop->GetParent();
        }
    }
    else if( BitSet( edt_setCharFormat, pTag->type ) && !GetParseState()->m_formatTypeStack.IsEmpty()){
        // The navigator is forgiving about the nesting of style end tags, e.g. </b> vs. </i>.
        // So you can say, for example,
        // <b>foo<i>bar</b>baz</i> and it will work as if you had said:
        // <b>foo<i>bar</i>baz</b>.

        // LTNOTE: This crashes sometimes??
        //delete GetParseState()->m_pNextText;
        GetParseState()->m_pNextText = 0;
        GetParseState()->m_formatTypeStack.Pop();
        GetParseState()->m_pNextText = GetParseState()->m_formatTextStack.Pop();

        // </a> pops all the HREFs. So if you say:
        // <a href="a">foo<a href="b">bar</a>baz, it's as if you said:
        // <a href="a">foo</a><a href="b">bar</a>baz
        // For fun, try this:
        // <a href="a">foo<a name="b">bar</a>baz
        // The 'bar' will look like it is a link, but it won't act like a link if you
        // press on it.
        // Anyway, we emulate this mess by clearing the hrefs out of the format text stack when we
        // close any P_ANCHOR tag

        if ( pTag->type == P_ANCHOR ) {
            if ( GetParseState()->m_pNextText ) {
                GetParseState()->m_pNextText->SetHREF( ED_LINK_ID_NONE );
            }
            TXP_PtrStack_CEditTextElement& textStack = GetParseState()->m_formatTextStack;
            for ( int i = 0; i < textStack.Size(); i++) {
                if ( textStack[i] ) {
                    textStack[i]->SetHREF( ED_LINK_ID_NONE );
                }
            }
        }
    }
    else if( pTag->type == P_PARAGRAPH ) {
        if( pPop && pTag->type == pPop->GetType() && pPop->IsContainer() ) {
            CEditContainerElement *pCont = pPop->Container();
            if ( pCont ) {
                pCont->m_bHasEndTag = TRUE;
            }
        }

        if( pPop && pTag->type == pPop->GetType() && pPop->GetParent() != 0 ) {
            m_pCreationCursor = pPop->GetParent();
        }
    }
    else if( pPop && pTag->type == pPop->GetType() && pPop->GetParent() != 0 ) {
        m_pCreationCursor = pPop->GetParent();
    }
    else{
        // We have an error.  Ignore it for now.
        //DebugBreak();
    }
    if ( pPop && (pTag->type == P_TABLE_HEADER || pTag->type == P_TABLE_DATA || pTag->type == P_CAPTION) ) {
        PopParseState();
    }
    return retVal;
}

void CEditBuffer::WriteClosingScriptTag()
{
    // Write out the closing Script tag.
    TagType type = GetParseState()->m_inJavaScript;
    if ( ! type ) return;
    char* tagName =  EDT_TagString(type);
    CStreamOutMemory* pOut = GetParseState()->GetStream();
    pOut->Write( "</", 2);
    pOut->Write( tagName, XP_STRLEN(tagName));
    pOut->Write( ">\n", 2);
    GetParseState()->m_inJavaScript = 0;
    if ( GetParseState()->InBody() ){
        RecordJavaScriptAsUnknownTag(pOut);
    }
}

PRIVATE void edt_MakeAbsoluteUsingBaseTag(CEditElement *pCreationCursor,char **ppURL) {
  // Don't do anything if NULL.
  if (!(ppURL && *ppURL)) {
    return;
  }

  // Find enclosing mail quote.
  CEditListElement *pMQuote = pCreationCursor->GetMailQuote();
  if (pMQuote) {
    EDT_ListData *pListData = pMQuote->GetData();
    if (pListData) {
      // If mail quote has a <BASE HREF=> tag.
      if (pListData->pBaseURL && *pListData->pBaseURL) {
        char *pAbs = NET_MakeAbsoluteURL(pListData->pBaseURL,*ppURL);
        if (pAbs) {
          XP_FREE(*ppURL);
          *ppURL = pAbs;
        }
      }
      CEditListElement::FreeData(pListData);
    }
  }
}

intn CEditBuffer::ParseOpenTag(pa_DocData *pData, PA_Tag* pTag, intn status){
    char *pStr;
    CEditElement* pElement = 0;
    CEditElement *pContainer;
    PA_Block buff;
    intn retVal = OK_CONTINUE;
    INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_pContext);
    int16 win_csid = INTL_GetCSIWinCSID(c);

    //
    // make sure all solo tags are placed in a paragraph.
    //
    if( pTag->type != P_TEXT
            && (BitSet( edt_setSoloTags,  pTag->type  )
                    || BitSet( edt_setUnsupported,  pTag->type ) )
            && !BitSet( edt_setTextContainer,  m_pCreationCursor->GetType()  )
            && m_pCreationCursor->FindContainer() == 0 ){
        //m_pCreationCursor = new CEditElement(m_pCreationCursor,P_PARAGRAPH);
        m_pCreationCursor = CEditContainerElement::NewDefaultContainer( m_pCreationCursor,
                    GetCurrentAlignment() );

    }
    if ( BitSet( edt_setUnsupported, pTag->type ) ) {
        ParseUnsupportedTag(pTag, pElement, retVal);
    }
    else
        switch(pTag->type){

        case P_LINK:
            ParseLink(pTag, pElement, retVal);
            break;

        case P_TEXT:
            if( pTag->data ){
                PA_LOCK(pStr, char*, pTag->data);

                if( GetParseState()->m_bInTitle ){
                    AppendTitle( pStr );
                    PA_UNLOCK( pTag->data );
                    return retVal;
                }
                else if( GetParseState()->m_inJavaScript ){
                    GetParseState()->GetStream()->Write( pStr, XP_STRLEN( pStr ) );
                    PA_UNLOCK( pTag->data );
                    return OK_IGNORE;
                }
                else if( !BitSet( edt_setFormattedText, m_pCreationCursor->GetType() )
#ifdef USE_SCRIPT
                        && !(GetParseState()->m_pNextText->m_tf & (TF_SERVER|TF_SCRIPT|TF_STYLE))
#endif
                        && !m_pCreationCursor->InFormattedText() ){
                    NormalizeText( pStr );
                }
                else {
                    if( !m_bInPreformat ){
                        //
                        // calls EDT_ProcessTag iteratively
                        //
                        return NormalizePreformatText( pData, pTag, status );
                    }
                }
                // we probably need to adjust the length in the parse tag.
                //  it works without it, but I'm not sure why.

                // if the text didn't get reduced away.
                // Strip white space in tables
                XP_Bool bGoodText = *pStr && ! (( XP_IS_SPACE(*pStr) && pStr[1] == '\0' ) &&
                    BitSet( edt_setIgnoreWhiteSpace,  m_pCreationCursor->GetType()));
                if( bGoodText ){
                    // if this text is not within a container, make one.
                    if( !BitSet( edt_setTextContainer,  m_pCreationCursor->GetType()  )
                            && m_pCreationCursor->FindContainer() == 0 ){
                        m_pCreationCursor = CEditContainerElement::NewDefaultContainer( m_pCreationCursor,
                                GetCurrentAlignment() );
                    }
                    CEditTextElement *pNew = GetParseState()->m_pNextText->CopyEmptyText(m_pCreationCursor);
                    pNew->SetText( pStr, IsMultiSpaceMode(), win_csid );

                    // create a new text element.  It adds itself as a child
                    pElement = pNew;
                }
                else {
                    retVal =  OK_IGNORE;
                }
                PA_UNLOCK( pTag->data );
            }
            break;

        //
        // Paragraphs are always inserted at the level of the current
        //  container.
        //
        case P_HEADER_1:
        case P_HEADER_2:
        case P_HEADER_3:
        case P_HEADER_4:
        case P_HEADER_5:
        case P_HEADER_6:
        case P_DESC_TITLE:
        case P_ADDRESS:
        case P_LIST_ITEM:
        case P_DESC_TEXT:
        case P_PARAGRAPH:
        ContainerCase:
            pContainer = m_pCreationCursor->FindContainerContainer();
            if( pContainer ){
                m_pCreationCursor = pContainer;
            }
            else {
                m_pCreationCursor = m_pRoot;
            }
            m_pCreationCursor = pElement = new CEditContainerElement( m_pCreationCursor,
                    pTag, GetRAMCharSetID(), GetCurrentAlignment() );
            break;

        case P_UNUM_LIST:
        case P_NUM_LIST:
        case P_DESC_LIST:
        case P_MENU:
        case P_DIRECTORY:
        case P_BLOCKQUOTE:
        case P_MQUOTE: // Mailing quotation.
            pContainer = m_pCreationCursor->FindContainerContainer();
            if( pContainer ){
                m_pCreationCursor = pContainer;
            }
            else {
                m_pCreationCursor = m_pRoot;
            }
            m_pCreationCursor = pElement = new CEditListElement( m_pCreationCursor, pTag, GetRAMCharSetID());
            break;

        case P_BODY:
            ParseBodyTag(pTag);
            break;

        case P_HEAD:
            // Silently eat this tag.
            break;

    // character formatting
        case P_STYLE:
        case P_SCRIPT:
        case P_SERVER:
            if( GetParseState()->InBody() ){
                PushParseState();
            }
            RecordTag(pTag, FALSE);
            GetParseState()->m_inJavaScript = pTag->type;
            return OK_IGNORE;
            /*
            // Save tag parameters.
            if ( GetParseState()->m_pNextText ) {
               char *locked_buff;
               PA_LOCK(locked_buff, char *, pTag->data );
               GetParseState()->m_pNextText->SetScriptExtra(locked_buff);
               PA_UNLOCK(pTag->data);
            }
            m_preformatLinePos = 0;
            // intentionally fall through.
            */
        case P_BOLD:
        case P_STRONG:
        case P_ITALIC:
        case P_EMPHASIZED:
        case P_VARIABLE:
        case P_CITATION:
        case P_FIXED:
        case P_CODE:
        case P_KEYBOARD:
        case P_SAMPLE:
        case P_SUPER:
        case P_SUB:
        case P_NOBREAK:
        case P_STRIKEOUT:
        case P_SPELL:
        case P_INLINEINPUT:
        case P_INLINEINPUTTHICK:
        case P_INLINEINPUTDOTTED:
        case P_STRIKE:
        case P_UNDERLINE:
        case P_BLINK:
            GetParseState()->m_formatTextStack.Push(GetParseState()->m_pNextText);
            GetParseState()->m_formatTypeStack.Push(pTag->type);
            GetParseState()->m_pNextText = GetParseState()->m_pNextText->CopyEmptyText();
            GetParseState()->m_pNextText->m_tf |= edt_TagType2TextFormat(pTag->type);
            break;

        case P_SMALL:
            GetParseState()->m_formatTextStack.Push(GetParseState()->m_pNextText);
            GetParseState()->m_formatTypeStack.Push(pTag->type);
            GetParseState()->m_pNextText = GetParseState()->m_pNextText->CopyEmptyText();
            if( GetParseState()->m_pNextText->m_tf & TF_FONT_SIZE ){
                GetParseState()->m_pNextText->SetFontSize(GetParseState()->m_pNextText->GetFontSize()-1);
            }
            else {
                GetParseState()->m_pNextText->SetFontSize(m_pCreationCursor->GetDefaultFontSize()-1);
            }
            break;

        case P_BIG:
            GetParseState()->m_formatTextStack.Push(GetParseState()->m_pNextText);
            GetParseState()->m_formatTypeStack.Push(pTag->type);
            GetParseState()->m_pNextText = GetParseState()->m_pNextText->CopyEmptyText();
            if( GetParseState()->m_pNextText->m_tf & TF_FONT_SIZE ){
                GetParseState()->m_pNextText->SetFontSize(GetParseState()->m_pNextText->GetFontSize()+1);
            }
            else {
                GetParseState()->m_pNextText->SetFontSize(m_pCreationCursor->GetDefaultFontSize()+1);
            }
            break;

        case P_BASEFONT:
             GetParseState()->m_baseFontSize = edt_FetchParamInt(pTag, PARAM_SIZE, 3, GetRAMCharSetID());
             GetParseState()->m_pNextText->SetFontSize(GetParseState()->m_baseFontSize);
            break;

        case P_FONT:
            {
                GetParseState()->m_formatTextStack.Push(GetParseState()->m_pNextText);
                GetParseState()->m_formatTypeStack.Push(pTag->type);
                GetParseState()->m_pNextText = GetParseState()->m_pNextText->CopyEmptyText();
                buff = PA_FetchParamValue(pTag, PARAM_SIZE, win_csid);
                if (buff != NULL) {
                    char *size_str;
                    PA_LOCK(size_str, char *, buff);
                    GetParseState()->m_pNextText->SetFontSize(LO_ChangeFontSize(
                            GetParseState()->m_baseFontSize, size_str ));
                    PA_UNLOCK(buff);
                    PA_FREE(buff);
                }
				buff = PA_FetchParamValue(pTag, PARAM_FACE, win_csid);
				if (buff != NULL)
				{
                    char* str;
				    PA_LOCK(str, char *, buff);
                    GetParseState()->m_pNextText->SetFontFace(str);
				    PA_UNLOCK(buff);
				    PA_FREE(buff);
				}

				int16 iWeight = (int16) edt_FetchParamInt(pTag, PARAM_FONT_WEIGHT, ED_FONT_WEIGHT_NORMAL, GetRAMCharSetID());
				if (iWeight != ED_FONT_WEIGHT_NORMAL)
				{
                    GetParseState()->m_pNextText->SetFontWeight(iWeight);
				}

				int16 iPointSize = (int16) edt_FetchParamInt(pTag, PARAM_POINT_SIZE, ED_FONT_POINT_SIZE_DEFAULT, GetRAMCharSetID());
				if (iPointSize != ED_FONT_POINT_SIZE_DEFAULT)
				{
                    GetParseState()->m_pNextText->SetFontPointSize(iPointSize);
				}

                ED_Color c = edt_FetchParamColor( pTag, PARAM_COLOR, GetRAMCharSetID() );
                if( c.IsDefined() ){
                    GetParseState()->m_pNextText->SetColor( c );
                }
            }
            break;

        case P_ANCHOR:
            // push the formatting stack in any case so we properly pop
            //  it in the future.
            GetParseState()->m_formatTextStack.Push(GetParseState()->m_pNextText);
            GetParseState()->m_formatTypeStack.Push(pTag->type);
            GetParseState()->m_pNextText = GetParseState()->m_pNextText->CopyEmptyText();

            pStr = edt_FetchParamString(pTag, PARAM_HREF, win_csid);
            if( pStr != NULL ){
                // If in a mail quote with a <BASE> tag, make absolute.
                edt_MakeAbsoluteUsingBaseTag(m_pCreationCursor,&pStr);

                // collect extra stuff..
                // LTNOTE:
                char* pExtra = edt_FetchParamExtras( pTag, anchorHrefParams, win_csid );
                ED_LinkId id = linkManager.Add( pStr, pExtra );
                GetParseState()->m_pNextText->SetHREF( id );
                linkManager.Free( id );         // reference counted.
                XP_FREE( pStr );
                XP_FREEIF( pExtra );
            }

            pStr = edt_FetchParamString(pTag, PARAM_NAME, win_csid);
            if( pStr ){
                // We don't catch this case above because by default
                //  an anchor is a character formatting
                if( m_pCreationCursor->FindContainer() == 0 ){
                    m_pCreationCursor = CEditContainerElement::NewDefaultContainer(
                            m_pCreationCursor, GetCurrentAlignment() );
                }
                pElement = new CEditTargetElement(m_pCreationCursor, pTag);
                m_pCreationCursor = pElement->GetParent();
                XP_FREE( pStr );
            }
            break;

        case P_IMAGE:
        case P_NEW_IMAGE:
            {
                pElement = new CEditImageElement(m_pCreationCursor, pTag, GetRAMCharSetID(),
                            GetParseState()->m_pNextText->GetHREF());
                
                // If in a mail quote with a <BASE> tag, make SRC and LOWSRC absolute.
                EDT_ImageData *pImageData = pElement->Image()->GetImageData();
                if (pImageData) {
                  // regular image
                  edt_MakeAbsoluteUsingBaseTag(m_pCreationCursor,&pImageData->pSrc);
                  // low resolution
                  edt_MakeAbsoluteUsingBaseTag(m_pCreationCursor,&pImageData->pLowSrc);

                  pElement->Image()->SetImageData(pImageData);
                  EDT_FreeImageData(pImageData);
                }
 
                m_pCreationCursor = pElement->GetParent();
                GetParseState()->bLastWasSpace = FALSE;
                break;
            }

        case P_HRULE:
            {
            pElement = new CEditHorizRuleElement(m_pCreationCursor, pTag);
            m_pCreationCursor = pElement->GetParent();
            GetParseState()->bLastWasSpace = TRUE;
            break;
            }

        case P_PLAIN_TEXT:
        case P_PLAIN_PIECE:
        case P_LISTING_TEXT:
        case P_PREFORMAT:
            pTag->type = P_PREFORMAT;
            m_preformatLinePos = 0;
            goto ContainerCase;


        //
        // pass these tags on so the document looks right
        //
        case P_CENTER:
            // Forces a new paragraph unless the current paragraph is empty
            if( m_pCreationCursor->IsContainer() && m_pCreationCursor->GetChild() ){
                m_pCreationCursor = m_pCreationCursor->GetParent();
            }
            GetParseState()->m_formatAlignStack.Push(ED_ALIGN_CENTER);
            pContainer = m_pCreationCursor->FindContainer();
            if( pContainer ){
                pContainer->Container()->AlignIfEmpty( ED_ALIGN_CENTER );
            }
            break;

        case P_DIVISION:
            // Forces a new paragraph unless the current paragraph is empty
            if( m_pCreationCursor->IsContainer() && m_pCreationCursor->GetChild() ){
                m_pCreationCursor = m_pCreationCursor->GetParent();
            }
            {
                pContainer = m_pCreationCursor->FindContainerContainer();
                if( pContainer ){
                    m_pCreationCursor = pContainer;
                }
                else {
                    m_pCreationCursor = m_pRoot;
                }
                CEditDivisionElement* pDivision = new CEditDivisionElement( m_pCreationCursor, pTag, GetRAMCharSetID() );

                // For historical reasons, we handle alignment in the formatAlignStack.
                // It would probably be better to handle it in the DIV tag.
                ED_Alignment eAlign = pDivision->GetAlignment();
                if ( eAlign == ED_ALIGN_DEFAULT ) {
                    eAlign = ED_ALIGN_LEFT;
                }
                else {
                    pDivision->ClearAlignment(); 
                }
                GetParseState()->m_formatAlignStack.Push( eAlign );
                pContainer = m_pCreationCursor->FindContainer();
                if( pContainer ){
                    pContainer->Container()->AlignIfEmpty( eAlign );
                }
                // Create a division container.
                m_pCreationCursor = pDivision;
                break;
            }

        case P_LINEBREAK:
            {
            // Ignore <BR> after </LI>.
            if ( m_bLastTagIsEnd && BitSet( edt_setIgnoreBreakAfterClose, m_iLastTagType ) ) {
            }
            else {
                pElement = new CEditBreakElement(m_pCreationCursor, pTag);
                m_pCreationCursor = pElement->GetParent();
            }
            GetParseState()->bLastWasSpace = TRUE;
            break;
            }

        case P_TITLE:
            GetParseState()->m_bInTitle = TRUE;
            break;

        case P_META:
            ParseMetaTag( pTag );
            break;

        case P_BASE:
            // There are two known parameters in a BASE tag:
            // HREF which sets the base URL
            // TARGET which sets the default target for links.
            // We ignore HREF when it's not in a mail quotation.
            // We preserve TARGET as part of the document properties.
            {
                char *pTarget = edt_FetchParamString(pTag,PARAM_TARGET,m_pCreationCursor->GetWinCSID());
                if ( pTarget ) {
                    SetBaseTarget(pTarget);
                }
                XP_FREEIF(pTarget);
            }
            // We only deal with P_BASE tags for mail quotation.
            {
              // Look for enclosing mail quote CEditListElement and set pBaseURL to
              // be the HREF parameter of the <BASE> tag.
              CEditListElement *pMQuote = m_pCreationCursor->GetMailQuote();
              if (pMQuote) {
                char *pHref = edt_FetchParamString(pTag,PARAM_HREF,m_pCreationCursor->GetWinCSID());
                if (pHref) {
                  EDT_ListData *pData = pMQuote->GetData();
                  if (pData) {
                    XP_FREEIF(pData->pBaseURL);
                    pData->pBaseURL = pHref;
                    pMQuote->SetData(pData);
                    CEditListElement::FreeData(pData);
                  }
                  else {
                    XP_FREE(pHref);
                  }
                }
              }
            }
            break;

        case P_TABLE:
            {
              pContainer = m_pCreationCursor->FindContainerContainer();
              if( pContainer ){
                  m_pCreationCursor = pContainer;
              }
              else {
                  m_pCreationCursor = m_pRoot;
              }


              // A table following a </P> should have an extra break before the table.  Add a NSDT.
              CEditElement *pPrev = m_pCreationCursor->GetLastChild();
              if (pPrev && pPrev->IsContainer()) {
                  CEditContainerElement *pCont = pPrev->Container();
                  if (pCont && pCont->GetType() == P_PARAGRAPH && pCont->m_bHasEndTag) {
                    CEditContainerElement *pNew;
                    pNew = CEditContainerElement::NewDefaultContainer( m_pCreationCursor, pCont->GetAlignment() );
                    // g++ thinks the following value is not used, but it's mistaken.
                    // The constructor auto-inserts the element into the tree.
                    (void) new CEditTextElement(pNew, 0);
                  }
              }


              m_pCreationCursor = pElement = new CEditTableElement( m_pCreationCursor, pTag, GetRAMCharSetID(), GetCurrentAlignment() );

              // If in a mail quote with a <BASE> tag, make table background absolute.
              EDT_TableData *pData;
              if (pElement && NULL != (pData = CEditTableElement::Cast(pElement)->GetData())) {
                edt_MakeAbsoluteUsingBaseTag(m_pCreationCursor,&pData->pBackgroundImage);
                CEditTableElement::Cast(pElement)->SetData(pData);
                CEditTableElement::FreeData(pData);
              }
            }

            break;

        case P_TABLE_ROW:
            {
                CEditTableElement* pTable = m_pCreationCursor->GetTable();
                if ( ! pTable ){
                    // We might be in a TD that someone forgot to close.
                    CEditTableCellElement* pCell = m_pCreationCursor->GetTableCell();
                    if ( pCell ){
                        pTable = pCell->GetTable();
                    }
                }
                if ( pTable ) {
                    m_pCreationCursor = pElement = new CEditTableRowElement( pTable, pTag, GetRAMCharSetID() );

                    // If in a mail quote with a <BASE> tag, make table background absolute.
                    EDT_TableRowData *pData;
                    if (pElement && NULL != (pData = CEditTableRowElement::Cast(pElement)->GetData())) {
                      edt_MakeAbsoluteUsingBaseTag(m_pCreationCursor,&pData->pBackgroundImage);
                      CEditTableRowElement::Cast(pElement)->SetData(pData);
                      CEditTableRowElement::FreeData(pData);
                    }
                }
                else {
                    XP_TRACE(("Ignoring table row. Not in table."));
                }
            }
            break;

        case P_TABLE_HEADER:
        case P_TABLE_DATA:
            {
                CEditTableElement* pTable = m_pCreationCursor->GetTable();
                if ( ! pTable ){
                    // We might be in a TD that someone forgot to close.
                    CEditTableCellElement* pCell = m_pCreationCursor->GetTableCell();
                    if ( pCell ){
                        m_pCreationCursor = pCell->GetParent();
                        pTable = m_pCreationCursor->GetTable();
                    }
                }
                if ( ! pTable ) {
                    XP_TRACE(("Ignoring table cell. Not in table."));
                }
                else {
                    CEditTableRowElement* pTableRow = m_pCreationCursor->GetTableRow();
                    if ( ! pTableRow ){
                        // They forgot to put in a table row.
                        // Create one for them.

                        pTableRow = new CEditTableRowElement();
                        pTableRow->InsertAsLastChild(pTable);
                    }
                    m_pCreationCursor = pElement = new CEditTableCellElement( pTableRow, pTag, GetRAMCharSetID() );

                    // If in a mail quote with a <BASE> tag, make table background absolute.
                    EDT_TableCellData *pData;
                    if (pElement && NULL != (pData = CEditTableCellElement::Cast(pElement)->GetData())) {
                      edt_MakeAbsoluteUsingBaseTag(m_pCreationCursor,&pData->pBackgroundImage);
                      CEditTableCellElement::Cast(pElement)->SetData(pData);
                      CEditTableCellElement::FreeData(pData);
                    }

                    PushParseState();
                }
            }
            break;

        case P_CAPTION:
            {
                CEditTableElement* pTable = m_pCreationCursor->GetTable();
                if ( pTable ) {
                    m_pCreationCursor = pElement = new CEditCaptionElement( pTable, pTag, GetRAMCharSetID() );
                    PushParseState();
                }
                else {
                    XP_TRACE(("Ignoring caption. Not in a table."));
                }
            }
            break;

        case P_LAYER:
            pContainer = m_pCreationCursor->FindContainerContainer();
            if( pContainer ){
                m_pCreationCursor = pContainer;
            }
            else {
                m_pCreationCursor = m_pRoot;
            }
            m_pCreationCursor = new CEditLayerElement( m_pCreationCursor, pTag, GetRAMCharSetID() );
            retVal =  OK_IGNORE; // Remove this when we can display layers in the editor
            break;

        // unimplemented Tags

        case P_MAX:
            m_pCreationCursor = pElement = CreateElement( pTag, m_pCreationCursor );
            if( BitSet( edt_setSoloTags, m_pCreationCursor->GetType() ) ){
                m_pCreationCursor = m_pCreationCursor->GetParent();
            }
            break;

        //
        // someones added a new tag.  Inspect pTag and look in pa_tags.h
        // At the least, add it to edt_setUnsupported
        //
        default:
            XP_TRACE(("Someone added a new tag type %d.  Inspect pTag and look in pa_tags.h", pTag->type));
            XP_ASSERT(FALSE);
        case P_UNKNOWN:
            ParseUnsupportedTag(pTag, pElement, retVal);
            break;

        // its ok to ignore these tags.
        case P_NSCP_OPEN:
        case P_NSCP_CLOSE:
        case P_NSCP_REBLOCK:
        case P_HTML:
            break;
    }
    pTag->edit_element = pElement;
    return retVal;
}

void CEditBuffer::ParseLink(PA_Tag* pTag, CEditElement*& pElement, intn& retVal){
    INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_pContext);
    if ( GetParseState()->InBody() ){
        ParseUnsupportedTag(pTag, pElement, retVal);
        return;
    }
    char* pRel = edt_FetchParamString(pTag, PARAM_REL, INTL_GetCSIWinCSID(c));
    if ( pRel && XP_STRCASECMP(pRel, "FONTDEF") == 0 ) {
        ParseLinkFontDef(pTag, pElement, retVal); 
        return;
    }
    ParseUnsupportedTag(pTag, pElement, retVal);
}

void CEditBuffer::ParseLinkFontDef(PA_Tag* pTag, CEditElement*& /*pElement*/, intn& /*retVal*/){
    INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_pContext);
    if ( m_pFontDefURL ) {
        XP_FREE(m_pFontDefURL);
    }
    m_pFontDefURL = edt_FetchParamString(pTag, PARAM_SRC, INTL_GetCSIWinCSID(c));
    m_bFontDefNoSave = edt_FetchParamBoolExist(pTag, PARAM_NOSAVE, GetRAMCharSetID());
}


void CEditBuffer::ParseUnsupportedTag(PA_Tag* pTag, CEditElement*& pElement, intn& retVal){
    if ( IsDocTypeTag(pTag) ) {
        return; // Ignore document's idea about it's document type.
    }

    // If we don't understand it, and it's in the head, then assume that it needs to stay in the head.
    if ( !GetParseState()->InBody() ){
        RecordTag(pTag, TRUE);
        retVal = OK_IGNORE;
        return;
    }

    if( !m_pCreationCursor->IsContainer() && m_pCreationCursor->FindContainer() == 0 ){
        m_pCreationCursor = CEditContainerElement::NewDefaultContainer(
                m_pCreationCursor, GetCurrentAlignment() );
    }
    if ( HandleSelectionComment(pTag, pElement, retVal) ){
        return;
    }
    m_pCreationCursor = pElement = new CEditIconElement( m_pCreationCursor,
        pTag->is_end ?
            EDT_ICON_UNSUPPORTED_END_TAG
        :
            EDT_ICON_UNSUPPORTED_TAG,
        pTag );
    m_pCreationCursor->Icon()->MorphTag( pTag );
    m_pCreationCursor = m_pCreationCursor->GetParent();
    return;
}

void CEditBuffer::RecordTag(PA_Tag* pTag, XP_Bool bWithLinefeed){
    CStreamOutMemory* pOut = GetParseState()->GetStream();

    // Save the tag and its parameters
    char* kScriptTag =  EDT_TagString(pTag->type);
    pOut->Write("<",1);
    if ( pTag->is_end ) {
        pOut->Write("/",1);
    }
    pOut->Write(kScriptTag, XP_STRLEN(kScriptTag));

    char *locked_buff;
    PA_LOCK(locked_buff, char *, pTag->data );
    pOut->Write(locked_buff, XP_STRLEN(locked_buff));
    PA_UNLOCK(pTag->data);
    if ( bWithLinefeed ) pOut->Write("\n", 1);
}

void CEditBuffer::RecordJavaScriptAsUnknownTag(CStreamOutMemory* pOut){
    INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_pContext);
    // Store the script in a Icon tag.
    PA_Tag dummyTag;
    XP_BZERO( &dummyTag, sizeof( dummyTag ) );
    dummyTag.type = P_UNKNOWN; 
    dummyTag.is_end = FALSE;

    if( !m_pCreationCursor->IsContainer() && m_pCreationCursor->FindContainer() == 0 ){
        m_pCreationCursor = CEditContainerElement::NewDefaultContainer(
                m_pCreationCursor, GetCurrentAlignment() );
    }
    m_pCreationCursor = new CEditIconElement( m_pCreationCursor,
                EDT_ICON_UNSUPPORTED_TAG,
                &dummyTag );
    m_pCreationCursor->Icon()->MorphTag( &dummyTag );
    char* pData = edt_CopyFromHuge(INTL_GetCSIWinCSID(c), pOut->GetText(), pOut->GetLen(), NULL);
    m_pCreationCursor->Icon()->SetData( pData );
    m_pCreationCursor = m_pCreationCursor->GetParent();
    XP_FREE(pData);
}

void CEditBuffer::ParseBodyTag(PA_Tag *pTag){
    INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_pContext);
    GetParseState()->StartBody();
    // Allow multiple body tags -- don't replace parameter a new one isn't found.
    edt_FetchParamString2(pTag, PARAM_BACKGROUND, m_pBackgroundImage, INTL_GetCSIWinCSID(c));
    m_bBackgroundNoSave = edt_FetchParamBoolExist( pTag, PARAM_NOSAVE, GetRAMCharSetID() );
    edt_FetchParamColor2( pTag, PARAM_BGCOLOR, m_colorBackground, GetRAMCharSetID());
    edt_FetchParamColor2( pTag, PARAM_TEXT, m_colorText, GetRAMCharSetID() );
    edt_FetchParamColor2( pTag, PARAM_LINK, m_colorLink, GetRAMCharSetID() );
    edt_FetchParamColor2( pTag, PARAM_ALINK, m_colorActiveLink, GetRAMCharSetID() );
    edt_FetchParamColor2( pTag, PARAM_VLINK, m_colorFollowedLink, GetRAMCharSetID() );
    //m_pBody = m_pCreationCursor = pElement = CreateElement( pTag, m_pCreationCursor );
    edt_FetchParamExtras2( pTag, bodyParams, m_pBodyExtra, GetRAMCharSetID() );
}


CEditBuffer::CEditBuffer(MWContext *pContext):
        m_lifeFlag(0xbab3fac3),
        m_pContext(pContext),
        m_pRoot(0),
        m_pCurrent(0),
        m_iCurrentOffset(0),
        m_bCurrentStickyAfter(FALSE),
        m_pCreationCursor(0),
        m_colorText(ED_Color::GetUndefined()),
        m_colorBackground(ED_Color::GetUndefined()),
        m_colorLink(ED_Color::GetUndefined()),
        m_colorFollowedLink(ED_Color::GetUndefined()),
        m_colorActiveLink(ED_Color::GetUndefined()),
        m_pTitle(0),
        m_pBackgroundImage(0),
        m_bBackgroundNoSave(FALSE),
        m_pFontDefURL(0),
        m_bFontDefNoSave(FALSE),
        m_pBodyExtra(0),
        m_pLoadingImage(0),
        m_pSaveObject(0),
        m_bMultiSpaceMode(TRUE),
        m_hackFontSize(0),
        m_iDesiredX(-1),
        m_lastTopY(0),
        m_inScroll(FALSE),
        m_bBlocked(TRUE),
        m_bSelecting(0),
        m_bNoRelayout(FALSE),
        m_pSelectStart(0),
        m_iSelectOffset(0),
        m_preformatLinePos(0),
        m_bInPreformat(FALSE),
        printState(),
        linkManager(),
        m_metaData(),
        m_parseStateStack(),
        m_status(ED_ERROR_NONE),
        m_pCommandLog(0),
        m_bTyping(FALSE),
        m_pStartSelectionAnchor(0),
        m_pEndSelectionAnchor(0),
        m_bStartSelectionStickyAfter(0),
        m_bEndSelectionStickyAfter(0),
        m_bLayoutBackpointersDirty(TRUE),
        m_iFileWriteTime(0),
#ifdef DEBUG
        m_pTestManager(0),
        m_iSuppressPhantomInsertPointCheck(0),
        m_bSkipValidation(0),
#endif
        m_pSizingObject(0),
        m_finishLoadTimer(),
        m_relayoutTimer(),
        m_autoSaveTimer(),
        m_bDisplayTables(TRUE),
        m_bDummyCharacterAddedDuringLoad(FALSE),
        m_bReady(0),
        m_bPasteQuoteMode(FALSE),
        m_bPasteHTML(FALSE),
        m_bPasteHTMLWhenSavingDocument(FALSE),
        m_pPasteHTMLModeText(0),
        m_pPasteTranscoder(0),
        m_bForceDocCSID(FALSE),
        m_forceDocCSID(0),
        m_originalWinCSID(0),
        m_pSelectedLoTable(0),
        m_pSelectedEdTable(0),
        m_iNextSelectedCell(0),
        m_TableHitType(ED_HIT_NONE),
        m_pSelectedTableElement(0),
        m_pPrevExtendSelectionCell(0),
    	m_bEncrypt(PR_FALSE),
        m_pNonTextSelectedTable(0)
{
    INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_pContext);
    m_originalWinCSID = INTL_GetCSIWinCSID(c);
    m_pCommandLog = CGlobalHistoryGroup::GetGlobalHistoryGroup()->CreateLog(this);
    m_pStartSelectionAnchor = NULL;
    m_pEndSelectionAnchor = NULL;
    m_bStartSelectionStickyAfter = FALSE;
    m_bEndSelectionStickyAfter = FALSE;
    m_iLastTagType = -1;
    m_bLastTagIsEnd = FALSE;
    m_pBaseTarget = 0;

    // create a root element
    edt_InitBitArrays();
    m_pCreationCursor = m_pRoot = new CEditRootDocElement( this );
    ResetParseStateStack();

#ifdef DEBUG
    m_pTestManager = new CEditTestManager(this);
    m_iSuppressPhantomInsertPointCheck = FALSE;
    m_bSkipValidation = FALSE;
#endif

    m_relayoutTimer.SetEditBuffer(this);
    m_autoSaveTimer.SetEditBuffer(this);
#ifdef DEBUG_AUTO_SAVE
    m_autoSaveTimer.SetPeriod(1);
#endif

    m_pContext->display_table_borders = TRUE; // On by default.
}


CEditBuffer::~CEditBuffer(){
    m_lifeFlag = 0;

	if ( m_pContext ) {
        int32 doc_id = XP_DOCID(m_pContext);
	    lo_TopState* top_state = lo_FetchTopState(doc_id);
	    if (top_state != NULL )
	    {
    	    top_state->edit_buffer = 0;
        }
    }
#ifdef DEBUG
    delete m_pTestManager;
#endif
    delete m_pRoot;
    m_pRoot = NULL;
    // Delete meta data
    for(intn i = 0; i < m_metaData.Size(); i++ ) {
        FreeMetaData(m_metaData[i]);
    }
    ResetParseStateStack();
    CGlobalHistoryGroup::GetGlobalHistoryGroup()->DeleteLog(this);
    delete m_pPasteHTMLModeText;
    delete m_pPasteTranscoder;
    XP_FREEIF(m_pBaseTarget);
}

XP_Bool CEditBuffer::IsAlive(CEditBuffer* pBuffer){
    return pBuffer && pBuffer->m_lifeFlag == 0xbab3fac3;
}

XP_Bool CEditBuffer::IsReady(){
	return m_bReady;
}

void CEditBuffer::FixupInsertPoint(){
    CEditInsertPoint ip(m_pCurrent, m_iCurrentOffset, m_bCurrentStickyAfter);
    FixupInsertPoint(ip);
    m_pCurrent = ip.m_pElement;
    m_iCurrentOffset = ip.m_iPos;
    m_bCurrentStickyAfter = ip.m_bStickyAfter;
    XP_ASSERT(m_bCurrentStickyAfter == TRUE || m_bCurrentStickyAfter == FALSE);
}

void CEditBuffer::FixupInsertPoint(CEditInsertPoint& ip){
    if( ip.m_iPos == 0 && ! IsPhantomInsertPoint(ip) ){
        CEditLeafElement *pPrev = ip.m_pElement->PreviousLeafInContainer();
        if( pPrev && pPrev->GetLen() != 0 ){
            ip.m_pElement = pPrev;
            ip.m_iPos = pPrev->GetLen();
        }
    }
    // Since we fake up spaces at the beginning of paragraph, we might get
    //  an offset of 1 (after the fake space).
    else if( ip.m_pElement->GetLen() == 0 ){
        ip.m_iPos = 0;
    }
}

void CEditBuffer::SetInsertPoint( CEditLeafElement* pElement, int iOffset, XP_Bool bStickyAfter ){
#ifdef LAYERS
    LO_StartSelectionFromElement( m_pContext, 0, 0, NULL );
#else
    LO_StartSelectionFromElement( m_pContext, 0, 0);
#endif
    m_pCurrent = pElement;
    m_iCurrentOffset = iOffset;
    m_bCurrentStickyAfter = bStickyAfter;
    XP_ASSERT(m_bCurrentStickyAfter == TRUE || m_bCurrentStickyAfter == FALSE);
    SetCaret();
}

//
// Returns true if the insert point doesn't really exist.
//
XP_Bool CEditBuffer::IsPhantomInsertPoint(){
    if( !IsSelected() ) {
        CEditInsertPoint ip(m_pCurrent, m_iCurrentOffset, m_bCurrentStickyAfter);
        return IsPhantomInsertPoint(ip);
    }
    else {
        return FALSE;
    }
}

XP_Bool CEditBuffer::IsPhantomInsertPoint(CEditInsertPoint& ip){
    if(  ip.m_pElement
            && ip.m_pElement->IsA( P_TEXT )
            && ip.m_pElement->Text()->GetLen() == 0
            && !( ip.m_pElement->IsFirstInContainer()
                    && ip.m_pElement->LeafInContainerAfter() == 0 )
                ){
        return TRUE;
    }
    else {
        return FALSE;
    }
}

//
// Force the insertpoint to be a real insert point so we can do something.
//
void CEditBuffer::ClearPhantomInsertPoint(){
    if( IsPhantomInsertPoint() ){
        CEditLeafElement *pPrev = m_pCurrent->PreviousLeafInContainer();
        if( pPrev ){
            m_pCurrent = pPrev;
            m_iCurrentOffset = m_pCurrent->GetLen();
            m_bCurrentStickyAfter = FALSE;
        }
        else {
            CEditLeafElement *pNext = m_pCurrent->LeafInContainerAfter();
            if( pNext ){
                m_pCurrent = pNext;
                m_iCurrentOffset = 0;
                m_bCurrentStickyAfter = FALSE;
           }
            else {
                XP_ASSERT(FALSE);
            }
        }
        Reduce( m_pCurrent->FindContainer() );
    }
}

XP_Bool CEditBuffer::GetDirtyFlag(){
    return GetCommandLog()->IsDirty();
}

void CEditBuffer::DocumentStored(){
    DoneTyping();
    GetCommandLog()->DocumentStored();
}

CEditElement* CEditBuffer::FindRelayoutStart( CEditElement *pStartElement ){
    CEditElement* pOldElement = NULL;
    while ( pStartElement && pStartElement != pOldElement ) {
        pOldElement = pStartElement;
        CEditElement* pTable = pStartElement->GetTopmostTableOrLayer();
        if ( m_bDisplayTables && pTable ) {
            // If this is in a table, skip before it.
            pStartElement = pTable->PreviousLeaf();
        }
        else if( !pStartElement->IsLeaf() ){
            pStartElement = pStartElement->PreviousLeaf();
        }
        else if( pStartElement->Leaf()->GetLayoutElement() == 0 ){
           pStartElement = pStartElement->PreviousLeaf();
        }
    }
    return pStartElement;
}

static TXP_GrowableArray_LO_TableStruct edt_RelayoutTables;

// Add to current list of tables being relayed out
void EDT_AddToRelayoutTables(MWContext * /*pMWContext*/, LO_TableStruct *pLoTable )
{
    if(pLoTable)
    {
        edt_RelayoutTables.Add(pLoTable);
    }
}

void EDT_FixupTableData(MWContext *pMWContext)
{
    GET_WRITABLE_EDIT_BUF_OR_RETURN(pMWContext, pEditBuffer);
    pEditBuffer->FixupTableData();
}

// Change Table and all cell width data to match
//  sizes calculated by Layout. Must do for all tables during Relayout()
//  else generated HTML is very misleading!
void CEditBuffer::FixupTableData()
{
    for( int i = 0; i < edt_RelayoutTables.Size(); i++ )
    {
        LO_TableStruct *pLoTable = edt_RelayoutTables[i];
        LO_Element     *pLoTableElement = (LO_Element*)pLoTable;

        // Currently, embeded tables will cause reallocation during layout
        //   that end up reusing pointers, thus some table pointers
        //   may not be valid. Skip over them.
        // TODO: Figure out how to remove re-allocated tables from edt_RelayoutTables
        if( pLoTable->type != LO_TABLE )
            continue;

        CEditTableElement *pEdTable =
            (CEditTableElement*)GetTableElementFromLO_Element(pLoTableElement, LO_TABLE );
        if(!pEdTable ) 
            continue;

        // Fixup Table Data
        EDT_TableData *pTableData = pEdTable->GetData();
        if(!pTableData)
            return;

        pTableData->iInterCellSpace = pLoTable->inter_cell_space;

//XP_TRACE(("Fixup Table: Old iWidth = %d, iWidthPixels = %d; New iWidthPixels = %d", pTableData->iWidth, pTableData->iWidthPixels, pLoTable->width));

    	int32 iMaxWidth;
        int32 iMaxHeight;
        pEdTable->GetParentSize(m_pContext, &iMaxWidth, &iMaxHeight, pLoTable);

        // Save correct width even if bWidthDefined is FALSE
        //   (NOTE: width will NOT be save in m_pTagData if bWidthDefined == FALSE)
        pTableData->iWidthPixels = pLoTable->width;
        if( pTableData->bWidthPercent )
        {
            pTableData->iWidth = (pTableData->iWidthPixels * 100) / iMaxWidth;
        } else {
            pTableData->iWidth = pTableData->iWidthPixels;
        }

        pTableData->iHeightPixels = pLoTable->height;
        if( pTableData->bHeightPercent )
        {
            pTableData->iHeight = (pTableData->iHeightPixels * 100) / iMaxHeight;
        } else {
            pTableData->iHeight = pTableData->iHeightPixels;
        }
        // Get starting layout cell - usually first cell after table element
        LO_Element *pLoCell = pLoTableElement->lo_any.next;
        
        // Skip over non-cells
        while( pLoCell && pLoCell->type != LO_CELL )
            pLoCell = pLoCell->lo_any.next;
        
        CEditTableCellElement *pEdCell;

        // This is dependable even if row and cell indexes are not set for cells
        int32 iRows = pEdTable->GetRows();
        int32 iArraySize = iRows * sizeof(int32);
        int32 *ExtraColumns = (int32*)XP_ALLOC(iArraySize);
        if( !ExtraColumns )
        {
            return;
        }
        XP_MEMSET( ExtraColumns, 0, iArraySize );

        // This may actually be a CaptionElement,
        //  but we will test for that below
        CEditElement *pRow = pEdTable->GetChild();
        intn iRow = 0;
        intn iMaxColumns = 0;

        // Save coordinates of first cell in table
        pEdTable->SetFirstColumnX(pLoCell->lo_any.x);
        pEdTable->SetFirstRowY(pLoCell->lo_any.y);

        while( pRow )
        {
            // A Caption element may be a child, so test type first
            if( pRow->IsTableRow() )
            {
                pEdCell = (CEditTableCellElement*)(pRow->GetChild());

                // We will count number of actual cell locations in each row
                // Start with extra columns caused by ROWSPAN in previous rows
                int32 iColumnsInRow = ExtraColumns[iRow];

                while( pEdCell )
                {
                    intn iColSpan = pEdCell->GetColSpan();
                    intn iRowSpan = pEdCell->GetRowSpan();

                    iColumnsInRow += iColSpan;

    #ifdef DEBUG
                    // Test if list scan is in sync between layout and editor objects        
                    LO_Element *pNextCell = (LO_Element*)(pEdCell->GetLoCell());
                    if( !pLoCell || pLoCell !=  pNextCell)
                    {
                        XP_TRACE(("**** pNextCell (%d) is not correct", pLoCell));
                    }
                    else
    #endif                
                    {
                        // If current cell has extra ROWSPAN,
                        //  then it will cause extra columns in following row(s)
                        if( iRowSpan > 1 )
                        {
                            for( intn j = 1; j < iRowSpan; j++ )
                                ExtraColumns[iRow+j] += iColSpan;
                        }
                        //  Save actual location and size data
                        EDT_TableCellData *pCellData = pEdCell->GetData();
                        if( pCellData )
                        {
                            pCellData->X = pLoCell->lo_any.x;
                            pCellData->Y = pLoCell->lo_any.y;
                            pCellData->iRow = iRow;
                            pCellData->iWidthPixels = pLoCell->lo_any.width;
                            pCellData->iHeightPixels = pLoCell->lo_any.height;

                            if( pCellData->bWidthPercent )
                            {
                                pCellData->iWidth = (pCellData->iWidthPixels * 100) / iMaxWidth;
                            } else{
                                pCellData->iWidth = pCellData->iWidthPixels;
                            }

                            if( pCellData->bHeightPercent )
                            {
                                pCellData->iHeight = (pCellData->iHeightPixels * 100) / iMaxHeight;
                            } else{
                                pCellData->iHeight = pCellData->iHeightPixels;
                            }
                            pEdCell->SetData(pCellData);
                            EDT_FreeTableCellData(pCellData);
                        }
                    }
                    CEditTableCellElement *pNextEdCell = (CEditTableCellElement*)(pEdCell->GetNextSibling());
                
                    // Next cell in row 
                    //  (or signal to get next row if NULL)
                    pEdCell = pNextEdCell;

                    // If cell scanning is in sync
                    //   this should be much quicker than searching for 
                    //   a LO_Element from each CEditElement or vice versa
                    if( pLoCell )
                    {
                        pLoCell = pLoCell->lo_any.next;
                        // Skip over non-cells
                        while( pLoCell && pLoCell->type != LO_CELL )
                            pLoCell = pLoCell->lo_any.next;
                    }
                }
                // Save the column count in Row
                pRow->TableRow()->SetColumns(iColumnsInRow);

                // Save maximum for all rows
                if( iColumnsInRow > iMaxColumns )
                    iMaxColumns = iColumnsInRow;

                // PLEASE!!! I hope this rule holds: "We can never have an empty row"
                iRow++;
            }
            pRow = pRow->GetNextSibling();
        }
        // Now we know maximum number of columns to set in table
        pTableData->iColumns = iMaxColumns;
        pTableData->iRows = iRow;
        pEdTable->SetData(pTableData);
        EDT_FreeTableData(pTableData);

        XP_FREE(ExtraColumns);
    }

    // We need to do this just once, so clear the list
    edt_RelayoutTables.Empty();
}

//
// ReflowFromElement
//
// This attempts to reflow layout elements from a given edit element. The idea is to not
// have to return to the tags for basic edit operations. Text typing is currently the main
// client for this.
//
// We do not handle typing inside tables (yet).
//
// There will be some duplication of code/functionality with Relayout.
//
void CEditBuffer::Reflow( CEditElement* pStartElement,
                            int iEditOffset,
                            CEditElement *pEndElement,
                            intn relayoutFlags ){
    CEditElement *pEdStart, *pNewStartElement;
    LO_Element *pLayoutElement;
    LO_Element *pLoStartLine;
    int iOffset;
    int32 iLineNum;

    if( m_bNoRelayout ){
        return;
    }

#if defined( DEBUG_shannon )
	// do we want to force calls through to the old relayout?
	if ( !gEditorReflow ) {
        Relayout( pStartElement, iEditOffset, pEndElement, relayoutFlags );
        return;
	}
#endif

	// if the start element can't reflow, then do a relayout
	if ( ( pStartElement != NULL ) && !pStartElement->CanReflow() ) {
        Relayout( pStartElement, iEditOffset, pEndElement, relayoutFlags );
        return;
	}
	
    // Clear the list of tables created during layout
    // This will be rebuilt as each table is encountered
    edt_RelayoutTables.Empty();

    CEditLeafElement *pBegin, *pEnd;
    ElementOffset iBeginPos, iEndPos;
    XP_Bool bFromStart;
    XP_Bool bWasSelected = IsSelected();

    if( bWasSelected )
    {
        GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
#ifdef LAYERS
        LO_StartSelectionFromElement( m_pContext, 0, 0, NULL );
#else
        LO_StartSelectionFromElement( m_pContext, 0, 0);
#endif
    }

    pNewStartElement = FindRelayoutStart(pStartElement);
    if( pNewStartElement && pNewStartElement != pStartElement ){
        // we had to back up some.  Layout until we pass this point.
        if( pEndElement == 0 ){
            pEndElement = pStartElement;
        }
        pStartElement = pNewStartElement;
        iEditOffset = pStartElement->Leaf()->GetLen();
    }

    // If the end is in a table, move it outside of the table.
    if ( pEndElement ) {
        CEditElement* pTable = pEndElement->GetTopmostTableOrLayer();
        if ( m_bDisplayTables && pTable) {
            // If this is in a table, skip after it.
            pEndElement = pTable->GetLastMostChild()->NextLeaf();
        }
    }

    // laying out from the beginning of the document
    if( pNewStartElement == 0 ){
        if( pEndElement == 0 ){
            pEndElement = pStartElement;
        }
        iLineNum = 0;
        pEdStart = m_pRoot;
        iOffset = 0;
    }
    else {
        //
        // normal case
        //
        if( pStartElement->IsA(P_TEXT)){
            pLayoutElement = (LO_Element*)pStartElement->Text()->GetLOText( iEditOffset );
        }
        else {
            pLayoutElement = pStartElement->Leaf()->GetLayoutElement();
        }
        if( pLayoutElement == 0 ){
            // since we can't find the start element, we have to go back to the tags.
            // BRAIN DAMAGE: Is this really true - shouldn't we just be able to reflow
            // from here instead?
            XP_TRACE(("Cannot find start element - relayout from tags"));
            Relayout( pStartElement->FindContainer(), 0, pEndElement ?
                        pEndElement : pStartElement, relayoutFlags  );
            return;
        }
        //
        // Find the first element on this line.
        pLoStartLine = FirstElementOnLine( pLayoutElement, &iLineNum );

        //
        // Position the tag cursor at this position
        //
        pEdStart = pLoStartLine->lo_any.edit_element;
        iOffset = pLoStartLine->lo_any.edit_offset;
    }


    // Create a new cursor.
    CEditTagCursor cursor(this, pEdStart, iOffset, pEndElement);
    //CEditTagCursor *pCursor = new CEditTagCursor(this, m_pRoot, iOffset);

    LO_EditorReflow(m_pContext, &cursor, iLineNum, iOffset, m_bDisplayTables);


#if defined( DEBUG_shannon )
    XP_TRACE(("\n\nEDITOR REFLOW"));
	lo_PrintLayout(m_pContext);
#endif

    // Search for zero-length text elements, and remove the non-breaking spaces we put
    // in.

    CEditElement* pElement= m_pRoot;
    while ( NULL != (pElement = pElement->FindNextElement(&CEditElement::FindLeafAll,0))){
        switch ( pElement->GetElementType() ) {
        case eTextElement:
            {
				/* Am I correct that we don't need to do this? */
                CEditTextElement* pText = pElement->Text();
                intn iLen = pText->GetLen();
                if ( iLen == 0 ) {
                    LO_Element* pTextStruct = pText->GetLayoutElement();
                    if ( pTextStruct && pTextStruct->type == LO_TEXT
                            && pTextStruct->lo_text.text_len == 1
                            && pTextStruct->lo_text.text ) {
                        XP_ASSERT( pElement->Leaf()->PreviousLeafInContainer() == 0 );
                        XP_ASSERT( pElement->Leaf()->LeafInContainerAfter() == 0 );
                        // Need to strip the allocated text, because lo_bump_position cares.
                        pTextStruct->lo_text.text_len = 0;
                    }
                }
            }
            break;
        case eBreakElement:
            {
                CEditBreakElement* pBreak = pElement->Break();
                LO_Element* pBreakStruct = pBreak->GetLayoutElement();
                if ( pBreakStruct && pBreakStruct->type == LO_LINEFEED) {
                    // Need to strip the allocated text, because lo_bump_position cares.
                    pBreakStruct->lo_linefeed.break_type = LO_LINEFEED_BREAK_HARD;
                    pBreakStruct->lo_linefeed.edit_element = pElement;
                    pBreakStruct->lo_linefeed.edit_offset = 0;
                }
            }
            break;
       case eHorizRuleElement:
             {
                // The linefeeds after hrules need to be widened.
                // They are zero pixels wide by default.
                CEditHorizRuleElement* pHorizRule = (CEditHorizRuleElement*) pElement;
                LO_Element* pHRuleElement = pHorizRule->GetLayoutElement();
                if ( pHRuleElement ) {
                    LO_Element* pNext = pHRuleElement->lo_any.next;
                    if ( pNext && pNext->type == LO_LINEFEED) {
                        const int32 kMinimumWidth = 7;
                        if (pNext->lo_linefeed.width < kMinimumWidth ) {
                             pNext->lo_linefeed.width = kMinimumWidth;
                        }
                    }
                }
            }
            break;
      default:
            break;
        }
        // Set end-of-paragraph marks
        if ( pElement->GetNextSibling() == 0 ){
            // We're the last leaf in the container.
            CEditLeafElement* pLeaf = pElement->Leaf();
            LO_Element* pLastElement;
            int iOffset;
            if ( pLeaf->GetLOElementAndOffset(pLeaf->GetLen(), TRUE,
                pLastElement, iOffset) ){
                LO_Element* pNextElement = pLastElement ? pLastElement->lo_any.next : 0;
                if ( pNextElement && pNextElement->type == LO_LINEFEED ) {
                    pNextElement->lo_linefeed.break_type = LO_LINEFEED_BREAK_PARAGRAPH;
                }
            }
            else {
                // Last leaf, but we're empty. Yay. Try the previous leaf.
                CEditElement* pPrevious = pElement->GetPreviousSibling();
                if ( pPrevious ) {
                    CEditLeafElement* pLeaf = pPrevious->Leaf();
                    LO_Element* pLastElement;
                    int iOffset;
                    if ( pLeaf->GetLOElementAndOffset(pLeaf->GetLen(), TRUE,
                        pLastElement, iOffset) ){
                        LO_Element* pNextElement = pLastElement ? pLastElement->lo_any.next : 0;
                        if ( pNextElement && pNextElement->type == LO_LINEFEED ) {
                            pNextElement->lo_linefeed.break_type = LO_LINEFEED_BREAK_PARAGRAPH;
                        }
                    }
                }
            }
        }
    }
    if( (relayoutFlags & RELAYOUT_NOCARET) == 0 && !bWasSelected){
        SetCaret();
    }

    if( bWasSelected ){
        SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
    }

    m_bLayoutBackpointersDirty = FALSE;
    
    // For each table just layed out, readjust all table and cell width data
    //   to reflect the complicated Layout algorithm's size data
    FixupTableData();

#if defined(XP_WIN) || defined(XP_OS2)
    // This clears FE pointers to cached layout elements
    FE_FinishedRelayout(m_pContext);
#endif
}

//
// Relayout.
//
void CEditBuffer::Relayout( CEditElement* pStartElement,
                            int iEditOffset,
                            CEditElement *pEndElement,
                            intn relayoutFlags ){
    CEditElement *pEdStart, *pNewStartElement;
    CEditElement *pContainer;
    LO_Element *pLayoutElement;
    LO_Element *pLoStartLine;
    int iOffset;
    int32 iLineNum;

    if( m_bNoRelayout ){
        return;
    }
	
   // These elements will be destroyed during LO_Relayout,
    //  so remove the list -- reconstructed after laying out
    m_pSelectedLoTable = NULL;
    m_SelectedLoCells.Empty();
        
    // Clear the list of tables
    // This will be rebuilt as each table is encountered
    edt_RelayoutTables.Empty();

    CEditLeafElement *pBegin, *pEnd;
    ElementOffset iBeginPos, iEndPos;
    XP_Bool bFromStart;
    XP_Bool bWasSelected = IsSelected();

    if( bWasSelected )
    {
        GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
#ifdef LAYERS
        LO_StartSelectionFromElement( m_pContext, 0, 0, NULL );
#else
        LO_StartSelectionFromElement( m_pContext, 0, 0);
#endif
    }
    
    // we actually may need to go back more to the container as we need to layout whole text elements in relayout
    if ( pStartElement ){
		pContainer = pStartElement->FindContainer();
		if ( pContainer ){
			pStartElement = pContainer;
			iEditOffset = 0;
		}
	}

    pNewStartElement = FindRelayoutStart(pStartElement);
    if( pNewStartElement && pNewStartElement != pStartElement ){
    
    	// we really want to get the first element of this container
		pContainer = pNewStartElement->FindContainer();
		if ( pContainer ){
			pNewStartElement = pContainer->GetChild();
		}
		
        // we had to back up some.  Layout until we pass this point.
        if( pEndElement == 0 ){
            pEndElement = pStartElement;
        }
        pStartElement = pNewStartElement;
        iEditOffset = 0;
    }

    // If the end is in a table, move it outside of the table.
    if ( pEndElement ) {
        CEditElement* pTable = pEndElement->GetTopmostTableOrLayer();
        if ( m_bDisplayTables && pTable) {
            // If this is in a table, skip after it.
            pEndElement = pTable->GetLastMostChild()->NextLeaf();
        }
    }

    // laying out from the beginning of the document
    if( pNewStartElement == 0 ){
        if( pEndElement == 0 ){
            pEndElement = pStartElement;
        }
        iLineNum = 0;
        pEdStart = m_pRoot;
        iOffset = 0;
    }
    else {
        //
        // normal case
        //
		// we always go to the leaf's layout element in relayout
		pLayoutElement = pStartElement->Leaf()->GetLayoutElement();

        if( pLayoutElement == 0 ){
            // we are sunk! try something different.
            XP_TRACE(("Yellow Alert! Can't resync, plan B"));
            Relayout( pStartElement->FindContainer(), 0, pEndElement ?
                        pEndElement : pStartElement, relayoutFlags  );
            return;
        }
        //
        // Find the first element on this line.
        pLoStartLine = FirstElementOnLine( pLayoutElement, &iLineNum );

        //
        // Position the tag cursor at this position
        //
        pEdStart = pLoStartLine->lo_any.edit_element;
        iOffset = pLoStartLine->lo_any.edit_offset;
    }


    // Create a new cursor.
    CEditTagCursor cursor(this, pEdStart, iOffset, pEndElement);
    //CEditTagCursor *pCursor = new CEditTagCursor(this, m_pRoot, iOffset);

    LO_Relayout(m_pContext, &cursor, iLineNum, iOffset, m_bDisplayTables);

#if defined( DEBUG_shannon )
    XP_TRACE(("\n\nEDITOR RELAYOUT"));
	lo_PrintLayout(m_pContext);
#endif

    // Search for zero-length text elements, and remove the non-breaking spaces we put
    // in.

    CEditElement* pElement= m_pRoot;
    while ( NULL != (pElement = pElement->FindNextElement(&CEditElement::FindLeafAll,0))){
        switch ( pElement->GetElementType() ) {
        case eTextElement:
            {
				/* Am I correct that we don't need to do this? */
                CEditTextElement* pText = pElement->Text();
                intn iLen = pText->GetLen();
                if ( iLen == 0 ) {
                    LO_Element* pTextStruct = pText->GetLayoutElement();
                    if ( pTextStruct && pTextStruct->type == LO_TEXT
                            && pTextStruct->lo_text.text_len == 1
                            && pTextStruct->lo_text.text ) {
                        XP_ASSERT( pElement->Leaf()->PreviousLeafInContainer() == 0 );
                        XP_ASSERT( pElement->Leaf()->LeafInContainerAfter() == 0 );
                        // Need to strip the allocated text, because lo_bump_position cares.
                        pTextStruct->lo_text.text_len = 0;
                    }
                }
#ifdef OLDWAY
                else {
                    // Layout stripped out all the spaces that crossed soft line breaks.
                    // Put them back in so they can be selected.
                    LO_Element* pTextStruct = pText->GetLayoutElement();
                    while ( pTextStruct && pTextStruct->type == LO_TEXT
                        && pTextStruct->lo_any.edit_element == pText ) {
                        intn iOffset = pTextStruct->lo_any.edit_offset + pTextStruct->lo_text.text_len;
                        if ( iOffset >= iLen )
                            break;
                        LO_Element* pNext = pTextStruct->lo_any.next;
                        while ( pNext && pNext->type != LO_TEXT) {
                            pNext = pNext->lo_any.next;
                        }
                        intn bytesToCopy = iLen - iOffset;
                        if ( pNext && pNext->lo_any.edit_element == pText ){
                           bytesToCopy =  pNext->lo_text.edit_offset - iOffset;
                        }
                        if ( bytesToCopy > 0 ) {
                            // Layout has stripped some characters.
                            // (Probably just a single space character.)
                            // Put them back.
                            int16 length = pTextStruct->lo_text.text_len;
                            int16 newLength = (int16) (length + bytesToCopy + 1); // +1 for '\0'
                            PA_Block newData;
                            if (pTextStruct->lo_text.text) {
                                newData  = (PA_Block) PA_REALLOC(pTextStruct->lo_text.text, newLength);
                            }
                            else {
                                newData = (PA_Block) PA_ALLOC(newLength);
                            }
                            if ( ! newData ) {
                                XP_ASSERT(FALSE); /* Out of memory. Not well tested. */
                                break;
                            }
                            pTextStruct->lo_text.text = newData;
                            pTextStruct->lo_text.text_len = (int16) (newLength - 1);
                            char *locked_buff;
                            PA_LOCK(locked_buff, char *, pTextStruct->lo_text.text);
                            if ( locked_buff ) {
                                char* source = pText->GetText();
                                for ( intn i = 0; i < bytesToCopy; i++ ){
                                    locked_buff[length + i] = source[iOffset+ i];
                                }
                                locked_buff[length + bytesToCopy] = '\0';
                            }
                            PA_UNLOCK(pNext->lo_text.text);
                            // The string is now wider. We must adjust the width.
                            LO_TextInfo text_info;
                            FE_GetTextInfo(m_pContext, &pTextStruct->lo_text, &text_info);
                            int32 delta = text_info.max_width - pTextStruct->lo_text.width;
                            pTextStruct->lo_text.width = text_info.max_width;
                            // Shrink the linefeed
                            LO_Element* pNext = pTextStruct->lo_any.next;
                            if ( pNext && pNext->type == LO_LINEFEED ) {
                                if ( pNext->lo_linefeed.width > delta ) {
                                    pNext->lo_linefeed.width -= delta;
                                    pNext->lo_linefeed.x += delta;
                                }
                            }
                        }
                        pTextStruct = pNext;
                    }
                }
#endif
            }
            break;
        case eBreakElement:
            {
                CEditBreakElement* pBreak = pElement->Break();
                LO_Element* pBreakStruct = pBreak->GetLayoutElement();
                if ( pBreakStruct && pBreakStruct->type == LO_LINEFEED) {
                    // Need to strip the allocated text, because lo_bump_position cares.
                    pBreakStruct->lo_linefeed.break_type = LO_LINEFEED_BREAK_HARD;
                    pBreakStruct->lo_linefeed.edit_element = pElement;
                    pBreakStruct->lo_linefeed.edit_offset = 0;
                }
            }
            break;
       case eHorizRuleElement:
             {
                // The linefeeds after hrules need to be widened.
                // They are zero pixels wide by default.
                CEditHorizRuleElement* pHorizRule = (CEditHorizRuleElement*) pElement;
                LO_Element* pHRuleElement = pHorizRule->GetLayoutElement();
                if ( pHRuleElement ) {
                    LO_Element* pNext = pHRuleElement->lo_any.next;
                    if ( pNext && pNext->type == LO_LINEFEED) {
                        const int32 kMinimumWidth = 7;
                        if (pNext->lo_linefeed.width < kMinimumWidth ) {
                             pNext->lo_linefeed.width = kMinimumWidth;
                        }
                    }
                }
            }
            break;
      default:
            break;
        }
        // Set end-of-paragraph marks
        if ( pElement->GetNextSibling() == 0 ){
            // We're the last leaf in the container.
            CEditLeafElement* pLeaf = pElement->Leaf();
            LO_Element* pLastElement;
            int iOffset;
            if ( pLeaf->GetLOElementAndOffset(pLeaf->GetLen(), TRUE,
                pLastElement, iOffset) ){
                LO_Element* pNextElement = pLastElement ? pLastElement->lo_any.next : 0;
                if ( pNextElement && pNextElement->type == LO_LINEFEED ) {
                    pNextElement->lo_linefeed.break_type = LO_LINEFEED_BREAK_PARAGRAPH;
                }
            }
            else {
                // Last leaf, but we're empty. Yay. Try the previous leaf.
                CEditElement* pPrevious = pElement->GetPreviousSibling();
                if ( pPrevious ) {
                    CEditLeafElement* pLeaf = pPrevious->Leaf();
                    LO_Element* pLastElement;
                    int iOffset;
                    if ( pLeaf->GetLOElementAndOffset(pLeaf->GetLen(), TRUE,
                        pLastElement, iOffset) ){
                        LO_Element* pNextElement = pLastElement ? pLastElement->lo_any.next : 0;
                        if ( pNextElement && pNextElement->type == LO_LINEFEED ) {
                            pNextElement->lo_linefeed.break_type = LO_LINEFEED_BREAK_PARAGRAPH;
                        }
                    }
                }
            }
        }
    }
    if( (relayoutFlags & RELAYOUT_NOCARET) == 0 && !bWasSelected){
        SetCaret();
    }

    if( bWasSelected ){
        SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
    }

    m_bLayoutBackpointersDirty = FALSE;
    
    //XP_TRACE(("*** Resync Table Selection"));
    // Resynch table selection
    // TODO -- NOT NEEDED AFTER CHANGE SO LAYOUT ELEMENTS AREN'T DESTROYED
    if( m_pSelectedEdTable )
    {
        m_pSelectedLoTable = m_pSelectedEdTable->GetLoTable();
        SelectTable(TRUE, m_pSelectedLoTable, m_pSelectedEdTable);
    } else {
        for( int i = 0; i < m_SelectedEdCells.Size(); i++ )
        {
            // Get the new LO_CellStruct matching the previously-selected edit element
            LO_CellStruct *pLoCell = m_SelectedEdCells[i]->GetLoCell();
            // Resync the list and display the cell
            SelectCell(TRUE, pLoCell, m_SelectedEdCells[i]);
        }
    }

    // For each table just layed out, readjust all table and cell width data
    //   to reflect the complicated Layout algorithm's size data
    FixupTableData();

#if defined(XP_WIN) || defined(XP_OS2)
    // This clears FE pointers to cached layout elements
    FE_FinishedRelayout(m_pContext);
#endif
}

// Relayout selected table or parent table if any cells are selected
void CEditBuffer::RelayoutSelectedTable()
{
    // Should be cleared, but set just to be sure
    m_bNoRelayout = FALSE;
    CEditTableElement *pTable = NULL;
    if( m_pSelectedEdTable )
    {
        pTable = m_pSelectedEdTable;
    }
    else if( m_SelectedEdCells.Size() )
    {
        pTable = m_SelectedEdCells[0]->GetParentTable();
    }

    if( pTable )
        Relayout(pTable, 0, pTable);
}


//
// Insert a character at the current edit point
//
EDT_ClipboardResult CEditBuffer::InsertChar( int newChar, XP_Bool bTyping ){
    char buffer[2];
    buffer[0] = (char) newChar;
    buffer[1] = '\0';
    return InsertChars(buffer, bTyping, TRUE);
}

EDT_ClipboardResult CEditBuffer::InsertChars( char* pNewChars, XP_Bool bTyping, XP_Bool bReduce){

#ifdef DEBUG
    if ( bTyping ) {
        for(char* pChar = pNewChars; *pChar; pChar++ ) {
            if ( m_pTestManager->Key(*pChar) )
                return EDT_COP_OK;
        }
    }
#endif

    VALIDATE_TREE(this);
    EDT_ClipboardResult result = EDT_COP_OK;
    int iChangeOffset = m_iCurrentOffset;
    StartTyping(bTyping);

    if( IsSelected() ){
        result = DeleteSelection();
        if ( result != EDT_COP_OK ) return result;
    }
    //ClearSelection();

    ClearMove(FALSE);
    if(!IsPhantomInsertPoint() ){
        FixupInsertPoint();
    }
    if( m_pCurrent->IsA( P_TEXT )){
        // If this assert ever fails, it means that we've
        // got to uncomment this and make it work.
        XP_ASSERT(IsMultiSpaceMode()); 
#if 0
        // Check for space after space case.
        if ( newChar == ' ' && !m_pCurrent->InFormattedText()
                && !IsMultiSpaceMode() ) {
            CEditInsertPoint p(m_pCurrent, m_iCurrentOffset);
            // move to the right on spacebar if we are under a space.
            if( p.IsSpace() ){
                NextChar( FALSE );
                return result;
            }
            if ( p.IsSpaceBeforeOrAfter() ) {
                return result;
            }
        }
#endif
        //
        // The edit element can choose not to insert a character if it is
        //  a space and at the insertion point there already is a space.
        //
        int32 bytesInserted = m_pCurrent->Text()->InsertChars( m_iCurrentOffset, pNewChars );
        if( bytesInserted > 0 ){

            // move past the inserted characters
            m_iCurrentOffset += bytesInserted;
            // Reduce or die unless dont reduce
            if (bReduce)
            {
                Reduce(m_pCurrent->FindContainer());
                // relay out the stream
	    		Reflow(m_pCurrent, iChangeOffset);
            }
        }
    }
    else {
        if (bReduce)
        {
            Reduce( m_pCurrent->FindContainer());
        }
        if( m_iCurrentOffset == 0 ){
            // insert before leaf case.
            CEditElement *pPrev = m_pCurrent->GetPreviousSibling();
            if( pPrev == 0 || !pPrev->IsA( P_TEXT ) ){
                pPrev = new CEditTextElement((CEditElement*)0,0);
                pPrev->InsertBefore( m_pCurrent );
            }
            m_pCurrent = pPrev->Leaf();
            m_iCurrentOffset = m_pCurrent->Text()->GetLen();
        }
        else {
            XP_ASSERT( m_iCurrentOffset == 1 );
            // insert after leaf case
            CEditLeafElement *pNext = (CEditLeafElement*) m_pCurrent->GetNextSibling();
            if( pNext == 0 || !pNext->IsA( P_TEXT ) ){
                CEditTextElement* pPrev = m_pCurrent->PreviousTextInContainer();
                if( pPrev ){
                    pNext = pPrev->CopyEmptyText();
                }
                else {
                    pNext = new CEditTextElement((CEditElement*)0,0);
                }
                pNext->InsertAfter( m_pCurrent );
            }
            m_pCurrent = pNext;
            m_iCurrentOffset = 0;
        }
        // now we have a text.  Do the actual insert.
        int32 bytesInserted = m_pCurrent->Text()->InsertChars( m_iCurrentOffset, pNewChars );
        if ( bytesInserted > 0 ){
            m_iCurrentOffset += bytesInserted;
            if ( bTyping ) {
                m_relayoutTimer.Relayout(m_pCurrent, iChangeOffset);
            }
            else {
                Relayout(m_pCurrent, iChangeOffset);
            }
        }
    }
    return result;
}


EDT_ClipboardResult CEditBuffer::DeletePreviousChar(){
#ifdef DEBUG
    if ( m_pTestManager->Backspace() )
        return EDT_COP_OK;
#endif
    return DeleteChar(FALSE);
}

EDT_ClipboardResult CEditBuffer::DeleteNextChar()
{
    return DeleteChar(TRUE);
}

EDT_ClipboardResult CEditBuffer::DeleteChar(XP_Bool bForward, XP_Bool bTyping)
{
    VALIDATE_TREE(this);
    EDT_ClipboardResult result = EDT_COP_OK;
    if( IsTableOrCellSelected() )
    {
        // Delete just cell contents of all selected cells
        ClearSelectedCells();
    } 
    else 
    {
        ClearTableAndCellSelection();
        ClearPhantomInsertPoint();
        ClearMove();
        StartTyping(bTyping);
        if( IsSelected() ){
            result = DeleteSelection();
        }
        else {
            CEditSelection selection;
            GetSelection(selection);
            if ( Move(*selection.GetEdge(bForward), bForward ) ) {
                result = CanCut(selection, TRUE);
                if ( result == EDT_COP_OK ) {
                    DeleteSelection(selection, FALSE);
                }
            }
        }
    }
    return result;
}

EDT_ClipboardResult CEditBuffer::ClearSelectedCells()
{
    CEditTableCellElement *pFirstCell = GetFirstSelectedCell();
    if( pFirstCell )
    {
        
        BeginBatchChanges(kGroupOfChangesCommandID);

        CEditTableCellElement *pCell = pFirstCell;
        while(pCell)
        {
            pCell->DeleteContents();
            pCell = GetNextSelectedCell();
        }
        // Reset the insert point in the first cell cleared
        SetTableInsertPoint(pFirstCell);

        Relayout(pFirstCell->GetParentTable(), 0);

        EndBatchChanges();
    }
    return EDT_COP_OK;
}

XP_Bool CEditBuffer::Move(CEditInsertPoint& pt, XP_Bool forward) {
    CEditLeafElement* dummyNewElement = pt.m_pElement;
    ElementOffset dummyNewOffset = pt.m_iPos;
    XP_Bool result = FALSE;
    if ( forward )
        result = NextPosition(dummyNewElement, dummyNewOffset,
            dummyNewElement, dummyNewOffset);
    else
        result = PrevPosition(dummyNewElement, dummyNewOffset,
            dummyNewElement, dummyNewOffset);
    if ( result ) {
        pt.m_pElement = dummyNewElement;
        pt.m_iPos = dummyNewOffset;
    }
    return result;
}

XP_Bool CEditBuffer::CanMove(CEditInsertPoint& pt, XP_Bool forward) {
    CEditInsertPoint test(pt);
    return Move(test, forward);
}

XP_Bool CEditBuffer::CanMove(CPersistentEditInsertPoint& pt, XP_Bool forward) {
    CPersistentEditInsertPoint test(pt);
    return Move(test, forward);
}


XP_Bool CEditBuffer::Move(CPersistentEditInsertPoint& pt, XP_Bool forward) {
    CEditInsertPoint insertPoint = PersistentToEphemeral(pt);
    XP_Bool result = Move(insertPoint, forward);
    if ( result ) {
        pt = EphemeralToPersistent(insertPoint);
    }
    return result;
}

void CEditBuffer::SelectNextChar( ){
    CEditLeafElement *pBegin, *pEnd;
    ElementOffset iBeginPos, iEndPos;
    XP_Bool bFromStart;
    XP_Bool bFound;

    if( IsSelected() ){
        GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
        if( bFromStart ){
            bFound = NextPosition( pBegin, iBeginPos, pBegin, iBeginPos );
        }
        else {
            bFound = NextPosition( pEnd, iEndPos, pEnd, iEndPos );
        }
        if( bFound ){
            SelectRegion( pBegin, iBeginPos, pEnd, iEndPos, bFromStart, TRUE  );
        }
    }
    else {
        BeginSelection( TRUE, FALSE );
    }
}


void CEditBuffer::SelectPreviousChar( ){
    if( IsSelected() ){
        CEditLeafElement *pBegin, *pEnd;
        ElementOffset iBeginPos, iEndPos;
        XP_Bool bFound = FALSE;
        XP_Bool bFromStart;
        GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
        if( bFromStart ){
            bFound = PrevPosition( pBegin, iBeginPos, pBegin, iBeginPos );
        }
        else {
            bFound = PrevPosition( pEnd, iEndPos, pEnd, iEndPos );
        }
        if( bFound ){
            SelectRegion( pBegin, iBeginPos, pEnd, iEndPos, bFromStart, FALSE  );
        }
    }
    else {
         BeginSelection( TRUE, TRUE );
    }
}

XP_Bool CEditBuffer::PrevPosition(CEditLeafElement *pEle, ElementOffset iOffset,
                CEditLeafElement*& pNew, ElementOffset& iNewOffset ){

    LO_Element* pElement;
    int iLayoutOffset;
    int iNewLayoutOffset;

    CEditElement* pNewEditElement;
    if( pEle->GetLOElementAndOffset( iOffset, FALSE, pElement, iLayoutOffset ) ){
        XP_Bool result = LO_PreviousPosition( m_pContext, pElement, iLayoutOffset, &pNewEditElement, &iNewLayoutOffset );
        if ( result ){
            pNew = pNewEditElement->Leaf();
            iNewOffset = iNewLayoutOffset;
        }
        return result;
    }
    else {
        return pEle->PrevPosition( iOffset, pNew, iNewOffset );
    }
}

XP_Bool CEditBuffer::NextPosition(CEditLeafElement *pEle, ElementOffset iOffset,
                CEditLeafElement*& pNew, ElementOffset& iNewOffset ){

    LO_Element* pElement;
    int iLayoutOffset;
    int iNewLayoutOffset;
    CEditElement* pNewEditElement;

    if( pEle->GetLOElementAndOffset( iOffset, FALSE, pElement, iLayoutOffset )){
        XP_Bool result = LO_NextPosition( m_pContext, pElement, iLayoutOffset, &pNewEditElement, &iNewLayoutOffset );
        // The insert point can't be the end of the document.
        if ( result && (
            ! pNewEditElement || pNewEditElement->IsEndOfDocument() ) ) {
            result = FALSE;
        }
        if ( result ){
            pNew = pNewEditElement->Leaf();
            iNewOffset = iNewLayoutOffset;
        }
        return result;
    }
    else {
        pEle->NextPosition( iOffset, pNew, iNewOffset );
        return TRUE;
    }
}

void CEditBuffer::NextChar( XP_Bool bSelect ){
    VALIDATE_TREE(this);
//    int iSavePos = m_iCurrentOffset;

    if( bSelect ){
        SelectNextChar();
        return;
    }
    ClearPhantomInsertPoint();
    ClearMove();
    ClearSelection();
    //FixupInsertPoint();
    NextPosition( m_pCurrent, m_iCurrentOffset,
                    m_pCurrent, m_iCurrentOffset );
    SetCaret();
}



XP_Bool CEditBuffer::PreviousChar( XP_Bool bSelect ){
    VALIDATE_TREE(this);

    if( bSelect ){
        SelectPreviousChar();
        return FALSE;       // no return value on select previous..
    }

    ClearPhantomInsertPoint();
    ClearMove();
    ClearSelection();
    //FixupInsertPoint();
    if( PrevPosition( m_pCurrent, m_iCurrentOffset,
                    m_pCurrent, m_iCurrentOffset )){
        SetCaret();
        return TRUE;
    }
    else {
        return FALSE;
    }
}


//
// Find out our current layout position and move up or down from here.
//
void CEditBuffer::UpDown( XP_Bool bSelect, XP_Bool bForward ){
    VALIDATE_TREE(this);
    CEditLeafElement *pEle;
    ElementOffset iOffset;
    XP_Bool bStickyAfter;
    LO_Element* pElement;
    int iLayoutOffset;

    DoneTyping();

    BeginSelection();

    if( bSelect ){
        GetInsertPoint( &pEle, &iOffset, &bStickyAfter );
    }
    else {
        // do the right thing depending on insertion point change of direction
        ClearSelection( TRUE, !bForward );
        ClearPhantomInsertPoint();
        pEle = m_pCurrent;
        iOffset = m_iCurrentOffset;
        bStickyAfter = m_bCurrentStickyAfter;
    }

    XP_ASSERT(!pEle->IsEndOfDocument());
    pEle->GetLOElementAndOffset( iOffset, bStickyAfter, pElement, iLayoutOffset );

    LO_UpDown( m_pContext, pElement, iLayoutOffset, GetDesiredX(pEle, iOffset, bStickyAfter), bSelect, bForward );

    EndSelection();
}

XP_Bool CEditBuffer::NextTableCell( XP_Bool /*bSelect*/, XP_Bool bForward, intn* pRowCounter )
{
    if( !  IsInsertPointInTable() )
        return FALSE;

    CEditTableCellElement* pTableCell = m_pCurrent->GetTableCellIgnoreSubdoc();
    if( !pTableCell )
        return FALSE;

    CEditElement *pNext;
    if(bForward)
    {
        pNext = pTableCell->GetNextSibling();
    } else {
        pNext = pTableCell->GetPreviousSibling();
    }
    if( !pNext )
    {
        // No sibling found, but we may need to check for more rows
        CEditTableRowElement* pRow = pTableCell->GetTableRowIgnoreSubdoc();
        CEditElement* pNextRow;
        if( pRow )
        {
            if(bForward)
            {
                pNextRow = pRow->GetNextSibling();
            } else {
                pNextRow = pRow->GetPreviousSibling();
            }
            if( pNextRow && pNextRow->IsTableRow() )
            {
                if(bForward)
                {
                    pNext = pNextRow->GetChild();
                    // Tell caller we wrapped to next row
                    if( pNext && pRowCounter )
                        (*pRowCounter)++;

                } else {
                    pNext = pNextRow->GetLastChild();
                    // Tell caller we wrapped to previous row
                    if( pNext && pRowCounter )
                        (*pRowCounter)--;
                }
            }
        }
    }
    if( pNext )
    {
        CEditInsertPoint insertPoint;
        insertPoint.m_pElement = pNext->GetFirstMostChild()->Leaf();
        if( insertPoint.m_pElement )
        {
            SetInsertPoint(insertPoint);
            return TRUE;
        }
    }
    return FALSE;
}

int32
CEditBuffer::GetDesiredX(CEditLeafElement* pEle, intn iOffset, XP_Bool bStickyAfter)
{
    XP_ASSERT(bStickyAfter == TRUE || bStickyAfter == FALSE);
    // m_iDesiredX is where we would move if we could.  This keeps the
    //  cursor moving, basically, straight up and down, even if there is
    //  no text or gaps
    if( m_iDesiredX == -1 ){
        LO_Element* pElement;
        int iLayoutOffset;

        // A break at offset 1 is the beginning of the next line.  We really want
        //  the end in this case.
        if( pEle->IsBreak() && iOffset == 1 ){
            iOffset = 0;
        }

        pEle->GetLOElementAndOffset( iOffset, bStickyAfter, pElement, iLayoutOffset );
        int32 x;
        int32 y;
        int32 width;
        int32 height;
        LO_GetEffectiveCoordinates(m_pContext, pElement, iLayoutOffset, &x, &y, &width, &height);
        m_iDesiredX = x;
    }
    return m_iDesiredX;
}


void CEditBuffer::NavigateChunk( XP_Bool bSelect, intn chunkType, XP_Bool bForward ){
    if ( chunkType == EDT_NA_UPDOWN ){
        UpDown(bSelect, bForward);
    }
    else {
        VALIDATE_TREE(this);
        ClearPhantomInsertPoint();
        ClearMove();    /* Arrow keys clear the up/down target position */
        BeginSelection();
        LO_NavigateChunk( m_pContext, chunkType, bSelect, bForward );
        EndSelection();
        DoneTyping();
    }
}

//cmanske: TEST FOR SELECTED TABLE/CELLS
void CEditBuffer::ClearMailQuote(){
    if ( ! m_pCurrent->InMungableMailQuote() ) {
        XP_ASSERT(FALSE);
        return;
    }
    // Outdent to get rid of MailQuote info.
    while( m_pCurrent->InMungableMailQuote() ) {
        if ( ! Outdent() ){
            /* Currently (4.0) we can't outdent in the presence of DIV tags. */
            return;
        }
    }
    // Get rid of paragraph style
    MorphContainer(P_NSDT);
    SetParagraphAlign(ED_ALIGN_DEFAULT);
    if ( m_pCurrent->IsText() ) {
        // Get rid of character styles
        EDT_CharacterData* pCharacterData = EDT_NewCharacterData();
        // We wanted to do ~0, but this causes an assert in SetCharacterData.
        pCharacterData->mask = ~(TF_SERVER|TF_SCRIPT|TF_STYLE);
        pCharacterData->values = 0; // Clear everything.
        SetCharacterData(pCharacterData);
        EDT_FreeCharacterData(pCharacterData);
    }
}

//
// Break current container into two containers.
//
EDT_ClipboardResult CEditBuffer::ReturnKey(XP_Bool bTyping){
    ClearTableAndCellSelection();
    EDT_ClipboardResult result = EDT_COP_OK;
    if ( bTyping ) {
        VALIDATE_TREE(this);
#ifdef DEBUG
        if ( m_pTestManager->ReturnKey() )
            return result;
#endif
        if( IsSelected() ){
            result = DeleteSelection();
            if ( result != EDT_COP_OK ) return result;
        }
        // Check if we are in a mail quote
        CEditInsertPoint ip;
        GetInsertPoint(ip);
        if ( ip.m_pElement->InMungableMailQuote() ) {
            // Are we at the end of the mail quote? If so, move beyond
            XP_Bool bAtEndOfMailQuote = FALSE;
            if ( ip.IsEndOfContainer() ) {
                ip = ip.NextPosition();
                if ( ! ip.m_pElement->InMailQuote() ) {
                    bAtEndOfMailQuote = TRUE;
                }
            }
            result = InternalReturnKey(TRUE);
            if ( result == EDT_COP_OK ) {
                if ( ! bAtEndOfMailQuote ) {
                    InternalReturnKey(TRUE);
                    UpDown(FALSE, FALSE); // go back up one line.
                }
                ClearMailQuote();
            }
        }
        else {
            result = InternalReturnKey(TRUE);
        }
    }
    else {
        result = InternalReturnKey(TRUE);
    }
    return result;
}

EDT_ClipboardResult CEditBuffer::InternalReturnKey(XP_Bool bUserTyped){
    EDT_ClipboardResult result = EDT_COP_OK;

    if( IsSelected() ){
        result = DeleteSelection();
        if ( result != EDT_COP_OK ) return result;
    }

    // ClearSelection();
    ClearPhantomInsertPoint();
    FixupInsertPoint();

    CEditElement *pChangedElement = m_pCurrent;
    CEditLeafElement *pNew,*pSplitAfter;
    CEditLeafElement *pCopyFormat;
    int iChangedOffset = m_iCurrentOffset;

    pSplitAfter = m_pCurrent;
    pCopyFormat = m_pCurrent;

    if( m_iCurrentOffset == 0 ){
        // split at beginning of element
        if( m_pCurrent->PreviousLeafInContainer() == 0){
            CEditTextElement *pNew;
            pNew = m_pCurrent->CopyEmptyText();
            pNew->InsertBefore( m_pCurrent );
            pChangedElement = pNew->FindContainer();
            pSplitAfter = pNew;
        }

    }
    else {
        pNew = m_pCurrent->Divide( m_iCurrentOffset )->Leaf();
        /* Get rid of space at start of new line if user typed. */
        if( bUserTyped && !pNew->InFormattedText()
                && pNew->IsA(P_TEXT)
                && pNew->Text()->GetLen()
                && pNew->Text()->GetText()[0] == ' ' ){
            pNew->Text()->DeleteChar(m_pContext, 0);
            CEditLeafElement *pNext = 0;
            if( pNew->Text()->GetLen() == 0
                    && (pNext = pNew->TextInContainerAfter()) != 0 ){
                pNew->Unlink();
                // Should we delete pNew?
                delete pNew;
                pNew = pNext;
            }
        }
        m_pCurrent = pNew;
    }
    m_iCurrentOffset = 0;
    m_pCurrent->GetParent()->Split( pSplitAfter, 0,
                &CEditElement::SplitContainerTest, 0 );

    if ( bUserTyped && m_pCurrent->GetLen() == 0 && m_pCurrent->TextInContainerAfter() == 0 ){
        // An empty paragraph. If the style is one of the
        // heading styles, set it back to normal
        CEditContainerElement* pContainer = m_pCurrent->FindContainer();
        TagType tagType = pContainer->GetType();
        // To do - use one of those fancy bit arrays.
        if ( tagType >= P_HEADER_1 && tagType <=  P_HEADER_6){
#if EDT_DDT
            pContainer->SetType(P_NSDT);
#else
            pContainer->SetType(P_DESC_TITLE);
#endif
        }
    }

    if ( bUserTyped ) {
        Reduce(m_pRoot); // Or maybe just the two containers?
    }
	
	// We currently cannot do a reflow here as the new text elements have not been
	// created to be reflowed...
    Relayout(pChangedElement, iChangedOffset,
        (pChangedElement != m_pCurrent ? m_pCurrent : 0 ));

#if 0
    // The bug is that after a return is pressed, the formatting is wrong
    //  for the new paragrah.  Presents a problem.  If we insert an empty
    //  text element here, we get messed up because we may not be able to
    //  position the caret.  We end up reducing and loosing a pointer to
    //  the layout element.  It is a hard problem.

    CEditTextElement *pNewText = pCopyFormat->CopyEmptyText();
    pNewText->InsertBefore(m_pCurrent);
    m_pCurrent = pNewText;
    m_iCurrentOffset = 0;
#endif

    return result;
}

//TODO: Should this be a preference?
#define EDT_SPACES_PER_TAB  4

EDT_ClipboardResult CEditBuffer::TabKey(XP_Bool bForward, XP_Bool bForceTabChar){

    ClearTableAndCellSelection();
    if( IsInsertPointInTable() && !bForceTabChar){
        if( NextTableCell(FALSE, bForward) ){
            // This selects contents of next cell:
            //cmanske: Other programs select the contents of a cell when
            //  tab into it, but lets not do that!
            // This function will now select the cell boundary, not contents
//            SelectTableCell();
        }
        else if ( bForward ) {
            // Tabbing past last cell creates a new row
            // back-tabbing (Shift-Tab) in first cell is ignored
            BeginBatchChanges(kInsertTableRowCommandID);
            // Save current insert point so we can return
            //  there when done
            CEditInsertPoint ip;
            GetTableInsertPoint(ip);
            InsertTableRows(NULL, TRUE, 1);
            // Restore previous insert point
            //  then move to first new cell
            SetInsertPoint(ip);
            NextTableCell(FALSE, bForward);
            EndBatchChanges();
        }
        return EDT_COP_OK;
    }

    // Tab = insert some extra spaces
    // TODO: SHOULD WE USE "SPACER" TAG INSTEAD?
    EDT_ClipboardResult result;
    for( int i = 0; i < EDT_SPACES_PER_TAB; i++ ){
        if( EDT_COP_OK != (result = InsertChar(' ', FALSE)) ){
            break;
        }
    }
    return result;
}

void CEditBuffer::IndentSelection(CEditSelection& selection)
{
    CEditLeafElement *pBegin, *pEnd, *pCurrent;
    ElementOffset iBeginPos, iEndPos;
    XP_Bool bFromStart;

    // Don't relayout if we got a cell selection passed in
    XP_Bool bRelayout = selection.IsEmpty();

    // Get data from supplied selection or get current selection if empty
    GetSelection(selection, pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
    pCurrent = pBegin;
    XP_Bool bDone = FALSE;
    CEditContainerElement* pLastContainer = 0;
    CEditContainerElement* pContainer = 0;
    CEditListElement* pList;
    do {
        pCurrent->FindList( pContainer, pList );
        if( pContainer != pLastContainer ){
            IndentContainer( pContainer, pList );
            pLastContainer = pContainer;
        }
        bDone = (pEnd == pCurrent );    // For most cases
        pCurrent = pCurrent->NextLeafAll();
        bDone = bDone || (iEndPos == 0 && pEnd == pCurrent ); // Pesky edge conditions!
    } while( pCurrent && !bDone );

    if( bRelayout )
    {
        // force layout stop displaying the current selection.
#ifdef LAYERS
        LO_StartSelectionFromElement( m_pContext, 0, 0, NULL );
#else
        LO_StartSelectionFromElement( m_pContext, 0, 0);
#endif
        // probably needs to be the common ancestor.
        Relayout( pBegin->FindContainer(), 0, pEnd, RELAYOUT_NOCARET );
        // Need to force selection.
        SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
    }
}

void CEditBuffer::Indent()
{
    VALIDATE_TREE(this);
    CEditSelection selection;
    if( IsSelected() )
    {
        IndentSelection(selection);
        return;
    }
    if( IsTableOrCellSelected() )
    {
        if( GetFirstCellSelection(selection) )
        {
            BeginBatchChanges(kChangeAttributesCommandID);
            IndentSelection(selection);
            while( GetNextCellSelection(selection) )
            {
                IndentSelection(selection);
            }
            RelayoutSelectedTable();
            EndBatchChanges();
        }
        return;
    } 

    //
    // find where cont-cont has cont
    //
    CEditContainerElement* pContainer;
    CEditListElement* pList;
    m_pCurrent->FindList( pContainer, pList );
    IndentContainer( pContainer, pList );
    Relayout( pContainer, 0, pContainer->GetLastMostChild() );
}

void CEditBuffer::IndentContainer( CEditContainerElement *pContainer,
        CEditListElement *pList ){
    VALIDATE_TREE(this);

    CEditElement *pPrev = pContainer->GetPreviousSibling();
    CEditElement *pNext = pContainer->GetNextSibling();


    //
    // case 1
    //
    //     UL:
    //         LI:
    //     LI:         <- Indenting this guy
    //     LI:
    //     LI:
    //
    //  should result in
    //
    //     UL:
    //         LI:
    //         LI:     <-end up here
    //     LI:
    //     LI:
    //
    //
    //  We also need to handle the case
    //
    //      UL:
    //          LI:
    //      LI:         <- indent this guy
    //      UL:
    //          LI:
    //          LI:
    //
    //  and the second UL becomes redundant so we eliminate it.
    //
    //      UL:
    //          LI:
    //          LI:     <-Ends up here
    //          LI:     <-frm the redundant container
    //          LI:
    //
    if( pPrev && pPrev->IsList() ){
        CEditElement *pChild = pPrev->GetLastChild();
        if( pChild ){
            pContainer->Unlink();
            pContainer->InsertAfter( pChild );
        }
        else {
            pContainer->Unlink();
            pContainer->InsertAsFirstChild( pPrev );
        }
        if( pNext && pNext->IsList() ){
            pPrev->Merge( pNext );
        }
    }
    //
    // case 0
    //
    //      LI:             <- Indenting this guy
    //
    // should result in
    //
    //      UL:
    //          LI:
    //
    //
    else if( pList == 0 ){
        PA_Tag *pTag = XP_NEW( PA_Tag );
        XP_BZERO( pTag, sizeof( PA_Tag ) );
        pTag->type = P_UNUM_LIST;
        CEditElement *pEle = new CEditListElement( 0, pTag, GetRAMCharSetID() );
        PA_FreeTag( pTag );

        pEle->InsertAfter( pContainer );
        pContainer->Unlink();
        pContainer->InsertAsFirstChild( pEle );
        return;
    }
    //
    // case 2
    //
    //     UL:
    //          LI:
    //          LI:         <- Indenting this guy
    //          UL:
    //              LI:
    //          LI:
    //          LI:
    //
    //  should result in
    //
    //     UL:
    //          LI:
    //          UL:
    //              LI:     <- Ends up here.
    //              LI:
    //          LI:
    //          LI:
    //
    else if( pNext && pNext->IsList() ){
        CEditElement *pChild = pNext->GetChild();
        if( pChild ){
            pContainer->Unlink();
            pContainer->InsertBefore( pChild );
        }
        else {
            pContainer->Unlink();
            pContainer->InsertAsFirstChild( pPrev );
        }
    }
    //
    // case 3
    //
    //     UL:
    //          LI:
    //          LI:         <- Indenting this guy
    //          LI:
    //          LI:
    //
    //  should result in
    //
    //     UL:
    //          LI:
    //          UL:
    //              LI:     <- Ends up here.
    //          LI:
    //          LI:
    //
    else {
        CEditElement *pNewList;
        pNewList = pList->Clone(0);


        // insert the new cont-cont between the old one and the cont
        pNewList->InsertBefore( pContainer );
        pContainer->Unlink();
        pContainer->InsertAsFirstChild( pNewList);
    }
}

XP_Bool CEditBuffer::OutdentSelection(CEditSelection& selection)
{
    CEditLeafElement *pBegin, *pEnd, *pCurrent;
    ElementOffset iBeginPos, iEndPos;
    XP_Bool bFromStart;

    // Don't relayout if we got a cell selection passed in
    XP_Bool bRelayout = selection.IsEmpty();

    // Get data from supplied selection or get current selection if empty
    GetSelection(selection, pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
    pCurrent = pBegin;
    XP_Bool bDone = FALSE;
    CEditContainerElement* pLastContainer = 0;
    CEditContainerElement* pContainer = 0;
    CEditListElement* pList;
    do {
        pCurrent->FindList( pContainer, pList );
        if( pContainer != pLastContainer ){
            OutdentContainer( pContainer, pList );
            pLastContainer = pContainer;
        }
        bDone = (pEnd == pCurrent );    // For most cases
        pCurrent = pCurrent->NextLeafAll();
        bDone = bDone || (iEndPos == 0 && pEnd == pCurrent ); // Pesky edge conditions!
    } while( pCurrent && !bDone );

    if( bRelayout )
    {
        // force layout stop displaying the current selection.
#ifdef LAYERS
        LO_StartSelectionFromElement( m_pContext, 0, 0, NULL );
#else
        LO_StartSelectionFromElement( m_pContext, 0, 0);
#endif
        // probably needs to be the common ancestor.
        Relayout( pBegin->FindContainer(), 0, pEnd, RELAYOUT_NOCARET );
        // Need to force selection.
        SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
    }
    // How can we always return TRUE??
    return TRUE;
}

XP_Bool CEditBuffer::Outdent()
{
    CEditSelection selection;
    VALIDATE_TREE(this);

    if( IsSelected() ){
        return OutdentSelection(selection);
    }
    if( IsTableOrCellSelected() )
    {
        XP_Bool bResult = FALSE;
        if( GetFirstCellSelection(selection) )
        {
            BeginBatchChanges(kChangeAttributesCommandID);
            // Format first selected cell
            bResult = OutdentSelection(selection);
            // Select all other cells and format them
            while( GetNextCellSelection(selection) )
            {
                bResult = OutdentSelection(selection);
            }
            RelayoutSelectedTable();
            EndBatchChanges();
        }
        return bResult;
    } 

    CEditContainerElement* pContainer = NULL;
    CEditListElement* pList = NULL;
    m_pCurrent->FindList( pContainer, pList );
    if ( ! pContainer || !pList ) {
        return FALSE;
    }
    OutdentContainer( pContainer, pList );
    Relayout( pContainer, 0, pContainer->GetLastMostChild() );
    return TRUE;
}

void CEditBuffer::OutdentContainer( CEditContainerElement *pContainer,
        CEditListElement *pList ){

    //
    // case 0
    //
    //      LI:     <-- outdent this guy.
    //
    if( pList == 0 ){
        return;                         // no work to do
    }

    CEditElement *pPrev = pContainer->GetPreviousSibling();
    CEditElement *pNext = pContainer->GetNextSibling();

    //
    // case 1
    //
    //      UL:
    //          LI:     <-Outdenting this guy
    //
    // should result in
    //
    //      LI:
    //
    // No previous or next siblings.  Just remove the List and
    //  put its container in its place.
    //
    if( pPrev == 0 && pNext == 0 ){
        pContainer->Unlink();
        pContainer->InsertAfter( pList );
        pList->Unlink();
        delete pList;
    }


    //
    // case 2
    //
    //      UL:
    //          LI:     <-Outdenting this guy
    //          LI:
    //
    //
    //      Results in:
    //
    //      LI:         <-Outdenting this guy
    //      UL:
    //          LI:
    //
    else if( pPrev == 0 ){
        pContainer->Unlink();
        pContainer->InsertBefore( pList );
    }
    //
    // case 3
    //
    //      UL:
    //          LI:
    //          LI:     <-Outdenting this guy
    //
    //
    //      Results in:
    //
    //      UL:
    //          LI:
    //      LI:         <-Outdenting this guy
    //
    else if( pNext == 0 ){
        pContainer->Unlink();
        pContainer->InsertAfter( pList );
    }

    //
    // case 4
    //
    //      UL:
    //          LI:
    //          LI:     <-Outdenting this guy
    //          LI:
    //
    //      Results in:
    //
    //      UL:
    //          LI:
    //      LI:         <-Outdenting this guy
    //      UL:
    //          LI:
    else {
        CEditElement *pNewList = pList->Clone(0);
        while( (pNext = pContainer->GetNextSibling()) != 0 ){
            pNext->Unlink();
            pNext->InsertAsLastChild( pNewList );
        }
        pContainer->Unlink();
        pContainer->InsertAfter( pList );
        pNewList->InsertAfter( pContainer );
    }
}

ED_ElementType CEditBuffer::GetCurrentElementType(){
    //cmanske: VERY RISKY! Need to test the effects of this thoroughly!
    // Table and cell selection occurs when there is still a caret 
    // (but must not have anything else selected),
    if( m_pSelectedEdTable )
        return ED_ELEMENT_TABLE;

    if( m_SelectedEdCells.Size() )
        return ED_ELEMENT_CELL;
    
    CEditLeafElement *pInsertPoint;
    ElementOffset iOffset;
    XP_Bool bSingleItem;

    bSingleItem = GetPropertyPoint( &pInsertPoint, &iOffset );

    if( !bSingleItem ){
        if( IsSelected() ){
            return ED_ELEMENT_SELECTION;
        }
        else {
            return ED_ELEMENT_TEXT;
        }
    }
    else if( pInsertPoint->IsA(P_TEXT) ){
        // should never have single item text objects...
        XP_ASSERT(FALSE);
        return ED_ELEMENT_TEXT;
    }
    else if( pInsertPoint->IsImage() ){
        return ED_ELEMENT_IMAGE;
    }
    else if( pInsertPoint->IsA(P_HRULE) ){
        return ED_ELEMENT_HRULE;
    }
    else if( pInsertPoint->IsA(P_LINEBREAK )){
        return ED_ELEMENT_TEXT;
    }
    else if( pInsertPoint->GetElementType() == eTargetElement ){
        return ED_ELEMENT_TARGET;
    }
    else if( pInsertPoint->GetElementType() == eIconElement ){
        return ED_ELEMENT_UNKNOWN_TAG;
    }
    // current element type is unknown.
    XP_ASSERT( FALSE );
    return ED_ELEMENT_TEXT;     // so the compiler doesn't complain.
}

void CEditBuffer::MorphContainer( TagType t )
{
    VALIDATE_TREE(this);

    CEditSelection selection;

    // charley says this is happening.. Hmmmm...
    //XP_ASSERT( t != P_TEXT );
    if( t == P_TEXT ){
        // actually failed this assert.
        return;
    }

    // Only allow the type to be set to a text container type.
    // This is important. If we allow a different type to be
    // set, then CEditElement::SplitContainerTest will
    // potentially fail.
    if ( ! BitSet( edt_setTextContainer,  t  ) ) {
        XP_ASSERT(FALSE);
        return;
    }

    if( IsSelected() ){
        MorphContainerSelection(t, selection);
    }
    else if( IsTableOrCellSelected() )
    {
        if( GetFirstCellSelection(selection) )
        {
            BeginBatchChanges(kChangeAttributesCommandID);
            MorphContainerSelection(t, selection);
            while( GetNextCellSelection(selection) )
            {
                MorphContainerSelection(t, selection);
            }
            RelayoutSelectedTable();
            EndBatchChanges();
        }
    } 
    else {
        CEditElement *pContainer = m_pCurrent->FindContainer();
        // HACKOLA: just poke in a new tag type.
        pContainer->SetType( t );
        // Need to relayout from previous line.  Line break distance is wrong.
        Relayout( pContainer, 0, 0 );
    }
}

void CEditBuffer::MorphContainerSelection( TagType t, CEditSelection& selection )
{
    CEditLeafElement *pBegin, *pEnd, *pCurrent;
    CEditContainerElement* pContainer = NULL;
    ElementOffset iBeginPos, iEndPos;
    XP_Bool bFromStart;

    // Don't relayout if we got a cell selection passed in
    XP_Bool bRelayout = selection.IsEmpty();

    // Get elements from supplied selection or get current selection
    GetSelection( selection, pBegin, iBeginPos, pEnd, iEndPos, bFromStart );

    pCurrent = pBegin;
    XP_Bool bDone = FALSE;
    do {
        pContainer = pCurrent->FindContainer();
        pContainer->SetType( t );

        bDone = (pEnd == pCurrent );    // For most cases
        pCurrent = pCurrent->NextLeafAll();
        bDone = bDone || (iEndPos == 0 && pEnd == pCurrent ); // Pesky edge conditions!
    } while( pCurrent && !bDone );

    if( bRelayout )
    {
        // force layout stop displaying the current selection.
#ifdef LAYERS
        LO_StartSelectionFromElement( m_pContext, 0, 0, NULL );
#else
        LO_StartSelectionFromElement( m_pContext, 0, 0);
#endif
        // probably needs to be the common ancestor.
        Relayout( pBegin->FindContainer(), 0, pEnd, RELAYOUT_NOCARET );
        // Need to force selection.
        SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
    }
}


void CEditBuffer::MorphListContainer( TagType t )
{
    CEditSelection selection;
    if( IsTableOrCellSelected() )
    {
        if( GetFirstCellSelection(selection) )
        {
            BeginBatchChanges(kChangeAttributesCommandID);
            MorphListContainer2(t, selection);
            while( GetNextCellSelection(selection) )
            {
                MorphListContainer2(t, selection);
            }
            RelayoutSelectedTable();
            EndBatchChanges();
        }
    }
    else
    {
        MorphListContainer2(t, selection);
    }
}

void CEditBuffer::MorphListContainer2( TagType t, CEditSelection& /*selection*/ )
{
    //TODO: HANDLE LISTS IN SELECTION
    EDT_ListData * pListData = GetListData();
    if( !pListData ){
        // We don't have container -- start one
        Indent();
        pListData = GetListData();
        // Block quote can be any paragraph style,
        // Description list should contain only <DT> and <DD>,
        //   others must be list item
    } else if( t != P_BLOCKQUOTE && t != P_DESC_LIST ){
        MorphContainer(P_LIST_ITEM);
    }
    if( pListData ){
        pListData->iTagType = t;
        if( t != P_BLOCKQUOTE ){
            pListData->eType = ED_LIST_TYPE_DEFAULT;
        }
        SetListData(pListData);
        EDT_FreeListData(pListData);
    }
}

void CEditBuffer::ToggleList(intn iTagType)
{
    CEditSelection selection;
    if( IsTableOrCellSelected() )
    {
        if( GetFirstCellSelection(selection) )
        {
            BeginBatchChanges(kChangeAttributesCommandID);
            ToggleList2(iTagType, selection);
            while( GetNextCellSelection(selection) )
            {
                ToggleList2(iTagType, selection);
            }
            RelayoutSelectedTable();
            EndBatchChanges();
        }
    } else {
        ToggleList2(iTagType, selection);
    }
}

// This doesn't seem to pay attention to selection - probably source of some bugs
void CEditBuffer::ToggleList2(intn iTagType, CEditSelection& /*selection*/)
{
    EDT_ListData * pData = NULL;
	TagType nParagraphFormat = GetParagraphFormatting();
    XP_Bool bIsMyList = FALSE;
    XP_Bool bIsDescList = FALSE;

    if ( nParagraphFormat == P_LIST_ITEM || iTagType == P_DESC_LIST ) {
        pData = GetListData();
        bIsMyList = ( pData && pData->iTagType == iTagType );
    }
    bIsDescList = bIsMyList && (iTagType == P_DESC_LIST);

    if ( (nParagraphFormat == P_LIST_ITEM || bIsDescList)
         && bIsMyList ) {
        // WILL THIS ALONE UNDO THE LIST?
        MorphContainer(P_NSDT);
        Outdent();
    } else {
        if ( !pData ) {
            // Must be indendented
            Indent();
            // Create a numbered list item ONLY if not Description list
            if (nParagraphFormat != P_LIST_ITEM && iTagType != P_DESC_LIST) {
                MorphContainer(P_LIST_ITEM);
            }
            pData = GetListData();
        } else if (nParagraphFormat == P_LIST_ITEM && iTagType == P_DESC_LIST ){
            // We are converting an existing list item into
            //   a description, so remove the list item
            MorphContainer(P_DESC_TITLE);
            pData = GetListData();
        }


        if ( pData && (pData->iTagType != iTagType) ){
            pData->iTagType = iTagType;
            pData->eType = ED_LIST_TYPE_DEFAULT;
            SetListData(pData);
        }
    }
    if ( pData ) {
        EDT_FreeListData(pData);
    }
}

void CEditBuffer::SetParagraphAlign( ED_Alignment eAlign ){
    VALIDATE_TREE(this);
    XP_Bool bDone;

    // To avoid extra HTML params, use default
    //  for left align since that's how layout will do it
    //cmanske: Windows FE was doing this - moved logic here for consistency
    // TODO: This is messy for table cells: We need to check if ROW
    //   has set alignment and NOT do this if it isn't already ED_ALIGN_DEFAULT,
    //   otherwise cell gets the row's alignment
    if( eAlign == ED_ALIGN_LEFT )
        eAlign = ED_ALIGN_DEFAULT;

    if( IsSelected() ){
        CEditLeafElement *pBegin, *pEnd, *pCurrent;
        CEditContainerElement *pContainer = NULL;
        ElementOffset iBeginPos, iEndPos;
        XP_Bool bFromStart;
        GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
        pCurrent = pBegin;
        bDone = FALSE;
        do {
            pContainer = pCurrent->FindContainer();
            pContainer->Container()->SetAlignment( eAlign );

            bDone = (pEnd == pCurrent );    // For most cases
            pCurrent = pCurrent->NextLeafAll();
            bDone = bDone || (iEndPos == 0 && pEnd == pCurrent ); // Pesky edge conditions!
        } while( pCurrent && !bDone );

        // force layout stop displaying the current selection.
#ifdef LAYERS
        LO_StartSelectionFromElement( m_pContext, 0, 0, NULL );
#else
        LO_StartSelectionFromElement( m_pContext, 0, 0);
#endif
        // probably needs to be the common ancestor.
        Relayout( pBegin->FindContainer(), 0, pEnd, RELAYOUT_NOCARET );
        // Need to force selection.
        SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
    }
    else if( m_pSelectedEdTable )
    {
        SetTableAlign(eAlign);
    }
    else if( m_SelectedEdCells.Size() )
    {
        // Tables are weird - must use ABSCENTER
        if( eAlign == ED_ALIGN_CENTER )
            eAlign = ED_ALIGN_ABSCENTER;

        int iCount = m_SelectedEdCells.Size();
        for( int i = 0; i < iCount; i++ )
        {
            // TODO: CHECK FOR ROW AND SET ALIGNMENT THERE INSTEAD
            EDT_TableCellData *pData = m_SelectedEdCells[i]->GetData();
            if( pData )
            {
                pData->align = eAlign;
                m_SelectedEdCells[i]->SetData( pData );
                EDT_FreeTableCellData(pData);
            }
        }
        CEditTableElement *pTable = m_SelectedEdCells[0]->GetTableIgnoreSubdoc();
        if( pTable )
            Relayout( pTable, 0 );
    }
    else {
        CEditElement *pContainer = m_pCurrent->FindContainer();
        // HACKOLA: just poke in a new tag type.
        pContainer->Container()->SetAlignment( eAlign );
        // Need to Relayout from previous line.  Line break distance is wrong.
        Relayout( pContainer, 0, pContainer->GetLastMostChild() );
    }
}

ED_Alignment CEditBuffer::GetParagraphAlign( ){
    CEditLeafElement *pInsertPoint;
    ElementOffset iOffset;

    GetPropertyPoint( &pInsertPoint, &iOffset );
    if( pInsertPoint ) {
        CEditContainerElement* pCont = pInsertPoint->FindContainer();
        if ( pCont != 0){
            return pCont->GetAlignment();
        }
    }
    return ED_ALIGN_DEFAULT;
}

void CEditBuffer::SetTableAlign( ED_Alignment eAlign )
{
    if( IsInsertPointInTable() )
    {
        // To avoid extra HTML params, use default
        //  for left align since that's how layout will do it
        if( eAlign == ED_ALIGN_LEFT )
            eAlign = ED_ALIGN_DEFAULT;
        
        // Tables need ABSCENTER or they don't center!
        if( eAlign == ED_ALIGN_CENTER )
            eAlign = ED_ALIGN_ABSCENTER;

        EDT_TableData* pData = GetTableData();
        if( pData )
        {
            if( eAlign != pData->align )
            {
                pData->align = eAlign;
                SetTableData(pData);
            }
            EDT_FreeTableData(pData);
        }
    }        
}

void CEditBuffer::SetFontPointSize( int iPoints )
{
    EDT_CharacterData * pData;
    CEditSelection selection;

    if( IsTableOrCellSelected() )
    {
        // Format each cell's contents
        if( GetFirstCellSelection(selection) )
        {
            BeginBatchChanges(kChangeAttributesCommandID);
            pData = GetCharacterDataSelection(0, selection);
            if( pData )
            {
                pData->iPointSize = iPoints;
                SetCharacterDataSelection(pData, selection, FALSE);
                EDT_FreeCharacterData(pData);
                while( GetNextCellSelection(selection) )
                {
                    pData = GetCharacterDataSelection(pData, selection);
                    if( pData )
                    {
                        pData->iPointSize = iPoints;
                        SetCharacterDataSelection(pData, selection, FALSE);
                        EDT_FreeCharacterData(pData);
                    }
                }
            }
            RelayoutSelectedTable();
            EndBatchChanges();
        }
        return;
    } 

    pData = GetCharacterData();
    if( pData ){
        pData->mask = pData->values = TF_FONT_POINT_SIZE;
        pData->iPointSize = (int16)iPoints;
        // This handles selected tables correctly
        SetCharacterData(pData);
        EDT_FreeCharacterData(pData);
    }
}

void CEditBuffer::SetFontSize( int iSize ){
    VALIDATE_TREE(this);

    CEditSelection selection;
    GetSelection(selection);
    if ( selection.IsContainerEnd() )
        return;
    else if( selection.AnyLeavesSelected() ){
        BeginBatchChanges(kChangeAttributesCommandID);
        SetFontSizeSelection( iSize, selection, TRUE );
        EndBatchChanges();
        return;
    }
    if( IsTableOrCellSelected() )
    {
        if( GetFirstCellSelection(selection) )
        {
            BeginBatchChanges(kChangeAttributesCommandID);
            // Format first selected cell
            SetFontSizeSelection( iSize, selection, FALSE );
            // Select all other cells and format them
            while( GetNextCellSelection(selection) )
            {
                SetFontSizeSelection( iSize, selection, FALSE );
            }
            RelayoutSelectedTable();
            EndBatchChanges();
        }
        return;
    } 

    FixupInsertPoint();
    CEditLeafElement *pRight = m_pCurrent->Divide( m_iCurrentOffset )->Leaf();
    if( pRight->IsA(P_TEXT)
            && pRight->Text()->GetLen() == 0 ) {
    //    && m_pCurrent == pRight
    // Bug 27891, this term in the conditional test should not be here, e.g.
    // in CEditBuffer::SetCharacterData() it is not there.
        pRight->Text()->SetFontSize(iSize);
        m_pCurrent = pRight;
        m_iCurrentOffset = 0;
    }
    else {
        CEditTextElement *pNew = m_pCurrent->CopyEmptyText();
        pNew->InsertBefore(pRight);
        pNew->SetFontSize( iSize );
        m_pCurrent = pNew;
        m_iCurrentOffset = 0;
    }
    Relayout( m_pCurrent, 0, 0 );
}

void CEditBuffer::SetFontSizeSelection( int iSize, CEditSelection& selection, XP_Bool bRelayout)
{
    VALIDATE_TREE(this);
    CEditLeafElement *pCurrent, *pEnd, *pBegin, *pNext;
    ED_BufferOffset iBeginPos, iEndPos;
    XP_Bool bFromStart;

    // Get data from supplied selection or get current selection
    GetSelection( selection, pBegin, iBeginPos, pEnd, iEndPos, bFromStart );

    //
    // Guarantee that the text blocks of the beginning and end of selection
    //  are atomic.
    //
    MakeSelectionEndPoints(selection, pBegin, pEnd );
    pCurrent = pBegin;

    while( pCurrent != pEnd ){
        pNext = pCurrent->NextLeafAll();
        if( pCurrent->IsA( P_TEXT ) &&
            ED_IS_NOT_SCRIPT(pCurrent) ){
            pCurrent->Text()->SetFontSize(iSize);
        }
        pCurrent = pNext;
    }
    if( bRelayout )
    {
        // force layout stop displaying the current selection.
#ifdef LAYERS
        LO_StartSelectionFromElement( m_pContext, 0, 0, NULL);
#else
        LO_StartSelectionFromElement( m_pContext, 0, 0);
#endif
        // probably needs to be the common ancestor.
        Relayout( pBegin, 0, pEnd, RELAYOUT_NOCARET );
        // Need to force selection.
        SelectRegion(pBegin, 0, pEnd, 0);
    }
    //TODO: Should we do this for cell "selection"?
    Reduce(pBegin->GetCommonAncestor(pEnd));
}

void CEditBuffer::SetFontColor( ED_Color iColor )
{
    // ToDo: Eliminate this function in favor of SetCharacterData
    VALIDATE_TREE(this);

    CEditSelection selection;
    GetSelection(selection);
    if ( selection.IsContainerEnd() )
        return;
    else if( selection.AnyLeavesSelected() )
    {
        BeginBatchChanges(kChangeAttributesCommandID);
        SetFontColorSelection( iColor, selection, TRUE );
        EndBatchChanges();
        return;
    }
    if( IsTableOrCellSelected() )
    {
        // Format each cell
        if( GetFirstCellSelection(selection) )
        {
            BeginBatchChanges(kChangeAttributesCommandID);
            // FALSE = don't relayout
            SetFontColorSelection( iColor, selection, FALSE );
            while( GetNextCellSelection(selection) )
            {
                SetFontColorSelection( iColor, selection, FALSE );
            }
            RelayoutSelectedTable();
            EndBatchChanges();
        }
        return;
    } 

    FixupInsertPoint();
    CEditLeafElement *pRight = m_pCurrent->Divide( m_iCurrentOffset )->Leaf();
    if( pRight->IsA(P_TEXT)
            && pRight->Leaf()->GetLen() == 0 ) {
    //    && m_pCurrent == pRight
    // Bug 27891, this term in the conditional test should not be here, e.g.
    // in CEditBuffer::SetCharacterData() it is not there.
        pRight->Text()->SetColor( iColor );
        m_pCurrent = pRight;
        m_iCurrentOffset = 0;
    }
    else {
        CEditTextElement *pNew = m_pCurrent->CopyEmptyText();
        pNew->InsertBefore(pRight);
        pNew->SetColor( iColor );
        m_pCurrent = pNew;
        m_iCurrentOffset = 0;
    }
    Relayout( m_pCurrent, 0, 0 );
}

void CEditBuffer::SetFontColorSelection( ED_Color iColor, CEditSelection& selection, XP_Bool bRelayout )
{
    VALIDATE_TREE(this);
    CEditLeafElement *pCurrent, *pEnd, *pBegin, *pNext;
    ED_BufferOffset iBeginPos, iEndPos;
    XP_Bool bFromStart;

    // Get data from supplied selection or get current selection
    GetSelection( selection, pBegin, iBeginPos, pEnd, iEndPos, bFromStart );

    //
    // Guarantee that the text blocks of the beginning and end of selection
    //  are atomic.
    //
    MakeSelectionEndPoints(selection, pBegin, pEnd );
    pCurrent = pBegin;

    while( pCurrent != pEnd ){
        pNext = pCurrent->NextLeafAll();
        if( pCurrent->IsA( P_TEXT ) ){
            pCurrent->Text()->SetColor( iColor );
        }
        pCurrent = pNext;
    }
    if( bRelayout )
    {
        // force layout stop displaying the current selection.
#ifdef LAYERS
        LO_StartSelectionFromElement( m_pContext, 0, 0, NULL);
#else
        LO_StartSelectionFromElement( m_pContext, 0, 0);
#endif
        // probably needs to be the common ancestor.
        Relayout( pBegin, 0, pEnd, RELAYOUT_NOCARET );
        // Need to force selection.
        SelectRegion(pBegin, 0, pEnd, 0);
    }
    Reduce(pBegin->GetCommonAncestor(pEnd));
}


void CEditBuffer::GetHREFData( EDT_HREFData *pData ){
    //TODO: Rewrite GetHREF to handle the target!
    ED_LinkId id = GetHREFLinkID();
    char* pURL = NULL;
    char* pExtra = NULL;
    if( id != ED_LINK_ID_NONE ){
        EDT_HREFData* pOriginal = linkManager.GetHREFData(id);
        if ( pOriginal ) {
            pURL = pOriginal->pURL;
            pExtra = pOriginal->pExtra;
        }
    }
    pData->pURL = pURL ? XP_STRDUP(pURL) : 0;
    pData->pExtra = pExtra ? XP_STRDUP(pExtra) : 0;
}

void CEditBuffer::SetHREFData( EDT_HREFData *pData ){
    SetHREF(pData->pURL, pData->pExtra);
}

void CEditBuffer::SetHREF( char *pHref, char *pExtra ){
    VALIDATE_TREE(this);
    ED_LinkId id;
    ED_LinkId oldId;
    CEditElement *pPrev, *pNext, *pStart, *pEnd;
    XP_Bool bSame;
    pStart = m_pCurrent;
    pEnd = 0;

    //
    // if we are setting link data and the current selection or word
    //  has extra data, we should preserve it.
    //
    if( pHref && *pHref!= '\0'){
        EDT_CharacterData* pCD = 0;
        if( pExtra == 0 ){
            pCD = GetCharacterData();
            if( pCD
                    && (pCD->mask & TF_HREF)
                    && (pCD->values & TF_HREF)
                    && pCD->pHREFData
                    && pCD->pHREFData->pExtra ){
                pExtra = pCD->pHREFData->pExtra;
            }
        }
        id  = linkManager.Add( pHref, pExtra );
        if( pCD ){
            EDT_FreeCharacterData( pCD );
        }
    }
    else {
        id = ED_LINK_ID_NONE;
    }

    CEditSelection selection;
    GetSelection(selection);
    if ( selection.IsContainerEnd() )
        return;
    else if( selection.AnyLeavesSelected() ){
        BeginBatchChanges(kChangeAttributesCommandID);
        SetHREFSelection( id );
        EndBatchChanges();
    }
    else {
        BeginBatchChanges(kChangeAttributesCommandID);
        //FixupInsertPoint();
        // check all text around us.
        oldId = m_pCurrent->GetHREF();
        m_pCurrent->SetHREF( id );
        pPrev = m_pCurrent->PreviousLeafInContainer();
        bSame = TRUE;
        while( pPrev && bSame ){
            bSame = pPrev->Leaf()->GetHREF() == oldId;
            if( bSame ){
                pPrev->Leaf()->SetHREF( id );
                pStart = pPrev;
            }
            pPrev = pPrev->PreviousLeafInContainer();
        }
        pNext = m_pCurrent->LeafInContainerAfter();
        bSame = TRUE;
        while( pNext && bSame ){
            bSame = pNext->Leaf()->GetHREF() == oldId;
            if( bSame ){
                pNext->Leaf()->SetHREF( id );
                pEnd = pNext;
            }
            pNext = pNext->LeafInContainerAfter();
        }
        Relayout( pStart, 0, pEnd );
        EndBatchChanges();
    }
    if( id != ED_LINK_ID_NONE ){
        linkManager.Free(id);
    }
}

// Use this only when setting one attribute at a time
// Always make Superscript and Subscript mutually exclusive - very tricky!
void CEditBuffer::FormatCharacter( ED_TextFormat tf ){
    VALIDATE_TREE(this);
    CEditSelection selection;
    GetSelection(selection);
    if ( selection.IsContainerEnd() )
        return;

    if( selection.AnyLeavesSelected() ){
        BeginBatchChanges(kChangeAttributesCommandID);
        FormatCharacterSelection(tf, selection, TRUE);
        EndBatchChanges();
        return;
    }
    if( IsTableOrCellSelected() )
    {
        // Default is to clear existing attribute
        // Use this if ANY cell has ANY bold elements at all
        intn iSetFormat = ED_CLEAR_FORMAT;

        // Get each cell contents as a selection
        //   and determine if we will set or clear the attribute
        if( GetFirstCellSelection(selection) )
        {
            // FALSE means we don't need to save/restore insert point
            XP_Bool bSetFormat = GetSetStateForCharacterSelection(tf, selection);
            if( bSetFormat )
            {
                XP_Bool bFoundCell; 
                while( (bFoundCell = GetNextCellSelection(selection)) == TRUE )
                {
                    // As soon as we hit a cell that wants us to clear format,
                    //  we are done -- we will clear format for ALL cells
                    if( !GetSetStateForCharacterSelection(tf, selection) )
                        break;
                }
                // If we ran through all cells without finding one
                //   to clear, then we should SET the format for all cells
                if( !bFoundCell )                
                    iSetFormat = ED_SET_FORMAT;
            }

            // Go through cells again and set the character format
            GetFirstCellSelection(selection);
            BeginBatchChanges(kChangeAttributesCommandID);
            // Format first cell
            FormatCharacterSelection(tf, selection, FALSE, iSetFormat );
            // Format all other cells
            while( GetNextCellSelection(selection) )
            {
                FormatCharacterSelection(tf, selection, FALSE, iSetFormat);
            }
            RelayoutSelectedTable();
            EndBatchChanges();
        }
        return;
    } 

    FixupInsertPoint();
    CEditLeafElement *pRight = m_pCurrent->Divide( m_iCurrentOffset )->Leaf();
    if( pRight->IsA(P_TEXT) && pRight->Leaf()->GetLen() == 0 ){
        if( tf == TF_NONE ){
            pRight->Text()->ClearFormatting();
        }
#ifdef USE_SCRIPT
        else if( tf == TF_SERVER || tf == TF_SCRIPT || tf == TF_STYLE ){
            pRight->Text()->ClearFormatting();
            pRight->Text()->m_tf = tf;
        }
#endif
        // If we are in either Java Script type, IGNORE change in format
        else if( ED_IS_NOT_SCRIPT(pRight) ){
            // Make superscript and subscript mutually-exclusive
            // if already either one type, and we are setting the opposite,
            //  clear this bit first
            if( tf == TF_SUPER && (pRight->Text()->m_tf & TF_SUB) ){
                pRight->Text()->m_tf &= ~TF_SUB;
            } else if( tf == TF_SUB && (pRight->Text()->m_tf & TF_SUPER) ){
                pRight->Text()->m_tf &= ~TF_SUPER;
            }
            // Fixed font attribute overrides font face
            if( tf == TF_FIXED && (pRight->Text()->m_tf & TF_FONT_FACE) ){
                pRight->Text()->m_tf &= ~TF_FONT_FACE;
            }
            pRight->Text()->m_tf ^= tf;
        }
        m_pCurrent = pRight;
        m_iCurrentOffset = 0;
    }
    else {
        // We are making new text element, so we don't need
        //   to worry about existing Java Script or Super/Subscript stuff
        CEditTextElement *pNew = m_pCurrent->CopyEmptyText();
        pNew->InsertBefore(pRight);
        if( tf == TF_NONE ){
            pNew->ClearFormatting();
        }
        else {
            if( tf == TF_SUPER && (pNew->Text()->m_tf & TF_SUB) ){
                pNew->Text()->m_tf &= ~TF_SUB;
            } else if( tf == TF_SUB && (pNew->Text()->m_tf & TF_SUPER) ){
                pNew->Text()->m_tf &= ~TF_SUPER;
            }
            pNew->m_tf ^= tf;
        }
        m_pCurrent = pNew;
        m_iCurrentOffset = 0;
    }
    Relayout( m_pCurrent, 0, 0 );
}

XP_Bool CEditBuffer::GetSetStateForCharacterSelection( ED_TextFormat tf, CEditSelection& selection )
{
    if( selection.IsEmpty() )
        return FALSE;
    //
    // Guarantee that the text blocks of the beginning and end of selection
    //  are atomic.
    //
//    MakeSelectionEndPoints(selection, pBegin, pEnd );
    // To be more efficient, do just what is necessary, since we only need the first element
    CEditLeafElement *pBegin = selection.m_start.m_pElement->Divide( selection.m_start.m_iPos )->Leaf();

    // We set the attribute if first element doesn't already have it,
    //   else we will clear existing attribute
    return !(pBegin->Text()->m_tf & tf);
}

void CEditBuffer::FormatCharacterSelection( ED_TextFormat tf, CEditSelection& selection,  XP_Bool bRelayout, intn iSetState )
{
    VALIDATE_TREE(this);
    CEditLeafElement *pCurrent, *pEnd, *pBegin, *pNext;
    XP_Bool bSet = (iSetState == ED_SET_FORMAT);
    // Passed in value determines if we figure out whether to set below,
    //   or just use state passed in.
    XP_Bool bHaveSet = (iSetState != ED_GET_FORMAT);

    // Get current selection if supplied with empty selection
    if( selection.IsEmpty() )
    {
        GetSelection(selection);
        bRelayout = TRUE;
    }

    //
    // Guarantee that the text blocks of the beginning and end of selection
    //  are atomic.
    //
    MakeSelectionEndPoints(selection, pBegin, pEnd );

    pCurrent = pBegin;

    while( pCurrent != pEnd && pCurrent )
    {
        pNext = pCurrent->NextLeafAll();
        XP_ASSERT(pNext);

        if( pCurrent->IsA( P_TEXT ) )
        {
            // figure out whether we are setting or clearing...
            if( !bHaveSet )
            {
                bSet = !(pCurrent->Text()->m_tf & tf);
                bHaveSet = TRUE;
            }

            if( tf == TF_NONE )
            {
                pCurrent->Text()->ClearFormatting();
            }
#ifdef USE_SCRIPT
            else if (tf == TF_SERVER || tf == TF_SCRIPT || tf == TF_STYLE)
            {
                pCurrent->Text()->ClearFormatting();
                pCurrent->Text()->m_tf = tf;
            }
#endif
            else 
            {
                if( bSet && pCurrent->IsA( P_TEXT ) )
                {
                    // If we are in either Java Script type, IGNORE change in format
                    if( ED_IS_NOT_SCRIPT(pCurrent) )
                    {
                        // Make superscript and subscript mutually-exclusive
                        if( tf == TF_SUPER && (pCurrent->Text()->m_tf & TF_SUB) )
                        {
                            pCurrent->Text()->m_tf &= ~TF_SUB;
                        } else if( tf == TF_SUB && (pCurrent->Text()->m_tf & TF_SUPER) )
                        {
                            pCurrent->Text()->m_tf &= ~TF_SUPER;
                        }
                        pCurrent->Text()->m_tf |= tf;
                    }
                    // Fixed font attribute overrides font face
                    if( tf == TF_FIXED && (pCurrent->Text()->m_tf & TF_FONT_FACE) )
                    {
                        pCurrent->Text()->m_tf &= ~TF_FONT_FACE;
                    }
                }
                else 
                {
                    pCurrent->Text()->m_tf &= ~tf;
                }
            }
        }
        else if( pCurrent->IsImage() && tf == TF_NONE )
        {
            // Clear the link for the image within the selection
            pCurrent->Image()->SetHREF( ED_LINK_ID_NONE );
        }
        pCurrent = pNext;
    }
	CEditSelection tmp(pBegin, 0, pEnd, 0);
    
    // Only do this (which basically does relayout)
    //  if working with current selection, not cell selection
    if( bRelayout )
        RepairAndSet(tmp);
}

void CEditBuffer::SetCharacterData( EDT_CharacterData *pData )
{
    VALIDATE_TREE(this);
    CEditSelection selection;
    GetSelection(selection);
    if ( selection.IsContainerEnd() )
        return;
    else if( selection.AnyLeavesSelected() ){
        BeginBatchChanges(kChangeAttributesCommandID);
        SetCharacterDataSelection( pData, selection, TRUE );
        EndBatchChanges();
        return;
    }
    
    if( IsTableOrCellSelected() )
    {
        // Select each cell and format the contents
        if( GetFirstCellSelection(selection) )
        {
            BeginBatchChanges(kChangeAttributesCommandID);
            // Format first selected cell, don't relayout yet
            SetCharacterDataSelection(pData, selection, FALSE);

            // Select all other cells and format them
            while( GetNextCellSelection(selection) )
            {
                SetCharacterDataSelection(pData, selection, FALSE);
            }
            RelayoutSelectedTable();
            EndBatchChanges();
        }
        return;
    } 

    FixupInsertPoint();
    CEditLeafElement *pRight = m_pCurrent->Divide( m_iCurrentOffset )->Leaf();
    if( pRight->IsA(P_TEXT) && pRight->Leaf()->GetLen() == 0 ){
        pRight->Text()->SetData( pData );
        m_pCurrent = pRight;
        m_iCurrentOffset = 0;
    }
    else {
        CEditTextElement *pNew = m_pCurrent->CopyEmptyText();
        pNew->InsertBefore(pRight);
        pNew->SetData( pData );
        m_pCurrent = pNew;
        m_iCurrentOffset = 0;
    }
    Relayout( m_pCurrent, 0, 0 );
}

void CEditBuffer::SetCharacterDataSelection( EDT_CharacterData *pData, CEditSelection& selection, XP_Bool bRelayout )
{
    VALIDATE_TREE(this);
    CEditLeafElement *pCurrent, *pEnd, *pBegin, *pNext;

    //
    // Guarantee that the text blocks of the beginning and end of selection
    //  are atomic.
    //
    MakeSelectionEndPoints( selection, pBegin, pEnd );
    pCurrent = pBegin;

    while( pCurrent != pEnd ){
        pNext = pCurrent->NextLeafAll();
        if( pCurrent->IsA( P_TEXT ) ){
            pCurrent->Text()->SetData( pData );
        }
        // Set just the HREF for an image
        else if( pCurrent->IsImage() &&
                 (pData->mask & TF_HREF) ){
            pCurrent->Image()->SetHREF( pData->linkId );
        }
        pCurrent = pNext;
    }

    if( bRelayout && !m_bNoRelayout )
    {
        // force layout stop displaying the current selection.
    #ifdef LAYERS
        LO_StartSelectionFromElement( m_pContext, 0, 0, NULL );
    #else
        LO_StartSelectionFromElement( m_pContext, 0, 0);
    #endif
        // probably needs to be the common ancestor.
        Relayout( pBegin, 0, pEnd, RELAYOUT_NOCARET );
        // Need to force selection.
        SelectRegion(pBegin, 0, pEnd, 0);
    }
    Reduce(pBegin->GetCommonAncestor(pEnd));
}

void CEditBuffer::SetCharacterDataAtOffset( EDT_CharacterData *pData,
                   ED_BufferOffset iBufOffset, int32 iLen ){

#if 0
    XP_ASSERT( m_bNoRelayout );  //put this back in when edt_paste does not relayout everything
#endif
    int32 i = iBufOffset+iLen;
    CPersistentEditSelection perSel;
    perSel = CPersistentEditSelection( CPersistentEditInsertPoint( iBufOffset ),
                CPersistentEditInsertPoint( i ) );

    CEditSelection sel = PersistentToEphemeral( perSel );

    SetCharacterDataSelection( pData, sel, TRUE );
}

void CEditBuffer::SetRefresh( XP_Bool bRefreshOn ){
    if( bRefreshOn ){
        m_bNoRelayout = FALSE;
        RefreshLayout();
    }
    else {
        m_bNoRelayout = TRUE;
    }
}


// Separated from GetCharacterData
// If pData is supplied, then multiple-selections may be combined
EDT_CharacterData* CEditBuffer::GetCharacterDataSelection(EDT_CharacterData *pData, CEditSelection& selection)
{
    // Get current selection if not supplied
    if( selection.IsEmpty() )
        GetSelection( selection );

    CEditElement *pElement = selection.m_start.m_pElement;

    while( pElement && pElement != selection.m_end.m_pElement ){
        if( pElement->IsA( P_TEXT ) ){
            pElement->Text()->MaskData( pData );
        }
        else if( pElement->IsImage() ){
            // We report the HREF status if we find an image
            pElement->Image()->MaskData( pData );
        }
        pElement = pElement->NextLeaf();
    }

    // if the selection ends in the middle of a text block.
    if( pElement && selection.m_end.m_iPos != 0
            && selection.m_end.m_pElement->IsA(P_TEXT) ){
        pElement->Text()->MaskData( pData );
    }

    if( pData == 0 ){
        return EDT_NewCharacterData();
    }
    return pData;
}

EDT_CharacterData* CEditBuffer::GetCharacterData()
{
	CEditElement *pElement = m_pCurrent;
    CEditSelection selection;

	// The following assert fails once at program startup.
	// XP_ASSERT(pElement || IsSelected());

    //
    // While selecting, we may not have a valid region
    //
    if( IsSelecting() ){
        return EDT_NewCharacterData();
    }

    if( IsSelected() ){
        return GetCharacterDataSelection(0, selection);
    }
    if( IsTableOrCellSelected() )
    {
        EDT_CharacterData *pData = 0;
        if( GetFirstCellSelection(selection) )
        {
            // Start with data from first cell
            pData = GetCharacterDataSelection(pData, selection);

            // Pass in pData to combine with other cell's data
            while( GetNextCellSelection(selection) )
                GetCharacterDataSelection(pData, selection);
        }
        return pData;
    } 

    if( pElement && pElement->IsA(P_TEXT ))
    {
        return pElement->Text()->GetData();;
    }

	// Try to find the previous text element and use its characterData
	if (pElement) {
		CEditTextElement *tElement = pElement->PreviousTextInContainer();
		if ( tElement){
			return tElement->GetData();
		}
	}

	// Create a dummy text element from scratch to guarantee
	// that we get the same EDT_CharacterData as when creating a new
	// CEditTextElement during typing.
	CEditTextElement dummy( (CEditElement *)0,0);
	return dummy.GetData();

	// return EDT_NewCharacterData();
}

char *CEditBuffer::GetTabDelimitedTextFromSelectedCells()
{
    char *pText = NULL;
    if( IsTableOrCellSelected() )
    {
        // Get each cell contents to examine - as if it were selected
        CEditTableCellElement *pCell = GetFirstSelectedCell();
        if( pCell )
        {
            // Get text from cell: TRUE = join paragraphs (no embeded CR/LF)
            pText = pCell->GetText(TRUE);
            if( pText )
            {
                intn iPrevCounter = 0;
                intn iRowCounter = 0;
                while( (pCell = GetNextSelectedCell(&iRowCounter)) != NULL )
                {
                    if( iRowCounter != iPrevCounter )
                    {
                        // We are in next row - so add row delimiter to previous text
                        pText = edt_AppendEndOfLine( pText);
                        iPrevCounter = iRowCounter;
                    } else {
                        // Next cell is in same row, so add TAB delimeter
                        pText = PR_sprintf_append( pText, "\t" );
                    }

                    // Append next cell's text
                    char *pCellText = pCell->GetText();
                    if( pCellText && *pCellText )
                        pText = PR_sprintf_append( pText, pCellText );
                }
                // Add last row delimiter at the end
                pText = edt_AppendEndOfLine( pText);
            }
        }
    } 
    return pText;
}

// Convert Selected text into a table (put each paragraph in separate cell)
// Number of rows is automatic - creates as many as needed
void CEditBuffer::ConvertTextToTable(intn iColumns)
{
    CEditLeafElement *pBegin, *pEnd, *pCurrent;
    ElementOffset iBeginPos, iEndPos;
    XP_Bool bFromStart;

    // Get data from supplied selection or get current selection if empty
    CEditSelection s;
    GetSelection(s, pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
    pCurrent = pBegin;
    XP_Bool bDone = FALSE;
    CEditContainerElement* pLastContainer = 0;
    CEditContainerElement* pContainer = s.m_start.m_pElement->FindContainer();
    // Insert a new table here first. Ask user how many columns.

    do {
        if( pContainer != pLastContainer ){
            // MOVE THIS CONTAINER (PARA) TO A NEW TABLE.
            pLastContainer = pContainer;
        }
        bDone = (pEnd == pCurrent );    // For most cases
        pCurrent = pCurrent->NextLeafAll();
        bDone = bDone || (iEndPos == 0 && pEnd == pCurrent ); // Pesky edge conditions!
    } while( pCurrent && !bDone );

    
}

// Convert the table into text - unravel existing paragraphs in cells
void CEditBuffer::ConvertTableToText()
{
    CEditTableElement *pTable;
    
    // Get the table to convert
    if( m_pSelectedEdTable )
    {
        pTable = m_pSelectedEdTable;
    }
    else
    {
        CEditInsertPoint ip;
        GetTableInsertPoint(ip);
        if( ip.m_pElement )
            pTable = ip.m_pElement->GetParentTable();
    }
    if( pTable )
    {
        // Be sure we don't have anything in this table selected
        ClearTableAndCellSelection();

        // Get the previous container to append to
        CEditContainerElement* pPrevContainer = pTable->PreviousContainer();
        CEditElement* pNextContainer = NULL;
        CEditElement* pLastContainer = NULL;
        if( !pPrevContainer )
            return; //TODO: CAN THIS HAPPEN??? HANLDE IT IF YES!

        CEditElement* pAppendContainer = pPrevContainer;
        CEditElement* pFirstContainer = NULL;
        CEditElement *pContainer = NULL;

        // Move all containers in each cell to previous container
        CEditTableCellElement *pCell = pTable->GetFirstCell();
        if( pCell )
        {
            BeginBatchChanges(kGroupOfChangesCommandID);
            while( pCell )
            {
                // Get first container of the cell
                pContainer = pCell->GetChild();
                XP_ASSERT(pContainer->IsContainer());

                // Save very first container while we're here
                if( pFirstContainer == NULL )
                    pFirstContainer = pContainer;

                // Move all containers
                while( pContainer )
                {
                    CEditElement* pNextContainer = pContainer->GetNextSibling();
                    pContainer->Unlink();
                    pContainer->InsertAfter(pAppendContainer);
                    pAppendContainer = pContainer;

                    // Save the last container moved
                    if( pContainer )
                        pLastContainer = pContainer;

                    pContainer = pNextContainer;
                }
                pCell = pTable->GetNextCellInTable();
            }
            
            // Connect the last moved container to
            //   the next one after table we are deleting
            if( pLastContainer )
            {
                pNextContainer = pLastContainer->NextContainer();
            }
            delete pTable;

            if( pNextContainer )
                pLastContainer->SetNextSibling(pNextContainer);


            if( pNextContainer && pLastContainer )
                pLastContainer->SetNextSibling(pNextContainer);

            // Move insert point to first moved element
            if( pFirstContainer )
            {
                CEditInsertPoint ip(pFirstContainer->GetFirstMostChild(), 0);
                SetInsertPoint(ip);
            }        
            if( pNextContainer )
            {
                Relayout(pPrevContainer->GetFirstMostChild(), 0, pNextContainer->GetFirstMostChild(), 0);
            } else {
                // Deep doodoo if we didn't get all containers around the table!
                XP_ASSERT(FALSE);
            }

#ifdef DEBUG
            m_pRoot->ValidateTree();
#endif

            EndBatchChanges();
        }
    }
}

// Save the character and paragraph style of selection or at caret
void CEditBuffer::CopyStyle()
{
    if( m_pCopyStyleCharacterData )
        EDT_FreeCharacterData(m_pCopyStyleCharacterData);
    
    m_pCopyStyleCharacterData = GetCharacterData();
}

// Apply the style to selection or at caret. Use bApplyStyle = FALSE to cancel
void CEditBuffer::PasteStyle(XP_Bool bApplyStyle)
{
    if( m_pCopyStyleCharacterData )
    {
        if( bApplyStyle )
        {
            SetCharacterData(m_pCopyStyleCharacterData);
        }
        EDT_FreeCharacterData(m_pCopyStyleCharacterData);
        m_pCopyStyleCharacterData = NULL;
    }
}

EDT_PageData* CEditBuffer::GetPageData(){
    EDT_PageData* pData = EDT_NewPageData();

    pData->pColorBackground = edt_MakeLoColor( m_colorBackground );
    pData->bBackgroundNoSave = m_bBackgroundNoSave;
    pData->pColorText = edt_MakeLoColor( m_colorText );
    pData->pColorLink = edt_MakeLoColor( m_colorLink );
    pData->pColorFollowedLink = edt_MakeLoColor( m_colorFollowedLink );
    pData->pColorActiveLink = edt_MakeLoColor( m_colorActiveLink );

    pData->pTitle = XP_STRDUP( ( m_pTitle ? m_pTitle : "" ) );
    pData->pBackgroundImage = ( m_pBackgroundImage
                    ? XP_STRDUP( m_pBackgroundImage )
                    : 0 );
    pData->pFontDefURL = ( m_pFontDefURL
                    ? XP_STRDUP( m_pFontDefURL )
                    : 0 );
    pData->bFontDefNoSave = m_bFontDefNoSave;
    return pData;
}

void CEditBuffer::FreePageData( EDT_PageData* pData ){
    if ( pData->pColorBackground ) XP_FREE( pData->pColorBackground );
    if ( pData->pColorText ) XP_FREE( pData->pColorText );
    if ( pData->pColorLink ) XP_FREE( pData->pColorLink );
    if ( pData->pColorFollowedLink ) XP_FREE( pData->pColorFollowedLink );
    if ( pData->pColorActiveLink ) XP_FREE( pData->pColorActiveLink );
    if ( pData->pTitle ) XP_FREE( pData->pTitle );
    if ( pData->pBackgroundImage ) XP_FREE( pData->pBackgroundImage );
    if ( pData->pFontDefURL ) XP_FREE( pData->pFontDefURL );

    XP_FREE( pData );
}

void CEditBuffer::SetPageData( EDT_PageData* pData ){
    m_colorBackground = EDT_LO_COLOR( pData->pColorBackground );
    m_colorLink = EDT_LO_COLOR( pData->pColorLink );
    m_colorText = EDT_LO_COLOR( pData->pColorText );
    m_colorFollowedLink = EDT_LO_COLOR( pData->pColorFollowedLink );
    m_colorActiveLink = EDT_LO_COLOR( pData->pColorActiveLink );

    // While we would like to change doc title only if it
    //   is really different, there is the case when both existing
    //   m_pTitle and the new pData->pTitle are NULL and we 
    //   need to always change the title in that case since
    //   we may be changing an existing page with no title
    //   into an "Untitled" page. This happens when we open a template file
    //   or we import a text file
    XP_Bool bNoChange = FALSE;

    if( m_pTitle && pData->pTitle && XP_STRCMP(m_pTitle, pData->pTitle) )
    {
        bNoChange = TRUE;
    }

    if( !bNoChange )
    {
        if( m_pTitle ) XP_FREE( m_pTitle );
        m_pTitle = (pData->pTitle ? XP_STRDUP( pData->pTitle ) : NULL);
        FE_SetDocTitle( m_pContext, m_pTitle );
    }
    
    //    If there was an image, and now there isn't remove it.   
    if(m_pBackgroundImage != NULL)  {
        XP_FREE(m_pBackgroundImage);
        m_pBackgroundImage = NULL;
    }

    if( pData->pBackgroundImage ){  
        m_pBackgroundImage = XP_STRDUP(pData->pBackgroundImage);  
    }  
    m_bBackgroundNoSave = pData->bBackgroundNoSave;

    // Font reference
    if(m_pFontDefURL != NULL)  {
        XP_FREE(m_pFontDefURL);
        m_pFontDefURL = NULL;
    }
    if( pData->pFontDefURL ){  
        m_pFontDefURL = XP_STRDUP(pData->pFontDefURL);  
    }  
    m_bFontDefNoSave = pData->bFontDefNoSave;

    // Note: all LO_SetDocumentColor calls
    //       and LO_SetBackgroundImage are done in RefreshLayout

    // Clear bit used to set color only once when multiple colors
    //   are in mail message. Fix for bug 64850
    // If we don't, color doesn't change when user wants to!
    lo_TopState *top_state = lo_FetchTopState(XP_DOCID(m_pContext));
    if( top_state ){
        top_state->body_attr &= ~BODY_ATTR_TEXT;
    }
    RefreshLayout();
}

void CEditBuffer::SetImageAsBackground()
{
    EDT_PageData * pPageData = GetPageData();
    if (pPageData == NULL) {
        return;
    }
    EDT_ImageData* pImageData = NULL;
    
    if( IsSelected() && GetCurrentElementType() == ED_ELEMENT_IMAGE ){
        pImageData = GetImageData();
        if( pImageData ){
            XP_FREEIF(pPageData->pBackgroundImage);
            pPageData->pBackgroundImage = XP_STRDUP(pImageData->pSrc);

            BeginBatchChanges(kGroupOfChangesCommandID);
            // Delete image from page
//            DeleteSelection();
            SetPageData(pPageData);

            EDT_FreeImageData(pImageData);
            EndBatchChanges();
        }
    }
    EDT_FreePageData(pPageData);
}

// MetaData

intn CEditBuffer::MetaDataCount( ){
    intn i = 0;
    intn iRetVal = 0;
    while( i < m_metaData.Size() ){
        if( m_metaData[i] != 0 ){
            iRetVal++;
        }
        i++;
    }
    return iRetVal;
}

intn CEditBuffer::FindMetaData( EDT_MetaData *pMetaData ){
    return FindMetaData(pMetaData->bHttpEquiv, pMetaData->pName);
}

intn CEditBuffer::FindMetaData(XP_Bool bHttpEquiv, char* pName){
    intn i = 0;
    intn iRetVal = 0;
    while( i < m_metaData.Size() ){
        if( m_metaData[i] != 0 ){
            if( (!!bHttpEquiv == !!m_metaData[i]->bHttpEquiv)
                    && XP_STRCMP( m_metaData[i]->pName, pName) == 0 ){
                return iRetVal;
            }
            iRetVal++;
        }
        i++;
    }
    return -1;
}

intn CEditBuffer::FindMetaDataIndex( EDT_MetaData *pMetaData ){
    intn i = 0;
    while( i < m_metaData.Size() ){
        if( m_metaData[i] != 0 ){
            if( (!!pMetaData->bHttpEquiv == !!m_metaData[i]->bHttpEquiv)
                    && XP_STRCMP( m_metaData[i]->pName, pMetaData->pName) == 0 ){
                return i;
            }
        }
        i++;
    }
    return -1;
}

EDT_MetaData* CEditBuffer::MakeMetaData( XP_Bool bHttpEquiv, char *pName, char*pContent ){
    EDT_MetaData *pData = XP_NEW( EDT_MetaData );
    XP_BZERO( pData, sizeof( EDT_MetaData ) );

    pData->bHttpEquiv = bHttpEquiv;
    pData->pName = (pName
                            ? XP_STRDUP(pName)
                            : 0);
    pData->pContent = (pContent
                            ? XP_STRDUP(pContent)
                            : 0);
    return pData;
}

EDT_MetaData* CEditBuffer::GetMetaData( int n ){
    intn i = 0;
//    intn iRetVal = 0;
    n++;
    while( n ){
        if ( i >= m_metaData.Size() ) {
            return NULL;
        }
        if( m_metaData[i] != 0 ){
            n--;
        }
        i++;
    }
    i--;
    if ( i < 0 ) {
        return NULL;
    }
    return MakeMetaData( m_metaData[i]->bHttpEquiv,
                         m_metaData[i]->pName,
                         m_metaData[i]->pContent );
}

void CEditBuffer::SetMetaData( EDT_MetaData *pData ){
    intn i = FindMetaDataIndex( pData );
    if ( ! (pData && pData->pContent && *(pData->pContent)) ) {
        // We've been asked to erase an entry
        if ( i == -1 ) {
            // We've been asked to erase an entry that we don't have. So just return.
            return;
        }
        else {
            // Erase the entry.
            FreeMetaData( m_metaData[i] );
            m_metaData[i] = NULL;
        }
    }
    else {
        EDT_MetaData* pNew = MakeMetaData( pData->bHttpEquiv, pData->pName, pData->pContent );
        if( i == -1 ){
            // Add an entry
            m_metaData.Add( pNew );
        }
        else {
            // Replace an entry
            FreeMetaData( m_metaData[i] );
            m_metaData[i] = pNew;
        }
    }
}

void CEditBuffer::DeleteMetaData( EDT_MetaData *pData ){
    intn i = FindMetaDataIndex( pData );
    if( i != -1 ){
        FreeMetaData( m_metaData[i] );
        m_metaData[i] = 0;
    }
}

void CEditBuffer::FreeMetaData( EDT_MetaData *pData ){
    if ( pData ) {
        if( pData->pName ) XP_FREE( pData->pName );
        if( pData->pContent ) XP_FREE( pData->pContent );
        XP_FREE( pData );
    }
}

void CEditBuffer::ParseMetaTag( PA_Tag *pTag ){
    XP_Bool bHttpEquiv = TRUE;
    char *pName;
    char *pContent;
    INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_pContext);
    int16 win_csid = INTL_GetCSIWinCSID(c);

    pContent = edt_FetchParamString( pTag, PARAM_CONTENT, win_csid );
    pName = edt_FetchParamString( pTag, PARAM_HTTP_EQUIV, win_csid );

    // if we didn't get http-equiv, try for name=
    if( pName == 0 ){
        bHttpEquiv = FALSE;
        pName = edt_FetchParamString( pTag, PARAM_NAME, win_csid );
    }

    // if we got one or the other, add it to the list of meta tags.
    if( pName ){
        EDT_MetaData *pData = MakeMetaData( bHttpEquiv, pName, pContent );
        SetMetaData( pData );
        FreeMetaData( pData );
    }

    if( pName ) XP_FREE( pName );
    if( pContent ) XP_FREE( pContent );
}


// Image

EDT_ImageData* CEditBuffer::GetImageData(){
    CEditLeafElement *pInsertPoint;
    ElementOffset iOffset;
    XP_Bool bSingleItem;

    bSingleItem = GetPropertyPoint( &pInsertPoint, &iOffset );
    XP_ASSERT( bSingleItem );

    XP_ASSERT( pInsertPoint->IsA(P_IMAGE) );

    return pInsertPoint->Image()->GetImageData( );
}

int32 CEditBuffer::GetDefaultBorderWidth(){
    CEditLeafElement *pInsertPoint;
    ElementOffset iOffset;
    XP_Bool bSingleItem;

    // Just check for consistency, should both have a link or both not.
    bSingleItem = GetPropertyPoint( &pInsertPoint, &iOffset );
    if (bSingleItem && pInsertPoint->IsA(P_IMAGE)) {
        ED_LinkId fromImage = pInsertPoint->Image()->GetHREF();
        char *fromEdtBuf = GetHREF();
        XP_ASSERT((fromImage && fromEdtBuf) || (!fromImage && !fromEdtBuf));
    }

    // Duplicating code in CEditImageElement::GetDefaultBorder, but at
    // least its all in the back end now.
    if (GetHREF()) {
        return 2;
    }
    else {
        return 0;
    }
}


#define XP_STRCMP_NULL( a, b ) XP_STRCMP( (a)?(a):"", (b)?(b):"" )

void CEditBuffer::SetImageData( EDT_ImageData* pData, XP_Bool bKeepImagesWithDoc ){
    VALIDATE_TREE(this);
    ClearSelection( TRUE, TRUE );

    XP_ASSERT( m_pCurrent->IsA(P_IMAGE) );

    // This function is only called when changing attributes of an image, not
    //   when inserting an image.
    //
    // There are 4 cases...
    //
    // 1) They change the src of the image, but not the size of the image
    //      (assume the dimensions of the new image).
    // 2) They change the src and the size of the image
    // 3) They change just the size of the image.
    // 4) They reset the size of the image.
    //

    CEditImageElement *pImage = m_pCurrent->Image();
    EDT_ImageData *pOldData = m_pCurrent->Image()->GetImageData();

    // Case 1, 2, or 4
    if( (XP_STRCMP( pOldData->pSrc, pData->pSrc ) != 0 )
            || XP_STRCMP_NULL( pOldData->pLowSrc, pData->pLowSrc )
            || (pData->iHeight == 0 || pData->iWidth== 0) ){


        // if they touched height or width, assume they know what they
        //  are doing.
        //
        if( pData->iHeight == pOldData->iHeight
                && pData->iWidth == pOldData->iWidth ){

            // LoadImage will get the image size from the image.
            pData->iHeight = 0;
            pData->iWidth = 0;
        }

        LoadImage( pData, bKeepImagesWithDoc, TRUE );
        edt_FreeImageData( pOldData );
    }
    else {
        m_pCurrent->Image()->SetImageData( pData );
        Relayout( m_pCurrent->FindContainer(), 0, m_pCurrent, RELAYOUT_NOCARET );
        SelectCurrentElement();
    }
    // Also set the HREF if it exists
    if( pData->pHREFData ){
        if( !pData->pHREFData->pURL || XP_STRLEN(pData->pHREFData->pURL) == 0 ){
            pImage->SetHREF( ED_LINK_ID_NONE );
        } else {
            // Add link data to the image
            pImage->SetHREF( linkManager.Add( pData->pHREFData->pURL,
                                              pData->pHREFData->pExtra) );
        }
    }
}

void CEditBuffer::LoadImage( EDT_ImageData* pData, XP_Bool /*bKeepImagesWithDoc*/,
            XP_Bool bReplaceImage ){

    XP_Bool bMakeAbsolute = TRUE;

    if( m_pLoadingImage != 0 ){
        XP_ASSERT(0);       // can only be loading one image at a time.
        return;
    }

    if( bMakeAbsolute ){
        m_pLoadingImage = new CEditImageLoader( this, pData, bReplaceImage );
        m_pLoadingImage->LoadImage();
    }

}

//
// need to do a better job of splitting and inserting in the right place.
//
void CEditBuffer::InsertImage( EDT_ImageData* pData ){
    if( IsSelected() ){
        ClearSelection();
    }
    CEditImageElement *pImage = new CEditImageElement(0);
    pImage->SetImageData( pData );
    // Add link data to the image
    if( pData->pHREFData && pData->pHREFData->pURL &&
        XP_STRLEN(pData->pHREFData->pURL) > 0){
        pImage->SetHREF( linkManager.Add( pData->pHREFData->pURL,
                                          pData->pHREFData->pExtra) );
    }
    InsertLeaf( pImage );
}


void CEditBuffer::InsertLeaf( CEditLeafElement *pLeaf ){
    VALIDATE_TREE(this);
    FixupInsertPoint();
    if( m_iCurrentOffset == 0 ){
        pLeaf->InsertBefore( m_pCurrent );
    }
    else {
        m_pCurrent->Divide(m_iCurrentOffset);
        pLeaf->InsertAfter( m_pCurrent );
    }
    m_pCurrent = pLeaf;
    m_iCurrentOffset = 1;

    if ( ! m_bNoRelayout ){
        Reduce(pLeaf->FindContainer());
    }

    // we could be doing a better job here.  We probably repaint too much...
    CEditElement *pContainer = m_pCurrent->FindContainer();
    Relayout( pContainer, 0, pContainer->GetLastMostChild() );
}

void CEditBuffer::InsertNonLeaf( CEditElement *pElement){
    VALIDATE_TREE(this);
    FixupInsertPoint();
    XP_ASSERT( ! pElement->IsLeaf());
//    CEditElement* pStart = m_pCurrent;
//    int iStartOffset = m_iCurrentOffset;
    CEditInsertPoint start(m_pCurrent, m_iCurrentOffset);
	CEditLeafElement* pRight = NULL;
    InternalReturnKey(FALSE);

    pRight = m_pCurrent;
    CEditLeafElement* pLeft = pRight->PreviousLeaf(); // Will be NULL at start of document
    pElement->InsertBefore(pRight->FindContainer());
    pElement->FinishedLoad(this);

#ifdef DEBUG
    m_pRoot->ValidateTree();
#endif
	// Put cursor at the end of what has been inserted

    if ( pRight->IsEndOfDocument() ) {
        m_pCurrent = pRight->PreviousLeaf();
        m_iCurrentOffset = m_pCurrent->GetLen();
    }
    else {
        m_pCurrent = pRight;
        m_iCurrentOffset = 0;
   }

    Relayout( pElement, 0, pRight );
}

// Table stuff

void CEditBuffer::GetTableInsertPoint(CEditInsertPoint& ip){
    GetInsertPoint(ip);
    CEditSelection s;
    GetSelection(s);
    if ( ! s.IsInsertPoint() && s.m_end == ip && ip.IsStartOfContainer()){
        ip = ip.PreviousPosition();
    };
}

XP_Bool CEditBuffer::SetTableInsertPoint(CEditTableCellElement *pCell, XP_Bool bStartOfCell )
{
    // Move the insert point to new cell,
    if( pCell )
    {
        CEditElement *pChild;
        if( bStartOfCell )
            pChild = pCell->GetFirstMostChild();
        else
            pChild = pCell->GetLastMostChild();
        
        if( pChild && pChild->IsLeaf() )
        {
            CEditInsertPoint ip(pChild, bStartOfCell ? 0 : pChild->Leaf()->GetLen());
            SetInsertPoint(ip);
            return TRUE;
#ifdef DEBUG
        } else {
            XP_ASSERT(TRUE);
#endif
        }
    }
    return FALSE;
}

XP_Bool CEditBuffer::IsInsertPointInTable(){
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    return ip.m_pElement->GetTableIgnoreSubdoc() != NULL;
}

XP_Bool CEditBuffer::IsInsertPointInNestedTable(){
    XP_Bool result = FALSE;
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableElement* pFirstTable = ip.m_pElement->GetTableIgnoreSubdoc();
    if ( pFirstTable ) {
        CEditTableElement* pSecondTable = pFirstTable->GetParent()->GetTableIgnoreSubdoc();
        if ( pSecondTable ) {
            result = TRUE;
        }
    }
    return result;
}

EDT_TableData* CEditBuffer::GetTableData(){
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
    if ( pTable ){
        return pTable->GetData();
    }
    else {
        return NULL;
    }
}

void CEditBuffer::SetTableData(EDT_TableData *pData){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
    if ( pTable ){
        pTable->SetData( pData );
        Relayout( pTable, 0 );
        // Update the size data to reflect changes made by Layout
        pData->iWidth = pTable->GetWidthOrPercent();
        pData->iWidthPixels = pTable->GetWidth();
        pData->iHeight = pTable->GetHeightOrPercent();
        pData->iHeightPixels = pTable->GetHeight();
    }
}

void CEditBuffer::InsertTable(EDT_TableData *pData){
    CEditTableElement *pTable = new CEditTableElement(pData->iColumns, pData->iRows);
    if( pTable ){
        pTable->SetData( pData );
        pTable->FinishedLoad(this); // Sets default paragraphs for all the cells
        if( IsSelected() ){
            ClearSelection();
        }
        InsertNonLeaf(pTable);
        // Set insert point inside first cell in table
        CEditElement *pFirstChild = pTable->GetFirstMostChild();
        CEditInsertPoint ip(pFirstChild, 0);
        SetInsertPoint(ip);
    }
}

void CEditBuffer::DeleteTable(){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
    if ( pTable ){
        BeginBatchChanges(kGroupOfChangesCommandID);
		AdoptAndDo(new CDeleteTableCommand(this));
        EndBatchChanges();
    }
}

/* Use to enable/disable Merge Cells feature. 
 * Selected cells can be merged only if in a continuous set
 * within the same row or column
*/
ED_MergeType CEditBuffer::GetMergeTableCellsType()
{
    // Need at least 2 cells to merge
    CEditTableCellElement* pTableCell = NULL;
    int iCount = m_SelectedEdCells.Size();
    if( iCount == 1 )
    {
        pTableCell = m_SelectedEdCells[0];
    } else if(iCount == 0 ){
        CEditInsertPoint ip;
        GetTableInsertPoint(ip);
        pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    }
    if( pTableCell && pTableCell->GetNextSibling() )
    {
        // We have a single cell with a neighbor to the right
        return ED_MERGE_NEXT_CELL;
    }
    
    // Nothing to merge
    if( iCount < 2 )
        return ED_MERGE_NONE;
#if 0
//  TODO: REVISIT THIS
    // If any selected cell is not inline with all others,
    //  don't allow merging
    int32 left = m_SelectedLoCells[0]->x;
    int32 top = m_SelectedLoCells[0]->y;
    XP_Bool bSameTop = (m_SelectedLoCells[1]->y == top);
    XP_Bool bSameLeft = (m_SelectedLoCells[1]->x == left);

    // Neither is the same - quit
    if( !bSameLeft && !bSameTop )
        return ED_MERGE_NONE;

    for( int i = 2; i < iCount; i++ )
    {
        if( (bSameLeft && m_SelectedLoCells[i]->x != left) || 
            (bSameTop &&  m_SelectedLoCells[i]->y != top) )
        {
            return ED_MERGE_NONE;
        }
    }
#endif
    return ED_MERGE_SELECTED_CELLS;
}

/* Use to enable/disable Split Cell feature. 
 * Current cell (containing caret) can be split 
 * only if it has COLSPAN or ROWSPAN
*/
XP_Bool CEditBuffer::CanSplitTableCell()
{
    XP_Bool bResult = FALSE;
    // We can split a cell if single cell is selected
    // and/or caret is inside a cell with COLSPAN or ROWSPAN
    if( m_SelectedLoCells.Size() <= 1 )
    {
        EDT_TableCellData *pData = GetTableCellData();
        if( pData )
        {
            bResult = (pData->iColSpan > 1) || (pData->iRowSpan > 1);
            EDT_FreeTableCellData(pData);
        }
    }
    return bResult;
}

/* Set appropriate COLSPAN or ROWSPAN and move all
 * cell contents into first cell of set
 */
void CEditBuffer::MergeTableCells()
{
    // Don't attempt if not allowed
    ED_MergeType MergeType = GetMergeTableCellsType();
    //if( MergeType == ED_MERGE_NONE )
    //    return;
    int iCount = m_SelectedEdCells.Size();

    // Be sure selection is ordered properly
    SortSelectedCells();
    
    // Set beginning of UNDO block
    BeginBatchChanges(kGroupOfChangesCommandID);

    int32 x = 0;
    int32 y = 0;

    // This is the cell we will merge into
    CEditTableCellElement *pFirstCell = NULL;
    if( iCount > 0 )
    {
        // We have selected cell as first
        pFirstCell = m_SelectedEdCells[0];
        // Save the x and y of first cell
        //  to figure out COL or ROW span
        x = m_SelectedLoCells[0]->x;
        y = m_SelectedLoCells[0]->y;
    } else {
        // Get current cell at caret 
        CEditInsertPoint ip;
        GetTableInsertPoint(ip);
        pFirstCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
        if(pFirstCell )
        {
            LO_CellStruct *pLoCell = GetLoCell(pFirstCell);
            if(pLoCell)
            {
                x = pLoCell->x;
                y = pLoCell->y;
            }
        }
    }

    EDT_TableCellData *pFirstCellData = pFirstCell->GetData();
    if(!pFirstCellData)
        return;
    EDT_TableCellData *pNextCellData = NULL;

    if( iCount > 1 )
    {
        // We are merging all selected cells
        for( int i = 1; i < iCount; i++ )
        {
            // We must add all COLSPANs of all cells to be merged
            pNextCellData = m_SelectedEdCells[i]->GetData();
            if( pNextCellData )
            {
                // If y value is same, next cell is in same row, so do ColSpan
                if( y == m_SelectedLoCells[i]->y )
                {
                    pFirstCellData->iColSpan += pNextCellData->iColSpan;
                }

                // If x value is same, next cell is in same col, so do RowSpan
                // Note: This will work for strange selection sets, but if all
                //  cells in largest bounding rect aren't selected, we can get some strange results!
                if( x == m_SelectedLoCells[i]->x )
                {
                    pFirstCellData->iRowSpan += pNextCellData->iRowSpan;
                }
                EDT_FreeTableCellData(pNextCellData);
            }
            pFirstCell->MergeCells(m_SelectedEdCells[i]);
        }
    } else {
        // We are merging with just the next cell to the right
        CEditTableCellElement *pNextCell = (CEditTableCellElement*)pFirstCell->GetNextSibling();
        pNextCellData = pNextCell->GetData();
        if( pNextCellData )
        {
            pFirstCellData->iColSpan += pNextCellData->iColSpan;
            EDT_FreeTableCellData(pNextCellData);
        }
        pFirstCell->MergeCells( pNextCell );
    }
    // Set the SPAN data for the merged cell
    pFirstCell->SetData(pFirstCellData);
    EDT_FreeTableCellData(pFirstCellData);

    // Be sure insert point is in first cell, 
    //   not one of the cells that was deleted
    //   (else we crash in Relayout)
    SetTableInsertPoint(pFirstCell);
    
    ClearTableAndCellSelection();
    
    // Relayout the entire table
    Relayout(pFirstCell->GetParentTable(), 0);
    EndBatchChanges();
}

/* Separate paragraphs into sepaparate cells,
 * removing COLSPAN or ROWSPAN
*/
void CEditBuffer::SplitTableCell()
{
    //TODO: WRITE THIS!
}


XP_Bool CEditBuffer::IsInsertPointInTableCaption(){
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    // We allow the insert point to be in a table with a table caption.
    CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
    CEditCaptionElement* pTableCaption = pTable ? pTable->GetCaption() : 0;
    return pTableCaption != NULL;
}

EDT_TableCaptionData* CEditBuffer::GetTableCaptionData(){
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    // We allow the insert point to be in a table with a table caption.
    CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
    CEditCaptionElement* pTableCaption = pTable ? pTable->GetCaption() : 0;
    if ( pTableCaption ){
        return pTableCaption->GetData();
    }
    else {
        return NULL;
    }
}

void CEditBuffer::SetTableCaptionData(EDT_TableCaptionData *pData){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
    CEditCaptionElement* pTableCaption = pTable ? pTable->GetCaption() : 0;
    if ( pTableCaption ){
        pTableCaption->SetData( pData );
        pTable->FinishedLoad(this); // Can move caption up or down.
        Relayout( pTable, 0 );
    }
}

void CEditBuffer::InsertTableCaption(EDT_TableCaptionData *pData){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
    if ( pTable ){
        if ( ! pTable->GetCaption() ) {
            // CInsertTableCaptionCommand actually performs the operation in the
            // constructor of the command. So in order for save-based undo to
            // work, we must wrap its constructor in BeginBatchChanges.
            BeginBatchChanges(kGroupOfChangesCommandID /*kInsertTableCaptionCommandID*/);
            AdoptAndDo(new CInsertTableCaptionCommand(this, pData));
            EndBatchChanges();
        }
    }
}

void CEditBuffer::DeleteTableCaption(){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
    CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if ( pTable ){
        if ( pTable->GetCaption() ) {
            BeginBatchChanges(kGroupOfChangesCommandID);
            AdoptAndDo(new CDeleteTableCaptionCommand(this));
            EndBatchChanges();
        }
    }
}

XP_Bool CEditBuffer::IsInsertPointInTableRow(){
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    return ip.m_pElement->GetTableRowIgnoreSubdoc() != NULL;
}

EDT_TableRowData* CEditBuffer::GetTableRowData(){
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableRowElement* pTableRow = ip.m_pElement->GetTableRowIgnoreSubdoc();
    if ( pTableRow ){
        return pTableRow->GetData();
    }
    else {
        return NULL;
    }
}

void CEditBuffer::SetTableRowData(EDT_TableRowData *pData){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableRowElement* pTableRow = ip.m_pElement->GetTableRowIgnoreSubdoc();
    if ( pTableRow ){
        pTableRow->SetData( pData );
        Relayout( pTableRow, 0 );
    }
}

intn CEditBuffer::GetNumberOfSelectedRows()
{
    intn iRows = 0;
    int iSize = m_SelectedLoCells.Size();
    if( iSize && m_TableHitType == ED_HIT_SEL_ROW )
    {
        SortSelectedCells();

        iRows = 1;
        int32 iCurrentTop = m_SelectedLoCells[0]->y;
        for(int i = 2; i < iSize; i++ )
        {
            // Different row when y value changes            
            if( iCurrentTop != m_SelectedLoCells[i]->y )
            {
                iRows++;
                iCurrentTop = m_SelectedLoCells[i]->y;
            }
        }
    }
    return iRows;
}

intn CEditBuffer::GetNumberOfSelectedColumns()
{
    intn iCols = 0;
    int iSize = m_SelectedLoCells.Size();
    if( iSize && m_TableHitType == ED_HIT_SEL_COL )
    {
        // Finding number of columns is more complicated 
        //  than rows because ordering is rows first,
        //  and because of COLSPAN and ROWSPAN
        TXP_GrowableArray_LO_CellStruct  UniqueXCells;
        UniqueXCells.Add(m_SelectedLoCells[0]);

        for(int i=2; i < iSize; i++ )
        {
            intn j;
            // Check if next cell has diffferent x than others
            intn iUniqueCount = UniqueXCells.Size();
            for( j = 0; j < iUniqueCount; j++ )
            {
                if( UniqueXCells[j]->x == m_SelectedLoCells[i]->x )
                    break;
            }
            // We didn't find it, so add to list
            if( j == iUniqueCount )
                UniqueXCells.Add(m_SelectedLoCells[i]);
        }
        iCols = UniqueXCells.Size();
    }
    return iCols;
}

void CEditBuffer::InsertTableRows(EDT_TableRowData *pData, XP_Bool bAfterCurrentRow, intn number)
{
    VALIDATE_TREE(this);

    if( number <= 0 )
    {
        // Get number of rows from the table selection
        number = GetNumberOfSelectedRows();

        if( number )
        {        
            // Move to appropriate cell
            if( bAfterCurrentRow )
                MoveToLastSelectedCell();
            else
                MoveToFirstSelectedCell();
        }
    }

    // If none or just one selected cell, then insert 1 row
    if( number == 0 && m_SelectedLoCells.Size() <= 1 )
        number = 1;

    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if ( pTableCell ){
        // CInsertTableRowCommand actually performs the operation in the
        // constructor of the command. So in order for save-based undo to
        // work, we must wrap its constructor in BeginBatchChanges.
        BeginBatchChanges(kGroupOfChangesCommandID/*kInsertTableRowCommandID*/);
        ClearTableAndCellSelection();
        AdoptAndDo(new CInsertTableRowCommand(this, pData, bAfterCurrentRow, number));
        EndBatchChanges();
    }
}

void CEditBuffer::DeleteTableRows(intn number){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if ( pTableCell ){
        BeginBatchChanges(kGroupOfChangesCommandID);
		AdoptAndDo(new CDeleteTableRowCommand(this, number));
        EndBatchChanges();
    }
}

void CEditBuffer::SyncCursor(CEditLayerElement* pLayer)
{
    CEditInsertPoint insertPoint;
    insertPoint.m_pElement = m_pRoot->GetFirstMostChild()->Leaf();
    if ( pLayer && pLayer->GetFirstMostChild()->IsLeaf() ) {
        insertPoint.m_pElement = pLayer->GetFirstMostChild()->Leaf();
    }

    SetInsertPoint(insertPoint);
}

XP_Bool CEditBuffer::IsInsertPointInTableCell(){
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    return ip.m_pElement->GetTableCellIgnoreSubdoc() != NULL;
}

EDT_TableCellData* CEditBuffer::GetTableCellData()
{
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableCellElement* pCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if ( pCell )
    {
        // Get data for the current cell (containing the caret)
        EDT_TableCellData *pData = pCell->GetData();

        XP_ASSERT(pData); //Should never fail
        if( !pData )
            return NULL;

        // Used by front end to figure modify Cell Properties
        //  dialog based on number of selected cells
        pData->iSelectedCount = m_SelectedEdCells.Size();

        if( pData->iSelectedCount > 1 )
        {
            ED_HitType iNewSelType = ED_HIT_NONE;
            XP_Bool bColSelected = FALSE;
            XP_Bool bRowSelected = FALSE;

            if( m_TableHitType == ED_HIT_SEL_ALL_CELLS )
            {
                // To the front ends, all cells means "rows"
                iNewSelType = ED_HIT_SEL_ROW;
            } 
            else if(m_TableHitType == ED_HIT_SEL_ROW || 
                    m_TableHitType == ED_HIT_SEL_COL )
            {
                iNewSelType = m_TableHitType;
            }

            // Go through all selected cells to set mask bits 
            //  that tell what attributes are the same 
            //  for all selected cells
            for( intn i = 0; i < pData->iSelectedCount; i++ )
            {
                // Skip current cell when comparing data
                if( m_SelectedEdCells[i] != pCell )
                {
                    m_SelectedEdCells[i]->MaskData(pData);

                    // Check if all cells in just rows or just column are selected
                    if( iNewSelType == ED_HIT_NONE )
                    {
                        if( lo_AllCellsSelectedInColumnOrRow( m_SelectedLoCells[i], TRUE ) ) // Column
                        {
                            if( bRowSelected )
                                // Can't have both row and columns selected
                                iNewSelType = ED_HIT_SEL_CELL;
                            else
                                bColSelected = TRUE;
                        }
                        else if( lo_AllCellsSelectedInColumnOrRow( m_SelectedLoCells[i], FALSE ) ) // Row
                        {
                            if( bColSelected )
                                // Can't have both row and columns selected
                                iNewSelType = ED_HIT_SEL_CELL;
                            else
                                bRowSelected = TRUE;
                        } else 
                        {
                            // Neither row or column is fully selected
                            iNewSelType = ED_HIT_SEL_CELL;
                        }
                    }
                    // Override ColSpan and RowSpan mask bit
                    //  to NOT allow changing it when > 1 cell is selected
                    // TODO: MAYBE ALLOW CHANGING THIS IF NOT A FULL ROW OR COL SELECTED???
                    pData->mask &= ~(CF_COLSPAN | CF_ROWSPAN);
                }
                if( iNewSelType == ED_HIT_NONE )
                {
                    if( bRowSelected )
                        iNewSelType = ED_HIT_SEL_ROW;
                    else if( bColSelected )
                        iNewSelType = ED_HIT_SEL_COL;
                }
            }
            pData->iSelectionType = iNewSelType;
        }
        return pData;
    }
    else {
        return NULL;
    }
}

static void edt_CopyLoColor( LO_Color **ppDestColor, LO_Color *pSourceColor )
{
    if( ppDestColor )
    {
        if( !pSourceColor )
        {
            XP_FREEIF(*ppDestColor);
            return;
        }

        // Create new struct if it doesn't exist        
        if( !*ppDestColor )
            *ppDestColor = XP_NEW(LO_Color);
        XP_ASSERT(*ppDestColor);

        if( *ppDestColor )
        {
            (*ppDestColor)->red = pSourceColor->red;
            (*ppDestColor)->green = pSourceColor->green;
            (*ppDestColor)->blue = pSourceColor->blue;
        }
    }
}

static void edt_CopyTableCellData( EDT_TableCellData *pDestData, EDT_TableCellData *pSourceData )
{
    if( pDestData && pSourceData )
    {
        // Change data only for attributes whose bit is set in data mask
        if( pSourceData->mask & CF_ALIGN )
            pDestData->align = pSourceData->align;

        if( pSourceData->mask & CF_VALIGN )
            pDestData->valign = pSourceData->valign;

        if( pSourceData->mask & CF_COLSPAN )
            pDestData->iColSpan = pSourceData->iColSpan;

        if( pSourceData->mask & CF_ROWSPAN )
            pDestData->iRowSpan = pSourceData->iRowSpan;

        if( pSourceData->mask & CF_HEADER )
            pDestData->bHeader = pSourceData->bHeader;

        if( pSourceData->mask & CF_NOWRAP )
            pDestData->bNoWrap = pSourceData->bNoWrap;

        if( pSourceData->mask & CF_BACK_NOSAVE )
            pDestData->bBackgroundNoSave = pSourceData->bBackgroundNoSave;

        if( pSourceData->mask & CF_WIDTH )
        {
            pDestData->bWidthDefined = pSourceData->bWidthDefined;
            pDestData->iWidth = pSourceData->iWidth;
            pDestData->bWidthPercent = pSourceData->bWidthPercent;
        }

        if( pSourceData->mask & CF_HEIGHT )
        {
            pDestData->bHeightDefined = pSourceData->bHeightDefined;
            pDestData->iHeight = pSourceData->iHeight;
            pDestData->bHeightPercent = pSourceData->bHeightPercent;
        }

        if( pSourceData->mask & CF_BACK_COLOR )
            edt_CopyLoColor( &pDestData->pColorBackground, pSourceData->pColorBackground);

        if( pSourceData->mask & CF_BACK_IMAGE )
        {
            XP_FREEIF(pDestData->pBackgroundImage);
            if( pSourceData->pBackgroundImage )
                pDestData->pBackgroundImage = XP_STRDUP(pSourceData->pBackgroundImage);
        }

        if( pSourceData->mask & CF_EXTRA_HTML )
        {
            XP_FREEIF(pDestData->pExtra);
            if( pSourceData->pExtra )
                pDestData->pExtra = XP_STRDUP(pSourceData->pExtra);
        }
    }
}

void CEditBuffer::ChangeTableSelection(ED_HitType iHitType, ED_MoveSelType iMoveType, EDT_TableCellData *pData)
{
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableCellElement* pCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if( !pCell )
        return;

    CEditTableElement *pTable = pCell->GetTable();
    XP_ASSERT(pTable);

    // Clear existing selection - we will always reselect
    ClearTableAndCellSelection();

    // First see if we are changing the current selection type
    if( iHitType != ED_HIT_NONE && pData && iHitType != pData->iSelectionType )
    {
        // This is relatively simple as long as we can trust that the
        //   caret is in the "focus cell" 
        if( iHitType == ED_HIT_SEL_CELL )
        {
            SelectTableElement( pCell, iHitType );
        } else 
        {
            // Keep focus cell the same - just select the row or column it is in
            SelectTableElement( pCell, iHitType );
        }
    }

    CEditTableCellElement *pNextCell = NULL;
    CEditTableCellElement *pCurrentCell = pCell;
    int32 iCurrentX = pCell->GetX();
    int32 iCurrentY = pCell->GetY();

    if( iMoveType == ED_MOVE_PREV )
    {
        switch (iHitType )
        {
            case ED_HIT_SEL_CELL:
                pCell = pCell->GetPreviousCellInTable();
                if( !pCell )
                {
                    pCell = pCurrentCell;
                    // We found beginning of table - find last cell in table
                    while( (pNextCell = pCell->GetNextCellInTable()) != NULL )
                    {
                        pCell = pNextCell;
                    }
                }
                break;

            case ED_HIT_SEL_COL:
                // Previous column contains 
                //   the previous cell in current row
                pCell = pTable->GetPreviousCellInRow(pCurrentCell);
                if( !pCell )
                    pCell = pTable->GetLastCellInRow(pCurrentCell);
                break;

            case ED_HIT_SEL_ROW:
                // Previous row contains 
                //   the cell cell in current column
                pCell = pTable->GetPreviousCellInColumn(pCurrentCell);

                if( !pCell )
                    pCell = pTable->GetLastCellInColumn(pCurrentCell);
                break;
        }
    } 
    else if( iMoveType == ED_MOVE_NEXT )
    {
        switch (iHitType )
        {
            case ED_HIT_SEL_CELL:
                // Move to Next Cell
                pCell = pCell->GetNextCellInTable();
                if( !pCell )
                    // At end of table - wrap to first cell
                    pCell = pTable->GetFirstCell();
                break;

            case ED_HIT_SEL_COL:
                pCell = pTable->GetNextCellInRow(pCurrentCell);
                if( !pCell )
                    // At last column -- wrap to first cell of current row
                    pCell = pTable->GetFirstCellInRow(pCurrentCell);
                break;

            case ED_HIT_SEL_ROW:
                pCell = pTable->GetNextCellInColumn(pCurrentCell);
                if( !pCell )
                    // At last row end -- wrap to first cell of current column
                    pCell = pTable->GetFirstCellInColumn(pCurrentCell);
                break;
        }
    }

    if( pCell )
    {
        if( iMoveType != ED_MOVE_NONE &&
            (iHitType == ED_HIT_SEL_ROW || iHitType == ED_HIT_SEL_COL) )
        {
            // We need to select the column or row we are moving to
            // NOTE: This will move caret to this cell
            SelectTableElement( pCell, iHitType );
        } else {
            // Move caret to new focus cell
            SetTableInsertPoint(pCell);
        }

        if( m_SelectedEdCells.Size() > 1 )
        {
            // NOTE: If previous selection was a set of cells or row or column,
            //   and new iHitType == ED_HIT_SEL_CELL,
            //   then the new selection will be the single cell after the
            //   focus cell of the current selection
            // This does NOT change selected cells, just marks
            //   the non-focus cells with LO_ELE_SELECTED_SPECIAL
            //   and updates iSelectionType and iSelectedCount in pData
            DisplaySpecialCellSelection(pCell, pData);
        } else 
        {
            // Select just one new cell in table
            SelectTableElement( pCell, ED_HIT_SEL_CELL );
            // Change cell data to reflect single cell selection
            if( pData )
            {
                pData->iSelectionType = ED_HIT_SEL_CELL;
                pData->iSelectedCount = 1;
            }
        }

        if( pData )
        {
            // Fill supplied struct with data for next cell
            EDT_TableCellData *pNextData = pCell->GetData();
            if( pNextData )
            {
                // Set all bits in mask so all values are copied
                // This does not change current pData->mask
                pNextData->mask = -1;
                edt_CopyTableCellData(pData, pNextData);
                EDT_FreeTableCellData(pNextData);
            }
        }
    }
}

static void edt_SetTableCellData( CEditTableCellElement *pCell, EDT_TableCellData *pData )
{
    XP_ASSERT(pCell && pData);
    if(!pCell || !pData )
        return;

    // Get current data before setting new stuff
    EDT_TableCellData *pCurrentData = pCell->GetData();
    XP_ASSERT( pCurrentData );
    if( !pCurrentData )
        return;

    // Copy new data to current cell 
    edt_CopyTableCellData( pCurrentData, pData );
    // TEMP - TRYING TO TRACK DOWN GROWING CELL BUG 
    if( pCurrentData->bWidthDefined && !pCurrentData->bWidthPercent )
        pCurrentData->iWidthPixels = pCurrentData->iWidth;
    if( pCurrentData->bHeightDefined && !pCurrentData->bHeightPercent )
        pCurrentData->iHeightPixels = pCurrentData->iHeight;

    pCell->SetData( pCurrentData );


    // Try to be smart and automatically delete empty cells
    //  when user is increasing COLSPAN relative to previous value
    //TODO: NEED TO ACCOUNT FOR ROWSPAN AS WELL
    if( (pData->mask & CF_COLSPAN) && pData->iColSpan > 1 )
    {
        // Check ROWSPAN and COLSPAN
        if( pCurrentData )
        {
            int iDeleteCount = pData->iColSpan - pCurrentData->iColSpan;
            int i = 0;

            pCell = (CEditTableCellElement*)pCell->GetNextSibling();
            while( pCell && i < iDeleteCount )
            {
                CEditTableCellElement *pNextCell = 
                        (CEditTableCellElement*)pCell->GetNextSibling();

                if( pCell->IsEmpty() )
                {
                    i++;
                    delete pCell;
                }    
                pCell = pNextCell;
            }
        }
    }

    EDT_FreeTableCellData(pCurrentData);
}

void CEditBuffer::SetTableCellData(EDT_TableCellData *pData)
{
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableCellElement* pCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if( !pCell )
        return;    
    CEditElement *pTable = pCell->GetTable();
    if( !pTable )
        return;

    BeginBatchChanges(kGroupOfChangesCommandID);

    XP_Bool bCurrentCellFound = FALSE;
    intn iCellCount = m_SelectedEdCells.Size();
    if( iCellCount > 1 )
    {
        // Set data for all selected cells
        for( intn i = 0; i < iCellCount; i++ )
        {
            edt_SetTableCellData( m_SelectedEdCells[i], pData );
            if( m_SelectedEdCells[i] == pCell )
                bCurrentCellFound = TRUE;
        }
        // Current cell should alway be in selected cell set,
        //  so check for that
        XP_ASSERT( bCurrentCellFound );
    } else {
        // Set data for just current cell
        edt_SetTableCellData( pCell, pData );
    }
    Relayout( pTable, 0 );

    // Adjust caller's data - possibly different size data 
    //   as a result of Relayout()
    pData->iWidth = pCell->GetWidthOrPercent();
    pData->iWidthPixels = pCell->GetWidth();
    pData->iHeight = pCell->GetHeightOrPercent();
    pData->iHeightPixels = pCell->GetHeight();

    EndBatchChanges();
}

void CEditBuffer::InsertTableColumns(EDT_TableCellData *pData, XP_Bool bAfterCurrentCell, intn number){
    VALIDATE_TREE(this);
    if( number <= 0 )
    {
        // Get number of columns from the table selection
        number = GetNumberOfSelectedColumns();
        if( number )
        {        
            // Move to appropriate cell
            if( bAfterCurrentCell )
                MoveToLastSelectedCell();
            else
                MoveToFirstSelectedCell();
        }
    }
    // If none or just one selected cell, then insert 1 row
    if( number == 0 && m_SelectedLoCells.Size() <= 1 )
        number = 1;

    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if ( pTableCell ){
        // CInsertTableColumnCommand actually performs the operation in the
        // constructor of the command. So in order for save-based undo to
        // work, we must wrap its constructor in BeginBatchChanges.
        BeginBatchChanges(kGroupOfChangesCommandID/*kInsertTableColumnCommandID*/);
        ClearTableAndCellSelection();
        AdoptAndDo(new CInsertTableColumnCommand(this, pData, bAfterCurrentCell, number));
        EndBatchChanges();
    }
}

void CEditBuffer::DeleteTableColumns(intn number){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if ( pTableCell ){
        BeginBatchChanges(kGroupOfChangesCommandID);
		AdoptAndDo(new CDeleteTableColumnCommand(this, number));
        EndBatchChanges();
    }
}

void CEditBuffer::InsertTableCells(EDT_TableCellData* /* pData */, XP_Bool bAfterCurrentCell, intn number){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if ( pTableCell ){
        // CInsertTableCellCommand actually performs the operation in the
        // constructor of the command. So in order for save-based undo to
        // work, we must wrap its constructor in BeginBatchChanges.
        BeginBatchChanges(kGroupOfChangesCommandID /*kInsertTableCellCommandID*/);
		AdoptAndDo(new CInsertTableCellCommand(this, bAfterCurrentCell, number));
        EndBatchChanges();
    }
}

void CEditBuffer::DeleteTableCells(intn number){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if ( pTableCell ){
        BeginBatchChanges(kGroupOfChangesCommandID);
		AdoptAndDo(new CDeleteTableCellCommand(this, number));
        EndBatchChanges();
    }
}

// Layer stuff

XP_Bool CEditBuffer::IsInsertPointInLayer(){
    CEditInsertPoint ip;
    GetInsertPoint(ip);
    return ip.m_pElement->GetLayerIgnoreSubdoc() != NULL;
}

EDT_LayerData* CEditBuffer::GetLayerData(){
    CEditInsertPoint ip;
    GetInsertPoint(ip);
    CEditLayerElement* pLayer = ip.m_pElement->GetLayerIgnoreSubdoc();
    if ( pLayer ){
        return pLayer->GetData();
    }
    else {
        return NULL;
    }
}

void CEditBuffer::SetLayerData(EDT_LayerData *pData){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetInsertPoint(ip);
    CEditLayerElement* pLayer = ip.m_pElement->GetLayerIgnoreSubdoc();
    if ( pLayer ){
        pLayer->SetData( pData );
        Relayout( pLayer, 0 );
    }
}

void CEditBuffer::InsertLayer(EDT_LayerData *pData){
    CEditLayerElement *pLayer = new CEditLayerElement();
    if( pLayer ){
        pLayer->SetData( pData );
        pLayer->FinishedLoad(this); // Sets default paragraphs for all the cells
        InsertNonLeaf(pLayer);
        SyncCursor(pLayer);
    }
}

void CEditBuffer::DeleteLayer(){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetInsertPoint(ip);
    CEditLayerElement* pLayer = ip.m_pElement->GetLayerIgnoreSubdoc();
    if ( pLayer ){
        CEditInsertPoint ip;
        GetInsertPoint(ip);
		CEditElement* pRefreshStart = pLayer->GetFirstMostChild()->PreviousLeaf();
		CEditInsertPoint replacePoint(pLayer->GetLastMostChild()->NextLeaf(), 0);
		SetInsertPoint(replacePoint);
		delete pLayer;
		Relayout(pRefreshStart, 0, replacePoint.m_pElement);
    }
}

EDT_HorizRuleData* CEditBuffer::GetHorizRuleData(){
    CEditLeafElement *pInsertPoint;
    ElementOffset iOffset;
    XP_Bool bSingleItem;

    bSingleItem = GetPropertyPoint( &pInsertPoint, &iOffset );
//  CLM: This causes problems for dynamic sizing
//    XP_ASSERT( bSingleItem );

    XP_ASSERT( pInsertPoint->IsA(P_HRULE) );
    return pInsertPoint->HorizRule()->GetData( );
}

void CEditBuffer::SetHorizRuleData( EDT_HorizRuleData* pData ){
    ClearSelection( TRUE, TRUE );
    XP_ASSERT( m_pCurrent->IsA(P_HRULE) );
    m_pCurrent->HorizRule()->SetData( pData );
    Relayout( m_pCurrent->FindContainer(), 0, m_pCurrent, RELAYOUT_NOCARET );
    SelectCurrentElement();
}

void CEditBuffer::InsertHorizRule( EDT_HorizRuleData* pData ){
    if( IsSelected() ){
        ClearSelection();
    }
    BeginBatchChanges(kInsertHorizRuleCommandID);
    CEditHorizRuleElement *pHorizRule = new CEditHorizRuleElement(0);
    pHorizRule->SetData( pData );
    InsertLeaf( pHorizRule );
    EndBatchChanges();
}

char* CEditBuffer::GetTargetData(){
    CEditLeafElement *pInsertPoint;
    ElementOffset iOffset;
    XP_Bool bSingleItem;

    bSingleItem = GetPropertyPoint( &pInsertPoint, &iOffset );
    XP_ASSERT( bSingleItem );

    XP_ASSERT( pInsertPoint->GetElementType() == eTargetElement );
    return pInsertPoint->Target()->GetName( );
}

void CEditBuffer::SetTargetData( char* pData ){
    ClearSelection( TRUE, TRUE );
    if ( m_pCurrent->GetElementType() == eTargetElement ){
        m_pCurrent->Target()->SetName( pData, GetRAMCharSetID() );
        Relayout( m_pCurrent->FindContainer(), 0, m_pCurrent, RELAYOUT_NOCARET );
        SelectCurrentElement();
    }
    else {
        XP_ASSERT(FALSE);
    }
}

void CEditBuffer::InsertTarget( char* pData ){
    if( IsSelected() ){
        ClearSelection(TRUE, TRUE);
    }
    int16 csid = GetRAMCharSetID();
    CEditTargetElement *pTarget = new CEditTargetElement(0, 0, csid);
    pTarget->SetName( pData, csid );
    InsertLeaf( pTarget );
}

char* CEditBuffer::GetAllDocumentTargets(){
    intn iSize = 500;
    int iCur = 0;
    char *pBuf = (char*)XP_ALLOC( iSize );
    CEditElement *pNext = m_pRoot;

    pBuf[0] = 0;
    pBuf[1] = 0;

    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindTarget, 0 )) ){
        char *pName = pNext->Target()->GetName();
        if( pName && *pName ){
            int iLen = XP_STRLEN( pName );
            if( iCur + iLen + 2 > iSize ){
                iSize = iSize + iSize;
                pBuf = (char*)XP_REALLOC( pBuf, iSize );
            }
            XP_STRCPY( &pBuf[iCur], pName );
            iCur += iLen+1;
        }
    }
    pBuf[iCur] = 0;
    return pBuf;
}

#define LINE_BUFFER_SIZE  4096

char* CEditBuffer::GetAllDocumentTargetsInFile(char *pHref){
    intn iSize = 500;
    int iCur = 0;
    char *pBuf = (char*)XP_ALLOC( iSize );
    INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_pContext);
//    CEditElement *pNext = m_pRoot;
    pBuf[0] = 0;
    pBuf[1] = 0;

    char *pFilename = NULL;

// The version for windows is the old bad code that only works
// right for windows.
// The UNIX/MAC code is XP and should really be used for
// windows also.  We're just scared to change it this late
// in the cycle. Bug 50888
#ifdef XP_WIN
    // First check if URL is a local file that exists
    XP_StatStruct stat;
    XP_Bool bFreeFilename = FALSE;

#if defined(XP_MAC) || defined(XP_UNIX) || defined(XP_OS2)
    if ( -1 != XP_Stat(pHref, &stat, xpURL) &&
        stat.st_mode & S_IFREG ) {
#else
    if ( -1 != XP_Stat(pHref, &stat, xpURL) &&
        stat.st_mode & _S_IFREG ) {
#endif
        // We can use unprocessed URL,
        //   and we don't need to free it
        pFilename = pHref;
    }
    else {
        // We probably have a URL,
        //  get absolute URL path then convert to local format
        char *pAbsolute = NET_MakeAbsoluteURL( LO_GetBaseURL(m_pContext ), pHref );

        if( !pAbsolute ||
            !XP_ConvertUrlToLocalFile(pAbsolute, &pFilename) ){
            if( pFilename ) XP_FREE(pFilename);
            if( pAbsolute ) XP_FREE(pAbsolute);
            return NULL;
        }
        XP_FREE(pAbsolute);
        // We need to free the filename made for us
        bFreeFilename = TRUE;
    }

#else
    // pFilename is in xpURL format.

    // First check if URL is a local file that exists
    char *pURL = XP_PlatformFileToURL(pHref);
    if (pURL && XP_ConvertUrlToLocalFile(pURL,NULL)) {
        pFilename = NET_ParseURL(pURL,GET_PATH_PART);
        XP_FREE(pURL);
    }
    else {
        XP_FREEIF(pURL);

        // We probably have a URL,
        //  get absolute URL path then convert to local format
        char *pAbsolute = NET_MakeAbsoluteURL( LO_GetBaseURL(m_pContext ), pHref );

        if( pAbsolute &&
            XP_ConvertUrlToLocalFile(pAbsolute,NULL) ) {
            pFilename = NET_ParseURL(pAbsolute,GET_PATH_PART);
        }

        XP_FREEIF(pAbsolute);

    }
    if (!pFilename) {
      return NULL;
    }
#endif

    // Open local file
    XP_File file = XP_FileOpen( pFilename, xpURL, XP_FILE_READ );
    if( !file ) {

        // Same story as above. Should use the non-windows version.
#ifdef XP_WIN
        if( pFilename && bFreeFilename ){
            XP_FREE(pFilename);
        }
#else
        XP_FREEIF(pFilename);
#endif
        return NULL;
    }

	char    pFileBuf[LINE_BUFFER_SIZE];
    char   *ptr, *pEnd, *pStart;
    size_t  count;
    PA_Tag *pTag;
    char   *pName;

    // Read unformated chunks
    while( 0 < (count = fread(pFileBuf, 1, LINE_BUFFER_SIZE, file)) ){
        // Scan from the end to find end of last tag in block
        ptr = pFileBuf + count -1;
        while( (ptr > pFileBuf) && (*ptr != '>') ) ptr--;

        //Bloody unlikely, but we didn't find a tag!
        if( ptr == pFileBuf ) continue;


        // Move file pointer back so next read starts
        //   1 char after the region just found
        int iBack = int(ptr - pFileBuf) -  int(count) + 1;
        fseek( file, iBack, SEEK_CUR );

        // Save the end of "tagged" region
        //  and reset to beginning
        pEnd = ptr;
        ptr = pFileBuf;

FIND_TAG:
        // Scan to beginning of any tag
        while( (ptr < pEnd) && (*ptr != '<') ) ptr++;
        if( ptr == pEnd ) continue;

        // Save start of tag
        pStart = ptr;

        // Skip over whitespace before tag name
        ptr++;
        while( (ptr < pEnd) && (XP_IS_SPACE(*ptr)) ) ptr++;
        if( ptr == pEnd ) continue;

        // Check for Anchor tag
        if( ((*ptr == 'a') || (*ptr == 'A')) &&
             XP_IS_SPACE(*(ptr+1)) ){
            // Find end of the tag
            while( (ptr < pEnd) && (*ptr != '>') ) ptr++;
            if( ptr == pEnd ) continue;

            // Parse into tag struct so we can use
            //   edt_FetchParamString to do the tricky stuff
            // Kludge city. pa_CreateMDLTag needs a pa_DocData solely to
            // look at the line count.
            {
                pa_DocData doc_data;
                doc_data.newline_count = 0;
                pTag = pa_CreateMDLTag(&doc_data, pStart, (ptr - pStart)+1 );
            }
            if( pTag ){
                if( pTag->type == P_ANCHOR ){
                    pName = edt_FetchParamString(pTag, PARAM_NAME, INTL_GetCSIWinCSID(c));
                    if( pName ){
                        // We found a Name
                        int iLen = XP_STRLEN( pName );
                        if( iCur + iLen + 2 > iSize ){
                            iSize = iSize + iSize;
                            pBuf = (char*)XP_REALLOC( pBuf, iSize );
                        }
                        XP_STRCPY( &pBuf[iCur], pName );
                        iCur += iLen+1;
                        XP_FREE(pName);
                    }
                }
                PA_FreeTag(pTag);
                // Move past the tag we found and search again
                ptr++;
                goto FIND_TAG;
            }
            else {
                // We couldn't find a complete tag, get another block
                continue;
            }
        } else {
            // Not an anchor tag, try again
            ptr++;
            goto FIND_TAG;
        }
    }
    XP_FileClose(file);

// Same story as above. Should use the non-windows version.
#ifdef XP_WIN
    if( pFilename && bFreeFilename ){
        XP_FREE(pFilename);
    }
#else
    XP_FREEIF(pFilename);
#endif

    pBuf[iCur] = 0;

    return pBuf;
}

#ifdef FIND_REPLACE
XP_Bool CEditBuffer::FindAndReplace( EDT_FindAndReplaceData *pData ){
    return FALSE;
}
#endif



class CStretchBuffer {
private:
    char *m_pBuffer;
    intn m_iSize;
    intn m_iCur;

public:
    CStretchBuffer();
    ~CStretchBuffer(){}        // intentionall don't destroy the buffer.
    void Add( char *pString );
    char* GetBuffer(){ return m_pBuffer; }
    // if index non-NULL, return the zero-based index of the found
    // string.
    XP_Bool Contains( char* p, int *pIndex = NULL );
};

CStretchBuffer::CStretchBuffer(){
    m_pBuffer = (char*)XP_ALLOC( 512 );
    m_iSize = 512;
    m_iCur = 0;
    m_pBuffer[0] = 0;
    m_pBuffer[1] = 0;
}

void CStretchBuffer::Add( char *p ){
    if( p == 0 || *p == 0 ){
        return;
    }
    int iLen = XP_STRLEN( p );
    while( m_iCur + iLen + 2 > m_iSize ){
        m_iSize = m_iSize + m_iSize;
        m_pBuffer = (char*)XP_REALLOC( m_pBuffer, m_iSize );
    }
    XP_STRCPY( &m_pBuffer[m_iCur], p );
    m_iCur += iLen+1;
    m_pBuffer[m_iCur] = 0;
}

XP_Bool CStretchBuffer::Contains( char* p, int *pIndex ){
    if (pIndex) {
      *pIndex = 0;
    }
    char *s = GetBuffer();
    while( s && *s ){
        // Assume we are dealing with absolute URLs only.
        if( EDT_IsSameURL(s,p,NULL,NULL) ){
            return TRUE;
        }
        s += XP_STRLEN(s)+1;
        if (pIndex) {
          (*pIndex)++;
        }
    }
    return FALSE;
}


// Helper for CEditBuffer::GetAllDocumentFiles.  If pImageURL is not in buf, add it and add 
// element to the "selected" list.  If pImageURL is in buf and was previously unselected, we may
// now select it.
//
// Analogous to CEditSaveObject::CheckAddFile()
PRIVATE
void AddToBufferUnique(CStretchBuffer &buf, TXP_GrowableArray_int32 &selected,
            char *pBaseURL, char *pImageURL, XP_Bool bSelected) {
  //XP_Bool bSelected = !bNoSave;
  char *pFilename = NET_MakeAbsoluteURL( pBaseURL, pImageURL);
  if (pFilename) {
     int index;
    // Don't insert duplicates.
     if (!buf.Contains( pFilename, &index )) {
       // keep buf and selected the same size.
       buf.Add( pFilename );
       selected.Add((int32)bSelected); 
     }
     else if (bSelected) {
        // If wasn't selected before, it is now.
        selected[index] = (int32)bSelected;
     }
     XP_FREE(pFilename);
  }
}

// CEditBuffer::GetAllDocumentFiles() has been significantly changed.
// The returned files do not have to be in the same directory, and are not
// checked for existence.  hardts
//
// If ppSelected is passed in, it will be set to an array of length equal to the number
// of strings in the returned value.  For each image, it tells whether, by default, it should be 
// published/saved along with the document.  E.g. looks at the NOSAVE attribute.
// ppSelected may be NULL.
// If no images found, no memory will be allocated for ppSelected.
char* CEditBuffer::GetAllDocumentFiles(XP_Bool **ppSelected,XP_Bool bKeepImagesWithDoc){
//////// WARNING: if you change this function, fix CEditSaveObject::AddAllFiles() and CEditSaveObject::FixupLinks() also.
    TXP_GrowableArray_int32 selected;

    CEditElement *pNext = m_pRoot;
    CStretchBuffer buf;
    char *pRetVal = 0;

    char *pDocURL = edt_GetDocRelativeBaseURL(m_pContext);
    if( !pDocURL ){
        return NULL;
    }

    // Font Ref.
    if( m_pFontDefURL && *m_pFontDefURL) {
      AddToBufferUnique(buf,selected,pDocURL,m_pFontDefURL,
            bKeepImagesWithDoc && !m_bFontDefNoSave);
    }

    // Background image.
    if( m_pBackgroundImage && *m_pBackgroundImage) {
      AddToBufferUnique(buf,selected,pDocURL,m_pBackgroundImage,
            bKeepImagesWithDoc && !m_bBackgroundNoSave);
    }

    // Regular images.
    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindImage, 0 )) ){
        EDT_ImageData *pData = pNext->Image()->GetImageData();
        if( pData && pData->pSrc && *pData->pSrc) {
          AddToBufferUnique(buf,selected,pDocURL,pData->pSrc,
                bKeepImagesWithDoc && !pData->bNoSave);
        }

        if( pData && pData->pLowSrc && *pData->pLowSrc) {
          AddToBufferUnique(buf,selected,pDocURL,pData->pLowSrc,
                bKeepImagesWithDoc && !pData->bNoSave);
        }
        EDT_FreeImageData( pData );
    }

    
    //// Sure would be nice to abstract adding all the different types of table
    //// backgrounds.
    // table backgrounds <table>
    pNext = m_pRoot;
    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindTable, 0 )) ){
      EDT_TableData *pData = ((CEditTableElement *)pNext)->GetData();
      if (pData) {
        if (pData->pBackgroundImage && *pData->pBackgroundImage) {
          AddToBufferUnique(buf,selected,pDocURL,pData->pBackgroundImage,
                  bKeepImagesWithDoc && !pData->bBackgroundNoSave);
        }
        CEditTableElement::FreeData(pData);
      }
    }
    // table row backgrounds <tr>
    pNext = m_pRoot;
    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindTableRow, 0 )) ){
      EDT_TableRowData *pData = ((CEditTableRowElement *)pNext)->GetData();
      if (pData) {
        if (pData->pBackgroundImage && *pData->pBackgroundImage) {
          AddToBufferUnique(buf,selected,pDocURL,pData->pBackgroundImage,
                  bKeepImagesWithDoc && !pData->bBackgroundNoSave);
        }
        CEditTableRowElement::FreeData(pData);
      }
    }
    // table cell backgrounds <td> <th>
    pNext = m_pRoot;
    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindTableCell, 0 )) ){
      EDT_TableCellData *pData = ((CEditTableCellElement *)pNext)->GetData();
      if (pData) {
        if (pData->pBackgroundImage && *pData->pBackgroundImage) {
          AddToBufferUnique(buf,selected,pDocURL,pData->pBackgroundImage,
                  bKeepImagesWithDoc && !pData->bBackgroundNoSave);
        }
        CEditTableCellElement::FreeData(pData);
      }
    }


    // UnknownHTML tags with LOCALDATA attribute.
    pNext = m_pRoot;
    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindUnknownHTML, 0 )) ){
      CEditIconElement *pIcon = CEditIconElement::Cast(pNext);
      if (pIcon) {
        char **pMimeTypes;
        char **pURLs;
        int count = pIcon->ParseLocalData(&pMimeTypes,&pURLs);

        // Maybe should make a check that pURLs[n] is a relative URL
        // in current directory.
        for (int n = 0; n < count; n++) {
          AddToBufferUnique(buf,selected,pDocURL,pURLs[n],TRUE);
        }
        CEditIconElement::FreeLocalDataLists(pMimeTypes,pURLs,count);
      }
    } // while

    pRetVal = buf.GetBuffer();

    // If no files found, return NULL
    if( pRetVal && *pRetVal == '\0' ){
        XP_FREE(pRetVal);
        pRetVal = NULL;
    }

    // Create ppSelected list if it was passed in.
    if (ppSelected) {
      if (pRetVal) {
        // allocate memory.
        *ppSelected = (XP_Bool *)XP_ALLOC(selected.Size() * sizeof(XP_Bool));
        if (!*ppSelected) {
          // Out of memory, not really the best thing to do.
          return pRetVal;
        }
        // Fill the array.
        for (int n  = 0; n < selected.Size(); n++) {
          (*ppSelected)[n] = (XP_Bool)selected[n];
        }
      }
      // No images in doc, set ppSelected to NULL.
      else {
        *ppSelected = NULL;
      }
    }

    XP_FREE(pDocURL);
    return pRetVal;
}

char* CEditBuffer::GetUnknownTagData(){
    CEditLeafElement *pInsertPoint;
    ElementOffset iOffset;
    XP_Bool bSingleItem;

    bSingleItem = GetPropertyPoint( &pInsertPoint, &iOffset );
    XP_ASSERT( bSingleItem );

    XP_ASSERT( pInsertPoint->IsIcon() );
    return pInsertPoint->Icon()->GetData( );
}

void CEditBuffer::SetUnknownTagData( char* pData ){
    ClearSelection( TRUE, TRUE );
    XP_ASSERT( m_pCurrent->GetElementType() == eIconElement );
    m_pCurrent->Icon()->SetData( pData );
    Relayout( m_pCurrent->FindContainer(), 0, m_pCurrent, RELAYOUT_NOCARET );
    SelectCurrentElement();
}

void CEditBuffer::InsertUnknownTag( char *pData ){
    if( IsSelected() ){
        ClearSelection();
    }
    if ( ! pData ) {
        XP_ASSERT(FALSE);
        return;
    }

    // This should probably be done elsewhere.  Remove trailing whitespace. HARDTS.
    int32 iLen = XP_STRLEN(pData);
    if (iLen > 0) {
      char *pLast = pData + (iLen - 1);
      while (pLast >= pData && XP_IS_SPACE(*pLast))
        pLast--;
      *(pLast+1) = '\0';
    }

    NormalizeEOLsInString(pData);


    XP_Bool bEndTag = ( iLen > 1 && pData[1] == '/' );
    CEditIconElement *pUnknownTag = new CEditIconElement(0,
        bEndTag ? EDT_ICON_UNSUPPORTED_END_TAG :EDT_ICON_UNSUPPORTED_TAG);
    pUnknownTag->SetData( pData );
    InsertLeaf( pUnknownTag );
}

EDT_ListData* CEditBuffer::GetListData(){
    CEditContainerElement *pContainer;
    CEditListElement *pList;
    if( IsSelected() ){
        CEditContainerElement *pEndContainer;
        CEditListElement *pEndList;
        CEditSelection selection;

        // LTNOTE: this is a hack.  It doesen't handle a bunch of cases.
        // It needs to be able to handle multiple lists selected.
        // It should work a little better.
        //cmanske: TODO: FIX LISTS TO HANDLE SELECTIONS
        GetSelection( selection );
        selection.m_start.m_pElement->FindList( pContainer, pList );
        selection.GetClosedEndContainer()->GetLastMostChild()->FindList( pEndContainer, pEndList );
        if( pList != pEndList ){
            return 0;
        }
    }
    else {
        m_pCurrent->FindList( pContainer, pList );
    }
    if( pList ){
        return pList->GetData( );
    }
    else {
        return 0;
    }
}

void CEditBuffer::SetListData( EDT_ListData* pData ){
    VALIDATE_TREE(this);
    CEditContainerElement *pContainer;
    CEditListElement *pList;

    if( IsSelected() ){
        CEditLeafElement *pBegin, *pEnd, *pCurrent;
        ElementOffset iBeginPos, iEndPos;
        XP_Bool bFromStart;
        GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
        pCurrent = pBegin;
        XP_Bool bDone = FALSE;
        do {
            pCurrent->FindList( pContainer, pList );
            if( pList ){
                pList->SetData( pData );
            }

            bDone = (pEnd == pCurrent );    // For most cases
            pCurrent = pCurrent->NextLeafAll();
            bDone = bDone || (iEndPos == 0 && pEnd == pCurrent ); // Pesky edge conditions!
        } while( pCurrent && !bDone );

        // force layout stop displaying the current selection.
#ifdef LAYERS
        LO_StartSelectionFromElement( m_pContext, 0, 0, NULL);
#else
        LO_StartSelectionFromElement( m_pContext, 0, 0);
#endif

        CEditElement *pLast = pEnd;
        if( pEnd ){
            pEnd->FindList( pContainer, pList );
            if( pList ) pLast = pList->GetLastMostChild();
        }

        Relayout( pBegin->FindContainer(), 0, pLast, RELAYOUT_NOCARET );
        // Need to force selection.
        SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
    }
    else {

        m_pCurrent->FindList( pContainer, pList );
        if( pList ){
            pList->SetData( pData );
            Relayout( pList, 0, pList->GetLastMostChild() );
        }
    }
}

void CEditBuffer::InsertBreak( ED_BreakType eBreak, XP_Bool bTyping ){
    VALIDATE_TREE(this);

    if( IsSelected() ){
        // ToDo: Consider cutting the selection here, like InsertChar does.
        ClearSelection();
    }

    StartTyping(bTyping);
    PA_Tag *pTag = XP_NEW( PA_Tag );
    XP_BZERO( pTag, sizeof( PA_Tag ) );
    pTag->type = P_LINEBREAK;

    switch( eBreak ){
        case ED_BREAK_NORMAL:
            break;
        case ED_BREAK_LEFT:
            edt_SetTagData( pTag, "CLEAR=LEFT>" );
            break;
        case ED_BREAK_RIGHT:
            edt_SetTagData( pTag, "CLEAR=RIGHT>" );
            break;
        case ED_BREAK_BOTH:
            edt_SetTagData( pTag, "CLEAR=BOTH>" );
            break;
    }
    CEditBreakElement *pBreak = new CEditBreakElement( 0, pTag );
    InsertLeaf(pBreak);
    FixupSpace(bTyping);
    Reduce(pBreak->GetParent());
}

EDT_ClipboardResult CEditBuffer::CanCut(XP_Bool bStrictChecking){
    CEditSelection selection;
    GetSelection(selection);
    return CanCut(selection, bStrictChecking);
}

EDT_ClipboardResult CEditBuffer::CanCut(CEditSelection& selection, XP_Bool bStrictChecking)
{
    if( IsTableOrCellSelected() )
    {
        //TODO: MODIFY TO CHECK FOR CONTENTS TO COPY?
        return EDT_COP_OK;
    }
    EDT_ClipboardResult result = selection.IsInsertPoint() ? EDT_COP_SELECTION_EMPTY : EDT_COP_OK;
    if ( bStrictChecking && result == EDT_COP_OK
        && selection.CrossesSubDocBoundary() ) {
        result = EDT_COP_SELECTION_CROSSES_TABLE_DATA_CELL;
    }
    return result;
}

EDT_ClipboardResult CEditBuffer::CanCopy(XP_Bool bStrictChecking){
    return CanCut(bStrictChecking); /* In the future we may do something for read-only docs. */
}

EDT_ClipboardResult CEditBuffer::CanCopy(CEditSelection& selection, XP_Bool bStrictChecking){
    return CanCut(selection, bStrictChecking); /* In the future we may do something for read-only docs. */
}

EDT_ClipboardResult CEditBuffer::CanPaste(XP_Bool bStrictChecking){
    CEditSelection selection;
    GetSelection(selection);
    return CanPaste(selection, bStrictChecking);
}

EDT_ClipboardResult CEditBuffer::CanPaste(CEditSelection& selection, XP_Bool bStrictChecking){
    EDT_ClipboardResult result = EDT_COP_OK;
    if ( bStrictChecking
        && selection.CrossesSubDocBoundary() ) {
        result = EDT_COP_SELECTION_CROSSES_TABLE_DATA_CELL;
    }
    return result;
}

XP_Bool CEditBuffer::CanSetHREF(){
    if( IsSelected()
            || ( m_pCurrent && m_pCurrent->GetHREF() != ED_LINK_ID_NONE ) ){
        return TRUE;
    }
    else {
        return FALSE;
    }
}

//  This needs to be written to:
//    Return data in EDT_HREFData struct containing:
//    pTitle:   ALL the text of elements with same HREF
//              or the image name if only an image is selected
//    pHREF     what we are getting now
//    pTarget   what we are ignoring now!
//    pMocha    "

char *CEditBuffer::GetHREF(){
    ED_LinkId id = GetHREFLinkID();
    if( id != ED_LINK_ID_NONE ){
        return linkManager.GetHREF(id) ;
    }
    else {
        return 0;
    }
}

ED_LinkId CEditBuffer::GetHREFLinkID(){
    CEditLeafElement *pElement = m_pCurrent;

    if( IsSelecting() ){
        return ED_LINK_ID_NONE;
    }

    if( IsSelected() ){
        ElementOffset i,i1;
        CEditLeafElement *pEnd;
        XP_Bool bFromStart;
        GetSelection( pElement, i, pEnd, i1, bFromStart );
    }
    if( pElement ){
        return pElement->Leaf()->GetHREF();
    }
    return ED_LINK_ID_NONE;
}

char* CEditBuffer::GetHREFText(){
    CEditLeafElement *pElement = m_pCurrent;
    ED_LinkId id;
    char* pBuf = 0;

    if( pElement && pElement->IsText() &&
        (id = pElement->GetHREF()) != ED_LINK_ID_NONE ){

        // Move back to find start of contiguous elements
        //  with same HREF
        CEditLeafElement *pStartElement = pElement;
        while ( (pElement = (CEditLeafElement*)pElement->GetPreviousSibling()) != NULL &&
                 pElement->IsText() &&
                 pElement->GetHREF() == id ) {
            pStartElement = pElement;
        }

        // We now have starting EditText element
        pElement = pStartElement;
        char* pText = pStartElement->Text()->GetText();
        pBuf = pText ? XP_STRDUP( pText ) : 0;
        if( !pBuf ){
            return 0;
        }

        // Now scan forward to accumulate text
        while ( (pElement = (CEditLeafElement*)pElement->GetNextSibling()) != NULL &&
                pElement && pElement->IsText() &&
                pElement->GetHREF() == id ){
            // Cast to access text class (we always test element first)
            // CEditTextElement *pText = (CEditTextElement*)pElement;

            pBuf = (char*)XP_REALLOC(pBuf, XP_STRLEN(pBuf) + pElement->Text()->GetSize() + 1);
            if( !pBuf ){
                return 0;
            }
            char* pText = pElement->Text()->GetText();
            if ( pText ) {
                strcat( pBuf, pText );
            }
        }
   }
   return pBuf;
}

void CEditBuffer::SetCaret(){
    InternalSetCaret(TRUE);
}

void CEditBuffer::InternalSetCaret(XP_Bool bRevealPosition){
    LO_TextStruct* pText;
    int iLayoutOffset;
    int32 winHeight, winWidth;
    int32 topX, topY;

    if( m_bNoRelayout ){
        return;
    }

    // jhp Check m_pCurrent because IsSelected won't give accurate results when
    // the document is being layed out.
    if( !IsSelected() && m_pCurrent && !m_inScroll ){

        if( m_pCurrent->IsA( P_TEXT ) ){
            if( !m_pCurrent->Text()->GetLOTextAndOffset( m_iCurrentOffset,
                    m_bCurrentStickyAfter, pText, iLayoutOffset ) ){
                return;
            }
        }

        FE_DestroyCaret(m_pContext);

        if ( bRevealPosition ) {
            RevealPosition(m_pCurrent, m_iCurrentOffset, m_bCurrentStickyAfter);
        }
        FE_GetDocAndWindowPosition( m_pContext, &topX, &topY, &winWidth, &winHeight );
        m_lastTopY = topY;

        if( m_pCurrent->IsA( P_TEXT ) ){
            if( !m_pCurrent->Text()->GetLOTextAndOffset( m_iCurrentOffset, m_bCurrentStickyAfter,
                                                    pText, iLayoutOffset ) ){
                return;
            }

            FE_DisplayTextCaret( m_pContext, FE_VIEW, pText, iLayoutOffset );
        }
        else
        {
            LO_Position effectiveCaretPosition = GetEffectiveCaretLOPosition(m_pCurrent, m_iCurrentOffset, m_bCurrentStickyAfter);

            switch ( effectiveCaretPosition.element->type )
            {
                case LO_IMAGE:
                    FE_DisplayImageCaret( m_pContext,
                                & effectiveCaretPosition.element->lo_image,
                                (ED_CaretObjectPosition)effectiveCaretPosition.position );
                    break;
                default:
                    FE_DisplayGenericCaret( m_pContext,
                                & effectiveCaretPosition.element->lo_any,
                                (ED_CaretObjectPosition)effectiveCaretPosition.position );
                    break;
            }
        }
    }
}


LO_Position CEditBuffer::GetEffectiveCaretLOPosition(CEditElement* pElement, intn iOffset, XP_Bool bCurrentStickyAfter)
{
    LO_Position position;
    int iPos;

	XP_Bool bValid = pElement->Leaf()->GetLOElementAndOffset( iOffset, bCurrentStickyAfter, position.element, iPos);
    if ( ! bValid ) {
        position.element = 0;
        position.position = 0;
        return position;
    }

    XP_ASSERT(position.element);
    position.position = iPos;

    // Linefeeds only have a single position.  If you are at the end
    //  of a linefeed, you are really at the beginning of the next line.
    if( position.element && position.element->type == LO_LINEFEED && position.position == 1 ){
        //LO_Element *pNext = LO_NextEditableElement( pLoElement );
        LO_Element *pNext= position.element->lo_any.next;

#ifdef MQUOTE
         // Skip over bullets resulting from MQUOTE.
        while (pNext && pNext->type == LO_BULLET)
        {
            pNext = pNext->lo_any.next;
        }
#endif

        if( pNext ){
            position.element = pNext;
            position.position = 0;
        }
        else {
            position.position = 0;
        }
    }

    return position;
}

// A utility function for ensuring that a range is visible. This handles one
// axis; it's called twice, once for each dimension.
//
// All variable names are given in terms of the vertical case. The horizontal
// case is the same.
//
// top - the position of the top edge of the visible portion of the document,
// in the document's coordinate system.
//
// length - the length of the window, in the document's coordinate system.
//
// targetLow - the first position we desire to be visible.
//
// targetHigh - the last position we desire to be visible.
//
// In case both positions can't be made visible, targetLow wins.
//
// upMargin - if we have to move up, where to
// position the targetLow relative to top.
// downMargin - if we have to move down, where to position targetLow relative to top.
//
// The margins should be in the range 0..length - 1
//
// Returns TRUE if top changed.

PRIVATE
XP_Bool MakeVisible(int32& top, int32 length, int32 targetLow, int32 targetHigh, int32 upMargin, int32 downMargin)
{
    int32 newTop = top;
    if ( targetLow < newTop ) {
        // scroll up
        newTop = targetLow - upMargin;
        if ( newTop < 0 ) {
            newTop = 0;
        }
    }
    else if ( targetHigh >= (newTop + length) ) {
        // scroll down
        int32 potentialTop = targetHigh - downMargin;
        if ( potentialTop < 0 ) {
            potentialTop = 0;
        }
        if ( potentialTop < targetLow && targetLow < potentialTop + length ) {
            newTop = potentialTop;
        }
        else {
            // Too narrow to show all of cursor. Show just the top part.
            newTop = targetLow - upMargin;
            if ( newTop < 0 ) {
                newTop = 0;
            }
        }
    }

    XP_Bool changed = newTop != top;
    top = newTop;
    return changed;
}

void CEditBuffer::RevealPosition(CEditElement* pElement, int iOffset, XP_Bool bStickyAfter)
{
    int32 winHeight, winWidth;
    int32 topX, topY;
    int32 targetX, targetYLow, targetYHigh;

    if ( ! pElement ) {
       XP_ASSERT(FALSE);
       return;
    }
    FE_GetDocAndWindowPosition( m_pContext, &topX, &topY, &winWidth, &winHeight );

    CEditInsertPoint ip(pElement, iOffset, bStickyAfter);
    if (IsPhantomInsertPoint(ip) ){
        pElement = pElement->GetPreviousSibling();
        if (pElement == NULL ) {
            return; // Shouldn't happen because phantom insert points are always after something.
        }
        iOffset = pElement->Leaf()->GetLen();
    }


    if ( ! GetLOCaretPosition(pElement, iOffset, bStickyAfter, targetX, targetYLow, targetYHigh) )
        return;

    // XP_TRACE(("top %d %d target %d %d %d", topX, topY, targetX, targetYLow, targetYHigh));

    // The visual position policy is that when moving up or down we stick close to
    // the existing margin, but when moving left or right we move to 2/3rds of the
    // way across the screen. (If we ever support right-to-left text we'll
    // have to revisit this.)

    const int32 kUpMargin = ( winHeight > 30 ) ? 10 : (winHeight / 3);
    const int32 kDownMargin = winHeight - kUpMargin;
    const int32 kLeftRightMargin = winWidth * 2 / 3;
    XP_Bool updateX = MakeVisible(topX, winWidth, targetX, targetX, kLeftRightMargin, kLeftRightMargin);
    XP_Bool updateY = MakeVisible(topY, winHeight, targetYLow, targetYHigh, kUpMargin, kDownMargin);

    if ( updateX || updateY ) {
        // XP_TRACE(("new top %d %d", topX, topY));
        XP_Bool bHaveCaret = ! IsSelected();
        if ( bHaveCaret){
            FE_DestroyCaret(m_pContext);
        }
        FE_SetDocPosition(m_pContext, FE_VIEW, topX, topY);
        if ( bHaveCaret){
            InternalSetCaret(FALSE);
        }
    }
}

XP_Bool CEditBuffer::GetLOCaretPosition(CEditElement* pElement, int iOffset, XP_Bool bStickyAfter,
    int32& targetX, int32& targetYLow, int32& targetYHigh)
{
    if ( ! pElement ) {
       XP_ASSERT(FALSE);
       return FALSE;
    }
    LO_Position position = GetEffectiveCaretLOPosition(pElement, iOffset, bStickyAfter);
    if ( position.element == NULL ) return FALSE;
    FE_GetCaretPosition(m_pContext, &position, &targetX, &targetYLow, &targetYHigh);
    return TRUE;
}


/* Begin of document check preference and if move curosr is enabled, 
we will move the cursor if not, we leave the cursor alone.
REQUIRES (Boolean to select or not), Valid editbuffer
RETURNS Nothing
NOTE: uses
    NavigateChunk( bSelect, LO_NA_DOCUMENT, FALSE ); 
    if we move the cursor.
and
    FE_ScrollDocTo (MWContext *context, int iLocation, int32 x,int32 y);
    if we do not
*/
void CEditBuffer::NavigateDocument(XP_Bool bSelect, XP_Bool bForward )
{
    if (!m_bEdtBufPrefInitialized)
        CEditBuffer::InitializePrefs();//static function to initialize the prefs
    if (m_bMoveCursor==TRUE)
        NavigateChunk( bSelect, LO_NA_DOCUMENT, bForward ); 
    else
    {
        int32 winHeight, winWidth;
        int32 topX, topY;

        if( m_inScroll ){
            return;
        }
        m_inScroll = TRUE;

    
        FE_GetDocAndWindowPosition( m_pContext, &topX, &topY, &winWidth, &winHeight );
        CEditLeafElement *t_leaf;
        ElementOffset t_offset; //offset is used to reveal the position of the caret and to call GetLOElementAndOffset
        XP_Bool t_stickyafter; //I honestly dont know what this is yet, but we use it for the same reasons as t_offset
        long t_maxwidth,t_maxheight; //used for the TOTAL width and height of the document.
        if (m_pCurrent)
        {
            t_leaf= m_pCurrent;
            t_offset=m_iCurrentOffset;
            t_stickyafter=m_bCurrentStickyAfter;
        }
        else
            GetInsertPoint( &t_leaf, &t_offset, &t_stickyafter );//gets the insert point.
        CL_Layer *layer = LO_GetLayerFromId(m_pContext, LO_DOCUMENT_LAYER_ID); 
        t_maxheight = LO_GetLayerScrollHeight(layer); 
        t_maxwidth = LO_GetLayerScrollWidth(layer);         
        if (bForward)
            FE_ScrollDocTo (m_pContext, t_offset, topX, t_maxheight);
        else
            FE_ScrollDocTo (m_pContext, t_offset, topX, 0);
        m_inScroll = FALSE;
    }
}


/* PageUpDown
this function will scroll the CURSOR to the proper location depending on bForward definition
we have 2 parts.  move the cursor and make the window catch up. IFF the preference tells us
*/
void CEditBuffer::PageUpDown(XP_Bool bSelect, XP_Bool bForward )
{
    if (!m_bEdtBufPrefInitialized)
        CEditBuffer::InitializePrefs();//static function to initialize the prefs

    LO_Element* pElement;
    int bCenterWindow; //0= no need, 1= bottom, -1 = top
    int iLayoutOffset;
    int32 winHeight, winWidth;
    int32 topX, topY;
    int32 iDesiredY;
    CEditLeafElement *t_leaf;
    ElementOffset t_offset; //offset is used to reveal the position of the caret and to call GetLOElementAndOffset
    XP_Bool t_stickyafter; //I honestly dont know what this is yet, but we use it for the same reasons as t_offset

    if( m_inScroll ){
        return;
    }
    m_inScroll = TRUE;

    
    FE_GetDocAndWindowPosition( m_pContext, &topX, &topY, &winWidth, &winHeight );

    if (m_pCurrent)
    {
        t_leaf= m_pCurrent;
        t_offset=m_iCurrentOffset;
        t_stickyafter=m_bCurrentStickyAfter;
    }
    else
        GetInsertPoint( &t_leaf, &t_offset, &t_stickyafter );//gets the insert point.

    t_leaf->GetLOElementAndOffset( t_offset, t_stickyafter, pElement, iLayoutOffset );

    if (m_bMoveCursor==TRUE)
    {
        ClearPhantomInsertPoint();
        ClearMove();    /* Arrow keys clear the up/down target position */
        BeginSelection();
        //is the cursor on the screen?? if not,  center scrollview on lead leaf element/cursor
        //if it is, just move the window +- one screen height
        if ( pElement->lo_any.y < topY ) 
          bCenterWindow = -1;//move window such that the TOP of the window shows the carret
        else if ((pElement->lo_any.y+pElement->lo_any.height)>(topY+winHeight))
        {
            bCenterWindow = 1;//move window such that the BOTTOM of the window shows the carret
        }
        else
          bCenterWindow = 0;
        // m_iDesiredX is where we would move if we could.  This keeps the
        //  cursor moving, basically, straight up and down, even if there is
        //  no text or gaps
        if( m_iDesiredX == -1 ){
          m_iDesiredX = pElement->lo_any.x +
                  (t_leaf->IsA(P_TEXT)
                      ? LO_TextElementWidth( m_pContext,
                              (LO_TextStruct*) pElement, iLayoutOffset)
                      : 0 );
        }

        if (bForward)
            iDesiredY = pElement->lo_any.y + winHeight;
        else
            iDesiredY = pElement->lo_any.y - winHeight;
        if (iDesiredY<0)
        {
            iDesiredY=0;
            m_iDesiredX=0;//x and y should go to beginnning
        }

        // what is location?? cant find it used. i will use t_offset???
        // FE_SetDocPosition(MWContext *context, int iLocation, int32 iX, int32 iY);
        if (bCenterWindow == -1)
            FE_SetDocPosition(m_pContext, t_offset, topX, iDesiredY);//keep x the same
        else if (bCenterWindow == 1)
        {
            int t_tempdesiredy=iDesiredY+pElement->lo_any.height-winHeight;
            if (t_tempdesiredy<0)
                t_tempdesiredy=0;
            FE_SetDocPosition(m_pContext, t_offset, topX, t_tempdesiredy);
        }
        else
        {
          if (bForward)
              FE_SetDocPosition(m_pContext, t_offset, topX, topY+winHeight);
          else
          {
              int t_tempdesiredy=topY-winHeight;
              if (t_tempdesiredy<0)
                  t_tempdesiredy=0;
              FE_SetDocPosition(m_pContext, t_offset, topX, t_tempdesiredy);
          }
        }

        if (bSelect)
        {
            ExtendSelection( m_iDesiredX, iDesiredY );
        }
        else
        {
    #ifdef LAYERS
            LO_PositionCaret( m_pContext, m_iDesiredX, iDesiredY, NULL );
    #else
            LO_PositionCaret( m_pContext, m_iDesiredX, iDesiredY );
    #endif
        }
        m_lastTopY = topY;
        m_inScroll = FALSE;//do not forget this!  this allows the screen to refresh properly.  used only to prevent recursion
        EndSelection();
        DoneTyping();
    }
    else
    {
        if (bForward)
            iDesiredY = topY + winHeight;
        else
            iDesiredY = topY - winHeight;
        if (iDesiredY<0)
        {
            iDesiredY=0;
        }
        m_inScroll = FALSE;//do not forget this!  this allows the screen to refresh properly.  used only to prevent recursion
        FE_SetDocPosition(m_pContext, t_offset, topX, iDesiredY);//keep x the same
    }
}


void CEditBuffer::WindowScrolled(){

    if( m_inScroll ){
        return;
    }
    m_inScroll = TRUE;
    if( !IsSelected() ){
        InternalSetCaret(FALSE);
    }

    m_inScroll = FALSE;

#if 0
//TODO: Ask mjudge if we need to keep this for anything

    // This code keeps the cursor on the screen, which is pretty weird behavior for
    // a text editor. That's not how any WYSIWYG editor works -- I'm guessing it's
    // some crufty key-based editor feature. -- jhp

    LO_Element* pElement;
    int iLayoutOffset;
    int32 winHeight, winWidth;
    int32 topX, topY;
    int32 iDesiredY;

    if( m_inScroll ){
        return;
    }
    m_inScroll = TRUE;


    FE_GetDocAndWindowPosition( m_pContext, &topX, &topY, &winWidth, &winHeight );


    // if there is a current selection, we have nothing to do.
    if( !IsSelected() ){

        m_pCurrent->GetLOElementAndOffset( m_iCurrentOffset, m_bCurrentStickyAfter,
                                                    pElement, iLayoutOffset );

        // m_iDesiredX is where we would move if we could.  This keeps the
        //  cursor moving, basically, straight up and down, even if there is
        //  no text or gaps
        if( m_iDesiredX == -1 ){
            m_iDesiredX = pElement->lo_any.x +
                    (m_pCurrent->IsA(P_TEXT)
                        ? LO_TextElementWidth( m_pContext,
                                (LO_TextStruct*) pElement, iLayoutOffset)
                        : 0 );
        }


        if( pElement->lo_any.y < topY ){
            iDesiredY = (pElement->lo_any.y - m_lastTopY) + topY;
            // caret is above the current window
#ifdef LAYERS
            LO_PositionCaret( m_pContext, m_iDesiredX, iDesiredY, NULL );
#else
            LO_PositionCaret( m_pContext, m_iDesiredX, iDesiredY );
#endif
        }
        else if( pElement->lo_any.y+pElement->lo_any.height > topY+winHeight ){
            // caret is below the current window
            iDesiredY = (pElement->lo_any.y - m_lastTopY) + topY;
#ifdef LAYERS
            LO_PositionCaret( m_pContext, m_iDesiredX, iDesiredY, NULL);
#else
            LO_PositionCaret( m_pContext, m_iDesiredX, iDesiredY);
#endif
        }
        else {
            SetCaret();
        }
    }
    m_lastTopY = topY;
    m_inScroll = FALSE;
   InternalSetCaret(FALSE);
#endif
}

void CEditBuffer::DebugPrintTree(CEditElement* pElement){
    const size_t kIndentBufSize = 256;
    char indent[kIndentBufSize];
    char *pData;
    char *pData1;
    PA_Tag *pTag;

    int indentSize = printState.m_iLevel*2;
    if ( indentSize >= kIndentBufSize ) {
        indentSize = kIndentBufSize - 1;
    }

    CEditElement *pNext = pElement->GetChild();
	int i;
    for(i = 0; i < indentSize; i++ ){
        indent[i] = ' ';
    }
    indent[i] = '\0';

    if( pElement->IsA(P_TEXT) ) {
        pData = pElement->Text()->GetText();
        pData1 = pElement->Text()->DebugFormat();
        pTag = 0;
    }
    else if( pElement->IsContainer() ){
        ED_Alignment alignment = pElement->Container()->GetAlignment();
        if ( alignment < LO_ALIGN_CENTER || alignment > ED_ALIGN_ABSTOP ) {
            pData = "Bad alignment.";
        }
        else {
            pData = lo_alignStrings[alignment];
        }
        pData1 = "";
        pTag = 0;
    }
    else {
        pData1 = "";
        pTag = pElement->TagOpen(0);
        if( pTag ){
            // no multiple tag fetches.
            //XP_ASSERT( pTag->next == 0 );
            PA_LOCK( pData, char*, pTag->data );
        }
        else {
            pData = 0;
        }
    }

    printState.m_pOut->Printf("\n");
    ElementIndex baseIndex = pElement->GetElementIndex();
    ElementIndex elementCount = pElement->GetPersistentCount();
    printState.m_pOut->Printf("0x%08x %6ld-%6ld", pElement, (long)baseIndex,
                (long)baseIndex + elementCount);
    printState.m_pOut->Printf("%s %s: %s%c",
            indent,
            EDT_TagString(pElement->GetType()),
            pData1,
            (pData ? '"': ' ') );
    if( pData ){
        printState.m_pOut->Write( pData, XP_STRLEN(pData));
        printState.m_pOut->Write( "\"", 1 );
    }

    if( pTag ){
        PA_UNLOCK( pTag->data );
        PA_FreeTag( pTag );
    }

    while( pNext ){
        printState.m_iLevel++;
        DebugPrintTree( pNext );
        printState.m_iLevel--;
        pNext = pNext->GetNextSibling();
    }
}

void CEditBuffer::CheckAndPrintComment(CEditLeafElement* pElement, CEditSelection& selection, XP_Bool bEnd){
    CEditInsertPoint ip(pElement, bEnd ? pElement->GetLen() : 0);
    CheckAndPrintComment2(ip, selection, FALSE);
    CheckAndPrintComment2(ip, selection, TRUE);
}

void CEditBuffer::CheckAndPrintComment2(const CEditInsertPoint& where, CEditSelection& selection, XP_Bool bEnd){
    CEditInsertPoint* pSelEdge = selection.GetEdge(bEnd);
    if (where.m_pElement == pSelEdge->m_pElement && where.m_iPos == pSelEdge->m_iPos){
        printState.PrintSelectionComment(bEnd, pSelEdge->m_bStickyAfter);
    }
}

void CEditBuffer::PrintTree( CEditElement* pElement ){
    CEditElement* pNext = pElement->GetChild();

    // Note the hack here: text elements are responsible for
    // printing their own selection comments because the selection
    // edge may fall inside the text element. We could make all
    // elements handle their own selection, but that would be tedious.

    XP_Bool bCheckForSelectionAsComment = printState.m_bEncodeSelectionAsComment
        && pElement->IsLeaf() && ! pElement->IsText();
    if ( bCheckForSelectionAsComment){
        CheckAndPrintComment(pElement->Leaf(), printState.m_selection, FALSE);
    }
    pElement->PrintOpen( &printState );

    while( pNext ){
        PrintTree( pNext );
        pNext = pNext->GetNextSibling();
    }
    pElement->PrintEnd( &printState );
    if ( bCheckForSelectionAsComment){
        CheckAndPrintComment(pElement->Leaf(), printState.m_selection, TRUE);
    }
}


void CEditBuffer::StreamToPositionalText( CEditElement* pElement, IStreamOut* pOut ){
    CEditElement* pNext = pElement->GetChild();

    pElement->StreamToPositionalText( pOut, FALSE );

    while( pNext ){
        StreamToPositionalText( pNext, pOut );
        pNext = pNext->GetNextSibling();
    }
    pElement->StreamToPositionalText( pOut, TRUE );
}


void CEditBuffer::AppendTitle( char *pTitle ){
    if( m_pTitle == 0 ){
        m_pTitle = XP_STRDUP( pTitle );
    }
    else {
        int32 titleLen = XP_STRLEN(m_pTitle)+XP_STRLEN(pTitle)+1;
        m_pTitle = (char*)XP_REALLOC( m_pTitle,
                 titleLen);
        strcat( m_pTitle, pTitle );
    }
}

void CEditBuffer::PrintMetaData( CPrintState *pPrintState ){
    // According to RFC 2070, print the charset http-equiv as soon as possible.
    int contentTypeID = FindMetaData(TRUE, CONTENT_TYPE);
    if ( contentTypeID >= 0 ) {
        PrintMetaData(pPrintState, contentTypeID);
    }
    for( int i = 0; i < m_metaData.Size(); i++ ){
        if ( i != contentTypeID ) {
            PrintMetaData(pPrintState, i);
        }
    }
}

void CEditBuffer::PrintMetaData( CPrintState *pPrintState, int index ){
    if ( index < 0 || index >= m_metaData.Size() ) {
        return;
    }
    EDT_MetaData *pData = m_metaData[index];
    if( pData != 0  ){
        // LTNOTE:
        // You can't merge these into a single printf.  edt_MakeParamString
        //  uses a single static buffer.
        //
        pPrintState->m_pOut->Printf( "   <META %s=%s ",
                (pData->bHttpEquiv ? "HTTP-EQUIV" : "NAME"),
                edt_MakeParamString( pData->pName ) );

        pPrintState->m_pOut->Printf( "CONTENT=%s>\n",
                edt_MakeParamString( pData->pContent ) );
    }
}

void CEditBuffer::PrintDocumentHead( CPrintState *pPrintState ){
    XP_Bool bPageComposer = ! IsComposeWindow();

    XP_Bool bHaveBaseTarget = m_pBaseTarget && *m_pBaseTarget;
    XP_Bool bHaveFontDefURL = m_pFontDefURL && *m_pFontDefURL;
    CParseState* pParseState = GetParseState();
    XP_Bool bHaveHeadTags = pParseState->m_pJavaScript != NULL;
    XP_Bool bNeedHead = bPageComposer
        || bHaveBaseTarget || bHaveFontDefURL
        || bHaveHeadTags;
    
    // Unfortunately, we don't conform to any standard DOCTYPE yet.
    // "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">\n"

    pPrintState->m_pOut->Printf("<HTML>\n");
    
    if ( bNeedHead ) {
        pPrintState->m_pOut->Printf("<HEAD>\n");

        if ( bPageComposer ) {
            // Print the MetaData first because it includes the charset, which can affect the way the
            // title is parsed. See RFC 2070.
            PrintMetaData( pPrintState );

            // Only print the title if it exists and is not empty.

            if ( m_pTitle && *m_pTitle ) {
                pPrintState->m_pOut->Printf("   <TITLE>");
                edt_PrintWithEscapes( pPrintState, m_pTitle, FALSE );
                pPrintState->m_pOut->Printf(
                              "</TITLE>\n");
            }
        }

        // Print BASE TARGET if nescessary
        if ( bHaveBaseTarget ) {
            pPrintState->m_pOut->Printf("   <BASE TARGET=%s>\n",
                edt_MakeParamString( m_pBaseTarget ));
        }

        // Print the font ref if nescessary
        if ( bHaveFontDefURL ){
            pPrintState->m_pOut->Printf("   <LINK REL=FONTDEF SRC=%s",
                edt_MakeParamString( m_pFontDefURL ));
            if ( m_bFontDefNoSave ) {
                pPrintState->m_pOut->Printf(" NOSAVE");
            }
            pPrintState->m_pOut->Printf(">\n");
        }
        if( bHaveHeadTags ){
            Write(pParseState->m_pJavaScript, pPrintState->m_pOut);
        }
        pPrintState->m_pOut->Printf("</HEAD>\n");
    }

    if ( IsBodyTagRequired() ) {
        // print the contents of the body tag.
        pPrintState->m_pOut->Printf("<BODY");
        if( m_colorText.IsDefined() ) {
            pPrintState->m_pOut->Printf(" TEXT=\"#%06lX\"", m_colorText.GetAsLong() );
        }
        if( m_colorBackground.IsDefined() ) {
            pPrintState->m_pOut->Printf(" BGCOLOR=\"#%06lX\"", m_colorBackground.GetAsLong() );
        }
        if( m_colorLink.IsDefined() ) {
            pPrintState->m_pOut->Printf(" LINK=\"#%06lX\"", m_colorLink.GetAsLong() );
        }
        if( m_colorFollowedLink.IsDefined() ) {
            pPrintState->m_pOut->Printf(" VLINK=\"#%06lX\"", m_colorFollowedLink.GetAsLong() );
        }
        if( m_colorActiveLink.IsDefined() ) {
            pPrintState->m_pOut->Printf(" ALINK=\"#%06lX\"", m_colorActiveLink.GetAsLong() );
        }
        if( m_pBackgroundImage && *m_pBackgroundImage) {
            pPrintState->m_pOut->Printf(" BACKGROUND=%s", edt_MakeParamString(m_pBackgroundImage) );
        }
        if ( m_bBackgroundNoSave ) {
            pPrintState->m_pOut->Printf(" NOSAVE");
        }

        if( m_pBodyExtra && *m_pBodyExtra ) {
            pPrintState->m_pOut->Printf(" %s", m_pBodyExtra );
        }

        pPrintState->m_pOut->Printf(">\n");
    }
}

XP_Bool CEditBuffer::IsBodyTagRequired(){
    return (! IsComposeWindow() )
        || m_colorText.IsDefined() 
        || m_colorBackground.IsDefined()
        || m_colorLink.IsDefined()
        || m_colorFollowedLink.IsDefined() 
        || m_colorActiveLink.IsDefined()
        || ( m_pBackgroundImage && *m_pBackgroundImage ) 
        || ( m_pBodyExtra && *m_pBodyExtra );
}

void CEditBuffer::PrintDocumentEnd( CPrintState *pPrintState ){
    if ( IsBodyTagRequired() ){
        pPrintState->m_pOut->Printf( "\n"
                "</BODY>\n");
    }
    if( GetParseState()->m_pPostBody ){
        Write(GetParseState()->m_pPostBody, pPrintState->m_pOut);
    }
    pPrintState->m_pOut->Printf("</HTML>\n");
}

void CEditBuffer::Write(CStreamOutMemory *pSource, IStreamOut* pDest){
    // Copies entire contents of pSource to pDest.
    int32 iLen = pSource->GetLen();
    XP_HUGE_CHAR_PTR pScript = pSource->GetText();
    while( iLen ){
        int16 iToWrite = (int16) min( iLen, 0x4000 );
        pDest->Write( pScript, iToWrite );
        iLen -= iToWrite;
        pScript += iToWrite;
    }
}


class CEditSaveData {
public:
  CEditSaveData() {ppIncludedFiles = NULL;pSourceURL = NULL;pSaveToTempData = NULL;}
  ~CEditSaveData(); 

// Don't save CEditBuffer because it might have changed from undo/redo logic
  MWContext *pContext; 

  ED_SaveFinishedOption finishedOpt;
  char* pSourceURL;
  ITapeFileSystem *tapeFS;
  XP_Bool bSaveAs;
  XP_Bool bKeepImagesWithDoc;
  XP_Bool bAutoAdjustLinks;
  XP_Bool bAutoSave;
  char **ppIncludedFiles;
  CEditSaveToTempData *pSaveToTempData;
};

CEditSaveData::~CEditSaveData() {
  XP_FREEIF(pSourceURL);
  
  // In the success case, ppIncludedFiles and pSaveToTempData will
  // have been set to NULL in CEditBuffer::SaveFileReal(), so they don't
  // get freed here.
  CEditSaveObject::FreeList(ppIncludedFiles);
  if (pSaveToTempData) {
    // failure.
    (*pSaveToTempData->doneFn)(NULL,pSaveToTempData->hook);
    delete pSaveToTempData;
  }
}

// Callback from plugins to start saving process.
PRIVATE void edt_SaveFilePluginCB(EDT_ImageEncoderStatus status, void* pArg) {
  CEditSaveData *pData = (CEditSaveData *)pArg;
  XP_ASSERT(pData);
  XP_Bool bFileSaved = FALSE;

  if (status != ED_IMAGE_ENCODER_USER_CANCELED) {
    // OK or exception.  Note that we still do the save if an exception.

    CEditBuffer *pBuffer = LO_GetEDBuffer(pData->pContext);
    if (pBuffer) {
      pBuffer->SaveFileReal(pData);
      bFileSaved = TRUE;
      // ppIncludedFiles, and tapeFS will be deleted by CEditBuffer::SaveFileReal().
    }
    else {
      XP_ASSERT(0);
    }
  }
  else {
    // user canceled.
    pData->tapeFS->Complete( FALSE, NULL, NULL ); // Tell tapeFS to kill itself.
  }
  if( !bFileSaved )
  {
      //cmanske EXPERIMENTAL - This was set at start of file save
      // and must be cleared now if we really didn't do the save
      pData->pContext->edit_saving_url = FALSE;
  }
  
  delete pData;
}

// Setup to save file
// !!!!! All of the saving functions ultimately go through this one !!!!!
ED_FileError CEditBuffer::SaveFile( ED_SaveFinishedOption finishedOpt,
                                    char* pSourceURL,
                                    ITapeFileSystem *tapeFS,
                                    XP_Bool bSaveAs,
                                    XP_Bool bKeepImagesWithDoc,
                                    XP_Bool bAutoAdjustLinks,
                                    XP_Bool bAutoSave,
                                    char **ppIncludedFiles,
                                    CEditSaveToTempData *pSaveToTempData){
  // Create argument for asynchoronous call to plugin code.
  CEditSaveData *pData = new CEditSaveData;
  if (!pData) {
    XP_ASSERT(0);
    tapeFS->Complete( FALSE, NULL, NULL ); // Tell tapeFS to kill itself.
    CEditSaveObject::FreeList(ppIncludedFiles);
    if (pSaveToTempData) {
      // failure.
      (*pSaveToTempData->doneFn)(NULL,pSaveToTempData->hook);
      delete pSaveToTempData;
    }
    return ED_ERROR_BLOCKED;
  }
  // fill pData.
  pData->pContext = m_pContext;
  pData->finishedOpt = finishedOpt;
  pData->pSourceURL = XP_STRDUP(pSourceURL);
  pData->tapeFS = tapeFS;
  pData->bSaveAs = bSaveAs;
  pData->bKeepImagesWithDoc = bKeepImagesWithDoc;
  pData->bAutoAdjustLinks = bAutoAdjustLinks;
  pData->bAutoSave = bAutoSave;
  pData->ppIncludedFiles = ppIncludedFiles;
  pData->pSaveToTempData = pSaveToTempData;

  //cmanske - This is also set in constructor of CFileSave,
  //  but we must set it earlier because of asynchronous plugin --
  //  the front ends should stay in a wait loop while this is TRUE 
  //  so they do not continue until saving is completed
  m_pContext->edit_saving_url = TRUE;

  if (pSaveToTempData) {
    // Don't send event when saving temp file.
    // Call callback directly.
    edt_SaveFilePluginCB(ED_IMAGE_ENCODER_OK,(void *)pData);
  }
  else {
    // Build event name to pass to plugin code.
    char *pDestURL = tapeFS->GetDestAbsURL();
    switch (tapeFS->GetType()) {
      case ITapeFileSystem::File:
        if (bAutoSave) {
          EDT_PerformEvent(m_pContext,"autosave",pDestURL,TRUE,TRUE,
                           edt_SaveFilePluginCB,(void *)pData);    
        }
        else {
          EDT_PerformEvent(m_pContext,"save",pDestURL,TRUE,TRUE,
                           edt_SaveFilePluginCB,(void *)pData);    
        }
        break;
      case ITapeFileSystem::Publish:
        EDT_PerformEvent(m_pContext,"publish",pDestURL,TRUE,TRUE,
                         edt_SaveFilePluginCB,(void *)pData);     
        break;
      case ITapeFileSystem::MailSend:
        EDT_PerformEvent(m_pContext,"send",NULL,TRUE,TRUE,
                         edt_SaveFilePluginCB,(void *)pData);     
        // Don't give a URL.
        break;
      default:
        XP_ASSERT(0);
    }
    XP_FREEIF(pDestURL);
  }

  // I'm sick of loosing prefs, so save them as part of 
  //  file saving, since that will take some time anyway
  PREF_SavePrefFile();
  
  return ED_ERROR_NONE;
}


void CEditBuffer::SaveFileReal(CEditSaveData *pData) {
    if (pData->pSaveToTempData) {
      // Don't want to edit temp file after call to EDT_SaveToTempFile().
      pData->finishedOpt = ED_FINISHED_REVERT_BUFFER;
    }
    else {
      // We are going to ignore the finishedOpt flag and do the right thing
      // in the back end for saving and publishing.  Not for mailing.
      if (pData->tapeFS->GetType() == ITapeFileSystem::File ||
          pData->tapeFS->GetType() == ITapeFileSystem::Publish) {
        if (EDT_IS_NEW_DOCUMENT(m_pContext)) {
          // Source is a new document.
          pData->finishedOpt = ED_FINISHED_GOTO_NEW;
        }
        else {
          if (NET_URL_Type(pData->pSourceURL) != FILE_TYPE_URL) {
            // Source is a remote doc.
            pData->finishedOpt = ED_FINISHED_GOTO_NEW;
          }
          else {
            if (pData->tapeFS->GetType() == ITapeFileSystem::File) {
              // Source is a local file, dest is local file.
              pData->finishedOpt = ED_FINISHED_GOTO_NEW;
            }
            else {  
              XP_ASSERT(pData->tapeFS->GetType() == ITapeFileSystem::Publish);
              // Source is a local file, dest is remote file.
              pData->finishedOpt = ED_FINISHED_REVERT_BUFFER;
            }
          }
        }
      }
    }

    DoneTyping();
    CEditSaveObject *pSaveObject;
    if( m_pSaveObject != 0 ){
        // This could happen if user is in the middle of saving a file
        //  (such as at an overwrite or error dialog when inserting image)
        //  and then AutoSave triggers saving the file. So don't ASSERT
        // XP_ASSERT(0);           // shouldn't be returning a value...
        
        pData->tapeFS->Complete( FALSE, NULL, NULL ); // Tell tapeFS to kill itself.
        return;
    }
    // Set write time to 0 so we don't trigger reload before file is saved
    // (doesn't completely solve this problem)
    if( pData->bSaveAs) {
        m_iFileWriteTime = 0;
    }

    // Stamp the document character set into the document.
    SetEncoding(GetDocCharSetID());

    // tapeFS freed by CEditSaveObject (actually CFileSaveObject::~CFileSaveObject).
    m_pSaveObject = 
         pSaveObject = new CEditSaveObject( this, pData->finishedOpt, pData->pSourceURL, pData->tapeFS, 
                   pData->bSaveAs, pData->bKeepImagesWithDoc, pData->bAutoAdjustLinks, pData->bAutoSave,
                   pData->pSaveToTempData);
    pData->pSaveToTempData = NULL; // memory is now the responsibility of CEditSaveObject.

    pSaveObject->AddAllFiles(pData->ppIncludedFiles);
    pData->ppIncludedFiles = NULL;  // freed in AddAllFiles().
        
    // This may save 1 or more files and delete itself before
    //  returning here. Error status (m_status) is set to pSaveObject->m_status
    //  via CEditBuffer::FileFetchComplete(m_status)
    m_status = ED_ERROR_NONE;
    pSaveObject->SaveFiles();
}

int16 CEditBuffer::GetRAMCharSetID() {
    return m_originalWinCSID & (~CS_AUTO);
}

int16 CEditBuffer::GetDocCharSetID() {
    // Copied from layform.c
	INTL_CharSetInfo csi = LO_GetDocumentCharacterSetInfo(m_pContext);
    int16 doc_csid = INTL_GetCSIDocCSID(csi);
    if ( m_bForceDocCSID ) {
        // Used when changing a document's character set.
        doc_csid = m_forceDocCSID;
    }
    else if (doc_csid == CS_DEFAULT)
		doc_csid = INTL_DefaultDocCharSetID(m_pContext);
	doc_csid &= (~CS_AUTO);
    return doc_csid;
}

void CEditBuffer::ForceDocCharSetID(int16 csid){
    m_bForceDocCSID = TRUE;
    m_forceDocCSID = csid;
}

ED_FileError CEditBuffer::PublishFile( ED_SaveFinishedOption finishedOpt,
                           char *pSourceURL,
                           char **ppIncludedFiles,
                           char *pDestURL, /* Directories must have trailing slash, ie after HEAD call */
                           char *pUsername,
                           char *pPassword,
                           XP_Bool   bKeepImagesWithDoc,
                           XP_Bool   bAutoAdjustLinks ) {
    char *pDocTempDir = GetCommandLog()->GetDocTempDir();
    if (!pDocTempDir) {
      char *tmplate = XP_GetString(XP_EDT_ERR_PUBLISH_PREPARING_ROOT);
      if (tmplate) {
        char *msg = PR_smprintf(tmplate,pSourceURL);
        if (msg) {
          FE_Alert(m_pContext,msg);
          XP_FREE(msg);
        }
      }
      return ED_ERROR_FILE_OPEN;
    }

    // Create Abstract file system to write to remote server.
    ITapeFileSystem *tapeFS = 
      new CTapeFSPublish(m_pContext,pDestURL,pUsername,pPassword,pDocTempDir);
    XP_ASSERT(tapeFS);
    // tapeFS freed by CEditSaveObject.

    // saveAs set to TRUE, why not?
    return SaveFile( finishedOpt, pSourceURL, tapeFS, TRUE,
                     bKeepImagesWithDoc, bAutoAdjustLinks, 
                     FALSE,  // not auto-saving
                     ppIncludedFiles ); // explicitly specify which files to send with doc.
}

void CEditBuffer::InitEscapes(){
    // We escape character in the RAM character set, but we decide to escape depending upon the
    // document character set. So, for example if we're writing to SJIS, we don't escape,
    // even if we're originally in LATIN-1

    // Our strategy is:
    // For MacRoman or ISO_Latin_1, use entites for hi-bit characters.
    // For other character sets, minimize the number of entities we put out.
    // The minimum is:
    // &amp; for ampersand
    // &lt; for less-than

    int16 win_csid = GetRAMCharSetID();
    int16 doc_csid = GetDocCharSetID();

	XP_Bool bWinSimple = 
        win_csid == CS_MAC_ROMAN
        || win_csid == CS_LATIN1
        || win_csid == CS_ASCII;

	XP_Bool bDocSimple = 
        doc_csid == CS_MAC_ROMAN
        || doc_csid == CS_LATIN1
        || doc_csid == CS_ASCII;

    // Bug to fix after 4.0B3 -- can't quote if original csid is not 8-bit either.
    // The fix is to quote after transcoding, rather than before transcoding.
    XP_Bool bQuoteHiBits = bWinSimple && bDocSimple;

    edt_InitEscapes(win_csid, bQuoteHiBits);
}

// CM: Return 0 if OK, or -1 if device is full.
int CEditBuffer::WriteToFile( XP_File hFile ){ //char *pFileName ){
    int iResult = 0;
    CStreamOutFile *pOutFile;
    pOutFile = new CStreamOutFile(hFile, FALSE);
    CConvertCSIDStreamOut* pConverter = new CConvertCSIDStreamOut( GetRAMCharSetID(), GetDocCharSetID(), pOutFile);
    InitEscapes();
    printState.Reset( pConverter, this );
    Reduce( m_pRoot );
    PrintTree( m_pRoot );
    printState.Reset( 0, this );
    if( pOutFile->Status() != IStreamOut::EOS_NoError ){
        iResult = -1;
    }
    else {
        iResult = 0;
    }
    delete pConverter;
    // To Jack: this is a problem
    // When saving to a new file, this does nothing
    // 7/2/96
    //   edit_saving_url flag is set/cleared in CEditSaveObject
    //   and GetFileWriteTime() is done in that object's destructor
    // GetFileWriteTime();

    ////m_autoSaveTimer.Restart();  hardts. Moved to CEditBuffer::FileFetchComplete
    return iResult;
}

CEditDocState *CEditBuffer::RecordState() {
  CEditDocState *ret = new CEditDocState();
  if (!ret) {
    XP_ASSERT(0);
    return NULL;
  }
  
  WriteToBuffer(&ret->m_pBuffer,TRUE);
  ret->m_version = GetCommandLog()->GetVersion();
  return ret;
}

int32 CEditBuffer::WriteToBuffer( XP_HUGE_CHAR_PTR* ppBuffer, XP_Bool bEncodeSelectionAsComment ){
    CStreamOutMemory *pOutMemory = new CStreamOutMemory();
    CConvertCSIDStreamOut* pConverter = new CConvertCSIDStreamOut( GetRAMCharSetID(), GetDocCharSetID(), pOutMemory);

    InitEscapes();
    printState.Reset( pConverter, this );
    Reduce( m_pRoot );
    if ( m_bLayoutBackpointersDirty ) {
        bEncodeSelectionAsComment = FALSE;
    }
    printState.m_bEncodeSelectionAsComment = bEncodeSelectionAsComment;
    if ( printState.m_bEncodeSelectionAsComment ){
        GetSelection(printState.m_selection);
    }
    PrintTree( m_pRoot );

    printState.Reset( 0, this );
    pConverter->ForgetStream();
    delete pConverter;

    *ppBuffer = pOutMemory->GetText();
    int32 result = pOutMemory->GetLen();
#ifdef DEBUG_WRITETOBUFFER
    // Use XP_TRACE to print out the document one line at a time.
    // XP_TRACE has an internal limit of around 512 characters on
    // the ammount of characters it can print in one call.
    char* pBuffer = *ppBuffer;
    int32 bufLen = result;
    int32 lineStart = 0;
    const int MAXLINELENGTH = 130;
    for(int32 i = 0; i < bufLen; i++){
        char c = pBuffer[i];
        if ( c == '\n' || c == '\r' || i - lineStart > MAXLINELENGTH ) {
            pBuffer[i] = '\0';
            XP_TRACE(("%s", pBuffer + lineStart));
            pBuffer[i] = c;
            lineStart = i;
            if ( c == '\n' || c == '\r' ){
                lineStart++;
                if ( c == '\r' && pBuffer[lineStart] == '\n') {
                    i++;
                    lineStart++;
                }
            }
        }
    }
#endif
    delete pOutMemory;
    return result;
}

XP_HUGE_CHAR_PTR CEditBuffer::GetPositionalText( ){
    CStreamOutMemory *pOutMemory = new CStreamOutMemory();
    INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_pContext);
    Reduce( m_pRoot );
    StreamToPositionalText( m_pRoot, pOutMemory );
    XP_HUGE_CHAR_PTR pRet = pOutMemory->GetText();

    // If the buffer is too large, display alert message and return NULL.
    // In win16 edt_RemoveNBSP() would loop for larger buffers.
    if (pOutMemory->GetLen() > MAX_POSITIONAL_TEXT) {
        char* tmplate = XP_GetString(XP_EDT_MSG_TEXT_BUFFER_TOO_LARGE);
        if (tmplate) {
            char* msg = PR_smprintf(tmplate, pOutMemory->GetLen(), MAX_POSITIONAL_TEXT);
            if (msg) {
                FE_Alert(m_pContext, msg);
                XP_FREE(msg);
            }
        }
        else
            XP_ASSERT(FALSE);

        XP_HUGE_FREE(pRet);
        pRet = NULL;
    }
    else
        edt_RemoveNBSP( INTL_GetCSIWinCSID(c), (char *)pRet );

    delete pOutMemory;
    return pRet;
}


void CEditBuffer::RestoreState(CEditDocState *pState) {
  if (!pState) return;

  // Maybe we should check that ReadFromBuffer() succeeded before setting the
  // version.
  GetCommandLog()->SetVersion(pState->m_version);
  ReadFromBuffer(pState->m_pBuffer);
}


void CEditBuffer::ReadFromBuffer(XP_HUGE_CHAR_PTR pBuffer){
    /* This starts an asynchronous process that causes the current buffer to be deleted,
     * and then a new CEditBuffer instance to be created for the same MWContext.
     */
    CGlobalHistoryGroup::GetGlobalHistoryGroup()->IgnoreNextDeleteOf(this);
    GetCommandLog()->SetInReload(TRUE);

    char *pDocURL = LO_GetBaseURL(m_pContext);
    URL_Struct *url_s = NET_CreateURLStruct(pDocURL, NET_DONT_RELOAD);

    if(!url_s){
        XP_TRACE(("Couldn't create the url struct."));
        return;
    }

    StrAllocCopy(url_s->content_type, TEXT_HTML);
    NET_StreamClass *stream =
        NET_StreamBuilder(FO_PRESENT, url_s, m_pContext);
    if(stream) {
        delete m_pRoot;
        m_pRoot = 0;
        m_pCurrent = 0;
        delete this; // See ya!

        // XP_STRCPY(buffer, XP_GetString(XP_HTML_MISSING_REPLYDATA));
        int32 length = XP_STRLEN(pBuffer);
        if ( length <= 0 ) {
            pBuffer = EDT_GetEmptyDocumentString();
            length = XP_STRLEN(pBuffer);
        }

        // Output buffer in smaller chunks. put_clock does not take XP_HUGE_CHAR_PTR.
        const int CHUNK_SIZE = 32000;
        char *pChunk = (char *)XP_ALLOC(CHUNK_SIZE);
        if (pChunk != NULL) {
            XP_HUGE_CHAR_PTR pSrc = pBuffer;
            char *pDest = pChunk;
            int count = 0;
            for (;;) {
                if ((*pDest++ = *pSrc++) == 0) {
                    (*stream->put_block)(stream, pChunk, count);
                    break;
                }                    
                else if (++count == CHUNK_SIZE) {
                    (*stream->put_block)(stream, pChunk, count);
                    pDest = pChunk;
                    count = 0;
                }
            }
        }
        else
            XP_ASSERT(FALSE);

        (*stream->complete)(stream);
        XP_FREE(stream);
        XP_FREEIF(pChunk);
    }
    NET_FreeURLStruct(url_s);
}

void CEditBuffer::WriteToStream( IStreamOut *pOut ) {
    InitEscapes();
    CConvertCSIDStreamOut converter( GetRAMCharSetID(), GetDocCharSetID(), pOut);

    printState.Reset( &converter, this );
    Reduce( m_pRoot );
    PrintTree( m_pRoot );
    printState.Reset( 0, this );
    converter.ForgetStream();
}

#ifdef XP_WIN16
// code segment is full, switch to a new segment
#pragma code_seg("EDTBUF2_TEXT","CODE")
#endif

#ifdef DEBUG
void CEditBuffer::DebugPrintState(IStreamOut& stream) {
    {
        // Print the document as HTML
        XP_HUGE_CHAR_PTR pData;
        WriteToBuffer(&pData, TRUE);
        XP_HUGE_CHAR_PTR b = pData;
        int lineLimit = 200;
        char* pLine = (char*) XP_ALLOC(lineLimit);
        while ( *b != '\0' ) {
            int i = 0;
            while ( *b != '\0' && *b != '\n'){
                if ( i < lineLimit-1) {
                    pLine[i++] = *b;
                }
                b++;
            }
            pLine[i] = '\0';
            stream.Printf("%s\n", pLine);
            if ( *b == '\0') break;
            b++;
        }
        XP_FREE(pLine);
        XP_HUGE_FREE(pData);
    }
    {
        CEditSelection selection;
        GetSelection(selection);
        stream.Printf("\nCurrent selection: ");
        selection.Print(stream);
        stream.Printf("\n");
    }
    {
        CPersistentEditSelection selection;
        GetSelection(selection);
        stream.Printf("\nCurrent selection: ");
        selection.Print(stream);
        stream.Printf("\n");
    }
    if ( IsPhantomInsertPoint() ) {
        stream.Printf("   IsPhantomInsertPoint() = TRUE\n");
    }
    stream.Printf("Current typing command: ");
    stream.Printf("%s ", m_bTyping ? "true" : "false");
    stream.Printf("\n");
    stream.Printf("Document version: %d. Stored version %d.", GetCommandLog()->GetVersion(),
        GetCommandLog()->GetStoredVersion());
    stream.Printf("\n");
    GetCommandLog()->Print(stream);
    {
        stream.Printf("\n\nFiles asociated with the current Buffer:");
        char*p = GetAllDocumentFiles(NULL,TRUE);
        char *p1 = p;
        while( p && *p ){
            stream.Printf("\n  %s", p );
            p += XP_STRLEN( p );
        }
        if( p1) XP_FREE( p1 );
    }

    {
        stream.Printf("\n\nNamed Anchors in the current Buffer:");
        char*p = GetAllDocumentTargets();
        char *p1 = p;
        while( p && *p ){
            stream.Printf("\n  %s", p );
            p += XP_STRLEN( p );
        }
        if( p1 ) XP_FREE( p1 );
    }
    // lo_PrintLayout(m_pContext);
}
#endif

#ifdef DEBUG
void CEditBuffer::ValidateTree(){
    if ( m_bSkipValidation ) return;
    // Have the tree validate itself.
    m_pRoot->ValidateTree();
    if ( ! m_iSuppressPhantomInsertPointCheck ) {
        // Check that there are no phantom insert points other than at m_pCurrent
        CEditLeafElement* pElement;
        for ( pElement = m_pRoot->GetFirstMostChild()->Leaf();
            pElement;
            pElement = pElement->NextLeafAll() ){
            CEditInsertPoint ip(pElement, 0);
            if ( IsPhantomInsertPoint(ip) ){
                if ( pElement != m_pCurrent ) {
                    XP_ASSERT(FALSE);
                }
            }
        }
    }
}

void CEditBuffer::SuppressPhantomInsertPointCheck(XP_Bool bSuppress){
    if ( bSuppress )
        m_iSuppressPhantomInsertPointCheck++;
    else
        m_iSuppressPhantomInsertPointCheck--;
}
#endif

void CEditBuffer::DisplaySource( ){
    CStreamOutNet *pOutNet = new CStreamOutNet( m_pContext );
    CConvertCSIDStreamOut* pConverter = new CConvertCSIDStreamOut( GetRAMCharSetID(), GetDocCharSetID(), pOutNet);
    InitEscapes();
    printState.Reset( pConverter, this );
    PrintTree( m_pRoot );
#ifdef DEBUG
    DebugPrintTree( m_pRoot );
    DebugPrintState(*pConverter);
#endif
    printState.Reset( 0, this );
    delete pConverter;
}


//mjudge, this method will leave spaces instead of "\n"s unfortunately, 
//they wont go away.  I will look for another place to remove them nicely....
char* CEditBuffer::NormalizeText( char* pSrc ){
    char *pStr = pSrc;
    char *pDest;
    char lastChar = 1;
    pDest = pStr;

    if( GetParseState()->bLastWasSpace ){
        while( *pStr != '\0' && XP_IS_SPACE( *pStr ) ){
            lastChar = ' ';
            pStr++;
        }
    }

    //
    // Loop through convert all white space to a single white space.
    // leave \r\n and \n and \r and convert them to \n's to later parse out in edtele.cpp finishload
    while( *pStr != '\0'){

        if (  ( !*(pStr+1) && (*pStr== '\n' || *pStr== '\r') ) ||
                ( *(pStr+1) && !*(pStr+2) && *pStr== '\r' && *pStr== '\n' )  ){
            lastChar = *pDest++= '\n';
            break;//out of while loop
            }
        else
            if( XP_IS_SPACE( *pStr ) ){
                lastChar = *pDest++ = ' ';
                while( *pStr != '\0' && XP_IS_SPACE( *pStr )){
                    pStr++;
                }
        }
        else {
            lastChar = *pDest++ = *pStr++;
        }
    }
    *pDest = '\0';

    // if we actually coppied something, check the last character. happens when there is ANY text
    if( pSrc != pDest ){
        GetParseState()->bLastWasSpace = XP_IS_SPACE( lastChar );
    }
    return pSrc;
}



void CEditBuffer::RepairAndSet(CEditSelection& selection)
{
    // force layout stop displaying the current selection.
#ifdef LAYERS
    LO_StartSelectionFromElement( m_pContext, 0, 0, NULL );
#else
    LO_StartSelectionFromElement( m_pContext, 0, 0);
#endif
    CPersistentEditSelection persel = EphemeralToPersistent(selection);
    Reduce(selection.GetCommonAncestor());
    selection = PersistentToEphemeral(persel);
    Relayout( selection.m_start.m_pElement, 0, selection.m_end.m_pElement, RELAYOUT_NOCARET );
    // Need to force selection.
    SetSelection(selection);
}

XP_Bool CEditBuffer::Reduce( CEditElement *pRoot ){
    // work backwards so when children go away, we don't have
    //  lossage.
    CEditElement *pChild, *pNext;
    pChild = pRoot->GetChild();

    while( pChild ) {
        pNext = pChild->GetNextSibling();
        if( Reduce( pChild )){
            pChild->Unlink();       // may already be unlinked
            delete pChild;
        }
        pChild = pNext;
    }
    return pRoot->Reduce( this );
}

//
//
//
void CEditBuffer::NormalizeTree(){
    CEditElement* pElement;

    // Make sure all text is in a container
    pElement = m_pRoot;
    while( (pElement = pElement->FindNextElement( &CEditElement::FindTextAll,0 ))
            != 0 ) {
        // this block of text does not have a container.
        if( pElement->FindContainer() == 0 ){
        }
    }

}

// Finish load timer.

CFinishLoadTimer::CFinishLoadTimer(){
}

void CFinishLoadTimer::OnCallback() {
    m_pBuffer->FinishedLoad2();
}

void CFinishLoadTimer::FinishedLoad(CEditBuffer* pBuffer)
{
    m_pBuffer = pBuffer;
	const uint32 kRelayoutDelay = 1;
    SetTimeout(kRelayoutDelay);
}

void CEditBuffer::FinishedLoad(){
    // To avoid race conditions, set up a timer.
    // An example of a race condition is when the
    // save dialog box goes up when the load is
    // finished, but the image
    // attributes haven't been set yet.
    m_finishLoadTimer.FinishedLoad(this);
}


#ifdef DEBUG_ltabb
int bNoAdjust = 0;
#endif


// Callback from plugins to start saving process.
PRIVATE void edt_OpenDoneCB(EDT_ImageEncoderStatus /*status*/, void* pArg) {
    MWContext *pContext = (MWContext *)pArg;
    // Notify front end that user interaction can resume
    FE_EditorDocumentLoaded( pContext );

    // Make current doc the most-recently-edited in prefs history list
    EDT_SyncEditHistory( pContext );
}

// Relayout the whole document.
void CEditBuffer::FinishedLoad2(){

    // In case this is a reload, restore the state of the wait flag.
    if ( EDT_IsPluginActive(m_pContext) ) {
        m_pContext->waitingMode = 1;
    }

    // Are there any extra levels on the Parse state?
    while ( m_parseStateStack.StackSize() > 1 ) {
        if ( GetParseState()->m_inJavaScript ) {
            WriteClosingScriptTag();
        }
        else {
            // XP_ASSERT(FALSE); // How did this happen?
        }
        PopParseState();
    }
    // And make sure the head has been finished.
    WriteClosingScriptTag();

    m_pCreationCursor = NULL;

    // Get rid of empty items.
    Reduce( m_pRoot );

    // Give everyone a chance to clean themselves up

    m_pRoot->FinishedLoad(this);

#ifdef EDT_DDT
#ifdef DEBUG_ltabb
    if( ! bNoAdjust ){
        m_pRoot->AdjustContainers(this);
    }
#else
    m_pRoot->AdjustContainers(this);
#endif
    // The AdjustContainer process may have produced empty paragraphs.
    // Calling FinishedLoad a second time cleans these up.
    m_pRoot->FinishedLoad(this);
#endif

#ifdef DEBUG
    m_pRoot->ValidateTree();
#endif

    // May have cleared out some more items.
    Reduce( m_pRoot );

#ifdef DEBUG
    m_pRoot->ValidateTree();
#endif

    // Temporarily set a legal selection.
    m_pCurrent = m_pRoot->NextLeafAll();
    while ( m_pCurrent && m_pCurrent->GetElementType() == eInternalAnchorElement ) {
        m_pCurrent = m_pCurrent->NextLeaf();
    }
    m_iCurrentOffset = 0;

    XP_Bool bShouldSendOpenEvent = !GetCommandLog()->InReload();

    // Set page properties (color, background, etc.)
    //  for a new document:
    XP_Bool bIsNewDocument = EDT_IS_NEW_DOCUMENT(m_pContext)
                                    && !GetCommandLog()->InReload();

    if( bIsNewDocument ){
        // This will call EDT_SetPageData(),
        // which calls RefreshLayout()
        FE_SetNewDocumentProperties(m_pContext);
        m_bDummyCharacterAddedDuringLoad = TRUE; /* Sometimes it's a no-break space. */
        // Get rid of extra characters.
    }
    RefreshLayout();

    // Add GENERATOR meta-data. This tells us who most recently edited the doucment.
    {
 	    char generatorValue[300];
	    XP_SPRINTF(generatorValue, "%.90s/%.100s [%.100s]", XP_AppCodeName, XP_AppVersion, XP_AppName);

        EDT_MetaData *pData = MakeMetaData( FALSE, "GENERATOR", generatorValue);

        SetMetaData( pData );
        FreeMetaData( pData );
    }

    SetSelectionInNewDocument();

    m_bBlocked = FALSE;
	m_bReady = TRUE;

    if ( GetCommandLog()->InReload() ){
        GetCommandLog()->SetInReload(FALSE);
    }
    else {
        DocumentStored();
        GetCommandLog()->Trim();
    }

#ifdef DEBUG
    m_pRoot->ValidateTree();
#endif

    // Initialize correct witdth data for all tables and cells in document
    FixupTableData();
    
    if ( bShouldSendOpenEvent ) {
		History_entry * hist_entry = SHIST_GetCurrent(&(m_pContext->hist));
        char* pURL = hist_entry ? hist_entry->address : 0;
        EDT_PerformEvent(m_pContext, "open", pURL, TRUE, FALSE, edt_OpenDoneCB, m_pContext);
    }
    else {
        // Notify front end that user interaction can resume
        FE_EditorDocumentLoaded( m_pContext );

        // Make current doc the most-recently-edited in prefs history list
        EDT_SyncEditHistory( m_pContext );
    }
}

void CEditBuffer::SetSelectionInNewDocument(){
    VALIDATE_TREE(this);
    if ( ! m_pRoot )
        return;

    CEditInternalAnchorElement* pBeginSelection = m_pStartSelectionAnchor;
    CEditInternalAnchorElement* pEndSelection = m_pEndSelectionAnchor;
    XP_Bool bBeginStickyAfter = m_bStartSelectionStickyAfter;
    XP_Bool bEndStickyAfter = m_bEndSelectionStickyAfter;

    // At this point there is at most one selection comment of each type in
    // the document.

    // Error recovery - if we have either a begin selection or an end selection,
    // use it for the other edge.
    if ( pBeginSelection && ! pEndSelection){
        pEndSelection = pBeginSelection;
        bEndStickyAfter = bBeginStickyAfter;
    }
    else if ( ! pBeginSelection && pEndSelection){
        pBeginSelection = pEndSelection;
        bBeginStickyAfter = bEndStickyAfter;
    }

    if ( pBeginSelection && pEndSelection ){
        // We have an existing selection.
        // Move from the selection markers to the nearby leaf.
        CEditLeafElement* pB = (CEditLeafElement*) pBeginSelection->NextLeafAll(bBeginStickyAfter);
        if ( pB == NULL ){
            // At the edge of the document
            bBeginStickyAfter = ! bBeginStickyAfter;
            pB = (CEditLeafElement*) pBeginSelection->NextLeafAll(bBeginStickyAfter);
        }
        CEditLeafElement* pE = (CEditLeafElement*) pEndSelection->NextLeafAll(bEndStickyAfter);
        if ( pE == NULL ){
            // At the edge of the document
            bEndStickyAfter = ! bEndStickyAfter;
            pE = (CEditLeafElement*) pEndSelection->NextLeafAll(bEndStickyAfter);
        }
        if ( pB == pEndSelection ){ // The two comments are next to each other.
            pB = pE;
        }
        else if ( pE == pBeginSelection ){ // The two comments are reversed and next to each other.
            pE = pB;
        }

        if ( pB == pBeginSelection || pB == pEndSelection
            || pE == pBeginSelection || pE == pEndSelection ){
            // Degenerate case -- no other elements in the document.
            delete pBeginSelection;
            if ( pBeginSelection != pEndSelection ){
                delete pEndSelection;
            }
            m_pRoot->FinishedLoad(this); // Fixes up container.
            m_pCurrent = m_pRoot->NextLeafAll();
            m_iCurrentOffset = 0;
            Relayout(m_pRoot, 0, m_pRoot->GetLastMostChild() );
       }
        else if ( pB != NULL && pE != NULL ){
            // Get rid of the comments
            delete pBeginSelection;
            if ( pBeginSelection != pEndSelection ){
                delete pEndSelection;
            }

            // Swap if start happens to come after end.
            CEditInsertPoint start(pB, bBeginStickyAfter ? 0 : pB->GetLen(), bBeginStickyAfter);
            CEditInsertPoint end(pE, bEndStickyAfter ? 0 : pE->GetLen(), bEndStickyAfter);
            XP_Bool bFromStart = FALSE;
            if ( start > end ){
                CEditInsertPoint temp = start;
                start = end;
                end = temp;
                bFromStart = TRUE;
            }
            if ( start.m_pElement->IsEndOfDocument() ){
                // Selection at the end of the document.
                // So select the end of the previous element.
                XP_Bool bInsertPoint = (pB == pE);
                start.m_pElement = start.m_pElement->PreviousLeaf();
                if ( start.m_pElement != NULL ) {
                    start.m_iPos = start.m_pElement->GetLen();
                }
                if ( bInsertPoint ) {
                    end = start;
                }
            }
            if ( start.m_pElement != NULL && end.m_pElement != NULL ) {
                CEditSelection selection(start, end, bFromStart);
                SetSelection(selection);
                // FinishedLoad can delete the selected items.
                // Using a persistent selection is safe, because
                // the worst that can happen is that the resulting
                // selection is several characters off.
                // (The right thing to do is make FinishedLoad
                // preserve the selection. But this would be
                // expensive to implement.)
                CPersistentEditSelection persel = EphemeralToPersistent(selection);
                LO_ClearSelection(m_pContext);
                m_pRoot->FinishedLoad(this);
                Reduce( m_pRoot );
                Relayout(m_pRoot, 0, m_pRoot->GetLastMostChild(), RELAYOUT_NOCARET );
                SetSelection(persel);
                return;
            }
            // Otherwise fall through to default behavior
        }
    }
    //if ( pRelayoutFrom != NULL ) Relayout(pRelayoutFrom,0);

    // Default behavior
    m_pCurrent = m_pRoot->NextLeafAll();
    m_iCurrentOffset = 0;
}

void CEditBuffer::DummyCharacterAddedDuringLoad(){
    m_bDummyCharacterAddedDuringLoad = TRUE;
}

XP_Bool CEditBuffer::IsFileModified(){
    int32 last_write_time = m_iFileWriteTime;

    //Get current time and save in m_iFileWriteTime,
    // but NOT if in the process of saving a file
    if( m_pContext->edit_saving_url ){
        return FALSE;
    }

    GetFileWriteTime();
    // Skip if current_time is 0 -- first time through
    return( last_write_time != 0 && last_write_time != m_iFileWriteTime );
}

void CEditBuffer::GetFileWriteTime(){
    char *pDocURL = LO_GetBaseURL(m_pContext);
    char *pFilename = NULL;

    // Don't set the time if we are in the process of saving the file
    if( m_pContext->edit_saving_url ){
        return;
    }

    // Must be a local file type
    if( NET_IsLocalFileURL(pDocURL) &&
    // No longer uses XP_ConvertURLToLocalFile
		( XP_STRCMP( pDocURL, XP_GetString(XP_EDIT_NEW_DOC_NAME) ) != 0 ) &&
		((pFilename = NET_ParseURL(pDocURL, GET_PATH_PART)) != NULL ) ) {
        XP_StatStruct statinfo;
        if( -1 != XP_Stat(pFilename, &statinfo, xpURL) &&
            statinfo.st_mode & S_IFREG ){
            m_iFileWriteTime = statinfo.st_mtime;
        }
    }
    // pFilename is allocated even if XP_ConvertUrlToLocalFile returns FALSE
    if( pFilename ){
        XP_FREE(pFilename);
    }
}

static void SetDocColor(MWContext* pContext, int type, ED_Color& color)
{
    LO_Color *pColor = edt_MakeLoColor(color);
    // If pColor is NULL, this will use the "MASTER",
    //  (default Browser) color, and that's OK!
    LO_SetDocumentColor( pContext, type, pColor );
    if( pColor) XP_FREE(pColor);
}

void CEditBuffer::RefreshLayout(){
    VALIDATE_TREE(this);

    SetDocColor(m_pContext, LO_COLOR_BG, m_colorBackground);
    SetDocColor(m_pContext, LO_COLOR_FG, m_colorText);
    SetDocColor(m_pContext, LO_COLOR_LINK, m_colorLink);
    SetDocColor(m_pContext, LO_COLOR_VLINK, m_colorFollowedLink);
    SetDocColor(m_pContext, LO_COLOR_ALINK, m_colorActiveLink);

    // Fix for bug 42390.
    // Implementation of LO_SetBackgroundImage has changed, it now requires
    // an absolute URL.  Maybe we should require that m_pBackgroundImage is 
    // always absolute.
    if (m_pBackgroundImage && *m_pBackgroundImage) {
      char *pAbsolute = NET_MakeAbsoluteURL( LO_GetBaseURL(m_pContext ), m_pBackgroundImage );
      if (pAbsolute) {
        LO_SetBackgroundImage( m_pContext, pAbsolute );
        XP_FREE(pAbsolute);
      }
    }
    else {
      LO_SetBackgroundImage( m_pContext, m_pBackgroundImage );
    }

    Relayout(m_pRoot, 0, m_pRoot->GetLastMostChild() );
}

void CEditBuffer::SetDisplayParagraphMarks(XP_Bool bDisplay) {
    m_pContext->display_paragraph_marks = bDisplay;
    RefreshLayout();
}

XP_Bool CEditBuffer::GetDisplayParagraphMarks() {
    return m_pContext->display_paragraph_marks;
}


void CEditBuffer::SetDisplayTables(XP_Bool bDisplay) {
    m_pContext->display_table_borders = bDisplay;
    RefreshLayout();
}

XP_Bool CEditBuffer::GetDisplayTables() {
    return m_pContext->display_table_borders;
}

ED_TextFormat CEditBuffer::GetCharacterFormatting( ){
    CEditLeafElement *pElement = m_pCurrent;

    //
    // While selecting, we may not have a valid region
    //
    if( IsSelecting() ){
        return TF_NONE;
    }

    if( IsSelected() ){
        ElementOffset i,i1;
        CEditLeafElement *pEnd;
        XP_Bool bFromStart;
        GetSelection( pElement, i, pEnd, i1, bFromStart );
    }

    if( pElement && pElement->IsA(P_TEXT )){
        return pElement->Text()->m_tf;
    }

    return TF_NONE;
}

int CEditBuffer::GetFontSize( ){
    CEditLeafElement *pElement = m_pCurrent;

    if( IsSelecting() ){
        return 0;
    }

    if( IsSelected() ){
        ElementOffset i,i1;
        CEditLeafElement *pEnd;
        XP_Bool bFromStart;
        GetSelection( pElement, i, pEnd, i1, bFromStart );
    }
    if( pElement && pElement->IsA(P_TEXT )){
        return pElement->Text()->GetFontSize();
    }

	//
	// The following logic is similar to CEditBuffer::GetCharacterData().
	//

	// Try to find the previous text element and use its font size.
	if (pElement) {
		CEditTextElement *tElement = pElement->PreviousTextInContainer();
		if ( tElement){
			return tElement->GetFontSize();
		}

		// Get font size from parent of pElement.
		return pElement->GetDefaultFontSize();
	}

    return 0;
}

int CEditBuffer::GetFontPointSize( ){
    CEditLeafElement *pElement = m_pCurrent;

    if( IsSelecting() ){
        return 0;
    }

    if( IsSelected() ){
        ElementOffset i,i1;
        CEditLeafElement *pEnd;
        XP_Bool bFromStart;
        GetSelection( pElement, i, pEnd, i1, bFromStart );
    }
    if( pElement && pElement->IsA(P_TEXT )){
        return pElement->Text()->GetFontPointSize();
    }

	//
	// The following logic is similar to CEditBuffer::GetCharacterData().
	//

	// Try to find the previous text element and use its font size.
	if (pElement) {
		CEditTextElement *tElement = pElement->PreviousTextInContainer();
		if ( tElement){
			return tElement->GetFontPointSize();
		}

		// Get font size from parent of pElement.
        // There is only one default for all containers, unlike relative font size
		// return pElement->GetDefaultFontSize();
	}

    return ED_FONT_POINT_SIZE_DEFAULT;
}

int CEditBuffer::GetFontFaceIndex()
{
    int iReturn = -1;   // Default is "unknown"

    EDT_CharacterData * pData = GetCharacterData();
    if( pData ){
        if( pData->mask & TF_FONT_FACE ){
            // We are sure of the font state - see if it is set to something
            if( (pData->values & TF_FONT_FACE) && pData->pFontFace ){
                iReturn = ED_FONT_LOCAL;
            } else if( pData->mask & TF_FIXED ){
                // We don't have a NS Font - we have a default HTML font
                //   and we are sure of the Fixed Width state
                if( pData->values & TF_FIXED ){
                    iReturn = 1;   // We have default fixed-with font
                } else {
                    iReturn = 0;   // We have default proportional font
                }
            }
        }
        XP_FREE(pData);
    }
    return iReturn;
}

static char pFontFace[256] = "";

// Use this to search font faces in menu or dropdown list
char * CEditBuffer::GetFontFace()
{
    char * pFontFaces = EDT_GetFontFaces();
    if( !pFontFaces ){
        return NULL;
    }
    EDT_CharacterData * pData = GetCharacterData();
    if( !pData ){
        return NULL;
    }
    
    // Truncate static string we might return;
    *pFontFace = '\0';
    
    // Most of the time we copy font from EDT_CharacterData to this static
    char * pReturn = pFontFace;

    if( pData->mask & TF_FONT_FACE ){
        // We are sure of the font state - see if it is set to something
        if( (pData->values & TF_FONT_FACE) && pData->pFontFace ){
            // We are sure we have a NS Font - get the list to scan

            char * pFontTagList = EDT_GetFontFaceTags();
            if( pFontTagList ){
                //  Start at the second string in the list ("Fixed Width")
                char *pFontTag = pFontTagList + XP_STRLEN(pFontTagList) + 1;
                char *pFace = pFontFaces + XP_STRLEN(pFontFaces) + 1;
                if( *pFontTag != '\0' ) {
                    int iLen;
                    // Skip over fixed-width string for 1st real NS Font
                    pFontTag += XP_STRLEN(pFontTag) + 1;
                    pFace += XP_STRLEN(pFace) + 1;
                    int index = 2;
    	            while( (iLen = XP_STRLEN(pFontTag)) > 0 ) {
                        if( 0 == XP_STRCMP( pFontTag, pData->pFontFace ) ){
                            // We found a "translated" font group,
                            //   return local font that corresponds
                            pReturn = pFace;
                            break;
                        }
                        // Next string
                        pFontTag += iLen+1;
                        pFace += XP_STRLEN(pFace) + 1; 
                        index++;
                    }
                }
            }
            // If we didn't find it in the XP list, it must be
            //   a local system font, so just copy what's in character data
            if( !*pReturn ){
                XP_STRCPY(pFontFace, pData->pFontFace);
            }
        } else if( pData->mask & TF_FIXED ){
            // We don't have a font face -- must be a default HTML font
            //   and we are sure of the Fixed Width state
            if( pData->values & TF_FIXED ){
                // we are "Fixed Width" - its the second string
                pReturn = pFontFaces + XP_STRLEN(pFontFaces) + 1;
            } else {
                // we are "Variable Width"
                pReturn = pFontFaces;
            }
        }
    }

    XP_FREE(pData);

    if(pReturn && !*pReturn){
        // Empty string - return NULL instead
        return NULL;
    }
    return pReturn;
}

ED_Color CEditBuffer::GetFontColor( ){
    CEditLeafElement *pElement = m_pCurrent;

    if( IsSelecting() ){
        return ED_Color::GetUndefined();
    }

    if( IsSelected() ){
        ElementOffset i,i1;
        CEditLeafElement *pEnd;
        XP_Bool bFromStart;
        GetSelection( pElement, i, pEnd, i1, bFromStart );
    }
    if( pElement && pElement->IsA(P_TEXT )){
        return pElement->Text()->GetColor();
    }
	return ED_Color::GetUndefined();
}

TagType CEditBuffer::GetParagraphFormattingSelection(CEditSelection& selection)
{
    TagType type = P_UNKNOWN;

    // Grab the current selection.
//    CEditSelection selection;
    if( selection.IsEmpty() )
        GetSelection(selection);
    CEditContainerElement* pStart = selection.m_start.m_pElement->FindContainer();
    CEditContainerElement* pEnd = selection.m_end.m_pElement->FindContainer();
    XP_Bool bUseEndContainer = !selection.EndsAtStartOfContainer();

    // Scan for all text elements and save first container type found
    // return that type only if all selected text elements have the same type
    while( pStart ){
        if( type == P_UNKNOWN ){
            // First time through
            type = pStart->GetType();
        } else if( type != pStart->GetType() ) {
            // Type is different - get out now
            return P_UNKNOWN;
        }
        // Start = end, we're done
        if( pStart == pEnd ){
            break;
        }
        CEditElement* pLastChild = pStart->GetLastMostChild();
        if ( ! pLastChild ) break;
        CEditElement* pNextChild = pLastChild->NextLeaf();
        if ( ! pNextChild ) break;
        pStart = pNextChild->FindContainer();
        if ( pStart == pEnd && ! bUseEndContainer ) break;
    }
    return type;
}

TagType CEditBuffer::GetParagraphFormatting()
{
    CEditContainerElement *pCont;
    // Empty selection -- will get current selection if exists
    CEditSelection selection;
    TagType type = P_UNKNOWN;

    if( IsSelected() ){
        return GetParagraphFormattingSelection(selection);
    }
    if( IsTableOrCellSelected() )
    {
        // Get each cell contents to examine - as if it were selected
        if( GetFirstCellSelection(selection) )
        {
            // Get format from first selected cell
            type = GetParagraphFormattingSelection(selection);
            if( type != P_UNKNOWN )
            {
                while( GetNextCellSelection(selection) )
                {
                    // All cells must have the same type, else stop now
                    if( type != GetParagraphFormattingSelection(selection) )
                    {
                        type = P_UNKNOWN;
                        break;
                    }
                }
            }
        }
        return type;
    } 
    else {
        if( m_pCurrent && (pCont = m_pCurrent->FindContainer()) != 0){
            type = pCont->GetType();
        }
        else {
            type = P_UNKNOWN;
        }
    }
    return type;
}

void CEditBuffer::PositionCaret(int32 x, int32 y) {
    VALIDATE_TREE(this);
    ClearPhantomInsertPoint();
    DoneTyping();
    ClearMove();
#ifdef LAYERS
    LO_PositionCaret( m_pContext, x, y, NULL );
#else
    LO_PositionCaret( m_pContext, x, y );
#endif
}

// This is static so all windows may share current drag table data
EDT_DragTableData* CEditBuffer::m_pDragTableData = NULL;

XP_Bool CEditBuffer::StartDragTable(int32 /*x*/, int32 /*y*/)
{
    if( !m_pDragTableData )
    {
        m_pDragTableData = XP_NEW( EDT_DragTableData );
    }
    if( m_pDragTableData )
    {
        // Initialize data for possible dragging
        // This is used by PositionDropCaret()
        m_pDragTableData->iDropType = ED_DROP_NORMAL;
        m_pDragTableData->pDragOverCell = NULL;
        // Source that we are dragging
        m_pDragTableData->iSourceType = m_TableHitType;

        // Get first cell of selection - we ignore a drop on that cell
        if( m_pSelectedLoTable )
        {
            // Get first cell of entire table
            LO_Element *pLoCell = m_pSelectedLoTable->next;
            while( pLoCell && pLoCell->type != LO_CELL ) { pLoCell = pLoCell->lo_any.next; }
            m_pDragTableData->pFirstSelectedCell = pLoCell;
        }
        else {
            // Get first cell in selected LO_CELL list
            m_pDragTableData->pFirstSelectedCell = (LO_Element*)m_SelectedLoCells[0];
        }
        return TRUE;
    }
    return FALSE;
}

void CEditBuffer::StopDragTable()
{ 
    if( m_pDragTableData )
    {
        XP_FREE(m_pDragTableData);
        m_pDragTableData = NULL;
    }
}

/* Converts ("snaps") input X, Y (doc coordinates) to X, Y needed for drop caret 
 *  and calls appropriate front-end FE_Display<Text|Generic|Image>Caret to use show where
 *  a drop would occur. It does NOT change current selection or internal caret position
 * Also handles table selection
*/
XP_Bool CEditBuffer::PositionDropCaret(int32 x, int32 y)
{
#ifdef XP_WIN
    // If we are dragging a table selection,
    //  get feedback data, except when entire table selected
    //   (use normal caret placement for that)
    if( m_pDragTableData && m_pDragTableData->iSourceType > ED_HIT_SEL_TABLE )
    {
        // Get where we currently are
        LO_Element *pLoCell = NULL;
        int32 iWidth, iHeight;

        // Get where user is currently dragging over
        ED_DropType iDropType = GetTableDropRegion(&x, &y, &iWidth, &iHeight, &pLoCell);

        // No point in dropping exactly on top of selection being dragged
        if( iDropType == ED_DROP_NONE )
        {
            FE_DestroyCaret(m_pContext);
            return FALSE;
        }
        
        if( m_pDragTableData->iDropType != ED_DROP_NORMAL )
        {
            // Call Front end to display drop feedback only if
            //  different from previous condition
            if( pLoCell != m_pDragTableData->pDragOverCell ||
                iDropType != m_pDragTableData->iDropType )
            {
                m_pDragTableData->iDropType = iDropType;
                m_pDragTableData->pDragOverCell = pLoCell;
                m_pDragTableData->X = x;
                m_pDragTableData->Y = y;
                m_pDragTableData->iWidth = iWidth;
                m_pDragTableData->iHeight = iHeight;
                if( m_pDragTableData->iDropType != ED_DROP_REPLACE_CELL )
                {
                    FE_DestroyCaret(m_pContext);
                    FE_DisplayDropTableFeedback(m_pContext, m_pDragTableData);
                } else {
                    // TODO: SET "REPLACE" BIT FOR ALL CELLS TO BE REPLACED
                    //   AND CALL FEs TO REDRAW EACH CELL
                }
            }
            return TRUE;
        }
    }
#endif
    FE_DestroyCaret(m_pContext);
    lo_PositionDropCaret(m_pContext, x, y, NULL);
    return TRUE;
}

void CEditBuffer::DeleteSelectionAndPositionCaret( int32 x, int32 y ) {
    // Nothing selected - nothing to delete
    if( !IsSelected() ){
        PositionCaret(x,y);    
        return;
    }

    // Get where we would drop if not deleting anything
    int32 position;
    LO_Element * pLoElement = lo_PositionDropCaret(m_pContext, x, y, &position );
    if( pLoElement ){
        // Get the CEditObject at drop point and use it to create new insert point
        CPersistentEditSelection PSel;
        GetSelection(PSel);
        CEditElement * pEdElement = pLoElement->lo_any.edit_element;
	    // Create an "ephemeral" insert point from the EditLeafObject
	    CEditInsertPoint IP(pEdElement, pLoElement->lo_any.edit_offset + position /*???? offset */);
        CPersistentEditInsertPoint Insert = EphemeralToPersistent(IP);

        // Check if new insert point will be after the selection
        //  we will delete and adjust the index by the amount deleted
        if( Insert.m_index > PSel.m_start.m_index ){
		    Insert.m_index -= PSel.m_end.m_index - PSel.m_start.m_index;
	    }
	    // Delete the selection
	    DeleteSelection();
	    SetInsertPoint(Insert);
    }
}
 
 // this routine is called when the mouse goes down.
//
void CEditBuffer::StartSelection( int32 x, int32 y, XP_Bool doubleClick ){
    VALIDATE_TREE(this);
    ClearPhantomInsertPoint();
    {
        // This is a hack to avoid auto-scrolling to the old selection.
        XP_Bool scrolling = m_inScroll;
        m_inScroll = TRUE;
        ClearSelection();
        m_inScroll = scrolling;
    }

    // Remove any existing table or cell selection
    // This is the best place to put this so all "normal" selection-setting
    //  clears the table selection, but it prevents moving caret without clearing table
    ClearTableAndCellSelection();

    FE_DestroyCaret(m_pContext);
    ClearMove(); // do this when moving caret and not up or down arrow
    DoneTyping();
    if ( doubleClick ) {
#ifdef LAYERS
        LO_DoubleClick( m_pContext, x, y, NULL );
#else
        LO_DoubleClick( m_pContext, x, y );
#endif
    }
    else {
#ifdef LAYERS
        LO_Click( m_pContext, x, y, FALSE, NULL );
#else
        LO_Click( m_pContext, x, y, FALSE );
#endif /* LAYERS */
    }
}

// Note: By using MoveAndHideCaretInTable,
//  we do NOT expose caret, scroll window etc.
void CEditBuffer::MoveToFirstSelectedCell()
{
    int iSize = m_SelectedLoCells.Size();
    if( iSize )
        MoveAndHideCaretInTable((LO_Element*)m_SelectedLoCells[0]);
}

void CEditBuffer::MoveToLastSelectedCell()
{
    int iSize = m_SelectedLoCells.Size();
    if( iSize )
        MoveAndHideCaretInTable((LO_Element*)m_SelectedLoCells[iSize-1]);
}

void CEditBuffer::MoveAndHideCaretInTable(LO_Element *pLoElement)
{
    if(!pLoElement)
        return;
    // From StartSelection()
    ClearPhantomInsertPoint();
    FE_DestroyCaret(m_pContext);
    // What do these do?
    ClearMove();
    DoneTyping();
    CEditElement *pLastElement = NULL;

    if( pLoElement->type == LO_TABLE )
    {
        // Get first cell
        while( pLoElement->type != LO_CELL )
            pLoElement = pLoElement->lo_any.next;
    }

    if( pLoElement->type != LO_CELL )
        return;

    // Search for the last element in the cell that 
    //   has edit pointer, and thus is a leaf
    LO_Element * pCellElement = pLoElement->lo_cell.cell_list;
    while( pCellElement )
    {
        if( pCellElement->lo_any.edit_element )
            pLastElement = pCellElement->lo_any.edit_element;

		pCellElement = pCellElement->lo_any.next;
    }

    if( pLastElement )
    {    
        // From SetInsertPoint()
    #ifdef LAYERS
        LO_StartSelectionFromElement( m_pContext, 0, 0, NULL );
    #else
        LO_StartSelectionFromElement( m_pContext, 0, 0);
    #endif
        m_pCurrent = pLastElement->Leaf();
        if( pLastElement->IsText() )
        {
            char *pText = pLastElement->Text()->m_pText;
            m_iCurrentOffset = pText ? XP_STRLEN(pText) : 0;
        } else {
            m_iCurrentOffset = 1;   //TODO: ASK JACK IF THIS IS OK FOR ALL NON-TEXT
        }
        m_bCurrentStickyAfter = FALSE;
    }
}

// Use only after deleting a cell, and before Relayout(),
//  so later doesn't crash because current element is deleted
void CEditBuffer::MoveToExistingCell(CEditTableElement *pTable, int32 X, int32 Y)
{
    if( !pTable )
        return;
    CEditInsertPoint insertPoint;
    LO_Element *pLoElement = NULL;

    // Get the cell under the supplied coordinates
    GetTableHitRegion(X, Y, &pLoElement);
    CEditElement *pTableCell = NULL;
    if( pLoElement && pLoElement->type == LO_CELL )
    {
        // Try to get cell at
        //   Current cursor location...
        pTableCell = GetTableElementFromLO_Element(pLoElement, LO_CELL);
        
        // ...First cell of current row...
        if( !pTableCell )
            pTableCell = pTable->GetFirstCellInRow(Y, TRUE);

        // ...or First cell in table
        if( !pTableCell )
            pTableCell = pTable->GetFirstCell();
    }
    if( pTableCell )
    {
        insertPoint.m_pElement = pTableCell->GetLastMostChild()->Leaf();
    } else {
        insertPoint.m_pElement = m_pRoot->GetFirstMostChild()->Leaf();
    }
    insertPoint.m_iPos = insertPoint.m_pElement->GetLen();

    CEditSelection selection = CEditSelection(insertPoint, insertPoint);
    SetSelection(selection);
}

// this routine is called when the right mouse goes down.
//
void CEditBuffer::SelectObject( int32 x, int32 y ){
    PositionCaret(x,y);
    ClearSelection();
    FE_DestroyCaret(m_pContext);
#ifdef LAYERS
    LO_SelectObject(m_pContext, x, y, NULL);
#else
    LO_SelectObject(m_pContext, x, y);
#endif
}

//
//
// this routine is called when the mouse is moved while down.
//  at this point we actually decide that we are inside a selection.
//
void CEditBuffer::ExtendSelection( int32 x, int32 y ){
    BeginSelection();
    LO_ExtendSelection( m_pContext, x, y );

    // may not result in an actual selection.
    //  if this is the case, just position the caret.
    if( ! IsSelected() ) {
        // If there's no selection, it is still possible that a
        // selection has been started.
        if ( ! LO_IsSelectionStarted( m_pContext ) ) {
            StartSelection( x, y, FALSE );
        }
        else
        {
            // The selection is the empty selection.
            //ClearSelection();
            //FE_DestroyCaret(m_pContext);
            //ClearMove();
        }
    }
    else {
        m_pCurrent = 0;
        m_iCurrentOffset = 0;
        // We really did extend selection - 
        // Remove any existing table or cell selection
        ClearTableAndCellSelection();
     }
}

void CEditBuffer::ExtendSelectionElement( CEditLeafElement *pEle, int iOffset, XP_Bool bStickyAfter ){
    int iLayoutOffset;
    LO_Element* pLayoutElement;
    BeginSelection();

    if( IsSelected() ){
        LO_Element *pStart, *pEnd;
        intn iStartPos, iEndPos;
        XP_Bool bFromStart;
        LO_GetSelectionEndPoints( m_pContext,
                    &pStart, &iStartPos,
                    &pEnd, &iEndPos,
                    &bFromStart, 0);
        if( !(pStart == pEnd && iStartPos == iEndPos) ){
            if( iOffset ) iOffset--;
        }
    }
    else {
        if( iOffset ) iOffset--;
    }


    // we need to convert a selection edit iOffset to a Selection
    //  offset.  This will only work when selecting to the right...

    pEle->GetLOElementAndOffset( iOffset, bStickyAfter,
               pLayoutElement, iLayoutOffset );

    LO_ExtendSelectionFromElement( m_pContext, pLayoutElement,
                iLayoutOffset, FALSE );
}

void CEditBuffer::SelectAll(){
    VALIDATE_TREE(this);
    // Remove any existing table or cell selection
    ClearTableAndCellSelection();
    ClearPhantomInsertPoint();
    ClearMove();
    DoneTyping();
	// Clear selection (don't resync insertion point to avoid scrolling.)
	ClearSelection(FALSE);
    // Destroy the cursor. Perhaps this is something ClearSelection should do.
    FE_DestroyCaret(m_pContext);
	// Delegate actual selection to the layout engine
    if( LO_SelectAll(m_pContext) ) {
	    // Clear insertion point
	    m_pCurrent = 0;
	    m_iCurrentOffset = 0;
	}
    RevealSelection();
}


// This is the old version 
void CEditBuffer::SelectTable(){
    VALIDATE_TREE(this);
    ClearPhantomInsertPoint();
    ClearMove();
    DoneTyping();
    CEditSelection selection;
    GetSelection(selection);
    CEditTableElement* pStartTable = selection.m_start.m_pElement->GetTableIgnoreSubdoc();
    CEditSelection startAll;
    if ( pStartTable ) {
        pStartTable->GetAll(startAll);
    }
    CEditTableElement* pEndTable = selection.m_end.m_pElement->GetTableIgnoreSubdoc();
    CEditSelection endAll;
    if ( pEndTable ) {
        pEndTable->GetAll(endAll);
    }
    if ( pStartTable ) {
        if ( pEndTable ) {
            // Both a start table and an end table.
            selection.m_start = startAll.m_start;
            selection.m_end = endAll.m_end;
        }
        else {
            // Just a start table.
            selection = startAll;
        }
    } else {
        if ( pEndTable ) {
            // Just an end table.
            selection = endAll;
        }
        else {
            // No table. Don't change selection.
            return;
        }
    }

    SetSelection(selection);
}

// Select the cell boundary of cell containing the current edit element
void CEditBuffer::SelectTableCell()
{
    CEditTableCellElement* pEdCell = m_pCurrent->GetTableCellIgnoreSubdoc();
    if( pEdCell )
    {
        LO_CellStruct *pLoCell = pEdCell->GetLoCell();
        if( pLoCell )
        {
            SelectCell(TRUE, pLoCell, pEdCell);
        }
    }
}


void CEditBuffer::BeginSelection( XP_Bool bExtend, XP_Bool bFromStart ){
    // Remove any existing table or cell selection
    ClearTableAndCellSelection();

    ClearMove(); // For side effect of flushing table formatting timer.
    if( !IsSelected() ){
        int iLayoutOffset;
        CEditLeafElement *pNext = 0;
        LO_Element* pLayoutElement;

        m_bSelecting = TRUE;

        FE_DestroyCaret( m_pContext );

        ClearPhantomInsertPoint();

        // If we're at the end of an element, and the next element isn't a break,
        // then move the cursor to the start of the next element.
        if ( m_pCurrent->GetLen() == m_iCurrentOffset
                && (pNext = m_pCurrent->LeafInContainerAfter()) != 0
                && ! m_pCurrent->CausesBreakAfter() && ! pNext->CausesBreakBefore()){
            XP_Bool good = pNext->GetLOElementAndOffset( 0, FALSE,
                    pLayoutElement, iLayoutOffset );
            if ( ! good ) {
                XP_ASSERT(FALSE);
                return;
            }
       }
        else {
            XP_Bool good = m_pCurrent->GetLOElementAndOffset( m_iCurrentOffset, m_bCurrentStickyAfter,
                        pLayoutElement, iLayoutOffset );
            if ( ! good ) {
                XP_ASSERT(FALSE);
                return;
            }
        }

        // if we're selecting forward, and
        // if we are before the space that cause the wrap, we should be
        //  starting selection from the line feed.
        if( ! bFromStart && pLayoutElement->type == LO_TEXT
                    && iLayoutOffset == pLayoutElement->lo_text.text_len )
        {
            if( pLayoutElement->lo_any.next &&
                pLayoutElement->lo_any.next->type == LO_LINEFEED )
            {
                pLayoutElement = pLayoutElement->lo_any.next;
                iLayoutOffset = 0;
            }
            else
            {
                // The layout element is shorter than we think it should
                //  be.
                // This appears to be an OK situation, now that we don't merge
                // text elements. See bug 58763.
                // XP_ASSERT( FALSE );
            }
        }

        XP_ASSERT( pLayoutElement );

        if ( bExtend ) {
            if ( ! LO_SelectElement( m_pContext, pLayoutElement,
                    iLayoutOffset, bFromStart ) ) {
                SetCaret(); // If the selection moved off the end of the document, set the caret again.
            }
        }
        else {
#ifdef LAYERS
            LO_StartSelectionFromElement( m_pContext, pLayoutElement,
                    iLayoutOffset, NULL );
#else
            LO_StartSelectionFromElement( m_pContext, pLayoutElement,
                    iLayoutOffset);
#endif
        }
    }
}

void CEditBuffer::EndSelection(int32 /* x */, int32 /* y */){
    EndSelection();
}

void CEditBuffer::EndSelection( )
{
    // Clear the move just in case EndSelection is called out of order
    // (this seems to be happening in the WinFE when you click a lot.)
    // ClearMove has a side-effect of flushing the relayout timer.
    ClearMove();

    // Fix for the mouse move that does not move enough
    //   to show selection (kills caret)

    if( LO_IsSelectionStarted(m_pContext) ){
        SetCaret();
    }
    m_bSelecting = FALSE;

    // Make the active end visible
    RevealSelection();
}

void CEditBuffer::RevealSelection()
{
    if ( IsSelected() ) {
        CEditLeafElement* pStartElement;
        ElementOffset iStartOffset;
        CEditLeafElement* pEndElement;
        ElementOffset iEndOffset;
        XP_Bool bFromStart;
        GetSelection( pStartElement, iStartOffset, pEndElement, iEndOffset, bFromStart );
        if ( bFromStart ) {
            RevealPosition(pStartElement, iStartOffset, FALSE);
        }
        else {
            RevealPosition(pEndElement, iEndOffset, FALSE);
        }
    }
    else
    {
        if(m_pCurrent != NULL){
            RevealPosition(m_pCurrent, m_iCurrentOffset, m_bCurrentStickyAfter);
        }
    }
}

void CEditBuffer::SelectCurrentElement(){
    FE_DestroyCaret(m_pContext);
    SelectRegion( m_pCurrent, 0, m_pCurrent, 1,
        TRUE, TRUE );
    m_pCurrent = 0;
    m_iCurrentOffset = 0;
}


void CEditBuffer::ClearSelection( XP_Bool bResyncInsertPoint, XP_Bool bKeepLeft ){

    if( !bResyncInsertPoint ){
#ifdef LAYERS
       LO_StartSelectionFromElement( m_pContext, 0, 0, NULL);
#else
       LO_StartSelectionFromElement( m_pContext, 0, 0);
#endif
       return;
    }

    CEditSelection selection;
    GetSelection(selection);
    selection.ExcludeLastDocumentContainerEnd();
    SetInsertPoint(*selection.GetEdge( ! bKeepLeft ));
}


void CEditBuffer::GetInsertPoint( CEditLeafElement** ppEle, ElementOffset *pOffset, XP_Bool *pbStickyAfter){
    LO_Element *pStart, *pEnd;
    intn iStartPos, iEndPos;
    XP_Bool bFromStart;

    if( IsSelected() ){
        //
        // Grab the current selection.
        //
        LO_GetSelectionEndPoints( m_pContext,
                    &pStart, &iStartPos,
                    &pEnd, &iEndPos,
                    &bFromStart, 0 );

        XP_ASSERT( pStart );
        XP_ASSERT( pEnd );

        if( pStart == pEnd && iEndPos == iStartPos ){
            bFromStart = TRUE;
        }

        if( bFromStart ){
            *pbStickyAfter = iStartPos != 0;
            while( pStart && pStart->lo_any.edit_element == 0 ){
                pStart = pStart->lo_any.next;
                if( pStart &&pStart->type == LO_TEXT ){
                    iStartPos = pStart->lo_text.text_len;
                    if( iStartPos ) iStartPos--;
                }
                else {
                    iStartPos = 0;
                }
            }
            if( pStart ){
                *ppEle = pStart->lo_any.edit_element->Leaf(),
                *pOffset = pStart->lo_any.edit_offset+iStartPos;
            }
            else{
                // LTNOTE: NEED to implement end of document:
                //Insert point is at end of document
                //EndOfDocument( FALSE );
                XP_ASSERT(FALSE);
            }
       }
        else {
            *pbStickyAfter = iEndPos != 0;
            while( pEnd && pEnd->lo_any.edit_element == 0 ){
                pEnd = pEnd->lo_any.prev;
                if( pEnd && pEnd->type == LO_TEXT ){
                    iEndPos = pEnd->lo_text.text_len;
                    if( iEndPos ) iEndPos--;
                }
                else {
                    iEndPos = 0;
                }
            }
            if( pEnd ){
                *ppEle = pEnd->lo_any.edit_element->Leaf(),
                *pOffset = pEnd->lo_any.edit_offset+iEndPos;
            }
            else {
                // LTNOTE: NEED to implement end of document:
                //Insert point is at end of document.
                //EndOfDocument( FALSE );
                XP_ASSERT(FALSE);
            }
        }

    }
    else {
        *ppEle = m_pCurrent;
        *pOffset = m_iCurrentOffset;
        *pbStickyAfter = m_bCurrentStickyAfter;
    }
    if (ppEle && *ppEle && (*ppEle)->IsEndOfDocument()) {
        // End of document. Move back one edit position
        CEditInsertPoint ip(*ppEle, *pOffset, *pbStickyAfter);
        ip = ip.PreviousPosition();
        *ppEle = ip.m_pElement;
        *pOffset = ip.m_iPos;
        *pbStickyAfter = ip.m_bStickyAfter;
    }
}


//
// The 'PropertyPoint' is the beginning of a selection or current insert
//  point.
//
// Returns
//  TRUE if a single element is selected like an image or hrule
//  FALSE no selection or an extended selection.
//
XP_Bool CEditBuffer::GetPropertyPoint( CEditLeafElement**ppElement,
            ElementOffset* pOffset ){
    LO_Element *pStart, *pEnd;
    intn iStartPos, iEndPos;
    XP_Bool bFromStart;
    XP_Bool bSingleElementSelection;


    if( IsSelected() ){
        //
        // Grab the current selection.
        //
        LO_GetSelectionEndPoints( m_pContext,
                    &pStart, &iStartPos,
                    &pEnd, &iEndPos,
                    &bFromStart, &bSingleElementSelection );

        XP_ASSERT( pStart->lo_any.edit_element );

        //------------------------- Begin Removable code ------------------------
        //
        // LTNOTE:  We are doing this in GetSelectionEndpoints, but I
        //  don't think we need to be doing it.  If the above assert never
        //  happens remove this code
        //
        while( pStart && pStart->lo_any.edit_element == 0 ){
            pStart = pStart->lo_any.next;
            if( pStart &&pStart->type == LO_TEXT ){
                iStartPos = pStart->lo_text.text_len;
                if( iStartPos ) iStartPos--;
            }
            else {
                iStartPos = 0;
            }
        }
        //------------------------- End Removable code ------------------------

        *ppElement = pStart->lo_any.edit_element->Leaf();
        *pOffset = pStart->lo_any.edit_offset+iStartPos;
        XP_ASSERT( *ppElement );
    }
    else {
        *ppElement = m_pCurrent;
        *pOffset = m_iCurrentOffset;
        bSingleElementSelection = FALSE;
    }
    return bSingleElementSelection;
}

void CEditBuffer::SelectRegion(CEditLeafElement *pBegin, intn iBeginPos,
            CEditLeafElement* pEnd, intn iEndPos, XP_Bool bFromStart, XP_Bool bForward  ){
    int iBeginLayoutOffset;
    LO_Element* pBeginLayoutElement;
    int iEndLayoutOffset;
    LO_Element* pEndLayoutElement;

    // Remove any existing table or cell selection???
    //ClearTableAndCellSelection();

    // If the start is an empty element, grab previous item.
    if( iBeginPos == 0 && pBegin->Leaf()->GetLen() == iBeginPos ){
        CEditLeafElement *pPrev = pBegin->PreviousLeafInContainer();
        if( pPrev ){
            pBegin = pPrev;
            iBeginPos = pPrev->GetLen();
        }
    }

    // if the end is a phantom insert point, grab the previous
    // item.

    if( iEndPos == 0 ){
        CEditLeafElement* pPrev = pEnd->PreviousLeafInContainer();
        if( pPrev ){
            pEnd = pPrev;
            iEndPos = pPrev->GetLen();
        }
    }

    XP_Bool startOK = pBegin->GetLOElementAndOffset( iBeginPos, FALSE,
            pBeginLayoutElement, iBeginLayoutOffset );
    XP_Bool endOK = pEnd->GetLOElementAndOffset( iEndPos, FALSE,
               pEndLayoutElement, iEndLayoutOffset );

    XP_ASSERT(startOK);
    XP_ASSERT(endOK);
    if (startOK && endOK ) {
        if ( ! IsSelected() ) {
            FE_DestroyCaret( m_pContext );
        }
        // Hack for end-of-document
        if ( iEndLayoutOffset < 0 ) iEndLayoutOffset = 0;

        /* LO_SelectRegion can call back to relayout the document under these circumstances:
         * If we've changed the size of an image, and we select or unselect the image, then
         * lo_HilightSelect calls CL_CompositeNow, which decides that the document bounds have
         * changed, and calls the front end to change the document dimensions.
         * This will eventually call EDT_RefreshLayout, which will
         * get confused and think that the document is not currently selected.
         */
        XP_Bool save_NoRelayout = m_bNoRelayout;
        m_bNoRelayout = TRUE;

        LO_SelectRegion( m_pContext, pBeginLayoutElement, iBeginLayoutOffset,
            pEndLayoutElement, iEndLayoutOffset, bFromStart, bForward );

        m_bNoRelayout = save_NoRelayout;

#ifdef DEBUG_EDITOR_LAYOUT
        // Verify that the selection is reversible.
        CEditSelection selection;
        GetSelection( selection );
        CEditInsertPoint a(pBegin, iBeginPos);
        CEditInsertPoint b(pEnd, iEndPos);
        CEditSelection original(a, b, bFromStart);
        XP_ASSERT(original == selection);
#endif
    }

//    EndSelection();
}

void CEditBuffer::SetSelection(CEditSelection& selection)
{
    if ( selection.IsInsertPoint() ) {
        SetInsertPoint(selection.m_start.m_pElement, selection.m_start.m_iPos, selection.m_start.m_bStickyAfter);
    }
    else {
        SelectRegion(selection.m_start.m_pElement, selection.m_start.m_iPos,
            selection.m_end.m_pElement, selection.m_end.m_iPos,
            selection.m_bFromStart);
    }
}

void CEditBuffer::SetInsertPoint(CEditInsertPoint& insertPoint) {
    CEditSelection selection(insertPoint, insertPoint, FALSE);
    SetSelection(selection);
}

void CEditBuffer::SetInsertPoint(CPersistentEditInsertPoint& insertPoint) {
    CEditInsertPoint p = PersistentToEphemeral(insertPoint);
    SetInsertPoint(p);
}

// Use data from supplied selection, or get from current selection if supplied is empty
void CEditBuffer::GetSelection( CEditSelection& selection, CEditLeafElement*& pStartElement, ElementOffset& iStartOffset,
                CEditLeafElement*& pEndElement, ElementOffset& iEndOffset, XP_Bool& bFromStart )
{
    if( selection.m_start.m_pElement == 0 || selection.m_end.m_pElement == 0 )
    {
        // This version will get existing selection first
        GetSelection( pStartElement, iStartOffset, pEndElement, iEndOffset, bFromStart );
    }
    else
    {
        // Get data from supplied selection
        pStartElement = selection.m_start.m_pElement;
        iStartOffset = selection.m_start.m_iPos;
        pEndElement = selection.m_end.m_pElement;
        iEndOffset = selection.m_end.m_iPos;
        bFromStart = selection.m_bFromStart;
    }
}

void CEditBuffer::GetSelection( CEditLeafElement*& pStartElement, ElementOffset& iStartOffset,
                CEditLeafElement*& pEndElement, ElementOffset& iEndOffset, XP_Bool& bFromStart ){
    INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_pContext);
    int16 win_csid = INTL_GetCSIWinCSID(c);

    //cmanske: IS THIS RISKY? WE ASSERT HERE IF WE DON'T RELAYOUT
    if( /*!m_bNoRelayout && */ m_bLayoutBackpointersDirty ) {
        XP_ASSERT(FALSE);
        return;
    }
    LO_Element *pStart, *pEnd;
    intn iStartPos, iEndPos;


    XP_ASSERT( IsSelected());

    //
    // Grab the current selection.
    //
    LO_GetSelectionEndPoints( m_pContext,
                &pStart, &iStartPos,
                &pEnd, &iEndPos,
                &bFromStart, 0 );

     if ( ! pStart->lo_any.edit_element ) {
        XP_ASSERT(FALSE);
        // We should always be handed back an editable element.
        // LTNOTE: if we blow up here it is because we have searched to the end
        //  of document.
        while( pStart->lo_any.edit_element == 0 ){
            pStart = pStart->lo_any.next;
            iStartPos = 0;
        }
    }

    pStartElement = pStart->lo_any.edit_element->Leaf();
    iStartOffset = pStart->lo_any.edit_offset+iStartPos;

    if ( ! pEnd->lo_any.edit_element ) {
        XP_ASSERT(FALSE); // We should always be handed back an editable element.
        while( pEnd->lo_any.edit_element == 0 ){
            pEnd = pEnd->lo_any.prev;
            if( pEnd->type == LO_TEXT ){
                iEndPos = pEnd->lo_text.text_len;
                if( iEndPos ) iEndPos--;
            }
            else {
                iEndPos = 0;
            }
        }
    }

    pEndElement = pEnd->lo_any.edit_element->Leaf();
    // jhp The simple way seems to work better.
    iEndOffset = pEnd->lo_any.edit_offset+iEndPos;

    if ( iEndOffset < 0 ) {
        iEndOffset = 0; // End-of-document flummery
    }


	// Normalize multibyte position,
	// make it always start from beginning of char, end of the beginning of next char
	if (pStart->type == LO_TEXT && pStart->lo_text.text
	    && INTL_NthByteOfChar(win_csid, (char *) pStart->lo_text.text, iStartOffset+1) > 1) {
		XP_TRACE(("iStartOffset = %d which is wrong for multibyte", (int)iStartOffset));
		iStartOffset = INTL_NextCharIdx(win_csid, (unsigned char *) pStart->lo_text.text,iStartOffset);
	}
	if (pEnd->type == LO_TEXT && pEnd->lo_text.text
	    && INTL_NthByteOfChar(win_csid, (char *) pEnd->lo_text.text, iEndOffset+1) > 1) {
		XP_TRACE(("iEndOffset = %d which is wrong for multibyte", (int)iEndOffset));
		iEndOffset = INTL_NextCharIdx(win_csid, (unsigned char *) pStart->lo_text.text,iEndOffset);
	}
}

void CEditBuffer::MakeSelectionEndPoints( CEditSelection& selection,
    CEditLeafElement*& pBegin, CEditLeafElement*& pEnd ){
#ifdef DEBUG
    // If the selection is busted, we're in big trouble.
    CPersistentEditSelection persel = EphemeralToPersistent(selection);
    XP_ASSERT ( persel.m_start.m_index <=  persel.m_end.m_index);
#endif
    pEnd = selection.m_end.m_pElement->Divide( selection.m_end.m_iPos )->Leaf();
    pBegin = selection.m_start.m_pElement->Divide( selection.m_start.m_iPos )->Leaf();
    
    //cmanske: Don't set this flag unless we really did divide into new elements
    if( pEnd != selection.m_end.m_pElement ||
        pBegin != selection.m_start.m_pElement )
    {
        m_bLayoutBackpointersDirty = TRUE;
    }
}

void CEditBuffer::MakeSelectionEndPoints( CEditLeafElement*& pBegin,
        CEditLeafElement*& pEnd ){
    CEditSelection selection;
    GetSelection(selection);
    MakeSelectionEndPoints(selection, pBegin, pEnd);
}

int CEditBuffer::Compare( CEditElement *p1, int i1Offset,
                           CEditElement *p2, int i2Offset ) {

    CEditPositionComparable *pPos1, *pPos2;
    int iRetVal;

    pPos1 = new CEditPositionComparable( p1, i1Offset);
    pPos2 = new CEditPositionComparable( p2, i2Offset);

    // find which one is really the beginning.
    iRetVal = pPos1->Compare( pPos2 );
    delete pPos1;
    delete pPos2;
    return iRetVal;
}

// Prevent recursive calls for autosaving
XP_Bool CEditBuffer::m_bAutoSaving = FALSE;

void CEditBuffer::AutoSaveCallback() {
    if ( m_bAutoSaving || IsBlocked() ) {
        XP_TRACE(("Auto Save ignored -- busy."));
        return;
    }
    if ( ! GetCommandLog()->IsDirty() ) {
        XP_TRACE(("Skipping AutoSave because we're not dirty."));
        return;
    }
    // Skip trying to save if we are already in the process of saving a file
    if( m_pContext->edit_saving_url ) {
        XP_TRACE(("Skipping AutoSave because we're not already saving a file."));
        return;
    }
    // This unnecessary on WinFE as FE_CheckAndAutoSaveDocument()
    // catches it.  But, avoids having to make the other FEs deal with it.
    if (m_pContext->type == MWContextMessageComposition) {
        XP_TRACE(("No auto-save for message composition"));
        return;
    }
    XP_TRACE(("Auto Save...."));

    m_bAutoSaving = TRUE;
    if( EDT_IS_NEW_DOCUMENT(m_pContext) ){
        // New document -- prompt front end to get filename and "save file as"
         if( !FE_CheckAndAutoSaveDocument(m_pContext) ){
            // User canceled the Autosave prompt, turn off autosave
            SetAutoSavePeriod(0);
         }
    } else {
        History_entry * hist_entry = SHIST_GetCurrent(&(m_pContext->hist));

	    if(hist_entry && hist_entry->address)
	    {

          if (NET_URL_Type(hist_entry->address) != FILE_TYPE_URL) {
            // Remote document -- prompt front end to get local filename and "save file as"
             if( !FE_CheckAndAutoSaveDocument(m_pContext) ){
                // User canceled the Autosave prompt, turn off autosave
                SetAutoSavePeriod(0);
             }
          }
          else {      
            // A local file, just save it.
            char *szLocalFile = NULL;
            if ( XP_ConvertUrlToLocalFile( hist_entry->address, &szLocalFile ) )
            {
                char buf[300];

	            PR_snprintf(buf, sizeof(buf)-1, XP_GetString(XP_EDITOR_AUTO_SAVE), szLocalFile);
	            FE_Progress(m_pContext, buf);


              /////// ! Duplicating some code from EDT_SaveFile, but I didn't want to change
              /////// ! the interface for EDT_SaveFile(), but we still need to specify that we
              /////// ! are auto-saving.
              // Create Abstract file system to write to disk.
              char *pDestPathURL = edt_GetPathURL(hist_entry->address);
              XP_ASSERT(pDestPathURL);
              ITapeFileSystem *tapeFS = new CTapeFSFile(pDestPathURL,hist_entry->address);
              XP_ASSERT(tapeFS);
              XP_FREEIF(pDestPathURL);
              // tapeFS freed by CEditSaveObject.

              char **ppEmptyList = (char **)XP_ALLOC(sizeof(char *));
              XP_ASSERT(ppEmptyList);
              ppEmptyList[0] = NULL;
  
              // go to the newly saved document.
              SaveFile( ED_FINISHED_GOTO_NEW, hist_entry->address, tapeFS, 
                              FALSE, // bSaveAs 
                              FALSE, FALSE, // don't need to move images or adjust
                                            // links because document isn't moving.
                              TRUE, // is AutoSave
                              ppEmptyList); // Don't send along any files, even LOCALDATA.
            }
            if (szLocalFile){
                XP_FREE(szLocalFile);
            }
          } // local file
        }
    } // not a new document

    m_bAutoSaving = FALSE;
}

void CEditBuffer::FileFetchComplete(ED_FileError status) {
    m_status = status;
    FE_Progress(m_pContext, NULL);

    // hardts.  Moved from CEditBuffer::WriteToFile()
    if (status == ED_ERROR_NONE) {
        m_autoSaveTimer.Restart();
    }
}

void CEditBuffer::SetAutoSavePeriod(int32 minutes) {
    m_autoSaveTimer.SetPeriod(minutes);
}

int32 CEditBuffer::GetAutoSavePeriod() {
    return m_autoSaveTimer.GetPeriod();
}

void CEditBuffer::SuspendAutoSave(){
    m_autoSaveTimer.Suspend();
}

void CEditBuffer::ResumeAutoSave(){
    m_autoSaveTimer.Resume();
}

void CEditBuffer::ClearMove(XP_Bool bFlushRelayout){
    if ( bFlushRelayout ) {
        m_relayoutTimer.Flush();
    }
    m_iDesiredX = -1;
}


void CEditBuffer::SetHREFSelection( ED_LinkId id ){
    VALIDATE_TREE(this);
    CEditLeafElement *pCurrent, *pEnd, *pBegin, *pNext;

    //
    // Guarantee that the text blocks of the beginning and end of selection
    //  are atomic.
    //
    MakeSelectionEndPoints( pBegin, pEnd );
    pCurrent = pBegin;

    while( pCurrent != pEnd ){
        pNext = pCurrent->NextLeafAll();
        pCurrent->Leaf()->SetHREF( id );
        pCurrent = pNext;
    }

	CEditSelection tmp(pBegin, 0, pEnd, 0);
    RepairAndSet(tmp);
}

EDT_ClipboardResult CEditBuffer::DeleteSelection(CEditSelection& selection, XP_Bool bCopyAppendAttributes){
    SetSelection(selection);
    return DeleteSelection(bCopyAppendAttributes);
}

EDT_ClipboardResult CEditBuffer::DeleteSelection(XP_Bool bCopyAppendAttributes){
    VALIDATE_TREE(this);
    EDT_ClipboardResult result = CanCut(TRUE);
    if ( result == EDT_COP_OK ){
        if ( IsSelected() ) {
            CEditLeafElement *pBegin;
            CEditLeafElement *pEnd;

            CEditSelection selection;
            GetSelection(selection);
            if ( selection.ContainsLastDocumentContainerEnd() ) {
                selection.ExcludeLastDocumentContainerEnd();
                SetSelection(selection);
            }
            if ( selection.IsInsertPoint() ) {
                return result;
            }
            MakeSelectionEndPoints( selection, pBegin, pEnd );
            DeleteBetweenPoints( pBegin, pEnd, bCopyAppendAttributes );
        }
    }
    return result;
}

CPersistentEditSelection CEditBuffer::GetEffectiveDeleteSelection(){
    // If the selection includes the end of the document, then the effective selection is
    // less than that.
    CEditSelection selection;
    GetSelection(selection);
    selection.ExcludeLastDocumentContainerEnd();
    selection.ExpandToIncludeFragileSpaces();
    return EphemeralToPersistent(selection);
}

//
// Get comparable positions to the
//
void CEditBuffer::DeleteBetweenPoints( CEditLeafElement* pBegin, CEditLeafElement* pEnd,
     XP_Bool bCopyAppendAttributes){
    CEditLeafElement *pCurrent;
    CEditLeafElement *pNext;

    pCurrent = pBegin;

    XP_ASSERT( pEnd != 0 );

    CEditLeafElement* pPrev = pCurrent->PreviousLeafInContainer();
    CEditElement* pCommonAncestor = pBegin->GetCommonAncestor(pEnd);
    CEditElement* pContainer = pCurrent->FindContainer();
    CEditElement* pCurrentContainer;
    while( pCurrent != pEnd ){
        pNext = pCurrent->NextLeafAll();

        // if we've entered a new container, merge it.
        pCurrentContainer = pCurrent->FindContainer();

        // DeleteElement removes empty paragraphs so we don't have to do the merges.
        //
        //if( pContainer != pCurrentContainer ){
        //    pContainer->Merge( pCurrentContainer );
        //}

#ifdef DEBUG
         m_pRoot->ValidateTree();
#endif

        if( pCurrent->DeleteElement( pContainer )) {
            pContainer = pNext->FindContainer();
        }
#ifdef DEBUG
        m_pRoot->ValidateTree();
#endif
        pCurrent = pNext;
    }

    //
    // if the selection spans partial paragraphs, merge the remaining paragraphs
    //
    if( pPrev && pPrev->FindContainer() != pEnd->FindContainer() ){
        pPrev->FindContainer()->Merge( pEnd->FindContainer(), bCopyAppendAttributes );
    }

    m_pCurrent = pEnd->PreviousLeafInContainer();
    if( m_pCurrent != 0 ){
        m_iCurrentOffset = m_pCurrent->GetLen();
    }
    else {
        m_pCurrent = pEnd;
        m_iCurrentOffset = 0;
    }

    //FixupSpace();

    // probably needs to be the common ancestor.
    ClearSelection(FALSE);
    CEditInsertPoint tmp(pEnd,0);
    CPersistentEditInsertPoint end2 = EphemeralToPersistent(tmp);
    Reduce( pCommonAncestor );
    SetCaret();
    if ( pPrev && pPrev->FindContainer() != pContainer ) {
        Reduce( pPrev->FindContainer() );
    }
    CEditInsertPoint end3 = PersistentToEphemeral(end2);
    Relayout( pContainer, 0,  end3.m_pElement);
}

EDT_ClipboardResult CEditBuffer::PasteQuoteBegin( XP_Bool bHTML ){
    if ( m_bPasteQuoteMode != FALSE ) {
        XP_ASSERT(FALSE);
    }

    EDT_ClipboardResult result = CanPaste(TRUE);
    if ( result != EDT_COP_OK ) return result;

    m_bPasteQuoteMode = TRUE;
    BeginBatchChanges(kPasteTextCommandID);
    DeleteSelection();
    m_bPasteHTML = bHTML;
    if ( ! m_pPasteHTMLModeText ) {
        m_pPasteHTMLModeText = new CStreamOutMemory();
        m_pPasteTranscoder = NULL;
    }
    m_bAbortPasteQuote = FALSE;
    return result;
}

EDT_ClipboardResult CEditBuffer::PasteQuote(char* pText){
    // XP_ASSERT(FALSE); // Should convert to new code.
    return PasteQuoteINTL(pText, GetRAMCharSetID());
}

EDT_ClipboardResult CEditBuffer::PasteQuoteINTL(char* pText, int16 csid){
    EDT_ClipboardResult result = EDT_COP_OK;
    XP_Bool bBackwardsCompatible = FALSE;
    if ( m_bPasteQuoteMode != TRUE ) {
        //  XP_ASSERT(FALSE); // Mail guys forgot to call PasteQuoteBegin()
        bBackwardsCompatible = TRUE;
        result = PasteQuoteBegin(FALSE);
        if ( result != EDT_COP_OK ) {
            return result;
        }
    }

    // Stop if we are in "abort paste quote" mode
    if (m_bAbortPasteQuote)
        return result;

    // If the text is too long, abort paste operation.
    // An error message will be displayed in PasteQuoteEnd().
    if (XP_STRLEN(pText) >= MAX_PASTE_SIZE) {
        m_bAbortPasteQuote = TRUE;
    }
    else if ( m_bPasteHTML ) {
        // Check if we need to set up a new transcoder.
        if ( ! m_pPasteTranscoder || csid != m_pPasteTranscoder->GetOldCSID() ) {
            if ( m_pPasteTranscoder ) {
                m_pPasteTranscoder->ForgetStream();
                delete m_pPasteTranscoder;
            }
            m_pPasteTranscoder = new CConvertCSIDStreamOut( csid, GetRAMCharSetID(), m_pPasteHTMLModeText);
        }
        m_pPasteTranscoder->Write(pText, XP_STRLEN(pText));
    }
    else {
        result = PasteText(pText, TRUE, FALSE, csid, TRUE,TRUE);
    }

    if ( bBackwardsCompatible ) {
        result = PasteQuoteEnd();
    }

    return result;
}

EDT_ClipboardResult CEditBuffer::PasteQuoteEnd(){
    EDT_ClipboardResult result = EDT_COP_OK;
	XP_Bool bThisIsDeleted = FALSE;
	CEditCommandLog* pCommandLog = GetCommandLog();
    if ( m_bPasteQuoteMode != TRUE ) {
        XP_ASSERT(FALSE);
        return result;
    }
    if ( m_bPasteHTML && m_pPasteHTMLModeText ) {
        // Paste as HTML. The other 1/2 of this logic
        // is in CheckAndPrintComment2
        result = CanPaste(TRUE);
		// Save to delete after we've deleted "this".
		CStreamOutMemory* pPasteText = m_pPasteHTMLModeText;
        CConvertCSIDStreamOut* pPasteTextTranscoder = m_pPasteTranscoder;

        // Display error message if aborting paste operation.
        if (m_bAbortPasteQuote) {
            char* msg = XP_GetString(XP_EDT_MSG_CANNOT_PASTE);
            FE_Alert(m_pContext, msg);
        }

        if ( result == EDT_COP_OK && !m_bAbortPasteQuote) {
            if( IsSelected() ){
                DeleteSelection();
            }
            m_bPasteHTMLWhenSavingDocument = TRUE;
            XP_HUGE_CHAR_PTR pData;
            WriteToBuffer(&pData, TRUE);
            m_bPasteHTMLWhenSavingDocument = FALSE;
			m_pPasteHTMLModeText = 0;  // so our destructor doesn't try to clean this up.
            m_pPasteTranscoder = 0;
            ReadFromBuffer(pData); // This deletes "this".
			bThisIsDeleted = TRUE;
            XP_HUGE_FREE(pData);
        }
        XP_HUGE_FREE(pPasteText->GetText());
        delete pPasteTextTranscoder; // Automaticly deletes pPasteText
        if ( ! bThisIsDeleted){
			m_pPasteHTMLModeText = 0;
            m_pPasteTranscoder = 0;
		}
    }
	pCommandLog->EndBatchChanges();
    if ( ! bThisIsDeleted ) {
		m_bPasteQuoteMode = FALSE;
	}
    return result;
}

void CEditBuffer::PasteHTMLHook(CPrintState* pPrintState){
    if (m_bPasteHTMLWhenSavingDocument ) {
        // Insert the paste text in-line, as we're writing out the document.
        XP_HUGE_CHAR_PTR pSource = m_pPasteHTMLModeText->GetText();
        int32 len = m_pPasteHTMLModeText->GetLen();
        const int32 kChunkSize = 4096; // Must be smaller than 64K for Win16.
        char* buf = (char*) XP_ALLOC(kChunkSize);
        while ( len > 0 ) {
            int32 chunk = len;
            if ( chunk > kChunkSize ){
                chunk = kChunkSize;
            }
            len -= chunk;
            // Copy from the huge pointer to the regular pointer
            char* p = buf;
            for(int32 i = 0; i < chunk; i++ ){
                *p++ = *pSource++;
            }
            pPrintState->m_pOut->Write(buf, chunk);
        }
        XP_FREE(buf);
    }
}

EDT_ClipboardResult CEditBuffer::PasteText( char *pText, XP_Bool bMailQuote, XP_Bool bIsContinueTyping, int16 csid, XP_Bool bRelayout , XP_Bool bReduce){
    int16 newcsid = GetRAMCharSetID();
    if ( csid != newcsid) {
        // Need to transcode pText to the current
        CStreamOutMemory memory;
        CConvertCSIDStreamOut transcoder( csid, newcsid, &memory);
        transcoder.Write(pText, XP_STRLEN(pText));
        XP_HUGE_CHAR_PTR pTranscoded = memory.GetText();
        EDT_ClipboardResult result = PasteText((char*) pTranscoded, bMailQuote, bIsContinueTyping, bRelayout, bReduce);
        XP_HUGE_FREE(pTranscoded);
        transcoder.ForgetStream();
        return result;
    }
    else {
        return PasteText(pText, bMailQuote, bIsContinueTyping, bRelayout, bReduce);
    }
}

//
// LTNOTE: this routine needs to be broken into PasteText and PasteFormattedText
//  It didn't happen because we were too close to ship.
//
EDT_ClipboardResult CEditBuffer::PasteText( char *pText, XP_Bool bMailQuote, XP_Bool bIsContinueTyping, XP_Bool bRelayout, XP_Bool bReduce)
{
    // If the text is too long, display alert message and return.
    if (XP_STRLEN(pText) >= MAX_PASTE_SIZE) {
        char* msg = XP_GetString(XP_EDT_MSG_CANNOT_PASTE);
        FE_Alert(m_pContext, msg);
        return EDT_COP_OK;
    }

    VALIDATE_TREE(this);
    SUPPRESS_PHANTOMINSERTPOINTCHECK(this);

    if ( !bIsContinueTyping ) {
        EDT_ClipboardResult result = CanPaste(TRUE);
        if ( result != EDT_COP_OK ) return result;
    }

    //
    // No one likes the other way of quoting, always use mailquote
    //
    bMailQuote = TRUE;

    m_bNoRelayout = TRUE;
    int iCharsOnLine = 0;

    if( IsSelected() ){
        DeleteSelection();
    }
    FixupInsertPoint();

    // If the pasted text starts with returns, the start element can end up
    // being moved down the document as paragraphs are inserted above it.
    // So we hold onto the start as a persistent insert point.

    CPersistentEditInsertPoint persistentStart;
    GetInsertPoint(persistentStart);

    // If we're in preformatted text, every return is a break.
    // If we're in normal text, single returns are spaces,
    // double returns are paragraph marks.

    //XP_Bool bInFormattedText = m_pCurrent->FindContainer()->GetType() == P_PREFORMAT;
    XP_Bool bInFormattedText = m_pCurrent->InFormattedText();

    XP_Bool bLastCharWasReturn = FALSE;

    while( *pText ){
        XP_Bool bReturn = FALSE;
        if( *pText == 0x0d ){
            if( *(pText+1) == 0xa ){
                pText++;
            }
            bReturn = TRUE;
        }
        else if ( *pText == 0xa ){
            bReturn = TRUE;
        }

        if( bReturn ){
            if ( bInFormattedText ) {
                InsertBreak( ED_BREAK_NORMAL, bIsContinueTyping );
                iCharsOnLine = 0;
            } 
            else if ( bMailQuote ){
                ReturnKey( bIsContinueTyping );
#ifdef EDT_DDT
                MorphContainer( P_NSDT);
#else
                MorphContainer( P_DESC_TITLE );
#endif
                iCharsOnLine = 0;
            }
            else {
                if ( bLastCharWasReturn ){
                    ReturnKey( bIsContinueTyping );
                    iCharsOnLine = 0;
                    bLastCharWasReturn = FALSE;
                }
                else {
                    // Remember this return for later. It will
                    // become either a space or a return.
                    bLastCharWasReturn = TRUE;
                }
            }
        }
        else {
            if ( bLastCharWasReturn ) {
                InsertChar( ' ', bIsContinueTyping );
                bLastCharWasReturn = FALSE;
            }

            if ( *pText == '\t' ){
                if ( bInFormattedText ) {
                    do {
                        InsertChar( ' ', bIsContinueTyping );
                        iCharsOnLine++;
                    } while( iCharsOnLine % DEF_TAB_WIDTH != 0 );
                }
                else {
                    InsertChar( ' ', bIsContinueTyping );
                    iCharsOnLine++;
                }
            }
            else {
                // Insert all the characters up to the next return or tab
                int32 value = 0;
                char old = 0;
                while((old = pText[value]) != '\0'){
                    if ( old == '\t' || old == '\015' || old == '\012' ){
                        break;
                    }
                    value++;
                }
                if ( value > 0 ) {
                    pText[value] = '\0';
                    InsertChars( pText, bIsContinueTyping, bReduce); //adding this flag to stop the insertchars from reducing the tree. 
                    pText[value] = old;
                    pText += value - 1; /* -1 Because pText is incremented by one at the end of the loop. */
                    iCharsOnLine += value;
                }
            }
            bLastCharWasReturn = FALSE;
        }
        pText++;
    }

    if ( bLastCharWasReturn ) {
        InsertChar( ' ', bIsContinueTyping );
    }


    if (bReduce)//dont reduce if bReduce is false
        Reduce(m_pRoot);

    CEditInsertPoint start = PersistentToEphemeral(persistentStart);

    m_bNoRelayout = FALSE;

    // We now suppress layout when pasting into tables:
    //  entire table will be layed out after all cell pasting is finished
    if( bRelayout )
    {
        // At one time pEnd was managed so that it pointed beyond the pasted text.
        // Aparently that code was lost in the mists of time. Now we just hope that
        // the next element is far enough. (Lloyd and I think it has to be.)
        CEditElement* pEnd = m_pCurrent->NextLeaf();
        // if the end of the paragraph ends in a break, we need to relayout further.
        if( pEnd && pEnd->IsBreak()
            || ( pEnd->GetNextSibling() && pEnd->GetNextSibling()->IsBreak() ) ){
            pEnd = m_pRoot->GetLastMostChild(); // This is almost always too far, but it is safe.
        }

        Reflow( start.m_pElement, start.m_iPos, pEnd );
        //
        if( bMailQuote ){
            CEditInsertPoint start = PersistentToEphemeral(persistentStart);
            //SetInsertPoint( start );
        }
    }

    return EDT_COP_OK;
}

EDT_ClipboardResult CEditBuffer::PasteHTML( char *pBuffer, XP_Bool bUndoRedo ){

    CStreamInMemory stream(pBuffer);
    return PasteHTML(stream, bUndoRedo);
}

EDT_ClipboardResult CEditBuffer::PasteHTML( IStreamIn& stream, XP_Bool /* bUndoRedo */ ){
    INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_pContext);
    VALIDATE_TREE(this);
    EDT_ClipboardResult result = CanPaste(TRUE);
    if ( result != EDT_COP_OK ) return result;

    // First thing in the buffer is some version info
    
    int32 signature = stream.ReadInt();
    if ( signature != GetClipboardSignature() ) {
        FE_Alert(m_pContext,XP_GetString(XP_EDT_BAD_CLIPBOARD_VERSION));
        return EDT_COP_CLIPBOARD_BAD;
    }
    int32 version = stream.ReadInt();
    if ( version != GetClipboardVersion() ) {
        FE_Alert(m_pContext,XP_GetString(XP_EDT_BAD_CLIPBOARD_VERSION));
        return EDT_COP_CLIPBOARD_BAD;
    }
    int32 clipcsid = stream.ReadInt();
    if ( clipcsid != INTL_GetCSIWinCSID(c) ) {
        /* In the future we could transcode. */
        FE_Alert(m_pContext,XP_GetString(XP_EDT_BAD_CLIPBOARD_ENCODING));
        return EDT_COP_CLIPBOARD_BAD;
    }
    // This is non-zero when stream contains just a table
    // It tells us whether it is an entire table or row(s), 
    //      column(s), or "loose cells" copying
    EEditCopyType iCopyType = (EEditCopyType)stream.ReadInt();

    // Pasting cells into existing table is entirely different
    //  if pasting rows, columns, or cells
    // Note: type = eEditCopyTable used for entire table,
    //  allowing an embeded table in existing table
    if( iCopyType > eCopyTable && IsInsertPointInTableCell() )
    {
        return PasteCellsIntoTable(stream, iCopyType);
    }
    
    // Don't relayout until all is pasted
    m_bNoRelayout = TRUE;

    // "Normal" HTML pasting at current caret location
    if( IsSelected() )
    {
        DeleteSelection();
    }
#ifdef DEBUG
    m_pRoot->ValidateTree();
#endif
    ClearPhantomInsertPoint();
    FixupInsertPoint();

    CEditElement* pStart = m_pCurrent;
    int iStartOffset = m_iCurrentOffset;
    CEditLeafElement* pRight = m_pCurrent->Divide(m_iCurrentOffset)->Leaf();
    XP_ASSERT(pRight->FindContainer());
    XP_Bool bAtStartOfParagraph = pRight->PreviousLeafInContainer() == NULL;
    CEditLeafElement* pLeft = pRight->PreviousLeaf(); // Will be NULL at start of document

    // The first thing in the buffer is a flag that tells us if we need to merge the end.
    int32 bMergeEnd = stream.ReadInt();
    // The buffer has zero or more elements. (The case where there's more than one is
    // the one where there are multiple paragraphs.)
    CEditElement* pFirstInserted = NULL;
    CEditElement* pLastInserted = NULL;

    {
        CEditElement* pElement;
        while ( NULL != (pElement = CEditElement::StreamCtor(&stream, this)) )
        {
            // We should always begin with a container
            if ( pElement->IsLeaf() )
            {
                XP_ASSERT(FALSE);
                delete pElement;
                continue;
            }
#ifdef DEBUG
            pElement->ValidateTree();
#endif
            if ( ! pFirstInserted ) 
            {
                pFirstInserted = pElement;
                if ( ! bAtStartOfParagraph )
                {
    XP_ASSERT(pRight->FindContainer());
                    InternalReturnKey(FALSE);
    XP_ASSERT(pRight->FindContainer());
               }
            }
            pLastInserted = pElement;
            pElement->InsertBefore(pRight->FindContainer());

            // Table seems to be a special case
            // If it is inserted NOT at beginning of existing paragraph, contents of
            //  first cell are moved into that paragraph (see below)
            //cmanske: KLUDGE - THIS WORKS, BUT IS IT REALLY OK???
            // (This BUG  exists in 4.0x)
            if( pElement->IsTable() )
            {
                bAtStartOfParagraph = TRUE;
            }

#ifdef DEBUG
            m_pRoot->ValidateTree();
#endif
        }
    }

#ifdef DEBUG
    m_pRoot->ValidateTree();
#endif

    if ( pFirstInserted )
    {
        CEditElement* pLast = pLastInserted->GetLastMostChild()->NextLeafAll();
        // Get the children early, because if we paste a single container,
        // it will be deleted when we merge the left edge
        CEditLeafElement* pFirstMostNewChild = pFirstInserted->GetFirstMostChild()->Leaf();
        CEditLeafElement* pLastMostNewChild = pLastInserted->GetLastMostChild()->Leaf();
        m_pCurrent = pLastMostNewChild;
        m_iCurrentOffset = pLastMostNewChild->GetLen();
        ClearPhantomInsertPoint();
        if ( ! bAtStartOfParagraph )
        {
            if ( pLeft )
            {
                CEditContainerElement* pLeftContainer = pLeft->FindContainer();
                CEditContainerElement* pFirstNewContainer =
                    pFirstMostNewChild->FindContainer();
                if ( pLeftContainer != pFirstNewContainer )
                {
                    pLeftContainer->Merge(pFirstNewContainer);
                    // Now deleted in Merge delete pFirstNewContainer;
                    FixupInsertPoint();
                }
            }
        }
        else {
            // The insert went into the container before us. Adjust the start.
            pStart = pFirstInserted->GetFirstMostChild();
            iStartOffset = 0;
        }

        if ( bMergeEnd )
        {
            CEditContainerElement* pRightContainer = pRight->FindContainer();
            CEditContainerElement* pLastNewContainer = pLastMostNewChild->FindContainer();
            if ( pRightContainer != pLastNewContainer )
            {
                pLastNewContainer->Merge(pRightContainer);
                // Now deleted in Merge delete pRightContainer;
                FixupInsertPoint();
            }
        }
        else {
            // Move to beginning of next container
            m_pCurrent = pLastMostNewChild->NextContainer()->GetFirstMostChild()->Leaf();
            m_iCurrentOffset = 0;
        }

        // Have to FinishedLoad after merging because FinishedLoad is eager to
        // remove spaces from text element at the end of containers. If we let
        // FinishedLoad run earlier, we couldn't paste text that ended in white space.

        m_pRoot->FinishedLoad(this);

#ifdef DEBUG
        m_pRoot->ValidateTree();
#endif

        m_bNoRelayout = FALSE;
        //FixupSpace( );
        // We need to reduce before we relayout because otherwise
        // null insert points mess up the setting of end-of-paragraph
        // marks.
		CEditInsertPoint tmp(pLast,0);
		CPersistentEditInsertPoint end2 = EphemeralToPersistent(tmp);
        Reduce( m_pRoot );
        CEditInsertPoint end3 = PersistentToEphemeral(end2);
        Relayout( pStart, iStartOffset, end3.m_pElement );
    }
    else {
        m_bNoRelayout = FALSE;
    }

   return result;
}

EDT_ClipboardResult CEditBuffer::PasteCellsIntoTable( IStreamIn& stream, EEditCopyType iCopyType )
{
    m_bNoRelayout = FALSE;
    CEditTableCellElement* pTableCell = NULL;
    // Default for pasting is BEFORE
    // TODO: Should we ask user to insert AFTER if currently in last cell in row or column?
    XP_Bool bAfterCurrentCell = FALSE;

    // Shouldn't be here if this is "normal" or entire table
    XP_ASSERT(iCopyType > eCopyTable);
    
    // Move caret into target cell
    if( m_pDragTableData )
    {
        // We are dragging source cells
        XP_ASSERT(m_pDragTableData->pDragOverCell);

        // Dragging has more flexibility for where to paste
        bAfterCurrentCell = (m_pDragTableData->iDropType == ED_DROP_INSERT_AFTER || 
                             m_pDragTableData->iDropType == ED_DROP_INSERT_BELOW );

        CEditElement *pElement = GetTableElementFromLO_Element(m_pDragTableData->pDragOverCell, LO_CELL);
        if( pElement )
        {
            CEditElement *pChild = pElement->GetFirstMostChild();
            if( pChild )
            {
                CEditInsertPoint ip(pChild, 0);
                SetInsertPoint(ip);
            }
        }
    } else {
        // Pasting from clipboard
        // Get current insert point
        CEditInsertPoint ip;
        GetTableInsertPoint(ip);
        pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    }

    //Get the table from the stream
    int32 bMergeEnd = stream.ReadInt();
    CEditTableElement* pSourceTable =(CEditTableElement*)CEditElement::StreamCtor(&stream, this);

    XP_ASSERT(pSourceTable->IsTable());

    if( pTableCell && pSourceTable )
    {
        CEditTableElement* pTable = pTableCell->GetTable();
        if ( pTable )
        {
            intn iSourceCols = pSourceTable->GetColumns();
            int32 X = pTableCell->GetX();
            int32 Y = pTableCell->GetY();
            int32 iNewX = X + (bAfterCurrentCell ? pTableCell->GetWidth() : 0);
            // Try to place cursor in inserted cell
            int32 iCaretX = iNewX + (bAfterCurrentCell ? pTable->GetInterCellSpace() : 0);

            CEditTableRowElement* pCurrentRow = (CEditTableRowElement*)pTableCell->GetParent();
            XP_ASSERT(pCurrentRow->IsTableRow());
            
            intn iCurrentRows = pTable->GetRows();
            // Number of rows we may insert
            intn iTotalRows = pSourceTable->GetRows();
            // Current row index
            intn iRow = 0;

            CEditTableRowElement* pFirstRow = pTable->GetFirstRow();
            if( pFirstRow != pCurrentRow )
            {
                //Insert empty cells for rows above target row
                CEditTableRowElement* pTempRow = pFirstRow;
                while( pTempRow != pCurrentRow )
                {
                    pTempRow->InsertCells(X, iNewX, iSourceCols);
                    pTempRow = pTempRow->GetNextRow();
                    // Increment row where we will start to insert source cells
                    //   and also the total rows we may insert
                    iRow++;
                    iTotalRows++;
                }
            }
            
            CEditTableRowElement* pSourceRow = NULL;
            CEditTableRowElement *pNextRow = NULL;
            // The number of columns we will have to insert if we
            //   need to add rows at bottom of table
            //   so overflow from inserted columns line up
            intn iColumnsBefore = pTable->GetColumnsSpanned(pTable->GetFirstColumnX(), X);
            for( ; iRow < iCurrentRows; iRow++ )
            {
                if( !pCurrentRow )
                    break;

                pSourceRow = pSourceTable->GetFirstRow();
                // pSourceRow may be 0 if there are less rows in source
                //   than in target, so this will just insert blank cell in that case
                pCurrentRow->InsertCells(X, iNewX, iSourceCols, pSourceRow);
                // Delete the row just used
                if( pSourceRow )
                    delete pSourceRow;

                // Next row to insert into
                if( (pNextRow = pCurrentRow->GetNextRow()) != NULL )
                    pCurrentRow = pNextRow;
            }
            // There shouldn't be any rows left now
            //   (this is a check for GetRows();
            XP_ASSERT(pNextRow == NULL);
            
            // Let's be sure!
            pNextRow = NULL;
            
            // If needed, continue to insert more source rows,
            //  but we need to add new rows to current table first            
            // TODO: ASK USER IF THEY WANT TO APPEND EXTRA ROWS OR JUST SKIP THEM
            for( ; iRow < iTotalRows; iRow++ )
            {
                pSourceRow = pSourceTable->GetFirstRow();
                if( !pCurrentRow || !pSourceRow )
                {
                    // We should always have source row,
                    //  else iSourceRows is not accurate
                    //  and we should always have a current row
                    XP_ASSERT(TRUE);
                    break;
                }
                if( iColumnsBefore )
                {
                    // We need cells before the insert column,
                    //  so make a new row with blank cells
                    pNextRow = new CEditTableRowElement(iColumnsBefore);
                    if( pNextRow )
                    {
                        pNextRow->InsertAfter(pCurrentRow);
                        // Append the source cells to the the new row
                        pNextRow->AppendRow(pSourceRow);
                    }
                } else {
                    // No extra columns needed,
                    //   just append source row
                    pSourceRow->Unlink();
                    pSourceRow->InsertAfter(pCurrentRow);
                    pNextRow = pSourceRow;
                }

                XP_ASSERT(pNextRow);
                if( pNextRow )
                {
                    // Fill in rest of row with empty cells
                    pNextRow->PadRowWithEmptyCells(pTable->GetMaxColumns() + iSourceCols);
                    // Be sure any empty cells have required empty text elements
                    pNextRow->FinishedLoad(this);
                }
                pCurrentRow = pNextRow;
            }

            // Delete whats left of the source table
            delete pSourceTable;

            Relayout(pTable, 0);
            MoveToExistingCell(pTable, iCaretX, Y);
        }
#ifdef DEBUG
        m_pRoot->ValidateTree();
#endif
    }

    //TODO: FINISH PASTECELLSINTOTABLE
    return EDT_COP_OK;
}


EDT_ClipboardResult CEditBuffer::PasteHREF( char **ppHref, char **ppTitle, int iCount){
    VALIDATE_TREE(this);
    SUPPRESS_PHANTOMINSERTPOINTCHECK(this);
    EDT_ClipboardResult result = CanPaste(TRUE);
    if ( result != EDT_COP_OK ) return result;
    m_bNoRelayout = TRUE;
    XP_Bool bFirst = TRUE;

    if( IsSelected() ){
        DeleteSelection();
    }
    FixupInsertPoint();

    FormatCharacter( TF_NONE );
    InsertChar(' ', FALSE);

    CEditElement *pStart = m_pCurrent;
    int iStartOffset = m_iCurrentOffset;

    m_pCurrent->Divide( m_iCurrentOffset );

    int i = 0;
    while( i < iCount ){
        char *pTitle = ppTitle[i];

        if( pTitle == 0 ){
            pTitle = ppHref[i];
        }
        else {
            // LTNOTE:
            // probably shouldn't be doing this to a buffer we were passed
            //  but what the hell.
            NormalizeText( pTitle );

            // kill any trailing spaces..
            int iLen = XP_STRLEN( pTitle )-1;
            while( iLen >= 0 && pTitle[iLen] == ' ' ){
                pTitle[iLen] = 0;
                iLen--;
            }
            if( *pTitle == 0 ){
                pTitle = ppHref[i];
            }
        }

        CEditTextElement *pElement = new CEditTextElement( 0, pTitle );
        pElement->SetHREF( linkManager.Add( ppHref[i], 0 ));

        if( bFirst && m_iCurrentOffset == 0 ){
            pElement->InsertBefore( m_pCurrent );
            pStart = pElement;
        }
        else {
            pElement->InsertAfter( m_pCurrent );
        }
        if( bFirst ){
            //FixupSpace();
            bFirst = FALSE;
        }
        m_pCurrent = pElement;
        m_iCurrentOffset = pElement->Text()->GetLen();
        i++;
    }

    m_bNoRelayout = FALSE;
    //FixupSpace( );
    // Fixes bug 21920 - Crash when dropping link into blank document.
    // We might have an empty text container just after m_pCurrent.
    Reduce(m_pRoot);
    FormatCharacter( TF_NONE );
    InsertChar(' ', FALSE);
    Relayout( pStart, iStartOffset, m_pCurrent );
    Reduce( m_pRoot );
    return result;
}

EDT_ClipboardResult CEditBuffer::PasteTextAsNewTable(char *pText, intn iRows, intn iCols)
{
    if( iRows == 0 || iCols == 0 )
       CountRowsAndColsInPasteText(pText, &iRows, &iCols); 

    EDT_TableData *pData = EDT_NewTableData();
    if( pData )
    {
        pData->iRows = iRows;
        pData->iColumns = iCols;
        InsertTable(pData);
        EDT_FreeTableData(pData);
        intn iRow = 0;
        // We assume InsertTable will put caret in first cell of table
        CEditInsertPoint ip;
        GetTableInsertPoint(ip);
        CEditTableElement *pTable = ip.m_pElement->GetTableIgnoreSubdoc();
        if( pTable )
        {
            CEditTableCellElement* pNextCell = pTable->GetFirstCell();
            
            char *pCellText = pText;
            XP_Bool bDone = FALSE;
            XP_Bool bEndOfRow = FALSE;

            while(!bDone)
            {            
                do {
                    char current = *pText;
                    if( current == 9)
                    {
                        // We found the end of the cell's text
                        *pText = '\0';
                        pText++;
                        break;
                    }
                    if( current == 13 || current == 10 || current == '\0' )
                    {
                        // We found the end of the cell's text
                        if( current != '\0' )
                        {
                            // Terminate text for this cell
                            *pText = '\0';

                            // Skip over other end-of-line characters
                            char next = *(pText+1);
                            if( (current == 13 && next == 10) || 
                                (current == 10 && next == 13) )
                            {
                                pText++;
                            }                    
                            pText++;
                        }
                        bEndOfRow = TRUE;
                        break;
                    }
                    pText++;
                } while( pText );

                if( pCellText && *pCellText )
                    PasteText(pCellText, FALSE, FALSE, FALSE, TRUE); //Last 2param = don't relayout but we want to reduce

                intn iNextRow = iRow;
                
                // Move insert point to next cell
                // If its in next row, iNextRow will be incremented
                if( NextTableCell(FALSE, TRUE, &iNextRow) )
                {
                    if(bEndOfRow)
                    {
                        // Next item should go into next row
                        iRow++;
                        // If we have a short row (fewer tabs than maximum),
                        //   skip to first cell of next row
                        XP_Bool bGetNext = TRUE;
                        while( bGetNext && iRow != iNextRow )
                        {
                            bGetNext = NextTableCell(FALSE, TRUE, &iNextRow);
                        }
                        bEndOfRow = FALSE;
                    } 
                    else if( iRow != iNextRow )
                    {
                        // Should never happen - we are in next row of new table,
                        //   but we're not at end of text "row"
                        XP_ASSERT(FALSE);
                    }

                    // The next cell item starts at next character
                    pCellText = pText;

                    if( *pText == '\0' )
                        bDone = TRUE;
                }
                else
                {
                    //TODO: We should probably test if any text is left
                    //      here - there shouldn't be any
                    bDone = TRUE;
                }
            } //while(!bDone)

            // Return insert point to the first cell
            SetInsertPoint(ip);
            // Relayout the entire table
            Relayout(pTable, 0);
        }
    }

    return EDT_COP_OK;
}

EDT_ClipboardResult CEditBuffer::PasteTextIntoTable(char *pText, XP_Bool /*bReplace*/, intn iRows, intn iCols)
{
    if( iRows == 0 || iCols == 0 )
       CountRowsAndColsInPasteText(pText, &iRows, &iCols); 
    //TODO: FINISH PASTETEXTINTOTABLE()
    return EDT_COP_OK;
}


//
// Make sure that there are not spaces next to each other after a delete,
//  or paste
//
void CEditBuffer::FixupSpace( XP_Bool /*bTyping*/){
    if( m_pCurrent->InFormattedText() ){
        return;
    }

    if( m_pCurrent->IsBreak() && m_iCurrentOffset == 1 ){
        // Can't have a space after a break.
        CEditLeafElement *pNext = m_pCurrent->TextInContainerAfter();
        if( pNext
                && pNext->IsA(P_TEXT)
                && pNext->Text()->GetLen() != 0
                && pNext->Text()->GetText()[0] == ' '){
            pNext->Text()->DeleteChar(m_pContext, 0);
            return;
        }
    }

    if( !m_pCurrent->IsA(P_TEXT) ){
        return;
    }

    CEditTextElement *pText = m_pCurrent->Text();

    if( pText->GetLen() == 0 ){
        return;
    }
    if( m_iCurrentOffset > 0 && pText->GetText()[ m_iCurrentOffset-1 ] == ' ' ){
        if( m_iCurrentOffset == pText->GetLen() ){
            CEditLeafElement *pNext = pText->TextInContainerAfter();
            if( pNext
                    && pNext->IsA(P_TEXT)
                    && pNext->Text()->GetLen() != 0
                    && pNext->Text()->GetText()[0] == ' '){
                pNext->Text()->DeleteChar(m_pContext, 0);
                //m_iCurrentOffset--;  // WHy???
                return;
            }
        }
        else if( m_iCurrentOffset < pText->GetLen() && pText->GetText()[m_iCurrentOffset] == ' ' ){
            pText->DeleteChar(m_pContext, m_iCurrentOffset);
            return;
        }
    }
    // check for beginning of paragraph with a space.
    else if( m_iCurrentOffset == 0 && pText->GetText()[0] == ' ' ){
        pText->DeleteChar(m_pContext, 0);
        CEditLeafElement *pNext = m_pCurrent->TextInContainerAfter();
        if( m_pCurrent->Text()->GetLen() == 0 && pNext ){
            m_pCurrent = pNext;
        }
    }
    // Can't reduce here because it might smash objects pointers we know about
}

EDT_ClipboardResult CEditBuffer::CutSelection( char **ppText, int32* pTextLen,
                    char **ppHtml, int32* pHtmlLen){
    VALIDATE_TREE(this);
    EDT_ClipboardResult result = CanCut(TRUE);
    if ( result != EDT_COP_OK ) return result;

    result = CopySelection( ppText, pTextLen, ppHtml, pHtmlLen );
    if ( result != EDT_COP_OK ) return result;

    CPersistentEditSelection selection = GetEffectiveDeleteSelection();
    BeginBatchChanges(kCutCommandID);
    result = DeleteSelection();
    EndBatchChanges();

    // Check to see if we trashed the document
    XP_ASSERT ( m_pCurrent && m_pCurrent->GetElementType() != eEndElement );
    return result;
}

XP_Bool CEditBuffer::CutSelectionContents( CEditSelection& selection,
                    char **ppHtml, int32* pHtmlLen ){
    XP_Bool result = CopySelectionContents( selection, ppHtml, pHtmlLen );
    if ( result ) {
        CEditLeafElement *pBegin;
        CEditLeafElement *pEnd;
        MakeSelectionEndPoints( selection, pBegin, pEnd );

        DeleteBetweenPoints( pBegin, pEnd );
    }
    return TRUE;
}


EDT_ClipboardResult CEditBuffer::CopySelection( char **ppText, int32* pTextLen,
                    char **ppHtml, int32* pHtmlLen )
{
    EDT_ClipboardResult result = CanCopy(TRUE);
    if ( result != EDT_COP_OK ) return result;

    //cmanske: Added table selection handling
    if ( ppText )
    {
        if( IsTableOrCellSelected() )
        {
            // Collect text from all cells in
            //  format ready to paste into spreadsheets
            *ppText = GetTabDelimitedTextFromSelectedCells();
        }
        else 
        {
            // This adds hard CR/LF at the end of each row
            // TODO: Shouldn't we "unwrap" paragraphs?
            *ppText = (char*) LO_GetSelectionText( m_pContext );
        }
    }
    if ( pTextLen ) *pTextLen = XP_STRLEN( *ppText );
    
    CEditSelection selection;
    EEditCopyType iCopyType;
    CEditTableElement *pTempTable = NULL;
    CEditRootDocElement *pTempRoot = NULL;


    CEditTableElement *pTable = NULL;    

    if( IsTableOrCellSelected() )
    {
        if( m_pSelectedEdTable )
        {
            // Make selection from the entire table
            m_pSelectedEdTable->GetAll(selection);
            iCopyType = eCopyTable;
            pTable = m_pSelectedEdTable;
        } else {
            // Create a new empty table
            pTempTable = new CEditTableElement(0,0);

            if(!pTempTable)
                return EDT_COP_SELECTION_EMPTY;

            CEditTableRowElement *pRow = new CEditTableRowElement();
            if(!pRow)
            {
                delete pTempTable;
                return EDT_COP_SELECTION_EMPTY;
            }
            pRow->InsertAsFirstChild(pTempTable);

            intn iCurrentRow = 1;
            intn iRow = 1;
            intn iMaxCols = 1;
            intn iCols = 0;

            CEditTableCellElement *pCell = GetFirstSelectedCell();
            
            if(!pCell)
            {
            ABORT_COPY:
                delete pTempTable; // This will delete row as well
                return EDT_COP_SELECTION_EMPTY;
            }
            
            pTable = pCell->GetParentTable();
            
            // We need to make a temporary root to our table, 
            //    primarily to supply the current buffer
            //    needed by GetWinCSID() during tag parsing from stream
            pTempRoot = new CEditRootDocElement(this);
            if(!pTempRoot)
                goto ABORT_COPY;

            pTempTable->InsertAsFirstChild(pTempRoot);
                
            // Just in case something tries to relayout
            //    during this. We will be temporarily 
            //    mangling cell pointers, which will 
            //    definitely crash if layout occurs before 
            //    we are fully restored
            m_bNoRelayout = TRUE;

            while( pRow && pCell )
            {
                if( iRow > iCurrentRow )
                {
                    // We are on next row
                    
                    // Save maximum number of columns
                    if( iCols > iMaxCols )
                        iMaxCols = iCols;
                    
                    iCurrentRow = iRow;

                    // Add new row to table
                    pRow = new CEditTableRowElement();
                    if(pRow)
                        pRow->InsertAsLastChild(pTempTable);

                } else {
                    iCols++;
                }
                // Switch cell to current row
                if( pRow )
                    pCell->SwitchLinkage(pRow);

                // Get next selected cell
                pCell = GetNextSelectedCell(&iRow);
            }
            // cmanske: There is a problem with streaming out a table whose last element 
            //   is an empty cell - it fails "local.Intersects(selection)"
            //   in CEditElement::PartialStreamOut
            // I tried adding an extra container+text element after the table, but this didn't work

            // The copy type is the last-selected block???
            //TODO: WRITE SOMETHING TO DETECT SELECTED ROWS/COLS BY INSPECTION
            switch( m_TableHitType )
            {
                case ED_HIT_SEL_ROW:
                    iCopyType = eCopyRows;
                    break;
                case ED_HIT_SEL_COL:
                    iCopyType = eCopyColumns;
                    break;
                default:
                    iCopyType = eCopyCells;
                    break;
            }
            // Set the number of columns and rows in table data
            // Note that we must supply csid:
            //   temp table can't get it 'cause its not part of doc.
            EDT_TableData *pData = pTempTable->GetData();
            if( pData )
            {
                pData->iColumns = iMaxCols;
                pData->iRows = iRow;
                EDT_TableData *pOldData = pTable->GetData();
                if( pOldData )
                {
                    pData->bBorderWidthDefined = pOldData->bBorderWidthDefined;
                    pData->iBorderWidth = pOldData->iBorderWidth;
                    pData->iCellSpacing = pOldData->iCellSpacing;
                    pData->iCellPadding = pOldData->iCellPadding;
                }
                pTempTable->SetData(pData);
                EDT_FreeTableData(pData);
            }
            // Make the selection from the temporary table
            pTempTable->GetAll(selection);
        }
    } else {
        // Get normal selection
        GetSelection(selection);
        iCopyType = eCopyNormal;
    }
    // Build the HTML stream from the selection
    if( !selection.IsEmpty() )
        CopySelectionContents( selection, ppHtml, pHtmlLen, iCopyType );

    if( pTempRoot )
    {
        // Restore original linkage for all selected cells
        CEditTableCellElement *pCell = GetFirstSelectedCell();
        while( pCell )
        {
            pCell->RestoreLinkage();
            pCell = GetNextSelectedCell();
        }
        // Deletes root and table under it
        delete pTempRoot;

        // Restore layout
        m_bNoRelayout = FALSE;
    }
    return result;
}

XP_Bool CEditBuffer::CopySelectionContents( CEditSelection& selection,
                                            char **ppHtml, int32* pHtmlLen, 
                                            EEditCopyType iCopyType )
{
    CStreamOutMemory stream;

    XP_Bool result = CopySelectionContents(selection, stream, iCopyType);

    *ppHtml = stream.GetText();
    *pHtmlLen = stream.GetLen();

    return result;
}

XP_Bool CEditBuffer::CopySelectionContents( CEditSelection& selection,
                                            IStreamOut& stream, EEditCopyType iCopyType )
{
    INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_pContext);
    stream.WriteInt(GetClipboardSignature());
    stream.WriteInt(GetClipboardVersion());
    stream.WriteInt(INTL_GetCSIWinCSID(c));
    //cmanske: New - contains info about what we are copying,
    //  used for tables
    int32 iTemp = iCopyType; // Lets be sure it ends up as an int32!
    stream.WriteInt(iTemp);

    // Never merge at the end of table
    //TODO: Problem: This will be TRUE if we have a "normal" selection
    //   that happens to end in a table, thus the last cell will be merged with
    //   next conainter after insert point
    int32 bMergeEnd = ( iCopyType == eCopyNormal ) ? !selection.EndsAtStartOfContainer() : 0;
    stream.WriteInt(bMergeEnd);

    // If streaming out "temporary" table for cell copying,
    //    we must use it as the root of stream creation since that is 
    //    where elements are examined if they contain the selection elements
    if( iCopyType > eCopyNormal )
    {
        CEditTableElement *pTable = selection.m_start.m_pElement->GetParentTable();
        if( pTable )
            pTable->PartialStreamOut(&stream, selection);
#ifdef DEBUG
        else XP_TRACE(("CEditBuffer::CopySelectionContents:  Failed to find parent table"));
#endif            
    } else {
        m_pRoot->PartialStreamOut(&stream, selection);
    }
    stream.WriteInt( (int32)eElementNone );
    return TRUE;
}

XP_Bool CEditBuffer::CopyBetweenPoints( CEditElement *pBegin,
                    CEditElement *pEnd, char **ppText, int32* pTextLen,
                    char **ppHtml, int32* pHtmlLen ){
    if ( ppText ) *ppText = (char*) LO_GetSelectionText( m_pContext );
    if ( pTextLen ) *pTextLen = XP_STRLEN( *ppText );
    CEditInsertPoint a(pBegin, 0);
    CEditInsertPoint b(pEnd, 0);
    CEditSelection s(a,b);
    return CopySelectionContents(s, ppHtml, pHtmlLen);
}

int32 CEditBuffer::GetClipboardSignature(){
    return 0xc43954af; /* Doesn't mean anything special, but must stay the same. */
}

int32 CEditBuffer::GetClipboardVersion(){
    return 0x040000; /* Should roughly match product version in binary-coded-decimal */
}



// New Table Cut/Paste routines
XP_Bool CEditBuffer::CountRowsAndColsInPasteText(char *pText, intn* pRows, intn* pCols)
{
    intn iRows = 1;
    intn iCols = 1;
    intn iMaxCols = 0;
    XP_Bool bColsOK = TRUE;
    while( *pText )
    {
        char current = *pText;
        char next = *(pText+1);

        if( current == 13 || current == 10 )
        {
            // We have CR            
            // Check if number of cells in this row is same as 
            //   previous. Return this to user
            if(iMaxCols > 0 && iMaxCols != iCols )
                bColsOK = FALSE;

            // Save maximumn columns per row and setup for next row
            if( iCols > iMaxCols )
                iMaxCols = iCols;
            iCols = 1;
            
            // Skip over other next-line chars
            if( (current == 13 && next == 10) || 
                (current == 10 && next == 13) )
            {
                pText++;
            }                    
            pText++;

            // Check if there's anything else after this,
            // Increment rows only there's something else in next row
            if(*pText)
                iRows++;
        } else {
            if( current == 9)
                iCols++;

            pText++;
        }
    }
    if( pRows )
        *pRows = iRows;
    if( pCols )
        *pCols = iMaxCols;

    return bColsOK;
}



//
// Used during parse phase.
//
ED_Alignment CEditBuffer::GetCurrentAlignment(){
    if( GetParseState()->m_formatAlignStack.IsEmpty()){
        return m_pCreationCursor->GetDefaultAlignment();
    }
    else {
        return GetParseState()->m_formatAlignStack.Top();
    }
}

void CEditBuffer::GetSelection( CEditSelection& selection ){
    if ( IsSelected() ) {
        GetSelection( selection.m_start.m_pElement, selection.m_start.m_iPos,
            selection.m_end.m_pElement, selection.m_end.m_iPos,
            selection.m_bFromStart);
    }
    else {
        GetInsertPoint( &selection.m_start.m_pElement, &selection.m_start.m_iPos, &selection.m_start.m_bStickyAfter);
        selection.m_end = selection.m_start;
        selection.m_bFromStart = FALSE;
    }
}

void CEditBuffer::GetSelection( CPersistentEditSelection& persistentSelection ){
    CEditSelection selection;
    GetSelection(selection);
    persistentSelection = EphemeralToPersistent(selection);
}

void CEditBuffer::GetInsertPoint(CEditInsertPoint& insertPoint){
    if ( ! IsSelected() )
    {
        GetInsertPoint( & insertPoint.m_pElement, & insertPoint.m_iPos, & insertPoint.m_bStickyAfter );
    }
    else
    {
        CEditSelection selection;
        GetSelection(selection);
        selection.ExcludeLastDocumentContainerEnd();
        insertPoint = *selection.GetActiveEdge();
    }
}

void CEditBuffer::GetInsertPoint(CPersistentEditInsertPoint& insertPoint){
    CEditInsertPoint pt;
    GetInsertPoint(pt);
    insertPoint = EphemeralToPersistent(pt);
}

void CEditBuffer::SetSelection(CPersistentEditSelection& persistentSelection){
    CEditSelection selection = PersistentToEphemeral(persistentSelection);
    SetSelection(selection);
}

void CEditBuffer::CopyEditText(CEditText& text){
    text.Clear();
    if ( IsSelected() ) {
        XP_Bool copyOK = CopySelection(NULL, NULL, text.GetPChars(), text.GetPLength());
        XP_ASSERT(copyOK);
    }
}

void CEditBuffer::CopyEditText(CPersistentEditSelection& persistentSelection, CEditText& text){
    text.Clear();
    if ( ! persistentSelection.IsInsertPoint() ) {
        CEditSelection selection = PersistentToEphemeral(persistentSelection);
        CopySelectionContents(selection, text.GetPChars(), text.GetPLength());
    }
}

void CEditBuffer::CutEditText(CEditText& text){
    text.Clear();
    CEditSelection selection;
    GetSelection(selection);
    if ( ! selection.IsInsertPoint() ) {
        CutSelectionContents(selection, text.GetPChars(), text.GetPLength());
    }
}

void CEditBuffer::PasteEditText(CEditText& text){
    if ( text.Length() > 0 )
        PasteHTML( text.GetChars(), TRUE);
}

// Persistent to regular selection conversion routines
CEditInsertPoint CEditBuffer::PersistentToEphemeral(CPersistentEditInsertPoint& persistentInsertPoint){
    CEditInsertPoint result = m_pRoot->IndexToInsertPoint(
        persistentInsertPoint.m_index, persistentInsertPoint.m_bStickyAfter);
#ifdef DEBUG_EDITOR_LAYOUT
    // Check for reversability
    CPersistentEditInsertPoint p2 = result.m_pElement->GetPersistentInsertPoint(result.m_iPos);
    XP_ASSERT(persistentInsertPoint == p2);
    // Check for legality.
    if ( result.m_pElement->IsEndOfDocument() &&
        result.m_iPos != 0){
        XP_ASSERT(FALSE);
        result.m_iPos = 0;
    }

#endif
    return result;
}

CPersistentEditInsertPoint CEditBuffer::EphemeralToPersistent(CEditInsertPoint& insertPoint){
    CPersistentEditInsertPoint result = insertPoint.m_pElement->GetPersistentInsertPoint(insertPoint.m_iPos);
#ifdef DEBUG_EDITOR_LAYOUT
    // Check for reversability
    CEditInsertPoint p2 = m_pRoot->IndexToInsertPoint(result.m_index);
    if ( ! ( p2 == insertPoint ||
        insertPoint.IsDenormalizedVersionOf(p2) ) ) {
        // One or the other is empty.
        if ( insertPoint.m_pElement->Leaf()->GetLen() == 0 ||
            p2.m_pElement->Leaf()->GetLen() == 0 ) {
            // OK, do they at least have the same persistent point?
            CPersistentEditInsertPoint pp2 = p2.m_pElement->GetPersistentInsertPoint(p2.m_iPos);
            XP_ASSERT(result == pp2);
        }
        else
        {
            // Just wrong.
            XP_ASSERT(FALSE);
        }
    }
#endif
    return result;
}

CEditSelection CEditBuffer::PersistentToEphemeral(CPersistentEditSelection& persistentSelection){
    CEditSelection selection(PersistentToEphemeral(persistentSelection.m_start),
        PersistentToEphemeral(persistentSelection.m_end),
        persistentSelection.m_bFromStart);
    XP_ASSERT(!selection.m_start.IsEndOfDocument());
    return selection;
}

CPersistentEditSelection CEditBuffer::EphemeralToPersistent(CEditSelection& selection){
    CPersistentEditSelection persistentSelection;
    XP_ASSERT(!selection.m_start.IsEndOfDocument());
    persistentSelection.m_start = EphemeralToPersistent(selection.m_start);
    persistentSelection.m_end = EphemeralToPersistent(selection.m_end);
    persistentSelection.m_bFromStart = selection.m_bFromStart;
    return persistentSelection;
}

void CEditBuffer::AdoptAndDo(CEditCommand* command) {
    DoneTyping();
    GetCommandLog()->AdoptAndDo(command);
}

void CEditBuffer::Undo() {
    DoneTyping();
    GetCommandLog()->Undo();
}

void CEditBuffer::Redo() {
    DoneTyping();
    GetCommandLog()->Redo();
}

void CEditBuffer::Trim() {
    DoneTyping();
    GetCommandLog()->Trim();
}

intn CEditBuffer::GetCommandHistoryLimit() {
    return GetCommandLog()->GetCommandHistoryLimit();
}

void CEditBuffer::SetCommandHistoryLimit(intn newLimit) {
    GetCommandLog()->SetCommandHistoryLimit(newLimit);
}

// Returns NULL if out of range
intn CEditBuffer::GetUndoCommand(intn n) {
    return GetCommandLog()->GetUndoCommand(n);
}

intn CEditBuffer::GetRedoCommand(intn n) {
    return GetCommandLog()->GetRedoCommand(n);
}

void CEditBuffer::BeginBatchChanges(intn id) {
    DoneTyping();
    GetCommandLog()->BeginBatchChanges(id);
}

void CEditBuffer::EndBatchChanges() {
    GetCommandLog()->EndBatchChanges();
}

XP_Bool CEditBuffer::IsWritable(){
#ifdef EDITOR_JAVA
	return ! ( EditorPluginManager_PluginsExist() 
        && EditorPluginManager_IsPluginActive(GetPlugins()));
#else
    return TRUE;
#endif
}

void CEditBuffer::StartTyping(XP_Bool bTyping){
    if ( bTyping && ! m_bTyping ) {
        GetCommandLog()->StartTyping(kTypingCommandID);
        m_bTyping = TRUE;
    }
}

void CEditBuffer::DoneTyping() {
    if ( m_bTyping ) {
#ifdef DEBUG_TYPING
        XP_TRACE(("Done typing."));
#endif
        GetCommandLog()->EndTyping();
    }
    m_bTyping = FALSE;
}

#if 0 
// NOT USED But may be useful?
// This must be fairly efficient, so minimal checking
// Find the cell whose X value is within a cell's horizontal location
// (IGNORE the Y value -- assumes we are already near top of table)
LO_Element* FindClosestCellToTableTop(LO_Element *pLoTable, LO_Element *pEndLoElement, int32 x)
{
    LO_Element *pLoCell = NULL;
    if( pLoTable && pEndLoElement)
    {
        LO_Element *pLoElement = pLoTable->lo_any.next;
        while(pLoElement != pEndLoElement)
        {
            if( pLoElement->lo_any.type == LO_CELL &&
                x >= pLoElement->lo_any.x && x <= (pLoElement->lo_any.x + pLoElement->lo_any.width) )
            {
                return pLoElement;
            }
            pLoElement = pLoElement->lo_any.next;
        }
    }
    return pLoCell;
}

// Find the cell whose Y value is within a cell's vertical location
// (IGNORE the X value -- assumes we are already near left edge of table)
PRIVATE
LO_Element* FindClosestCellToTableLeft(LO_Element *pLoTable, LO_Element *pEndLoElement, int32 y)
{
    LO_Element *pLoCell = NULL;
    if( pLoTable && pEndLoElement)
    {
        LO_Element *pLoElement = pLoTable->lo_any.next;
        while(pLoElement != pEndLoElement)
        {
            if( pLoElement->lo_any.type == LO_CELL &&
                y >= pLoElement->lo_any.y && y <= (pLoElement->lo_any.y + pLoElement->lo_any.height) )
            {
                return pLoElement;
            }
            pLoElement = pLoElement->lo_any.next;
        }
    }
    return pLoCell;
}
#endif

CEditTableCellElement* CEditBuffer::GetFirstCellInCurrentColumn()
{
//    if( !IsInsertPointInTable() )
//        return NULL;

    // Get current leaf element at caret or selection
    // Leaf elements are our only connection to coresponding LO_Elements
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if( pTableCell )
    {
        // Returns cell with EXACT X value (don't include spanned cells)
        return pTableCell->GetFirstCellInColumn(pTableCell->GetX());
    }
    return NULL;
}

CEditTableCellElement* CEditBuffer::GetFirstCellInCurrentRow()
{
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if( pTableCell )
    {
        // Returns cell with EXACT Y value (don't include spanned cells)
        return pTableCell->GetFirstCellInRow(pTableCell->GetY());
    }
    return NULL;
}

PRIVATE
LO_Element *edt_XYToNestedTableAndCell(LO_Element *pStartCell, int32 x, int32 y, LO_Element **ppCellElement)
{
    if( pStartCell == NULL || pStartCell->type != LO_CELL )
        return NULL;

    // Check cell contents list for a nested table
	LO_Element *pTableElement = NULL;
	LO_Element *pCellElement = NULL;
	LO_Element *pCellList = pStartCell->lo_cell.cell_list;
    while( pCellList )
    {
        if( pCellList->type == LO_TABLE &&
		    ((y >= pCellList->lo_any.y) &&
            (y < pCellList->lo_any.y + pCellList->lo_any.y_offset + pCellList->lo_any.height) &&
			(x >= pCellList->lo_any.x) &&
			(x < (pCellList->lo_any.x + pCellList->lo_any.x_offset + pCellList->lo_any.width))) )
        {
            // We are inside a nested table
            pTableElement = pCellList;
            
            // Get first cell in table and search through that cell list
            //   to find deepest nested table and cell enclosing cursor
            LO_Element *pNestedCell = pCellList->lo_any.next;
            XP_ASSERT(pNestedCell->type == LO_CELL);
            while( pNestedCell )
            {
		        if( (y >= pNestedCell->lo_any.y) &&
                    (y < pNestedCell->lo_any.y + pNestedCell->lo_any.y_offset + pNestedCell->lo_any.height) &&
			        (x >= pNestedCell->lo_any.x) &&
			        (x < (pNestedCell->lo_any.x + pNestedCell->lo_any.x_offset + pNestedCell->lo_any.width)) )
                {
                    // We are inside a cell in a nested table
                    // We may stop here, but look recursively into its cell list first
                    pCellElement = pNestedCell;

                    LO_Element *pNestedCell2 = NULL;
                    LO_Element *pNestedTable2 = edt_XYToNestedTableAndCell(pNestedCell, x, y, &pNestedCell2);
                    if( pNestedTable2 )
                    {
                        // We found a deeper nested table
                        pTableElement = pNestedTable2;
                        // This may be NULL if we are in table and not inside a cell in that table
                        pCellElement = pNestedCell2;
                    }
                    // We found the enclosing cell, so stop searching nested table
                    break;
                }
                // Check all cells in the nested table
                pNestedCell = pNestedCell->lo_any.next;
            }
            // Found an enclosing table - done scaning original cell list
            break;
        }
        // Next cell in list
        pCellList = pCellList->lo_any.next;
    }
    // Return deepest table and/or cell found
    if( ppCellElement )
        *ppCellElement = pCellElement;
    
    return pTableElement;
}

static void edt_SetHitLimits(LO_Element *pLoElement,
                             int32 *pLeftLimit, int32 *pTopLimit, int32 *pRightLimit, int32 *pBottomLimit,
                             int32 *pRight, int32 *pBottom, XP_Bool /*bDropLimits*/)
{
    int32 left = pLoElement->lo_any.x;
    int32 top = pLoElement->lo_any.y;
    int32 right = left + pLoElement->lo_any.width;
    int32 bottom = top + pLoElement->lo_any.height;

    // We use this for cell sizing detection in inter_cell_space
    if( pRight )
        *pRight = right;

    if( pBottom )
        *pBottom = bottom;

    if( pLoElement->type == LO_TABLE )
    {
        // We assume that we don't need to worry about too-small tables
        // Include the entire beveled border as hit region
        *pLeftLimit = left + max(ED_SIZING_BORDER, pLoElement->lo_table.border_left_width);
        *pRightLimit = right - max(ED_SIZING_BORDER, pLoElement->lo_table.border_left_width);
        
        *pTopLimit = top + max(ED_SIZING_BORDER, pLoElement->lo_table.border_top_width);
        *pBottomLimit = bottom - max(ED_SIZING_BORDER, pLoElement->lo_table.border_bottom_width);
    } else {
        // Figure sizing regions but reduce if cell is too small
        //  so side and top hit regions are at least ED_SIZING_BORDER wide
        //  This may eliminate corner hit regions if element rect is too small
    
        int32 border;
        if( pLoElement->lo_any.width < 3*ED_SIZING_BORDER )
        {
            border = pLoElement->lo_any.width > ED_SIZING_BORDER ? 
                                                  ((pLoElement->lo_any.width-ED_SIZING_BORDER)/2) : 0;
        } else {
        
            border = ED_SIZING_BORDER;
        }
        *pLeftLimit =  left + border;

        // This reduces the hit region within the cell if the inter-cell space is large enough
        //   to serve as the column sizing hit region
        *pRightLimit =  right - min(border, max(0, ED_SIZING_BORDER - pLoElement->lo_cell.inter_cell_space));

        if( pLoElement->lo_any.width < 3*ED_SIZING_BORDER )
        {
            border = pLoElement->lo_any.height > ED_SIZING_BORDER ? 
                                                  ((pLoElement->lo_any.width-ED_SIZING_BORDER)/2) : 0;
        } else {
            border = ED_SIZING_BORDER;
        }

        *pTopLimit = top + border;
        *pBottomLimit = bottom - border;
    }
}

// Find Table or cell element and specific mouse hit regions,
//  do not look into cells for other element types except another table
ED_HitType CEditBuffer::GetTableHitRegion(int32 x, int32 y, LO_Element **ppElement, XP_Bool bModifierKeyPressed)
{
    // Signal to use last-selected element and hit type
    if( x <= 0 )
    {
        if( ppElement )
            *ppElement = m_pSelectedTableElement;
        return m_TableHitType;
    }

    // Initialize in case we don't find any table element
    if( ppElement)
        *ppElement = NULL;

    // From LO_XYToElement
	int32 doc_id = XP_DOCID(m_pContext);
	lo_TopState *top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
		return ED_HIT_NONE;

	lo_DocState *state = top_state->doc_state;

	LO_Element *pLoElement = NULL;
	LO_Element *pEndLoElement = NULL;
	LO_Element *pTableElement = NULL;
	LO_Element *pCellElement = NULL;
    int32 left_limit, right_limit, top_limit, bottom_limit, right, bottom;
    int32 xPlus = x + ED_SIZING_BORDER;
    XP_Bool     bAboveOrBelowTable = FALSE;

	int32 line = lo_PointToLine(m_pContext, state, x, y);
	int32 lineBelow = lo_PointToLine(m_pContext, state, x, y + ED_SIZING_BORDER);
	int32 lineAbove = lo_PointToLine(m_pContext, state, x, y - ED_SIZING_BORDER);
  
    if( lineBelow != line || lineAbove != line )
    {
        // We are near boundary between two lines,
        //   check if we are above or below a table
        // THIS ASSUMES TABLE IS FIRST ELEMENT ON lineBelow or lineAbove
        //   AND IS TALLEST ELEMENT IN THE LINE
        if( lineBelow != line )
            lo_GetLineEnds(m_pContext, state, lineBelow, &pLoElement, &pEndLoElement);
        
        if( lineAbove != line )
            lo_GetLineEnds(m_pContext, state, lineAbove, &pLoElement, &pEndLoElement);
         
        if( pLoElement && pLoElement->type == LO_TABLE &&
            (xPlus >= pLoElement->lo_any.x) &&
			(x < (pLoElement->lo_any.x + pLoElement->lo_any.x_offset 
                    + pLoElement->lo_any.width + ED_SIZING_BORDER)) )
        {
            pTableElement = pLoElement;
            bAboveOrBelowTable = TRUE;
        }
    }

    if( !bAboveOrBelowTable )
    {
        lo_GetLineEnds(m_pContext, state, line, &pLoElement, &pEndLoElement);

	    while (pLoElement != pEndLoElement)
	    {
			if (pLoElement->type == LO_TABLE)
			{
                if ((y >= pLoElement->lo_any.y) &&
                    (y <= pLoElement->lo_any.y + pLoElement->lo_any.y_offset + pLoElement->lo_any.height) &&
			        (xPlus >= pLoElement->lo_any.x) &&
			        (x <= (pLoElement->lo_any.x + pLoElement->lo_any.x_offset + pLoElement->lo_any.width + ED_SIZING_BORDER)))
                {
                    // Save the table element
                    pTableElement = pLoElement;
                }
			}
		    else if( pLoElement->type == LO_CELL )
            {
                // Note that we include the inter-cell space when testing if inside a cell
                if ((y >= pLoElement->lo_cell.y) &&
                    (y <= pLoElement->lo_cell.y + pLoElement->lo_cell.y_offset + pLoElement->lo_any.height +
                            pLoElement->lo_cell.inter_cell_space ) &&
			        (x >= pLoElement->lo_cell.x) &&
			        (x <= (pLoElement->lo_cell.x + pLoElement->lo_cell.x_offset + pLoElement->lo_any.width +
                            pLoElement->lo_cell.inter_cell_space )))
		        {

                    pCellElement = pLoElement;

                    // Search its contents for nested table and cell
                    LO_Element *pNestedCell = NULL;
                    LO_Element *pNestedTable = edt_XYToNestedTableAndCell(pLoElement, x, y, &pNestedCell);
                    if( pNestedTable )
                    {
                        pTableElement = pNestedTable;
                        pCellElement = pNestedCell;
                    }
                    // We're done once we find the enclosing cell
                    break;
		        }

            }
		    pLoElement = pLoElement->lo_any.next;
	    }
    }

    // Now check where in Table and Cell the cursor is over
    if (pTableElement)
    {
        // First check for cell hit regions
        if(pCellElement)
        {
            // Return the cell found, even if result is ED_HIT_NONE
            if(ppElement) *ppElement = pCellElement;
            
            // Set test limits for Cell element
            edt_SetHitLimits(pCellElement, &left_limit, &top_limit, &right_limit, &bottom_limit, &right, &bottom, FALSE );
                        
            // We are in multiple cell selection mode,
            //  so select the cell automatically,
            //  but be sure we are not in inter-cell area (too confusing)
            //  Also leave area near bottom for drag selection
            if( m_SelectedLoCells.Size() && bModifierKeyPressed &&
                x <= right && y <= bottom_limit )
            {
                return ED_HIT_SEL_CELL;
            }
            
            if( x <= left_limit )
            {
                // Leave left edge for placing cursor
                return ED_HIT_NONE;
            }
            if( x >= right_limit )
            {
                if( x >= right_limit ||  // Anywhere in inter-cell space to the right of the cell
                   (y >= top_limit && y <= bottom_limit) ) // Inside cell, near right edge, excluding corners
                {
                    // If a selected cell - drag selection
                    if( pCellElement->lo_cell.ele_attrmask & LO_ELE_SELECTED )
                        return ED_HIT_DRAG_TABLE;

                    return ED_HIT_SIZE_COL;
                }
                
                if( y >= bottom_limit )
                    // Lower right corner - leave for sizing object in cell
                    return ED_HIT_NONE;
            }

            if( y <= top_limit )
            {
                // Along top edge
                if( x <= right_limit )
                {
                    // Near top border of cell, excluding corners
                    return ED_HIT_SEL_CELL;
                }
                // Upper right corner - leave for sizing image flush against borders
                return ED_HIT_NONE;
            }

            if( y >= bottom_limit )  // Along bottom edge (including intercell region)
            {
                // If a selected cell - drag selection
                if( pCellElement->lo_cell.ele_attrmask & LO_ELE_SELECTED )
                    return ED_HIT_DRAG_TABLE;
                
                // Cell is not selected - size the row
                return ED_HIT_SIZE_ROW;
            }


            // Inside of a cell and no hit regions
            return ED_HIT_NONE;
        }

        // Return the table element
        if( ppElement) *ppElement = pTableElement;

        // Set test limits for Table element
        edt_SetHitLimits(pTableElement, &left_limit, &top_limit, &right_limit, &bottom_limit, 0, 0, FALSE );

        if( x <= left_limit )
        {
            // Within left border of table
            if( y <= top_limit )
            {
                // Upper left corner
                if( bModifierKeyPressed )
                    return ED_HIT_SEL_ALL_CELLS;

                return ED_HIT_SEL_TABLE; 
            }
            if( y >= bottom_limit )
                // Lower left corner
                return ED_HIT_ADD_ROWS;
            
            // Along left edge of table - select row
            if( ppElement )
            { 
                *ppElement = lo_GetFirstCellInColumnOrRow(m_pContext, pTableElement, x, y, GET_ROW, NULL);
            }
            return ED_HIT_SEL_ROW;
        } 
        else if( x >= right_limit )
        {
            if( y >= bottom_limit )
                // Lower right corner
                return ED_HIT_ADD_COLS;

            // Along right edge
            return ED_HIT_SIZE_TABLE_WIDTH;
        }
        else if( y >= bottom_limit )
        {
            // Below bottom, excluding corners
            return ED_HIT_SIZE_TABLE_HEIGHT;
        }

        if( /*y >= top && */ y <= top_limit )    // CHECK THIS 
        {
            // Along top border - select column
            if( ppElement)
            { 
                *ppElement = lo_GetFirstCellInColumnOrRow(m_pContext, pTableElement, x, y, GET_COL, NULL);
            }
            return ED_HIT_SEL_COL;
        }
    }
    return ED_HIT_NONE;
}

ED_DropType CEditBuffer::GetTableDropRegion(int32 *pX, int32 *pY, int32 *pWidth, int32 *pHeight, LO_Element **ppElement)
{
    if( !m_pDragTableData || !pX || !pY || !pWidth || !pHeight )
        return ED_DROP_NORMAL;

    ED_HitType  iSourceType = m_pDragTableData->iSourceType;
    ED_DropType iDropType = ED_DROP_NORMAL;

    // Initialize in case we don't find a table element
    if( ppElement)
        *ppElement = NULL;

    // Filter out types that are not applicable
    if( ! (iSourceType == ED_HIT_SEL_COL  ||
           iSourceType == ED_HIT_SEL_ROW  || 
           iSourceType == ED_HIT_SEL_CELL ||
           iSourceType == ED_HIT_SEL_ALL_CELLS) )
    {
        return ED_DROP_NORMAL;
    }


    // From LO_XYToElement
	int32 doc_id = XP_DOCID(m_pContext);
	lo_TopState *top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
		return ED_DROP_NORMAL;

	lo_DocState *state = top_state->doc_state;

	LO_Element *pLoElement = NULL;
	LO_Element *pEndLoElement;
	LO_Element *pTableElement = NULL;
	LO_Element *pCellElement = NULL;
    LO_Element *pFirstCell = NULL;
    XP_Bool     bLastCellInRow = FALSE;
    int32 x = *pX;
    int32 y = *pY;
	int32 line = lo_PointToLine(m_pContext, state, x, y);

    int32 iLeft, iTop, iRight, iBottom;
    int32 left_limit, right_limit, top_limit, bottom_limit, right, bottom;
  
    lo_GetLineEnds(m_pContext, state, line, &pLoElement, &pEndLoElement);

	while (pLoElement != pEndLoElement)
	{
		if (pLoElement->type == LO_TABLE)
		{
            // Save table if Y value is ANYWHERE inside the table
            // (if outside this, then there can't be a table on the line)
            if( (y >= pLoElement->lo_any.y) &&
                (y <= pLoElement->lo_any.y + pLoElement->lo_any.y_offset + pLoElement->lo_any.height) )
            {
                pTableElement = pLoElement;
                LO_TableStruct *pTable = (LO_TableStruct*)pTableElement;

                // Snap cursor coordinates to bounding rect of all cells in the table
                //  so we find correct cell in first pass when cursor is to the left
                //  or right of the this rect (in table borders or margins before and after table)
                // (We will not find a cell only when there are ragged-right rows in the table)

                iLeft = pTable->x + pTable->x_offset + pTable->border_left_width + pTable->inter_cell_space;
                iRight = pTable->x + pTable->x_offset + pTable->width;
                iTop = pTable->y + pTable->y_offset + pTable->border_top_width + pTable->inter_cell_space;
                iBottom = pTable->y + pTable->y_offset + pTable->height;

                x = min(iRight, max(x, iLeft));
                y = min(iBottom, max(y, iTop));
            }
		}
		else if( pLoElement->type == LO_CELL )
        {
            // Save the first cell
            if( !pFirstCell )
                pFirstCell = pLoElement;

            int32 iCellRight;
            LO_Element *pNextCell = pLoElement->lo_any.next;
            while( pNextCell && pNextCell->type != LO_CELL ) pNextCell = pNextCell->lo_any.next;
            
            if( pNextCell && pNextCell->lo_cell.x < pLoElement->lo_cell.x )
            {
                // This is last cell in current row, so we can consider inserting at AFTER
                bLastCellInRow = TRUE;
                // Extend hit region to rightmost edge of cell area
                iCellRight = iRight;
            }
            else 
            {
                iCellRight = pLoElement->lo_cell.x + pLoElement->lo_cell.x_offset + pLoElement->lo_any.width;
            }   

            // Note that we include the inter-cell space BEFORE the cell when testing if inside a cell
            if ((y >= pLoElement->lo_cell.y - pLoElement->lo_cell.inter_cell_space) &&
                (y <= pLoElement->lo_cell.y + pLoElement->lo_cell.y_offset + pLoElement->lo_any.height) &&
			    (x >= pLoElement->lo_cell.x - pLoElement->lo_cell.inter_cell_space) &&
			    (x <= iCellRight))
		    {

                pCellElement = pLoElement;

                // Search its contents for nested table and cell
                LO_Element *pNestedCell = NULL;
                LO_Element *pNestedTable = edt_XYToNestedTableAndCell(pLoElement, x, y, &pNestedCell);
                if( pNestedTable )
                {
                    pTableElement = pNestedTable;
                    pCellElement = pNestedCell;
                }
                // We're done once we find the enclosing cell
                break;
		    }
        }
		pLoElement = pLoElement->lo_any.next;
	}

    if( pTableElement && pFirstCell )
    {
#if 0
// TODO: FINISH THIS!
        if( !pCellElement )
        {
            // Get bounding sides of all cells in table
            LO_Element *pLastCellInRow = NULL;
            LO_Element *pLastCellInCol = NULL;
            lo_GetFirstAndLastCellsInColumnOrRow(pCellElement, &pLastCellInRow, FALSE );
            lo_GetFirstAndLastCellsInColumnOrRow(pCellElement, &pLastCellInCol, TRUE );
            if( !pLastCellInRow || !pLastCellInCol )
                return ED_DROP_NORMAL; 
            int32 iLeft = pFirstCell->lo_any.x;
            int32 iTop =  pFirstCell->lo_any.y;
            int32 iRight = pLastCellInRow->lo_any.x + pLastCellInRow->lo_any.width;
            int32 iBottom = pLastCellInRow->lo_any.y + pLastCellInRow->lo_any.height;
            
            XP_Bool bFindBefore = FALSE;
            XP_Bool bFindAfter = FALSE;
            XP_Bool bFindAbove = FALSE;
            XP_Bool bFindBelow = FALSE;
            if( iSourceType != ED_HIT_SEL_COL )
            {
                if( y < pFirstCell->lo_any.y )
                    bFindAbove = TRUE;
                else {
                    bFindBelow = TRUE;
            }
            if( iSourceType != ED_HIT_SEL_ROW )
            {
                if( x < pFirstCell->lo_any.x )
                    bFindBefore = TRUE;
                else
                    bFindAfter = TRUE;
            }
            if(! bFindBefore || !bFindAbove || !bFindAfter || !bFindBelow )
            {
                if( 
            }

            LO_Element *pLoElement = pLoTable->lo_any.next;
            int32 iMaxX = 0;
            int32 iMaxY = 0;

            // Find cell closest to table edge we figured out above
            while( pLoElement && pLoElement->type != LO_LINEFEED)
            {
                
                if( pLoElement->lo_any.type == LO_CELL )
                {
                    if( y >= pLoElement->lo_any.y && y <= (pLoElement->lo_any.y + pLoElement->lo_any.height) )
                    {
                        if( bFindBefore )
                        {
                            pCellElement = pLoElement;
                            break;
                        }
                        if( 
                    }
                }
                pLoElement = pLoElement->lo_any.next;
            }
            if( !pCellElement )
            {
            }
        }
#endif
        if(pCellElement)
        {
            if(ppElement) *ppElement = pCellElement;

            // Return signal that we shouldn't drop here,
            //   we are exactly over the source selection
            if( pCellElement == m_pDragTableData->pFirstSelectedCell )
                return ED_DROP_NONE;
            
            LO_Element *pBefore = pCellElement->lo_any.prev;
            while( pBefore->type != LO_CELL && pBefore->type != LO_TABLE )
                pBefore = pBefore->lo_any.prev;

            // Save cell before only if it is first cell of selection being dragged
            if( pBefore->type == LO_TABLE || 
                (pBefore->type == LO_CELL && pBefore->lo_cell.x >= pCellElement->lo_cell.x) )
            {
                pBefore = NULL;
            }

            // Also don't allow dropping under other circumstances
            if( iSourceType == ED_HIT_SEL_COL && 
                (pBefore != NULL && pBefore == m_pDragTableData->pFirstSelectedCell) )
            {
                return ED_DROP_NONE;        
            }
            
            // Set test limits for Cell element: TRUE = get limits for Drag/Drop
            edt_SetHitLimits(pCellElement, &left_limit, &top_limit, &right_limit, &bottom_limit, &right, &bottom, TRUE );
                        
            *pX = pCellElement->lo_cell.x;
            *pY = pCellElement->lo_cell.y;
            int32 iExtra = pCellElement->lo_cell.inter_cell_space - ED_SIZING_BORDER;

            if( iSourceType != ED_HIT_SEL_ROW && (x <= left_limit || x > right) )
            {
                // Position the feedback in the center of intercell region
                //   or just before cell if intercell is too narrow
                *pWidth = ED_SIZING_BORDER;
                *pHeight = pCellElement->lo_cell.height;
                if( x > right )
                {
                    iDropType = ED_DROP_INSERT_AFTER;
                    *pX += pCellElement->lo_cell.width;
                    if( iExtra > 1 )
                        *pX += iExtra/2;
                } else {
                    iDropType = ED_DROP_INSERT_BEFORE;
                    if( iExtra > 1 )
                        *pX -= (iExtra/2 + ED_SIZING_BORDER);
                    else
                        *pX -= ED_SIZING_BORDER;
                }
            }
            if( iDropType == ED_DROP_NORMAL && iSourceType != ED_HIT_SEL_COL && (y <= top_limit || y > bottom) )
            {
                *pWidth = pCellElement->lo_cell.width;
                *pHeight = ED_SIZING_BORDER;;
                if( y > bottom )
                {
                    iDropType = ED_DROP_INSERT_BELOW;
                    *pY += pCellElement->lo_cell.height;
                    if( iExtra > 1 )
                        *pY += iExtra/2;
                } else {
                    iDropType = ED_DROP_INSERT_ABOVE;
                    if( iExtra > 1 )
                        *pY -= (iExtra/2 + ED_SIZING_BORDER);
                    else
                        *pY -= ED_SIZING_BORDER;
                }
            }
            if( iDropType == ED_DROP_NORMAL )
            {
                // Inside of a cell and no hit regions
                iDropType = ED_DROP_REPLACE_CELL;
                *pWidth = pCellElement->lo_cell.width;
                *pHeight = pCellElement->lo_cell.height;
            }
        }
    }
    return iDropType;
}

//
// Table and Cell selection 
//

// Internal version to select table cell
XP_Bool CEditBuffer::SelectTableElement(CEditTableCellElement *pCell, ED_HitType iHitType)
{
    if( pCell )
    {
        return SelectTableElement( pCell->GetX(), pCell->GetY(), 
                                   (LO_Element*)GetLoCell((CEditElement*)pCell),
                                   iHitType, FALSE, FALSE );
    }
    return FALSE;
}

// The only table and cell selection method exposed to FEs
XP_Bool CEditBuffer::SelectTableElement( int32 x, int32 y, LO_Element *pLoElement, ED_HitType iHitType,
                                         XP_Bool bModifierKeyPressed, XP_Bool bExtendSelection )
{
    if( iHitType == ED_HIT_NONE )
        return FALSE;

    // We will drag existing selection - so don't change selection
    if( iHitType == ED_HIT_DRAG_TABLE )
        return TRUE;

    // If not supplied, try to get appropriate element 
    //   and coordinates from current edit element
    if( !pLoElement )
        pLoElement = GetCurrentLoTableElement( iHitType, &x, &y );

    if( !pLoElement )
    {
        return FALSE;
    }

    // Properly end current non-cell selection if in progress
    if( IsSelecting() )
        EndSelection();

    // Extend selection from previous cell element
    //  to new element if SHIFT key is pressed
    if( bExtendSelection &&
        (iHitType == ED_HIT_SEL_COL ||
         iHitType == ED_HIT_SEL_ROW ||
         iHitType == ED_HIT_SEL_CELL) )
    {
        return ED_HIT_NONE != ExtendTableCellSelection(x,y);
    }
    m_pSelectedTableElement = NULL;
    m_TableHitType = ED_HIT_NONE;
    m_pPrevExtendSelectionCell = NULL;
    
    if( pLoElement->type == LO_TABLE &&
        (iHitType == ED_HIT_SEL_TABLE  ||
         iHitType == ED_HIT_SIZE_TABLE_WIDTH ||
         iHitType == ED_HIT_ADD_ROWS   ||
         iHitType == ED_HIT_ADD_COLS) )
    {
        LO_TableStruct *pLoTable =  &pLoElement->lo_table;
        // Remove any existing table or cell selection
        ClearTableAndCellSelection();

        CEditTableElement *pEdTable = SelectTable(TRUE, pLoTable);
        if( pEdTable )
        {
            if( IsSelected() )
                ClearSelection();

            // Move the caret to the first cell in table being selected
            CEditElement *pLeaf = pEdTable->FindNextElement(&CEditElement::FindLeafAll,0 );
            if( pLeaf )
            {
                ClearPhantomInsertPoint();
                ClearMove();
                FE_DestroyCaret(m_pContext);
                SetInsertPoint( pLeaf->Leaf(), 0, FALSE );
            }

            // Save selected element data
            m_pSelectedTableElement = pLoElement;
            m_TableHitType = ED_HIT_SEL_TABLE;
            return TRUE;
        } else {
            return FALSE;
        }
    }
    
    if( bModifierKeyPressed )
    {
        // Remove just the selection of a table
        //  since that can never be appended
        SelectTable(FALSE);
    } else {
        ClearTableAndCellSelection();
    }

    if( iHitType == ED_HIT_SEL_CELL )
    {
        if( pLoElement->type != LO_CELL )
            return FALSE;

        // We need to set this so mouse-move event
        //  doesn't wipe out appended cell selections
        m_pPrevExtendSelectionCell = pLoElement;

        // If cell was already selected and "Append" key is pressed,
        //  UNSELECT that cell (First param = FALSE)
        XP_Bool bSelect = !(bModifierKeyPressed && (pLoElement->lo_cell.ele_attrmask & LO_ELE_SELECTED));
        CEditTableCellElement *pEdCell = SelectCell( bSelect, &pLoElement->lo_cell, NULL );
        if( pEdCell )
        {
            if( IsSelected() )
                ClearSelection();

            // Move the caret to the cell being selected
            SetTableInsertPoint(pEdCell);
            if( bSelect )
            {
                // Save selected element data if selecting cell
                m_pSelectedTableElement = pLoElement;
            }
            // Set selection type
            // Note: Unselecting a cell will discontinue existing row or column selection
            // For efficiency, don't try to figure out if extending cell selection
            //   now results in a full row or column -- do that in GetTableCellData();
            m_TableHitType = ED_HIT_SEL_CELL;
            
            SortSelectedCells();
            return TRUE;
        } else {
            return FALSE;
        }
    }

    XP_Bool bSelRow = iHitType == ED_HIT_SEL_ROW;
    XP_Bool bSelCol = iHitType == ED_HIT_SEL_COL;

    CEditTableCellElement * pEdCell = NULL;

    if( bSelCol || bSelRow )
    {
        // Find cell closest to the given X or Y so that column or row
        //   doesn't include ROWSPAN or COLSPAN "spillover" cells
        LO_Element *pLastCellElement = NULL;
        
        LO_Element *pElement = lo_GetFirstCellInColumnOrRow(m_pContext, pLoElement, x, y, bSelCol, &pLastCellElement );

        int32 iColLeft, iRowTop;
        if( !pElement )
            return FALSE;
        if( bSelCol )
            // Left side defines the column
            iColLeft = pElement->lo_any.x;
        else
            // Top defines the row
            iRowTop = pElement->lo_any.y;

        //while( pElement && pElement != pLastCellElement )
        XP_Bool bDone = FALSE;
        do {             
            if( pElement && pElement->lo_any.type == LO_CELL )
            {
                // Select cells in a column (cells that share left border value)
                //  or a row (all cells sharing top border value)
                if( (bSelCol && iColLeft == pElement->lo_any.x) ||
                    (bSelRow && iRowTop == pElement->lo_any.y) )
                {
                    // Select the cell - returns the corresponding Edit Cell object
                    CEditTableCellElement * pSelectedCell = SelectCell(TRUE, &pElement->lo_cell, NULL);

                    // Save the edit cell and other data 
                    if( pElement == pLoElement )
                    {
                        pEdCell = pSelectedCell;
                        m_pSelectedTableElement = pElement;
                        m_pPrevExtendSelectionCell = pElement;
                    }
                }
            }
            bDone = (pElement == NULL) || (pElement == pLastCellElement);
            pElement = pElement->lo_any.next;
        } while( !bDone );

        if( IsSelected() )
            ClearSelection();
        
        if( pEdCell )
        {
            // Move caret to edit cell 
            SetTableInsertPoint(pEdCell);

            
            // Decide new selection type
            // This will not be entirely correct if
            //  we are appending to an existing selection
            if( bModifierKeyPressed && m_TableHitType != ED_HIT_NONE && 
                m_TableHitType != iHitType )
            {
                // We are appending to a previous selection
                //  that is NOT of the same type,
                //  then we can't assume total selection is all rows or all columns
                // But leave "All cells" type as is
                if( !(m_TableHitType == ED_HIT_SEL_ALL_CELLS &&
                     iHitType == ED_HIT_SEL_ROW ) )
                {
                    m_TableHitType = ED_HIT_SEL_CELL;
                }
            } else {
                m_TableHitType = iHitType;
            }

            SortSelectedCells();

            return TRUE;
        }
#ifdef DEBUG_akkana
        printf("CEditBuffer::SelectTableElement(): Ugh -- falling through!\n");
#endif /* DEBUG_akkana */
    }

    if( iHitType == ED_HIT_SEL_ALL_CELLS )
    {
        LO_Element *pLastCellElement = NULL;
        LO_Element *pElement = lo_GetFirstAndLastCellsInTable(m_pContext, pLoElement, &pLastCellElement );
        
        XP_Bool bDone = FALSE;
        do {             
            if( pElement && pElement->lo_any.type == LO_CELL )
            {
                // Select the cell - returns the corresponding Edit Cell object
                CEditTableCellElement * pSelectedCell = SelectCell(TRUE, &pElement->lo_cell, NULL);

                // Save the edit cell and other data 
                if( pElement == pLoElement )
                {
                    pEdCell = pSelectedCell;
                    m_pSelectedTableElement = pElement;
                    m_pPrevExtendSelectionCell = pElement;
                }
            }
            bDone = (pElement == pLastCellElement);
            pElement = pElement->lo_any.next;
        } while( pElement && !bDone );
        
        if( pEdCell )
        {
            // Move caret to edit cell 
            SetTableInsertPoint(pEdCell);
            // Assume we are selected as "rows"
            m_TableHitType = ED_HIT_SEL_ALL_CELLS;
            SortSelectedCells();
            return TRUE;
        }
    }

    // Not likely - no hit types found
    return FALSE;
}

void CEditBuffer::SelectBlockOfCells(LO_CellStruct *pLastCell)
{
    if( !pLastCell )
        return;

    // Clear any existing selected cells
    for( int i = m_SelectedLoCells.Size()-1; i >= 0; i-- )
    {
        SelectCell(FALSE, m_SelectedLoCells[i], m_SelectedEdCells[i]);
    }

    if( m_pSelectedTableElement && m_pSelectedTableElement->type == LO_CELL )
    {
        LO_CellStruct *pFirstCell = (LO_CellStruct*)m_pSelectedTableElement;
        int32 xMin = min(pFirstCell->x, pLastCell->x);
        int32 yMin = min(pFirstCell->y, pLastCell->y);
        int32 xMax = max(pFirstCell->x, pLastCell->x);
        int32 yMax = max(pFirstCell->y, pLastCell->y);
    
        // Scan back to first cell in table
        LO_Element *pElement = m_pSelectedTableElement;
        do {
            pElement = pElement->lo_any.prev;
        } while (pElement->type != LO_TABLE ); 
    
	    // Scan forward and select all cells within desired coordinates
        pElement = pElement->lo_any.next;
        do {
            LO_CellStruct *pLoCell = (LO_CellStruct*)pElement;
            if( pLoCell->x >= xMin && pLoCell->x <= xMax &&
                pLoCell->y >= yMin && pLoCell->y <= yMax )
            {
                // Select the cell (3rd param: find EditCell, 4th: append selection)
                SelectCell(TRUE, pLoCell, NULL /*, TRUE*/);
            }
            pElement = pElement->lo_any.next;
        } while (pElement && pElement->type == LO_CELL); 
    }
    else 
    {
        // No previous selected cell - select just given cell
        SelectCell(TRUE, pLastCell, NULL/*, TRUE*/);
    }
}

ED_HitType CEditBuffer::ExtendTableCellSelection(int32 x, int32 y)
{
    LO_Element *pLoElement = NULL;
    // Get the cell we are currently over
    ED_HitType iHitType = GetTableHitRegion(x, y, &pLoElement, TRUE);

    // Eliminate incompatable conditions to extend the selection
    if( !pLoElement ||
        !(iHitType == ED_HIT_SEL_ROW ||
          iHitType == ED_HIT_SEL_COL ||
          iHitType == ED_HIT_SEL_CELL ) ||
        !(m_TableHitType == ED_HIT_SEL_ROW ||
          m_TableHitType == ED_HIT_SEL_COL ||
          m_TableHitType == ED_HIT_SEL_CELL ) ||
        (m_TableHitType == ED_HIT_SEL_ROW &&
         iHitType == ED_HIT_SEL_COL) ||
        (m_TableHitType == ED_HIT_SEL_COL &&
         iHitType == ED_HIT_SEL_ROW) ||
        (m_TableHitType == ED_HIT_SEL_CELL &&
         iHitType != ED_HIT_SEL_CELL) )
    {
        return ED_HIT_NONE;
    }

    // Range of selected cells
    LO_Element *pFirstLoCell = NULL;
    LO_Element *pLastLoCell = NULL;

    if( m_TableHitType == ED_HIT_SEL_CELL )
    {
        // We are extending a previous cell selection
        pFirstLoCell = pLastLoCell = pLoElement;
    }
    else
    {
        // Get the last cell of row or column
        pFirstLoCell = lo_GetFirstAndLastCellsInColumnOrRow(pLoElement, &pLastLoCell, m_TableHitType == ED_HIT_SEL_COL);
    }

    if( pFirstLoCell && pFirstLoCell != m_pPrevExtendSelectionCell )
    {
        // Reselect block starting from previous start to end of current addition
        SelectBlockOfCells( (LO_CellStruct*)pLastLoCell );
        
        // Remember the first cell
        // We must use first instead of last so SelectTableElement
        //   can use the result of GetTableHitRegion (which is first cell of col or row)
        //   to suppress conflicts between setting first selection and extending selections 
        m_pPrevExtendSelectionCell = pFirstLoCell;
    }
    // Return same select type as we used before
    return m_TableHitType;
}

LO_Element* CEditBuffer::GetCurrentLoTableElement(ED_HitType iHitType, int32 *pX, int32 *pY)
{
    // Get current leaf element at caret or selection
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    if( ip.m_pElement && ip.m_pElement->GetLayoutElement() )
    {
        // Table select hit areas
        if( iHitType == ED_HIT_SEL_TABLE  ||
            iHitType == ED_HIT_SIZE_TABLE_WIDTH ||
            iHitType == ED_HIT_ADD_ROWS   ||
            iHitType == ED_HIT_ADD_COLS )
        {
            LO_TableStruct *pLoTable = lo_GetParentTable(m_pContext, ip.m_pElement->GetLayoutElement());
            if( pLoTable )
            {
                *pX = pLoTable->x+1;
                *pY = pLoTable->y+1;
            }
            return (LO_Element*)pLoTable;
        }

        LO_CellStruct *pLoCell = lo_GetParentCell(m_pContext, ip.m_pElement->GetLayoutElement());
        if( pLoCell )
        {
            if( iHitType == ED_HIT_SEL_CELL || iHitType == ED_HIT_SEL_ALL_CELLS )
            {
                *pX = pLoCell->x+1;
                *pY = pLoCell->y+1;
                return (LO_Element*)pLoCell;
            }
            if( iHitType == ED_HIT_SEL_ROW || iHitType == ED_HIT_SEL_COL )
            {
                // Find the first cell of the row or column
                *pX = pLoCell->x+1;
                *pY = pLoCell->y+1;
                return (LO_Element*)pLoCell;
            }
        }
    }
    return NULL;
}

// Get the corresponding CEditTableElement if LoElementType == LO_TABLE,
//   or CEditTableCellElement if LoElementType = LO_CELL
CEditElement* CEditBuffer::GetTableElementFromLO_Element(LO_Element *pLoElement, int16 LoElementType)
{
    if( pLoElement &&
        (LoElementType == LO_TABLE ||
         LoElementType == LO_CELL) )
    {
        LO_Element *pTemp = pLoElement;

        // The editor assures that every cell has an editable element in it
        // so find the first CEditElement and get CEditTable or CEditTableCell
        //   object containing it
        while ( pTemp != NULL )
        {
            // Find first cell (should hit immediately if pLoElement is a cell,
            //   else looks for first cell if pLoElement is a table
            if( pTemp->lo_any.type == LO_CELL )
            {
                LO_Element *pCellElement  = pTemp->lo_cell.cell_list;

	            // Find first editable element inside the cell
                while( pCellElement )
	            {
                    if( pCellElement->lo_any.edit_element )
                        break;
                    // TODO: CHECK IF CELL CONTAINS ANOTHER TABLE HERE?

		            pCellElement = pCellElement->lo_any.next;
	            }
                if(!pCellElement )
                {
                    // None found - should not happen
                    XP_ASSERT(FALSE);
                    return NULL;
                }
            
                if( LoElementType == LO_TABLE )
                    return pCellElement->lo_any.edit_element->GetTableIgnoreSubdoc();
                else
                    return pCellElement->lo_any.edit_element->GetTableCellIgnoreSubdoc();
            }
            
            pTemp = pTemp->lo_any.next;
        }
    }
    // We should always find an Edit element
    XP_ASSERT(FALSE);
    return NULL;
}

LO_CellStruct* CEditBuffer::GetLoCell(CEditElement *pEdElement)
{
    // Get closest leaf element so we can find the corresponding layout element
    if( !pEdElement || !pEdElement->IsInTableCell() )
        return NULL;
    if( !pEdElement->IsLeaf() )
    {
        pEdElement = pEdElement->FindNextElement(&CEditElement::FindLeafAll,0 );
    }
    if( pEdElement && pEdElement->Leaf()->GetLayoutElement() )
    {    
        return lo_GetParentCell(m_pContext, pEdElement->Leaf()->GetLayoutElement());
    }
    return NULL;
}

CEditTableElement* CEditBuffer::SelectTable(XP_Bool bSelect, LO_TableStruct *pLoTable,
                              CEditTableElement *pEdTable)
{
    // We can allow NULL pointer for table if we are just unselecting existing table
    if( bSelect && !pLoTable )
    {
        XP_ASSERT(FALSE);
        return NULL;
    }
    // Assume we are deselecting current table if no pointers supplied
    if( !bSelect && !pLoTable && !pEdTable )
    {
        pLoTable = m_pSelectedLoTable;
        pEdTable = m_pSelectedEdTable;
    }
    if( !pLoTable )
        return NULL;

    XP_Bool bWasSelected = bSelect ? pLoTable->ele_attrmask & LO_ELE_SELECTED : FALSE;

    // Clear existing selected table if different
    if( m_pSelectedLoTable && pLoTable != m_pSelectedLoTable )
    {
        m_pSelectedLoTable->ele_attrmask &= ~LO_ELE_SELECTED;

        FE_DisplayEntireTableOrCell(m_pContext, (LO_Element*)m_pSelectedLoTable);
        m_pSelectedLoTable = NULL;

        // We should always have the matching CEditTableElement
        XP_ASSERT(m_pSelectedEdTable);

        m_pSelectedEdTable = NULL;
    }
    if( !pLoTable )
        return NULL;

    XP_TRACE(("Table width = %d", pLoTable->width));

    // Get Edit table element if not supplied
    if( !pEdTable )
        pEdTable = (CEditTableElement*)GetTableElementFromLO_Element( (LO_Element*)pLoTable, LO_TABLE );

    XP_ASSERT(pEdTable);
    if( !pEdTable )
        return NULL;

    XP_Bool bChanged;

    if( bSelect )
    {
        // Select the table and save pointers
        pLoTable->ele_attrmask |= LO_ELE_SELECTED;
        m_pSelectedEdTable = pEdTable;
        m_pSelectedLoTable = pLoTable;
        bChanged = !bWasSelected;
    } 
    else 
    {
        // Unselect the table
        pLoTable->ele_attrmask &= ~LO_ELE_SELECTED;
        bChanged = bWasSelected;
    }        

    // Redisplay only if changed from previous state
    if( bChanged )
        FE_DisplayEntireTableOrCell(m_pContext, (LO_Element*)pLoTable);
    return pEdTable;
}

CEditTableCellElement* CEditBuffer::SelectCell(XP_Bool bSelect, LO_CellStruct *pLoCell, 
                             CEditTableCellElement *pEdCell)
{
    // We must have one type of cell or the other
    XP_ASSERT(pLoCell || pEdCell);
    if( !pLoCell )
    {
        pLoCell = GetLoCell((CEditElement*)pEdCell);
        if( !pLoCell )
            return NULL;
    }

    XP_Bool bWasSelected = (XP_Bool)pLoCell->ele_attrmask & LO_ELE_SELECTED;

    // Get corresponding Edit cell if not supplied
    if( !pEdCell )
        pEdCell = (CEditTableCellElement*)GetTableElementFromLO_Element( (LO_Element*)pLoCell, LO_CELL );

    if(bSelect && !pEdCell)
        return NULL;

    XP_Bool bChanged;
    int iSize = m_SelectedEdCells.Size();

    if( bSelect )
    {
        // Select the cell
        pLoCell->ele_attrmask |= LO_ELE_SELECTED;

        // Add pointers to lists if not already in the respective list
        int i;
        CEditTableCellElement *pNewEdCell = pEdCell;
        LO_CellStruct *pNewLoCell = pLoCell;
        for( i = 0; i < iSize; i++ )
        {
            if( m_SelectedEdCells[i] == pEdCell )
            {
                pNewEdCell = NULL;
                // Be sure the Layout array matches the EditArray in size
                // A bit risky, but most efficient for relayout usage
                m_SelectedLoCells.SetSize(iSize);
                m_SelectedLoCells[i] = pLoCell;
                break;
            }
        }
        for( i = 0; i < m_SelectedLoCells.Size(); i++ )
        {
            if( m_SelectedLoCells[i] == pLoCell )
            {
                pNewLoCell = NULL;
                break;
            }
        }
        if( pNewEdCell && pNewLoCell )
        {
            // Returns index to last element, add 1 for size
            iSize = m_SelectedEdCells.Add(pNewEdCell) + 1;
            m_SelectedLoCells.Add(pNewLoCell);
        }

#ifdef DEBUG
        // TRACE CELL
//        if( bSelect )
//            XP_TRACE(("Cell: Row= %d, X= %d, Y= %d, width= %d", pEdCell->GetRow(), pEdCell->GetX(), pEdCell->GetY(), pLoCell->width));
#endif
        bChanged = !bWasSelected;
    } 
    else 
    {
        // Delete existing cell from list
        iSize = m_SelectedEdCells.Delete(pEdCell) + 1;
        m_SelectedLoCells.Delete(pLoCell);

        // Unselect the cell, including "special" selection style
        pLoCell->ele_attrmask &= ~(LO_ELE_SELECTED | LO_ELE_SELECTED_SPECIAL);
        bChanged = bWasSelected;
    }        

#ifdef DEBUG
    if( iSize != m_SelectedLoCells.Size() )
        XP_TRACE(("Edit Array Size = %d, LO Array Size = %d",iSize, m_SelectedLoCells.Size()));
#endif

    // Redisplay only if changed from previous state
    //XP_TRACE(("Display Cell - Select=%d, Changed=%d",bSelect,bChanged));
    if( bChanged )
        FE_DisplayEntireTableOrCell(m_pContext, (LO_Element*)pLoCell);
    return pEdCell;    
}

void CEditBuffer::DisplaySpecialCellSelection( CEditTableCellElement *pFocusCell,  EDT_TableCellData *pCellData )
{
    XP_Bool bCellFound = FALSE;
    intn iSelectedCount = m_SelectedLoCells.Size(); 

    // PROBLEM: If we don't clear table selection, we end up with table and cell
    //  selected when examining table properties. Simplest solution is to 
    //  clear table selection
    if( m_pSelectedLoTable )
        SelectTable(FALSE);        

    for( intn i = 0; i < iSelectedCount; i++ )
    {
        LO_CellStruct *pLoCell = m_SelectedLoCells[i];
        CEditTableCellElement *pEdCell = m_SelectedEdCells[i];
        XP_ASSERT(pEdCell);
        XP_Bool bWasSpecial = (XP_Bool)pLoCell->ele_attrmask & LO_ELE_SELECTED_SPECIAL;

        if( pEdCell == pFocusCell )
        {
            // Remove the special selection from the focus cell
            if( bWasSpecial )
            {
                pLoCell->ele_attrmask &= ~LO_ELE_SELECTED_SPECIAL;
                FE_DisplayEntireTableOrCell(m_pContext, (LO_Element*)pLoCell);
            }
            bCellFound = TRUE;
        }
        else if( !bWasSpecial )
        {
            // Set special selection attribute in addition to existing "normal" selection
            pLoCell->ele_attrmask |= LO_ELE_SELECTED_SPECIAL;
            FE_DisplayEntireTableOrCell(m_pContext, (LO_Element*)pLoCell);
        }
    }
    // Select the focus cell if it was not found to be
    //  selected above
    if( pFocusCell && !bCellFound )
    {
        // If no other cells were selected,
        //   save single-cell selection type
        if( iSelectedCount == 0 )
            m_TableHitType = ED_HIT_SEL_CELL;
        SelectCell(TRUE, NULL, pFocusCell);
    }

    // Update cell data. Used primarily when EDT_StartSpecialCellSelection()
    //   is called when no cells were previously selected
    if( pCellData )
    {
        pCellData->iSelectionType = m_TableHitType;
        pCellData->iSelectedCount = m_SelectedLoCells.Size();
    }
}

// Start multi-cell selection highlighting with cell containing the caret
void CEditBuffer::StartSpecialCellSelection(EDT_TableCellData *pCellData)
{
    CEditTableCellElement* pEdCell = m_pCurrent->GetTableCellIgnoreSubdoc();
    if( pEdCell )
        DisplaySpecialCellSelection( pEdCell, pCellData );
}

static void edt_ClearSpecialCellSelection(MWContext *pContext, LO_CellStruct *pLoCell)
{
    if( pContext && pLoCell && (pLoCell->ele_attrmask & LO_ELE_SELECTED_SPECIAL) )
    {
        pLoCell->ele_attrmask &= ~LO_ELE_SELECTED_SPECIAL;
        FE_DisplayEntireTableOrCell(pContext, (LO_Element*)pLoCell);
    }
}

void CEditBuffer::ClearSpecialCellSelection()
{
    for( intn i = 0; i < m_SelectedLoCells.Size(); i++ )
    {
        edt_ClearSpecialCellSelection(m_pContext, m_SelectedLoCells[i]);
    }
}

// Sort cell lists so simple traversal through array 
//  moves row-wise from upper left corner to lower right corner of table
void CEditBuffer::SortSelectedCells()
{
    int iCount = m_SelectedLoCells.Size();
    if( iCount <= 1 )
        return;
    
    // Optimization for only two cells selected
    if( iCount == 2 )
    {
        if(m_SelectedLoCells[1]->x < m_SelectedLoCells[0]->x &&
           m_SelectedLoCells[1]->y <= m_SelectedLoCells[0]->y )
        {
            LO_CellStruct *pLoCell = m_SelectedLoCells[0];
            m_SelectedLoCells[0] = m_SelectedLoCells[1];
            m_SelectedLoCells[1] = pLoCell;   

            CEditTableCellElement *pEdElement = m_SelectedEdCells[0];
            m_SelectedEdCells[0] = m_SelectedEdCells[1];
            m_SelectedEdCells[1] = pEdElement;   
        }
        return;
    }

    XP_ASSERT(iCount == m_SelectedEdCells.Size());

    LO_CellStruct **pNewLoCells = (LO_CellStruct**)XP_ALLOC(iCount*sizeof(LO_CellStruct*));
    if( !pNewLoCells )
        return;

    CEditTableCellElement **pNewEdCells = (CEditTableCellElement**)XP_ALLOC(iCount*sizeof(CEditTableCellElement*));
    if( !pNewEdCells )
    {
        XP_FREE(pNewLoCells);
        return;
    }

    LO_Element *pLoElement = (LO_Element*)m_SelectedLoCells[0];
    LO_Element *pFirstSelected = NULL;

    // Scan backwards until we hit the table element to find-selected cell
    do {
        if( pLoElement->type == LO_CELL && (pLoElement->lo_cell.ele_attrmask & LO_ELE_SELECTED) )
            pFirstSelected = pLoElement;

        pLoElement = pLoElement->lo_any.prev;
    } while (pLoElement->type != LO_TABLE ); 
    
    
    if( pFirstSelected ) // Should never fail
    {
        pLoElement = pFirstSelected;
        int i = 0;
        do {
            if( pLoElement->lo_cell.ele_attrmask & LO_ELE_SELECTED )
            {
                // Build new lists of both LO and EDIT elements
                pNewLoCells[i] = (LO_CellStruct*)pLoElement;
                
                // Find the corresponding Edit element from the old lists
                int iIndex = m_SelectedLoCells.Find(pLoElement);
                //XP_ASSERT(iIndex >= 0);
                if( iIndex >= 0 )
                    pNewEdCells[i] = m_SelectedEdCells[iIndex];
                else
                    // THIS IS A PROBLEM -- WE SHOULD NEVER GET HERE
                    pNewEdCells[i] = (CEditTableCellElement*)GetTableElementFromLO_Element(pLoElement, LO_CELL);
                i++;
            }
            pLoElement = pLoElement->lo_any.next;
        } while (pLoElement && pLoElement->type == LO_CELL ); 
    
        // Replace old lists with new sorted lists
        for( i = 0; i < iCount; i++ )
        {
            m_SelectedLoCells[i] = pNewLoCells[i];
            m_SelectedEdCells[i] = pNewEdCells[i];
        }
    }
    XP_FREE(pNewLoCells);
    XP_FREE(pNewEdCells);
}

void CEditBuffer::ClearTableAndCellSelection()
{
    m_pSelectedTableElement = NULL;
    m_pPrevExtendSelectionCell = NULL;
    m_TableHitType = ED_HIT_NONE;

    int iEdSize = m_SelectedEdCells.Size();
    int iLoSize = m_SelectedLoCells.Size();
    if( m_pSelectedEdTable == 0 &&
        iEdSize == 0 )
        // Nothing to do
        return;

    SelectTable(FALSE);

    // These arrays should always be in synch
    //XP_ASSERT( iEdSize == iLoSize );
    if( iEdSize != iLoSize )
        XP_TRACE(("EdSize=%d, LoSize=%d", iEdSize, iLoSize));

    // Remove selection for all existing cells
    //  (From the end is quicker - no pointer copying needed)
    for( int i = iEdSize-1; i >= 0; i-- )
    {
        SelectCell(FALSE, m_SelectedLoCells[i], m_SelectedEdCells[i]);
    }
    
    // Array of pointers should now be empty
    XP_ASSERT(m_SelectedLoCells.Size() == 0);
    XP_ASSERT(m_SelectedEdCells.Size() == 0);
}

void CEditBuffer::ClearCellAttrmask( LO_Element *pLoElement, uint16 attrmask )
{
    if( pLoElement )
    {
        while( pLoElement && pLoElement->type != LO_LINEFEED )
        {
            if( pLoElement->type == LO_CELL )
            {
                XP_Bool bWasSet = pLoElement->lo_cell.ele_attrmask & attrmask;
                // Clear the mask bit(s)
                pLoElement->lo_cell.ele_attrmask &= ~attrmask;
                // Tell front end to invalidate cell for redraw
                if( bWasSet )
                    FE_DisplayEntireTableOrCell(m_pContext, pLoElement);
            }
            pLoElement = pLoElement->lo_any.next;
        }
    }
}

// Should be called after positioning caret on object
//   clicked on for context menu (right button on Windows)
void CEditBuffer::ClearCellSelectionIfNotInside()
{
    // Not in a table - clear table selection
    if( !IsInsertPointInTable() )
    {
        ClearTableAndCellSelection();
        return;
    }
    // Get current leaf element at caret or selection
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    if( ip.m_pElement )
    {
        // Find the LO cell element and check if it is selected
        //  (No cell found if not within a table)
        LO_CellStruct *pLoCell = GetLoCell(ip.m_pElement);
        if( pLoCell && !(pLoCell->ele_attrmask & LO_ELE_SELECTED) )
        {
            ClearTableAndCellSelection();
        }
    }
}

void CEditBuffer::ClearTableIfContainsElement(CEditElement *pElement)
{
    if( pElement )
    {
        // Depend on member flag being set correctly
        if( pElement->IsSelected() )
        {
            if( pElement->IsTable() )
            {
                SelectTable(FALSE, NULL, (CEditTableElement*)pElement);
                return;
            }
            else if( pElement->IsTableCell() )
            {
                SelectCell(FALSE, NULL, (CEditTableCellElement*)pElement);
                return;
            }
        }
        // We are not a cell or table - check if containing layout cell or table is selected
        LO_CellStruct *pLoCell = GetLoCell(pElement);
        if( pLoCell )
        {
            // If selected, unselect
            if( pLoCell->ele_attrmask & LO_ELE_SELECTED )
            {
                SelectCell(FALSE, pLoCell);
            } else {
                // Cell not selected - check for table
                LO_TableStruct *pLoTable = lo_GetParentTable(m_pContext, (LO_Element*)pLoCell);
                if( pLoTable && pLoTable->ele_attrmask & LO_ELE_SELECTED )
                {
                    SelectTable(FALSE, pLoTable);
                }
            }
        }
    }
}

// Call this the first time, if result is TRUE then call 
//   GetNextCellSelection() and repeat formating or other action 
//    until it returns null. 
// Current insert point is saved during first time,
//   and restored after no more cells are left to process
XP_Bool CEditBuffer::GetFirstCellSelection(CEditSelection& selection)
{
    CEditTableCellElement *pCell = NULL;

    m_iNextSelectedCell = 0;

    if( m_pSelectedEdTable )
    {
        pCell = m_pSelectedEdTable->GetFirstCell();
    }
    else if( m_SelectedEdCells.Size() )
    {
        // Get first "selected" cell
        pCell = m_SelectedEdCells[0];

        // Set for next selected item
        m_iNextSelectedCell = 1;
    }
    if( pCell )
    {
        // Just return pointers and offsets of the cell contents as a selection
        pCell->GetAll(selection);
        selection.m_bFromStart = TRUE;
        EphemeralToPersistent(selection);
        return TRUE;
    }
    return FALSE;
}

XP_Bool CEditBuffer::GetNextCellSelection(CEditSelection& selection)
{
    CEditTableCellElement *pCell = NULL;

    if( m_pSelectedEdTable )
    {
        // Get next cell in entire table
        pCell = m_pSelectedEdTable->GetNextCellInTable();
    } 
    else if( m_iNextSelectedCell && m_iNextSelectedCell < m_SelectedEdCells.Size() )
    {
        // Get next "selected" cell
        pCell = m_SelectedEdCells[m_iNextSelectedCell++];
    }
    if( pCell )
    {
        pCell->GetAll(selection);
        selection.m_bFromStart = TRUE;
        EphemeralToPersistent(selection);
        return TRUE;
    }
    return FALSE;
}

// Extracted from old version of SelectTableCell
void CEditBuffer::SelectCellContents(CEditTableCellElement *pCell)
{

    CEditSelection selection;
    if ( pCell ) 
    {
        pCell->GetAll(selection);
        // KLUDGE ALERT: Without this, attempts to set cell data
        //  go to next cell. As it is, there is a bug in which
        //  caret is in next cell after unselecting this cell
        selection.m_bFromStart = TRUE;
        EphemeralToPersistent(selection);
        SetSelection(selection);
    }
}


CEditTableCellElement *CEditBuffer::GetFirstSelectedCell()
{
    CEditTableCellElement *pCell = NULL;

    m_iNextSelectedCell = 0;

    if( m_pSelectedEdTable )
    {
        pCell = m_pSelectedEdTable->GetFirstCell();
    }
    else if( m_SelectedEdCells.Size() )
    {
        // Get first "selected" cell
        pCell = m_SelectedEdCells[0];

        // Set for next selected item
        m_iNextSelectedCell = 1;
    }
    return pCell;
}

CEditTableCellElement *CEditBuffer::GetNextSelectedCell(intn* pRowCounter)
{
    CEditTableCellElement *pCell = NULL;

    if( m_pSelectedEdTable )
    {
        // Get next cell in entire table
        pCell = m_pSelectedEdTable->GetNextCellInTable(pRowCounter);
    } 
    else if( m_iNextSelectedCell && m_iNextSelectedCell < m_SelectedEdCells.Size() )
    {
        if( m_iNextSelectedCell < 1 )
            return GetFirstSelectedCell(); 

        // Get next "selected" cell
        pCell = m_SelectedEdCells[m_iNextSelectedCell];
        if( pRowCounter )
        {
            intn iPrevRow = m_SelectedEdCells[m_iNextSelectedCell - 1]->GetRow();
            // Increment the row counter if supplied
            intn iRow =  pCell->GetRow();
            if( iRow != iPrevRow )
                (*pRowCounter)++;
        }
        m_iNextSelectedCell++;
    }
    return pCell;
}

// Dynamic object sizing

ED_SizeStyle CEditBuffer::CanSizeObject(LO_Element *pLoElement, int32 xVal, int32 yVal)
{
    // Table and Cells are special case - use more complicated hit testing
    if( pLoElement && (pLoElement->type == LO_TABLE || pLoElement->type == LO_CELL) )
    {
        ED_HitType iHitType = GetTableHitRegion(xVal, yVal, &pLoElement, FALSE);
        switch( iHitType )
        {
            case ED_HIT_SIZE_COL:
            case ED_HIT_SIZE_TABLE_WIDTH:
                return ED_SIZE_RIGHT;
            case ED_HIT_SIZE_ROW:
            case ED_HIT_SIZE_TABLE_HEIGHT:
                return ED_SIZE_BOTTOM;
            case ED_HIT_ADD_COLS:
                return ED_SIZE_ADD_COLS;
            case ED_HIT_ADD_ROWS:
                return ED_SIZE_ADD_ROWS;
            default:
                return 0;
        }
    } 

    if( !pLoElement )
    {
#ifdef LAYERS
		pLoElement = LO_XYToElement(m_pContext, xVal, yVal, 0);
#else
		pLoElement = LO_XYToElement(m_pContext, xVal, yVal);
#endif
    }
    if( !pLoElement || pLoElement->lo_any.edit_offset < 0)
        return 0;

    XP_Bool bCanSizeWidth = FALSE;
    XP_Bool bCanSizeHeight = FALSE;
    XP_Bool bInHRule = pLoElement->type == LO_HRULE;
    XP_Bool bIsIcon = FALSE;

    LO_Any_struct * pAny = (LO_Any_struct*) pLoElement;

    // NOTE: NO CONVERSION DONE -- ASSUMES FE IS IN PIXELS
    int32 iXoffset = xVal - (pAny->x + pAny->x_offset);
    int32 iYoffset = yVal - (pAny->y + pAny->y_offset);
    int32 bottom_limit = pAny->height - ED_SIZING_BORDER;
    int32 right_limit =  pAny->width - ED_SIZING_BORDER;

    // Look for the elements that can be sized:
    switch ( pLoElement->type )
    {
        case LO_IMAGE:       // 4
        {
      		CEditElement * pElement = pLoElement->lo_any.edit_element;
            if( !pElement ){
                return 0;
            }
            if( pElement->IsIcon() ){
                 //  Only allow sizing width or height
                 //  if it already has the respective attribute set
                bCanSizeWidth = pElement->GetWidth();
                bCanSizeHeight = pElement->GetHeight();
                bIsIcon = TRUE;
                break;
            }
            if( !pElement->IsImage() )
                return 0;
            // Adjust for image border
            iXoffset -= ((LO_ImageStruct*)pAny)->border_width;
            iYoffset -= ((LO_ImageStruct*)pAny)->border_width;
            // Fall through to allow sizing both width and height
        }
        case LO_HRULE:       // 3
            bCanSizeWidth = TRUE;
            bCanSizeHeight = TRUE;
            break;
        default:
            return 0;
    }

    // Can't size anything - get out now!
    if( !bCanSizeWidth && !bCanSizeHeight )
        return 0;

    if( iXoffset < 0 || iYoffset < 0 )
        return 0;

    // Note that TOP RIGHT is prefered in the case
    //   of small object (overlapping hits regions),
    //   and left/right sides are favored over bottom corners
    if( iXoffset >= right_limit )
    {
        if( !bCanSizeHeight || bInHRule )
        {
            return ED_SIZE_RIGHT;
        }
        if( iYoffset <= ED_SIZING_BORDER )
            return ED_SIZE_TOP_RIGHT;
        if( iYoffset < bottom_limit )
            return ED_SIZE_RIGHT;

        return ED_SIZE_BOTTOM_RIGHT;

    } else if( iXoffset <= ED_SIZING_BORDER )
    {
        if( !bCanSizeHeight || bInHRule )
            return ED_SIZE_LEFT;

        if( iYoffset <= ED_SIZING_BORDER )
            return ED_SIZE_TOP_LEFT;

        if( iYoffset < bottom_limit )
            return ED_SIZE_LEFT;

        return ED_SIZE_BOTTOM_LEFT;

    } else if( bCanSizeHeight  )
    {
        if( iYoffset >= bottom_limit )
            return ED_SIZE_BOTTOM;

        if( iYoffset <= ED_SIZING_BORDER )
            return ED_SIZE_TOP;
    }
    return 0;
}

ED_SizeStyle CEditBuffer::StartSizing(LO_Element *pLoElement, int32 xVal, int32 yVal,
                                      XP_Bool bLockAspect, XP_Rect *pRect){
    XP_ASSERT(pRect);

    if( m_pSizingObject ){
        // Improbable, but what the heck
        delete m_pSizingObject;
    }
    // Unselect table or cell(s)
    ClearTableAndCellSelection();

    // Force absolute measurements to avoid confusing behavior when using percents
    if( pLoElement->type == LO_TABLE || pLoElement->type == LO_CELL )
    {
        CEditTableElement *pTable = (CEditTableElement*)GetTableElementFromLO_Element( pLoElement, LO_TABLE );
        if( pTable )
        {
            int iMode = ED_TABLE_PIXELS | ED_TABLE_USE_COLS;
            // If sizing a table, remove all widths from cells
            if( pLoElement->type == LO_TABLE )
                iMode |= ED_TABLE_NO_CELL_WIDTH;
            else 
                iMode |= ED_TABLE_NO_CELL_WIDTH;
            pTable->SetTableMode(m_pContext, iMode);
        }
    }

    // Shouldn't be here without this, but check anyway
    int iStyle = CanSizeObject(pLoElement, xVal, yVal);
    if( iStyle ){
        m_pSizingObject = new CSizingObject();
        if( m_pSizingObject ){
            if( m_pSizingObject->Create(this, pLoElement, iStyle,
                                         xVal, yVal, bLockAspect, pRect) ){
                return iStyle;
            } else {
                delete m_pSizingObject;
                m_pSizingObject = NULL;
            } 
        }
    }
    return 0;
}

XP_Bool CEditBuffer::GetSizingRect(int32 xVal, int32 yVal,
                                   XP_Bool bLockAspect, XP_Rect *pRect){
    XP_ASSERT(pRect);
    if( m_pSizingObject && pRect ){
        return m_pSizingObject->GetSizingRect(xVal, yVal, bLockAspect, pRect);
    }
    return FALSE;
}

void CEditBuffer::EndSizing(){
    if( ! m_pSizingObject ){
        return;
    }
    m_pSizingObject->ResizeObject();

    delete m_pSizingObject;
    m_pSizingObject = NULL;

    ResumeAutoSave();
}

void CEditBuffer::CancelSizing(){
    if( m_pSizingObject )
    {
        // Erase the drawing of added rows or columns
        m_pSizingObject->EraseAddRowsOrCols();
        delete m_pSizingObject;
        m_pSizingObject = NULL;
    }
    ResumeAutoSave();
}

#ifdef EDITOR_JAVA
EditorPluginManager CEditBuffer::GetPlugins(){
    return GetCommandLog()->GetPlugins();
}
#endif


//-----------------------------------------------------------------------------
// Spellcheck stuff
//-----------------------------------------------------------------------------

XP_Bool CEditBuffer::FindNextMisspelledWord( XP_Bool bFirst, 
        XP_Bool bSelect, CEditLeafElement** ppWordStart ){
    CEditLeafElement *pInsertPoint;
    XP_Bool bSingleItem;
    ElementOffset iOffset;

    if( ppWordStart && *ppWordStart ){
        pInsertPoint = *ppWordStart;
        iOffset = 0;
    }
    else {
        bSingleItem = GetPropertyPoint( &pInsertPoint, &iOffset );
    }
    while( pInsertPoint->IsMisspelled() && ! bFirst ){
        pInsertPoint = pInsertPoint->NextLeaf();
        if ( pInsertPoint && pInsertPoint->IsFirstInContainer() ) {
            break;
        }
    }
    while( pInsertPoint && !pInsertPoint->IsMisspelled() ){
        pInsertPoint = pInsertPoint->NextLeaf();
    }

    if( pInsertPoint ){
        CEditLeafElement *pEndInsertPoint = pInsertPoint;
        while( pEndInsertPoint && pEndInsertPoint->IsMisspelled() ){
            CEditLeafElement *pNext = pEndInsertPoint->LeafInContainerAfter();
            if ( pNext != NULL )
                pEndInsertPoint = pNext;
            else
                break;
        }

        if (pEndInsertPoint && pEndInsertPoint->IsMisspelled()){
            iOffset = pEndInsertPoint->GetLen();
        }
        else {
            iOffset = 0;
        }

        if( bSelect ){
            // force the window to scroll to the beginning of the selection.
            SetInsertPoint( pInsertPoint, 0, FALSE );
            SelectRegion( pInsertPoint, 0, pEndInsertPoint, iOffset, TRUE, TRUE );
        }
    }

    if( ppWordStart != 0 ) *ppWordStart = pInsertPoint;

    return pInsertPoint != 0;
}

static XP_HUGE_CHAR_PTR GetMisspelledWordText( CEditLeafElement *pLeaf ){
    CStreamOutMemory m;
    while( pLeaf && pLeaf->IsMisspelled() && pLeaf->IsText() ){
        m.Write( pLeaf->Text()->GetText(), pLeaf->Text()->GetLen() );
        pLeaf = pLeaf->LeafInContainerAfter();
    }
    // zero terminate the buffer
    m.Write( "", 1 );
    return m.GetText();
}

static void IgnoreMisspelledWord( CEditLeafElement *pLeaf ){
    while( pLeaf && pLeaf->IsMisspelled() && pLeaf->IsText() ){
        pLeaf->Text()->m_tf &= ~TF_SPELL;
        pLeaf = pLeaf->LeafInContainerAfter();
    }
}

static void ReplaceMisspelledWord( CEditLeafElement *pStart, char *pNewWord ){
    CEditLeafElement *pPrev;

    pStart->Text()->m_tf &= ~TF_SPELL;

    // hack to keep us from deleting the entire word
    if( pNewWord && *pNewWord ){
        pStart->Text()->SetText( pNewWord );
    }
    else {
        pStart->Text()->SetText(" ");
    }

    pStart = pStart->LeafInContainerAfter();
    while( pStart && pStart->IsMisspelled() ){
        pPrev = pStart;
        pStart = pStart->LeafInContainerAfter();
        pPrev->Unlink();
        delete pPrev;
    }
}

void CEditBuffer::IterateMisspelledWords( EMSW_FUNC eFunc, char* pOldWord, 
        char* pNewWord, XP_Bool bAll ){

    CEditLeafElement *pWordStart, *pBegin, *pEnd;
    XP_Bool bFirst = TRUE;

    // 
    // Find the next word (including the one we might be on), but don't select
    //  it and return the word
    //

    
    pBegin = 0;
    pEnd = 0;
    pWordStart = 0;
    do {
        FindNextMisspelledWord( bFirst, FALSE, &pWordStart );
        if( bFirst ){
            bFirst = FALSE;
            ClearSelection(TRUE,TRUE);       
        }
        if ( pWordStart ) {
            XP_HUGE_CHAR_PTR pThisWord = GetMisspelledWordText( pWordStart );

            // Ignore all words if no specific word is specified.
            if( (eFunc == EMSW_IGNORE && pOldWord == NULL) ||
                XP_STRCMP( (char*) pThisWord, pOldWord ) == 0 ){
                // do the actual replacing here
                if( pBegin == 0 ) pBegin = pWordStart;
                pEnd = pWordStart;

                switch( eFunc ){
                case EMSW_IGNORE:
                    IgnoreMisspelledWord( pWordStart );
                    break;

                case EMSW_REPLACE:
                    ReplaceMisspelledWord( pWordStart, pNewWord );
                    break;
                }
            }

            XP_HUGE_FREE( pThisWord );
        }
    } while( bAll && pWordStart );

    // if we actually did some work
    if( pBegin && pEnd ) {
        Relayout( pBegin, 0, pEnd );
    }
}

void CEditBuffer::SetBaseTarget(char* pTarget){
    XP_FREEIF(m_pBaseTarget);
    m_pBaseTarget = pTarget ? XP_STRDUP(pTarget) : 0;
}

char* CEditBuffer::GetBaseTarget(){
    return m_pBaseTarget;
}




XP_Bool
CEditBuffer::ReplaceOnce( char *pReplaceText, XP_Bool bRelayout, XP_Bool bReduce)
{/* This utility function assumes that the text 
    you want replaced has been selected already. */

    CEditSelection selection;
	int i;
    EDT_CharacterData  *formatting;  /* This will hold the formatting of the selected text */

#ifdef FORMATING_FIXED
    /* Get the formatting of the selected text */
    formatting = GetCharacterDataSelection(0, selection);

    /* Paste the new text over the selected text */
    PasteText( pReplaceText, FALSE, FALSE, bRelayout , bReduce);

    /* select the new text */
    for (i = XP_STRLEN( pReplaceText ); i > 0; i-- )
    	SelectPreviousChar();

    /* apply the old formatting */
    SetCharacterData( formatting );
#else
    /* Paste the new text over the selected text */
    PasteText( pReplaceText, FALSE, FALSE, bRelayout , bReduce);
#endif

    return TRUE;
}



void
CEditBuffer::ReplaceLoop(char *pReplaceText, XP_Bool bReplaceAll,
                         char *pTextToLookFor, XP_Bool bCaseless,
                         XP_Bool bBackward, XP_Bool bDoWrap )
{
	int i;
    LO_Element *start_ele_loc, *end_ele_loc,
               *original_start_ele_loc, *original_end_ele_loc;
    int32       start_pos, end_pos,
                original_start_pos, original_end_pos,
                tlx, tly;
    CL_Layer*   layer;/* this will be ignored */
    MWContext*  tempContext = m_pContext;
    CEditLeafElement* origEditElement;//used for replace all to move insertion point back to the proper location

    BeginBatchChanges(kGroupOfChangesCommandID);


    /* Get the original starting and finising elements */
    LO_GetSelectionEndpoints( m_pContext,
							  &original_start_ele_loc, /* The element the selection begins in */
							  &original_end_ele_loc,   /* The element the selection ended in */
							  &original_start_pos,     /* Index into the starting element */
							  &original_end_pos,    /* Index into the ending element */
							  &layer);                 /* this will be ignored */

    if ( bReplaceAll )
    {/**/
        origEditElement = this->m_pCurrent;

        NavigateDocument( FALSE, FALSE );
        LO_GetSelectionEndpoints( m_pContext,
							  &start_ele_loc, /* The element the selection begins in */
							  &end_ele_loc,   /* The element the selection ended in */
							  &start_pos,     /* Index into the starting element */
							  &end_pos,       /* Index into the ending element */
							  &layer);                 /* this will be ignored */
    }
    else
    {/**/
        start_ele_loc = original_start_ele_loc;
        end_ele_loc = original_end_ele_loc;
        start_pos = original_start_pos;
        end_pos = original_end_pos;
    }

    XP_Bool     done = FALSE, Wrapped = FALSE;
    //if we are replacing all, start at top of doc. remember original insertion point.
    while ( !done )
    {
        XP_Bool found = LO_FindText(m_pContext, pTextToLookFor, &start_ele_loc,
                            &start_pos, &end_ele_loc, &end_pos, !bCaseless, !bBackward);
        if ( found )
        {
			LO_SelectText( m_pContext, start_ele_loc, start_pos,
				           end_ele_loc, end_pos, &tlx, &tly);

            ReplaceOnce( pReplaceText , !bReplaceAll, !bReplaceAll); //do not relayout if we are replacing all!
            //ReplaceOnce( pReplaceText , TRUE, TRUE); 
            if ( bReplaceAll )
            {/* We need to reset our starting position to our previous ending position */
                //do not care to refresh anything now!!
                m_bLayoutBackpointersDirty = FALSE;//trust me
                start_ele_loc =  end_ele_loc;
                start_pos = end_pos;
            }
            else
                done=TRUE;
        }
        else if (!bReplaceAll) /* found == FALSE  and we are not replacing all.*/
        {/* If found == FALSE, and there isn't a selection, 
            then we need to wrap to check the portion before the insertion point. */
            if ( !Wrapped )/* We only want to Wrap ONCE */
            {
                start_pos = 0;
                end_pos = original_start_pos;
                start_ele_loc = NULL;
                end_ele_loc = original_start_ele_loc;
                Wrapped = TRUE;
            }
            else
                done = TRUE;
        }
        else
        {
            done = TRUE;
        }
    }
    if (bReplaceAll) //need to relayout now that we are done.
    {
        //set insertion point back to its old location, valid, or not.
        /*call Reflow( CEditElement* pStartElement,
                            int iEditOffset,
                            CEditElement *pEndElement,
                            intn relayoutFlags ){*/
        m_bLayoutBackpointersDirty = TRUE;//trust me

        SetInsertPoint(origEditElement, original_start_pos, FALSE);
        Reduce(this->m_pRoot->GetFirstMostChild());//we must finish what we have begun...
        // relay out the stream
	    Relayout(this->m_pRoot->GetFirstMostChild(), 0);
    }
}




void CEditBuffer::ReplaceText( char *pReplaceText, XP_Bool bReplaceAll,
								char *pTextToLookFor, XP_Bool bCaseless,
								XP_Bool bBackward, XP_Bool bDoWrap )
{
	EDT_CharacterData *formatting;
    CEditSelection selection;
	int i;

    BeginBatchChanges(kGroupOfChangesCommandID);
    // This replaces the selected "find" text just fine,
    //  but it ignores character attributes of replaced text
    if( IsSelected() )
        formatting = GetCharacterDataSelection(0, selection);
    else
    	formatting = NULL;

    PasteText(pReplaceText, FALSE, FALSE, TRUE, TRUE); 
	for (i = XP_STRLEN( pReplaceText ); i > 0; i-- )
	    SelectPreviousChar();
    SetCharacterData( formatting );
    
    if ( bReplaceAll )
    {
    	/* normal local variables */
		int32 		start_position, end_position;
		LO_Element	*start_ele_loc = NULL, *end_ele_loc = NULL, 
					*final_done_loc, *current_done_location;
    	MWContext	*tempContext;
    	CL_Layer	*layer;	// this will be ignored
     	XP_Bool		found;

   		/* use non-editor LO_* call */
		LO_GetSelectionEndpoints( m_pContext,
								&start_ele_loc,
								&end_ele_loc,
								&start_position,
								&end_position,
								&layer);
		final_done_loc = start_ele_loc;
		current_done_location = NULL;
		
		/* brade: as of today; the bDoWrap is not working correct so force it off for now */
		bDoWrap = FALSE;
		
   		do {
			start_ele_loc = end_ele_loc;
			end_ele_loc = current_done_location;
    		tempContext = m_pContext;
	    	found = LO_FindGridText( m_pContext,
								&tempContext,
								pTextToLookFor,
								&start_ele_loc,
								&start_position,
								&end_ele_loc,
								&end_position,
								!bCaseless,
								!bBackward);
			if ( found )
			{
				int32		tlx, tly;
				
				LO_SelectText( m_pContext,
					start_ele_loc,
					start_position,
					end_ele_loc,
					end_position,
					&tlx,
					&tly);
				
			    if( IsSelected() )
        			formatting = GetCharacterDataSelection(0, selection);
			    PasteText( pReplaceText, FALSE, FALSE, TRUE, TRUE);
				for (i = XP_STRLEN( pReplaceText ); i > 0; i-- )
				    SelectPreviousChar();
			    SetCharacterData( formatting );

   				/* use non-editor LO_* call */
				LO_GetSelectionEndpoints( m_pContext,
								&start_ele_loc,
								&end_ele_loc,
								&start_position,
								&end_position,
								&layer);
			}
			else if ( bDoWrap )
			{
				/* not found; but we need to wrap */
				bDoWrap = FALSE;	/* only wrap this once */
				found = TRUE;		/* reset our flag so we keep looping! */

				/* Try again from the beginning.  These are the values
				   LO_GetSelectionEndpoints returns if there is no selection */
				end_ele_loc = NULL;		/* this will reset start_ele_loc above */
				start_position = 0;
				end_position = 0;
				
				current_done_location = final_done_loc;
			}
			
		} while ( found );
    }
    
    EndBatchChanges();
}

//-----------------------------------------------------------------------------
// CEditTagCursor
//-----------------------------------------------------------------------------
CEditTagCursor::CEditTagCursor( CEditBuffer* pEditBuffer,
            CEditElement *pElement, int iEditOffset, CEditElement* pEndElement ):
            m_pEditBuffer(pEditBuffer),
            m_pCurrentElement(pElement),
            m_endPos(pEndElement,0),
            m_tagPosition(tagOpen),
            m_stateDepth(0),
            m_currentStateDepth(0),
            m_iEditOffset( iEditOffset ),
            m_bClearRelayoutState(0),
            m_pContext( pEditBuffer->GetContext() ),
            m_pStateTags(0)
            {

    CEditElement *pContainer = m_pCurrentElement->FindContainer();
    if( (m_pCurrentElement->PreviousLeafInContainer() == 0 )
            && iEditOffset == 0
            && pContainer
            && pContainer->IsA( P_LIST_ITEM ) ){
        m_pCurrentElement = pContainer;
    }

    //m_bClearRelayoutState = (m_pCurrentElement->PreviousLeafInContainer() != 0 );

    //
    // If the element has a parent, setup the depth
    //
    pElement = m_pCurrentElement->GetParent();

    while( pElement ){
        PA_Tag *pTag = pElement->TagOpen( 0 );
        PA_Tag *pTagEnd = pTag;

        while( pTagEnd->next != 0 ) pTagEnd = pTag->next;

        pTagEnd->next = m_pStateTags;
        m_pStateTags = pTag;
        pElement = pElement->GetParent();
    }
}

CEditTagCursor::~CEditTagCursor(){
    EDT_DeleteTagChain( m_pStateTags );
}

CEditTagCursor* CEditTagCursor::Clone(){
    CEditTagCursor *pCursor = new CEditTagCursor( m_pEditBuffer,
                                                  m_pCurrentElement,
                                                  m_iEditOffset,
                                                  m_endPos.Element());
    return pCursor;
}

//
// We iterate through the edit tree generating tags.  The the goto code here
//  insures that after we deliver a tag, the m_pCurrentElement is always pointing
//  to the next element to be output
//
PA_Tag* CEditTagCursor::GetNextTag(){
    XP_Bool bDone = FALSE;
    PA_Tag* pTag = 0;

    // Check for end
    if( m_pCurrentElement == 0 ){
        return 0;
    }

    while( !bDone && m_pCurrentElement != 0 ){
        // we are either generating the beginning tag, content or end tags
        switch( m_tagPosition ){

        case tagOpen:        	
            pTag = m_pCurrentElement->TagOpen( m_iEditOffset );
            bDone = (pTag != 0);
            m_iEditOffset = 0;  // only counts on the first tag.
            if( m_pCurrentElement->GetChild() ){
                m_pCurrentElement = m_pCurrentElement->GetChild();
                m_tagPosition = tagOpen;
            }
            else {
                m_tagPosition = tagEnd;
                if( !m_pCurrentElement->IsContainer() &&
                        !WriteTagClose(m_pCurrentElement->GetType()) 
                         ){
                    goto seek_next;
                }
            }
            break;

        case tagEnd:
            pTag = m_pCurrentElement->TagEnd();
            bDone = (pTag != 0);
        seek_next:
            if( m_pCurrentElement->GetNextSibling()){
                m_pCurrentElement = m_pCurrentElement->GetNextSibling();
                m_tagPosition = tagOpen;
            }
            else {
                //
                // We've exausted all the elements at this level. Set the
                //  current element to the end of our parent.  m_tagPosition is
                //  already set to tagEnd, but lets be explicit
                //
                m_pCurrentElement = m_pCurrentElement->GetParent();
                m_tagPosition = tagEnd;
                if( m_pCurrentElement 
                        && !m_pCurrentElement->IsContainer() 
                        && !WriteTagClose(m_pCurrentElement->GetType()) 
                        ){
                    goto seek_next;
                }
            }
            break;
        }
    }

    if( bDone ){
        return pTag;
    }
    else {
        XP_DELETE(pTag);
        return 0;
    }
}

PA_Tag* CEditTagCursor::GetNextTagState(){
    PA_Tag *pRet = m_pStateTags;
    if( m_pStateTags ){
        m_pStateTags = 0;
    }
    return pRet;
}

XP_Bool CEditTagCursor::AtBreak( XP_Bool* pEndTag ){
    XP_Bool bAtPara;
    XP_Bool bEndTag;

    if( m_pCurrentElement == 0 ){
        return FALSE;
    }

    if(  m_tagPosition == tagEnd ){
        bEndTag = TRUE;
        bAtPara = BitSet( edt_setTextContainer,  m_pCurrentElement->GetType()  );
    }
    else {
        bEndTag = FALSE;
        bAtPara = BitSet( edt_setParagraphBreak,  m_pCurrentElement->GetType()  );
    }

    if( bAtPara ){
        // force the layout engine to process this tag before computing line
        //  position.
        //if( m_pCurrentElement->IsA( P_LINEBREAK ){
        //    bEndTag = TRUE;
        //}

        // if there is an end position and the current position is before it
        //  ignore this break.
        CEditPosition p(m_pCurrentElement);

        if( m_endPos.IsPositioned() &&
                 m_endPos.Compare(&p) <= 0  ){
            return FALSE;
        }
        if( CurrentLine() != -1 ){
            *pEndTag = bEndTag;
            return TRUE;
        }
        else {
            //XP_ASSERT(FALSE)
        }
    }
    return FALSE;
}


int32 CEditTagCursor::CurrentLine(){
    CEditElement *pElement;
    LO_Element * pLayoutElement, *pLoStartLine;
    int32 iLineNum;

    //
    // Pop to the proper state
    //
    CEditElement *pSave = m_pCurrentElement;
    while( m_pCurrentElement && m_tagPosition == tagEnd ){
        PA_Tag* pTag = GetNextTag();
        EDT_DeleteTagChain( pTag );
        m_tagPosition = tagEnd;     // restore this.
    }


    // if we fell of the end of the document, then we are done.
    if( m_pCurrentElement == 0 ){
        m_pCurrentElement = pSave;
        return -1;
    }

    // LTNOTE: need to check to see if we are currently at a TextElement..
    pElement = m_pCurrentElement->NextLeaf();
    m_pCurrentElement = pSave;

    if( pElement == 0 ){
        return -1;
    }

    XP_ASSERT( pElement->IsLeaf() );
    pLayoutElement = pElement->Leaf()->GetLayoutElement();
    if( pLayoutElement == 0 ){
        return -1;
    }

    // Find the first element on this line.
    pLoStartLine = m_pEditBuffer->FirstElementOnLine( pLayoutElement, &iLineNum );

    // LTNOTE: this isn't true in delete cases.
    //XP_ASSERT( pLoStartLine == pLayoutElement );

    return iLineNum;
}

LO_Element* CEditBuffer::FirstElementOnLine(LO_Element* pTarget, int32* pLineNum){
    // LO_FirstElementOnLine does a binary search in Y to find the first element on a line for
    // a given (x,y) position. If the y coordinate of the position is right at the
    // top of a given line, and the previous line has an image, it is possible for
    // the search to return the previous line rather than the line than the
    // correct line. See bug 76945. A work-around is to start the search in the center of
    // the current element. (We don't calculate the exact center, since we don't
    // take the margin of an image into account, but it's close enough. Actually, probably just
    // adding 1 to the Y coordinate would be enough, but this way seems safer.
    //
    // I don't know why we don't just start at the current element and work
    // backwards through the linked list to find the first element on the line. That should
    // be faster and more accurate. ltabb might know why we do it this way instead.

    int32 x = pTarget->lo_any.x + pTarget->lo_any.width / 2;
    int32 y = pTarget->lo_any.y + pTarget->lo_any.height / 2;
    return LO_FirstElementOnLine(m_pContext, x, y, pLineNum);
}

void CEditBuffer::ChangeEncoding(int16 csid) {
    // NOTE: We no longer need to check and warn user if doc is
    // dirty because we are not reloading buffer from a URL.
    // Causes doc to be reread from temporary internal buffer.

    ForceDocCharSetID(csid); // Will be translated to this id when saved
    // Note: Why doesn't this allow Undo to work?
    BeginBatchChanges(0); // Marks document as dirty.
    SetEncoding(csid); // Will claim to be this id when saved.
    EndBatchChanges();
    CEditDocState *pState = RecordState(); // Actually translates.
    if (pState) {
        RestoreState(pState);
        // At this point this is deleted
        delete pState;
    }
    else {
        XP_ASSERT(0);
    }
}

// Add content-type meta-data. This tells the reader which character set was used to create the document.
// See RFC 2070, "Internationalization of the Hypertext Markup Language"
// http://ds.internic.net/rfc/rfc2070.txt
// <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-2022-JP">
void CEditBuffer::SetEncoding(int16 csid) {
 	char contentValue[200];
    char charSet[100];
    int16 plainCSID = csid & ~CS_AUTO;
    INTL_CharSetIDToName(plainCSID, charSet);
    XP_SPRINTF(contentValue, "text/html; charset=%.100s", charSet);

    EDT_MetaData *pData = MakeMetaData( TRUE, CONTENT_TYPE, contentValue);

    SetMetaData( pData );
    FreeMetaData( pData );
}

XP_Bool CEditBuffer::HasEncoding() {
    return FindMetaData(TRUE, CONTENT_TYPE) >= 0;
}

// Used for QA only - Ctrl+Alt+Shift+N accelerator for automated testing
void EDT_SelectNextNonTextObject(MWContext *pContext)
{
    GET_EDIT_BUF_OR_RETURN(pContext, pEditBuffer);
    pEditBuffer->SelectNextNonTextObject();
}

void CEditBuffer::SelectNextNonTextObject()
{
    CEditLeafElement *pElement;
    ElementOffset iOffset;
    XP_Bool bStickyAfter;

    GetInsertPoint( &pElement, &iOffset, &bStickyAfter );
    XP_Bool bSelectObject = FALSE;
    XP_Bool bDone = FALSE;

    while( !bDone )
    {
        if( m_pSelectedEdTable )
        {
            // Last item selected was a table - clear it
            ClearTableAndCellSelection();
        } else {
            pElement = (CEditLeafElement*)pElement->FindNextElement(&CEditElement::FindLeafAll,0);
        }

        // No more to find
        if( ! pElement )
            return;

        // Test if we are inside a table
        CEditTableElement *pTable = pElement->GetParentTable();

        if( pTable && pTable != m_pNonTextSelectedTable )
        {
            // We found a table -- select it
            LO_TableStruct *pLoTable = pTable->GetLoTable();
            if( pLoTable )
            {
                SelectTable(TRUE, pLoTable, pTable);
            }
            // That's all we need to do
            bDone = TRUE;
        }
        m_pNonTextSelectedTable = pTable;    

        if( !pTable )
        {
            EEditElementType eType = pElement->GetElementType();
            if( eType == eImageElement ||
                eType == eHorizRuleElement ||
                eType == eTargetElement )
            {
                bSelectObject = TRUE;
                bDone = TRUE;
            }
        }
    }
    
    if( pElement )
    {
        CEditInsertPoint ip(pElement, 0);
        SetInsertPoint(ip);
        if( bSelectObject )
        {
            LO_Element *pLoElement = pElement->GetLayoutElement();
            if( pLoElement )
            {
                StartSelection( pLoElement->lo_any.x+5, pLoElement->lo_any.y+1 );
            }
        }
    }
    
}

#ifdef XP_WIN16
// code segment is full, switch to a new segment
#pragma code_seg("EDTSAVE3_TEXT","CODE")
#endif

#endif
