/* AbiWord * Copyright (C) 1998 AbiSource, Inc. * Copyright (C) 2001,2002 Tomas Frydrych * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ // deleteSpan-related routines for class pt_PieceTable #include "ut_types.h" #include "ut_misc.h" #include "ut_assert.h" #include "ut_debugmsg.h" #include "ut_growbuf.h" #include "ut_stack.h" #include "pt_PieceTable.h" #include "pf_Frag.h" #include "pf_Frag_FmtMark.h" #include "pf_Frag_Object.h" #include "pf_Frag_Strux.h" #include "pf_Frag_Strux_Block.h" #include "pf_Frag_Strux_Section.h" #include "pf_Frag_Text.h" #include "pf_Fragments.h" #include "px_ChangeRecord.h" #include "px_CR_Span.h" #include "px_CR_SpanChange.h" #include "px_CR_Strux.h" #define SETP(p,v) do { if (p) (*(p)) = (v); } while (0) /****************************************************************/ /****************************************************************/ bool pt_PieceTable::_deleteSpan(pf_Frag_Text * pft, UT_uint32 fragOffset, PT_BufIndex bi, UT_uint32 length, pf_Frag ** ppfEnd, UT_uint32 * pfragOffsetEnd) { // perform simple delete of a span of text. // we assume that it is completely contained within this fragment. UT_ASSERT(fragOffset+length <= pft->getLength()); SETP(ppfEnd, pft); SETP(pfragOffsetEnd, fragOffset); if (fragOffset == 0) { // the change is at the beginning of the fragment, if (length == pft->getLength()) { // the change exactly matches the fragment, just delete the fragment. // as we delete it, see if the fragments around it can be coalesced. _unlinkFrag(pft,ppfEnd,pfragOffsetEnd); delete pft; return true; } // the change is a proper prefix within the fragment, // do a left-truncate on it. pft->adjustOffsetLength(m_varset.getBufIndex(bi,length),pft->getLength()-length); return true; } if (fragOffset+length == pft->getLength()) { // the change is a proper suffix within the fragment, // do a right-truncate on it. pft->changeLength(fragOffset); SETP(ppfEnd, pft->getNext()); SETP(pfragOffsetEnd, 0); return true; } // otherwise, the change is in the middle of the fragment. // we right-truncate the current fragment at the deletion // point and create a new fragment for the tail piece // beyond the end of the deletion. UT_uint32 startTail = fragOffset + length; UT_uint32 lenTail = pft->getLength() - startTail; PT_BufIndex biTail = m_varset.getBufIndex(pft->getBufIndex(),startTail); pf_Frag_Text * pftTail = new pf_Frag_Text(this,biTail,lenTail,pft->getIndexAP(),pft->getField()); UT_ASSERT(pftTail); pft->changeLength(fragOffset); m_fragments.insertFrag(pft,pftTail); SETP(ppfEnd, pftTail); SETP(pfragOffsetEnd, 0); return true; } bool pt_PieceTable::_deleteSpanWithNotify(PT_DocPosition dpos, pf_Frag_Text * pft, UT_uint32 fragOffset, UT_uint32 length, pf_Frag_Strux * pfs, pf_Frag ** ppfEnd, UT_uint32 * pfragOffsetEnd) { // create a change record for this change and put it in the history. UT_ASSERT(pfs); if (length == 0) // TODO decide if this is an error. { UT_DEBUGMSG(("_deleteSpanWithNotify: length==0\n")); SETP(ppfEnd, pft->getNext()); SETP(pfragOffsetEnd, 0); return true; } // we do this before the actual change because various fields that // we need are blown away during the delete. we then notify all // listeners of the change. PT_BlockOffset blockOffset = _computeBlockOffset(pfs,pft) + fragOffset; PX_ChangeRecord_Span * pcr = new PX_ChangeRecord_Span(PX_ChangeRecord::PXT_DeleteSpan, dpos, pft->getIndexAP(), m_varset.getBufIndex(pft->getBufIndex(),fragOffset), length,blockOffset,pft->getField()); UT_ASSERT(pcr); bool bResult = _deleteSpan(pft,fragOffset,pft->getBufIndex(),length,ppfEnd,pfragOffsetEnd); if (_canCoalesceDeleteSpan(pcr)) { m_history.coalesceHistory(pcr); m_pDocument->notifyListeners(pfs,pcr); delete pcr; } else { m_history.addChangeRecord(pcr); m_pDocument->notifyListeners(pfs,pcr); } return bResult; } bool pt_PieceTable::_deleteSpan_norec(PT_DocPosition dpos, pf_Frag_Text * pft, UT_uint32 fragOffset, UT_uint32 length, pf_Frag_Strux * pfs, pf_Frag ** ppfEnd, UT_uint32 * pfragOffsetEnd) { // create a change record for this change and put it in the history. UT_ASSERT(pfs); if (length == 0) // TODO decide if this is an error. { UT_DEBUGMSG(("_deleteSpanWithNotify: length==0\n")); SETP(ppfEnd, pft->getNext()); SETP(pfragOffsetEnd, 0); return true; } // we do this before the actual change because various fields that // we need are blown away during the delete. we then notify all // listeners of the change. PT_BlockOffset blockOffset = _computeBlockOffset(pfs,pft) + fragOffset; PX_ChangeRecord_Span * pcr = new PX_ChangeRecord_Span(PX_ChangeRecord::PXT_DeleteSpan, dpos, pft->getIndexAP(), m_varset.getBufIndex(pft->getBufIndex(),fragOffset), length,blockOffset,pft->getField()); UT_ASSERT(pcr); bool bResult = _deleteSpan(pft,fragOffset,pft->getBufIndex(),length,ppfEnd,pfragOffsetEnd); m_pDocument->notifyListeners(pfs,pcr); delete pcr; return bResult; } bool pt_PieceTable::_canCoalesceDeleteSpan(PX_ChangeRecord_Span * pcrSpan) const { // see if this record can be coalesced with the most recent undo record. UT_ASSERT(pcrSpan->getType() == PX_ChangeRecord::PXT_DeleteSpan); PX_ChangeRecord * pcrUndo; if (!m_history.getUndo(&pcrUndo)) return false; if (pcrSpan->getType() != pcrUndo->getType()) return false; if (pcrSpan->getIndexAP() != pcrUndo->getIndexAP()) return false; PX_ChangeRecord_Span * pcrUndoSpan = static_cast(pcrUndo); UT_uint32 lengthUndo = pcrUndoSpan->getLength(); PT_BufIndex biUndo = pcrUndoSpan->getBufIndex(); UT_uint32 lengthSpan = pcrSpan->getLength(); PT_BufIndex biSpan = pcrSpan->getBufIndex(); if (pcrSpan->getPosition() == pcrUndo->getPosition()) { if (m_varset.getBufIndex(biUndo,lengthUndo) == biSpan) return true; // a forward delete return false; } else if ((pcrSpan->getPosition() + lengthSpan) == pcrUndo->getPosition()) { if (m_varset.getBufIndex(biSpan,lengthSpan) == biUndo) return true; // a backward delete return false; } else { return false; } } bool pt_PieceTable::_isSimpleDeleteSpan(PT_DocPosition dpos1, PT_DocPosition dpos2) const { // see if the amount of text to be deleted is completely // contained within the fragment found. if so, we have // a simple delete. otherwise, we need to set up a multi-step // delete -- it may not actually take more than one step, but // it is too complicated to tell at this point, so we assume // it will and don't worry about it. // // we are in a simple change if the beginning and end are // within the same fragment. pf_Frag * pf_First; pf_Frag * pf_End; PT_BlockOffset fragOffset_First; PT_BlockOffset fragOffset_End; bool bFound = getFragsFromPositions(dpos1,dpos2,&pf_First,&fragOffset_First,&pf_End,&fragOffset_End); UT_ASSERT(bFound); if ((fragOffset_End==0) && pf_End->getPrev() && (pf_End->getPrev()->getType() == pf_Frag::PFT_Text)) { pf_End = pf_End->getPrev(); fragOffset_End = pf_End->getLength(); } return (pf_First == pf_End); } bool pt_PieceTable::_tweakDeleteSpanOnce(PT_DocPosition & dpos1, PT_DocPosition & dpos2, UT_Stack * pstDelayStruxDelete) const { // Our job is to adjust the end positions of the delete // operating to delete those structural object that the // user will expect to have deleted, even if the dpositions // aren't quite right to encompass those. pf_Frag * pf_First; pf_Frag * pf_End; pf_Frag * pf_Other; PT_BlockOffset fragOffset_First; PT_BlockOffset fragOffset_End; bool bFound = getFragsFromPositions(dpos1,dpos2,&pf_First,&fragOffset_First,&pf_End,&fragOffset_End); UT_ASSERT(bFound); pf_Frag_Strux * pfsContainer = NULL; bool bFoundStrux = _getStruxFromPosition(dpos1,&pfsContainer); UT_ASSERT(bFoundStrux); _tweakFieldSpan(dpos1,dpos2); switch (pfsContainer->getStruxType()) { default: UT_ASSERT(0); return false; case PTX_Section: #if 0 // if the previous container is a section, then pf_First // must be the first block in the section. UT_ASSERT((pf_First->getPrev() == pfsContainer)); UT_ASSERT((pf_First->getType() == pf_Frag::PFT_Strux)); UT_ASSERT(((static_cast(pf_First))->getStruxType() == PTX_Block)); // since, we cannot delete the first block in a section, we // secretly translate this into a request to delete the section; // the block we have will then be slurped into the previous // section. dpos1 -= pfsContainer->getLength(); return true; #endif case PTX_SectionEndnote: case PTX_SectionHdrFtr: // if the previous container is a Header/Footersection, then pf_First // must be the first block in the section. UT_ASSERT((pf_First->getPrev() == pfsContainer)); UT_ASSERT((pf_First->getType() == pf_Frag::PFT_Strux)); UT_ASSERT(((static_cast(pf_First))->getStruxType() == PTX_Block)); // since, we cannot delete the first block in a section, we // secretly translate this into a request to delete the section; // the block we have will then be slurped into the previous // section. dpos1 -= pfsContainer->getLength(); return true; case PTX_Block: // if the previous container is a block, we're ok. // the loop below will take care of everything. break; } if (pf_First->getType() == pf_Frag::PFT_Strux) { switch(static_cast(pf_First)->getStruxType()) { default: break; case PTX_Section: UT_ASSERT(fragOffset_First == 0); if (dpos2 == dpos1 + pf_First->getLength()) { // If we are just deleting a section break, then // we should delete the first block marker in the // next section, combining the blocks before and // after the section break. pf_Other = pf_First->getNext(); UT_ASSERT(pf_Other); UT_ASSERT(pf_Other->getType() == pf_Frag::PFT_Strux); UT_ASSERT(((static_cast(pf_Other))->getStruxType() == PTX_Block)); dpos2 += pf_Other->getLength(); return true; } case PTX_SectionHdrFtr: UT_ASSERT(fragOffset_First == 0); if (dpos2 == dpos1 + pf_First->getLength()) { // If we are just deleting a section break, then // we should delete the first block marker in the // next section, combining the blocks before and // after the section break. pf_Other = pf_First->getNext(); UT_ASSERT(pf_Other); UT_ASSERT(pf_Other->getType() == pf_Frag::PFT_Strux); UT_ASSERT(((static_cast(pf_Other))->getStruxType() == PTX_Block)); dpos2 += pf_Other->getLength(); return true; } break; } } if (fragOffset_First == 0 && fragOffset_End == 0 && pf_First != pf_End) { pf_Frag * pf_Before = pf_First->getPrev(); while (pf_Before && pf_Before->getType() == pf_Frag::PFT_FmtMark) pf_Before = pf_Before->getPrev(); pf_Frag * pf_Last = pf_End->getPrev(); while (pf_Last && pf_Last->getType() == pf_Frag::PFT_FmtMark) pf_Last = pf_Last->getPrev(); if (pf_Before && pf_Before->getType() == pf_Frag::PFT_Strux && pf_Last && pf_Last->getType() == pf_Frag::PFT_Strux) { PTStruxType pt_BeforeType = static_cast(pf_Before)->getStruxType(); PTStruxType pt_LastType = static_cast(pf_Last)->getStruxType(); if (pt_BeforeType == PTX_Block && pt_LastType == PTX_Block) { // Check that there is something between the pf_Before and pf_Last, otherwise // This leads to a segfault from continually pushing pf_Before onto the stack // if we delete a whole lot of blank lines. These get popped off then deleted // only to find the same pointer waiting to come off the stack. pf_Frag * pScan = pf_Before->getNext(); while(pScan && pScan != pf_Last && (pScan->getType() != pf_Frag::PFT_Strux)) { pScan = pScan->getNext(); } if(pScan == pf_Last) { // If we are the structure of the document is // '[Block] ... [Block]' and we are deleting the // '... [Block]' part, then the user is probably expecting // us to delete '[Block] ... ' instead, so that any text // following the second block marker retains its properties. // The problem is that it might not be safe to delete the // first block marker until the '...' is deleted because // it might be the first block of the section. So, we // want to delete the '...' first, and then get around // to deleting the block later. pf_Frag_Strux * pfs_BeforeSection, * pfs_LastSection; _getStruxOfTypeFromPosition(dpos1 - 1, PTX_Section, &pfs_BeforeSection); _getStruxOfTypeFromPosition(dpos2 - 1, PTX_Section, &pfs_LastSection); if ((pfs_BeforeSection == pfs_LastSection) && (dpos2 > dpos1 +1)) { dpos2 -= pf_Last->getLength(); pstDelayStruxDelete->push(pf_Before); return true; } } } } } return true; } bool pt_PieceTable::_tweakDeleteSpan(PT_DocPosition & dpos1, PT_DocPosition & dpos2, UT_Stack * pstDelayStruxDelete) const { // We want to keep tweaking the delete span until there is nothing // more to tweak. We check to see if nothing has changed in the // last tweak, and if so, we are done. while (1) { PT_DocPosition old_dpos1 = dpos1; PT_DocPosition old_dpos2 = dpos2; UT_uint32 old_iStackSize = pstDelayStruxDelete->getDepth(); if(!_tweakDeleteSpanOnce(dpos1, dpos2, pstDelayStruxDelete)) return false; if (dpos1 == old_dpos1 && dpos2 == old_dpos2 && pstDelayStruxDelete->getDepth() == old_iStackSize) return true; } } bool pt_PieceTable::_deleteFormatting(PT_DocPosition dpos1, PT_DocPosition dpos2) { pf_Frag * pf_First; pf_Frag * pf_End; PT_BlockOffset fragOffset_First; PT_BlockOffset fragOffset_End; bool bFound = getFragsFromPositions(dpos1,dpos2,&pf_First,&fragOffset_First,&pf_End,&fragOffset_End); UT_ASSERT(bFound); // before we delete the content, we do a quick scan and delete // any FmtMarks first -- this let's us avoid problems with // coalescing FmtMarks only to be deleted. pf_Frag * pfTemp = pf_First; PT_BlockOffset fragOffsetTemp = fragOffset_First; PT_DocPosition dposTemp = dpos1; while (dposTemp <= dpos2) { if (pfTemp->getType() == pf_Frag::PFT_EndOfDoc) break; if (pfTemp->getType() == pf_Frag::PFT_FmtMark) { pf_Frag * pfNewTemp; PT_BlockOffset fragOffsetNewTemp; pf_Frag_Strux * pfsContainerTemp = NULL; bool bFoundStrux = _getStruxFromPosition(dposTemp,&pfsContainerTemp); UT_ASSERT(bFoundStrux); bool bResult = _deleteFmtMarkWithNotify(dposTemp,static_cast(pfTemp), pfsContainerTemp,&pfNewTemp,&fragOffsetNewTemp); UT_ASSERT(bResult); // FmtMarks have length zero, so we don't need to update dposTemp. pfTemp = pfNewTemp; fragOffsetTemp = fragOffsetNewTemp; } else if(pfTemp->getType() == pf_Frag::PFT_Strux) { pf_Frag_Strux * pfFragStrux = static_cast(pfTemp); if(pfFragStrux->getStruxType() == PTX_Section) { pf_Frag_Strux_Section * pfSec = static_cast(pfFragStrux); _deleteHdrFtrsFromSectionStruxIfPresent(pfSec); } dposTemp += pfTemp->getLength() - fragOffsetTemp; pfTemp = pfTemp->getNext(); fragOffsetTemp = 0; } else { dposTemp += pfTemp->getLength() - fragOffsetTemp; pfTemp = pfTemp->getNext(); fragOffsetTemp = 0; } } return true; } bool pt_PieceTable::_deleteComplexSpan(PT_DocPosition dpos1, PT_DocPosition dpos2) { pf_Frag * pfNewEnd; UT_uint32 fragOffsetNewEnd; pf_Frag * pf_First; pf_Frag * pf_End; PT_BlockOffset fragOffset_First; PT_BlockOffset fragOffset_End; bool bFound = getFragsFromPositions(dpos1,dpos2,&pf_First,&fragOffset_First,&pf_End,&fragOffset_End); UT_ASSERT(bFound); pf_Frag_Strux * pfsContainer = NULL; bool bFoundStrux = _getStruxFromPosition(dpos1,&pfsContainer); UT_ASSERT(bFoundStrux); // loop to delete the amount requested, one text fragment at a time. // if we encounter any non-text fragments along the way, we delete // them too. that is, we implicitly delete Strux and Objects here. UT_uint32 length = dpos2 - dpos1; while (length > 0) { UT_uint32 lengthInFrag = pf_First->getLength() - fragOffset_First; UT_uint32 lengthThisStep = UT_MIN(lengthInFrag, length); switch (pf_First->getType()) { case pf_Frag::PFT_EndOfDoc: default: UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return false; case pf_Frag::PFT_Strux: { pf_Frag_Strux * pfs = static_cast (pf_First); bool bResult = _deleteStruxWithNotify(dpos1,pfs, &pfNewEnd,&fragOffsetNewEnd); UT_ASSERT(bResult); // we do not update pfsContainer because we just deleted pfs. } break; case pf_Frag::PFT_Text: { bool bResult = _deleteSpanWithNotify(dpos1,static_cast(pf_First), fragOffset_First,lengthThisStep, pfsContainer,&pfNewEnd,&fragOffsetNewEnd); UT_ASSERT(bResult); } break; // the bookmark and hyperlink objects require special treatment; since // they come in pairs, we have to ensure that both are deleted together // so we have to find the other part of the pair, delete it, adjust the // positional variables and the delete the one we were originally asked // to delete case pf_Frag::PFT_Object: { bool bResult, bResult2; pf_Frag_Object *pO = static_cast(pf_First); switch(pO->getObjectType()) { case PTO_Bookmark: { bool bFoundStrux2; PT_DocPosition posComrade; pf_Frag_Strux * pfsContainer2 = NULL; po_Bookmark * pB = pO->getBookmark(); UT_ASSERT(pB); pf_Frag * pF; if(pB->getBookmarkType() == po_Bookmark::POBOOKMARK_END) { pF = pO->getPrev(); while(pF) { if(pF->getType() == pf_Frag::PFT_Object) { pf_Frag_Object *pOb = static_cast(pF); po_Bookmark * pB1 = pOb->getBookmark(); if(!strcmp(pB->getName(),pB1->getName())) { m_pDocument->removeBookmark(pB1->getName()); posComrade = getFragPosition(pOb); bFoundStrux2 = _getStruxFromPosition(posComrade,&pfsContainer2); UT_ASSERT(bFoundStrux2); bResult2 = _deleteObjectWithNotify(posComrade,pOb,0,1, pfsContainer2,0,0); // now adjusting the positional variables if(posComrade <= dpos1) // delete before the start of the segement we are working on dpos1--; else { // we are inside that section length--; } break; } } pF = pF->getPrev(); } } else { pF = pO->getNext(); while(pF) { if(pF->getType() == pf_Frag::PFT_Object) { pf_Frag_Object *pOb = static_cast(pF); po_Bookmark * pB1 = pOb->getBookmark(); if(!strcmp(pB->getName(),pB1->getName())) { m_pDocument->removeBookmark(pB1->getName()); posComrade = getFragPosition(pOb); bool bFoundStrux2 = _getStruxFromPosition(posComrade,&pfsContainer2); UT_ASSERT(bFoundStrux2); bResult2 = _deleteObjectWithNotify(posComrade,pOb,0,1, pfsContainer2,0,0); if(posComrade < dpos1 + length) length--; break; } } pF = pF->getNext(); } } bResult = _deleteObjectWithNotify(dpos1,pO,fragOffset_First,lengthThisStep, pfsContainer,&pfNewEnd,&fragOffsetNewEnd); } break; // the one singnificant difference compared to the bookmarks is // that we have to always delete the start marker first; this is // so that in the case of undo the endmarker would be restored // first, because the mechanism that marks runs between them // as a hyperlink depents on the end-marker being in place before // the start marker case PTO_Hyperlink: { bool bFoundStrux2; PT_DocPosition posComrade; pf_Frag_Strux * pfsContainer2 = NULL; pf_Frag * pF; const PP_AttrProp * pAP = NULL; pO->getPieceTable()->getAttrProp(pO->getIndexAP(),&pAP); UT_ASSERT(pAP); const XML_Char* pszHref = NULL; const XML_Char* pszHname = NULL; UT_uint32 k = 0; bool bStart = false; while((pAP)->getNthAttribute(k++,pszHname, pszHref)) { if(!UT_strcmp(pszHname, "xlink:href")) { bStart = true; break; } } if(!bStart) { // in this case we are looking for the start marker // and so we delete it and then move on pF = pO->getPrev(); while(pF) { if(pF->getType() == pf_Frag::PFT_Object) { pf_Frag_Object *pOb = static_cast(pF); if(pOb->getObjectType() == PTO_Hyperlink) { posComrade = getFragPosition(pOb); bFoundStrux2 = _getStruxFromPosition(posComrade,&pfsContainer2); UT_ASSERT(bFoundStrux2); bResult2 = _deleteObjectWithNotify(posComrade,pOb,0,1, pfsContainer2,0,0); // now adjusting the positional variables if(posComrade <= dpos1) // delete before the start of the segement we are working on dpos1--; else { // we are inside that section length--; } break; } } pF = pF->getPrev(); } bResult = _deleteObjectWithNotify(dpos1,pO,fragOffset_First,lengthThisStep, pfsContainer,&pfNewEnd,&fragOffsetNewEnd); } else { // in this case we are looking for the end marker, // so we have to be carefult the get rid of the start // marker first pF = pO->getNext(); while(pF) { if(pF->getType() == pf_Frag::PFT_Object) { pf_Frag_Object *pOb = static_cast(pF); if(pOb->getObjectType() == PTO_Hyperlink) { posComrade = getFragPosition(pOb); bFoundStrux2 = _getStruxFromPosition(posComrade,&pfsContainer2); UT_ASSERT(bFoundStrux2); // delete the original start marker bResult = _deleteObjectWithNotify(dpos1,pO,fragOffset_First,lengthThisStep, pfsContainer,&pfNewEnd,&fragOffsetNewEnd); // now adjusting the positional variables posComrade--; bResult2 = _deleteObjectWithNotify(posComrade,pOb,0,1, pfsContainer2,0,0); if(posComrade >= dpos1 && posComrade <= dpos1 + length - 2) { // the endmarker was inside of the segment we are working // so we have to adjust the length length--; } break; } } pF = pF->getNext(); } } } break; case PTO_Field: { // when deleting endnote reference, have to delete the // the associated stuff in the endnote section as well if(pO->getField()->getFieldType() == fd_Field::FD_Endnote_Ref) { const PP_AttrProp * pAP = NULL; pO->getPieceTable()->getAttrProp(pO->getIndexAP(),&pAP); UT_ASSERT(pAP); const XML_Char* pszEid = NULL; const XML_Char* pszEidName = NULL; UT_uint32 k = 0; bool bFound = false; pf_Frag_Strux *pMySection, *pESection; while((pAP)->getNthAttribute(k++,pszEidName, pszEid)) { if(!UT_strcmp(pszEidName, "endnote-id")) { bFound = true; break; } } UT_ASSERT(bFound); pf_Frag_Strux * pfsContainer = NULL; pf_Frag * pF; // in theory, we should find our section, from // there get the id of the endnote section, find the endnote // section and then delete our block // however, to get to the section we have to scroll through // the fragments one by one, so we might just as well not // bother and test for the endnote-id directly // we know that the endnote section is definitely after this // fragment, so we will start with the next one // on further thought we will bother const XML_Char* pszEnote = NULL; pF = pO->getPrev(); while(pF) { if(pF->getType() == pf_Frag::PFT_Strux) { pfsContainer = static_cast(pF); if(pfsContainer->getStruxType() == PTX_Section) { pfsContainer->getPieceTable()->getAttrProp(pfsContainer->getIndexAP(),&pAP); UT_ASSERT(pAP); const XML_Char* pszEnoteName = NULL; k = 0; bFound = false; while((pAP)->getNthAttribute(k++,pszEnoteName, pszEnote)) { if(!UT_strcmp(pszEnoteName, "endnote")) { bFound = true; break; } } UT_ASSERT(bFound); break; } } pF = pF->getPrev(); } pMySection = static_cast(pF); // now that we have got our endnote section id // we will find it pF = pO->getNext(); while(pF) { if(pF->getType() == pf_Frag::PFT_Strux) { pfsContainer = static_cast(pF); if(pfsContainer->getStruxType() == PTX_SectionEndnote) { pfsContainer->getPieceTable()->getAttrProp(pfsContainer->getIndexAP(),&pAP); UT_ASSERT(pAP); const XML_Char* pszId = NULL; const XML_Char* pszIdName = NULL; k = 0; bFound = false; while((pAP)->getNthAttribute(k++,pszIdName, pszId)) { if(!UT_strcmp(pszIdName, "id")) { if(!UT_strcmp(pszId, pszEnote)) { bFound = true; break; } } } if(bFound) break; } } pF = pF->getNext(); } // we should have our endnote section now UT_ASSERT(pF); pESection = static_cast(pF); // lets get deleting ... pF = pF->getNext(); UT_ASSERT(pF); pf_Frag_Strux * pFirstBlock = NULL; while(pF) { // first check we are still in our section if(pF->getType() == pf_Frag::PFT_Strux) { pfsContainer = static_cast(pF); if(pfsContainer->getStruxType() != PTX_Block) { UT_DEBUGMSG(("non-block strux\n")); break; } } PT_AttrPropIndex iAPI = 0; bool bHasIndex = true; switch(pF->getType()) { case pf_Frag::PFT_Strux: { pf_Frag_Strux * pFS = static_cast(pF); iAPI = pFS->getIndexAP(); } break; case pf_Frag::PFT_Object: { pf_Frag_Object * pFO = static_cast(pF); iAPI = pFO->getIndexAP(); } break; case pf_Frag::PFT_Text: { pf_Frag_Text * pFT = static_cast(pF); iAPI = pFT->getIndexAP(); } break; case pf_Frag::PFT_FmtMark: // formating marks are real pain and we // we delete any we find in this section // without mercy { UT_DEBUGMSG(("Deleting FmtMark\n")); pf_Frag_FmtMark * pFM = static_cast(pF); pF = pF->getPrev(); pf_Frag_Strux * pfsContainerTemp = NULL; bool bFoundStrux = _getStruxFromPosition(pFM->getPos(),&pfsContainerTemp); UT_ASSERT(bFoundStrux); bool bRes = _deleteFmtMarkWithNotify(pFM->getPos(),pFM, pfsContainerTemp,NULL,NULL); UT_ASSERT(bRes); pF = pF->getNext(); continue; } break; case pf_Frag::PFT_EndOfDoc: bHasIndex = false; break; default: UT_ASSERT(UT_SHOULD_NOT_HAPPEN); bHasIndex = false; } if(bHasIndex) { pF->getPieceTable()->getAttrProp(iAPI,&pAP); UT_ASSERT(pAP); const XML_Char* pszEid2 = NULL; k = 0; bFound = false; bool bResult = false; while((pAP)->getNthAttribute(k++,pszEidName, pszEid2)) { if(!UT_strcmp(pszEidName, "endnote-id")) { if(!UT_strcmp(pszEid,pszEid2)) { bFound = true; break; } } } // OK, if we have got something that carries our id // we have to delete it, we do not know what it is // the simplest thing is to call deleteSpan recursively // this works in all cases but the first block, where // calling deleteSpan deletes both the block and the // section break; further, when deleting the first block // we have to delete the content first if(bFound) { #ifdef DEBUG UT_DEBUGMSG(("FOUND AND DELETING ... type %d\n", pF->getType())); if(pF->getType() == pf_Frag::PFT_Strux) { pf_Frag_Strux * pFS = static_cast(pF); UT_DEBUGMSG(("strux type %d\n", pFS->getStruxType())); } #endif PT_DocPosition posStart = pF->getPos(); PT_DocPosition posEnd = pF->getLength() + posStart; // if this is a strux, we // are going to need it, otherwise // we just need to get the frag ahead of us pf_Frag_Strux * pFS; if(pF->getType() == pf_Frag::PFT_Strux) pFS = static_cast (pF); else pFS = NULL; // get the run before us pF = pF->getPrev(); #ifdef DEBUG UT_DEBUGMSG(("type prev %d\n", pF ? pF->getType() : -1)); if(pF->getType() == pf_Frag::PFT_Strux) { pf_Frag_Strux * pFS = static_cast(pF); UT_DEBUGMSG(("strux type %d\n", pFS->getStruxType())); } #endif // if this is a strux and the previous // is also a strux, we need to handle this // with more care, so we do not delelete both // together if(pFS && pF->getType() == pf_Frag::PFT_Strux) { UT_DEBUGMSG(("Two strux in sequence\n")); if(pF == pESection) { // we trying to delete the first block // it will not work, we have to delete // the content first, so we just skip it // for the moment UT_DEBUGMSG(("First block in Endnote section\n")); pFirstBlock = pFS; pF = pFS; bResult = true; } else bResult = _deleteStruxWithNotify(dpos1,pFS,NULL,NULL); } else bResult = deleteSpan(posStart,posEnd,NULL,false); UT_ASSERT(bResult); } } pF = pF->getNext(); UT_DEBUGMSG(("pF 0x%x\n", pF)); UT_DEBUGMSG(("type %d\n", pF ? pF->getType() : -1)); #ifdef DEBUG if(pF && pF->getType() == pf_Frag::PFT_Strux) { pf_Frag_Strux * pFS = static_cast(pF); UT_DEBUGMSG(("strux type %d\n", pFS->getStruxType())); } #endif } // now if we ignored the first block, we should // be able to delete it. Also, if we delete the // first block, we need to check whether there // are any endnotes left, and if not then we have to // delete the endnote section and reset the endnote // attribute of the original section. if(pFirstBlock) { bool bSectionEmpty = true; pF = pFirstBlock->getNext(); UT_ASSERT(pF); switch(pF->getType()) { case pf_Frag::PFT_Strux: { pf_Frag_Strux * pFS = static_cast(pF); switch(pFS->getStruxType()) { case PTX_Block: bSectionEmpty = false; default: ; } } break; case pf_Frag::PFT_FmtMark: case pf_Frag::PFT_Text: case pf_Frag::PFT_Object: bSectionEmpty = false; break; case pf_Frag::PFT_EndOfDoc: break; default: UT_ASSERT(UT_SHOULD_NOT_HAPPEN); } if(bSectionEmpty) { UT_DEBUGMSG(("Endnote section empty, deleting\n")); bResult = _deleteStruxWithNotify(pESection->getPos(),pESection,NULL,NULL); // now we need to remove the endnote attribute from the orig section // we assume here that each endnote section is used by only one // other section in the document; if this should change in the // future, we will have to scan the whole doc ... const XML_Char* sec_attributes2[] = { "endnote", "", NULL, NULL }; PT_DocPosition posSec = pMySection->getPos(); changeStruxFmt(PTC_RemoveFmt, posSec, posSec, sec_attributes2, NULL, PTX_Section); } UT_DEBUGMSG(("Deleting first block\n")); bResult = _deleteStruxWithNotify(pFirstBlock->getPos(),pFirstBlock,NULL,NULL); UT_ASSERT(bResult); } } // no break, let everything fall through } default: bResult = _deleteObjectWithNotify(dpos1,pO,fragOffset_First,lengthThisStep, pfsContainer,&pfNewEnd,&fragOffsetNewEnd); } UT_ASSERT(bResult); } break; case pf_Frag::PFT_FmtMark: // we already took care of these... UT_ASSERT(UT_SHOULD_NOT_HAPPEN); break; } // we do not change dpos1, since we are deleting stuff, but we // do decrement the length-remaining. // dpos2 becomes bogus at this point. length -= lengthThisStep; // since _delete{*}WithNotify(), can delete pf_First, mess with the // fragment list, and does some aggressive coalescing of // fragments, we cannot just do a pf_First->getNext() here. // to advance to the next fragment, we use the *NewEnd variables // that each of the _delete routines gave us. pf_First = pfNewEnd; if (!pf_First) length = 0; fragOffset_First = fragOffsetNewEnd; } return true; } bool pt_PieceTable::_deleteComplexSpan_norec(PT_DocPosition dpos1, PT_DocPosition dpos2) { pf_Frag * pfNewEnd; UT_uint32 fragOffsetNewEnd; pf_Frag * pf_First; pf_Frag * pf_End; PT_BlockOffset fragOffset_First; PT_BlockOffset fragOffset_End; bool bFound = getFragsFromPositions(dpos1,dpos2,&pf_First,&fragOffset_First,&pf_End,&fragOffset_End); UT_ASSERT(bFound); pf_Frag_Strux * pfsContainer = NULL; bool bFoundStrux = _getStruxFromPosition(dpos1,&pfsContainer); UT_ASSERT(bFoundStrux); // loop to delete the amount requested, one text fragment at a time. // if we encounter any non-text fragments along the way, we delete // them too. that is, we implicitly delete Strux and Objects here. UT_uint32 length = dpos2 - dpos1; while (length > 0) { UT_uint32 lengthInFrag = pf_First->getLength() - fragOffset_First; UT_uint32 lengthThisStep = UT_MIN(lengthInFrag, length); switch (pf_First->getType()) { case pf_Frag::PFT_EndOfDoc: default: UT_ASSERT(0); return false; case pf_Frag::PFT_Strux: { pf_Frag_Strux * pfs = static_cast (pf_First); bool bResult = _deleteStrux_norec(dpos1,pfs, &pfNewEnd,&fragOffsetNewEnd); UT_ASSERT(bResult); // we do not update pfsContainer because we just deleted pfs. } break; case pf_Frag::PFT_Text: { bool bResult = _deleteSpan_norec(dpos1,static_cast(pf_First), fragOffset_First,lengthThisStep, pfsContainer,&pfNewEnd,&fragOffsetNewEnd); UT_ASSERT(bResult); } break; case pf_Frag::PFT_Object: { bool bResult = _deleteObject_norec(dpos1,static_cast(pf_First), fragOffset_First,lengthThisStep, pfsContainer,&pfNewEnd,&fragOffsetNewEnd); UT_ASSERT(bResult); } break; case pf_Frag::PFT_FmtMark: // we already took care of these... UT_ASSERT(UT_SHOULD_NOT_HAPPEN); break; } // we do not change dpos1, since we are deleting stuff, but we // do decrement the length-remaining. // dpos2 becomes bogus at this point. length -= lengthThisStep; // since _delete{*}WithNotify(), can delete pf_First, mess with the // fragment list, and does some aggressive coalescing of // fragments, we cannot just do a pf_First->getNext() here. // to advance to the next fragment, we use the *NewEnd variables // that each of the _delete routines gave us. pf_First = pfNewEnd; if (!pf_First) length = 0; fragOffset_First = fragOffsetNewEnd; } return true; } bool pt_PieceTable::deleteSpan(PT_DocPosition dpos1, PT_DocPosition dpos2, PP_AttrProp *p_AttrProp_Before, bool bDontGlob) { // remove (dpos2-dpos1) characters from the document at the given position. UT_ASSERT(m_pts==PTS_Editing); UT_ASSERT(dpos2 > dpos1); bool bSuccess = true; UT_Stack stDelayStruxDelete; PT_DocPosition old_dpos2 = dpos2; // Before we begin the delete proper, we might want to adjust the ends // of the delete slightly to account for expected behavior on // structural boundaries. bSuccess = _tweakDeleteSpan(dpos1, dpos2, &stDelayStruxDelete); if (!bSuccess) { return false; } // Get the attribute properties before the delete. PP_AttrProp AttrProp_Before; { pf_Frag * pf1; PT_BlockOffset Offset1; getFragFromPosition(dpos1, &pf1, &Offset1); if(pf1->getType() == pf_Frag::PFT_Text) { const PP_AttrProp *p_AttrProp; getAttrProp(((pf_Frag_Text *)pf1)->getIndexAP(), &p_AttrProp); AttrProp_Before = *p_AttrProp; if(p_AttrProp_Before) *p_AttrProp_Before = *p_AttrProp; } } // The code used to only glob for the complex case. But when // there's a simple delete, we may still end up adding the // formatmark below (i.e., when deleting all the text in a // document), and thus creating a two-step undo for a perceived // one-step operation. See Bug FIXME if(!bDontGlob) { beginMultiStepGlob(); } bool bIsSimple = _isSimpleDeleteSpan(dpos1, dpos2) && stDelayStruxDelete.getDepth() == 0; if (bIsSimple) { // If the delete is sure to be within a fragment, we don't // need to worry about much of the bookkeeping of a complex // delete. bSuccess = _deleteComplexSpan(dpos1, dpos2); } else { // If the delete spans multiple fragments, we need to // be a bit more careful about deleting the formatting // first, and then the actual spans. _changePointWithNotify(old_dpos2); bSuccess = _deleteFormatting(dpos1, dpos2); if (bSuccess) bSuccess = _deleteComplexSpan(dpos1, dpos2); while (bSuccess && stDelayStruxDelete.getDepth() > 0) { pf_Frag_Strux * pfs; stDelayStruxDelete.pop((void **)&pfs); _deleteFormatting(dpos1 - pfs->getLength(), dpos1); pf_Frag *pf; PT_DocPosition dp; bSuccess = _deleteStruxWithNotify(dpos1 - pfs->getLength(), pfs, &pf, &dp); } _changePointWithNotify(dpos1); } // Have we deleted all the text in a paragraph. pf_Frag * p_frag_before; PT_BlockOffset Offset_before; getFragFromPosition(dpos1 - 1, &p_frag_before, &Offset_before); pf_Frag * p_frag_after; PT_BlockOffset Offset_after; getFragFromPosition(dpos1, &p_frag_after, &Offset_after); if(((p_frag_before->getType() == pf_Frag::PFT_Strux) || (p_frag_before->getType() == pf_Frag::PFT_EndOfDoc)) && ((p_frag_after->getType() == pf_Frag::PFT_Strux) || (p_frag_after->getType() == pf_Frag::PFT_EndOfDoc))) { xxx_UT_DEBUGMSG(("pt_PieceTable::deleteSpan Paragraph empty\n")); // All text in paragraph is deleted so insert a text format. // Except if we're realy don't want it. We know we dont if // bDontGlob is true. if(!bDontGlob) _insertFmtMarkFragWithNotify(PTC_AddFmt, dpos1, &AttrProp_Before); } // End the glob after (maybe) having inserted the FmtMark if (!bDontGlob) { endMultiStepGlob(); } return bSuccess; } // need a special delete for a field update because otherwise // the whole field object would be deleted by _tweakDeleteSpan bool pt_PieceTable::deleteFieldFrag(pf_Frag * pf) { UT_ASSERT(m_pts==PTS_Editing); bool bSuccess = true; UT_Stack stDelayStruxDelete; PT_DocPosition dpos1 = getFragPosition(pf); UT_ASSERT(dpos1); PT_DocPosition dpos2 = dpos1 + pf->getLength(); // If the delete is sure to be within a fragment, we don't // need to worry about much of the bookkeeping of a complex // delete. bSuccess = _deleteComplexSpan_norec(dpos1, dpos2); return bSuccess; } void pt_PieceTable::_tweakFieldSpan(PT_DocPosition & dpos1, PT_DocPosition & dpos2) const { // Our job is to adjust the end positions of the delete // operating to delete those structural object that the // user will expect to have deleted, even if the dpositions // aren't quite right to encompass those. pf_Frag * pf_First; pf_Frag * pf_End; pf_Frag * pf_Other; PT_BlockOffset fragOffset_First; PT_BlockOffset fragOffset_End; bool bFound = getFragsFromPositions(dpos1,dpos2,&pf_First,&fragOffset_First,&pf_End,&fragOffset_End); UT_ASSERT(bFound); pf_Frag_Strux * pfsContainer = NULL; bool bFoundStrux = _getStruxFromPosition(dpos1,&pfsContainer); UT_ASSERT(bFoundStrux); // if start in middle of field widen to include object if ((pf_First->getType() == pf_Frag::PFT_Text)&& (static_cast(pf_First)->getField())) { pf_Frag_Text * pft = static_cast(pf_First); pf_Frag_Text * pft2 = NULL; // we can't delete part of a field so widen deletion to // include object at start while (pft->getPrev()->getType() == pf_Frag::PFT_Text) { pft2 = static_cast(pft->getPrev()); UT_ASSERT(pft->getField() == pft2->getField()); pft = pft2; } UT_ASSERT(pft->getPrev()->getType() == pf_Frag::PFT_Object); pf_Frag_Object *pfo = static_cast(pft->getPrev()); UT_ASSERT(pfo->getObjectType()==PTO_Field); UT_ASSERT(pfo->getField()==pft->getField()); dpos1 = getFragPosition(pfo); } // if end in middle of field widen to include whole Frag_Text if (((pf_End->getType() == pf_Frag::PFT_Text)&& (pf_End)->getField())/*|| ((pf_End->getType() == pf_Frag::PFT_Object )&& (static_cast(pf_End) ->getObjectType()==PTO_Field))*/) { fd_Field * pField = pf_End->getField(); UT_ASSERT(pField); pf_Other = pf_End->getNext(); UT_ASSERT(pf_Other); while (pf_Other->getField()==pField) { pf_Other = pf_Other->getNext(); UT_ASSERT(pf_Other); } dpos2 = getFragPosition(pf_Other); } }