/* AbiWord * Copyright (C) 1998 AbiSource, Inc. * * 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. */ // insert-object-related routines for piece table. #include "ut_types.h" #include "ut_misc.h" #include "ut_assert.h" #include "ut_debugmsg.h" #include "ut_growbuf.h" #include "pt_PieceTable.h" #include "pf_Frag.h" #include "pf_Frag_FmtMark.h" #include "pf_Frag_Text.h" #include "pf_Frag_Strux.h" #include "pf_Fragments.h" #include "px_ChangeRecord.h" #include "px_CR_FmtMark.h" #include "px_CR_FmtMarkChange.h" #define SETP(p,v) do { if (p) (*(p)) = (v); } while (0) ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// bool pt_PieceTable::_insertFmtMarkFragWithNotify(PTChangeFmt ptc, PT_DocPosition dpos, const gchar ** attributes, const gchar ** properties) { UT_return_val_if_fail (m_pts==PTS_Editing, false); pf_Frag * pf; PT_BlockOffset fo; // The return value of getFragFromPosition was never checked (sterwill) // bool bFound = getFragFromPosition(dpos,&pf,&fo); UT_return_val_if_fail (pf, false); if ((fo==0) && (pf->getPrev())) { if (pf->getPrev()->getType() == pf_Frag::PFT_FmtMark) { // we are adjacent to another FmtMark. we can just hack on this // one rather than inserting two consecutive marks. pf_Frag_FmtMark * pffm = static_cast(pf->getPrev()); pf_Frag_Strux * pfsContainer = NULL; bool bFoundStrux; bFoundStrux = _getStruxOfTypeFromPosition(dpos,PTX_Block,&pfsContainer); UT_return_val_if_fail (bFoundStrux, false); return _fmtChangeFmtMarkWithNotify(ptc,pffm,dpos,attributes,properties,pfsContainer,NULL,NULL); } if (pf->getPrev()->getType() == pf_Frag::PFT_Text) { // if we are on a boundary between two frags and the one to our left (before us) // is a text fragment, we pretend to be at the end of it. pf = pf->getPrev(); fo = pf->getLength(); } } PT_AttrPropIndex indexOldAP = _chooseIndexAP(pf,fo); PT_AttrPropIndex indexNewAP; bool bMerged; bMerged = m_varset.mergeAP(ptc,indexOldAP,attributes,properties,&indexNewAP,getDocument()); UT_ASSERT_HARMLESS(bMerged); if (indexOldAP == indexNewAP) // the requested change will have no effect on this fragment. return true; pf_Frag_Strux * pfs = NULL; bool bFoundStrux = false; // // This code is to ensure that FmtMarks get inserted into empty // Embeded containers and not the enclosing block // if(pf->getType() == pf_Frag::PFT_Strux) { pf_Frag_Strux * pfse = static_cast(pf); if(isEndFootnote(pfse)) { if(pf->getPrev() && pf->getPrev()->getType() == pf_Frag::PFT_Strux) { pfs = static_cast(pf->getPrev()); if(pfs->getStruxType() == PTX_Block) { bFoundStrux = true; } } } } if(!bFoundStrux) bFoundStrux = _getStruxFromFragSkip(pf,&pfs); UT_return_val_if_fail (bFoundStrux, false); PT_BlockOffset blockOffset = _computeBlockOffset(pfs,pf) + fo; if (!_insertFmtMark(pf,fo,indexNewAP)) return false; // create a change record, add it to the history, and notify // anyone listening. PX_ChangeRecord_FmtMark * pcr = new PX_ChangeRecord_FmtMark(PX_ChangeRecord::PXT_InsertFmtMark, dpos,indexNewAP,blockOffset); UT_return_val_if_fail (pcr,false); m_history.addChangeRecord(pcr); m_pDocument->notifyListeners(pfs,pcr); return true; } bool pt_PieceTable::_insertFmtMark(pf_Frag * pf, UT_uint32 fragOffset, PT_AttrPropIndex api) { pf_Frag_FmtMark * pffm = new pf_Frag_FmtMark(this,api); if (!pffm) return false; if (fragOffset == 0) { // we are at the beginning of a fragment, insert the // new FmtMark immediately prior to it. m_fragments.insertFrag(pf->getPrev(),pffm); } else if (fragOffset == pf->getLength()) { // we are at the end of a fragment, insert the new // FmtMark immediately after it. m_fragments.insertFrag(pf,pffm); } else { // if the insert is in the middle of the (text) fragment, we // split the current fragment and insert the FmtMark between // them. UT_return_val_if_fail (pf->getType() == pf_Frag::PFT_Text,false); pf_Frag_Text * pft = static_cast(pf); UT_uint32 lenTail = pft->getLength() - fragOffset; PT_BufIndex biTail = m_varset.getBufIndex(pft->getBufIndex(),fragOffset); pf_Frag_Text * pftTail = new pf_Frag_Text(this,biTail,lenTail,pft->getIndexAP(),pft->getField()); if (!pftTail) goto MemoryError; pft->changeLength(fragOffset); m_fragments.insertFrag(pft,pffm); m_fragments.insertFrag(pffm,pftTail); } return true; MemoryError: DELETEP(pffm); return false; } bool pt_PieceTable::_insertFmtMarkAfterBlockWithNotify(pf_Frag_Strux * pfsBlock, PT_DocPosition dpos, PT_AttrPropIndex api) { UT_return_val_if_fail (m_pts==PTS_Editing, false); PT_BlockOffset blockOffset = 0; #ifdef DEBUG { PT_DocPosition dposTest = getFragPosition(pfsBlock) + pfsBlock->getLength(); UT_ASSERT_HARMLESS(dposTest == dpos); } #endif if (!_insertFmtMark(pfsBlock,pfsBlock->getLength(),api)) return false; // create a change record, add it to the history, and notify // anyone listening. PX_ChangeRecord_FmtMark * pcr = new PX_ChangeRecord_FmtMark(PX_ChangeRecord::PXT_InsertFmtMark, dpos,api,blockOffset); UT_return_val_if_fail (pcr,false); m_history.addChangeRecord(pcr); m_pDocument->notifyListeners(pfsBlock,pcr); return true; } ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// bool pt_PieceTable::_deleteFmtMarkWithNotify(PT_DocPosition dpos, pf_Frag_FmtMark * pffm, pf_Frag_Strux * pfs, pf_Frag ** ppfEnd, UT_uint32 * pfragOffsetEnd) { UT_return_val_if_fail (m_pts==PTS_Editing,false); UT_return_val_if_fail (pfs,false); PT_BlockOffset blockOffset = _computeBlockOffset(pfs,pffm); PX_ChangeRecord_FmtMark * pcr = new PX_ChangeRecord_FmtMark(PX_ChangeRecord::PXT_DeleteFmtMark, dpos, pffm->getIndexAP(), blockOffset); UT_return_val_if_fail (pcr,false); // actually remove the fragment from the list and delete it. _deleteFmtMark(pffm,ppfEnd,pfragOffsetEnd); m_history.addChangeRecord(pcr); m_pDocument->notifyListeners(pfs,pcr); return true; } bool pt_PieceTable::_deleteFmtMark(pf_Frag_FmtMark * pffm, pf_Frag ** ppfEnd, UT_uint32 * pfragOffsetEnd) { // unlink the FmtMark from the fragment list and try to // coalesce the neighboring fragments. _unlinkFrag(pffm,ppfEnd,pfragOffsetEnd); delete pffm; return true; } /* purges fmt marks from document; leaves no records in the undo history */ bool pt_PieceTable::purgeFmtMarks() { pf_Frag * pf_First = m_fragments.getFirst(); pf_Frag * pfTemp = pf_First; #ifdef DEBUG UT_uint32 iCount = 0; #endif while (pfTemp) { if (pfTemp->getType() == pf_Frag::PFT_EndOfDoc) break; if (pfTemp->getType() == pf_Frag::PFT_FmtMark) { pf_Frag * pfNewTemp; PT_BlockOffset fragOffsetNewTemp; bool bResult = _deleteFmtMark(static_cast(pfTemp), &pfNewTemp,&fragOffsetNewTemp); UT_return_val_if_fail (bResult,false); // FmtMarks have length zero, so we don't need to update dposTemp. pfTemp = pfNewTemp; #ifdef DEBUG ++iCount; #endif } else { pfTemp = pfTemp->getNext(); } } UT_DEBUGMSG(("pt_PieceTable::purgeFmtMarks: removed %d marks\n", iCount)); return true; } ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// bool pt_PieceTable::_fmtChangeFmtMarkWithNotify(PTChangeFmt ptc, pf_Frag_FmtMark * pffm, PT_DocPosition dpos, const gchar ** attributes, const gchar ** properties, pf_Frag_Strux * pfs, pf_Frag ** ppfNewEnd, UT_uint32 * pfragOffsetNewEnd) { UT_return_val_if_fail (m_pts==PTS_Editing,false); // apply a span-level change to the given FmtMark. // create a change record for this change and put it in the history. PT_AttrPropIndex indexNewAP; PT_AttrPropIndex indexOldAP = pffm->getIndexAP(); bool bMerged; bMerged = m_varset.mergeAP(ptc,indexOldAP,attributes,properties,&indexNewAP,getDocument()); UT_ASSERT_HARMLESS(bMerged); if (indexOldAP == indexNewAP) // the requested change will have no effect on this fragment. { SETP(ppfNewEnd, pffm->getNext()); SETP(pfragOffsetNewEnd, 0); return true; } PT_BlockOffset blockOffset = _computeBlockOffset(pfs,pffm); // we do this before the actual change because various fields that // we need may be blown away during the change. we then notify all // listeners of the change. PX_ChangeRecord_FmtMarkChange * pcr = new PX_ChangeRecord_FmtMarkChange(PX_ChangeRecord::PXT_ChangeFmtMark, dpos, indexOldAP,indexNewAP, blockOffset); UT_return_val_if_fail (pcr,false); // apply the change to this fragment _fmtChangeFmtMark(pffm,indexNewAP,ppfNewEnd,pfragOffsetNewEnd); // add record to history. we do not attempt to coalesce these. m_history.addChangeRecord(pcr); m_pDocument->notifyListeners(pfs,pcr); return true; } bool pt_PieceTable::_fmtChangeFmtMark(pf_Frag_FmtMark * pffm, PT_AttrPropIndex indexNewAP, pf_Frag ** ppfNewEnd, UT_uint32 * pfragOffsetNewEnd) { // actually apply the format change. pffm->setIndexAP(indexNewAP); SETP(ppfNewEnd, pffm->getNext()); SETP(pfragOffsetNewEnd, 0); return true; } bool pt_PieceTable::_insertFmtMarkFragWithNotify(PTChangeFmt ptc, PT_DocPosition dpos, PP_AttrProp *pAttrProp) { UT_return_val_if_fail (pAttrProp,false); #if 1 // the old way is rather inefficient, inserting one property at a time ... _insertFmtMarkFragWithNotify(ptc, dpos, pAttrProp->getAttributes(), pAttrProp->getProperties()); #else const gchar * properties[] = { NULL, NULL, 0}; int Index = 0; do { if(p_AttrProp->getNthProperty(Index, properties[0], properties[1])) { _insertFmtMarkFragWithNotify(ptc, dpos, NULL, properties); } else { break; } Index++; } while(true); const gchar * Attributes[] = { NULL, NULL, 0}; Index = 0; do { if(p_AttrProp->getNthAttribute(Index, Attributes[0], Attributes[1])) { _insertFmtMarkFragWithNotify(ptc, dpos, Attributes, NULL); } else { break; } Index++; } while(true); #endif return true; }