/* -*- 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   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 ( "" ), we switch it to * P_STRIKEOUT ( "" ). 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 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 s and other tags that appear // after the . 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. vs. . // So you can say, for example, // foobarbaz and it will work as if you had said: // foobarbaz. // LTNOTE: This crashes sometimes?? //delete GetParseState()->m_pNextText; GetParseState()->m_pNextText = 0; GetParseState()->m_formatTypeStack.Pop(); GetParseState()->m_pNextText = GetParseState()->m_formatTextStack.Pop(); // pops all the HREFs. So if you say: // foobarbaz, it's as if you said: // foobarbaz // For fun, try this: // foobarbaz // 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( "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 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
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 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 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
after . 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 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

in * CEditContainerElement::AdjustContainers(). * * - kin */ #if 0 // A table following a

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 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 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 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
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
and
, // 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: // // 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 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 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
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( " bHttpEquiv ? "HTTP-EQUIV" : "NAME"), edt_MakeParamString( pData->pName ) ); // This changes internal quotes into ", 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 " 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("\n"); pPrintState->m_pOut->Printf("\n"); if ( bNeedHead ) { pPrintState->m_pOut->Printf("\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(" "); if ( m_pTitle && *m_pTitle ) edt_PrintWithEscapes( pPrintState, m_pTitle, FALSE ); pPrintState->m_pOut->Printf("\n"); } // Print BASE TARGET if nescessary if ( bHaveBaseTarget ) { pPrintState->m_pOut->Printf(" \n", edt_MakeParamString( m_pBaseTarget )); } // Print the font refs if nescessary for( int i = 0; i < iFontDefCount; i++ ) { pPrintState->m_pOut->Printf(" m_pOut->Printf(" NOSAVE"); pPrintState->m_pOut->Printf(">\n"); } if( bHaveHeadTags ) Write(pParseState->m_pJavaScript, pPrintState->m_pOut); pPrintState->m_pOut->Printf("\n"); } if ( IsBodyTagRequired() ) { // print the contents of the body tag. pPrintState->m_pOut->Printf("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" "\n"); } if( GetParseState()->m_pPostBody ){ Write(GetParseState()->m_pPostBody, pPrintState->m_pOut); } pPrintState->m_pOut->Printf("\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: // & for ampersand // < 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 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_DisplayCaret 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 // 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