/* -*- 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"
#define PARAM_CHARSET "charset"

// 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"

#if defined(ENDER) && defined(MOZ_ENDER_MIME)

#include "mprdecod.h"
#endif /* ENDER && MOZ_ENDER_MIME */


#define GET_COL TRUE
#define GET_ROW FALSE

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

EDT_CharacterData *CEditBuffer::m_pCopyStyleCharacterData = 0;

// When we are dragging table over another table, this is the previous cell
//  we were dragging over. Used to avoid reselecting when cell hasn't changed
//  and to unselect when moving off of the table or terminate dragging
static LO_Element *edt_pPrevReplaceCellSelected = NULL;


//-----------------------------------------------------------------------------
// 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
XP_Bool CEditBuffer::m_bNewCellHasSpace = TRUE; //New cells we create have an &nbsp in them so border displays
//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);
    PREF_GetBoolPref("editor.new_cell_has_space",&m_bNewCellHasSpace);
  }
  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);
    
    // 8/31/98: List types no longer supported
    // (they were always displayed the same as P_UNUM_LIST anyway)
    if( pTag->type == P_MENU || pTag->type == P_DIRECTORY )
        pTag->type = P_UNUM_LIST;

    /* 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;
                }
                // New strategy: Format each line of imported text into a <BR> line
                // (calls EDT_ProcessTag iteratively)
                else if( m_bImportText && !m_bInPreformat ) {
                        // calls EDT_ProcessTag iteratively
                        return NormalizePreformatText( pData, pTag, status );
                }
                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
                        //
                        // Note: We hit the "New Strategy" above when parsing
                        //       imported text, so I don't think we ever hit
                        //       this, but lets keep it to be safe.
                        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:
            if( m_bImportText )
            {
                // New strategy for importing text:
                // We will convert each imported line to a paragraph,
                //   so we can ignore the initial tag that would have
                //   resulted in PREFORMAT around all imported text 
                // (We used to fall through to insert the PREFORMAT tag)
                break;
            }
        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 ) &&  m_iLastTagType != P_PARAGRAPH) {
            }
            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, retVal );
            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;
              }


/* Don't add extra breaks here. We now add extra breaks after a </P> in
 * CEditContainerElement::AdjustContainers().
 *
 * - kin
 */
#if 0
              // 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);
                  }
              }
#endif


              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);

    char *pFontDefURL = edt_FetchParamString(pTag, PARAM_SRC, INTL_GetCSIWinCSID(c));
    if( pFontDefURL )
    {
        m_FontDefURL.Add(pFontDefURL);
        XP_Bool bFontDefNoSave = edt_FetchParamBoolExist(pTag, PARAM_NOSAVE, GetRAMCharSetID());
        m_FontDefNoSave.Add(bFontDefNoSave);
    }
}


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, XP_Bool bImportText):
        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_pBodyExtra(0),
#ifdef ENDER
		m_bEmbedded(FALSE),      
		m_pEmbeddedData(0),      
		m_pImportedHTMLStream(0),
#endif //ENDER
        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_bDontClearTableSelection(0),
        m_pCellForInsertPoint(0),
        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_bDeleteTableAfterPasting(FALSE),
        m_pStartSelectionAnchor(0),
        m_pEndSelectionAnchor(0),
        m_bStartSelectionStickyAfter(0),
        m_bEndSelectionStickyAfter(0),
        m_bLayoutBackpointersDirty(TRUE),
        m_bUseCurrentTextFormat(FALSE),
        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),
        m_bImportText(bImportText),
        m_bFillNewCellWithSpace(FALSE),
        m_iReplaceCSID(0),
        m_pWatchForDeletionElement(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

    // This is no longer changable by user
    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]);
        m_metaData.Delete(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){
    XP_ASSERT(ip.m_pElement);
    if ( ip.m_pElement == NULL )
    	return;
    
    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();
}

void CEditBuffer::SetInsertPoint(CEditInsertPoint& insertPoint) {
    CEditSelection selection(insertPoint, insertPoint, FALSE);
    SetSelection(selection);
}

void CEditBuffer::SetInsertPoint(CPersistentEditInsertPoint& insertPoint) {
    CEditInsertPoint p = PersistentToEphemeral(insertPoint);
    SetInsertPoint(p);
}

//
// 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, XP_Bool bRelayoutEntireTable ){
    CEditElement* pOldElement = NULL;
    while ( pStartElement && pStartElement != pOldElement ) {
        pOldElement = pStartElement;
        CEditElement* pTable = pStartElement->GetTopmostTableOrLayer();
        if ( m_bDisplayTables && bRelayoutEntireTable && 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 size data to match
//  sizes calculated by Layout. Must do for all tables during Relayout()
//  else generated HTML is very misleading!
void CEditBuffer::FixupTableData()
{
    // Reuse one cell data struct for maximumn efficiency
    EDT_TableCellData *pCellData = XP_NEW( EDT_TableCellData );
    if( pCellData )
        XP_MEMSET( pCellData, 0, sizeof(EDT_TableCellData));
    else
        return;

    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*)edt_GetTableElementFromLO_Element(pLoTableElement, LO_TABLE );
        if(!pEdTable ) 
            continue;

        EDT_TableData *pTableData = pEdTable->GetData();
        if(!pTableData)
            continue;

        // Get space between cells
        pTableData->iCellSpacing = 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 saved 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 or the caption
        while( pLoCell && (pLoCell->type != LO_CELL || pLoCell->lo_cell.isCaption) )
            pLoCell = pLoCell->lo_any.next;
        
        // Get space between cell border and contents 
        // ASSUMES ALL CELLS THE SAME (Need to revisit if CSS ver.2 is implemented)
        pTableData->iCellPadding = lo_GetCellPadding(pLoCell);

        CEditTableCellElement *pEdCell;

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

        // There may be a CaptionElement as the first or last
        //  child in a table. Be sure to use GetFirstRow(), GetNextRow()
        //   which guarentee a non-caption row 
        CEditTableRowElement *pRow = pEdTable->GetFirstRow();
        intn iRow = 0;

        // Clear existing layout data
        pEdTable->DeleteLayoutData();

        while( pRow )
        {
            pEdCell = pRow->GetFirstCell();

            // 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;
                
#if 0 //#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++ )
                        {
                            // We may overrun our array if table is "bad"
                            //   because of a ROWSPAN value that exceeds actual
                            //   number of rows. Just skip attempts to access a value too high
                            //   since there is no row to use the "ExtraColumns" anyway.
                            if( iRow+j < iRows )
                                ExtraColumns[iRow+j] += iColSpan;
                        }
                    }
                    
                    //  Save actual location and size data
                    //  GetSizeData only returns valid size-related info
                    //    and is much more efficient than GetData (no tag param parsing)
                   
                    pEdCell->GetSizeData(pCellData);
                    if( pCellData )
                    {
                        XP_Bool bChangedX = pLoCell->lo_any.x != pCellData->X;
                        XP_Bool bChangedY = pLoCell->lo_any.y != pCellData->Y;

                        // The LO_Element's concept of cell "width" INCLUDES
                        //   the border and cell padding, but the HTML tag value excludes these,
                        //   so we must compensate here.
                        int32 iWidthPixels = lo_GetCellTagWidth(pLoCell);
                        int32 iHeightPixels = lo_GetCellTagHeight(pLoCell);
                        
                        if( iWidthPixels != pCellData->iWidthPixels ||
                            iHeightPixels != pCellData->iHeightPixels )
                        {
                            // Only write new size data if it really changed,
                            //  since this formats HTML tag and is slow
                            pCellData->X = pLoCell->lo_any.x;
                            pCellData->Y = pLoCell->lo_any.y;
                            pCellData->iRow = iRow;
                            pCellData->iWidthPixels = iWidthPixels;
                            pCellData->iHeightPixels = iHeightPixels;
                            pEdCell->SetSizeData(pCellData);
                        }
                        else 
                        {
                            // These don't affect tag params
                            if( iRow != pCellData->iRow )
                                pEdCell->SetRow(iRow);
                            if( bChangedX )
                                pEdCell->SetX(pLoCell->lo_any.x);
                            if( bChangedY )
                                pEdCell->SetY(pLoCell->lo_any.y);
                        }
                    }
                    // Add this cell to Column and Row layout data
                    // Note: Can do only after setting size params above
                    //if( bChangedX || bChangedY )
                    // This sucks, but we have to do it for all cells each time
                    pEdTable->AddLayoutData(pEdCell, pLoCell);
                }

                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);

            // PLEASE!!! I hope this rule holds: "We can never have an empty row"
            iRow++;

            pRow = pRow->GetNextRow();
        }
        // Safety check
        XP_ASSERT(iRow == iRows);

        // Now we know maximum number of columns to set in table
        // (This is actually already set correctly by AddLayoutData,
        //  but we need to set the new width and height data anyway)
        pTableData->iColumns = pEdTable->GetColumns();
        pTableData->iRows = iRows; // Should = pTable->m_iRows;
        pEdTable->SetData(pTableData);
        EDT_FreeTableData(pTableData);

        XP_FREE(ExtraColumns);
    }

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

    EDT_FreeTableCellData(pCellData);
}

//
// 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 ){

// Use this to skip new layout (when things go wrong!)
//    Relayout( pStartElement, iEditOffset, pEndElement, relayoutFlags );
//    return;

    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
    }

    // 2nd param: Do NOT move start outside the table
    pNewStartElement = FindRelayoutStart(pStartElement, FALSE);
    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();
    } 
    else if( pEndElement == 0 )
    {
        if( pStartElement->IsLeaf() )
            pEndElement = pStartElement;
        else
            pEndElement = pStartElement->NextLeafAll();
    }

#if 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. 
            //do not go beyond the last cell for reflow. bad things happed when we reflow the wrong things
            pEndElement = pTable->GetLastMostChild();//->NextLeaf();
        }
    }
#endif

    CEditTableCellElement *pCell = FALSE;

    // 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;
        }
        // Note: If here, we must have a pStartElement and associated LO_Element
        XP_ASSERT(pStartElement && pLayoutElement);

        // If we are entirely within one cell, then use new cell reflow
        // ****** TODO: FINISH THIS WORK BEFORE ACTIVATING
        //pCell = pStartElement->GetTableCellIgnoreSubdoc();

        if( pCell && pCell == pEndElement->GetTableCellIgnoreSubdoc() )
        {
            // Start with first element in the cell
            pEdStart = pCell->GetFirstMostChild()->Leaf();
            XP_ASSERT(pEdStart);
            iOffset = 0;
        }
        else
        {
            // Clear this - we use it below to tell how to reflow
            pCell = NULL;
            
            // Find the first element on this line and get the current line number
            pLoStartLine = FirstElementOnLine( pLayoutElement, &iLineNum );
            //
            // Position the tag cursor at this position
            //
            pLoStartLine = FirstElementOnLine( pLayoutElement, &iLineNum );
            pEdStart = pLoStartLine->lo_any.edit_element;
            iOffset = pLoStartLine->lo_any.edit_offset;
        }
    }

    // This avoids crashes if end element wasn't set
    if( pStartElement == m_pRoot && pEndElement == 0)
        pEndElement = m_pRoot->GetLastMostChild();

    // Create a new cursor and reflow
    CEditTagCursor cursor(this, pEdStart, iOffset, pEndElement);
    if( pCell )
        lo_EditorCellReflow(m_pContext, &cursor, GetLoCell(pCell));
    else
        lo_EditorReflow(m_pContext, &cursor, iLineNum, iOffset);

#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.

    // Why do this for entire doc? Changed to do only from pStartElement to pEndElement
    //CEditElement* pElement= pStartElement; // m_pRoot;
    CEditElement* pElement;
    if( pStartElement->IsLeaf() )
        pElement = pStartElement;
    else
        pElement = pStartElement->FindNextElement(&CEditElement::FindLeafAll,0);

    if( pEndElement && !pEndElement->IsLeaf() )
        pEndElement = pEndElement->FindNextElement(&CEditElement::FindLeafAll,0);

    do {
        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;
                        }
                    }
                }
            }
        }
        pElement = pElement->FindNextElement(&CEditElement::FindLeafAll,0);
        if( pElement && pElement == pEndElement )
            break;
    }
    while ( pElement != NULL );
    
    
    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;
    }

    if( m_pCellForInsertPoint )
    {
        // Move insert point to a safe place?
        // (at first leaf element before the table)
        CEditElement *pElement = (CEditElement*)m_pCellForInsertPoint->GetTable();
        if( pElement )
        {
            CEditElement *pLeaf = pElement->PreviousLeaf();
            if( pLeaf )
                SetInsertPoint(pLeaf->Leaf(), 0, m_bCurrentStickyAfter);
        }
    }

   // 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;
    }
    // This avoids crashes if end element wasn't set
    if( pStartElement == m_pRoot && pEndElement == 0)
        pEndElement = m_pRoot->GetLastMostChild();

    // 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
    // in.

    // Why do this for entire doc? Changed to do only from pStartElement to pEndElement
    //CEditElement* pElement= pStartElement; // m_pRoot;
    CEditElement* pElement;
    if( pStartElement->IsLeaf() )
        pElement = pStartElement;
    else
        pElement = pStartElement->FindNextElement(&CEditElement::FindLeafAll,0);

    if( pEndElement && !pEndElement->IsLeaf() )
        pEndElement = pEndElement->FindNextElement(&CEditElement::FindLeafAll,0);

    do {
        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;
                        }
                    }
                }
            }
        }
        pElement = pElement->FindNextElement(&CEditElement::FindLeafAll,0);
        if( pElement && pElement == pEndElement )
            break;
    }
    while ( pElement != NULL );

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

    // Resynch table selection
    if( m_pSelectedEdTable )
    {
        m_pSelectedLoTable = m_pSelectedEdTable->GetLoTable();
        SelectTable(TRUE, m_pSelectedLoTable, m_pSelectedEdTable);
    } else {
        intn iEdCount = m_SelectedEdCells.Size();
        for( int i = 0; i < iEdCount; i++ )
        {
            // Get something we can observe in debugger
            CEditTableCellElement *pEdCell = m_SelectedEdCells[i];
            // Get the new LO_CellStruct matching the previously-selected edit element
            LO_CellStruct *pLoCell = pEdCell->GetLoCell();
            // Resync the list and display the cell
            SelectCell(TRUE, pLoCell, pEdCell);
        }
    }

    // Check for cell to move insert point into after relayout
    if( m_pCellForInsertPoint )
    {
        // Move caret to end of contents in designated cell
        SetTableInsertPoint(m_pCellForInsertPoint->TableCell(), FALSE);
        m_pCellForInsertPoint = 0;
    } 
    else 
    {
        // Else reset caret or selection as it was before
        if( (relayoutFlags & RELAYOUT_NOCARET) == 0 && !bWasSelected){
            SetCaret();
        }

        if( bWasSelected ){
            SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
        }
    }
    m_bLayoutBackpointersDirty = FALSE;
    
#if defined(XP_WIN) || defined(XP_OS2)
    // This clears FE pointers to cached layout elements
    FE_FinishedRelayout(m_pContext);
#endif
}

// Call the next two funcions instead of Relayout( pTable )
//  when we want to relayout entire table because we are changing
//  either the table size or cell size(s)
// These set the correct "table size mode", call Relayout,
//  then RestoreSizeMode() so the resize actually
//  happens. If we do not, fixed cell sizes can defeat attempts
//  to resize the table and vice versa.
// bChangeWidth and bChangeHeight tell us which dimension is changing

void CEditBuffer::ResizeTable(CEditTableElement *pTable, XP_Bool bChangeWidth, XP_Bool bChangeHeight)
{
    if( !pTable )
        return;

    // Convert all the cell's values to PERCENT mode
    // This is the best attempt we can make to keep current column
    //  and row dimensions. If we use Pixels, we cannot reduce
    //  the size of the table since layout uses max(Table-WIDTH, sum-of-Cell-WIDTHS)
    //  Also, percent mode results in less distortion in size
    //  of individual columns than pixel mode when increasing the width or height of table
    //  WE NEED EQUIVALENT OF CSS2's "FIXED" TABLE LAYOUT MODE TO FREEZE COL AND ROW SIZES!
    //  (See note in ResizeTableCell about column span problem
    // DON'T USE COLS MODE! TOO MESSED UP FOR COMPLICATED TABLES (COLSPAN and ROWSPAN > 1)
    int iMode = ED_MODE_CELL_PERCENT | ED_MODE_NO_COLS; //((pTable->FirstRowHasColSpan() ? ED_MODE_NO_COLS : ED_MODE_USE_COLS));
    
    if( bChangeWidth )
        iMode |= ED_MODE_USE_TABLE_WIDTH;

    if( bChangeHeight )
        iMode |= ED_MODE_USE_TABLE_HEIGHT;

    // Set size params to the above modes but saves current settings first
    pTable->SetSizeMode(m_pContext, iMode);
    Relayout(pTable, 0, pTable);
    // Restore to the size mode settings saved in SetSizeMode
    pTable->RestoreSizeMode(m_pContext);
}

void CEditBuffer::ResizeTableCell(CEditTableElement  *pTable, XP_Bool bChangeWidth, XP_Bool bChangeHeight)
{
    if( !pTable )
        return;

    // Set all cell sizes to pixel mode so each col or row are equally 
    //   treated by layout. This makes sure all the change is just in the desired col or row
    // NOTE: If COLS mode is used, but 1st row has any cells with COLSPAN>1,
    //       the column widths "under" each spanned cell will always be
    //       apportioned equally. Thus, in general, NOT using COLS results
    //       in more deterministic column sizing.
    // PROBLEM: When some cells have COLSPAN (not necessarily the first row),
    //          using COLS prevents the last column from being resized 
    //          (it seems to have a lower limit = width of column to the left)
    //          TODO: Lets not use COLS until this is fixed
    int iMode = ED_MODE_CELL_PIXELS | ED_MODE_NO_COLS; // | (pTable->FirstRowHasColSpan() ? ED_MODE_NO_COLS : ED_MODE_USE_COLS);

    // Change param only for the dimension being resized
    // Also turn OFF size param in table to remove its influence
    //   on laying out the table.
    if( bChangeWidth )
        iMode |= (ED_MODE_USE_CELL_WIDTH | ED_MODE_NO_TABLE_WIDTH);
    if( bChangeHeight )
        iMode |= (ED_MODE_USE_CELL_HEIGHT  | ED_MODE_NO_TABLE_HEIGHT);

    pTable->SetSizeMode(m_pContext, iMode);
    Relayout(pTable, 0, pTable);
    pTable->RestoreSizeMode(m_pContext);
}

// 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;
    } 
    else
    {
        ClearTableAndCellSelection();
        CEditTableCellElement* pTableCell = m_pCurrent->GetTableCellIgnoreSubdoc();
        CEditElement *pParent = m_pCurrent->GetParent();
        CEditElement* pPrev = m_pCurrent->GetPreviousSibling();
        CEditElement* pNew = m_pCurrent->GetNextSibling();
        if( pParent && m_pCurrent->IsText() && pTableCell && 
            m_pCurrent == pParent->GetChild() &&     //We are the first child
            m_pCurrent->GetNextSibling() == NULL &&  //No other siblings after
            pParent->GetNextSibling() == NULL  &&
            m_pCurrent->Text()->GetLen() == 1 )
        {
            char* pText = m_pCurrent->Text()->GetText();
            if( pText[0] == ' ' && pNewChars && !(pNewChars[0] == ' ') )
            {
                // We are in a table cell with just one space in it
                // (probably there only to show cell borders)
                // Delete this space before inserting new text
                CEditLeafElement *pEle;
                ElementOffset iOffset;
                XP_Bool bStickyAfter;
                GetInsertPoint( &pEle, &iOffset, &bStickyAfter );

                // Delete forward if to the left of the space,
                //  or delete back if to the right.
                DeleteChar(iOffset == 0, bTyping);
            }
        }
    }

    ClearMove(FALSE);
    
    // Following will move the insert point that is at the beginning of 
    //   one element to the end of the previous.
    // Don't do this if we are at an empty element or we set the flag
    //  to use the current element's text formatting 
    if( !IsPhantomInsertPoint() && !m_bUseCurrentTextFormat ){
        FixupInsertPoint();
    }
    // This is good for only one key action
    m_bUseCurrentTextFormat = FALSE;

    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()); 

        // 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() )
    {
        DoneTyping();
        BeginBatchChanges(kGroupOfChangesCommandID);
        // We assume that table or cells are selected ONLY
        //  if there's not a "normal" selection
        if( m_pSelectedEdTable )
        {
            // Delete the entire table
            // Assumes current insert point is inside the selected table
    		AdoptAndDo(new CDeleteTableCommand(this));
        } else if( m_SelectedEdCells.Size() )
        {
            // Delete all selected cells
            // This will delete the cell elements ONLY
            // if a complete row or column is selected.
            // If not, only cell contents are cleared
            // to miminize messing up table layout
            // TRUE means we really delete cell contents
            //   and not put a space in those cells
            DeleteSelectedCells(TRUE);
        }
        EndBatchChanges();
    } 
    else 
    {
        ClearPhantomInsertPoint();
        ClearMove();
        StartTyping(bTyping);
        if( IsSelected() ){
            result = DeleteSelection();
        }
        else
        {
            CEditSelection selection;
            GetSelection(selection);
            if ( Move(*selection.GetEdge(bForward), bForward ) )
            {
                // First TRUE = "strict checking"
                // We can currently (I hope!) handle backspacing from 
                //  outside a table into a table from cell to cell,
                //  but delete key (bForward = TRUE) has lots of strange side-effects
                //  so lets not allow that (TRUE means check for cell boundaries)
                result = CanCut(selection, (XP_Bool)TRUE, bForward);
                if ( result == EDT_COP_OK ) {
                    DeleteSelection(selection, FALSE);
                }
            }
        }
        // After deleting at a boundary between elements, 
        // caret is always at the end of the current element to the left of caret.
        // Check for this and move to the beginning of next element 
        //   and set flag to use format of this element
        // Note that testing for bForward means Backspace key will
        ///  always use format of element to the left. 
        // TODO: Is that the correct thing to to?
        CEditElement *pNext = m_pCurrent ? m_pCurrent->GetNextSibling() : NULL;
        m_bUseCurrentTextFormat = ( bForward && m_pCurrent && pNext && 
                                    m_pCurrent->IsText() && pNext->IsText() &&
                                    (m_iCurrentOffset == m_pCurrent->Text()->GetLen()) &&
                                    pNext->Text()->GetLen() > 0 ) ? TRUE : FALSE;
        if( m_bUseCurrentTextFormat )
        {
            // We need to move insert point so m_pCurrent = pNext;
            SetInsertPoint(pNext->Leaf(), 0, m_bCurrentStickyAfter);
        }
    }
    return result;
}

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);
    m_bUseCurrentTextFormat = FALSE;
    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 bForward,  XP_Bool bEndOfCell, 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 )
    {
        // Place insert point at beginning of cell's contents, 
        //   or at the end.
        CEditInsertPoint insertPoint;
        insertPoint.m_pElement = bEndOfCell ? pNext->GetLastMostChild()->Leaf() :
                                              pNext->GetFirstMostChild()->Leaf();
        if( insertPoint.m_pElement )
        {
            if( bEndOfCell )
                insertPoint.m_iPos = insertPoint.m_pElement->GetLen();

            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();
        // If moving from right to left with arrow or Home,
        //  then set flag to use format to the right of caret upon next text insert
        m_bUseCurrentTextFormat = (!bForward && m_pCurrent && m_pCurrent->IsText() 
                                    && m_iCurrentOffset == 0) ? TRUE : FALSE;
    }
}

//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, XP_Bool bIndent){
    ClearTableAndCellSelection();
    EDT_ClipboardResult result = EDT_COP_OK;
    XP_Bool bEndBatch = FALSE;

    if ( bTyping )
    {
        VALIDATE_TREE(this);
#ifdef DEBUG
        if ( m_pTestManager->ReturnKey() )
            return result;
#endif
        if( IsSelected() )
        {
            BeginBatchChanges(kGroupOfChangesCommandID);
            bEndBatch = TRUE;
            result = DeleteSelection();
            if ( result != EDT_COP_OK )
                goto END_RETURN;
        }
        // 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 )
                {
                    StartTyping(TRUE);
                    InternalReturnKey(TRUE);
                    UpDown(FALSE, FALSE); // go back up one line.
                }
                ClearMailQuote();
            }
        }
        else 
        {
            if( m_pCurrent && m_iCurrentOffset == 0 &&
                m_pCurrent->IsText() && m_pCurrent->GetLen() == 0 &&
                m_pCurrent->GetNextSibling() == 0 &&
                m_pCurrent->PreviousLeafInContainer() == 0 )
            {
                CEditContainerElement *pContainer;
                CEditListElement* pList;
                m_pCurrent->FindList(pContainer, pList);
                if( pContainer && pList )
                {
                    // We are at the begining of an empty element in a list item container,
                    // If we were going to Indent, just do that to current container,
                    if( bIndent )
                        Indent();
                    else                            
                        // Outdent to next higher list level
                        Outdent();

                    result = EDT_COP_OK;
                    goto END_RETURN;
                }
            }
            // Fix for bug 80092 - force dirty flag on after Enter key alone is pressed,
            // but not if doing a replacement of selected text (we do batch UNDO for that)
            if( !bEndBatch )
                StartTyping(TRUE);
            result = InternalReturnKey(TRUE);
//          Include this to set the end of current UNDO at the end of a paragraph (return key)
//            so Undo will only remove the last paragraph typed.
//          By not including this, multiple paragraphs typed without setting caret with mouse 
//            will all be undone together.
//            DoneTyping();

            // Indent the newly-created container one level if requested
            if( bIndent )
                Indent();
        }
    }
    else {
        // We end up here when pasting text (and other paths?)
        //  so we don;t want to call StartType()
        result = InternalReturnKey(TRUE);
    }

END_RETURN:
    if( bEndBatch )
        EndBatchChanges();

    return result;
}

// Returns the container of the first element after the split
CEditElement* CEditBuffer::SplitAtContainer(XP_Bool bUserTyped, XP_Bool bSplitAtBeginning, CEditElement*& pRelayoutStart)
{
    // ClearSelection();
    // Make a text element now before we loose formatting styles
    //  because ClearPhantomInsertPoint() will delete m_pCurrent 
    //  if its an empty element
    //XP_Bool bFormatChanged;
    ClearPhantomInsertPoint();
    // Move the insert point from beginning of current 
    //  to the end of previous leaf element
    FixupInsertPoint();

    pRelayoutStart = m_pCurrent;
    CEditLeafElement *pNew,*pSplitAfter;
    CEditLeafElement *pCopyFormat;

    pSplitAfter = m_pCurrent;
    pCopyFormat = m_pCurrent;

    pRelayoutStart = m_pCurrent->FindContainer();
    if( m_iCurrentOffset == 0 )
    {
        // We are at the beginning of the element

        if( m_pCurrent->PreviousLeafInContainer() == 0 )
        {
            // There are no elements before - we are at beginning of Container

            if( bSplitAtBeginning )
            {
                // Create a new empty text element
                CEditTextElement *pNew;
                pNew = m_pCurrent->CopyEmptyText();
                pNew->InsertBefore( m_pCurrent );
//                pRelayoutStart = pNew->FindContainer();
                pSplitAfter = pNew;
            }
            else
            {
                // Don't split - don't create anything
//                pRelayoutStart = m_pCurrent->FindContainer();
                return pRelayoutStart;
            }
        }
    }
    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 );

    CEditContainerElement* pContainer = m_pCurrent->FindContainer();
    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
        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
        }
        else if ( tagType == P_DESC_TITLE )
        {
            pContainer->SetType(P_DESC_TEXT);
        }
    }
    return pContainer;
}

EDT_ClipboardResult CEditBuffer::InternalReturnKey(XP_Bool bUserTyped)
{
    EDT_ClipboardResult result = EDT_COP_CLIPBOARD_BAD;
    if( IsSelected() )
    {
        result = DeleteSelection();
        if ( result != EDT_COP_OK )
            return result;
    }
    CEditTextElement *pEmptyFormatElement = NULL;
    if( bUserTyped /*&& IsPhantomInsertPoint()*/ )
        pEmptyFormatElement = m_pCurrent->CopyEmptyText();

    CEditElement *pChangedElement;
    // Split at current element to create a new container (TRUE = split at beginning of element as well
    SplitAtContainer(bUserTyped, TRUE, pChangedElement);

    int iChangedOffset = (pChangedElement == m_pCurrent) ? m_iCurrentOffset : 0;
    if( pChangedElement )
    {
        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 ));

        // Check if we were originally at an empty text element
        //   with different formating than current element --
        //   it was deleted during ClearPhantomInsertPoint() in SplitAtContainer()
        if( !m_bUseCurrentTextFormat && pEmptyFormatElement && 
            m_pCurrent && m_pCurrent->IsText() &&
            !m_pCurrent->Text()->SameFormat(pEmptyFormatElement) )
	    {
            if( m_pCurrent->GetLen() > 0 )
		    {
                // We have some text, so we must insert the empty element
                //  that has the correct formatting
			    pEmptyFormatElement->InsertBefore(m_pCurrent);
			    // and make it the current element
                m_pCurrent = pEmptyFormatElement;
                m_iCurrentOffset = 0;
			    // Prevent deletion below
			    pEmptyFormatElement = NULL;
		    }
		    else
		    {
			    // Copy the previous attributes to the existing empty text elemet
			    pEmptyFormatElement->CopyTextFormat(m_pCurrent->Text());
                // Must relayout for this to display correctly
                Relayout(m_pCurrent, 0, m_pCurrent);
		    }
        }
        result = EDT_COP_OK;
    }

    if( pEmptyFormatElement )
        delete pEmptyFormatElement;

    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){
        // 2nd param = bEndOfCell: Should we move to the END of cell's contents? (see below as well)
        if( NextTableCell(bForward, FALSE) ){
            // 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(bForward, FALSE); // Should this be TRUE?
            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 );
    if( !pContainer )
        return;

    // This will return the new list if one was created
    IndentContainer( pContainer, pList );

    CEditElement *pStart = pContainer;
    CEditElement *pEnd = pContainer;
    XP_ASSERT(pContainer);
    CEditElement *pPrev;
    if( pList )
    {
        pStart = pList;
    } else if( (pPrev = pContainer->GetPreviousSibling()) != NULL )
    {
        pStart = pPrev;
    }
    Relayout( pStart, 0, pEnd->GetLastMostChild() );
}

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

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

    // Consider context (whether we are just after or before an existing list)
    //   only if we are indenting a list item
    if( pList )
    {
        //
        // 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 && pList->IsCompatableList(pPrev) )
        {
            CEditElement *pChild = pPrev->GetLastChild();
            if( pChild )
            {
                pContainer->Unlink();
                pContainer->InsertAfter( pChild );
            }
            else
            {
                pContainer->Unlink();
                pContainer->InsertAsFirstChild( pPrev );
            }

            if( pNext && pNext->IsList() )
                pPrev->Merge( pNext );

            bDone = TRUE;
        }
        //
        // 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 && pList->IsCompatableList(pNext) )
        {
            CEditElement *pChild = pNext->GetChild();
            if( pChild ){
                pContainer->Unlink();
                pContainer->InsertBefore( pChild );
            }
            else {
                pContainer->Unlink();
                pContainer->InsertAsFirstChild( pPrev );
            }
            bDone = TRUE;
        }
        //
        // 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);
            bDone = TRUE;
        }
    }
    //
    // case 0
    //
    //      LI:             <- Indenting this guy
    //
    // should result in
    //
    //      UL:
    //          LI:
    //
    //
    if( !bDone && pList == 0 )
    {
        PA_Tag *pTag = XP_NEW( PA_Tag );
        XP_BZERO( pTag, sizeof( PA_Tag ) );
        // Set appropriate list type for the given list item
        // (E.g., only list items should exist inside of UL, OL)
        // Use blockquote instead (like MS FrontPage)
        // Note: We pass through here when creating UL and OL
        //  lists as well, but the list type is changed 
        //  correctly later on, so this works fine
        TagType t = pContainer->GetType();
        switch ( t )
        {
            case P_LIST_ITEM:
                pTag->type = P_UNUM_LIST;
                break;
            case P_DESC_TITLE:
            case P_DESC_TEXT:
                pTag->type = P_DESC_LIST;
                break;
            default:
                pTag->type = P_BLOCKQUOTE;
                break;
        }

        if (pTag->type == P_BLOCKQUOTE && pPrev
            && pPrev->GetType() == pTag->type)
        {
            CEditElement *pChild = pPrev->GetLastChild();
            if( pChild )
            {
                pContainer->Unlink();
                pContainer->InsertAfter( pChild );
            }
            else
            {
                pContainer->Unlink();
                pContainer->InsertAsFirstChild( pPrev );
            }
            if( pNext && pNext->IsList() )
            {
                pPrev->Merge( pNext );
            }
        }
        else
        {
            CEditElement *pEle = new CEditListElement( 0, pTag, GetRAMCharSetID() );
            pEle->InsertAfter( pContainer );
            pContainer->Unlink();
            pContainer->InsertAsFirstChild( pEle );
            // Return the new list created
            pList = (CEditListElement*)pEle;
        }

        PA_FreeTag( pTag );
    }
}

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 )
        return FALSE;

    OutdentContainer( pContainer, pList );
    // Get the list elemement again - it might have changed
    m_pCurrent->FindList( pContainer, pList );
    CEditElement *pStart = pContainer;
    CEditElement *pEnd = pContainer;
    XP_ASSERT(pContainer);
    CEditElement *pPrev;
    if( pList )
    {
        pStart = pList;
    } else if( (pPrev = pContainer->GetPreviousSibling()) != NULL )
    {
        pStart = pPrev;
    }
    Relayout( pStart, 0, pEnd->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 
    {
        // Clone the list to put following elements in it
        CEditElement *pNewList = pList->Clone(0);
        while( (pNext = pContainer->GetNextSibling()) != 0 )
        {
            pNext->Unlink();
            pNext->InsertAsLastChild( pNewList );
        }
        pContainer->Unlink();
        pContainer->InsertAfter( pList );
        pNewList->InsertAfter( pContainer );
    }
    // If we outdented so that we are no longer in the list,
    //   we must change container types that are only allowed 
    //   in a list to our "normal" container type
    TagType t = pContainer->GetType();
    if( !pContainer->GetParent()->IsList() && 
         ( t == P_LIST_ITEM ||
           t == P_DESC_TITLE ||
           t == P_DESC_TEXT ) )
    {
        pContainer->SetType(P_NSDT);
    }
}

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.
}

// Outdent the container until we don't have a list
void CEditBuffer::TerminateList(CEditContainerElement *pContainer)
{
    CEditElement *pParent;
    
    if( !pContainer || !(pParent = pContainer->GetParent())->IsList() )
        return;
    do {
        // Move container up one level,
        //  this will also change container type to "normal" (P_NSDT)
        //  when we outdent from the top list level
        OutdentContainer(pContainer, pParent->List() );
        pParent = pContainer->GetParent();
    }
    while( pParent && pParent->IsList() );
}


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 {
        CEditContainerElement *pContainer;
        CEditListElement *pList;
        // Get the container and list 
        m_pCurrent->FindList(pContainer, pList);
        // HACKOLA: just poke in a new tag type.
        pContainer->SetType( t );
        if( pList )
        {
            TagType tList = pList->GetType();
            // Assure that list-type items are only contained in their proper parents. 
            // If not the right type, terminate the list
            // We should never see P_MENU or P_DIRECTORY any more
            // (translated into P_UNUM_LIST in ::ParseTag() )
            if( ((tList == P_UNUM_LIST || tList == P_NUM_LIST /*|| tList == P_MENU || tList == P_DIRECTORY*/) && 
                  t != P_LIST_ITEM) ||
                (tList == P_DESC_LIST && !(t == P_DESC_TITLE || t == P_DESC_TEXT)) )
            {
                TerminateList(pContainer);
                // Clear this -- we may have to change to a different list type below
                pList = NULL;
            }
        }

        if( !pList && (t == P_LIST_ITEM || t == P_DESC_TITLE || t == P_DESC_TEXT ) )
        {
            // We are changing to a list item that must be in a list container,
            //  but we are not currently in a list, so indent to create appropriate list
            // First reset container type to requested, since TerminateList probably changed it
            pContainer->SetType( t );
            IndentContainer(pContainer, pList);   
        }

        // Need to relayout from previous line.  Line break distance is wrong.
        CEditElement *pStart;
        if( pList ) 
            pStart = pList;
        else
            pStart = pContainer;

        // Note: This crashes in lo_MergeState (layedit.c) if 3rd param (pEndElement) is NULL
        Relayout( pStart, 0, pContainer );
    }
}

void CEditBuffer::MorphContainerSelection( TagType t, CEditSelection& selection )
{
    CEditLeafElement *pBegin, *pEnd, *pCurrent;
    CEditContainerElement* pContainer = NULL;
    ED_BufferOffset/*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;
    CEditListElement *pList = NULL;
    CEditListElement *pPrevList = NULL;
    do {
        // Get the container and list  
        pCurrent->FindList( pContainer, pList );
        XP_ASSERT(pContainer);
        pContainer->SetType( t );
        // TODO: CHECK THE EFFECTS OF <BR> AFTER A LIST ITEM
        if( pList )
        {
            TagType type = pList->GetType();
            // If we are changing an item in a list to something other than
            //   a valid list item for that type of list,
            //   this means terminate the list
            if( (type == P_DESC_LIST && 
                 !(t == P_DESC_TITLE || t == P_DESC_TEXT)) ||
                (type != P_BLOCKQUOTE && type != P_DESC_LIST && t != P_LIST_ITEM ) )
            {
                TerminateList(pContainer);
            } else {
                pPrevList = pList;
            }
        }
        else if( t == P_LIST_ITEM )
        {
            // We are changing to a list item but
            //  we are not in a list.
            // If pPrevList is NULL, this creates a default UNUM list,
            //   which is returned to us.
            // If we created one before or previous container was already
            //  in a list, this will insert the container into that list
            IndentContainer(pContainer, pPrevList);
        }
        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*/ )
{
    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 {
        GetSelection(selection);
        ToggleList2(iTagType, selection);
    }
}

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

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

    if ( bIsMyList && 
         (nParagraphFormat == P_LIST_ITEM || 
          iTagType == P_DESC_LIST) )
    {
        // This will remove any list container
        MorphContainer(P_NSDT);
    } 
    else if( bIsMyList && iTagType == P_BLOCKQUOTE )
    {
        // Remove all indent levels
        if( m_pCurrent )
        {
            CEditContainerElement *pContainer;
            CEditListElement *pList;
            m_pCurrent->FindList(pContainer, pList);
            TerminateList(pContainer);
            Relayout( pContainer, 0, pContainer );
        }
    }
    else 
    {
        if ( !pListData )
        {
            if( iTagType == P_BLOCKQUOTE )
            {
                Indent();
            }
            // Create a numbered list item ONLY if not Description list or BlockQuote
            // (This will automatically indent, i.e., create the list container)
            else if (nParagraphFormat != P_LIST_ITEM && iTagType != P_DESC_LIST ) 
            {
                MorphContainer(P_LIST_ITEM);
            }
            pListData = 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);
            pListData = GetListData();
        }

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

void CEditBuffer::SetParagraphAlign( ED_Alignment eAlign ){
    VALIDATE_TREE(this);
    XP_Bool bDone;
    XP_ASSERT(m_pCurrent);
    
    // Use insert point instead of m_pCurrent in case we're selected (m_pCurrent = 0)
    CEditElement *pParent = 0;
    CEditLeafElement *pInsertPoint;
    GetPropertyPoint(&pInsertPoint);
    XP_ASSERT(pInsertPoint);
    pParent = pInsertPoint->GetParent();

    // See we are inside a caption
    XP_Bool bInCaption = FALSE;
    while( pParent && pParent != m_pRoot )
    {
        if( pParent->IsCaption() )
        {
            bInCaption = TRUE;
            break;
        }
        pParent = pParent->GetParent();
    }
    // To avoid extra HTML params, use ED_ALIGN_DEFAULT instead of ED_ALIGN_LEFT
    //  for left align since that's how layout will do it
    // But we must keep an explicit ED_ALIGN_LEFT if inside a table caption
    if( !bInCaption && 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, XP_Bool bRelative )
{
    VALIDATE_TREE(this);

    CEditSelection selection;
    GetSelection(selection);
    if ( selection.IsContainerEnd() )
        return;
    else if( selection.AnyLeavesSelected() )
    {
        BeginBatchChanges(kChangeAttributesCommandID);
        SetFontSizeSelection( iSize, bRelative, selection, TRUE );
        EndBatchChanges();
        return;
    }
    if( IsTableOrCellSelected() )
    {
        if( GetFirstCellSelection(selection) )
        {
            BeginBatchChanges(kChangeAttributesCommandID);
            // Format first selected cell
            SetFontSizeSelection( iSize, bRelative, selection, FALSE );
            // Select all other cells and format them
            while( GetNextCellSelection(selection) )
            {
                SetFontSizeSelection( iSize, bRelative, 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, above term in the conditional test should not be here, e.g.
    // in CEditBuffer::SetCharacterData() it is not there.
        pRight->Text()->SetFontSize(iSize, bRelative);
        m_pCurrent = pRight;
        m_iCurrentOffset = 0;
    }
    else 
    {
        CEditTextElement *pNew = m_pCurrent->CopyEmptyText();
        pNew->InsertBefore(pRight);
        pNew->SetFontSize( iSize, bRelative );
        m_pCurrent = pNew;
        m_iCurrentOffset = 0;
    }
    Relayout( m_pCurrent, 0, 0 );
}

void CEditBuffer::SetFontSizeSelection( int iSize, XP_Bool bRelative, 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) )
        {
            // Set new size if its still inside allowable range
            pCurrent->Text()->SetFontSize(iSize, bRelative);
        }
        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));
}

ED_ElementType CEditBuffer::GetBackgroundColor(LO_Color *pColor)
{
    if( !pColor )
        return ED_ELEMENT_NONE;

    XP_Bool bDefaultColors = FALSE;
    ED_ElementType type = ED_ELEMENT_TEXT;

    if( m_pSelectedEdTable )
    {
        // A table is selected
        type = ED_ELEMENT_TABLE;

        EDT_TableData *pData = GetTableData();
        if( pData && pData->pColorBackground )
        {
            pColor->red = pData->pColorBackground->red;
            pColor->green = pData->pColorBackground->green;
            pColor->blue = pData->pColorBackground->blue;
        } else {
            bDefaultColors = TRUE;
        }
    } else if( m_SelectedEdCells.Size() || IsInsertPointInTableCell() )
    {
        // Table cell(s) are selected or caret is in a cell
        type = ED_ELEMENT_CELL;

        EDT_TableCellData *pData = GetTableCellData();
        if( pData && pData->pColorBackground )
        {
            pColor->red = pData->pColorBackground->red;
            pColor->green = pData->pColorBackground->green;
            pColor->blue = pData->pColorBackground->blue;
        } else {
            bDefaultColors = TRUE;
        }
    } else {
        EDT_PageData *pData = GetPageData();
        if( pData && pData->pColorBackground )
        {
            pColor->red = pData->pColorBackground->red;
            pColor->green = pData->pColorBackground->green;
            pColor->blue = pData->pColorBackground->blue;
        } else {
            bDefaultColors = TRUE;
        }
    }

    if( bDefaultColors )
    {
        pColor->red = lo_master_colors[LO_COLOR_BG].red;
        pColor->green = lo_master_colors[LO_COLOR_BG].green;
        pColor->blue= lo_master_colors[LO_COLOR_BG].blue;
    }
    return type;
}

void CEditBuffer::SetBackgroundColor(LO_Color *pColor)
{
    if( m_pSelectedEdTable )
    {
        // A table is selected
        EDT_TableData *pData = GetTableData();
        if( pData )
        {
            if( pColor )
            {
                // Set the color - create struct if not already set
                if( !pData->pColorBackground )
                    pData->pColorBackground = XP_NEW( LO_Color );

                if( pData->pColorBackground )
                {
                    pData->pColorBackground->red = pColor->red;
                    pData->pColorBackground->green = pColor->green;
                    pData->pColorBackground->blue = pColor->blue;
                }
            } else {
                // We are clearing the current color
                XP_FREEIF(pData->pColorBackground);
            }
            SetTableData(pData);
            EDT_FreeTableData(pData);
        }
    } else if( m_SelectedEdCells.Size() || IsInsertPointInTableCell() )
    {
        // Table cells are selected or caret is in table
        EDT_TableCellData *pData = GetTableCellData();
        if( pData )
        {
            // We are only interested in setting the background color
            pData->mask = CF_BACK_COLOR;

            if( pColor )
            {
                if( !pData->pColorBackground )
                    pData->pColorBackground = XP_NEW( LO_Color );

                if( pData->pColorBackground )
                {
                    pData->pColorBackground->red = pColor->red;
                    pData->pColorBackground->green = pColor->green;
                    pData->pColorBackground->blue = pColor->blue;
                }
            } else {
                XP_FREEIF(pData->pColorBackground);
            }
            SetTableCellData(pData);
            EDT_FreeTableCellData(pData);
        }
    } else {
        EDT_PageData *pData = GetPageData();
        if( pData )
        {
            if( pColor )
            {
                if( !pData->pColorBackground )
                    pData->pColorBackground = XP_NEW( LO_Color );

                if( pData->pColorBackground )
                {
                    pData->pColorBackground->red = pColor->red;
                    pData->pColorBackground->green = pColor->green;
                    pData->pColorBackground->blue = pColor->blue;
                }
            } else {
                XP_FREEIF(pData->pColorBackground);
            }
            SetPageData(pData);
            EDT_FreePageData(pData);
        }
    }
}

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 user is setting the format, then they
    //  will always want to use it instead of the 
    //  element to the right of the caret
    m_bUseCurrentTextFormat = FALSE;

    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);

    // We are setting data at caret, so clear flag
    //  to use style to the right of caret
    m_bUseCurrentTextFormat = FALSE;

    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 );
    m_bUseCurrentTextFormat = FALSE;
}

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 );
    if( selection.IsEmpty() )
        return 0;

    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;
}

XP_Bool CEditBuffer::CanConvertTextToTable()
{
    if( IsSelected() )
    {    
        CEditLeafElement *pEndLeaf, *pBeginLeaf;
        ED_BufferOffset iBeginPos, iEndPos;
        XP_Bool bFromStart;
        // Get current selection
        CEditSelection selection;
        GetSelection( selection, pBeginLeaf, iBeginPos, pEndLeaf, iEndPos, bFromStart );
        if( selection.m_start.m_pElement && selection.m_end.m_pElement )
        {
            CEditTableCellElement *pStartCell = selection.m_start.m_pElement->GetParentTableCell();
            CEditTableCellElement *pEndCell =  selection.m_end.m_pElement->GetParentTableCell();
            
            // We can convert if neither element is inside a table
            //   or both are inside the same cell
            if( (!pStartCell && !pEndCell) || pStartCell == pEndCell )
                return TRUE;
        }
    }
    return FALSE;
}

CEditElement *GetCommonParent(CEditElement *pElement, CEditElement *pCommonAncestor)
{
    CEditElement *pParent = pElement;
    CEditElement *pCommonParent = pElement;

    while( (pParent = pParent->GetParent()) != NULL && pParent != pCommonAncestor )
        pCommonParent = pParent;

    return pCommonParent;
}

// 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)
{
    if( !CanConvertTextToTable() )
        return;
    
    // We need at least one column
    iColumns = max(1, iColumns);

    VALIDATE_TREE(this);
    CEditLeafElement *pEndLeaf, *pBeginLeaf;
    ED_BufferOffset iBeginPos, iEndPos;
    XP_Bool bFromStart;
    
    // Get current selection
    CEditSelection selection;
    GetSelection( selection, pBeginLeaf, iBeginPos, pEndLeaf, iEndPos, bFromStart );

    // Guarantee that the text blocks of the beginning and end of selection
    //  are atomic.
    MakeSelectionEndPoints(selection, pBeginLeaf, pEndLeaf );
    // Check for end of doc and backup to previous leaf
    if (selection.m_end.m_pElement && selection.m_end.m_pElement->IsEndOfDocument() )
    {
        selection.m_end.m_pElement = selection.m_end.m_pElement->PreviousLeaf();
        selection.m_end.m_iPos = selection.m_end.m_pElement->GetLen();
    }
    
    if( !selection.m_start.m_pElement || !selection.m_end.m_pElement )
        return;    

    CEditElement* pCommonAncestor = selection.m_start.m_pElement->GetCommonAncestor(selection.m_end.m_pElement);

    // Create and insert a table with 1 row 
    EDT_TableData *pData = EDT_NewTableData();
    if( !pData )
        return;
    pData->iColumns = iColumns;

    BeginBatchChanges(kGroupOfChangesCommandID);

    // Suppress relayout until we are all done
    m_bNoRelayout = TRUE;

    CEditElement *pCurrent, *pEnd, *pBegin, *pNext;

    // Save the start insert point,
    //  then set insert point to end and break into new containers
    CEditInsertPoint save_start = selection.m_start;
    selection.m_start = selection.m_end;
    SetSelection(selection);

    // TODO: Go to topmost parent, but we need to split at the 
    //       same level as the "common parent", not at nearest 
    //       container, as done in InternalReturnKey()
//    pEnd = GetCommonParent(selection.m_end.m_pElement, pCommonAncestor);
    // For initial testing, use just containers
    InternalReturnKey(FALSE);
    
    // Get container of this last element
    pEnd = (CEditElement*)selection.m_end.m_pElement->FindContainer();
    if( !pEnd )
        return;    

    // Insert the table at beginning of original selection
    // This will do necessary splitting of containers
    //   and set insert point to first cell in table
    selection.m_end = selection.m_start = save_start;
    SetSelection(selection);
    CEditTableElement *pTable = InsertTable(pData);
    EDT_FreeTableData(pData);

    // Get container after the table just inserted
    pBegin = pTable->GetNextSibling();
    if( !pBegin )
        return;
    // DONT do this when coalescing containers such as lists
    pBegin = pBegin->FindContainer();
    if( !pBegin )
        return;    

    pCurrent = pBegin;

    // Put each container in a different cell
    XP_Bool bDone = FALSE;
    CEditTableCellElement *pFirstCell = NULL;
    CEditTableCellElement *pCell = NULL;
    CEditTableElement *pExistingTable = pCurrent->GetParentTable();

    while( !bDone )
    {
        // Search down element tree to find the end container,
        //  since we might be working at a higher level
        CEditElement *pContainer = pCurrent;
        if( pContainer )
            while (pContainer != pEnd && pContainer->GetChild() )
                pContainer = pContainer->GetChild();

        bDone = (pCurrent == NULL ) || (pContainer == pEnd);

        pNext = (CEditElement*)pCurrent->GetNextSibling();
        if( !pNext )
            pNext = (CEditElement*)pCurrent->NextContainer();

        // Move container into table cell,
        //  inserting it as first container in the cell
        pCell = m_pCurrent->GetParentTableCell();
        if( pCell )
        {
            // Save first cell to move caret when done
            if( !pFirstCell )
                pFirstCell = pCell;

            pCurrent->Unlink();

            // Insert the container and its children as the 1st child of current cell
            pCurrent->InsertAsFirstChild(pCell);

            // Set insert point inside first leaf element just moved,
            //   unless it was a table
            if( !pCurrent->IsTable() )
            {
                CEditElement *pFirstChild = pCurrent->GetFirstMostChild();
                CEditInsertPoint ip(pFirstChild, 0);
                SetInsertPoint(ip);

                // Delete the default container created with the table
                //    which contained the caret moved there by InsertTable()
                CEditElement *pDefaultContainer = pCurrent->GetNextSibling();
                if( pDefaultContainer )
                    delete pDefaultContainer;
            }
        } else {
            // Should never be here:
            XP_ASSERT(TRUE);
        }

        // Move to the next cell to insert next container, if it exists
        if( !bDone && !NextTableCell(TRUE) )
        {
            // We need to create a new row
            
            // Get current row
            CEditElement* pRow = pCell->GetParent();
            if( !pRow )
                break;

            CEditTableRowElement* pNewRow = new CEditTableRowElement(iColumns);
            if( !pNewRow )
                break;

            // Insert new row after current
            pNewRow->InsertAfter(pRow);
            // Initialize cell contents
            pNewRow->FinishedLoad( this );

            // Now move to the first cell in this new row
            if( !NextTableCell(TRUE) )
                break;
        }
        if( pNext == NULL ) // Should never happen?
            break;

        // Check if next element is inside a table
        //   abort if it isn't same as where starting element was
        CEditTableElement *pTable = pNext->GetParentTable();
        if( pTable )
        {
            // We probably want to put the entire table in next cell...
            pNext = (CEditElement*)pTable;

            // ..but first walk up nested tables to find appropriate level
            while( (pTable = pTable->GetParentTable()) != pExistingTable )
            {
                if( pTable )
                    pNext = (CEditElement*)pTable;
            }
        }
        pCurrent = pNext;
    }

    if( pFirstCell )
        SetTableInsertPoint(pFirstCell);

    SetFillNewCellWithSpace();

    // Finish initializing the remaining blank cells in the last row
    while( (pCell = (CEditTableCellElement*)pCell->GetNextSibling()) != NULL )
        pCell->FinishedLoad(this);

    ClearFillNewCellWithSpace();

    m_bNoRelayout = FALSE;

    Relayout( pBegin, 0, pEnd );

    EndBatchChanges();
}

// 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();
        }
    }
}

// Apply the style to selection or at caret. 
// Use bApplyStyle = FALSE to delete the saved style data
void CEditBuffer::PasteStyle(XP_Bool bApplyStyle)
{
    if( m_pCopyStyleCharacterData )
    {
        if( bApplyStyle )
        {
            SetCharacterData(m_pCopyStyleCharacterData);
        }
        else
        {
            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 );
    // FEs aren't using this now - maybe remove?
    // Or pass entire m_FontDefURL and m_FontDefNoSave arrays?
    // This was used by CEditSaveObject::AddAllFiles(),
    //  but now we access m_FontDefURL and m_FontDefNoSave directly,
    pData->pFontDefURL = 0;
    pData->bFontDefNoSave = FALSE;

    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 && 0 == 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 - not set by FEs yet
#if 0
// This is the old stuff
    if(m_pFontDefURL != NULL)  {
        XP_FREE(m_pFontDefURL);
        m_pFontDefURL = NULL;
    }
    if( pData->pFontDefURL ){  
        m_pFontDefURL = XP_STRDUP(pData->pFontDefURL);  
    }  
    m_bFontDefNoSave = pData->bFontDefNoSave;
#endif

    // 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::FindMetaData( EDT_MetaData *pMetaData )
{
    intn iCount = m_metaData.Size();
    for( intn i = 0; i < iCount; i++ )
    {
        if( (!!pMetaData->bHttpEquiv == !!m_metaData[i]->bHttpEquiv)
             && XP_STRCMP( m_metaData[i]->pName, pMetaData->pName) == 0 )
        {
            // No previous value, so we can stop at first name found
            if( pMetaData->pPrevContent == 0 )
                return i;
            
            // Check for previous value - match item only if
            //   old content string is found
            // This allows us to replace specific NAME/CONTENT items
            //   and allow > 1 items to have the same NAME
            char *pContent = m_metaData[i]->pContent;
            if( pContent &&
                ( pContent == pMetaData->pPrevContent ||
                 XP_STRCMP( pContent, pMetaData->pPrevContent) == 0 ) )
            {
                return i;
            }
        }
    }
    return -1;
}

intn CEditBuffer::FindContentTypeIndex()
{
    intn iIndex = 0;
    EDT_MetaData *pData = EDT_NewMetaData();
    if( pData )
    {
        pData->bHttpEquiv = TRUE;
        pData->pName = CONTENT_TYPE;
        iIndex = FindMetaData(pData);
        XP_FREE( pData );
   }
   return iIndex;
}

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 = FindMetaData( 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] );
            // Delete the entry from the list
            // (this now shuffles pointers so no gaps in list)
            m_metaData.Delete(i);
        }
    }
    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 = FindMetaData( pData );
    if( i != -1 ){
        FreeMetaData( m_metaData[i] );
        // Shuffle pointers to replace deleted item
        m_metaData.Delete(i);
    }
}

void CEditBuffer::FreeMetaData( EDT_MetaData *pData ){
    if ( pData )
    {
        if( pData->pName ) XP_FREE( pData->pName );
        if( pData->pContent )
        {
            // pPrevContent is set to pContent to allow multiple tags
            //   with same NAME but different CONTENT when parsing a doc)
            if( pData->pPrevContent && 
                pData->pPrevContent != pData->pContent )
            {
                XP_FREE( pData->pPrevContent );
            }
            XP_FREE( pData->pContent );
        }
        XP_FREE( pData );
    }
}

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

    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 );

        // Check our charset string for validity
        // (We may close done the buffer in this routine)
        if( CheckCharset(pTag, pData, win_csid) )
        {

            // We want to allow multiple entries with the same NAME,
            //  as long as CONTENT is different. So setting these the same
            //  will make FindMetaData() match CONTENT as well as NAME
            //  when deciding to replace and existing meta entry
            pData->pPrevContent = pData->pContent;
            SetMetaData( pData );
            FreeMetaData( pData );
        }
        else
        {
            // Signal to stop parsing
            // This alone seems to be enough to abort parsing,
            //  the net stream, and close the window!
            retVal = NOT_OK;
        }
    }

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

static void edt_ReplaceCharset(PA_Tag *pTag, EDT_MetaData *pData, char *pNewCharset)
{
 	char pContent[128];

    if( pTag && pData && pNewCharset && *pNewCharset )
    {
        // Replace existing Content-Type value
        XP_SPRINTF(pContent, "text/html; charset=%.100s", pNewCharset);
        XP_FREEIF( pData->pContent );
        pData->pContent = XP_STRDUP(pContent);
        
        // Example of a Content-Type meta tag:
        //<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">

        // Also replace the original tag contents
        char *pTagContent = NULL;
        pTagContent = PR_sprintf_append(pTagContent, "%s=\"%s\" CONTENT=\"%s\"", PARAM_HTTP_EQUIV, CONTENT_TYPE, pContent);
        
        if( pTagContent )
        {
            XP_FREEIF(pTag->data);
            pTag->data_len = XP_STRLEN(pTagContent);
            pTag->data = (PA_Block)pTagContent;
        }
    }
}

// Return FALSE only if we are closing down
XP_Bool CEditBuffer::CheckCharset( PA_Tag *pTag, EDT_MetaData *pData, int16 win_csid )
{
    XP_Bool bRetVal = TRUE;

    // If this is not 0, then we are reloading a page
    //   that we are trying to fix a bad charset
    if( m_iReplaceCSID )
    {
        char *pNewCharset = (char *)INTL_CsidToCharsetNamePt(m_iReplaceCSID);
        if( pNewCharset )
            edt_ReplaceCharset(pTag, pData, pNewCharset);

        m_iReplaceCSID = 0;
    }
    else if( pData && pData->pName && *pData->pName &&
             pData->pContent && *pData->pContent )
    {
        // Normal doc-loading path
        int16 default_csid = FE_DefaultDocCharSetID(m_pContext);
        
        if( CS_USRDEF2 != default_csid && 
            CS_USER_DEFINED_ENCODING != default_csid &&
            pData->bHttpEquiv &&
            0 == XP_STRCASECMP(pData->pName, CONTENT_TYPE) )
        {
            // Create a simple tag so we can use tag-parsing function to extract charset
            // PA_FetchParamValue (called from edt_FetchParamString)
            //  needs a terminal '>' to work

            int iLen = XP_STRLEN(pData->pContent);
            char *pContent = (char*)XP_ALLOC(iLen+1);
            if( !pContent )
                return FALSE; //Abort if not enough memory?
    
            XP_STRCPY(pContent, pData->pContent);
            pContent[iLen] = '>';
            iLen++;
            pContent[iLen] = '\0';

            PA_Tag *pTempTag = XP_NEW( PA_Tag );
            XP_BZERO( pTempTag, sizeof( PA_Tag ) );
            pTempTag->data_len = iLen;
            pTempTag->data = (PA_Block)pContent;
            char *pCharset = edt_FetchParamString(pTempTag, PARAM_CHARSET, win_csid );
            PA_FREE(pTempTag);

            if( pCharset )
            {
                int iBufLen = 255;
                char buf[256];
                char *pMsg = NULL;

                if(CS_UNKNOWN == INTL_CharSetNameToID(pCharset))  
                {
                    // Get the default charset
                    //INTL_CharSetIDToName(default_csid, &pDefaultCharset);
                    // Above uses presized buffer, but calls following, so this is safer:
                    // (Don't free this string!)
                    char *pDefaultCharset = (char *)INTL_CsidToCharsetNamePt(default_csid);
                    if( pDefaultCharset )
                    {
                        // Build a very wordy message box with the default and current 
                        //   charset strings inserted
	                    PR_snprintf(buf, iBufLen, XP_GetString(XP_EDT_CHARSET_LABEL), pCharset);
                        pMsg = PR_sprintf_append(pMsg, buf);
                        pMsg = PR_sprintf_append(pMsg, XP_GetString(XP_EDT_CHARSET_CANT_EDIT));
	                    PR_snprintf(buf, iBufLen, XP_GetString(XP_EDT_CURRENT_CHARSET), pDefaultCharset);
                        pMsg = PR_sprintf_append(pMsg, buf);
	                    PR_snprintf(buf, iBufLen, XP_GetString(XP_EDT_CHARSET_EDIT_REPLACE), pDefaultCharset);
                        pMsg = PR_sprintf_append(pMsg, buf);
                        pMsg = PR_sprintf_append(pMsg, XP_GetString(XP_EDT_CHARSET_EDIT_CANCEL));
                        // If user chooses "Cancel", then we should abort editing
                        bRetVal = FE_Confirm(m_pContext, pMsg);
                        if( bRetVal )
                        {
                            // Change to the default charset
                            edt_ReplaceCharset(pTag, pData, pDefaultCharset);
                        }
                    }
                    else
                        bRetVal = FALSE; //Abort if no default charset?
                }
                else
                {
                    char *pCorrectCharset = (char*)INTL_CharsetCorrection(pCharset);
                    if( pCorrectCharset && 0 != XP_STRCASECMP(pCorrectCharset, pCharset) )
                    {
                        // See if user wants to replace charset with the "correct" string
                        // In either case, we continue editing
	                    PR_snprintf(buf, iBufLen, XP_GetString(XP_EDT_CHARSET_LABEL), pCharset);
                        pMsg = PR_sprintf_append(pMsg, buf);
	                    PR_snprintf(buf, iBufLen, XP_GetString(XP_EDT_CHARSET_EDIT_SUGGESTED), pCorrectCharset);
                        pMsg = PR_sprintf_append(pMsg, buf);
	                    PR_snprintf(buf, iBufLen, XP_GetString(XP_EDT_CHARSET_EDIT_REPLACE), pCorrectCharset);
                        pMsg = PR_sprintf_append(pMsg, buf);
	                    PR_snprintf(buf, iBufLen, XP_GetString(XP_EDT_CHARSET_EDIT_NOREPLACE), pCharset);
                        pMsg = PR_sprintf_append(pMsg, buf);

                        if( FE_Confirm(m_pContext, pMsg) )
                        {
                            // Change to the "correct" charset
                            edt_ReplaceCharset(pTag, pData, pCorrectCharset);
                        }
                    }
                }
                XP_FREE(pCharset);
            }
        }
    }
    return bRetVal;
}



// 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());
    CEditInsertPoint start(m_pCurrent, m_iCurrentOffset);
	CEditLeafElement* pRight = NULL;
    
#if 0
    //TODO: Something like this
    // Don't do InternalReturnKey if at beginning of empty container?
    CEditElement* pParent = pElement->GetParent();
    if( pParent && pParent->IsContainer() && 
        pElement->IsText() &&
        pElement->GetNextSibling() == NULL )
    {
        char* pText = pElement->Text()->GetText();
    } else {
        InternalReturnKey(FALSE);
    }
#else
    InternalReturnKey(FALSE);
#endif

    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;
    }
}

// NOTE: Caller should manage BeginBatchChanges/EndBatchChanges
void CEditBuffer::SetTableData(EDT_TableData *pData)
{
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
    if ( pTable )
    {
        XP_Bool bChangeWidth = FALSE;
        XP_Bool bChangeHeight = FALSE; 
        int32 iMaxWidth = 0;
        int32 iMaxHeight;                
        intn iCurrentCols = pTable->GetColumns();
        intn iCurrentRows = pTable->GetRows();
        CEditTableCellElement *pCell = NULL;
        XP_Bool bMovedToLastCell = FALSE;
        XP_Bool bTableDeleted = FALSE;

        // Be sure we have at least 1 row and column
        pData->iColumns = max(1, pData->iColumns);
        pData->iRows = max(1, pData->iRows);

        // Add new rows or columns as needed
        // Note: InsertTableColumns and InsertTableRows will call Relayout() 
        if( pData->iColumns > iCurrentCols )
        {
            // Append new columns to end of table
            // Get first cell in last column
            pCell = pTable->GetFirstCellAtColumnIndex(iCurrentCols-1);
            if( pCell )
            {
                SetTableInsertPoint(pCell);
                InsertTableColumns(NULL, TRUE, pData->iColumns - iCurrentCols);
                bMovedToLastCell = TRUE;
            }
        } 
        else if( pData->iColumns < iCurrentCols )
        {
            // Delete columns from right side of table
            // TODO: PUT UP FE_CONFIRM DIALOG TO ASK USER
            //       IF IT IS OK TO DELETE WHEN REDUCING SIZE?
            // (or maybe let each front end do that?)
            
            // Move to first cell in new last column
            pCell = pTable->GetFirstCellAtColumnIndex(pData->iColumns-1);
            if( pCell )
            {
                // Delete commands assume caret is in the delete column
                SetTableInsertPoint(pCell);

                // This will relayout the table and move caret appropriately
        		AdoptAndDo(new CDeleteTableColumnCommand(this, iCurrentCols - pData->iColumns, &bTableDeleted));

                // Don't try to go back to original insert point,
                //  it may have been deleted
                bMovedToLastCell = FALSE;
            } else {
                // TODO: PUT UP AN FE_ALERT MESSAGE TO TELL USER WHY 
                //        THEY COULDN'T REDUCE COLUMNS?
                pData->iColumns = iCurrentCols;
                XP_TRACE(("CEditBuffer::SetTableData: Failed to delete columns to reduce table size"));
            }
        }
        if( bTableDeleted )
            return;

        if( pData->iRows > iCurrentRows )
        {
            // Move to first cell in last row of table
            pCell = pTable->GetFirstCellAtRowIndex(iCurrentRows-1);
            if( pCell )
            {
                SetTableInsertPoint(pCell);
                // Append new rows to end of table
                InsertTableRows(NULL, TRUE, pData->iRows - iCurrentRows);
                bMovedToLastCell = TRUE;
            }
        }
        else if( pData->iRows < iCurrentRows )
        {
            // Delete rows from bottom of table
            // Move to first cell in new last row
            pCell = pTable->GetFirstCellAtRowIndex(pData->iRows-1);
            if( pCell )
            {
                // Delete commands assume caret is in the delete row
                SetTableInsertPoint(pCell);

                // This will relayout the table and move caret appropriately
        		AdoptAndDo(new CDeleteTableRowCommand(this, iCurrentRows - pData->iRows, &bTableDeleted));

                // Don't try to go back to original insert point,
                //  it may have been deleted
                bMovedToLastCell = FALSE;
            } else {
                // TODO: PUT UP AN FE_ALERT MESSAGE TO TELL USER WHY 
                //        THEY COULDN'T REDUCE ROWS?
                pData->iRows = iCurrentRows;
                XP_TRACE(("CEditBuffer::SetTableData: Failed to delete rows to reduce table size"));
            }
        }

        // Move back to where we were if we expanded size
        if( bMovedToLastCell )
            SetInsertPoint(ip);

        // Calculate the Pixel values - FEs are only required to set iWidth and iHeight
        // Determine if we are changing the table width or height
        if( pData->bWidthDefined )
        {
            if( pData->bWidthPercent )
            {
                pTable->GetParentSize(m_pContext, &iMaxWidth, &iMaxHeight);
                pData->iWidthPixels = (pData->iWidth * iMaxWidth) / 100;
            }
            else
                pData->iWidthPixels = pData->iWidth;

            bChangeWidth = (pData->iWidthPixels != pTable->GetWidth());
        }
        if( pData->bHeightDefined )
        {
            if( pData->bHeightPercent )
            {
                if( iMaxWidth == 0 )
                    pTable->GetParentSize(m_pContext, &iMaxWidth, &iMaxHeight);
                pData->iHeightPixels = (pData->iHeight * iMaxHeight) / 100;
            }
            else
                pData->iHeightPixels = pData->iHeight;

            bChangeHeight = (pData->iHeightPixels != pTable->GetHeight());
        }

        pTable->SetData( pData );

        if( bChangeWidth || bChangeHeight )
        {
            // This will change cell data to allow table resize to work correctly,
            //  then restore previous settings
            ResizeTable( pTable, bChangeWidth, bChangeHeight );
        } else {
            // Normal relayout is OK if not resizing
            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();
    }
}

CEditTableElement* CEditBuffer::InsertTable(EDT_TableData *pData){
    CEditTableElement *pTable = new CEditTableElement(pData->iColumns, pData->iRows);
    if( pTable ){
        pTable->SetData( pData );
        SetFillNewCellWithSpace();
        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);
        ClearFillNewCellWithSpace();
        return pTable;
    }
    return NULL;
}

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 ){
        pTableCell = m_pCurrent ? m_pCurrent->GetTableCellIgnoreSubdoc() : 0;
    }
    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++ )
        {
            // The "next" editor cell is always at index 1 because
            //  we delete each "cell 1" and unselect it 
            //  (i.e., delete pointer from m_SelectedEdCells) as we merge.
            //  Note that corresponding LO_CellStruct was NOT deleted,
            //   but its pointer is deleted from m_pSelectedLoCells list.
            CEditTableCellElement *pNextCell = m_SelectedEdCells[1];
            // We must add all COLSPANs of all cells to be merged
            pNextCellData = pNextCell->GetData();
            if( pNextCellData )
            {
                // If y value is same, next cell is in same row, so do ColSpan
                if( y == m_SelectedLoCells[1]->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[1]->x )
                {
                    pFirstCellData->iRowSpan += pNextCellData->iRowSpan;
                }
                EDT_FreeTableCellData(pNextCellData);
            }
            // Move all contents of Next cell into First cell and delete Next cell
            pFirstCell->MergeCells(pNextCell, pFirstCellData->iRowSpan);
        }
    } 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, pFirstCellData->iRowSpan);
    }
    // Set the COLSPAN and ROWSPAN 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);
    
    // Note: Only pFirstCell should now be selected
    
    // Relayout the entire table
    Relayout(pFirstCell->GetParentTable(), 0);
    EndBatchChanges();
}

/* Separate paragraphs into separate cells,
 * removing COLSPAN or ROWSPAN
*/
void CEditBuffer::SplitTableCell()
{
    // Set beginning of UNDO block
    BeginBatchChanges(kGroupOfChangesCommandID);

    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
    CEditTableCellElement* pCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if( pTable == NULL || pCell == NULL )
        return;
    pCell->SplitCell();
    Relayout(pTable, 0, pTable);

    EndBatchChanges();
}


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 different 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.
// Caller should manage UNDO buffer
//        BeginBatchChanges(kGroupOfChangesCommandID/*kInsertTableRowCommandID*/);
        ClearTableAndCellSelection();
        SetFillNewCellWithSpace();
        AdoptAndDo(new CInsertTableRowCommand(this, pData, bAfterCurrentRow, number));
        ClearFillNewCellWithSpace();
//        EndBatchChanges();
    }
}

void CEditBuffer::DeleteTableRows(intn number){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if ( pTableCell ){
        BeginBatchChanges(kGroupOfChangesCommandID);
        // If we have entire rows selected, use that for number to delete
        if( number <= 0 )
        {
            intn iSelected = GetNumberOfSelectedRows();
            number = max( 1, iSelected);
            // If deleting selected rows -- move to the first selected cell
            if( iSelected > 0 )
                SetTableInsertPoint(m_SelectedEdCells[0], TRUE);
        }
        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;
}

// For cell selection, return:
//    ED_HIT_SEL_ROW if >= 1 complete row is selected (including all cells in table)
//    ED_HIT_SEL_COL if >= 1 complete column is selected,
//    else ED_HIT_SEL_CELL if any cell is selected.
ED_HitType CEditBuffer::GetTableSelectionType()
{
    if( m_pSelectedEdTable )
        return ED_HIT_SEL_TABLE;

    intn iSelectedCount = m_SelectedLoCells.Size();
    
    if( iSelectedCount == 0 )
        return ED_HIT_NONE;
    
    if( iSelectedCount == 1 )
        return ED_HIT_SEL_CELL;

    // Count number of cells in table to quickly determine if
    //  ALL cells in table are selected
    if( iSelectedCount == lo_GetNumberOfCellsInTable(lo_GetParentTable(m_pContext, (LO_Element*)m_SelectedLoCells[0])) )
    {
        CEditTableElement *pTable = m_SelectedEdCells[0]->GetTable();
        if( pTable && pTable->GetRows() > pTable->GetColumns() )
            return ED_HIT_SEL_COL;

        return ED_HIT_SEL_ROW; // ED_HIT_SEL_ALL_CELLS???
    }

    // Save the unique X and Y values we encounter,
    //   they define columns and rows
    // This avoids duplicate checks for each cell in row or col
    TXP_GrowableArray_int32  UniqueX;
    TXP_GrowableArray_int32  UniqueY;

    ED_HitType iNewSelType = ED_HIT_NONE;
    XP_Bool bColSelected = FALSE;
    XP_Bool bRowSelected = FALSE;

    for( intn i = 0; i < iSelectedCount; i++ )
    {
        XP_Bool bCheckCellInCol = TRUE;
        XP_Bool bCheckCellInRow = TRUE;
        
        intn j;
        // Check if we already did this column
        for( j = 0; j < UniqueX.Size(); j++ )
        {
            if( m_SelectedLoCells[i]->x == UniqueX[j] )
            {
                bCheckCellInCol = FALSE;
                break;
            }
        }
        if( !bCheckCellInCol )
            UniqueX.Add(m_SelectedLoCells[i]->x);

        // Check if we already did this row
        for( j = 0; j < UniqueY.Size(); j++ )
        {
            if( m_SelectedLoCells[i]->y == UniqueY[j] )
            {
                bCheckCellInRow = FALSE;
                break;
            }
        }
        if( !bCheckCellInRow )
            UniqueY.Add(m_SelectedLoCells[i]->y);

        if( (bCheckCellInCol || bCheckCellInRow) && iNewSelType == ED_HIT_NONE )
        {
            if( lo_AllCellsSelectedInColumnOrRow( m_SelectedLoCells[i], FALSE ) ) // Row
            {
                if( bColSelected )
                {
                    // Can't have both row and columns selected
                    iNewSelType = ED_HIT_SEL_CELL;
                    break;
                }
                else
                    bRowSelected = TRUE;
            } 
            else if( lo_AllCellsSelectedInColumnOrRow( m_SelectedLoCells[i], TRUE ) ) // Column
            {
                if( bRowSelected )
                {
                    // Can't have both row and columns selected
                    iNewSelType = ED_HIT_SEL_CELL;
                    break;
                }
                else
                    bColSelected = TRUE;
            } 
            else 
            {
                // Neither row or column is fully selected
                iNewSelType = ED_HIT_SEL_CELL;
                break;
            }
        }
    }
    if( iNewSelType == ED_HIT_NONE )
    {
        // We made it through all cells,
        //  so one of these must be TRUE
        if( bRowSelected )
            iNewSelType = ED_HIT_SEL_ROW;
        else if( bColSelected )
            iNewSelType = ED_HIT_SEL_COL;
    }
    return iNewSelType;
}

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 )
        {
            // This figures out if selection comprises just rows or columns
            pData->iSelectionType = GetTableSelectionType();;

            // 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);
                }
            }

            // 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);
        }
        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;
        }
    }
}

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->iWidthPixels = pSourceData->iWidthPixels;
            pDestData->bWidthPercent = pSourceData->bWidthPercent;
        }

        if( pSourceData->mask & CF_HEIGHT )
        {
            pDestData->bHeightDefined = pSourceData->bHeightDefined;
            pDestData->iHeight = pSourceData->iHeight;
            pDestData->iHeightPixels = pSourceData->iHeightPixels;
            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 && iMoveType == ED_MOVE_NONE &&
        (pData == NULL || (pData && iHitType != pData->iSelectionType)) )
    {
        // This is relatively simple as long as we can trust that the
        //   caret is in the "focus cell" 
        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;

            default:
                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;

            default:
                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);
        }

        int iSelectedCount = m_SelectedEdCells.Size();
        if( iSelectedCount > 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...
            if( !pCell->IsSelected() )
            {
                SelectTableElement( pCell, ED_HIT_SEL_CELL );
                // ... but retain requested selection type
                m_TableHitType = iHitType;
            }

            // Change cell data to reflect single cell selection...
            if( pData )
            {
                pData->iSelectionType = iHitType; // ...but use requested type
                pData->iSelectedCount = 1;
            }
        }

        if( pData )
        {
            // Fill supplied struct with data for next cell
            EDT_TableCellData *pNewData = pCell->GetData();
            if( pNewData )
            {
                // Set initial mask to that of the new focus cell
                pData->mask = pNewData->mask;
                // Set all bits in mask so all values are copied
                // This does not change pData->mask
                pNewData->mask = -1;
                edt_CopyTableCellData(pData, pNewData);
                EDT_FreeTableCellData(pNewData);

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

                    // Override ColSpan and RowSpan mask bit
                    //  to NOT allow changing it when > 1 cell is selected
                    pData->mask &= ~(CF_COLSPAN | CF_ROWSPAN);
                }
            }
        }
    }
}

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;
    
    // If we are changing column width or row height,
    //  its best to set it for all cells within that column or row.
    // While this fattens document, it confers better table stability
    //   when cells, columns, or rows are moved around because each cell knows its size
    // Note: If pData->bWidthDefined or pData->bHeightDefined is changed to FALSE, 
    //    then the that param will be cleared for entire column or row
    if( (pData->mask & CF_WIDTH) && 
        ( (!pData->bWidthDefined && pCurrentData->bWidthDefined ) ||
          (pData->bWidthDefined && pData->iWidthPixels !=  pCurrentData->iWidthPixels) ) )
    {
        pCell->SetColumnWidthLeft(pCell->GetParentTable(), pCell, pData); 
    }

    if( (pData->mask & CF_HEIGHT) &&
        ( (!pData->bHeightDefined && pCurrentData->bHeightDefined) ||
          (pData->bHeightDefined && pData->iHeightPixels !=  pCurrentData->iHeightPixels) ) )
    {
        pCell->SetRowHeightTop(pCell->GetParentTable(), pCell, pData); 
    }

    // Copy new data to current cell 
    // Note: Never do this BEFORE SetColumnWidthLeft or SetRowHeightTop since
    //       they use pCell's current width to determine how much to resize
    edt_CopyTableCellData( pCurrentData, pData );

    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;    

    CEditTableElement *pTable = pCell->GetTable();
    if( !pTable )
        return;

    // FEs are only required to set the pData->iWidth or pData->iHeight params
    //  so we need to calculate iWidthPixels and iHeightPixels from that
    pCell->CalcPixelWidth(pData);
    pCell->CalcPixelHeight(pData);
    
    BeginBatchChanges(kGroupOfChangesCommandID);

    XP_Bool bCurrentCellFound = FALSE;
    XP_Bool bChangeWidth = FALSE;
    XP_Bool bChangeHeight = FALSE;

    intn iCellCount = m_SelectedEdCells.Size();
    if( iCellCount > 1 )
    {
        // Set data for all selected cells
        for( intn i = 0; i < iCellCount; i++ )
        {
            if( pData->bWidthDefined && m_SelectedEdCells[i]->GetWidth() != pData->iWidthPixels )
                bChangeWidth = TRUE;

            if( pData->bHeightDefined && m_SelectedEdCells[i]->GetHeight() != pData->iHeightPixels )
                bChangeHeight = TRUE;

            edt_SetTableCellData( m_SelectedEdCells[i], pData );
            if( m_SelectedEdCells[i] == pCell )
                bCurrentCellFound = TRUE;
        }
        // Current cell should always be in selected cell set,
        //  so check for that
        XP_ASSERT( bCurrentCellFound );
    } 
    else {
        if( pData->bWidthDefined && pCell->GetWidth() != pData->iWidthPixels )
            bChangeWidth = TRUE;

        if( pData->bHeightDefined && pCell->GetHeight() != pData->iHeightPixels )
            bChangeHeight = TRUE;

        // Set data for just current cell
        edt_SetTableCellData( pCell, pData );
    }

    // We must use the special resize/relayout method if changing size
    if( bChangeWidth || bChangeHeight )
        ResizeTableCell( pTable, bChangeWidth, bChangeHeight );
    else
        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.
        // Note: Caller must do BeginBatchChanges()
        ClearTableAndCellSelection();
        SetFillNewCellWithSpace();
        AdoptAndDo(new CInsertTableColumnCommand(this, pData, bAfterCurrentCell, number));
        ClearFillNewCellWithSpace();
    }
}

void CEditBuffer::DeleteTableColumns(intn number){
    VALIDATE_TREE(this);
    CEditInsertPoint ip;
    GetTableInsertPoint(ip);
    CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
    if ( pTableCell ){
        BeginBatchChanges(kGroupOfChangesCommandID);
        // If we have entire columns selected, use that for number to delete
        if( number <= 0 )
        {
            intn iSelected = GetNumberOfSelectedColumns();
            number = max(1, iSelected);
            // Deleting selected columns - move to the first selected cell
            if( iSelected > 0 )
                SetTableInsertPoint(m_SelectedEdCells[0], TRUE);
        }

        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);
        SetFillNewCellWithSpace();
		AdoptAndDo(new CInsertTableCellCommand(this, bAfterCurrentCell, number));
        ClearFillNewCellWithSpace();
        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;
    GetPropertyPoint( &pInsertPoint, &iOffset );
    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 )
    {
        // Get old string so we can search and replace links to changed target name
        char *pOldName = m_pCurrent->Target()->GetName();
        XP_Bool bFixupLinks = FALSE;
        if( pOldName && *pOldName )
            bFixupLinks = linkManager.FixupLinksToTarget(pOldName, pData);
        XP_FREEIF(pOldName);

        m_pCurrent->Target()->SetName( pData, GetRAMCharSetID() );

        // Easiest (laziest) way - relayout everything if link to target was found
        if( bFixupLinks ) 
            Relayout(m_pRoot, 0, m_pRoot->GetLastMostChild(), RELAYOUT_NOCARET);
        else
            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)
{
    // Don't return a list for the file we are editing
    char *pCurrentURL = LO_GetBaseURL(m_pContext);
    if( EDT_IsSameURL(pHref,pCurrentURL, pCurrentURL,NULL) )
        return NULL;

    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
// cmanske: 10/6/98 Tested the XP code and it seems to work fine in Windows

    // 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( pCurrentURL, pHref );

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

        XP_FREEIF(pAbsolute);

    }
    if (!pFilename) {
      return NULL;
    }

    // Open local file
    XP_File file = XP_FileOpen( pFilename, xpURL, XP_FILE_READ );
    if( !file ) {
        XP_FREEIF(pFilename);
        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);
    XP_FREEIF(pFilename);

    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;
    }

    int iFontDefCount = m_FontDefURL.Size();
    if( iFontDefCount)
    {
        for( int i = 0; i < iFontDefCount; i++ )
            AddToBufferUnique(buf, selected, pDocURL, m_FontDefURL[i],
                              bKeepImagesWithDoc && !m_FontDefNoSave[i]);
    }

    // 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 )
        {
            if( EDT_IsImageURL(pData->pSrc) )
            {
              AddToBufferUnique(buf,selected,pDocURL,pData->pSrc,
                    bKeepImagesWithDoc && !pData->bNoSave);
            }

            if( EDT_IsImageURL(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;
        CEditElement *pFirst = pBegin->FindContainer();
        if( pBegin )
        {
            pBegin->FindList( pContainer, pList);
            if( pList ) pFirst = pList;
        }
        if( pEnd ){
            pEnd->FindList( pContainer, pList );
            if( pList ) pLast = pList->GetLastMostChild();
        }

        Relayout( pFirst, 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, XP_Bool bCheckForCellBoundary){
    CEditSelection selection;
    GetSelection(selection);
    return CanCut(selection, bStrictChecking, bCheckForCellBoundary);
}

EDT_ClipboardResult CEditBuffer::CanCut(CEditSelection& selection, XP_Bool bStrictChecking, XP_Bool bCheckForCellBoundary)
{
    if( IsTableOrCellSelected() )
        return EDT_COP_OK;

    EDT_ClipboardResult result = selection.IsInsertPoint() ? EDT_COP_SELECTION_EMPTY : EDT_COP_OK;
    if ( bStrictChecking && result == EDT_COP_OK
        && selection.CrossesSubDocBoundary() )
    {
        //Note: We return this only for calls internal to CEditBuffer,
        //      so it knows when to do special cell boundary copying or deleting
        if( bCheckForCellBoundary )
            return EDT_COP_SELECTION_CROSSES_TABLE_DATA_CELL;

        // Get table(s) at start and end of the selection
        CEditTableElement *pTableAtStart = selection.m_start.m_pElement->GetTableIgnoreSubdoc();
        CEditTableElement *pTableAtEnd = selection.m_end.m_pElement->GetTableIgnoreSubdoc();

        // Must have one or the other end within a table
        XP_ASSERT(pTableAtStart || pTableAtEnd);
        // If both are null (SHOULDN'T HAPPEN), or both ends are within same table, we're OK
        if( pTableAtStart == pTableAtEnd )
        {
            result = EDT_COP_OK;
        }
        else if( (pTableAtStart && pTableAtStart->GetParent()->GetTableIgnoreSubdoc()) ||
                 (pTableAtEnd && pTableAtEnd->GetParent()->GetTableIgnoreSubdoc()) )
        {
            // We still can't copy or delete if we go into a nested table
            result = EDT_COP_SELECTION_CROSSES_NESTED_TABLE;
        }
    }
    return result;
}

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

EDT_ClipboardResult CEditBuffer::CanCopy(CEditSelection& selection, XP_Bool bStrictChecking, XP_Bool bCheckForCellBoundary){
    return CanCut(selection, bStrictChecking, bCheckForCellBoundary); /* 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=0;
    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;
}

// Prevent reentrant calling of FE_SetDocPostion
static XP_Bool edt_bSettingDocPosition = FALSE;

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);
        }
		// Prevent reentrant calling of FE_SetDocPostion
        // Fix for bug 93341. Stack overflow crash because of infinite loop
        // (only in Win95 - weird!)
        if( !edt_bSettingDocPosition ){
			edt_bSettingDocPosition = TRUE;
			FE_SetDocPosition(m_pContext, FE_VIEW, topX, topY);
    		edt_bSettingDocPosition = FALSE;
        }
        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();
        // See we are inside a caption
        XP_Bool bInCaption = FALSE;
        CEditElement *pParent = pElement->GetParent();
        while( pParent && pParent != m_pRoot )
        {
            if( pParent->IsCaption() )
            {
                bInCaption = TRUE;
                break;
            }
            pParent = pParent->GetParent();
        }
        if( alignment == ED_ALIGN_DEFAULT )
        {
            alignment = bInCaption ? ED_ALIGN_ABSCENTER : ED_ALIGN_LEFT;
        }
        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 contentTypeIndex = FindContentTypeIndex();
    if ( contentTypeIndex >= 0 ) {
        PrintMetaData(pPrintState, contentTypeIndex);
    }
    for( int i = 0; i < m_metaData.Size(); i++ ){
        if ( i != contentTypeIndex ) {
            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.
        //
        if( pData->pContent ){
            pPrintState->m_pOut->Printf( "   <META %s=%s ",
                    (pData->bHttpEquiv ? "HTTP-EQUIV" : "NAME"),
                    edt_MakeParamString( pData->pName ) );
    
            // This changes internal quotes into &quot, which breaks some metatags
            //  that use quotes around URLs, such as NetWatch (bug 111054)
            //  edt_MakeParamString( pData->pContent ) );
            // TODO: INVESTIGATE IF WE SHOULD NOT TRANSLATE " INTO &QUOT IN edt_MakeParamString

            // Copy to new buff to append quotes before and after 
            int iLen = XP_STRLEN(pData->pContent) + 2; // 2 extra for added quotes
            char *pBuf = edt_WorkBuf( iLen+1 );        // Extra for terminal \0
            if( pBuf ){
                pBuf[0] = '"';
                XP_STRCPY((pBuf+1), pData->pContent);
                pBuf[iLen-1] = '"';
                pBuf[iLen] = '\0';
                pPrintState->m_pOut->Printf( "CONTENT=%s>\n", pBuf );
            }
        }
    }
}

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

    XP_Bool bHaveBaseTarget = m_pBaseTarget && *m_pBaseTarget;
    int     iFontDefCount = m_FontDefURL.Size();
    CParseState* pParseState = GetParseState();
    XP_Bool bHaveHeadTags = pParseState->m_pJavaScript != NULL;
    XP_Bool bNeedHead = bPageComposer
        || bHaveBaseTarget || (iFontDefCount > 0)
        || bHaveHeadTags;
    
    // Unfortunately, we don't conform to any standard DOCTYPE yet.
    pPrintState->m_pOut->Printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//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 );

            // Bug 105787: Print the TITLE tag even if it is not empty (HTML 4.0 requires it)
            pPrintState->m_pOut->Printf("   <TITLE>");
            if ( m_pTitle && *m_pTitle )
                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 refs if nescessary
        for( int i = 0; i < iFontDefCount; i++ )
        {
            pPrintState->m_pOut->Printf("   <LINK REL=FONTDEF SRC=%s",
                                        edt_MakeParamString( m_FontDefURL[i] ));
            if ( m_FontDefNoSave[i] )
                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.
    SetEncodingTag(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, // This includes the filename
                           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;
    }

#if defined(SingleSignon)
    // Prompt to remember the username and password for this location
    // It doesn't matter if its bad, because it will be saved again
    //   after getting correct values from the user via the username/password dialog
    // Strip filename off of the destination
    char *pLocation = EDT_ReplaceFilename(pDestURL, NULL, TRUE);
    if( pLocation )
    {
        SI_RememberSignonDataFromBrowser(m_pContext, pLocation, pUsername, pPassword);
        XP_FREE(pLocation);
    }
#endif 

    // 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) {
#ifdef ENDER
        XP_Bool bEmbedded   = m_bEmbedded;
        void *pEmbeddedData = m_pEmbeddedData;
        MWContext *pContext = m_pContext;
#endif /* ENDER */
        delete m_pRoot;
        m_pRoot = 0;
        m_pCurrent = 0;
        delete this; // See ya!

        // XP_STRCPY(buffer, XP_GetString(XP_HTML_MISSING_REPLYDATA));
        int32 length = 0;
        if (pBuffer)
            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);

#ifdef ENDER
        if (bEmbedded)
            EDT_SetEmbeddedEditorData(pContext, pEmbeddedData);
#endif /* ENDER */
    }
    NET_FreeURLStruct(url_s);
}

#ifdef MOZ_ENDER_MIME
void CEditBuffer::ReadMimeFromBuffer(XP_HUGE_CHAR_PTR pBuffer){
//we assume this is a mime multipart/related stream.
    if (!pBuffer)
    {
        XP_ASSERT(FALSE);
        return;
    }
    SimpleMultipartRelatedMimeDecoder t_decoder(pBuffer,XP_STRLEN(pBuffer));
    t_decoder.setFilePrefix("nswebm");
    if (!t_decoder.begin())
    {
        XP_ASSERT(FALSE); //debug only. did you get a bad message? or did the decoder screw up. let mjudge know!!!
    }
    if (t_decoder.getNumberOfParts() == 0)
    {
        pBuffer = ::ReadBufferFromFile(t_decoder.getHeaderFileName());
        //destructor of decoder will destroy file
    }
    else if (t_decoder.getNumberOfParts() == 1) 
    {
        /*check to make sure it is text/html then use it to feed to read from buffer*/
        SimpleMultiPart *t_part = t_decoder.getPart(0);
        if (t_part)
        {
            if (t_part->getType() == SimpleMultiPart::TEXTHTML)
                pBuffer = ::ReadBufferFromFile(t_part->getFileName());
            else //error
                return;
            XP_FileRemove(t_part->getFileName(),xpFileToPost);
        }
    }
    else
    {
        if (::DecodeSimpleMime(t_decoder,"nswebm")) //this just rewrote all those files as true binary files and renamed them
        {
            SimpleMultiPart *t_part = t_decoder.getPart(0);
            if (t_part)
            {
                if (t_part->getType() == SimpleMultiPart::TEXTHTML)
                    pBuffer = ::ReadBufferFromFile(t_part->getFileName()); //read from file, return char *
                else //error
                    return;
                XP_FileRemove(t_part->getFileName(),xpFileToPost);
                
                //returns pointer to new buffer with parsed out <IMG SRC=cid...>
                pBuffer = ::ParseBuffer(pBuffer,t_decoder); 
                if (pBuffer)
                {
                    for (int i=1; i < t_decoder.getNumberOfParts(); i++)
                    {
                        t_part = t_decoder.getPart(i);
                        if (t_part)
                        {
                            EDT_AddTmpFileURLToSafeList(m_pEmbeddedData, 
                                             t_part->getUrlFileName(),
                                             t_part->getFileName());
                        }
                    }
                }
            }
            
        }
    }
    ReadFromBuffer(pBuffer);
}
#endif /*MOZ_ENDER_MIME*/


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::Protect( CEditElement *pRoot  )
{
    // work backwards so when children go away, we don't have
    //  lossage.
    CEditElement *pChild, *pNext;
    pChild = pRoot->GetChild();
	if (!pChild) //maybe we need to protect this?
	{
		//if isContainer and parent and parent->islist protect!!
		if (pRoot->IsContainer() && pRoot->GetParent() && pRoot->GetParent()->IsList())
		{
			//add new text element
			CEditTextElement *t_ele=new CEditTextElement(pRoot, NULL);
		}
	}
    while( pChild ) 
	{
        pNext = pChild->GetNextSibling();
        Protect( pChild );
        pChild = pNext;
    }
}



//
//
//
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;

#ifdef ENDER

    XP_Bool isEmbedded = FALSE;
    if (m_pImportedHTMLStream && XP_STRLEN(m_pImportedHTMLStream))
	{
        char *t_ptr=m_pImportedHTMLStream;
        m_pImportedHTMLStream = NULL;
#ifndef MOZ_ENDER_MIME
		ReadFromBuffer(t_ptr);
#else
		ReadMimeFromBuffer(t_ptr);
#endif //MOZ_ENDER_MIME
        return;
	}
#endif //ENDER

    // protect empty SOME empty lines! //i.e. list items with nothing in their containers
	Protect( m_pRoot );
    // 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();

    // Be sure a doc that thinks its "new" has the correct URL
    // If user loads a URL into a "new doc" buffer that is unedited,
    //  we reuse that buffer and context, so we must clear the new-doc flag here
    // (this used to be done in NET_GetURL, mkgeturl.c, but that had bad side effects)
    char *pBaseURL = LO_GetBaseURL(m_pContext);
    if( pBaseURL && EDT_IS_NEW_DOCUMENT(m_pContext) && 
        0 != XP_STRCMP(pBaseURL, XP_GetString(XP_EDIT_NEW_DOC_NAME)) )
    {
        m_pContext->is_new_document = FALSE;
    }

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

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

#if ENDER
    // Embedded htmlareas shouldn't show a caret to start with,
    // but FE_SetNewDocumentProperties turned the cursor on six times!
    if (isEmbedded)
    {
        FE_DestroyCaret(m_pContext);
    }
#endif //ENDER

    // 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();
    
    // Flag set during buffer creation to tell 
    //  if we imported a text file
    if( m_bImportText )
        ConvertCurrentDocToNewDoc();
    if ( bShouldSendOpenEvent ) {
		History_entry * hist_entry = SHIST_GetCurrent(&(m_pContext->hist));
        char* pURL = hist_entry ? hist_entry->address : 0;
        // DANGER! DANGER! In mail composer, the current edit buffer may be deleted
        //   during this process, so DON'T ACCESS ANY "this" MEMBERS AFTER THIS
        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::ConvertCurrentDocToNewDoc()
{
    char * pUntitled = XP_GetString(XP_EDIT_NEW_DOC_NAME);

    // Traverse all links and images and change URLs to absolute
    //   since we will be destroying our current base doc URL
    EDT_ImageData *pImageData;
    char *pAbsolute = NULL;
    EDT_PageData *pPageData = GetPageData();
    if( !pPageData){
        return;
    }
    
    // Should be the same as pEntry->Address???
    char *pBaseURL = LO_GetBaseURL(m_pContext);

    // Walk the tree and find all HREFs.
    CEditElement *pLeaf = m_pRoot->FindNextElement( 
                                  &CEditElement::FindLeafAll,0 );
    // First sweep, mark all HREFs as not adjusted.
    while (pLeaf) {
        linkManager.SetAdjusted(pLeaf->Leaf()->GetHREF(),FALSE);
        pLeaf = pLeaf->FindNextElement(&CEditElement::FindLeafAll,0 );
    }
    // Second sweep, actually adjust the HREFs.
    pLeaf = m_pRoot->FindNextElement( 
            &CEditElement::FindLeafAll,0 );
    while (pLeaf) {
        ED_LinkId linkId = pLeaf->Leaf()->GetHREF();
        if (linkId && !linkManager.GetAdjusted(linkId)) {
            linkManager.AdjustLink(linkId, pBaseURL, (char*)NULL, (ED_HREFList *)NULL);
            linkManager.SetAdjusted(linkId,TRUE);
        }
        pLeaf = pLeaf->FindNextElement(&CEditElement::FindLeafAll,0 );
    }

    // Regular images.
    CEditElement *pImage = m_pRoot->FindNextElement( 
                                   &CEditElement::FindImage, 0 );
    while( pImage ){
        pImageData = pImage->Image()->GetImageData();
        if( pImageData ){
            if( pImageData->pSrc && *pImageData->pSrc ){
                char * pOld = XP_STRDUP(pImageData->pSrc);
                pAbsolute = NET_MakeAbsoluteURL( pBaseURL, pImageData->pSrc );
                if( pAbsolute ){
                    XP_FREE(pImageData->pSrc);
                    pImageData->pSrc = pAbsolute;
                }
             }
             if( pImageData->pLowSrc && *pImageData->pLowSrc){
                pAbsolute = NET_MakeAbsoluteURL( pBaseURL, pImageData->pLowSrc );
                if( pAbsolute ){
                    XP_FREE(pImageData->pLowSrc);
                    pImageData->pLowSrc = pAbsolute;
                }
            }    
            pImage->Image()->SetImageData( pImageData );
            edt_FreeImageData( pImageData );
        }
        pImage = pImage->FindNextElement( &CEditElement::FindImage, 0 );
    }

    // If there is a background Image, make it absolute also
    if( pPageData->pBackgroundImage && *pPageData->pBackgroundImage){
        pAbsolute = NET_MakeAbsoluteURL( pBaseURL, pPageData->pBackgroundImage );
        if( pAbsolute ){
            XP_FREE(pPageData->pBackgroundImage);
            pPageData->pBackgroundImage = pAbsolute;
        }
    }

    // Change context's "title" string
    XP_FREEIF(m_pContext->title);
    m_pContext->title = NULL;
    m_pContext->is_new_document = TRUE;

    // Change the history entry data
    History_entry * pEntry = SHIST_GetCurrent(&(m_pContext->hist));
	if(pEntry ){
        XP_FREEIF(pEntry->address);
        pEntry->address = XP_STRDUP(pUntitled);
        XP_FREEIF(pEntry->title);
    }
    // Layout uses this as the base URL for all links and images
    LO_SetBaseURL( m_pContext, pUntitled );

    // Cleat the old title in page data
    XP_FREEIF(pPageData->pTitle); 

    // This will set new background image,
    //   call FE_SetDocTitle() with new "file://Untitled" string,
    //   and refresh the layout of entire doc
    SetPageData(pPageData);

    FreePageData(pPageData);
    
    // This marks the page as "dirty" so we ask user to save
    //  the page even if they didn't type anything
    StartTyping(TRUE);
}
  
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!

    if( type == LO_COLOR_FG )
        // Use special version that overrides the flag that
        //  is used to restrict changing foreground color 
        lo_SetBodyTextFGColor(pContext, 0, pColor);
    else
        LO_SetDocumentColor( pContext, type, pColor );
    if( pColor) XP_FREE(pColor);
}

void CEditBuffer::RefreshLayout(){
    PRPackedBool saveResize;

    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 );
    }

    /* HACK ALERT!
     * See discussion in layedit.c regarding bug 94115.
     */
    saveResize = m_pContext->reSize;
    m_pContext->reSize = (PRPackedBool)TRUE;
    Relayout(m_pRoot, 0, m_pRoot->GetLastMostChild() );
    m_pContext->reSize = saveResize;
}

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();
		}
	}

    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 if not supplied
    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 )
    {
        m_pDragTableData->X = x;
        m_pDragTableData->Y = y;
        m_pDragTableData->iWidth = 0;
        m_pDragTableData->iHeight = 0;

        m_pDragTableData->iSourceType = GetTableSelectionType(); //m_TableHitType;
        // Initialize data for possible dragging
        // This is used by PositionDropCaret()
        m_pDragTableData->iDropType = ED_DROP_NORMAL;
        m_pDragTableData->pDragOverCell = NULL;

        // This will get first cell in table if that is what is selected
        CEditTableCellElement *pCell = GetFirstSelectedCell();
        if( !pCell )
            return FALSE;

        // Get first layout cell of selection
        if( m_pSelectedLoTable )
        {
            XP_ASSERT(m_pSelectedLoTable);
            if(!m_pSelectedLoTable)
                return FALSE;
            // 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 {
            XP_ASSERT(m_SelectedEdCells.Size() > 0 && m_SelectedLoCells.Size() > 0);
            m_pDragTableData->pFirstSelectedCell = (LO_Element*)m_SelectedLoCells[0];
        }                        
        // Source table we are dragging from
        m_pDragTableData->pSourceTable = lo_GetParentTable(m_pContext, m_pDragTableData->pFirstSelectedCell);

        intn  iPrevCounter = 0;
        intn  iRowCounter = 0;
        // Count number of actual cells per row (ignore COLSPAN)
        // This will be assembled into the array of CellsPerRow
        //   that is used to show cells to be replaced in destination
        int32 iCellsInRow = 1;
        // This is the total possible cells in the row,
        // This is used to show number of cells that will be inserted
        //   above or below a row
        int32 iCellsInColumn = pCell->GetColSpan();
        m_pDragTableData->iColumns = iCellsInColumn;

        TXP_GrowableArray_int32  CellsPerRow;
        int32 iRows = 1;

        while( TRUE )
        {
            if( m_pSelectedEdTable )
                pCell = pCell->GetNextCellInTable(&iRowCounter);
            else
                pCell = GetNextSelectedCell(&iRowCounter);

            if( !pCell )
            {
                // We're done    
                // Add final cell count
                if( iCellsInRow )
                    CellsPerRow.Add(iCellsInRow);
                break;
            }

            if( iRowCounter != iPrevCounter )
            {
                // This cell is in the next row
                // Save count for previous row
                CellsPerRow.Add(iCellsInRow);
                // Set up for new row
                iPrevCounter = iRowCounter;
                iCellsInColumn = pCell->GetColSpan();
                iCellsInRow = 1;
                iRows++;
            } 
            else 
            {
                // We have another cell in this row
                // Count number of cells in each row 
                // Note that we don't add the COLSPAN,
                //  which would be needed based on Colspan
                iCellsInRow++;
                iCellsInColumn += pCell->GetColSpan();
            }
            // Save the max number of cells in a column
            if( iCellsInColumn > m_pDragTableData->iColumns )
                m_pDragTableData->iColumns = iCellsInColumn;
        }

        // Now that we know the number of rows,
        //  create a simple int32 array and copy values from growable array
        m_pDragTableData->pCellsPerRow = (int32*)XP_ALLOC(iRows*sizeof(int32));
        m_pDragTableData->iRows = iRows;

        if( m_pDragTableData->pCellsPerRow )
        {
            for(intn i=0; i < iRows; i++ )
            {
                m_pDragTableData->pCellsPerRow[i] = CellsPerRow[i];
            }
            return TRUE;
        } 
        else 
        {
            // not enough memory
            XP_ASSERT(FALSE);
        }
    }
    return FALSE;
}

void CEditBuffer::StopDragTable()
{ 
    // Free global struct and set to NULL
    if( m_pDragTableData )
    {
        XP_FREEIF(m_pDragTableData->pCellsPerRow);
        XP_FREE(m_pDragTableData);
        m_pDragTableData = NULL;
    }
    if( edt_pPrevReplaceCellSelected )
        ClearSpecialCellSelection(edt_pPrevReplaceCellSelected);
}

static void edt_ClearSpecialCellSelection(MWContext *pContext, LO_CellStruct *pLoCell, CEditTableCellElement *pEdCell)
{
    if( pContext && pLoCell && (pLoCell->ele_attrmask & LO_ELE_SELECTED_SPECIAL) )
    {
        pLoCell->ele_attrmask &= ~LO_ELE_SELECTED_SPECIAL;
        FE_DisplayEntireTableOrCell(pContext, (LO_Element*)pLoCell);
        if( !pEdCell )
            pEdCell = (CEditTableCellElement*)edt_GetTableElementFromLO_Element((LO_Element*)pLoCell, LO_CELL);

        if( pEdCell )
               pEdCell->SetSpecialSelected(FALSE);
    }
}

/* 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 )
    {
        // We can't drop inside the table being dragged

        // Get where user is currently dragging over
        //int32 iWidth = 0;
        //int32 iHeight = 0;
        LO_Element *pLoCell;
        m_pDragTableData->X = x;
        m_pDragTableData->Y = y;
        ED_DropType iDropType = GetTableDropRegion(&m_pDragTableData->X, &m_pDragTableData->Y, 
                                                   &m_pDragTableData->iWidth, &m_pDragTableData->iHeight, 
                                                   &pLoCell);
        // Check if we changed cell or drop type
        XP_Bool bDisplayFeedback = ( pLoCell != m_pDragTableData->pDragOverCell ||
                                     iDropType != m_pDragTableData->iDropType );
        m_pDragTableData->iDropType = iDropType;
        m_pDragTableData->pDragOverCell = pLoCell;

        // Don't allow dropping on top of selection or table being dragged
        if( iDropType == ED_DROP_NONE ||
            (m_pDragTableData->iSourceType == ED_HIT_SEL_TABLE &&
             m_pDragTableData->pSourceTable == lo_GetParentTable(m_pContext, m_pDragTableData->pDragOverCell)) )
        {
            FE_DestroyCaret(m_pContext);
            // Be sure there's no special selection
            if( edt_pPrevReplaceCellSelected )
                ClearSpecialCellSelection(edt_pPrevReplaceCellSelected);
            return FALSE;
        }

        if( m_pDragTableData->iSourceType > ED_HIT_SEL_TABLE )
        {
            if( iDropType == ED_DROP_NORMAL )
            {
                // Be sure there's no special selection
                //  when we move off of the table
                if( edt_pPrevReplaceCellSelected )
                    ClearSpecialCellSelection(edt_pPrevReplaceCellSelected);
            }
            else 
            {
                // Call Front end to display drop feedback only if
                //  different from previous condition
                if( bDisplayFeedback )
                {
                    FE_DestroyCaret(m_pContext);
                    if( m_pDragTableData->iDropType == ED_DROP_REPLACE_CELLS )
                    {
                        SetReplaceCellSelection();

                        // Should we do this as well?
                        //lo_PositionDropCaret(m_pContext, x, y, NULL);
                    } else {
                        // Clear replace selection
                        if( edt_pPrevReplaceCellSelected )
                            ClearSpecialCellSelection(edt_pPrevReplaceCellSelected);

                        // Set the caret to show inserting between rows, colomns, or entire table
                        FE_DisplayDropTableFeedback(m_pContext, m_pDragTableData);
                    }
                }
                return TRUE;
            }
        }
    }
#endif
    FE_DestroyCaret(m_pContext);
    lo_PositionDropCaret(m_pContext, x, y, NULL);
    return TRUE;
}

void CEditBuffer::DeleteSelectionAndPositionCaret( int32 x, int32 y )
{
    // Table elements are too hard! Set flag to delete AFTER
    //   inserting the table instead. (Down side is double Relayout)
    if( IsTableOrCellSelected() )
    {
        m_bDeleteTableAfterPasting = TRUE;
        PositionCaret(x,y);    
        return;
    }
    if( !IsSelected() )
    {
        // Nothing selected - nothing to delete
        PositionCaret(x,y);    
        return;
    }

    // Get the CEditObject at drop point and use it to create new insert point
    CPersistentEditSelection PSelBefore;
    GetSelection(PSelBefore);

    // 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 and set new insert position
    	    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;
    }

    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 */
    }
    // If we set caret at beginning of a text element, 
    //  use its formating with next text typed
    m_bUseCurrentTextFormat = (m_pCurrent &&  m_pCurrent->IsText() && m_iCurrentOffset == 0 ) ? TRUE : FALSE;
}

// 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;
    }
}

// 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
    // If we set caret at beginning of a text element, 
    //  use its formating with next text typed
    m_bUseCurrentTextFormat = (m_pCurrent &&  m_pCurrent->IsText() && m_iCurrentOffset == 0 ) ? TRUE : FALSE;
}

//
//
// 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();
     }
     // Always clear table and 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();
}

// 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 );
            // WARNING: Null element is encountered in PRE text
            if( !pLayoutElement )
                return;
            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 edt_ForceTableSelection(MWContext *pMWContext, LO_TableStruct *pLoTable)
{
    GET_WRITABLE_EDIT_BUF_OR_RETURN(pMWContext, pEditBuffer);
    if( pLoTable )
    {
        // This is needed to suppress clearing the selection on mouse up
        pEditBuffer->m_bDontClearTableSelection = TRUE;
        pEditBuffer->SelectTableElement(pLoTable->x, pLoTable->y, (LO_Element*)pLoTable, 
                                        ED_HIT_SEL_TABLE, FALSE, FALSE); 
    }
}

void CEditBuffer::EndSelection( )
{
    // Almost always clear any table and cell selection
    // (We don't do this on mouse down now)
    // Exception is when we force a selection from layout
    //  by calling edt_ForceTableSelection()
    if( m_bDontClearTableSelection )
        m_bDontClearTableSelection = FALSE;
    else
        ClearTableAndCellSelection();

    // 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 );
#if 0
        //------------------------- 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 ------------------------
#endif

        *ppElement = pStart->lo_any.edit_element->Leaf();
        if( pOffset )
            *pOffset = pStart->lo_any.edit_offset+iStartPos;
        XP_ASSERT( *ppElement );
    }
    else {
        *ppElement = m_pCurrent;
        if( pOffset )
            *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);
    }
}

// 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 );
        // Fill in values for caller's selection object as well
        selection.m_start.m_pElement = pStartElement;
        selection.m_start.m_iPos = iStartOffset;
        selection.m_end.m_pElement = pEndElement;
        selection.m_end.m_iPos = iEndOffset;
        selection.m_bFromStart = 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);

    // Don't bother trying to do anything now
    //  if we will delete the table stuff later
    if( m_bDeleteTableAfterPasting )
        return EDT_COP_OK;

    // Do strict testing and tell us if we crossed a cell boundary
    EDT_ClipboardResult result = CanCut((XP_Bool)TRUE, (XP_Bool)TRUE);
    if ( result == EDT_COP_OK || result == EDT_COP_SELECTION_CROSSES_TABLE_DATA_CELL )
    {
        if ( IsSelected() )
        {
            CEditLeafElement *pBegin;
            CEditLeafElement *pEnd;

            CEditSelection selection;
            GetSelection(selection);
            if ( selection.ContainsLastDocumentContainerEnd() )
            {
                selection.ExcludeLastDocumentContainerEnd();
                SetSelection(selection);
            }
            if ( selection.IsInsertPoint() )
            {
                return result;
            }
            if( result == EDT_COP_OK )
            {
                // The normal deletion path
                MakeSelectionEndPoints( selection, pBegin, pEnd );
                DeleteBetweenElements( pBegin, pEnd, bCopyAppendAttributes );
            }
            else if( result == EDT_COP_SELECTION_CROSSES_TABLE_DATA_CELL )
            {
                // A dummy empty stream - it will be ignored
                CStreamOutMemory stream;
                return DeleteOrCopyAcrossCellBorders(selection, stream);
            }
        }
    }
    return result;
}

// Used for copying text HTML content across table cell boundaries
#define ED_COPY_TYPE_INDEX (3*sizeof(int32))

void CEditBuffer::CopySelectionAcrossCellBoundary(CEditSelection& selection, char **ppHtml, int32* pHtmlLen)
{
    CStreamOutMemory stream;
    INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(m_pContext);
    stream.WriteInt(GetClipboardSignature());
    stream.WriteInt(GetClipboardVersion());
    stream.WriteInt(INTL_GetCSIWinCSID(c));
    stream.WriteInt(int32(ED_COPY_NORMAL)); // Index to this is ED_COPY_TYPE_INDEX

    // IMPORTANT: We really can't know what this value is until
    //            we are done with AppendCopyBetweenElements,
    //            so be sure to to back and patch this value
    //            after determining the 
    int32 bMergeEnd = 0;
    stream.WriteInt(bMergeEnd);

    // Share the work with the routine that also deletes across cells
    DeleteOrCopyAcrossCellBorders(selection, stream);
    
    // Terminate the stream
    stream.WriteInt( (int32)eElementNone );

    *ppHtml = stream.GetText();
    *pHtmlLen = stream.GetLen();

    // Reset the original selection
    SetSelection(selection);
}

EDT_ClipboardResult CEditBuffer::DeleteOrCopyAcrossCellBorders(CEditSelection& selection, CStreamOutMemory& stream)
{
    CEditLeafElement *pBegin;
    CEditLeafElement *pEnd;
    // We will copy to a stream if there is already some data in it
    XP_Bool bCopy = stream.GetLen() > 0;

    // Get table(s) at start and end of the selection
    // Note that a table will be returned if start or end is
    //  in a caption element as well. DeleteOrCopyWithinTable will handle this
    CEditTableElement *pTableAtStart = selection.m_start.m_pElement->GetTableIgnoreSubdoc();
    CEditTableElement *pTableAtEnd = selection.m_end.m_pElement->GetTableIgnoreSubdoc();

    // Must have one or the other end within a table
    XP_ASSERT(pTableAtStart || pTableAtEnd);

    // We are OK if both are null (shouldn't be here)
    if( pTableAtStart == 0 && pTableAtEnd == 0 )
        return EDT_COP_OK;
    
    // Check for nested table - 
    //   We can't deal with crossing a nested table boundary
    if( (pTableAtStart && pTableAtStart->GetParent()->GetTableIgnoreSubdoc()) ||
        (pTableAtEnd && pTableAtEnd->GetParent()->GetTableIgnoreSubdoc()) )
    {
        return EDT_COP_SELECTION_CROSSES_NESTED_TABLE;
    }

    // This divides at both start and element
    MakeSelectionEndPoints( selection, pBegin, pEnd );
    XP_ASSERT(pBegin != 0 && pEnd != 0);

    // Captions make things more complicated
    // We must know if and where they are to know when to delete them
    XP_Bool bStartIsInCaptionAbove = FALSE;
    XP_Bool bStartIsInCaptionBelow = FALSE;
    XP_Bool bEndIsInCaptionAbove = FALSE;
    XP_Bool bEndIsInCaptionBelow = FALSE;
    
    if( pTableAtStart )
    {
        CEditCaptionElement *pCaption = pBegin->GetCaption();
        if( pCaption )
        {
            bStartIsInCaptionAbove = pTableAtStart->GetChild() == pCaption;
            bStartIsInCaptionBelow = !bStartIsInCaptionAbove;
        }
    }
    if( pTableAtEnd )
    {
        CEditCaptionElement *pCaption = pEnd->GetCaption();
        if( pCaption )
        {
            bEndIsInCaptionAbove = pTableAtEnd->GetChild() == pCaption;
            bEndIsInCaptionBelow = !bEndIsInCaptionAbove;
        }
    }

    // We must note if the end element is a temporary empty element
    //  created to mark the end of selection. It might get deleted
    //  below so we must know when to recreate it
    XP_Bool bEndIsTemporary = selection.m_end.m_pElement != pEnd && pEnd->GetLen() == 0;

    // Save where we will reposition the insert point and start of relayout
    // (it will be NULL if pBegin is at the start of the document,
    //  we fix that below)
    CEditLeafElement *pRelayoutStart = pBegin->PreviousLeaf();
    //int32 iInsertPointOffset = pRelayoutStart ? pRelayoutStart->GetLen() : 0;

    //TODO: Should we check for the end of the page?
    CEditElement *pRelayoutEnd = pEnd->NextLeafAll();

    // Suppress Relayout when deleting -- we will relayout once at the end
    m_bNoRelayout = TRUE;

    CEditLeafElement *pTempStartLeaf = NULL;
    CEditLeafElement *pTempEndLeaf = NULL;
    if( pTableAtStart )
    {
        if( pTableAtEnd == pTableAtStart )
        {
            // Start and end of selection are within the same table
            DeleteOrCopyWithinTable(pTableAtStart, pBegin, pEnd, stream);
        }
        else
        {
            // End is outside the start table, so delete/copy to the end of it
            CEditLeafElement *pTempEndLeaf2 = pTableAtStart->GetLastMostChild()->Leaf();
            if( pTempEndLeaf2 )
                pTempEndLeaf2 = pTempEndLeaf2->Divide( pTempEndLeaf2->GetLen() )->Leaf(); 
            
            if( pTempEndLeaf2 )
            {
                DeleteOrCopyWithinTable(pTableAtStart, pBegin, pTempEndLeaf2, stream);
                // If there's a caption below, then selection must
                //  have spanned it, so delete/copy it unless the start was within it
                if( !bCopy && !bStartIsInCaptionBelow )
                    pTableAtStart->DeleteCaptionBelow();
            }
        }

        if( pTableAtEnd && pTableAtEnd != pTableAtStart )
        {
            // End is in another table - setup to delete/copy between the two tables
            // Get last element in table
            pTempEndLeaf = pTableAtStart->GetLastMostChild()->Leaf();
            if( pTempEndLeaf )
            {
                // Next leaf after the table
                pTempStartLeaf = pTempEndLeaf->NextLeafAll();
                // Just before the table at the end
                pTempEndLeaf = pTableAtEnd->PreviousLeaf();
            }
            // Delete/copy captions if start or end aren't in them
            if( !bCopy )
            {
                if( !bStartIsInCaptionBelow )
                    pTableAtStart->DeleteCaptionBelow();
                if( !bEndIsInCaptionAbove )
                    pTableAtEnd->DeleteCaptionAbove();
            }
        }
    }
    else if( pTableAtStart != pTableAtEnd )
    {
        // We must have a table at the end (and its not the same as the start)
        // Delete/copy from start of selection to begining of table at end
        pTempStartLeaf = pBegin;
        pTempEndLeaf = pTableAtEnd->PreviousLeaf();

        if( pTempEndLeaf == selection.m_start.m_pElement )
        {
            // This is a special case when we are deleting from end of text before a table
            //   into the first element in the table. The selection is invalid at this point.
            // Save the element before as our relayout start and remove temporary 0-length element
            pRelayoutStart = pTempEndLeaf;
            pTempEndLeaf = 0;
            if( pRelayoutStart != pBegin && pBegin->GetLen() == 0 )
                Reduce(pBegin->GetParent());
        }

        // Since the end is in pTableAtEnd, 
        //   we must span a caption above in that table,
        //   so delete/copy it unless end of selection is within it
        if( !bCopy && !bEndIsInCaptionAbove )
            pTableAtEnd->DeleteCaptionAbove();
    }
    if( pTempStartLeaf && pTempEndLeaf )
    {
        // Both of the following calls will reduce (delete) our original pEnd element,
        //   so we will create a new temporary element if we need it

        // Get or create an end element after the point to delete/copy up to
        pTempEndLeaf = pTempEndLeaf->Divide(pTempEndLeaf->GetLen())->Leaf();
        if( bCopy )
            AppendCopyBetweenElements(pTempStartLeaf, pTempEndLeaf, stream);
        else
            DeleteBetweenElements(pTempStartLeaf, pTempEndLeaf);

        // Get the element after the end of the original selection
        // If it is 0-length, then the pEnd we got above is still valid,
        //  else it was destroyed by a Reduce call, so we must recreate it        
        CEditElement *pNextAfterSelection = selection.m_end.m_pElement->GetNextSibling();
        if( pNextAfterSelection == 0 || pNextAfterSelection->Leaf()->GetLen() !=0 )
            pEnd = selection.m_end.m_pElement->Divide( selection.m_end.m_iPos )->Leaf();
#ifdef DEBUG
        else  // A wise safety check
            XP_ASSERT( pEnd == pNextAfterSelection );
#endif
    }
    if( pTableAtEnd && pTableAtEnd != pTableAtStart )
    {
        // Delete/copy from start of the end table to the end of the selection
        pTempStartLeaf = pTableAtEnd->GetFirstMostChild()->Leaf();
        if( pTempStartLeaf )
            DeleteOrCopyWithinTable(pTableAtEnd, pTempStartLeaf, pEnd, stream);
    }
    else if( pTableAtEnd == 0 && pEnd )
    {
        // Delete/copy between the end of the start table and end of the selection
        pTempEndLeaf = pTableAtStart->GetLastMostChild()->Leaf();
        if( pTempEndLeaf )
        {
            pTempStartLeaf = pTempEndLeaf->NextLeafAll();
            if( bCopy )
                AppendCopyBetweenElements(pTempStartLeaf, pEnd, stream);
            else
                DeleteBetweenElements( pTempStartLeaf, pEnd );
            // We are deleting from within a table to outside it,
            //   so delete/copy the caption below unless start was within it
            if( !bCopy && !bStartIsInCaptionBelow )
                pTableAtStart->DeleteCaptionBelow();
        }
    }

    if( !bCopy )
    {
        CEditLeafElement *pLeafAfterTable = 0;
        // Be sure each table has a text element before and after
        // Delete entire rows if all elements in them are empty
        if( pTableAtStart )
        {
            pTableAtStart->FinishedLoad(this);
            // This will always be valid since Finished load will insert 
            //  a text element before or after if needed
            CEditLeafElement *pLeafBeforeTable = pTableAtStart->PreviousLeaf();
            // Get this to reset relayout end as well
            if( pTableAtEnd == pTableAtStart )
            {
                // This is also guarenteed to be valid
                pLeafAfterTable = pTableAtEnd->GetLastMostChild()->Leaf()->NextLeafAll();
            }
            CEditLeafElement *pPreviousLeaf = 0;
            if( pTableAtStart->DeleteEmptyRows(&pPreviousLeaf) )
            {
                // We deleted 1 or more rows - reset the start element to either the
                //  last leaf in row above the first deleted or to above the table
                pRelayoutStart = pPreviousLeaf ? pPreviousLeaf : pLeafBeforeTable;
                // and end element if in the same table
                if( pLeafAfterTable )
                    pRelayoutEnd = pLeafAfterTable;

                // If all the rows were deleted, then 
                //  move any text in a caption that would be deleted with the table
                //  and delete the table
                if( pTableAtStart->CountRows() == 0 )
                {
                    pTableAtStart->MoveCaptionOutsideTable(bEndIsInCaptionBelow);
                    pTableAtStart->Unlink();
                    delete pTableAtStart;
                }
            }
        }
        if( pTableAtEnd && pLeafAfterTable == 0 )
        {
            pTableAtEnd->FinishedLoad(this);
            pLeafAfterTable = pTableAtEnd->GetLastMostChild()->Leaf()->NextLeafAll();
            if( pTableAtEnd->DeleteEmptyRows() )
            {
                // We deleted 1 or more rows -- reset the end element
                pRelayoutEnd = pLeafAfterTable;
                // Delete table if necessary
                if( pTableAtEnd->CountRows() == 0 )
                {
                    pTableAtEnd->MoveCaptionOutsideTable(TRUE);
                    pTableAtEnd->Unlink();
                    delete pTableAtEnd;
                }
            }
        }
    }

    if( !pRelayoutStart )
    {
        // We probably deleted from the top of the page
        pRelayoutStart = m_pRoot->GetFirstMostChild()->Leaf();
    }

    // Reset the insert point to the end of the starting relayout element
    SetInsertPoint(pRelayoutStart, pRelayoutStart->GetLen(), m_bCurrentStickyAfter);

    // Turn layout back on
    m_bNoRelayout = FALSE;
    
    // There seems to be a 0-length elemement left over after copying,
    //  but Reducing isn't eliminating it. It reveals itself upon
    //  the next time we save buffer to the undo copy, but its not critical
    CEditElement* pCommonAncestor = pRelayoutStart->GetCommonAncestor(pRelayoutEnd);
    Reduce(pCommonAncestor);
    
    Relayout( pRelayoutStart, 0, pRelayoutEnd);
    return EDT_COP_OK;
}

// This assumes that both pBegin and pEnd are within the same table
//   and m_bNoRelayout must already be set to FALSE
// Used only by DeleteOrCopyAcrossCellBorders

void CEditBuffer::DeleteOrCopyWithinTable( CEditTableElement *pTable, 
                                           CEditLeafElement *pBegin, CEditLeafElement *pEnd,
                                           CStreamOutMemory& stream )
{
    if( !pTable || !pBegin || !pEnd )
        return;

    // We will copy to a stream if there is already some data in it
    XP_Bool bCopy = stream.GetLen() > 0;

    CEditTableCellElement *pStartCell = pBegin->GetTableCellIgnoreSubdoc();
    CEditTableCellElement *pEndCell = pEnd->GetTableCellIgnoreSubdoc();
    CEditLeafElement *pLastLeaf = NULL;
    CEditCaptionElement *pCaption = NULL;
    CEditLeafElement *pCaptionBelowStartLeaf = 0;

    if( !pStartCell )
    {
        // Start must be inside a caption
        pCaption = pBegin->GetCaptionIgnoreSubdoc();
        if( pCaption )
        {
            if( pCaption == pTable->GetChild() )
            {
                if( pTable->GetFirstMostChild() == pBegin && pEndCell == 0 &&
                    pCaption == pEnd->GetCaptionIgnoreSubdoc() )
                {
                    // The beginning supplied is at start of the caption
                    //  and the end is also within that caption.
                    //  Go to the code below to delete/copy just within the caption
                    goto CHECK_END_IS_IN_CAPTION;
                }
                else
                {
                    // Start is in caption "above" (first child) of table.
                    // Get end of the caption and jump to delete/copy
                    //   from start of selection to end of the caption,
                    //   then continue deleting through other cells
                    pLastLeaf = pCaption->GetLastMostChild()->Leaf();
                    goto DELETE_START;
                }
            }
            else
            {
                // Start is actually in a caption "below" (last child of) table,
                //  so simply delete/copy to the end of the caption and leave
                pLastLeaf = pCaption->GetLastMostChild()->Leaf();
                pLastLeaf = pLastLeaf->Divide(pLastLeaf->GetLen() )->Leaf();
                XP_ASSERT(pLastLeaf == pEnd);
                if( pLastLeaf )
                {
                if( bCopy )
                    AppendCopyBetweenElements(pBegin, pLastLeaf, stream);
                else
                    DeleteBetweenElements( pBegin, pLastLeaf );
                }
                return;
            }
        }
        else
        {
            // We are messed up
            XP_ASSERT(FALSE);
            return;
        }
    }
    if( !pEndCell )
    {
CHECK_END_IS_IN_CAPTION:
        // End of selection must be in a caption
        pCaption = pEnd->GetCaptionIgnoreSubdoc();
        if( pCaption )
        {
            XP_Bool bCaptionAbove = (pCaption == pTable->GetChild());
            // End is inside the caption
            // Delete/copy from start of caption to the end of selection
            CEditLeafElement *pCaptionStart  = pCaption->GetFirstMostChild()->Leaf();
            if( pCaptionStart )
            {
                if( bCopy )
                {
                    if( bCaptionAbove )
                    {
                        // Append the Caption text now and we must be done
                        AppendCopyBetweenElements(pCaptionStart, pEnd, stream);
                        return;
                    }
                    // Copying from a caption below the table:
                    // Save the start and copy after done copying other cell contents
                    pCaptionBelowStartLeaf = pCaptionStart;
                }
                else
                {
                    // We can delete now, doesn't matter if caption is above or below
                    DeleteBetweenElements( pCaptionStart, pEnd );
                    if( bCaptionAbove )
                        // End is in a caption "above" the table, so we're done
                        return;
                }
            }
        }
        else
        {
            // We are messed up
            XP_ASSERT(FALSE);
            return;
        }
    }

    if( pStartCell )
    {
        if( pStartCell != pEndCell )
        {
            // Delete/copy from start of selection to end of first cell
            pLastLeaf = pStartCell->GetLastMostChild()->Leaf();
DELETE_START:
            pLastLeaf = pLastLeaf->Divide(pLastLeaf->GetLen())->Leaf();

            if( bCopy )
                AppendCopyBetweenElements(pBegin, pLastLeaf, stream);
            else
                DeleteBetweenElements( pBegin, pLastLeaf );

            CEditTableCellElement *pCell;
            if( pStartCell )
                pCell = pStartCell->GetNextCellInTable();
            else
                // We jumped into here from a caption element
                //   that was "above" the table (actually the first child),
                //   so get the first cell in the table
                pCell = pTable->GetFirstCell();

            // Delete/copy entire contents in intervening cells                
            while( pCell && pCell != pEndCell )
            {
                if( bCopy )
                {
                    // Copy from the first to last leaves in the cell
                    CEditLeafElement *pFirstLeaf = pCell->GetFirstMostChild()->Leaf();
                    pLastLeaf = pCell->GetLastMostChild()->Leaf();
                    if( pFirstLeaf && pLastLeaf )
                    {
                        pLastLeaf = pLastLeaf->Divide(pLastLeaf->GetLen() )->Leaf();
                        AppendCopyBetweenElements(pFirstLeaf, pLastLeaf, stream);
                    }
                }
                else
                {
                    pCell->DeleteContents();
                }
                pCell = pCell->GetNextCellInTable();
            }
        }
        if( pEndCell )
        {
            if( pStartCell != pEndCell )
                // Be sure we start at the start of last cell
                pBegin = pEndCell->GetFirstMostChild()->Leaf();

            // Delete/copy to the selection endpoint
            if( pBegin )
            {
                if( bCopy )
                    AppendCopyBetweenElements(pBegin, pEnd, stream);
                else
                    DeleteBetweenElements( pBegin, pEnd );
            }
        }
        else if( pCaptionBelowStartLeaf )
        {
            // We have some contents in the Caption below the table at the end
            AppendCopyBetweenElements(pCaptionBelowStartLeaf, pEnd, stream);
        }
    }
}


// This should be used ONLY for "Normal" copying, not table elements
// Append elements to an existing stream.
//  Be sure to end the stream with stream.WriteInt( (int32)eElementNone );
void CEditBuffer::AppendCopyBetweenElements( CEditLeafElement *pBegin, CEditLeafElement *pEnd, CStreamOutMemory& stream )
{
    XP_ASSERT(pBegin && pEnd && pEnd->IsLeaf() && stream.GetLen() > 0 );
    CEditElement* pCommonAncestor = pBegin->GetCommonAncestor(pEnd);
    XP_Bool bEndIsTemporary = pEnd->Leaf()->GetLen() == 0;

    CEditInsertPoint begin(pBegin, 0);
    ElementOffset end_offset = 0; 

    // The end element supplied was prepared for DELETING,
    //  (i.e., the begining and end of the selection were "divided")
    //  so the end is guarenteed to be either a next sibling
    //  or the temporary 0-length text element at the end of a real element
    //  For the purpose of copying, we want to construct a "normal" selection
    //  whose end element is real and has an offset equal to its length
    pEnd = pEnd->PreviousLeaf();
    if( pEnd )
    {
        XP_ASSERT(pEnd->IsLeaf());
        end_offset = pEnd->Leaf()->GetLen();
    }
    else
    {
        XP_ASSERT(FALSE);
        return;
    }
    if( bEndIsTemporary ) 
        // We must remove the temporary pEnd element
        //  else the document is really horked
        Reduce(pCommonAncestor);

    CEditInsertPoint end(pEnd, end_offset);
    CEditSelection selection(begin, end);
    
    // Have the element tree write the selection to the stream as we normally do
    m_pRoot->PartialStreamOut(&stream, selection);

    // After each append, we must patch the bMerge value that we
    //  couldn't determine when we started the stream.
    //  The last call here will yield the correct value.
    int32 bMerge = end.IsStartOfContainer() == 0;
    stream.WriteIntAtIndex(bMerge, ED_COPY_TYPE_INDEX);

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

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);
}

void CEditBuffer::DeleteBetweenElements( 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);
}

// Deletes cells -- if all cells in a col or row are selected,
//  this removes the row or col. Otherwise, just cell contents
//  are deleted to minimize disturbing table structure
// Caller must manage UNDO (BatchChanges) process
// All users except Backspace and Delete should use bNoSpaceInNewCells = FALSE;
void CEditBuffer::DeleteSelectedCells(XP_Bool bNoSpaceInNewCells)
{
    CEditTableElement *pTable = NULL;
    XP_Bool bRelayout;
    XP_Bool bNeedRelayout = FALSE;
    XP_Bool bTableDeleted = FALSE;

    // Set the flag that controls whether we
    //   insert a single space in the cell contents we clear
    if( bNoSpaceInNewCells )
        ClearFillNewCellWithSpace();
    else
        SetFillNewCellWithSpace();

    do {
        bRelayout = FALSE;

        // We are done if no cells are selected
        intn iCount = m_SelectedEdCells.Size();
        CEditTableCellElement *pFirstSelectedCell = m_SelectedEdCells[0];

        if( iCount > 0 && pFirstSelectedCell )
        {
            if( !pTable )
                pTable = pFirstSelectedCell->GetParentTable();

            // Be sure insert point is in the table
            SetTableInsertPoint(pFirstSelectedCell, TRUE);

            CEditTableCellElement *pCell;
            intn i, number;

            for( i = 0; i < iCount; i++ )
            {    
                pCell = m_SelectedEdCells[i];
                // Skip over cells where we already deleted their contents
                if( pCell->IsDeleted() )
                    continue;
                
                // Are we deleting entire column(s)?
                if( pCell->AllCellsInColumnAreSelected() )
                {
                    // Move to the first column being deleted
                    SetTableInsertPoint(pCell, TRUE);

                    // For efficiency, find contiguous selected columns
                    //  so we can delete them all at once
                    number = 1;
                    // We don't want to use GetNextCellInRow() because of 
                    //  COLSPAN effect. This gets the first cell in the
                    //  next geometric row using the table's layout data
                    pCell = pTable->GetFirstCellInNextColumn(pCell->GetX());
                    while( pCell )
                    {
                        if( pCell->AllCellsInColumnAreSelected() )
                            number++;
                        else
                            break;

                        pCell = pTable->GetFirstCellInNextColumn(pCell->GetX());
                    }

                    // Delete the column(s) -- this will relayout the table
                    AdoptAndDo(new CDeleteTableColumnCommand(this, number, &bTableDeleted));

                    // Reset this flag since it was probably cleared 
                    if( !bNoSpaceInNewCells )
                        SetFillNewCellWithSpace();

                    bRelayout = TRUE;
                    // We don't need to do relayout if
                    // this is the last thing we do
                    bNeedRelayout = FALSE;
                    break;
                }
                // Are we deleting entire rows(s)?
                else if( pCell->AllCellsInRowAreSelected() )
                {
                    // Move to the first row being deleted
                    SetTableInsertPoint(pCell, TRUE);

                    // For efficiency, find contiguous selected rows
                    //  so we can delete them all at once
                    number = 1;
                    // We don't want to use GetNextCellInColumn() because of 
                    //  ROWSPAN effect. This gets the first cell in the
                    //  next geometric row using the table's layout data
                    pCell = pTable->GetFirstCellInNextRow(pCell->GetY());
                    while( pCell )
                    {
                        if( pCell->AllCellsInRowAreSelected() )
                            number++;
                        else
                            break;

                        pCell = pTable->GetFirstCellInNextRow(pCell->GetY());
                    }
            		AdoptAndDo(new CDeleteTableRowCommand(this, number, &bTableDeleted));

                    // Reset this flag since it was probably cleared 
                    if( !bNoSpaceInNewCells )
                        SetFillNewCellWithSpace();

                    bRelayout = TRUE;
                    bNeedRelayout = FALSE;
                    break;
                }
                else 
                {
                    // Clear contents only
                    // TRUE means mark cell as deleted
                    pCell->DeleteContents(TRUE);
                    // We need to force table relayout if
                    //  this is the last thing we do
                    bNeedRelayout = TRUE;

                    // If we cleared contents of cell containing caret
                    // we must reset the insert point
                    if( pCell == pFirstSelectedCell )
                        SetTableInsertPoint(pFirstSelectedCell, TRUE);
                }
            }
        }
    }
    // Repeat entire process if we actually deleted anything
    //   since we have a new m_SelectedEdCell array after relayout
    while( !bTableDeleted && bRelayout );


    if( !bTableDeleted && pTable )
    {
        // We need to relayout only if deleting cell contents
        //  was the last thing we did
        if( bNeedRelayout )
            Relayout(pTable, 0);

        // Clear the m_bDeleted flags for all cells in the table
        CEditTableCellElement *pCell = pTable->GetFirstCell();
        while( pCell )
        {
            pCell->SetDeleted(FALSE);
            pCell = pTable->GetNextCellInTable();
        }    
    }

    if( !bNoSpaceInNewCells )
        ClearFillNewCellWithSpace();
}

// Currently (4/22/98) not used. But we may want a FE call 
//   in the future to clear cells contents
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;
}

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.
        }

        Relayout( start.m_pElement, start.m_iPos, pEnd );
        // was Reflow.  did not fix up problems with tree and layout inconsistencies(sp)
        if( bMailQuote ){
            CEditInsertPoint start = PersistentToEphemeral(persistentStart);
            //SetInsertPoint( start );
        }
    }

    return EDT_COP_OK;
}

ED_CopyType EDT_GetHTMLCopyType(char *pBuffer)
{
    int32 iCopyType = ED_COPY_NORMAL;
    // Read the 4th int32 in the stream
    XP_MEMCPY( (char*)&iCopyType, &pBuffer[3*sizeof(int32)], sizeof(int32) );
    return (ED_CopyType)iCopyType;
}

EDT_ClipboardResult CEditBuffer::PasteHTML( char *pBuffer, ED_PasteType iPasteType )
{
    CStreamInMemory stream(pBuffer);
    return PasteHTML(stream, iPasteType);
}

EDT_ClipboardResult CEditBuffer::PasteHTML( IStreamIn& stream, ED_PasteType iPasteType )
{
    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 "arbitrary cells" copying
    ED_CopyType iCopyType = (ED_CopyType)stream.ReadInt();

    // We always do full table pasting here, 
    //  even when embedding into an existing table
    if( iPasteType != ED_PASTE_TABLE && IsInsertPointInTableCell() &&
        !(m_pDragTableData && m_pDragTableData->iDropType <= ED_DROP_NORMAL) )
    {
        // Caller has told us how to paste table elements
        if( iPasteType !=  ED_PASTE_NORMAL )
            return PasteCellsIntoTable(stream, iPasteType);
    
        // Figure out the default action when caller doesn't tell us
        // If source is a complete table, then fall through to
        //   insert embeded table
        if( iCopyType > ED_COPY_TABLE )
        {
            // When dragging, user gets better feedback, so use
            //   that information
            if( m_pDragTableData )
            {
                // Keep the enums in synch so we can avoid a switch statement!
                iPasteType = (ED_PasteType)m_pDragTableData->iDropType;
            }
            else
            {
                // We should be here only when pasting is NOT from dragdrop
                if( m_pSelectedEdTable )
                {
                    // We are pasting on top of a selected table
                    // Move insert point to just after the table
	                CEditInsertPoint replacePoint(m_pSelectedEdTable->GetLastMostChild()->NextLeaf(), 0);
                    SetInsertPoint(replacePoint);

	                // Then set flag so we delete the table below
                    m_bDeleteTableAfterPasting = TRUE;
                    // and paste the cells as a new table
                    goto NORMAL_PASTE;
                }
                else if( m_SelectedEdCells.Size() > 0 )
                {
                    
                    // Replace the existing selected cells 
                    // Note: Insert point (caret) will almost always be 
                    //   within one of the selected cells EXCEPT if user unselects a 
                    //   cell using modifier key while clicking. That will leave caret
                    //   in the unselected cell. So lets assume they wanted to replace
                    //   the selected cells anyway.
                    iPasteType = ED_PASTE_REPLACE_CELLS;

                    // Set the Special Selection attribute for all currently-selected cells
                    //  to tell us what cells to replace
                    DisplaySpecialCellSelection();
                }
                
                else switch( iCopyType )
                {
                    case ED_COPY_ROWS:
                        iPasteType = ED_PASTE_ROW_ABOVE;
                        break;
                    case ED_COPY_COLUMNS:
                        iPasteType = ED_PASTE_COLUMN_BEFORE;
                        break;
                    case ED_COPY_CELLS:
                        iPasteType = ED_PASTE_COLUMN_BEFORE;
                        break;
                    default:
                        break;
                }
            }
            return PasteCellsIntoTable(stream, iPasteType);
        }
    }    

NORMAL_PASTE:
    // 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());
               }
            }

#if defined(ENDER) && defined(MOZ_ENDER_MIME)

            if( m_bEmbedded )
                AddImagesToSafeList(pElement);

#endif /* ENDER && MOZ_ENDER_MIME */

            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 )
    {
        // 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();

                // The following merge will delete pFirstNewContainer,
                //  so if the pLastInserted is the same, 
                //  set it to the container we will merge into
                if ( pLastInserted == pFirstNewContainer )
                    pLastInserted = pLeftContainer;

                // Merge containers and delete pFirstNewContainer;
                if ( pLeftContainer != pFirstNewContainer ){
                    pLeftContainer->Merge(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;
        
        //Fix for bug 174178. This used to be obtained before FinishedLoad,
        // but when inserting before a single space in a table cell,
        // that space is deleted during FinishedLoad.
        CEditElement* pNextLeaf = pLastInserted->GetLastMostChild()->NextLeafAll();
        
        // We need to reduce before we relayout because otherwise
        // null insert points mess up the setting of end-of-paragraph
        // marks.
        CEditInsertPoint tmp(pNextLeaf,0);
		CPersistentEditInsertPoint end2 = EphemeralToPersistent(tmp);
        Reduce( m_pRoot );
        CEditInsertPoint end3 = PersistentToEphemeral(end2);
        Relayout( pStart, iStartOffset, end3.m_pElement );
    }
    else {
        m_bNoRelayout = FALSE;
    }

    // Delete table or cells in original location if moving within the doc
    if( m_bDeleteTableAfterPasting )
    {
        if( m_pSelectedEdTable )
        {
            // Delete the entire table.
            // FALSE = don't reposition insert point
            m_pSelectedEdTable->Delete(FALSE);
        } 
        else if( m_SelectedEdCells.Size() )
        {
            // We must save and restore current insert point
            //   because DeleteSelectedCells will move it
            //   to the table whose cells are deleted
            CEditInsertPoint ip;
            GetInsertPoint(ip);
            DeleteSelectedCells();
            SetInsertPoint(ip);
        }
        m_bDeleteTableAfterPasting = FALSE;
    }
   
    return result;
}

EDT_ClipboardResult CEditBuffer::PasteCellsIntoTable( IStreamIn& stream, ED_PasteType iPasteType )
{
    m_bNoRelayout = FALSE;
    CEditTableCellElement* pTableCell = NULL;
    CEditTableElement* pSourceTable = NULL;

    // Shouldn't be here if this is "normal" or entire table
    XP_ASSERT(iPasteType > ED_PASTE_TABLE);
    XP_ASSERT(m_pCurrent);

    if ( iPasteType <= ED_PASTE_TABLE )
        goto BAD_CLIPBOARD;
    else 
    {    
        // Move caret into target cell
        if( m_pDragTableData )
        {
            // We are dragging source cells
            XP_ASSERT(m_pDragTableData->pDragOverCell);
            
            // Convert the drop types into general paste type
            // (incomming iPasteType is always ED_PASTE_NORMAL with DragNDrop)
            iPasteType = (ED_PasteType)m_pDragTableData->iDropType;
            XP_ASSERT(iPasteType != ED_PASTE_TABLE );

            CEditElement *pElement = edt_GetTableElementFromLO_Element(m_pDragTableData->pDragOverCell, LO_CELL);
            if( pElement )
            {
                pTableCell = pElement->TableCell();
                SetTableInsertPoint(pTableCell);
            }
        } else {
            // Pasting from clipboard
            // Target cell contains the current insert point
            pTableCell = m_pCurrent->GetTableCellIgnoreSubdoc();
        }

        // Get the source table from the stream
        int32 bMergeEnd = stream.ReadInt();
        pSourceTable =(CEditTableElement*)CEditElement::StreamCtor(&stream, this);

        XP_ASSERT(pSourceTable && pSourceTable->IsTable());
        if( !pSourceTable )
            goto BAD_CLIPBOARD;

        PasteTable(pTableCell, pSourceTable, iPasteType);

        return EDT_COP_OK;
    }
BAD_CLIPBOARD:
    if( pSourceTable )
        delete pSourceTable;

    m_bDeleteTableAfterPasting = FALSE;
    return EDT_COP_CLIPBOARD_BAD;
}

#define EDT_PASTE_ROW(t) (t == ED_PASTE_ROW_ABOVE || t == ED_PASTE_ROW_BELOW)
#define EDT_PASTE_COLUMN(t) (t == ED_PASTE_COLUMN_BEFORE || t == ED_PASTE_COLUMN_AFTER)
#define END_OF_TABLE(pEle) (pEle == NULL || pEle->type == LO_LINEFEED)

// Returns TRUE if there is another row to process 
//   (pLoEle points to first cell on next row, and iRowY is updated)
static XP_Bool edt_SetSpecialSelectRow(MWContext *pMWContext, LO_Element*& pLoEle, int32 iCellsInRow, 
                                       int32 iStartColX, int32& iRowY )
{
    // Find first LO cell at appropriate column in each row to be marked
    while(TRUE)
    {
        if( pLoEle && pLoEle->type == LO_CELL && pLoEle->lo_cell.x >= iStartColX )
            break;
        pLoEle = pLoEle->lo_any.next;
    }
    if(END_OF_TABLE(pLoEle))
        return FALSE;

    XP_ASSERT(pLoEle && pLoEle->type == LO_CELL & iRowY == pLoEle->lo_cell.y );

    for( intn i = 0; i< iCellsInRow; i++ )
    {
        if(END_OF_TABLE(pLoEle))
            return FALSE;
        LO_CellStruct *pLoCell = (LO_CellStruct*)pLoEle;
        // Check if cell is in proper row
        if( pLoCell->y != iRowY )
            // We are in another row - get out
            break;

        // Mark cell with special selection
        pLoCell->ele_attrmask |= LO_ELE_SELECTED_SPECIAL;
        FE_DisplayEntireTableOrCell(pMWContext, pLoEle);

        // Set flag in Editor's cell elements
        CEditTableCellElement *pEdCell = 
            (CEditTableCellElement*)edt_GetTableElementFromLO_Element(pLoEle, LO_CELL);
        if( pEdCell )
            pEdCell->SetSpecialSelected(TRUE);
#ifdef DEBUG
        else
            XP_ASSERT(FALSE);
#endif

        // Move to next table cell
        pLoEle = pLoEle->lo_any.next;
        while(!END_OF_TABLE(pLoEle))
        {
            if( pLoEle->type == LO_CELL )
                break;
            pLoEle = pLoEle->lo_any.next;
        }
    }
    // Check if done because no more cells
    if(END_OF_TABLE(pLoEle))
        return FALSE;

    // We are done marking cells in this row
    //   move to next row unless already there
    if( pLoEle->type == LO_CELL && pLoEle->lo_cell.y == iRowY )
    {
        pLoEle = pLoEle->lo_any.next;
        while(!END_OF_TABLE(pLoEle))
        {
            if( pLoEle->type == LO_CELL && pLoEle->lo_cell.y != iRowY )
               break;
            pLoEle = pLoEle->lo_any.next;
        }
    }
    if(END_OF_TABLE(pLoEle))
        return FALSE;

    // If here, pLoEle should be first cell in next row to mark
    XP_ASSERT(pLoEle && pLoEle->type == LO_CELL);
    
    // Set top of next row
    iRowY = pLoEle->lo_cell.y;

    return TRUE;
}

void CEditBuffer::PasteTable( CEditTableCellElement *pCell, CEditTableElement *pSourceTable, ED_PasteType iPasteType )
{
    if( !pSourceTable )
        return;

    CEditTableElement* pTable = pCell ? pCell->GetTable() : 0;
    // Very unlikely!
    XP_ASSERT(pTable);
    if( !pTable )
        return;
    
    XP_Bool bAllCellsPasted = TRUE;
    XP_Bool bAfterCurrentCell = (iPasteType == ED_PASTE_COLUMN_AFTER || 
                                 iPasteType == ED_PASTE_ROW_BELOW );

    int32 X = pCell->GetX();
    int32 Y = pCell->GetY();
    int32 iNewX = X + (bAfterCurrentCell ? pCell->GetFullWidth() : 0);
    int32 iNewY = Y + (bAfterCurrentCell ? pCell->GetHeight() : 0);
    
    // Should always be 0 initially - lets test it
    XP_ASSERT(m_pCellForInsertPoint == NULL);
    m_pCellForInsertPoint = NULL;    
    
    // For now, ignore source layout when pasting over a user-selection
    // When drag/dropping, lets assume we predicted the destination layout correctly
    // TODO: ONLY IGNORE SOURCE LAYOUT FOR CERTAIN COPY TYPES?
    XP_Bool bIgnoreSourceLayout = (m_pDragTableData == NULL);
    
    if( iPasteType == ED_PASTE_REPLACE_CELLS ||
        (pCell->IsSelected() && iPasteType == ED_PASTE_NORMAL) )
    {
        if( !m_pDragTableData )
        {
            if( m_SelectedEdCells.Size() > 0 )
            {
                // We are pasting over current selected cells only
                // Set all selected cells to the "special selection" mode
                DisplaySpecialCellSelection();
            }
            else
            {
                // Replace as if we were dragging - 
                //  start special selection with current cell
                //  and mark cells according to pattern in the source table
                bIgnoreSourceLayout = FALSE;

                CEditTableRowElement *pSourceRow = pSourceTable->GetFirstRow();
                LO_Element *pLoEle = (LO_Element*)GetLoCell((CEditElement*)pCell);
                if( !pLoEle || !pSourceRow )
                    return;
                // This will get updated by edt_SetSpecialSelectRow
                int32 iRowY = pLoEle->lo_cell.y;
                while( pSourceRow )
                {
                    // Count cells in each source row
                    // We DON'T want to pay attention to COLSPAN and ROWSPAN
                    int32 iCellsInRow = 0;
                    CEditElement *pSourceCell = pSourceRow->GetChild();
                    while( pSourceCell )
                    {
                        XP_ASSERT(pSourceCell->IsTableCell());
                        iCellsInRow++;
                        pSourceCell = pSourceCell->GetNextSibling();
                    }
                    int32 iStartColX = pLoEle->lo_cell.x;

                    // This code is shared with SetReplaceCellSelection(),
                    if( !edt_SetSpecialSelectRow(m_pContext, pLoEle, iCellsInRow, 
                                                 iStartColX, iRowY) )
                        break;

                    pSourceRow = pSourceRow->GetNextRow();
                }
            }
        }
        bAllCellsPasted = pTable->ReplaceSpecialCells(pSourceTable, bIgnoreSourceLayout,
                                                      &m_pCellForInsertPoint);
    }
    else if( EDT_PASTE_ROW(iPasteType) )
    {
        // NOTE: If 2nd to last param (iStartColumn) is 0, then row is pasted starting at left edge,
        //  ignoring what column the target cell is
        pTable->InsertRows(Y, iNewY, pSourceTable->CountRows(), 
                           pSourceTable, pTable->GetColumnIndex(pCell->GetX()),
                           &m_pCellForInsertPoint);
        
        //TODO: INSERT ROWS DOESN'T PAD OTHER ROWS IF EXTRA CELL(S) INSERTED
    }
    else if( EDT_PASTE_COLUMN(iPasteType) )
    {
        // Similar comment for "iStartRow" param
        pTable->InsertColumns(X, iNewX, pSourceTable->CountColumns(), 
                              pSourceTable, pTable->GetRowIndex(pCell->GetY()),
                              &m_pCellForInsertPoint);
        //TODO: DOESN'T CREATE MORE ROWS AT BOTTOM IF NEED TO PASTE SOURCE CELLS
    }
    // Clear the source selection only if we need it
    //   to delete

    SetFillNewCellWithSpace();
    pTable->FinishedLoad(this);
    ClearFillNewCellWithSpace();

    // Delete whats left of the source table
    delete pSourceTable;

    Relayout(pTable, 0);

    // Now that Relayout has inserted the new cells,
    //  delete selected table or cells in original location if moving within the doc
    if( m_bDeleteTableAfterPasting )
    {
        if( m_pSelectedEdTable )
        {
            // Delete the entire table.
            // FALSE = don't reposition insert point
            m_pSelectedEdTable->Delete(FALSE);
        } else if( m_SelectedEdCells.Size() )
        {
            // We must save and restore current insert point
            //   because DeleteSelectedCells will move it
            CEditInsertPoint ip;
            GetInsertPoint(ip);
            DeleteSelectedCells();
            SetInsertPoint(ip);
        }
        m_bDeleteTableAfterPasting = FALSE;
    }
    // We don't want any cells selected after pasting
    // (same behavior as when pasting text)
    ClearTableAndCellSelection();

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

    if( !bAllCellsPasted )
    {
        // There's not much we can do with the leftover cells after a replace action,
        //  so just tell the user. We do this here and not
        //  in ReplaceSpecialCells since we want relayout to happen first
        //  THIS LEAVES AN UGLY SELECTION FROM THE ORIGINAL SOURCE TO
        //  THE CURRENT CURSOR LOCATION - DOESN'T MAKE SENSE!!!
        FE_Alert(m_pContext, XP_GetString(XP_EDT_NOT_ALL_CELLS_PASTED));
    }
}

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::PasteTextAsTable(char *pText, ED_PasteType iPasteType, intn iRows, intn iCols)
{
    if( iRows == 0 || iCols == 0 )
       CountRowsAndColsInPasteText(pText, &iRows, &iCols); 
    
    if( iPasteType == ED_PASTE_IMAGE || (iRows <= 0 || iCols <= 0) )
        return EDT_COP_CLIPBOARD_BAD;

    // We really shouldn't be called with this, but we know what to do with it
    if( iPasteType == ED_PASTE_TEXT )
    {
        // 2nd to last param = don't relayout, last param = we want to reduce
        return PasteText(pText, FALSE, FALSE, TRUE, TRUE);
    }
    
    // Decide default behavior: 
    if( iPasteType == ED_PASTE_NORMAL )
    {
        if( IsInsertPointInTable() )
        {
            CEditTableCellElement *pCell = m_pCurrent->GetParentTableCell();
            // Replace selected cells if trying to paste over them 
            if( pCell && pCell->IsSelected() )
                iPasteType = ED_PASTE_REPLACE_CELLS;
            else
                // Use shape of source to decide to paste row vs. column
                iPasteType = (iRows > iCols) ? ED_PASTE_ROW_ABOVE : ED_PASTE_COLUMN_BEFORE;
        }
        else
            iPasteType = ED_PASTE_TABLE;
    }
    XP_Bool bInsertFullTable = (iPasteType == ED_PASTE_TABLE );

    // Create a new table
    // This will either be inserted whole or used as the 
    //   source table for row/column pasting into existing table
    CEditTableElement *pTable = NULL;
    
    if( bInsertFullTable )
    {
        // Create table using default setting for new table
        EDT_TableData *pData = EDT_NewTableData();
        if( !pData )
            return EDT_COP_CLIPBOARD_BAD;
        pData->iColumns = iCols;
        pData->iRows = iRows;
        pTable = InsertTable(pData);
        EDT_FreeTableData(pData);
    }
    else 
    {
        XP_ASSERT( IsInsertPointInTable());
        if( !IsInsertPointInTable() )
            return EDT_COP_CLIPBOARD_BAD;
        // Just a temporary table to hold text data
        pTable = new CEditTableElement( iCols, iRows);
    }

    if( !pTable )
        return EDT_COP_CLIPBOARD_BAD;
    
    // Set default text in each cell
    SetFillNewCellWithSpace();
    pTable->FinishedLoad(this);
    ClearFillNewCellWithSpace();

    CEditTableCellElement* pFirstCell = pTable->GetFirstCell();
    XP_ASSERT(pFirstCell);
    CEditTableCellElement* pCell = pFirstCell;
    intn  iRow = 0;
    intn  iPrevRow = 0;
    // This is actually used as the X value for
    //  each cell created. We know we have "normal"
    //  array of cells so this allows CountColumns() to work when pasting
    int32 iCol = 0;

    char *pCellText = pText;
    XP_Bool bEndOfRow = FALSE;

    while(TRUE)
    {            
        // Get one token of text from the string
        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 );

        // We fake X,Y coordinates for cells so
        // CountColumns() will work correctly when pasting
        pCell->SetX(iCol++);

        if( pCellText && *pCellText )
        {
            // Get the existing empty text element in the table
            CEditTextElement *pText = pCell->GetFirstMostChild()->Text();
            pText->SetText(pCellText);
        }

        if(bEndOfRow)
        {
            // If we have a short row (fewer tabs than maximum),
            //   skip to first cell of next row
            while( pCell && iRow == iPrevRow )
            {
                pCell = pCell->GetNextCellInTable(&iRow);
                if(pCell)
                    pCell->SetX(iCol++);
            }
            bEndOfRow = FALSE;
            iPrevRow = iRow;
            iCol = 0;
        } 
        else
        {
            pCell = pCell->GetNextCellInTable(&iRow);
            if( iRow != iPrevRow )
            {
                // We are in next row, but there's still text in source
                // SHOULD NEVER HAPPEN if CountRowsAndColsInPasteText is working
                XP_ASSERT(FALSE);
                iPrevRow = iRow;
                iCol = 0;
            }
        }

        // The next cell item starts at next character
        pCellText = pText;

        if( !pCell || *pText == '\0' )
            break;

    }
    //TODO: We should probably trigger the "Not all cells were pasted" message
    //      if there's still source text left to paste

    if( bInsertFullTable )
    {
        // Set insert point inside first cell in table
        SetTableInsertPoint(pFirstCell);
        // Relayout the entire table
        Relayout(pTable, 0);
    } 
    else 
    {
        // Insert rows or cols -- this will Relayout the table
        pCell = m_pCurrent->GetParentTableCell();
        PasteTable(pCell, pTable, iPasteType);
    }

    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((XP_Bool)TRUE, (XP_Bool)FALSE);
    if ( result != EDT_COP_OK ) return result;

    result = CopySelection( ppText, pTextLen, ppHtml, pHtmlLen );
    if ( result != EDT_COP_OK ) return result;

    if( IsTableOrCellSelected() )
    {
        BeginBatchChanges(kGroupOfChangesCommandID);
        // We assume that table or cells are selected ONLY
        //  if there's not a "normal" selection
        if( m_pSelectedEdTable )
        {
            // Delete the entire table
            // Assumes current insert point is inside the selected table
    		AdoptAndDo(new CDeleteTableCommand(this));
        } else if( m_SelectedEdCells.Size() )
        {
            // Delete all selected cells
            // This will delete the cell elements ONLY
            // if a complete row or column is selected.
            // If not, only cell contents are cleared
            // to miminize messing up table layout
            DeleteSelectedCells();
        }
        EndBatchChanges();
    }
    else
    { 
        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 )
    {
        // Use this to handle deleting across cell boundaries
        DeleteSelection(selection);
#if 0
// The old way
        CEditLeafElement *pBegin;
        CEditLeafElement *pEnd;
        MakeSelectionEndPoints( selection, pBegin, pEnd );

        DeleteBetweenElements( pBegin, pEnd );
#endif
    }
    return TRUE;
}


EDT_ClipboardResult CEditBuffer::CopySelection( char **ppText, int32* pTextLen,
                                                char **ppHtml, int32* pHtmlLen )
{
    // Do "strict" testing and tell is if we crossed a cell boundary
    EDT_ClipboardResult result = CanCopy((XP_Bool)TRUE, (XP_Bool)TRUE);
    if ( !(result == EDT_COP_OK || result == EDT_COP_SELECTION_CROSSES_TABLE_DATA_CELL) )
        return result;

    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: FIX THIS: With new Copy accross cell boundaries, this is weird:
            //  there's CR/LF at the end of the table cell,
            //  then a bunch of extra spaces, then text of the 2nd cell 
            *ppText = (char*) LO_GetSelectionText( m_pContext );
        }
        if ( pTextLen && *ppText) *pTextLen = XP_STRLEN( *ppText );
    }

    CEditSelection selection;
    ED_CopyType 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 = ED_COPY_TABLE;
            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

            // Get selection type -- may be different from m_TableHitType
            //   when all cells in a row or col are selected
            ED_HitType iSelectionType = GetTableSelectionType();
            switch( iSelectionType  )
            {
                case ED_HIT_SEL_ROW:
                    iCopyType = ED_COPY_ROWS;
                    break;
                case ED_HIT_SEL_COL:
                    iCopyType = ED_COPY_COLUMNS;
                    break;
                default:
                    iCopyType = ED_COPY_CELLS;
                    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 = ED_COPY_NORMAL;
        
        // Copy character attributes only if start of selection is text
        // This can be "pasted" onto a selection or at the caret
        if( selection.m_start.m_pElement->IsText() )
        {
            if( m_pCopyStyleCharacterData )
                EDT_FreeCharacterData(m_pCopyStyleCharacterData);
            
            // Save attributes of start element only
            m_pCopyStyleCharacterData = selection.m_start.m_pElement->Text()->GetData();
        }
    }
    // Build the HTML stream from the selection
    if( !selection.IsEmpty() )
    {
        if( result == EDT_COP_SELECTION_CROSSES_TABLE_DATA_CELL )
        {
            CopySelectionAcrossCellBoundary(selection, ppHtml, pHtmlLen);
            result = EDT_COP_OK;
        }
        else
            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, 
                                            ED_CopyType 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, ED_CopyType 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 == ED_COPY_NORMAL ) ? !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 > ED_COPY_NORMAL )
    {
        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::CopyBetweenElements( 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)
{
    if( !pText )
        return FALSE;

    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());
    }
}

// This isn't used any more?
void CEditBuffer::PasteEditText(CEditText& text){
    if ( text.Length() > 0 )
        PasteHTML( text.GetChars(), ED_PASTE_NORMAL /*, TRUE*/); //Was bUndoRedo flag
}

// 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, int32 *pTopOfColumn)
{
    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;
    int32 top_of_column = 0;

    if( pLoElement->type == LO_TABLE )
    {
        // Unfortunately, table captions are a pain!
        // Check if we have a caption above the table and adjust the top to compensate
        LO_Element *pCell = pLoElement->lo_any.next;
        // Find first cell
        while( pCell && pCell->type != LO_CELL )
            pCell = pCell->lo_any.next;

        int32 top_limit;
        if( pCell && pCell->lo_cell.isCaption )
        {
            // Set the limit for selecting a column to be the top of the first cell
            LO_Element *pFirstCell = pCell->lo_any.next;
            while( pFirstCell && pFirstCell->type != LO_CELL )
                pFirstCell = pFirstCell->lo_any.next;
            XP_ASSERT(pFirstCell);
            top_limit = pFirstCell->lo_cell.y;

            // This should be just below the caption
            // We want to limit area for selecting a column
            // to between the caption and the top of first cell...
            top_of_column = top + pCell->lo_cell.height;

            // I'm not sure how much space is between bottom of caption 
            //  an the top table border, so calculate relative
            //  to something I am more certain about - the first cell!
            top = top_limit - (pLoElement->lo_table.inter_cell_space + pLoElement->lo_table.border_top_width);

            // Recalculate the bottom
            bottom = top + pLoElement->lo_any.height;

            // Make sure we have enough space to select the column,
            //  even if it encroaches into the caption
            if( top_limit - top_of_column < ED_SIZING_BORDER )
                top_of_column = top_limit - ED_SIZING_BORDER;
        }
        else
            top_limit = top + max(ED_SIZING_BORDER, pLoElement->lo_table.border_top_width);

        *pTopLimit = top_limit;
        *pBottomLimit = bottom - max(ED_SIZING_BORDER, pLoElement->lo_table.border_bottom_width);

        if( *pTopOfColumn )
            *pTopOfColumn = top_of_column;

        // 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);
        
    } 
    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;
    }

    // We use this for cell sizing detection in inter_cell_space
    if( pRight )
        *pRight = right;

    if( pBottom )
        *pBottom = bottom;
}

// 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 && 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
        // IGNORE THE CAPTION ELEMENT!
        if(pCellElement && !pCellElement->lo_cell.isCaption)
        {
            // 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, 0);
                        
            if( x >= right_limit )
            {
                if( !bModifierKeyPressed &&
                    (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
                {
                    // Modifier key pressed and near right edge, excluding corners
                    return ED_HIT_SIZE_COL;
                }
                
                if( y >= bottom_limit )
                    // Lower right corner - leave for sizing object in cell
                    return ED_HIT_NONE;
            }

            // "Quick select cell" strategy:
            //  If modifier key is pressed, anywhere inside cell 
            //  can be used to select the cell.
            //  (Usefull especially in multi-cell mode)
            //  But only allow this if the cell is in the same table 
            //    as current selected cells or there are no cells already selected
            if( bModifierKeyPressed &&
                x <= right && y <= bottom_limit &&
                (m_SelectedLoCells.Size() == 0 ||
                 (LO_Element*)(lo_GetParentTable(m_pContext, (LO_Element*)m_SelectedLoCells[0])) == pTableElement) )
            {
                return ED_HIT_SEL_CELL;
            }
            
            if( x <= left_limit )
            {
                // Leave left edge for placing cursor
                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 && !bModifierKeyPressed )  // Along bottom edge (including intercell region)
            {
                // Size the row
                return ED_HIT_SIZE_ROW;
            }


            // Inside of a cell

            // Can drag the cell or table if inside a selected cell
            if( (pCellElement->lo_cell.ele_attrmask & LO_ELE_SELECTED) ||
                (pTableElement->lo_table.ele_attrmask & LO_ELE_SELECTED) )
            {
                return ED_HIT_DRAG_TABLE;
            }

            return ED_HIT_NONE;
        }

        // No cell selected - return the table element
        if( ppElement) *ppElement = pTableElement;

        // Set test limits for Table element
        int32 top_of_column;
        edt_SetHitLimits(pTableElement, &left_limit, &top_limit, &right_limit, &bottom_limit, 0, 0, &top_of_column);

        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 && !bModifierKeyPressed )
                // 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 )
        {
            // Along right edge - size table
            if( bModifierKeyPressed )
                return ED_HIT_SIZE_TABLE_WIDTH;

            if( y >= bottom_limit && !bModifierKeyPressed )
                // Lower right corner
                return ED_HIT_ADD_COLS;
        }
        else if( y >= bottom_limit && bModifierKeyPressed )
        {
            // Below bottom, excluding corners
            return ED_HIT_SIZE_TABLE_HEIGHT;
        }

        if( y <= top_limit && y > top_of_column)
        {
            // Along top border - select column
            if( ppElement)
            { 
                *ppElement = lo_GetFirstCellInColumnOrRow(m_pContext, pTableElement, x, y, GET_COL, NULL);
            }
            return ED_HIT_SEL_COL;
        }
        // Inside a cell in a selected table
        if( pTableElement->lo_table.ele_attrmask & LO_ELE_SELECTED )
            return ED_HIT_DRAG_TABLE;
    }

    return ED_HIT_NONE;
}

// Note: Don't set any values in m_pDragTableData here,
//       we will compare to previous values in m_pDragTableData
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 ||
           iSourceType == ED_HIT_SEL_TABLE) )
    {
        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=0, iTop=0, iRight=0, iBottom=0;
    int32 left_limit, right_limit, top_limit, bottom_limit, right, bottom;
  
    lo_GetLineEnds(m_pContext, state, line, &pLoElement, &pEndLoElement);

	do {
		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 )
        {
            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;
		    }
        }
        // We're done after processing last element on the line
        if( pLoElement == pEndLoElement )
            break;
		pLoElement = pLoElement->lo_any.next;
	}
    while ( pLoElement != 0 );

    if( pTableElement && pCellElement )
    {
        // Save the cell we are currently dragging over
        if(ppElement)
            *ppElement = pCellElement;

        // Return signal that we shouldn't drop here
        //   if we are exactly over the source selection
        if( pCellElement == m_pDragTableData->pFirstSelectedCell )
            return ED_DROP_NONE;
        
        // Set test limits for Cell element:
        edt_SetHitLimits(pCellElement, &left_limit, &top_limit, &right_limit, &bottom_limit, &right, &bottom, 0);
                    
        *pX = pCellElement->lo_cell.x;
        *pY = pCellElement->lo_cell.y;
        int32 iInterCellSpace = pCellElement->lo_cell.inter_cell_space;
        int32 iExtra = iInterCellSpace - ED_SIZING_BORDER;

        if( iSourceType != ED_HIT_SEL_ROW && (x <= left_limit || x > right_limit /*right*/) )
        {
            if( x >  right_limit /*right*/ )
            {
                LO_Element *pLoCellBefore = pCellElement->lo_any.prev;
                while( pLoCellBefore->type != LO_CELL && pLoCellBefore->type != LO_TABLE )
                    pLoCellBefore = pLoCellBefore->lo_any.prev;

                 //   pLoCellBefore->lo_cell.x >= pCellElement->lo_cell.x) )
                // Save cell before only if it is first cell of selection being dragged
                // Can't drop just after itself.
                // Note that we can drop just after if vertical position is different,
                //   effect is to move cells up/down within the column 
                if( pLoCellBefore && pLoCellBefore->type == LO_CELL && 
                    pLoCellBefore == m_pDragTableData->pFirstSelectedCell )
                {
                    return ED_DROP_NONE;        
                }
        
                iDropType = ED_DROP_COLUMN_AFTER;
                *pX += pCellElement->lo_cell.width;
                if( iExtra > 1 )
                    *pX += iExtra/2;
            } else {
                // Can't drop just before the cell to the right
                LO_Element *pLoCellAfterFirstSelected  = m_pDragTableData->pFirstSelectedCell->lo_any.next;
                while( pLoCellAfterFirstSelected && pLoCellAfterFirstSelected->type != LO_CELL )
                    pLoCellAfterFirstSelected = pLoCellAfterFirstSelected->lo_any.next;
                
                if( pLoCellAfterFirstSelected && pLoCellAfterFirstSelected->type == LO_CELL &&
                    pLoCellAfterFirstSelected == m_pDragTableData->pDragOverCell )
                {
                    return ED_DROP_NONE;        
                }

                iDropType = ED_DROP_COLUMN_BEFORE;
                if( iExtra > 1 )
                    *pX -= (iExtra/2 + ED_SIZING_BORDER);
                else
                    *pX -= ED_SIZING_BORDER;
            }
            // Insert Column: Calculate dimensions for a caret that is as high as cells to be inserted near
            // 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;
            // Number of rows to use to get height of column to insert
            int32 iRowsLeft =  m_pDragTableData->iRows - lo_GetRowSpan(pCellElement);
            // Figure out height from number of rows we will insert
            if( iRowsLeft > 0 )
            {
                // Get next cell in the column (cell may span across the x value)
                LO_Element *pNextCell = lo_GetNextCellInColumnOrRow(m_pContext, pCellElement->lo_cell.x, 0, pCellElement, TRUE);
                if( pNextCell )
                {
                    *pHeight += (pNextCell->lo_cell.height + iInterCellSpace);
                    iRowsLeft -= lo_GetRowSpan(pNextCell);
                    while( iRowsLeft > 0 )
                    {
                        // Use the same X value supplied during first call
                        pNextCell = lo_GetNextCellInColumnOrRow(m_pContext, 0, 0, pNextCell, TRUE);
                        if(pNextCell)
                        {
                            *pHeight += (pNextCell->lo_cell.height + iInterCellSpace);
                            iRowsLeft -= lo_GetRowSpan(pNextCell);
                        }
                        else
                            break;
                    }
                }                
            }
        }
        if( iDropType == ED_DROP_NORMAL && iSourceType != ED_HIT_SEL_COL && (y <= top_limit || y > bottom) )
        {
            if( y > bottom )
            {
                iDropType = ED_DROP_ROW_BELOW;
                *pY += pCellElement->lo_cell.height;
                if( iExtra > 1 )
                    *pY += iExtra/2;
            } else {
                iDropType = ED_DROP_ROW_ABOVE;
                if( iExtra > 1 )
                    *pY -= (iExtra/2 + ED_SIZING_BORDER);
                else
                    *pY -= ED_SIZING_BORDER;
            }

            // Insert Row: Calculate dimensions for a caret that is as wide as cells to be inserted near
            *pHeight = ED_SIZING_BORDER;;
            *pWidth = pCellElement->lo_cell.width;
            int32 iColsLeft =  m_pDragTableData->iColumns - lo_GetColSpan(pCellElement);
            // Figure out width from number of columns we will insert
            if( iColsLeft > 0 )
            {
                LO_Element *pNextCell = lo_GetNextCellInColumnOrRow(m_pContext, 0, pCellElement->lo_cell.y, pCellElement, FALSE);
                if( pNextCell )
                {
                    *pWidth += (pNextCell->lo_cell.width + iInterCellSpace);
                    iColsLeft -= lo_GetColSpan(pNextCell);
                    while( iColsLeft > 0 )
                    {
                        pNextCell = lo_GetNextCellInColumnOrRow(m_pContext, 0, 0, pNextCell, FALSE);
                        if( pNextCell )
                        {
                            *pWidth += (pNextCell->lo_cell.width + iInterCellSpace);
                            iColsLeft -= lo_GetColSpan(pNextCell);
                        }
                        else
                            break;
                    }
                }
            }
        }
        if( iDropType == ED_DROP_NORMAL )
        {
            // Inside of a cell and no hit regions
            iDropType = ED_DROP_REPLACE_CELLS;
            // (These aren't really used for drawing feedback)
            *pWidth = 0; //pCellElement->lo_cell.width;
            *pHeight = 0; //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,
            // but not in the caption!
            SetTableInsertPoint(pEdTable->GetFirstCell());

            // 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
            // Also: Don't try to figure out if extending cell selection
            //   now results in a full row or column (via GetTableSelectionType())
            //   because that makes extending selections to complicated
            // Do only when needed, such as GetTableCellData() and when copying and dragNdrop
            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( bSelRow || bSelCol )
    {
        // 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=0, iRowTop=0;
        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;

        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
            // Note: Don't use GetTableSelectionType() here
            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;
            }

            // Be sure selected cell array is in same order
            //   as cells in table
            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 + pFirstCell->width, pLastCell->x + pLastCell->width);
        int32 yMax = max(pFirstCell->y + pFirstCell->height, pLastCell->y + pLastCell->height);
    
        // Scan all cells in table and select those within our range
        LO_Element *pLastCell;
        LO_Element *pElement = lo_GetFirstAndLastCellsInTable(m_pContext, m_pSelectedTableElement, &pLastCell);
        while( pElement )
        {
            if( pElement->type == LO_CELL )
            {
                LO_CellStruct *pLoCell = (LO_CellStruct*)pElement;

                if( pLoCell->x >= xMin && (pLoCell->x + pLoCell->width) <= xMax &&
                    pLoCell->y >= yMin && (pLoCell->y + pLoCell->height) <= yMax )
                {
                    // Select the cell
                    SelectCell(TRUE, pLoCell);
                }
            }
            // We're done when we hit last cell
            if( pElement == pLastCell )
                break;
            pElement = pElement->lo_any.next;
        }
    }
    else 
    {
        // No previous selected cell - select just given cell
        SelectCell(TRUE, pLastCell, NULL);
    }
}

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;
    }
    // Note: Don't call GetTableSelectionType() here,
    //  it complicates things to much.
    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* edt_GetTableElementFromLO_Element(LO_Element *pLoElement, int16 LoElementType)
{
    if( pLoElement &&
        (LoElementType == LO_TABLE ||
         LoElementType == LO_CELL) )
    {
        LO_Element *pTemp = pLoElement;
#if 0
// This code is much more efficient but works ONLY if 
//   we store the CEditElement pointer in the corresponding
//    LO_Element->lo_any.edit_element during table layout
        if( LoElementType == LO_TABLE )
        {
            // We want the table pointer
            if( pLoElement->type == LO_TABLE &&
                pLoElement->lo_any.edit_element )
            {
                return pLoElement->lo_any.edit_element;
            }
            else if( pLoElement->type == LO_CELL )
            {
                // We are a cell but want the parent table,
                //  so backup to find it
                while( pTemp->type != LO_TABLE )
                    pTemp = pTemp->lo_any.prev;
                if( pTemp->lo_any.edit_element )
                    return pTemp->lo_any.edit_element;
            }
        }
        else if( LoElementType == LO_CELL &&
                 pLoElement->type == LO_CELL &&
                 pLoElement->lo_any.edit_element )
        {
            // We are a cell and want the edit cell element
            return pLoElement->lo_any.edit_element;
        }
        // Fall through with "leaf searching" strategy 
        //   if LO_Element passed in is not a table or cell
#endif
        // 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;
}

void CEditBuffer::SelectTable()
{
    CEditTableElement* pTable = m_pCurrent->GetTableIgnoreSubdoc();
    if( pTable )
        SelectTable(TRUE, 0, pTable);
}

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 )
    {
        if( !pEdTable || (pLoTable = pEdTable->GetLoTable()) == NULL )
        {
            XP_ASSERT(FALSE);
            return NULL;
        }
    }
    // Assume we are deselecting current table if no pointers supplied or
    //   edit table is the selected table
    if( !bSelect && !pLoTable && (!pEdTable || pEdTable == m_pSelectedEdTable) )
    {
        pLoTable = m_pSelectedLoTable;
        pEdTable = m_pSelectedEdTable;
    }
    if( !pLoTable )
        return NULL;

    XP_Bool bWasSelected = (pLoTable->ele_attrmask & LO_ELE_SELECTED) ? TRUE : 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*)edt_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;
        m_pSelectedLoTable = NULL;
        m_pSelectedEdTable = NULL;
    }        

    // 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 = (pLoCell->ele_attrmask & LO_ELE_SELECTED) ? TRUE : FALSE;

    // Get corresponding Edit cell if not supplied
    if( !pEdCell )
        pEdCell = (CEditTableCellElement*)edt_GetTableElementFromLO_Element( (LO_Element*)pLoCell, LO_CELL );

    if(bSelect && !pEdCell)
        return NULL;


    pEdCell->SetSelected(bSelect);

    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 = (pLoCell->ele_attrmask & LO_ELE_SELECTED_SPECIAL) ? TRUE : FALSE;

        pEdCell->SetSpecialSelected(TRUE);

        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;
            pEdCell->SetSpecialSelected(FALSE);
        }
        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();
    }
}

// Select current cell and change other currently-selected cells
//  to LO_ELE_SELECTED_SPECIAL so user can tell difference between
//  the "focus" cell (current, where caret is) and other selected cells
//  Call just before Table Cell properties dialog is created
// Supply pCellData to update iSelectionType and iSelectedCount values
void CEditBuffer::StartSpecialCellSelection(EDT_TableCellData *pCellData)
{
    CEditTableCellElement* pEdCell = m_pCurrent->GetTableCellIgnoreSubdoc();
    if( pEdCell )
        DisplaySpecialCellSelection( pEdCell, pCellData );
}

void CEditBuffer::SetReplaceCellSelection()
{
    if( !m_pDragTableData || !m_pDragTableData->pDragOverCell || m_pDragTableData->iRows <= 0 || !m_pDragTableData->pCellsPerRow )
        return;
    
    XP_ASSERT(m_pDragTableData->pDragOverCell->type == LO_CELL);
    if(!m_pDragTableData->pDragOverCell->type == LO_CELL)
        return;

    // Do nothing if we haven't changed from the last cell we were over
    if( m_pDragTableData->pDragOverCell == edt_pPrevReplaceCellSelected )
        return;

    // Clear all existing special selection (this nulls pLastReplaceCellSelected)
    // (Must supply the cell to identify what table to clear)
    ClearSpecialCellSelection(m_pDragTableData->pDragOverCell);
    // Reset replace cell
    edt_pPrevReplaceCellSelected = m_pDragTableData->pDragOverCell;

    LO_TableStruct *pSourceTable = lo_GetParentTable(m_pContext, m_pDragTableData->pFirstSelectedCell);
    LO_TableStruct *pDragOverTable = lo_GetParentTable(m_pContext, m_pDragTableData->pDragOverCell);
    if( pSourceTable && pSourceTable != pDragOverTable &&
        (m_pDragTableData->pDragOverCell->lo_cell.ele_attrmask & LO_ELE_SELECTED) != 0 )
    {
        // We are hovering over selected cells in a different table
        // Set all selected cells to the "special selection" mode
        //  so they will be the ones replaced
        DisplaySpecialCellSelection();
        return;
    }

    LO_Element *pLoEle = m_pDragTableData->pDragOverCell;
    // This will get updated by  edt_SetSpecialSelectRow for each row
    int32 iRowY = pLoEle->lo_cell.y;
    int32 iStartColX = pLoEle->lo_cell.x;

    for( intn iRow = 0; iRow < m_pDragTableData->iRows; iRow++ )
    {
        // Most of the code is shared with section in PasteTable() for replace cell logic
        if( !edt_SetSpecialSelectRow(m_pContext, pLoEle, m_pDragTableData->pCellsPerRow[iRow], 
                                     iStartColX, iRowY) )
            break;
    }
}

void CEditBuffer::ClearSpecialCellSelection(LO_Element *pDragOverCell)
{
    if( pDragOverCell )
    {
        // Scan through all cells in table and clear those with special selection
        LO_Element *pLastCell;
        LO_Element *pLoCell = lo_GetFirstAndLastCellsInTable(m_pContext, pDragOverCell, &pLastCell);
        if( !pLoCell || !pLastCell )
            return;

        while (TRUE)
        {
            if( pLoCell && pLoCell->type == LO_CELL )
                edt_ClearSpecialCellSelection( m_pContext, (LO_CellStruct*)pLoCell, NULL );

            if( !pLoCell || pLoCell == pLastCell )
                break;

            pLoCell= pLoCell->lo_any.next;
        }
    } 
    else 
    {
        // Performance optimization: If no cell is supplied, assume we are clearing 
        //  just cells that have "normal" selection as well
        for( intn i = 0; i < m_SelectedLoCells.Size(); i++ )
            edt_ClearSpecialCellSelection( m_pContext, m_SelectedLoCells[i], m_SelectedEdCells[i] );
    }
    edt_pPrevReplaceCellSelected = NULL;
}

// 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*)edt_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;

    // If we have leftover special selection, clear it
    if( edt_pPrevReplaceCellSelected )
        ClearSpecialCellSelection(edt_pPrevReplaceCellSelected);

    int iEdSize = m_SelectedEdCells.Size();
    int iLoSize = m_SelectedLoCells.Size();
    if( m_pSelectedEdTable == 0 &&
        iEdSize == 0 )
    {
        XP_ASSERT(iLoSize == 0);
        // Nothing was selected
        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);
}

#if 0   
// NOT USED???
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;
        }
    }
}
#endif

// 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::CleanupForDeletedElement(CEditElement *pElement)
{
    if( pElement )
    {
        // Clear the saved pointer for an element we want
        //  to monitor for deletion
        if(pElement == m_pWatchForDeletionElement)
            m_pWatchForDeletionElement = 0;
        
        // Depend on member flag being set correctly
        if( pElement->IsSelected() )
        {
            if( pElement->IsTable() )
            {
                SelectTable(FALSE, (LO_TableStruct*)NULL, (CEditTableElement*)pElement);
                return;
            }
            else if( pElement->IsTableCell() )
            {
                SelectCell(FALSE, (LO_CellStruct*)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, XP_Bool bModifierKeyPressed)
{
    // 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, bModifierKeyPressed);
        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 bModifierKeyPressed, XP_Rect *pRect){
    XP_ASSERT(pRect);

    if( m_pSizingObject ){
        // Improbable, but what the heck
        delete m_pSizingObject;
    }
    // Unselect table or cell(s)
    ClearTableAndCellSelection();

    // We are using the "ModifierKeyPressed" (last param)
    //   for 2 purposes: 1. To unlock the aspect ratio for non-table objects,
    //                   2. To size table instead of last column
    int iStyle = CanSizeObject(pLoElement, xVal, yVal, bModifierKeyPressed);

    if( iStyle ){
        m_pSizingObject = new CSizingObject();
        if( m_pSizingObject ){
            if( m_pSizingObject->Create(this, pLoElement, iStyle,
                                         xVal, yVal, bModifierKeyPressed, pRect) ){
                return iStyle;
            } else {
                delete m_pSizingObject;
                m_pSizingObject = NULL;
            } 
        }
    }
    return 0;
}

XP_Bool CEditBuffer::GetSizingRect(int32 xVal, int32 yVal,
                                   XP_Bool bModifierKeyPressed, XP_Rect *pRect){
    XP_ASSERT(pRect);
    if( m_pSizingObject && pRect ){
        return m_pSizingObject->GetSizingRect(xVal, yVal, bModifierKeyPressed, 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. */

#ifdef FORMATING_FIXED
    CEditSelection selection;
    EDT_CharacterData  *formatting;  /* This will hold the formatting of the selected text */
	int i;

    /* 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 */ )
{
    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; 
    int32       iInsertPointOffset = 0;
    CL_Layer*   layer;/* this will be ignored */
    CEditLeafElement* origEditElement = 0;//used for replace all to move insertion point back to the proper location
    CEditLeafElement* origBeforeElement = 0;
    XP_Bool bFirstFind = TRUE;
    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 )
    {
        // Save element at current insert point (m_pCurrent = 0 if there's a selection)
        CEditInsertPoint ip;
        GetInsertPoint(ip);
        origEditElement = ip.m_pElement; 
        if( origEditElement )
        {
            // Also save the element before in case origEditElement is deleted
            //  during replacing
            origBeforeElement = origEditElement->PreviousLeaf();
            // Set this element to be monitored for deletion
            m_pWatchForDeletionElement = origEditElement;
            iInsertPointOffset = ip.m_iPos;
        }

        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!
            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.
    {
        m_bLayoutBackpointersDirty = TRUE;//trust me
        Reduce(this->m_pRoot->GetFirstMostChild());//we must finish what we have begun...

        // Check if original element at insert point was deleted
        if( m_pWatchForDeletionElement == 0 )
        {
            //  Get the new replaced element based on the 
            //  saved "previous" leaf
            // (Note: the replaced text may end up appended to origBeforeElement,
            //  so this will position new insert point AFTER that text.
            //  Nothing we can do about that - its close enough to where we started.)
            if( origBeforeElement )
                origEditElement = origBeforeElement->NextLeaf();
            else
                // This happens when first element in page was replaced,
                //   so just repostion back to the document start
                origEditElement = m_pRoot->GetFirstMostChild()->Leaf();

            // Assume offset at start of element
            iInsertPointOffset = 0;
        } 
        else
            m_pWatchForDeletionElement = 0;

        SetInsertPoint(origEditElement, iInsertPointOffset, FALSE);

        // (Note: Reduce call was here - moved above in case it deletes anything)
        // relayout the stream
	    Relayout(m_pRoot->GetFirstMostChild(), 0, m_pRoot->GetLastMostChild());
    }
    EndBatchChanges();
}




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 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.
    SetEncodingTag(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);
    }
}

XP_Bool CEditBuffer::SetEncoding(int16 csid)
{
    ED_CharsetEncode result = FE_EncodingDialog(m_pContext, (char*)INTL_CsidToCharsetNamePt(csid));

    switch (result)
    {
        case ED_ENCODE_CHANGE_CHARSET:
            // Change encoding and translate document
            ChangeEncoding(csid);
            return TRUE;

        case ED_ENCODE_CHANGE_METATAG:
        {
            // Set charset param in Content-Type metatag, but don't translate document
            SetEncodingTag(csid);
        	INTL_CharSetInfo csi = LO_GetDocumentCharacterSetInfo(m_pContext);
            INTL_SetCSIWinCSID(csi, csid);
            // This will be used to replace the charset in the metatag
            //  after the reload
            m_iReplaceCSID = csid;
            INTL_Relayout(m_pContext);
            return TRUE;
        }

        case ED_ENCODE_CANCEL:
            return FALSE;

        default:
            XP_ASSERT(0);
            return FALSE;
    }
    return FALSE;       // shouldn't get here
}

// 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::SetEncodingTag(int16 csid)
{
    int16 plainCSID = csid & ~CS_AUTO;
    // This is better than INTL_CharSetIDToName, which needs presized buffer;
    // (Don't free this string!)
    char *charSet = (char *)INTL_CsidToCharsetNamePt(plainCSID);
    SetEncodingTag(charSet);
}

void CEditBuffer::SetEncodingTag(char *pCharset)
{
 	char pContent[128];
    if( pCharset && *pCharset )
    {
        XP_SPRINTF(pContent, "text/html; charset=%.100s", pCharset);

        EDT_MetaData *pData = MakeMetaData( TRUE, CONTENT_TYPE, pContent);
        if( pData )
        {
            SetMetaData( pData );
            FreeMetaData( pData );
        }
        else
            XP_ASSERT(0);
    }
}

XP_Bool CEditBuffer::HasEncoding() {
    return FindContentTypeIndex() >= 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 );
            }
        }
    }
    
}

#if defined(ENDER) && defined(MOZ_ENDER_MIME)

void CEditBuffer::AddImagesToSafeList(CEditElement *pElement)
{
    CEditElement* pChild;

    if ( !m_bEmbedded )
        return;

    while ( pElement )
    {
        if (pElement->IsImage())
        {
            CEditImageElement *pImageElement = (CEditImageElement *)pElement;
            EDT_ImageData *pData = pImageElement->GetImageData();
            if (pData)
            {
                if (pData->pSrc)
                    EDT_AddURLToSafeList(m_pEmbeddedData, pData->pSrc);
                edt_FreeImageData(pData);
            }
        }

        if ( (pChild = pElement->GetChild()) )
            this->AddImagesToSafeList(pChild);

        pElement = pElement->GetNextSibling();
    }
}

#endif /* ENDER && MOZ_ENDER_MIME */

XP_Bool EDT_ScrollToTarget(MWContext *pMWContext, char *pTargetURL)
{
    if( !pTargetURL || !*pTargetURL )
        return FALSE;
    GET_WRITABLE_EDIT_BUF_OR_RETURN(pMWContext, pEditBuffer) FALSE;
    
    // We are only interested in relative URLs to internal targets
    char *pTarget = XP_STRCHR(pTargetURL, '#');
    if( !pTarget )
        return FALSE;

    // If target is preceeded by other stuff,
    //  be sure its to the current page    
    if( pTarget != pTargetURL )
    {
        char *pCurrentURL = LO_GetBaseURL(pMWContext);
        char *pAbsolute = NET_MakeAbsoluteURL(pTargetURL, pCurrentURL);
        if( !pAbsolute )
            return FALSE;

        if( !EDT_IsSameURL(pAbsolute, pCurrentURL, 0, 0) )
        {
            XP_FREE(pAbsolute);
            return FALSE;    
        }
        XP_FREE(pAbsolute);
    }
    // Point to first character in target name
    pTarget++;

    XP_Bool       bResult = FALSE;
    CEditElement *pNext = pEditBuffer->m_pRoot;
    pEditBuffer->ClearSelection();
    
    // Find the matching target element
    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindTarget, 0 )) )
    {
        char *pName = pNext->Target()->GetName();
        if( pName && 0 == XP_STRCMP(pName, pTarget) )
        {
            CEditElement *pElement = pNext->NextLeaf();
            if( pElement )
            {
                CEditInsertPoint ip(pElement, 0);
                pEditBuffer->SetInsertPoint(ip);
                bResult = TRUE;
            }
        }
    }
    return bResult;
}

// Tells if URL is to an internal Target tag 
XP_Bool EDT_IsInternalLink(MWContext *pMWContext, char *pURL)
{
    if( !pMWContext || !pURL )
        return FALSE;
    
    GET_WRITABLE_EDIT_BUF_OR_RETURN(pMWContext, pEditBuffer) FALSE;
    XP_Bool bReturn = FALSE;
    
    // We are only interested in relative URLs to internal targets
    char *pTarget = XP_STRCHR(pURL, '#');
    if( pTarget )
    {
        char *pCurrentURL = LO_GetBaseURL(pMWContext);
        char *pAbsolute = NET_MakeAbsoluteURL(pURL, pCurrentURL);
        // If memory error, act as if the link is internal
        if( pAbsolute )
        {
            bReturn = EDT_IsSameURL(pAbsolute, pCurrentURL, 0, 0);
            XP_FREE(pAbsolute);
        }
        else
        {
            bReturn = TRUE;
        }
    }
    return bReturn;
}

char * EDT_GetTargetNameFromIcon(LO_ImageStruct *pIcon)
{
    CEditElement * pEdElement = 0;
    char *pTargetMsg = 0;
    if( pIcon && pIcon->is_icon && (pEdElement = pIcon->edit_element) != 0 
        && pEdElement->GetElementType() == eTargetElement )
    {
        pTargetMsg = XP_STRDUP(XP_GetString(XP_EDT_TARGET_NAME));
        char *pTargetName = ((CEditTargetElement*)pEdElement)->GetName();
        if( pTargetName )
            pTargetMsg = PR_sprintf_append(pTargetMsg, pTargetName);
        else
            XP_FREEIF(pTargetMsg);
    }
    return pTargetMsg;
}


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

#endif
