/* -*- mode: C++; tab-width: 4; c-basic-offset: 4; -*- */ /* 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. */ #include "ut_types.h" #include "ut_vector.h" #include "px_ChangeRecord.h" #include "px_ChangeHistory.h" #include "px_CR_Span.h" #include "pt_PieceTable.h" #include "ut_debugmsg.h" // m_undoPosition is the position of the undo pointer. // a value of zero means no undo history. // the first undo item is at ...[m_undoPosition-1] // the first redo item is at ...[m_undoPosition] if present. px_ChangeHistory::px_ChangeHistory(pt_PieceTable * pPT) : m_undoPosition(0), m_savePosition(0), m_pPT(pPT), m_iAdjustOffset(0), m_bOverlap(false), m_iMinUndo(0), m_bScanUndoGLOB(false) { } px_ChangeHistory::~px_ChangeHistory() { UT_VECTOR_PURGEALL(PX_ChangeRecord *,m_vecChangeRecords); } // this function is used when restoring an older version of a document // when maintaining full history, since that makes the info in the // undo meaningless (and causes crashes) void px_ChangeHistory::clearHistory() { UT_VECTOR_PURGEALL(PX_ChangeRecord *,m_vecChangeRecords); m_vecChangeRecords.clear(); m_undoPosition = 0; m_savePosition = 0; m_iAdjustOffset = 0; m_bOverlap = false; m_iMinUndo = 0; m_bScanUndoGLOB = false; } void px_ChangeHistory::_invalidateRedo(void) { UT_sint32 kLimit = m_vecChangeRecords.getItemCount(); UT_return_if_fail (m_undoPosition <= kLimit); UT_sint32 i = m_undoPosition - m_iAdjustOffset; for (UT_sint32 k = m_undoPosition - m_iAdjustOffset; k < kLimit; k++) { PX_ChangeRecord * pcrTemp = m_vecChangeRecords.getNthItem(i); if (!pcrTemp) break; if (pcrTemp->isFromThisDoc()) { delete pcrTemp; m_vecChangeRecords.deleteNthItem(i); } else i++; } m_undoPosition = m_vecChangeRecords.getItemCount(); if (m_savePosition > m_undoPosition) m_savePosition = -1; m_iAdjustOffset = 0; } PD_Document * px_ChangeHistory::getDoc(void) const { return m_pPT->getDocument(); } bool px_ChangeHistory::addChangeRecord(PX_ChangeRecord * pcr) { // add a change record to the history. // blow away any redo, since it is now invalid. xxx_UT_DEBUGMSG(("Add CR Pos %d Type %d indexAP %x \n",pcr->getPosition(),pcr->getType(),pcr->getIndexAP())); xxx_UT_DEBUGMSG(("Before invalidate Undo pos %d savepos %d iAdjust %d \n",m_undoPosition,m_savePosition,m_iAdjustOffset)); if (pcr && pcr->getDocument() == NULL) { pcr->setDocument(getDoc()); } if (m_bOverlap) { clearHistory(); } if (!m_pPT->isDoingTheDo()) { if(pcr && pcr->isFromThisDoc()) { _invalidateRedo(); bool bResult = (m_vecChangeRecords.insertItemAt(pcr,m_undoPosition++) == 0); UT_ASSERT_HARMLESS(bResult); xxx_UT_DEBUGMSG(("After Invalidate Undo pos %d savepos %d iAdjust %d \n",m_undoPosition,m_savePosition,m_iAdjustOffset)); m_iAdjustOffset = 0; return bResult; } else { m_vecChangeRecords.addItem(pcr); UT_sint32 iPos = m_undoPosition - m_iAdjustOffset; m_undoPosition = m_vecChangeRecords.getItemCount(); m_iAdjustOffset = m_undoPosition - iPos; return true; } } else { // // Just save the cr for later deletion with the PT // UT_ASSERT(UT_SHOULD_NOT_HAPPEN); m_vecChangeRecords.addItem(pcr); return true; } } bool px_ChangeHistory::canDo(bool bUndo) const { if(m_bOverlap) return false; PX_ChangeRecord * pcr; UT_sint32 iAdj = m_iAdjustOffset; setScanningUndoGLOB(false); bool b = (bUndo ? getUndo(&pcr) : getRedo(&pcr)); setScanningUndoGLOB(false); m_iAdjustOffset = iAdj; return b; } UT_sint32 px_ChangeHistory::getSavePosition(void) const { return m_savePosition; } UT_uint32 px_ChangeHistory::getUndoPos(void) const { return (m_undoPosition - m_iAdjustOffset); } void px_ChangeHistory::setSavePosition(UT_sint32 savePosition) { m_savePosition = savePosition; } bool px_ChangeHistory::getUndo(PX_ChangeRecord ** ppcr, bool bStatic) const { if (m_bOverlap) { *ppcr = NULL; return false; } UT_sint32 iGLOB = 0; bool bGotOne = false; PX_ChangeRecord * pcr = NULL; PX_ChangeRecord * pcrFirst = NULL; bool bCorrect = false; UT_sint32 iAdjust = m_iAdjustOffset; UT_sint32 iLoop = 0; // _printHistory(50); while (!bGotOne) { if ((m_undoPosition - m_iAdjustOffset -iLoop) <= m_iMinUndo) { if (bStatic) m_iAdjustOffset = iAdjust; return false; } pcr = m_vecChangeRecords.getNthItem(m_undoPosition-m_iAdjustOffset-1-iLoop); UT_return_val_if_fail(pcr, false); // just bail out, everything seems wrong // // Do Adjustments for blocks of remote CR's. Scan through local globs // to check for remote CR's which overlap it. // if((iGLOB== 0) && !pcr->isFromThisDoc()) { bCorrect = true; m_iAdjustOffset++; UT_DEBUGMSG(("Doing undo iAdjust incremented to %d \n",m_iAdjustOffset)); } else if ((iGLOB==0) && (pcr->getType() == PX_ChangeRecord::PXT_GlobMarker) && pcr->isFromThisDoc() && !isScanningUndoGLOB() && (m_iAdjustOffset > 0)) { iGLOB++; pcrFirst = pcr; iLoop++; setScanningUndoGLOB(true); } else if((iGLOB>0) && (pcr->getType() == PX_ChangeRecord::PXT_GlobMarker) && pcr->isFromThisDoc()) { if(isScanningUndoGLOB()) pcr = pcrFirst; bGotOne = true; } else if(iGLOB == 0) { bGotOne = true; if(m_iAdjustOffset > 0) bCorrect = true; } // // we're here if we've started scanning through a glob in the local // document to see if it overlaps a later remote change. // else { PT_DocPosition low, high; PT_DocPosition lowWork = 0; PT_DocPosition highWork; UT_sint32 iAccumOffset = 0; getCRRange(pcr, low, high); for (UT_sint32 i = 0; iisFromThisDoc()) { UT_sint32 iCur = getDoc()->getAdjustmentForCR(pcrTmp); if(pcrTmp->getPosition() <= lowWork+iCur) { iAccumOffset += iCur; } lowWork = low + iAccumOffset; highWork = high + iAccumOffset; PT_DocPosition p1,p2; getCRRange(pcrTmp,p1,p2); bool bZero = (p1 == p2); if(bZero) lowWork++; if (doesOverlap(pcrTmp,lowWork,highWork)) { *ppcr = NULL; // // OK now we have to invalidate the undo stack // to just before the first pcr we pulled off. // if(m_undoPosition-iAdjust > 0) { m_iMinUndo = m_undoPosition-iAdjust-1; } else { m_iMinUndo = 0; } m_iAdjustOffset = iAdjust; m_iAdjustOffset++; return false; } } } iLoop++; } } PX_ChangeRecord * pcrOrig = pcr; if (bCorrect) { pcr->setAdjustment(0); PT_DocPosition pos = pcr->getPosition(); UT_sint32 iAdj = 0; UT_sint32 iCurrAdj = 0; PT_DocPosition low, high; getCRRange(pcr, low, high); for (UT_sint32 i = m_iAdjustOffset-1; i>=0;i--) { pcr = m_vecChangeRecords.getNthItem(m_undoPosition-i-1); if (!pcr->isFromThisDoc()) { iCurrAdj = getDoc()->getAdjustmentForCR(pcr); if(pcr->getPosition() <= static_cast(static_cast(pos) + iAdj + iCurrAdj)) { iAdj += iCurrAdj; low += iCurrAdj; high += iCurrAdj; } PT_DocPosition p1,p2; getCRRange(pcr,p1,p2); bool bZero = (p1 == p2); PT_DocPosition low1 = low; if(bZero) low1++; if (doesOverlap(pcr,low1,high)) { UT_DEBUGMSG(("CR Type %d adj pos %d Overlaps found with CR pos %d \n",pcrOrig->getType(),pcrOrig->getPosition()+iAdj,pcr->getPosition())); UT_DEBUGMSG((" Orig Adj low %d high %d \n",low,high)); *ppcr = NULL; m_iMinUndo = m_undoPosition-m_iAdjustOffset-1; return false; } } } pcrOrig->setAdjustment(iAdj); m_iAdjustOffset++; } UT_ASSERT(pcrOrig->isFromThisDoc()); *ppcr = pcrOrig; if(bStatic) m_iAdjustOffset = iAdjust; return true; } /*! * This method returns the nth element off the undo stack. * 0 returns the top element * 1 returns the next element * etc * The result is not adjusted for undo's in the presence of remote * changerecords and no attempt is made to see if an undo is legal * (ie doesn't overlap with a later remote CR) or not. */ bool px_ChangeHistory::getNthUndo(PX_ChangeRecord ** ppcr, UT_uint32 undoNdx) const { UT_sint32 iAdjust = static_cast(m_undoPosition) - m_iAdjustOffset; UT_sint32 iAdjIdx = static_cast(undoNdx); bool bGotOne = false; while(!bGotOne) { if (static_cast(iAdjust - iAdjIdx -1) <= static_cast(m_iMinUndo)) return false; PX_ChangeRecord * pcr = m_vecChangeRecords.getNthItem(iAdjust-iAdjIdx-1); UT_return_val_if_fail(pcr, false); if(pcr->isFromThisDoc()) { *ppcr = pcr; return true; } else { iAdjust--; } } return false; } bool px_ChangeHistory::getRedo(PX_ChangeRecord ** ppcr) const { if ((m_iAdjustOffset == 0) && (m_undoPosition >= m_vecChangeRecords.getItemCount())) return false; if (m_bOverlap) return false; UT_sint32 iRedoPos = m_undoPosition-m_iAdjustOffset; if(iRedoPos <0) return false; PX_ChangeRecord * pcr = m_vecChangeRecords.getNthItem(iRedoPos); UT_return_val_if_fail(pcr, false); // leave records from external documents in place so we can correct bool bIncrementAdjust = false; if (pcr->isFromThisDoc()) { *ppcr = pcr; if (m_iAdjustOffset == 0) { return true; } else { bIncrementAdjust = true; m_iAdjustOffset--; } } while (pcr && !pcr->isFromThisDoc() && (m_iAdjustOffset > 0)) { pcr = m_vecChangeRecords.getNthItem(iRedoPos); m_iAdjustOffset--; iRedoPos++; bIncrementAdjust = true; xxx_UT_DEBUGMSG(("AdjustOffset decremented -1 %d ", m_iAdjustOffset)); } if (pcr && bIncrementAdjust) { PX_ChangeRecord * pcrOrig = pcr; pcr->setAdjustment(0); PT_DocPosition low,high; getCRRange(pcr,low,high); PT_DocPosition pos = pcr->getPosition(); UT_sint32 iAdj = 0; for (UT_sint32 i = m_iAdjustOffset; i >= 1;i--) { pcr = m_vecChangeRecords.getNthItem(m_undoPosition-i); if (!pcr->isFromThisDoc()) { UT_sint32 iCur = getDoc()->getAdjustmentForCR(pcr); if (pcr->getPosition() <= static_cast(static_cast(pos) + iAdj + iCur)) { iAdj += iCur; low += iCur; high += iCur; } PT_DocPosition p1,p2; getCRRange(pcr,p1,p2); bool bZero = (p1 == p2); if(bZero) m_bOverlap = doesOverlap(pcr,low+1,high); else m_bOverlap = doesOverlap(pcr,low,high); if (m_bOverlap) { *ppcr = NULL; return false; } } } pcr = pcrOrig; pcr->setAdjustment(iAdj); xxx_UT_DEBUGMSG(("Redo Adjustment set to %d \n",iAdj)); } if (pcr && pcr->isFromThisDoc()) { *ppcr = pcr; if(bIncrementAdjust) { m_iAdjustOffset += 1; // for didRedo xxx_UT_DEBUGMSG(("AdjustOffset incremented -2 %d \n", m_iAdjustOffset)); } return true; } *ppcr = NULL; return false; } bool px_ChangeHistory::didUndo(void) { xxx_UT_DEBUGMSG((" Doing Undo void in PT undopos %d savePos pos %d \n",m_undoPosition,m_savePosition)); if (m_bOverlap) { clearHistory(); return false; } UT_return_val_if_fail(m_undoPosition > 0, false); UT_return_val_if_fail(m_undoPosition - m_iAdjustOffset > m_iMinUndo, false); PX_ChangeRecord * pcr = m_vecChangeRecords.getNthItem(m_undoPosition-m_iAdjustOffset-1); UT_return_val_if_fail(pcr && pcr->isFromThisDoc(), false); if (m_iAdjustOffset == 0) m_undoPosition--; pcr = m_vecChangeRecords.getNthItem(m_undoPosition-m_iAdjustOffset); if (pcr && !pcr->getPersistance()) { UT_return_val_if_fail(m_savePosition > 0,false); m_savePosition--; } return true; } bool px_ChangeHistory::didRedo(void) { xxx_UT_DEBUGMSG((" Doing didRedo void in PT undopos %d savePos pos %d iAdjustOffset %d \n",m_undoPosition,m_savePosition,m_iAdjustOffset)); if (m_bOverlap) { clearHistory(); return false; } if ((m_undoPosition - m_iAdjustOffset) >= m_vecChangeRecords.getItemCount()) return false; PX_ChangeRecord * pcr = m_vecChangeRecords.getNthItem(m_undoPosition - m_iAdjustOffset); // leave records from external documents in place so we can correct if (pcr && !pcr->isFromThisDoc() && (m_iAdjustOffset == 0)) return false; if (m_iAdjustOffset > 0) { m_iAdjustOffset--; xxx_UT_DEBUGMSG(("AdjustOffset decremented -3 redo %d ", m_iAdjustOffset)); } else { xxx_UT_DEBUGMSG(("Undo Position incremented in redo \n")); m_undoPosition++; } if (pcr && !pcr->getPersistance()) m_savePosition++; return true; } #if DEBUG void px_ChangeHistory::_printHistory(UT_sint32 iPrev) const { UT_sint32 i = 0; UT_sint32 iStop = 0; UT_sint32 iStart = 0; if(iPrev>0) { iStop =m_undoPosition-1 - iPrev; iStart = m_undoPosition-1; } else { iStart = m_vecChangeRecords.getItemCount() -1; iStop = iStart + iPrev; } if(iStop <0) iStop =0; for(i=iStart; i>= iStop;i--) { PX_ChangeRecord * pcr = m_vecChangeRecords.getNthItem(i); if(i != (m_undoPosition-m_iAdjustOffset-1)) { UT_DEBUGMSG((" loc %d pos %d type %d isLocal %d \n",i,pcr->getPosition(),pcr->getType(),pcr->isFromThisDoc())); } else { UT_DEBUGMSG((" loc %d pos %d type %d isLocal %d <- Current undo record \n",i,pcr->getPosition(),pcr->getType(),pcr->isFromThisDoc())); } } } #endif void px_ChangeHistory::coalesceHistory(const PX_ChangeRecord * pcr) { // coalesce this record with the current undo record. UT_sint32 iAdj = m_iAdjustOffset; PX_ChangeRecord * pcrUndo = m_vecChangeRecords.getNthItem(m_undoPosition-1); UT_return_if_fail (pcrUndo); UT_return_if_fail (pcr->getType() == pcrUndo->getType()); switch (pcr->getType()) { case PX_ChangeRecord::PXT_InsertSpan: case PX_ChangeRecord::PXT_DeleteSpan: { const PX_ChangeRecord_Span * pcrSpan = static_cast(pcr); PX_ChangeRecord_Span * pcrSpanUndo = static_cast(pcrUndo); if(pcr->isFromThisDoc()) { _invalidateRedo(); m_iAdjustOffset = 0; } else if(iAdj > 0) { m_iAdjustOffset = iAdj - 1; xxx_UT_DEBUGMSG(("AdjustOffset decremented - 3 %d ", m_iAdjustOffset)); } pcrSpanUndo->coalesce(pcrSpan); } return; default: UT_ASSERT_HARMLESS(0); return; } } void px_ChangeHistory::setClean(void) { m_savePosition = m_undoPosition; } bool px_ChangeHistory::isDirty(void) const { return (m_savePosition != m_undoPosition); } bool px_ChangeHistory::getCRRange(PX_ChangeRecord * pcr,PT_DocPosition & posLow, PT_DocPosition &posHigh) const { PT_DocPosition length = 0; length = static_cast(abs(getDoc()->getAdjustmentForCR(pcr))); posLow = pcr->getPosition(); posHigh = posLow+length; return true; } bool px_ChangeHistory:: doesOverlap(PX_ChangeRecord * pcr, PT_DocPosition low, PT_DocPosition high) const { PT_DocPosition crLow = 0, crHigh = 0; getCRRange(pcr, crLow, crHigh); if ((crLow>=low) && (crLowlow) && (crHigh<=high)) return true; return false; }