diff options
Diffstat (limited to 'sc/source/core/tool/chgtrack.cxx')
-rw-r--r-- | sc/source/core/tool/chgtrack.cxx | 4671 |
1 files changed, 4671 insertions, 0 deletions
diff --git a/sc/source/core/tool/chgtrack.cxx b/sc/source/core/tool/chgtrack.cxx new file mode 100644 index 000000000..55530e494 --- /dev/null +++ b/sc/source/core/tool/chgtrack.cxx @@ -0,0 +1,4671 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <chgtrack.hxx> +#include <compiler.hxx> +#include <formulacell.hxx> +#include <document.hxx> +#include <dociter.hxx> +#include <global.hxx> +#include <scmod.hxx> +#include <inputopt.hxx> +#include <patattr.hxx> +#include <hints.hxx> +#include <markdata.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <editutil.hxx> +#include <tokenarray.hxx> +#include <refupdatecontext.hxx> +#include <refupdat.hxx> + +#include <osl/diagnose.h> +#include <svl/numformat.hxx> +#include <sfx2/objsh.hxx> +#include <unotools/useroptions.hxx> +#include <unotools/datetime.hxx> +#include <tools/json_writer.hxx> +#include <algorithm> +#include <memory> + +ScChangeAction::ScChangeAction( ScChangeActionType eTypeP, const ScRange& rRange ) + : + aBigRange( rRange ), + aDateTime( DateTime::SYSTEM ), + pNext( nullptr ), + pPrev( nullptr ), + pLinkAny( nullptr ), + pLinkDeletedIn( nullptr ), + pLinkDeleted( nullptr ), + pLinkDependent( nullptr ), + nAction( 0 ), + nRejectAction( 0 ), + eType( eTypeP ), + eState( SC_CAS_VIRGIN ) +{ + aDateTime.ConvertToUTC(); +} + +ScChangeAction::ScChangeAction( + ScChangeActionType eTypeP, const ScBigRange& rRange, + const sal_uLong nTempAction, const sal_uLong nTempRejectAction, + const ScChangeActionState eTempState, const DateTime& aTempDateTime, + const OUString& aTempUser, const OUString& aTempComment) : + aBigRange( rRange ), + aDateTime( aTempDateTime ), + aUser( aTempUser ), + aComment( aTempComment ), + pNext( nullptr ), + pPrev( nullptr ), + pLinkAny( nullptr ), + pLinkDeletedIn( nullptr ), + pLinkDeleted( nullptr ), + pLinkDependent( nullptr ), + nAction( nTempAction ), + nRejectAction( nTempRejectAction ), + eType( eTypeP ), + eState( eTempState ) +{ +} + +ScChangeAction::ScChangeAction( ScChangeActionType eTypeP, const ScBigRange& rRange, + const sal_uLong nTempAction) + : + aBigRange( rRange ), + aDateTime( DateTime::SYSTEM ), + pNext( nullptr ), + pPrev( nullptr ), + pLinkAny( nullptr ), + pLinkDeletedIn( nullptr ), + pLinkDeleted( nullptr ), + pLinkDependent( nullptr ), + nAction( nTempAction ), + nRejectAction( 0 ), + eType( eTypeP ), + eState( SC_CAS_VIRGIN ) +{ + aDateTime.ConvertToUTC(); +} + +ScChangeAction::~ScChangeAction() +{ + RemoveAllLinks(); +} + +bool ScChangeAction::IsInsertType() const +{ + return eType == SC_CAT_INSERT_COLS || eType == SC_CAT_INSERT_ROWS || eType == SC_CAT_INSERT_TABS; +} + +bool ScChangeAction::IsDeleteType() const +{ + return eType == SC_CAT_DELETE_COLS || eType == SC_CAT_DELETE_ROWS || eType == SC_CAT_DELETE_TABS; +} + +bool ScChangeAction::IsVirgin() const +{ + return eState == SC_CAS_VIRGIN; +} + +bool ScChangeAction::IsAccepted() const +{ + return eState == SC_CAS_ACCEPTED; +} + +bool ScChangeAction::IsRejected() const +{ + return eState == SC_CAS_REJECTED; +} + +bool ScChangeAction::IsRejecting() const +{ + return nRejectAction != 0; +} + +bool ScChangeAction::IsVisible() const +{ + // sequence order of execution is significant! + if ( IsRejected() || GetType() == SC_CAT_DELETE_TABS || IsDeletedIn() ) + return false; + if ( GetType() == SC_CAT_CONTENT ) + return static_cast<const ScChangeActionContent*>(this)->IsTopContent(); + return true; +} + +bool ScChangeAction::IsTouchable() const +{ + // sequence order of execution is significant! + if ( IsRejected() || GetType() == SC_CAT_REJECT || IsDeletedIn() ) + return false; + // content may reject and be touchable if on top + if ( GetType() == SC_CAT_CONTENT ) + return static_cast<const ScChangeActionContent*>(this)->IsTopContent(); + if ( IsRejecting() ) + return false; + return true; +} + +bool ScChangeAction::IsClickable() const +{ + // sequence order of execution is significant! + if ( !IsVirgin() ) + return false; + if ( IsDeletedIn() ) + return false; + if ( GetType() == SC_CAT_CONTENT ) + { + ScChangeActionContentCellType eCCT = + ScChangeActionContent::GetContentCellType( + static_cast<const ScChangeActionContent*>(this)->GetNewCell() ); + if ( eCCT == SC_CACCT_MATREF ) + return false; + if ( eCCT == SC_CACCT_MATORG ) + { // no Accept-Select if one of the references is in a deleted col/row + const ScChangeActionLinkEntry* pL = + static_cast<const ScChangeActionContent*>(this)->GetFirstDependentEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p && p->IsDeletedIn() ) + return false; + pL = pL->GetNext(); + } + } + return true; // for Select() a content doesn't have to be touchable + } + return IsTouchable(); // Accept()/Reject() only on touchables +} + +bool ScChangeAction::IsRejectable() const +{ + // sequence order of execution is significant! + if ( !IsClickable() ) + return false; + if ( GetType() == SC_CAT_CONTENT ) + { + if ( static_cast<const ScChangeActionContent*>(this)->IsOldMatrixReference() ) + return false; + ScChangeActionContent* pNextContent = + static_cast<const ScChangeActionContent*>(this)->GetNextContent(); + if ( pNextContent == nullptr ) + return true; // *this is TopContent + return pNextContent->IsRejected(); // *this is next rejectable + } + return IsTouchable(); +} + +bool ScChangeAction::IsInternalRejectable() const +{ + // sequence order of execution is significant! + if ( !IsVirgin() ) + return false; + if ( IsDeletedIn() ) + return false; + if ( GetType() == SC_CAT_CONTENT ) + { + ScChangeActionContent* pNextContent = + static_cast<const ScChangeActionContent*>(this)->GetNextContent(); + if ( pNextContent == nullptr ) + return true; // *this is TopContent + return pNextContent->IsRejected(); // *this is next rejectable + } + return IsTouchable(); +} + +bool ScChangeAction::IsDialogRoot() const +{ + return IsInternalRejectable(); // only rejectables in root +} + +bool ScChangeAction::IsDialogParent() const +{ + // sequence order of execution is significant! + if ( GetType() == SC_CAT_CONTENT ) + { + if ( !IsDialogRoot() ) + return false; + if ( static_cast<const ScChangeActionContent*>(this)->IsMatrixOrigin() && HasDependent() ) + return true; + ScChangeActionContent* pPrevContent = + static_cast<const ScChangeActionContent*>(this)->GetPrevContent(); + return pPrevContent && pPrevContent->IsVirgin(); + } + if ( HasDependent() ) + return IsDeleteType() || !IsDeletedIn(); + if ( HasDeleted() ) + { + if ( IsDeleteType() ) + { + if ( IsDialogRoot() ) + return true; + ScChangeActionLinkEntry* pL = pLinkDeleted; + while ( pL ) + { + ScChangeAction* p = pL->GetAction(); + if ( p && p->GetType() != eType ) + return true; + pL = pL->GetNext(); + } + } + else + return true; + } + return false; +} + +bool ScChangeAction::IsMasterDelete() const +{ + if ( !IsDeleteType() ) + return false; + const ScChangeActionDel* pDel = static_cast<const ScChangeActionDel*>(this); + return pDel->IsMultiDelete() && (pDel->IsTopDelete() || pDel->IsRejectable()); +} + +void ScChangeAction::RemoveAllLinks() +{ + while (pLinkAny) + { + // coverity[use_after_free] - Moves up by itself + delete pLinkAny; + } + + RemoveAllDeletedIn(); + + while (pLinkDeleted) + { + // coverity[use_after_free] - Moves up by itself + delete pLinkDeleted; + } + + RemoveAllDependent(); +} + +bool ScChangeAction::RemoveDeletedIn( const ScChangeAction* p ) +{ + bool bRemoved = false; + ScChangeActionLinkEntry* pL = GetDeletedIn(); + while ( pL ) + { + ScChangeActionLinkEntry* pNextLink = pL->GetNext(); + if ( pL->GetAction() == p ) + { + delete pL; + bRemoved = true; + } + pL = pNextLink; + } + return bRemoved; +} + +bool ScChangeAction::IsDeletedIn() const +{ + return GetDeletedIn() != nullptr; +} + +bool ScChangeAction::IsDeletedIn( const ScChangeAction* p ) const +{ + ScChangeActionLinkEntry* pL = GetDeletedIn(); + while ( pL ) + { + if ( pL->GetAction() == p ) + return true; + pL = pL->GetNext(); + } + return false; +} + +void ScChangeAction::RemoveAllDeletedIn() +{ + //TODO: Not from TopContent, but really this one + while (pLinkDeletedIn) + { + // coverity[use_after_free] - Moves up by itself + delete pLinkDeletedIn; + } +} + +bool ScChangeAction::IsDeletedInDelType( ScChangeActionType eDelType ) const +{ + ScChangeActionLinkEntry* pL = GetDeletedIn(); + if ( pL ) + { + // InsertType for MergePrepare/MergeOwn + ScChangeActionType eInsType; + switch ( eDelType ) + { + case SC_CAT_DELETE_COLS : + eInsType = SC_CAT_INSERT_COLS; + break; + case SC_CAT_DELETE_ROWS : + eInsType = SC_CAT_INSERT_ROWS; + break; + case SC_CAT_DELETE_TABS : + eInsType = SC_CAT_INSERT_TABS; + break; + default: + eInsType = SC_CAT_NONE; + } + while ( pL ) + { + ScChangeAction* p = pL->GetAction(); + if ( p != nullptr && (p->GetType() == eDelType || p->GetType() == eInsType) ) + return true; + pL = pL->GetNext(); + } + } + return false; +} + +bool ScChangeAction::HasDependent() const +{ + return pLinkDependent != nullptr; +} + +bool ScChangeAction::HasDeleted() const +{ + return pLinkDeleted != nullptr; +} + +void ScChangeAction::SetDeletedIn( ScChangeAction* p ) +{ + ScChangeActionLinkEntry* pLink1 = new ScChangeActionLinkEntry( GetDeletedInAddress(), p ); + ScChangeActionLinkEntry* pLink2; + if ( GetType() == SC_CAT_CONTENT ) + pLink2 = p->AddDeleted( static_cast<ScChangeActionContent*>(this)->GetTopContent() ); + else + pLink2 = p->AddDeleted( this ); + pLink1->SetLink( pLink2 ); +} + +void ScChangeAction::RemoveAllDependent() +{ + while (pLinkDependent) + { + // coverity[use_after_free] - Moves up by itself + delete pLinkDependent; + } +} + +DateTime ScChangeAction::GetDateTime() const +{ + DateTime aDT( aDateTime ); + aDT.ConvertToLocalTime(); + return aDT; +} + +void ScChangeAction::UpdateReference( const ScChangeTrack* /* pTrack */, + UpdateRefMode eMode, const ScBigRange& rRange, + sal_Int32 nDx, sal_Int32 nDy, sal_Int32 nDz ) +{ + ScRefUpdate::Update( eMode, rRange, nDx, nDy, nDz, GetBigRange() ); +} + +OUString ScChangeAction::GetDescription( + ScDocument& /* rDoc */, bool /* bSplitRange */, bool bWarning ) const +{ + if (!IsRejecting() || !bWarning) + return OUString(); + + // Add comment if rejection may have resulted in references + // not properly restored in formulas. See specification at + // http://specs.openoffice.org/calc/ease-of-use/redlining_comment.sxw + + if (GetType() == SC_CAT_MOVE) + { + return ScResId(STR_CHANGED_MOVE_REJECTION_WARNING) + " "; + } + + if (IsInsertType()) + { + return ScResId(STR_CHANGED_DELETE_REJECTION_WARNING) + " "; + } + + const ScChangeTrack* pCT = GetChangeTrack(); + if (!pCT) + return OUString(); + + ScChangeAction* pReject = pCT->GetActionOrGenerated(GetRejectAction()); + + if (!pReject) + return OUString(); + + if (pReject->GetType() == SC_CAT_MOVE) + { + return ScResId(STR_CHANGED_MOVE_REJECTION_WARNING) + " "; + } + + if (pReject->IsDeleteType()) + { + return ScResId(STR_CHANGED_DELETE_REJECTION_WARNING) + " "; + } + + if (!pReject->HasDependent()) + return OUString(); + + ScChangeActionMap aMap; + pCT->GetDependents( pReject, aMap, false, true ); + ScChangeActionMap::iterator itChangeAction = std::find_if(aMap.begin(), aMap.end(), + [&pReject](const ScChangeActionMap::value_type& rEntry) { + return rEntry.second->GetType() == SC_CAT_MOVE || pReject->IsDeleteType(); }); + if (itChangeAction == aMap.end()) + return OUString(); + + if( itChangeAction->second->GetType() == SC_CAT_MOVE) + return ScResId(STR_CHANGED_MOVE_REJECTION_WARNING) + " "; + else + return ScResId(STR_CHANGED_DELETE_REJECTION_WARNING) + " "; +} + +OUString ScChangeAction::GetRefString( + const ScBigRange& rRange, const ScDocument& rDoc, bool bFlag3D ) const +{ + OUStringBuffer aBuf; + ScRefFlags nFlags = ( rRange.IsValid( rDoc ) ? ScRefFlags::VALID : ScRefFlags::ZERO ); + if ( nFlags == ScRefFlags::ZERO ) + aBuf.append(ScCompiler::GetNativeSymbol(ocErrRef)); + else + { + ScRange aTmpRange( rRange.MakeRange( rDoc ) ); + switch ( GetType() ) + { + case SC_CAT_INSERT_COLS : + case SC_CAT_DELETE_COLS : + if ( bFlag3D ) + { + OUString aTmp; + rDoc.GetName( aTmpRange.aStart.Tab(), aTmp ); + aBuf.append(aTmp); + aBuf.append('.'); + } + aBuf.append(ScColToAlpha(aTmpRange.aStart.Col())); + aBuf.append(':'); + aBuf.append(ScColToAlpha(aTmpRange.aEnd.Col())); + break; + case SC_CAT_INSERT_ROWS : + case SC_CAT_DELETE_ROWS : + if ( bFlag3D ) + { + OUString aTmp; + rDoc.GetName( aTmpRange.aStart.Tab(), aTmp ); + aBuf.append(aTmp); + aBuf.append('.'); + } + aBuf.append(static_cast<sal_Int64>(aTmpRange.aStart.Row()+1)); + aBuf.append(':'); + aBuf.append(static_cast<sal_Int64>(aTmpRange.aEnd.Row()+1)); + break; + default: + { + if ( bFlag3D || GetType() == SC_CAT_INSERT_TABS ) + nFlags |= ScRefFlags::TAB_3D; + + aBuf.append(aTmpRange.Format(rDoc, nFlags, rDoc.GetAddressConvention())); + } + } + if ( (bFlag3D && IsDeleteType()) || IsDeletedIn() ) + { + aBuf.insert(0, '('); + aBuf.append(')'); + } + } + return aBuf.makeStringAndClear(); +} + +void ScChangeAction::SetUser( const OUString& r ) +{ + aUser = r; +} + +void ScChangeAction::SetComment( const OUString& rStr ) +{ + aComment = rStr; +} + +OUString ScChangeAction::GetRefString( ScDocument& rDoc, bool bFlag3D ) const +{ + return GetRefString( GetBigRange(), rDoc, bFlag3D ); +} + +void ScChangeAction::Accept() +{ + if ( IsVirgin() ) + { + SetState( SC_CAS_ACCEPTED ); + DeleteCellEntries(); + } +} + +void ScChangeAction::SetRejected() +{ + if ( IsVirgin() ) + { + SetState( SC_CAS_REJECTED ); + RemoveAllLinks(); + DeleteCellEntries(); + } +} + +void ScChangeAction::RejectRestoreContents( ScChangeTrack* pTrack, + SCCOL nDx, SCROW nDy ) +{ + // Construct list of Contents + std::vector<ScChangeActionContent*> aContentsList; + for ( ScChangeActionLinkEntry* pL = pLinkDeleted; pL; pL = pL->GetNext() ) + { + ScChangeAction* p = pL->GetAction(); + if ( p && p->GetType() == SC_CAT_CONTENT ) + { + aContentsList.push_back(static_cast<ScChangeActionContent*>(p) ); + } + } + SetState( SC_CAS_REJECTED ); // Before UpdateReference for Move + pTrack->UpdateReference( this, true ); // Free LinkDeleted + OSL_ENSURE( !pLinkDeleted, "ScChangeAction::RejectRestoreContents: pLinkDeleted != NULL" ); + + // Work through list of Contents and delete + ScDocument& rDoc = pTrack->GetDocument(); + for (ScChangeActionContent* pContent : aContentsList) + { + if ( !pContent->IsDeletedIn() && + pContent->GetBigRange().aStart.IsValid( rDoc ) ) + pContent->PutNewValueToDoc( &rDoc, nDx, nDy ); + } + DeleteCellEntries(); // Remove generated ones +} + +void ScChangeAction::SetDeletedInThis( sal_uLong nActionNumber, + const ScChangeTrack* pTrack ) +{ + if ( nActionNumber ) + { + ScChangeAction* pAct = pTrack->GetActionOrGenerated( nActionNumber ); + OSL_ENSURE( pAct, "ScChangeAction::SetDeletedInThis: missing Action" ); + if ( pAct ) + pAct->SetDeletedIn( this ); + } +} + +void ScChangeAction::AddDependent( sal_uLong nActionNumber, + const ScChangeTrack* pTrack ) +{ + if ( nActionNumber ) + { + ScChangeAction* pAct = pTrack->GetActionOrGenerated( nActionNumber ); + OSL_ENSURE( pAct, "ScChangeAction::AddDependent: missing Action" ); + if ( pAct ) + { + ScChangeActionLinkEntry* pLink = AddDependent( pAct ); + pAct->AddLink( this, pLink ); + } + } +} + +// ScChangeActionIns +ScChangeActionIns::ScChangeActionIns( const ScDocument* pDoc, const ScRange& rRange, bool bEndOfList ) : + ScChangeAction(SC_CAT_NONE, rRange), + mbEndOfList(bEndOfList) +{ + if ( rRange.aStart.Col() == 0 && rRange.aEnd.Col() == pDoc->MaxCol() ) + { + aBigRange.aStart.SetCol( ScBigRange::nRangeMin ); + aBigRange.aEnd.SetCol( ScBigRange::nRangeMax ); + if ( rRange.aStart.Row() == 0 && rRange.aEnd.Row() == pDoc->MaxRow() ) + { + SetType( SC_CAT_INSERT_TABS ); + aBigRange.aStart.SetRow( ScBigRange::nRangeMin ); + aBigRange.aEnd.SetRow( ScBigRange::nRangeMax ); + } + else + SetType( SC_CAT_INSERT_ROWS ); + } + else if ( rRange.aStart.Row() == 0 && rRange.aEnd.Row() == pDoc->MaxRow() ) + { + SetType( SC_CAT_INSERT_COLS ); + aBigRange.aStart.SetRow( ScBigRange::nRangeMin ); + aBigRange.aEnd.SetRow( ScBigRange::nRangeMax ); + } + else + { + OSL_FAIL( "ScChangeActionIns: Block not supported!" ); + } +} + +ScChangeActionIns::ScChangeActionIns( + const sal_uLong nActionNumber, const ScChangeActionState eStateP, + const sal_uLong nRejectingNumber, const ScBigRange& aBigRangeP, + const OUString& aUserP, const DateTime& aDateTimeP, + const OUString& sComment, const ScChangeActionType eTypeP, + bool bEndOfList ) : + ScChangeAction(eTypeP, aBigRangeP, nActionNumber, nRejectingNumber, eStateP, aDateTimeP, aUserP, sComment), + mbEndOfList(bEndOfList) +{ +} + +ScChangeActionIns::~ScChangeActionIns() +{ +} + +OUString ScChangeActionIns::GetDescription( ScDocument& rDoc, bool bSplitRange, bool bWarning ) const +{ + OUString str = ScChangeAction::GetDescription( rDoc, bSplitRange, bWarning ); + + TranslateId pWhatId; + switch ( GetType() ) + { + case SC_CAT_INSERT_COLS : + pWhatId = STR_COLUMN; + break; + case SC_CAT_INSERT_ROWS : + pWhatId = STR_ROW; + break; + default: + pWhatId = STR_AREA; + } + + OUString aRsc = ScResId(STR_CHANGED_INSERT); + sal_Int32 nPos = aRsc.indexOf("#1"); + if (nPos < 0) + return str; + + // Construct a range string to replace '#1' first. + OUString aRangeStr = ScResId(pWhatId) + + " " + + GetRefString(GetBigRange(), rDoc); + + aRsc = aRsc.replaceAt(nPos, 2, aRangeStr); // replace '#1' with the range string. + + return str + aRsc; +} + +bool ScChangeActionIns::IsEndOfList() const +{ + return mbEndOfList; +} + +bool ScChangeActionIns::Reject( ScDocument& rDoc ) +{ + if ( !aBigRange.IsValid( rDoc ) ) + return false; + + ScRange aRange( aBigRange.MakeRange( rDoc ) ); + if ( !rDoc.IsBlockEditable( aRange.aStart.Tab(), aRange.aStart.Col(), + aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row() ) ) + return false; + + switch ( GetType() ) + { + case SC_CAT_INSERT_COLS : + rDoc.DeleteCol( aRange ); + break; + case SC_CAT_INSERT_ROWS : + rDoc.DeleteRow( aRange ); + break; + case SC_CAT_INSERT_TABS : + rDoc.DeleteTab( aRange.aStart.Tab() ); + break; + default: + { + // added to avoid warnings + } + } + SetState( SC_CAS_REJECTED ); + RemoveAllLinks(); + return true; +} + +// ScChangeActionDel +ScChangeActionDel::ScChangeActionDel( const ScDocument* pDoc, const ScRange& rRange, + SCCOL nDxP, SCROW nDyP, ScChangeTrack* pTrackP ) + : + ScChangeAction( SC_CAT_NONE, rRange ), + pTrack( pTrackP ), + pCutOff( nullptr ), + nCutOff( 0 ), + pLinkMove( nullptr ), + nDx( nDxP ), + nDy( nDyP ) +{ + if ( rRange.aStart.Col() == 0 && rRange.aEnd.Col() == pDoc->MaxCol() ) + { + aBigRange.aStart.SetCol( ScBigRange::nRangeMin ); + aBigRange.aEnd.SetCol( ScBigRange::nRangeMax ); + if ( rRange.aStart.Row() == 0 && rRange.aEnd.Row() == pDoc->MaxRow() ) + { + SetType( SC_CAT_DELETE_TABS ); + aBigRange.aStart.SetRow( ScBigRange::nRangeMin ); + aBigRange.aEnd.SetRow( ScBigRange::nRangeMax ); + } + else + SetType( SC_CAT_DELETE_ROWS ); + } + else if ( rRange.aStart.Row() == 0 && rRange.aEnd.Row() == pDoc->MaxRow() ) + { + SetType( SC_CAT_DELETE_COLS ); + aBigRange.aStart.SetRow( ScBigRange::nRangeMin ); + aBigRange.aEnd.SetRow( ScBigRange::nRangeMax ); + } + else + { + OSL_FAIL( "ScChangeActionDel: Block not supported!" ); + } +} + +ScChangeActionDel::ScChangeActionDel( + const sal_uLong nActionNumber, const ScChangeActionState eStateP, + const sal_uLong nRejectingNumber, const ScBigRange& aBigRangeP, + const OUString& aUserP, const DateTime& aDateTimeP, const OUString &sComment, + const ScChangeActionType eTypeP, const SCCOLROW nD, ScChangeTrack* pTrackP) : // which of nDx and nDy is set depends on the type + ScChangeAction(eTypeP, aBigRangeP, nActionNumber, nRejectingNumber, eStateP, aDateTimeP, aUserP, sComment), + pTrack( pTrackP ), + pCutOff( nullptr ), + nCutOff( 0 ), + pLinkMove( nullptr ), + nDx( 0 ), + nDy( 0 ) +{ + if (eType == SC_CAT_DELETE_COLS) + nDx = static_cast<SCCOL>(nD); + else if (eType == SC_CAT_DELETE_ROWS) + nDy = static_cast<SCROW>(nD); +} + +ScChangeActionDel::~ScChangeActionDel() +{ + DeleteCellEntries(); + while (pLinkMove) + { + // coverity[use_after_free] - Moves up by itself + delete pLinkMove; + } +} + +void ScChangeActionDel::AddContent( ScChangeActionContent* pContent ) +{ + mvCells.push_back(pContent); +} + +void ScChangeActionDel::DeleteCellEntries() +{ + pTrack->DeleteCellEntries( mvCells, this ); +} + +bool ScChangeActionDel::IsBaseDelete() const +{ + return !GetDx() && !GetDy(); +} + +bool ScChangeActionDel::IsTopDelete() const +{ + const ScChangeAction* p = GetNext(); + if ( !p || p->GetType() != GetType() ) + return true; + return static_cast<const ScChangeActionDel*>(p)->IsBaseDelete(); +} + +bool ScChangeActionDel::IsMultiDelete() const +{ + if ( GetDx() || GetDy() ) + return true; + const ScChangeAction* p = GetNext(); + if ( !p || p->GetType() != GetType() ) + return false; + const ScChangeActionDel* pDel = static_cast<const ScChangeActionDel*>(p); + return (pDel->GetDx() > GetDx() || pDel->GetDy() > GetDy()) && + pDel->GetBigRange() == aBigRange; +} + +bool ScChangeActionDel::IsTabDeleteCol() const +{ + if ( GetType() != SC_CAT_DELETE_COLS ) + return false; + const ScChangeAction* p = this; + while ( p && p->GetType() == SC_CAT_DELETE_COLS && + !static_cast<const ScChangeActionDel*>(p)->IsTopDelete() ) + p = p->GetNext(); + return p && p->GetType() == SC_CAT_DELETE_TABS; +} + +ScChangeActionDelMoveEntry* ScChangeActionDel::AddCutOffMove( + ScChangeActionMove* pMove, short nFrom, short nTo ) +{ + return new ScChangeActionDelMoveEntry(&pLinkMove, pMove, nFrom, nTo); +} + +void ScChangeActionDel::UpdateReference( const ScChangeTrack* /* pTrack */, + UpdateRefMode eMode, const ScBigRange& rRange, + sal_Int32 nDxP, sal_Int32 nDyP, sal_Int32 nDz ) +{ + ScRefUpdate::Update( eMode, rRange, nDxP, nDyP, nDz, GetBigRange() ); + + if ( !IsDeletedIn() ) + return ; + + // Correct in the ones who slipped through + for ( ScChangeActionLinkEntry* pL = pLinkDeleted; pL; pL = pL->GetNext() ) + { + ScChangeAction* p = pL->GetAction(); + if ( p && p->GetType() == SC_CAT_CONTENT && + !GetBigRange().Contains( p->GetBigRange() ) ) + { + switch ( GetType() ) + { + case SC_CAT_DELETE_COLS : + p->GetBigRange().aStart.SetCol( GetBigRange().aStart.Col() ); + p->GetBigRange().aEnd.SetCol( GetBigRange().aStart.Col() ); + break; + case SC_CAT_DELETE_ROWS : + p->GetBigRange().aStart.SetRow( GetBigRange().aStart.Row() ); + p->GetBigRange().aEnd.SetRow( GetBigRange().aStart.Row() ); + break; + case SC_CAT_DELETE_TABS : + p->GetBigRange().aStart.SetTab( GetBigRange().aStart.Tab() ); + p->GetBigRange().aEnd.SetTab( GetBigRange().aStart.Tab() ); + break; + default: + { + // added to avoid warnings + } + } + } + } +} + +ScBigRange ScChangeActionDel::GetOverAllRange() const +{ + ScBigRange aTmpRange( GetBigRange() ); + aTmpRange.aEnd.SetCol( aTmpRange.aEnd.Col() + GetDx() ); + aTmpRange.aEnd.SetRow( aTmpRange.aEnd.Row() + GetDy() ); + return aTmpRange; +} + +OUString ScChangeActionDel::GetDescription( ScDocument& rDoc, bool bSplitRange, bool bWarning ) const +{ + OUString str = ScChangeAction::GetDescription( rDoc, bSplitRange, bWarning ); + + TranslateId pWhatId; + switch ( GetType() ) + { + case SC_CAT_DELETE_COLS : + pWhatId = STR_COLUMN; + break; + case SC_CAT_DELETE_ROWS : + pWhatId = STR_ROW; + break; + default: + pWhatId = STR_AREA; + } + + ScBigRange aTmpRange( GetBigRange() ); + if ( !IsRejected() ) + { + if ( bSplitRange ) + { + aTmpRange.aStart.SetCol( aTmpRange.aStart.Col() + GetDx() ); + aTmpRange.aStart.SetRow( aTmpRange.aStart.Row() + GetDy() ); + } + aTmpRange.aEnd.SetCol( aTmpRange.aEnd.Col() + GetDx() ); + aTmpRange.aEnd.SetRow( aTmpRange.aEnd.Row() + GetDy() ); + } + + OUString aRsc = ScResId(STR_CHANGED_DELETE); + sal_Int32 nPos = aRsc.indexOf("#1"); + if (nPos < 0) + return str; + + // Build a string to replace with. + OUString aRangeStr = ScResId(pWhatId) + " " + + GetRefString(aTmpRange, rDoc); + aRsc = aRsc.replaceAt(nPos, 2, aRangeStr); // replace '#1' with the string. + + return str + aRsc; // append to the original. +} + +bool ScChangeActionDel::Reject( ScDocument& rDoc ) +{ + if ( !aBigRange.IsValid( rDoc ) && GetType() != SC_CAT_DELETE_TABS ) + return false; + + if ( IsTopDelete() ) + { // Restore whole section in one go + bool bOk = true; + ScBigRange aTmpRange( GetOverAllRange() ); + if ( !aTmpRange.IsValid( rDoc ) ) + { + if ( GetType() == SC_CAT_DELETE_TABS ) + { // Do we attach a Tab? + if ( aTmpRange.aStart.Tab() > rDoc.GetMaxTableNumber() ) + bOk = false; + } + else + bOk = false; + } + if ( bOk ) + { + ScRange aRange( aTmpRange.MakeRange( rDoc ) ); + // InDelete... for formula UpdateReference in Document + pTrack->SetInDeleteRange( aRange ); + pTrack->SetInDeleteTop( true ); + pTrack->SetInDeleteUndo( true ); + pTrack->SetInDelete( true ); + switch ( GetType() ) + { + case SC_CAT_DELETE_COLS : + if ( aRange.aStart.Col() != 0 || aRange.aEnd.Col() != rDoc.MaxCol() ) + { // Only if not TabDelete + bOk = rDoc.CanInsertCol( aRange ) && rDoc.InsertCol( aRange ); + } + break; + case SC_CAT_DELETE_ROWS : + bOk = rDoc.CanInsertRow( aRange ) && rDoc.InsertRow( aRange ); + break; + case SC_CAT_DELETE_TABS : + { + //TODO: Remember table names? + OUString aName; + rDoc.CreateValidTabName( aName ); + bOk = rDoc.ValidNewTabName( aName ) && rDoc.InsertTab( aRange.aStart.Tab(), aName ); + } + break; + default: + { + // added to avoid warnings + } + } + pTrack->SetInDelete( false ); + pTrack->SetInDeleteUndo( false ); + } + if ( !bOk ) + { + pTrack->SetInDeleteTop( false ); + return false; + } + // Keep InDeleteTop for UpdateReference Undo + } + + // Sets rejected and calls UpdateReference-Undo and DeleteCellEntries + RejectRestoreContents( pTrack, GetDx(), GetDy() ); + + pTrack->SetInDeleteTop( false ); + RemoveAllLinks(); + return true; +} + +void ScChangeActionDel::UndoCutOffMoves() +{ // Restore cut off Moves; delete Entries/Links + while ( pLinkMove ) + { + // coverity[deref_arg] - the call on delete pLinkMove at the block end Moves a new entry into pLinkMode by itself + ScChangeActionMove* pMove = pLinkMove->GetMove(); + short nFrom = pLinkMove->GetCutOffFrom(); + short nTo = pLinkMove->GetCutOffTo(); + switch ( GetType() ) + { + case SC_CAT_DELETE_COLS : + if ( nFrom > 0 ) + pMove->GetFromRange().aStart.IncCol( -nFrom ); + else if ( nFrom < 0 ) + pMove->GetFromRange().aEnd.IncCol( -nFrom ); + if ( nTo > 0 ) + pMove->GetBigRange().aStart.IncCol( -nTo ); + else if ( nTo < 0 ) + pMove->GetBigRange().aEnd.IncCol( -nTo ); + break; + case SC_CAT_DELETE_ROWS : + if ( nFrom > 0 ) + pMove->GetFromRange().aStart.IncRow( -nFrom ); + else if ( nFrom < 0 ) + pMove->GetFromRange().aEnd.IncRow( -nFrom ); + if ( nTo > 0 ) + pMove->GetBigRange().aStart.IncRow( -nTo ); + else if ( nTo < 0 ) + pMove->GetBigRange().aEnd.IncRow( -nTo ); + break; + case SC_CAT_DELETE_TABS : + if ( nFrom > 0 ) + pMove->GetFromRange().aStart.IncTab( -nFrom ); + else if ( nFrom < 0 ) + pMove->GetFromRange().aEnd.IncTab( -nFrom ); + if ( nTo > 0 ) + pMove->GetBigRange().aStart.IncTab( -nTo ); + else if ( nTo < 0 ) + pMove->GetBigRange().aEnd.IncTab( -nTo ); + break; + default: + { + // added to avoid warnings + } + } + delete pLinkMove; // Moves up by itself + } +} + +void ScChangeActionDel::UndoCutOffInsert() +{ //Restore cut off Insert + if ( !pCutOff ) + return; + + switch ( pCutOff->GetType() ) + { + case SC_CAT_INSERT_COLS : + if ( nCutOff < 0 ) + pCutOff->GetBigRange().aEnd.IncCol( -nCutOff ); + else + pCutOff->GetBigRange().aStart.IncCol( -nCutOff ); + break; + case SC_CAT_INSERT_ROWS : + if ( nCutOff < 0 ) + pCutOff->GetBigRange().aEnd.IncRow( -nCutOff ); + else + pCutOff->GetBigRange().aStart.IncRow( -nCutOff ); + break; + case SC_CAT_INSERT_TABS : + if ( nCutOff < 0 ) + pCutOff->GetBigRange().aEnd.IncTab( -nCutOff ); + else + pCutOff->GetBigRange().aStart.IncTab( -nCutOff ); + break; + default: + { + // added to avoid warnings + } + } + SetCutOffInsert( nullptr, 0 ); +} + +// ScChangeActionMove +ScChangeActionMove::ScChangeActionMove( + const sal_uLong nActionNumber, const ScChangeActionState eStateP, + const sal_uLong nRejectingNumber, const ScBigRange& aToBigRange, + const OUString& aUserP, const DateTime& aDateTimeP, + const OUString &sComment, const ScBigRange& aFromBigRange, + ScChangeTrack* pTrackP) : // which of nDx and nDy is set depends on the type + ScChangeAction(SC_CAT_MOVE, aToBigRange, nActionNumber, nRejectingNumber, eStateP, aDateTimeP, aUserP, sComment), + aFromRange(aFromBigRange), + pTrack( pTrackP ), + nStartLastCut(0), + nEndLastCut(0) +{ +} + +ScChangeActionMove::~ScChangeActionMove() +{ + DeleteCellEntries(); +} + +void ScChangeActionMove::AddContent( ScChangeActionContent* pContent ) +{ + mvCells.push_back(pContent); +} + +void ScChangeActionMove::DeleteCellEntries() +{ + pTrack->DeleteCellEntries( mvCells, this ); +} + +void ScChangeActionMove::UpdateReference( const ScChangeTrack* /* pTrack */, + UpdateRefMode eMode, const ScBigRange& rRange, + sal_Int32 nDx, sal_Int32 nDy, sal_Int32 nDz ) +{ + ScRefUpdate::Update( eMode, rRange, nDx, nDy, nDz, aFromRange ); + ScRefUpdate::Update( eMode, rRange, nDx, nDy, nDz, GetBigRange() ); +} + +void ScChangeActionMove::GetDelta( sal_Int32& nDx, sal_Int32& nDy, sal_Int32& nDz ) const +{ + const ScBigAddress& rToPos = GetBigRange().aStart; + const ScBigAddress& rFromPos = GetFromRange().aStart; + nDx = rToPos.Col() - rFromPos.Col(); + nDy = rToPos.Row() - rFromPos.Row(); + nDz = rToPos.Tab() - rFromPos.Tab(); +} + +OUString ScChangeActionMove::GetDescription( + ScDocument& rDoc, bool bSplitRange, bool bWarning ) const +{ + OUString str = ScChangeAction::GetDescription( rDoc, bSplitRange, bWarning ); + + bool bFlag3D = GetFromRange().aStart.Tab() != GetBigRange().aStart.Tab(); + + OUString aRsc = ScResId(STR_CHANGED_MOVE); + + OUString aTmpStr = ScChangeAction::GetRefString(GetFromRange(), rDoc, bFlag3D); + sal_Int32 nPos = aRsc.indexOf("#1"); + if (nPos >= 0) + { + aRsc = aRsc.replaceAt(nPos, 2, aTmpStr); + nPos += aTmpStr.getLength(); + } + + aTmpStr = ScChangeAction::GetRefString(GetBigRange(), rDoc, bFlag3D); + nPos = nPos >= 0 ? aRsc.indexOf("#2", nPos) : -1; + if (nPos >= 0) + { + aRsc = aRsc.replaceAt(nPos, 2, aTmpStr); + } + + return str + aRsc; // append to the original string. +} + +OUString ScChangeActionMove::GetRefString( ScDocument& rDoc, bool bFlag3D ) const +{ + if ( !bFlag3D ) + bFlag3D = ( GetFromRange().aStart.Tab() != GetBigRange().aStart.Tab() ); + + return ScChangeAction::GetRefString(GetFromRange(), rDoc, bFlag3D) + + ", " + + ScChangeAction::GetRefString(GetBigRange(), rDoc, bFlag3D); +} + +bool ScChangeActionMove::Reject( ScDocument& rDoc ) +{ + if ( !(aBigRange.IsValid( rDoc ) && aFromRange.IsValid( rDoc )) ) + return false; + + ScRange aToRange( aBigRange.MakeRange( rDoc ) ); + ScRange aFrmRange( aFromRange.MakeRange( rDoc ) ); + + bool bOk = rDoc.IsBlockEditable( aToRange.aStart.Tab(), + aToRange.aStart.Col(), aToRange.aStart.Row(), + aToRange.aEnd.Col(), aToRange.aEnd.Row() ); + if ( bOk ) + bOk = rDoc.IsBlockEditable( aFrmRange.aStart.Tab(), + aFrmRange.aStart.Col(), aFrmRange.aStart.Row(), + aFrmRange.aEnd.Col(), aFrmRange.aEnd.Row() ); + if ( !bOk ) + return false; + + pTrack->LookUpContents( aToRange, &rDoc, 0, 0, 0 ); // Contents to be moved + + rDoc.DeleteAreaTab( aToRange, InsertDeleteFlags::ALL ); + rDoc.DeleteAreaTab( aFrmRange, InsertDeleteFlags::ALL ); + // Adjust formula in the Document + sc::RefUpdateContext aCxt(rDoc); + aCxt.meMode = URM_MOVE; + aCxt.maRange = aFrmRange; + aCxt.mnColDelta = aFrmRange.aStart.Col() - aToRange.aStart.Col(); + aCxt.mnRowDelta = aFrmRange.aStart.Row() - aToRange.aStart.Row(); + aCxt.mnTabDelta = aFrmRange.aStart.Tab() - aToRange.aStart.Tab(); + rDoc.UpdateReference(aCxt); + + // Free LinkDependent, set succeeding UpdateReference Undo + // ToRange->FromRange Dependents + RemoveAllDependent(); + + // Sets rejected and calls UpdateReference Undo and DeleteCellEntries + RejectRestoreContents( pTrack, 0, 0 ); + + while ( pLinkDependent ) + { + ScChangeAction* p = pLinkDependent->GetAction(); + if ( p && p->GetType() == SC_CAT_CONTENT ) + { + ScChangeActionContent* pContent = static_cast<ScChangeActionContent*>(p); + if ( !pContent->IsDeletedIn() && + pContent->GetBigRange().aStart.IsValid( rDoc ) ) + pContent->PutNewValueToDoc( &rDoc, 0, 0 ); + // Delete the ones created in LookUpContents + if ( pTrack->IsGenerated( pContent->GetActionNumber() ) && + !pContent->IsDeletedIn() ) + { + pLinkDependent->UnLink(); // Else this one is also deleted! + pTrack->DeleteGeneratedDelContent( pContent ); + } + } + delete pLinkDependent; + } + + RemoveAllLinks(); + return true; +} + +ScChangeActionContent::ScChangeActionContent( const ScRange& rRange ) : + ScChangeAction(SC_CAT_CONTENT, rRange), + pNextContent(nullptr), + pPrevContent(nullptr), + pNextInSlot(nullptr), + ppPrevInSlot(nullptr) +{} + +ScChangeActionContent::ScChangeActionContent( const sal_uLong nActionNumber, + const ScChangeActionState eStateP, const sal_uLong nRejectingNumber, + const ScBigRange& aBigRangeP, const OUString& aUserP, + const DateTime& aDateTimeP, const OUString& sComment, + const ScCellValue& rOldCell, const ScDocument* pDoc, const OUString& sOldValue ) : + ScChangeAction(SC_CAT_CONTENT, aBigRangeP, nActionNumber, nRejectingNumber, eStateP, aDateTimeP, aUserP, sComment), + maOldCell(rOldCell), + maOldValue(sOldValue), + pNextContent(nullptr), + pPrevContent(nullptr), + pNextInSlot(nullptr), + ppPrevInSlot(nullptr) +{ + if (!maOldCell.isEmpty()) + SetCell(maOldValue, maOldCell, 0, pDoc); + + if (!sOldValue.isEmpty()) // #i40704# don't overwrite SetCell result with empty string + maOldValue = sOldValue; // set again, because SetCell removes it +} + +ScChangeActionContent::ScChangeActionContent( const sal_uLong nActionNumber, + const ScCellValue& rNewCell, const ScBigRange& aBigRangeP, + const ScDocument* pDoc, const OUString& sNewValue ) : + ScChangeAction(SC_CAT_CONTENT, aBigRangeP, nActionNumber), + maNewCell(rNewCell), + maNewValue(sNewValue), + pNextContent(nullptr), + pPrevContent(nullptr), + pNextInSlot(nullptr), + ppPrevInSlot(nullptr) +{ + if (!maNewCell.isEmpty()) + SetCell(maNewValue, maNewCell, 0, pDoc); + + if (!sNewValue.isEmpty()) // #i40704# don't overwrite SetCell result with empty string + maNewValue = sNewValue; // set again, because SetCell removes it +} + +ScChangeActionContent::~ScChangeActionContent() +{ + ClearTrack(); +} + +void ScChangeActionContent::ClearTrack() +{ + RemoveFromSlot(); + if ( pPrevContent ) + pPrevContent->pNextContent = pNextContent; + if ( pNextContent ) + pNextContent->pPrevContent = pPrevContent; +} + +ScChangeActionContent* ScChangeActionContent::GetTopContent() const +{ + if ( pNextContent ) + { + ScChangeActionContent* pContent = pNextContent; + while ( pContent->pNextContent && pContent != pContent->pNextContent ) + pContent = pContent->pNextContent; + return pContent; + } + return const_cast<ScChangeActionContent*>(this); +} + +ScChangeActionLinkEntry* ScChangeActionContent::GetDeletedIn() const +{ + if ( pNextContent ) + return GetTopContent()->pLinkDeletedIn; + return pLinkDeletedIn; +} + +ScChangeActionLinkEntry** ScChangeActionContent::GetDeletedInAddress() +{ + if ( pNextContent ) + return GetTopContent()->GetDeletedInAddress(); + return &pLinkDeletedIn; +} + +void ScChangeActionContent::SetOldValue( + const ScCellValue& rCell, const ScDocument* pFromDoc, ScDocument* pToDoc, sal_uLong nFormat ) +{ + SetValue(maOldValue, maOldCell, nFormat, rCell, pFromDoc, pToDoc); +} + +void ScChangeActionContent::SetOldValue( + const ScCellValue& rCell, const ScDocument* pFromDoc, ScDocument* pToDoc ) +{ + SetValue(maOldValue, maOldCell, aBigRange.aStart.MakeAddress(*pFromDoc), rCell, pFromDoc, pToDoc); +} + +void ScChangeActionContent::SetNewValue( const ScCellValue& rCell, ScDocument* pDoc ) +{ + SetValue(maNewValue, maNewCell, aBigRange.aStart.MakeAddress(*pDoc), rCell, pDoc, pDoc); +} + +void ScChangeActionContent::SetOldNewCells( + const ScCellValue& rOldCell, sal_uLong nOldFormat, const ScCellValue& rNewCell, + sal_uLong nNewFormat, const ScDocument* pDoc ) +{ + maOldCell = rOldCell; + maNewCell = rNewCell; + SetCell(maOldValue, maOldCell, nOldFormat, pDoc); + SetCell(maNewValue, maNewCell, nNewFormat, pDoc); +} + +void ScChangeActionContent::SetNewCell( + const ScCellValue& rCell, const ScDocument* pDoc, const OUString& rFormatted ) +{ + maNewCell = rCell; + SetCell(maNewValue, maNewCell, 0, pDoc); + + // #i40704# allow to set formatted text here - don't call SetNewValue with string from XML filter + if (!rFormatted.isEmpty()) + maNewValue = rFormatted; +} + +void ScChangeActionContent::SetValueString( + OUString& rValue, ScCellValue& rCell, const OUString& rStr, ScDocument* pDoc ) +{ + rCell.clear(); + if ( rStr.getLength() > 1 && rStr[0] == '=' ) + { + rValue.clear(); + rCell.meType = CELLTYPE_FORMULA; + rCell.mpFormula = new ScFormulaCell( + *pDoc, aBigRange.aStart.MakeAddress(*pDoc), rStr, + pDoc->GetGrammar() ); + rCell.mpFormula->SetInChangeTrack(true); + } + else + rValue = rStr; +} + +void ScChangeActionContent::SetOldValue( const OUString& rOld, ScDocument* pDoc ) +{ + SetValueString(maOldValue, maOldCell, rOld, pDoc); +} + +OUString ScChangeActionContent::GetOldString( const ScDocument* pDoc ) const +{ + return GetValueString(maOldValue, maOldCell, pDoc); +} + +OUString ScChangeActionContent::GetNewString( const ScDocument* pDoc ) const +{ + return GetValueString(maNewValue, maNewCell, pDoc); +} + +OUString ScChangeActionContent::GetDescription( + ScDocument& rDoc, bool bSplitRange, bool bWarning ) const +{ + OUString str = ScChangeAction::GetDescription( rDoc, bSplitRange, bWarning ); + + OUString aRsc = ScResId(STR_CHANGED_CELL); + + OUString aTmpStr = GetRefString(rDoc); + + sal_Int32 nPos = aRsc.indexOf("#1", 0); + if (nPos >= 0) + { + aRsc = aRsc.replaceAt(nPos, 2, aTmpStr); + nPos += aTmpStr.getLength(); + } + + aTmpStr = GetOldString( &rDoc ); + if (aTmpStr.isEmpty()) + aTmpStr = ScResId( STR_CHANGED_BLANK ); + + nPos = nPos >= 0 ? aRsc.indexOf("#2", nPos) : -1; + if (nPos >= 0) + { + aRsc = aRsc.replaceAt(nPos, 2, aTmpStr); + nPos += aTmpStr.getLength(); + } + + aTmpStr = GetNewString( &rDoc ); + if (aTmpStr.isEmpty()) + aTmpStr = ScResId( STR_CHANGED_BLANK ); + + nPos = nPos >= 0 ? aRsc.indexOf("#3", nPos) : -1; + if (nPos >= 0) + { + aRsc = aRsc.replaceAt(nPos, 2, aTmpStr); + } + + return str + aRsc; // append to the original string. +} + +OUString ScChangeActionContent::GetRefString( + ScDocument& rDoc, bool bFlag3D ) const +{ + ScRefFlags nFlags = ( GetBigRange().IsValid( rDoc ) ? ScRefFlags::VALID : ScRefFlags::ZERO ); + if ( nFlags != ScRefFlags::ZERO ) + { + const ScCellValue& rCell = GetNewCell(); + if ( GetContentCellType(rCell) == SC_CACCT_MATORG ) + { + ScBigRange aLocalBigRange( GetBigRange() ); + SCCOL nC; + SCROW nR; + rCell.mpFormula->GetMatColsRows( nC, nR ); + aLocalBigRange.aEnd.IncCol( nC-1 ); + aLocalBigRange.aEnd.IncRow( nR-1 ); + return ScChangeAction::GetRefString( aLocalBigRange, rDoc, bFlag3D ); + } + + ScAddress aTmpAddress( GetBigRange().aStart.MakeAddress( rDoc ) ); + if ( bFlag3D ) + nFlags |= ScRefFlags::TAB_3D; + OUString str = aTmpAddress.Format(nFlags, &rDoc, rDoc.GetAddressConvention()); + if ( IsDeletedIn() ) + { + // Insert the parentheses. + str = "(" + str + ")"; + } + return str; + } + else + return ScCompiler::GetNativeSymbol(ocErrRef); +} + +bool ScChangeActionContent::Reject( ScDocument& rDoc ) +{ + if ( !aBigRange.IsValid( rDoc ) ) + return false; + + PutOldValueToDoc( &rDoc, 0, 0 ); + + SetState( SC_CAS_REJECTED ); + RemoveAllLinks(); + + return true; +} + +bool ScChangeActionContent::Select( ScDocument& rDoc, ScChangeTrack* pTrack, + bool bOldest, ::std::stack<ScChangeActionContent*>* pRejectActions ) +{ + if ( !aBigRange.IsValid( rDoc ) ) + return false; + + ScChangeActionContent* pContent = this; + // accept previous contents + while ( ( pContent = pContent->pPrevContent ) != nullptr ) + { + if ( pContent->IsVirgin() ) + pContent->SetState( SC_CAS_ACCEPTED ); + } + ScChangeActionContent* pEnd = pContent = this; + // reject subsequent contents + while ( ( pContent = pContent->pNextContent ) != nullptr ) + { + // MatrixOrigin may have dependents, no dependency recursion needed + const ScChangeActionLinkEntry* pL = pContent->GetFirstDependentEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p ) + p->SetRejected(); + pL = pL->GetNext(); + } + pContent->SetRejected(); + pEnd = pContent; + } + + // If not oldest: Is it anyone else than the last one? + if ( bOldest || pEnd != this ) + { ScRange aRange( aBigRange.aStart.MakeAddress( rDoc ) ); + const ScAddress& rPos = aRange.aStart; + + ScChangeActionContent* pNew = new ScChangeActionContent( aRange ); + ScCellValue aCell; + aCell.assign(rDoc, rPos); + pNew->SetOldValue(aCell, &rDoc, &rDoc); + + if ( bOldest ) + PutOldValueToDoc( &rDoc, 0, 0 ); + else + PutNewValueToDoc( &rDoc, 0, 0 ); + + pNew->SetRejectAction( bOldest ? GetActionNumber() : pEnd->GetActionNumber() ); + pNew->SetState( SC_CAS_ACCEPTED ); + if ( pRejectActions ) + pRejectActions->push( pNew ); + else + { + aCell.assign(rDoc, rPos); + pNew->SetNewValue(aCell, &rDoc); + pTrack->Append( pNew ); + } + } + + if ( bOldest ) + SetRejected(); + else + SetState( SC_CAS_ACCEPTED ); + + return true; +} + +OUString ScChangeActionContent::GetStringOfCell( + const ScCellValue& rCell, const ScDocument* pDoc, const ScAddress& rPos ) +{ + if (NeedsNumberFormat(rCell)) + return GetStringOfCell(rCell, pDoc, pDoc->GetNumberFormat(rPos)); + else + return GetStringOfCell(rCell, pDoc, 0); +} + +OUString ScChangeActionContent::GetStringOfCell( + const ScCellValue& rCell, const ScDocument* pDoc, sal_uLong nFormat ) +{ + if (!GetContentCellType(rCell)) + return OUString(); + + switch (rCell.meType) + { + case CELLTYPE_VALUE: + { + OUString str; + pDoc->GetFormatTable()->GetInputLineString(rCell.mfValue, nFormat, str); + return str; + } + case CELLTYPE_STRING: + return rCell.mpString->getString(); + case CELLTYPE_EDIT: + if (rCell.mpEditText) + return ScEditUtil::GetString(*rCell.mpEditText, pDoc); + return OUString(); + case CELLTYPE_FORMULA: + return rCell.mpFormula->GetFormula(); + default: + return OUString(); + } +} + +ScChangeActionContentCellType ScChangeActionContent::GetContentCellType( const ScCellValue& rCell ) +{ + switch (rCell.meType) + { + case CELLTYPE_VALUE : + case CELLTYPE_STRING : + case CELLTYPE_EDIT : + return SC_CACCT_NORMAL; + case CELLTYPE_FORMULA : + switch (rCell.mpFormula->GetMatrixFlag()) + { + case ScMatrixMode::NONE : + return SC_CACCT_NORMAL; + case ScMatrixMode::Formula : + return SC_CACCT_MATORG; + case ScMatrixMode::Reference : + return SC_CACCT_MATREF; + } + return SC_CACCT_NORMAL; + default: + return SC_CACCT_NONE; + } +} + +ScChangeActionContentCellType ScChangeActionContent::GetContentCellType( const ScRefCellValue& rCell ) +{ + switch (rCell.meType) + { + case CELLTYPE_VALUE: + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + return SC_CACCT_NORMAL; + case CELLTYPE_FORMULA: + { + const ScFormulaCell* pCell = rCell.mpFormula; + switch (pCell->GetMatrixFlag()) + { + case ScMatrixMode::NONE : + return SC_CACCT_NORMAL; + case ScMatrixMode::Formula : + return SC_CACCT_MATORG; + case ScMatrixMode::Reference : + return SC_CACCT_MATREF; + } + return SC_CACCT_NORMAL; + } + default: + ; + } + + return SC_CACCT_NONE; +} + +bool ScChangeActionContent::NeedsNumberFormat( const ScCellValue& rVal ) +{ + return rVal.meType == CELLTYPE_VALUE; +} + +void ScChangeActionContent::SetValue( + OUString& rStr, ScCellValue& rCell, const ScAddress& rPos, const ScCellValue& rOrgCell, + const ScDocument* pFromDoc, ScDocument* pToDoc ) +{ + sal_uInt32 nFormat = NeedsNumberFormat(rOrgCell) ? pFromDoc->GetNumberFormat(rPos) : 0; + SetValue(rStr, rCell, nFormat, rOrgCell, pFromDoc, pToDoc); +} + +void ScChangeActionContent::SetValue( + OUString& rStr, ScCellValue& rCell, sal_uLong nFormat, const ScCellValue& rOrgCell, + const ScDocument* pFromDoc, ScDocument* pToDoc ) +{ + rStr.clear(); + + if (GetContentCellType(rOrgCell)) + { + rCell.assign(rOrgCell, *pToDoc); + switch (rOrgCell.meType) + { + case CELLTYPE_VALUE : + { // E.g.: Remember date as such + pFromDoc->GetFormatTable()->GetInputLineString( + rOrgCell.mfValue, nFormat, rStr); + } + break; + case CELLTYPE_FORMULA : + rCell.mpFormula->SetInChangeTrack(true); + break; + default: + { + // added to avoid warnings + } + } + } + else + rCell.clear(); +} + +void ScChangeActionContent::SetCell( OUString& rStr, ScCellValue& rCell, sal_uLong nFormat, const ScDocument* pDoc ) +{ + rStr.clear(); + if (rCell.isEmpty()) + return; + + switch (rCell.meType) + { + case CELLTYPE_VALUE : + // e.g. remember date as date string + pDoc->GetFormatTable()->GetInputLineString(rCell.mfValue, nFormat, rStr); + break; + case CELLTYPE_FORMULA : + rCell.mpFormula->SetInChangeTrack(true); + break; + default: + { + // added to avoid warnings + } + } +} + +OUString ScChangeActionContent::GetValueString( + const OUString& rValue, const ScCellValue& rCell, const ScDocument* pDoc ) const +{ + if (!rValue.isEmpty()) + { + return rValue; + } + + switch (rCell.meType) + { + case CELLTYPE_STRING : + return rCell.mpString->getString(); + case CELLTYPE_EDIT : + if (rCell.mpEditText) + return ScEditUtil::GetString(*rCell.mpEditText, pDoc); + return OUString(); + case CELLTYPE_VALUE : // Is always in rValue + return rValue; + case CELLTYPE_FORMULA : + return GetFormulaString(rCell.mpFormula); + case CELLTYPE_NONE: + default: + return OUString(); + } +} + +OUString ScChangeActionContent::GetFormulaString( + const ScFormulaCell* pCell ) const +{ + ScAddress aPos( aBigRange.aStart.MakeAddress( pCell->GetDocument()) ); + if ( aPos == pCell->aPos || IsDeletedIn() ) + return pCell->GetFormula(); + else + { + OSL_FAIL( "ScChangeActionContent::GetFormulaString: aPos != pCell->aPos" ); + ScFormulaCell aNew( *pCell, pCell->GetDocument(), aPos ); + return aNew.GetFormula(); + } +} + +void ScChangeActionContent::PutOldValueToDoc( ScDocument* pDoc, + SCCOL nDx, SCROW nDy ) const +{ + PutValueToDoc(maOldCell, maOldValue, pDoc, nDx, nDy); +} + +void ScChangeActionContent::PutNewValueToDoc( ScDocument* pDoc, + SCCOL nDx, SCROW nDy ) const +{ + PutValueToDoc(maNewCell, maNewValue, pDoc, nDx, nDy); +} + +void ScChangeActionContent::PutValueToDoc( + const ScCellValue& rCell, const OUString& rValue, ScDocument* pDoc, + SCCOL nDx, SCROW nDy ) const +{ + ScAddress aPos( aBigRange.aStart.MakeAddress( *pDoc ) ); + if ( nDx ) + aPos.IncCol( nDx ); + if ( nDy ) + aPos.IncRow( nDy ); + + if (!rValue.isEmpty()) + { + pDoc->SetString(aPos, rValue); + return; + } + + if (rCell.isEmpty()) + { + pDoc->SetEmptyCell(aPos); + return; + } + + if (rCell.meType == CELLTYPE_VALUE) + { + pDoc->SetString( aPos.Col(), aPos.Row(), aPos.Tab(), rValue ); + return; + } + + switch (GetContentCellType(rCell)) + { + case SC_CACCT_MATORG : + { + SCCOL nC; + SCROW nR; + rCell.mpFormula->GetMatColsRows(nC, nR); + OSL_ENSURE( nC>0 && nR>0, "ScChangeActionContent::PutValueToDoc: MatColsRows?" ); + ScRange aRange( aPos ); + if ( nC > 1 ) + aRange.aEnd.IncCol( nC-1 ); + if ( nR > 1 ) + aRange.aEnd.IncRow( nR-1 ); + ScMarkData aDestMark(pDoc->GetSheetLimits()); + aDestMark.SelectOneTable( aPos.Tab() ); + aDestMark.SetMarkArea( aRange ); + pDoc->InsertMatrixFormula( aPos.Col(), aPos.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), + aDestMark, OUString(), rCell.mpFormula->GetCode()); + } + break; + case SC_CACCT_MATREF : + // nothing + break; + default: + rCell.commit(*pDoc, aPos); + } +} + +static void lcl_InvalidateReference( const ScDocument& rDoc, formula::FormulaToken& rTok, const ScBigAddress& rPos ) +{ + ScSingleRefData& rRef1 = *rTok.GetSingleRef(); + if ( rPos.Col() < 0 || rDoc.MaxCol() < rPos.Col() ) + { + rRef1.SetColDeleted( true ); + } + if ( rPos.Row() < 0 || rDoc.MaxRow() < rPos.Row() ) + { + rRef1.SetRowDeleted( true ); + } + if ( rPos.Tab() < 0 || MAXTAB < rPos.Tab() ) + { + rRef1.SetTabDeleted( true ); + } + if ( rTok.GetType() != formula::svDoubleRef ) + return; + + ScSingleRefData& rRef2 = rTok.GetDoubleRef()->Ref2; + if ( rPos.Col() < 0 || rDoc.MaxCol() < rPos.Col() ) + { + rRef2.SetColDeleted( true ); + } + if ( rPos.Row() < 0 || rDoc.MaxRow() < rPos.Row() ) + { + rRef2.SetRowDeleted( true ); + } + if ( rPos.Tab() < 0 || MAXTAB < rPos.Tab() ) + { + rRef2.SetTabDeleted( true ); + } +} + +void ScChangeActionContent::UpdateReference( const ScChangeTrack* pTrack, + UpdateRefMode eMode, const ScBigRange& rRange, + sal_Int32 nDx, sal_Int32 nDy, sal_Int32 nDz ) +{ + SCSIZE nOldSlot = pTrack->ComputeContentSlot( aBigRange.aStart.Row() ); + ScRefUpdate::Update( eMode, rRange, nDx, nDy, nDz, aBigRange ); + SCSIZE nNewSlot = pTrack->ComputeContentSlot( aBigRange.aStart.Row() ); + if ( nNewSlot != nOldSlot ) + { + RemoveFromSlot(); + InsertInSlot( &(pTrack->GetContentSlots()[nNewSlot]) ); + } + + if ( pTrack->IsInDelete() && !pTrack->IsInDeleteTop() ) + return ; // Formula only update whole range + + bool bOldFormula = maOldCell.meType == CELLTYPE_FORMULA; + bool bNewFormula = maNewCell.meType == CELLTYPE_FORMULA; + if ( !(bOldFormula || bNewFormula) ) + return; + +// Adjust UpdateReference via ScFormulaCell (there) + if ( pTrack->IsInDelete() ) + { + const ScRange& rDelRange = pTrack->GetInDeleteRange(); + if ( nDx > 0 ) + nDx = rDelRange.aEnd.Col() - rDelRange.aStart.Col() + 1; + else if ( nDx < 0 ) + nDx = -(rDelRange.aEnd.Col() - rDelRange.aStart.Col() + 1); + if ( nDy > 0 ) + nDy = rDelRange.aEnd.Row() - rDelRange.aStart.Row() + 1; + else if ( nDy < 0 ) + nDy = -(rDelRange.aEnd.Row() - rDelRange.aStart.Row() + 1); + if ( nDz > 0 ) + nDz = rDelRange.aEnd.Tab() - rDelRange.aStart.Tab() + 1; + else if ( nDz < 0 ) + nDz = -(rDelRange.aEnd.Tab() - rDelRange.aStart.Tab() + 1); + } + ScBigRange aTmpRange( rRange ); + switch ( eMode ) + { + case URM_INSDEL : + if ( nDx < 0 || nDy < 0 || nDz < 0 ) + { // Delete starts there after removed range + // Position is changed there + if ( nDx ) + aTmpRange.aStart.IncCol( -nDx ); + if ( nDy ) + aTmpRange.aStart.IncRow( -nDy ); + if ( nDz ) + aTmpRange.aStart.IncTab( -nDz ); + } + break; + case URM_MOVE : + // Move is Source here and Target there + // Position needs to be adjusted before that + if ( bOldFormula ) + maOldCell.mpFormula->aPos = aBigRange.aStart.MakeAddress(pTrack->GetDocument()); + if ( bNewFormula ) + maNewCell.mpFormula->aPos = aBigRange.aStart.MakeAddress(pTrack->GetDocument()); + if ( nDx ) + { + aTmpRange.aStart.IncCol( nDx ); + aTmpRange.aEnd.IncCol( nDx ); + } + if ( nDy ) + { + aTmpRange.aStart.IncRow( nDy ); + aTmpRange.aEnd.IncRow( nDy ); + } + if ( nDz ) + { + aTmpRange.aStart.IncTab( nDz ); + aTmpRange.aEnd.IncTab( nDz ); + } + break; + default: + { + // added to avoid warnings + } + } + ScRange aRange( aTmpRange.MakeRange(pTrack->GetDocument()) ); + + sc::RefUpdateContext aRefCxt(pTrack->GetDocument()); + aRefCxt.meMode = eMode; + aRefCxt.maRange = aRange; + aRefCxt.mnColDelta = nDx; + aRefCxt.mnRowDelta = nDy; + aRefCxt.mnTabDelta = nDz; + + if ( bOldFormula ) + maOldCell.mpFormula->UpdateReference(aRefCxt); + if ( bNewFormula ) + maNewCell.mpFormula->UpdateReference(aRefCxt); + + if ( aBigRange.aStart.IsValid( pTrack->GetDocument() ) ) + return; + +//FIXME: + // UpdateReference cannot handle positions outside of the Document. + // Therefore set everything to #REF! + //TODO: Remove the need for this hack! This means big changes to ScAddress etc.! + const ScBigAddress& rPos = aBigRange.aStart; + if ( bOldFormula ) + { + formula::FormulaToken* t; + ScTokenArray* pArr = maOldCell.mpFormula->GetCode(); + formula::FormulaTokenArrayPlainIterator aIter(*pArr); + while ( ( t = aIter.GetNextReference() ) != nullptr ) + lcl_InvalidateReference( pTrack->GetDocument(), *t, rPos ); + aIter.Reset(); + while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr ) + lcl_InvalidateReference( pTrack->GetDocument(), *t, rPos ); + } + if ( bNewFormula ) + { + formula::FormulaToken* t; + ScTokenArray* pArr = maNewCell.mpFormula->GetCode(); + formula::FormulaTokenArrayPlainIterator aIter(*pArr); + while ( ( t = aIter.GetNextReference() ) != nullptr ) + lcl_InvalidateReference( pTrack->GetDocument(), *t, rPos ); + aIter.Reset(); + while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr ) + lcl_InvalidateReference( pTrack->GetDocument(), *t, rPos ); + } +} + +bool ScChangeActionContent::IsMatrixOrigin() const +{ + return GetContentCellType(GetNewCell()) == SC_CACCT_MATORG; +} + +bool ScChangeActionContent::IsOldMatrixReference() const +{ + return GetContentCellType(GetOldCell()) == SC_CACCT_MATREF; +} + +// ScChangeActionReject +ScChangeActionReject::ScChangeActionReject( + const sal_uLong nActionNumber, const ScChangeActionState eStateP, + const sal_uLong nRejectingNumber, + const ScBigRange& aBigRangeP, const OUString& aUserP, + const DateTime& aDateTimeP, const OUString& sComment) : + ScChangeAction(SC_CAT_CONTENT, aBigRangeP, nActionNumber, nRejectingNumber, eStateP, aDateTimeP, aUserP, sComment) +{ +} + +bool ScChangeActionReject::Reject(ScDocument& /*rDoc*/) +{ + return false; +} + +SCSIZE ScChangeTrack::ComputeContentSlot( sal_Int32 nRow ) const +{ + if ( nRow < 0 || nRow > rDoc.GetSheetLimits().mnMaxRow ) + return mnContentSlots - 1; + return static_cast< SCSIZE >( nRow / mnContentRowsPerSlot ); +} + +SCROW ScChangeTrack::InitContentRowsPerSlot() +{ + const SCSIZE nMaxSlots = 0xffe0 / sizeof( ScChangeActionContent* ) - 2; + SCROW nRowsPerSlot = rDoc.GetMaxRowCount() / nMaxSlots; + if ( nRowsPerSlot * nMaxSlots < sal::static_int_cast<SCSIZE>(rDoc.GetMaxRowCount()) ) + ++nRowsPerSlot; + return nRowsPerSlot; +} + +ScChangeTrack::ScChangeTrack( ScDocument& rDocP ) : + aFixDateTime( DateTime::SYSTEM ), + rDoc( rDocP ) +{ + Init(); + SC_MOD()->GetUserOptions().AddListener(this); + + ppContentSlots.reset( new ScChangeActionContent* [ mnContentSlots ] ); + memset( ppContentSlots.get(), 0, mnContentSlots * sizeof( ScChangeActionContent* ) ); +} + +ScChangeTrack::ScChangeTrack( ScDocument& rDocP, std::set<OUString>&& aTempUserCollection) : + maUserCollection(std::move(aTempUserCollection)), + aFixDateTime( DateTime::SYSTEM ), + rDoc( rDocP ) +{ + Init(); + SC_MOD()->GetUserOptions().AddListener(this); + ppContentSlots.reset( new ScChangeActionContent* [ mnContentSlots ] ); + memset( ppContentSlots.get(), 0, mnContentSlots * sizeof( ScChangeActionContent* ) ); +} + +ScChangeTrack::~ScChangeTrack() +{ + SC_MOD()->GetUserOptions().RemoveListener(this); + DtorClear(); +} + +void ScChangeTrack::Init() +{ + mnContentRowsPerSlot = InitContentRowsPerSlot(); + mnContentSlots = rDoc.GetMaxRowCount() / InitContentRowsPerSlot() + 2; + + pFirst = nullptr; + pLast = nullptr; + pFirstGeneratedDelContent = nullptr; + pLastCutMove = nullptr; + pLinkInsertCol = nullptr; + pLinkInsertRow = nullptr; + pLinkInsertTab = nullptr; + pLinkMove = nullptr; + xBlockModifyMsg.reset(); + nActionMax = 0; + nGeneratedMin = SC_CHGTRACK_GENERATED_START; + nMarkLastSaved = 0; + nStartLastCut = 0; + nEndLastCut = 0; + nLastMerge = 0; + eMergeState = SC_CTMS_NONE; + bInDelete = false; + bInDeleteTop = false; + bInDeleteUndo = false; + bInPasteCut = false; + bUseFixDateTime = false; + bTimeNanoSeconds = true; + + const SvtUserOptions& rUserOpt = SC_MOD()->GetUserOptions(); + maUser = rUserOpt.GetFirstName() + " " + rUserOpt.GetLastName(); + maUserCollection.insert(maUser); +} + +void ScChangeTrack::DtorClear() +{ + ScChangeAction* p; + ScChangeAction* pNext; + for ( p = GetFirst(); p; p = pNext ) + { + pNext = p->GetNext(); + delete p; + } + for ( p = pFirstGeneratedDelContent; p; p = pNext ) + { + pNext = p->GetNext(); + delete p; + } + for( const auto& rEntry : aPasteCutMap ) + { + delete rEntry.second; + } + pLastCutMove.reset(); + ClearMsgQueue(); +} + +void ScChangeTrack::ClearMsgQueue() +{ + xBlockModifyMsg.reset(); + aMsgStackTmp.clear(); + aMsgStackFinal.clear(); + aMsgQueue.clear(); +} + +void ScChangeTrack::Clear() +{ + DtorClear(); + aMap.clear(); + aGeneratedMap.clear(); + aPasteCutMap.clear(); + maUserCollection.clear(); + maUser.clear(); + Init(); +} + +bool ScChangeTrack::IsGenerated( sal_uLong nAction ) const +{ + return nAction >= nGeneratedMin; +} + +ScChangeAction* ScChangeTrack::GetAction( sal_uLong nAction ) const +{ + ScChangeActionMap::const_iterator it = aMap.find( nAction ); + if( it != aMap.end() ) + return it->second; + else + return nullptr; +} + +ScChangeAction* ScChangeTrack::GetGenerated( sal_uLong nGenerated ) const +{ + ScChangeActionMap::const_iterator it = aGeneratedMap.find( nGenerated ); + if( it != aGeneratedMap.end() ) + return it->second; + else + return nullptr; +} + +ScChangeAction* ScChangeTrack::GetActionOrGenerated( sal_uLong nAction ) const +{ + return IsGenerated( nAction ) ? + GetGenerated( nAction ) : + GetAction( nAction ); +} +sal_uLong ScChangeTrack::GetLastSavedActionNumber() const +{ + return nMarkLastSaved; +} + +void ScChangeTrack::SetLastSavedActionNumber(sal_uLong nNew) +{ + nMarkLastSaved = nNew; +} + +ScChangeAction* ScChangeTrack::GetLastSaved() const +{ + ScChangeActionMap::const_iterator it = aMap.find( nMarkLastSaved ); + if( it != aMap.end() ) + return it->second; + else + return nullptr; +} + +void ScChangeTrack::ConfigurationChanged( utl::ConfigurationBroadcaster*, ConfigurationHints ) +{ + if ( rDoc.IsInDtorClear() ) + return; + + const SvtUserOptions& rUserOptions = SC_MOD()->GetUserOptions(); + size_t nOldCount = maUserCollection.size(); + + SetUser(rUserOptions.GetFirstName() + " " + rUserOptions.GetLastName()); + + if ( maUserCollection.size() != nOldCount ) + { + // New user in collection -> have to repaint because + // colors may be different now (#106697#). + // (Has to be done in the Notify handler, to be sure + // the user collection has already been updated) + + SfxObjectShell* pDocSh = rDoc.GetDocumentShell(); + if (pDocSh) + pDocSh->Broadcast( ScPaintHint( ScRange(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB), PaintPartFlags::Grid ) ); + } +} + +void ScChangeTrack::SetUser( const OUString& rUser ) +{ + maUser = rUser; + maUserCollection.insert(maUser); +} + +void ScChangeTrack::StartBlockModify( ScChangeTrackMsgType eMsgType, + sal_uLong nStartAction ) +{ + if ( aModifiedLink.IsSet() ) + { + if ( xBlockModifyMsg ) + aMsgStackTmp.push_back( *xBlockModifyMsg ); // Block in Block + xBlockModifyMsg = ScChangeTrackMsgInfo(); + xBlockModifyMsg->eMsgType = eMsgType; + xBlockModifyMsg->nStartAction = nStartAction; + xBlockModifyMsg->nEndAction = 0; + } +} + +void ScChangeTrack::EndBlockModify( sal_uLong nEndAction ) +{ + if ( !aModifiedLink.IsSet() ) + return; + + if ( xBlockModifyMsg ) + { + if ( xBlockModifyMsg->nStartAction <= nEndAction ) + { + xBlockModifyMsg->nEndAction = nEndAction; + // Blocks dissolved in Blocks + aMsgStackFinal.push_back( *xBlockModifyMsg ); + } + else + xBlockModifyMsg.reset(); + if (aMsgStackTmp.empty()) + xBlockModifyMsg.reset(); + else + { + xBlockModifyMsg = aMsgStackTmp.back(); // Maybe Block in Block + aMsgStackTmp.pop_back(); + } + } + if ( !xBlockModifyMsg ) + { + bool bNew = !aMsgStackFinal.empty(); + aMsgQueue.reserve(aMsgQueue.size() + aMsgStackFinal.size()); + aMsgQueue.insert(aMsgQueue.end(), aMsgStackFinal.rbegin(), aMsgStackFinal.rend()); + aMsgStackFinal.clear(); + if ( bNew ) + aModifiedLink.Call( *this ); + } +} + +ScChangeTrackMsgQueue& ScChangeTrack::GetMsgQueue() +{ + return aMsgQueue; +} + +void ScChangeTrack::NotifyModified( ScChangeTrackMsgType eMsgType, + sal_uLong nStartAction, sal_uLong nEndAction ) +{ + if ( aModifiedLink.IsSet() ) + { + if ( !xBlockModifyMsg || xBlockModifyMsg->eMsgType != eMsgType || + (IsGenerated( nStartAction ) && + (eMsgType == ScChangeTrackMsgType::Append || eMsgType == ScChangeTrackMsgType::Remove)) ) + { // Append within Append e.g. not + StartBlockModify( eMsgType, nStartAction ); + EndBlockModify( nEndAction ); + } + } +} + +void ScChangeTrack::MasterLinks( ScChangeAction* pAppend ) +{ + ScChangeActionType eType = pAppend->GetType(); + + if ( eType == SC_CAT_CONTENT ) + { + if ( !IsGenerated( pAppend->GetActionNumber() ) ) + { + SCSIZE nSlot = ComputeContentSlot( + pAppend->GetBigRange().aStart.Row() ); + static_cast<ScChangeActionContent*>(pAppend)->InsertInSlot( + &ppContentSlots[nSlot] ); + } + return ; + } + + if ( pAppend->IsRejecting() ) + return ; // Rejects do not have dependencies + + switch ( eType ) + { + case SC_CAT_INSERT_COLS : + { + ScChangeActionLinkEntry* pLink = new ScChangeActionLinkEntry( + &pLinkInsertCol, pAppend ); + pAppend->AddLink( nullptr, pLink ); + } + break; + case SC_CAT_INSERT_ROWS : + { + ScChangeActionLinkEntry* pLink = new ScChangeActionLinkEntry( + &pLinkInsertRow, pAppend ); + pAppend->AddLink( nullptr, pLink ); + } + break; + case SC_CAT_INSERT_TABS : + { + ScChangeActionLinkEntry* pLink = new ScChangeActionLinkEntry( + &pLinkInsertTab, pAppend ); + pAppend->AddLink( nullptr, pLink ); + } + break; + case SC_CAT_MOVE : + { + ScChangeActionLinkEntry* pLink = new ScChangeActionLinkEntry( + &pLinkMove, pAppend ); + pAppend->AddLink( nullptr, pLink ); + } + break; + default: + { + // added to avoid warnings + } + } +} + +void ScChangeTrack::AppendLoaded( std::unique_ptr<ScChangeAction> pActionParam ) +{ + ScChangeAction* pAppend = pActionParam.release(); + aMap.insert( ::std::make_pair( pAppend->GetActionNumber(), pAppend ) ); + if ( !pLast ) + pFirst = pLast = pAppend; + else + { + pLast->pNext = pAppend; + pAppend->pPrev = pLast; + pLast = pAppend; + } + MasterLinks( pAppend ); +} + +void ScChangeTrack::Append( ScChangeAction* pAppend, sal_uLong nAction ) +{ + if ( nActionMax < nAction ) + nActionMax = nAction; + pAppend->SetUser( maUser ); + if ( bUseFixDateTime ) + pAppend->SetDateTimeUTC( aFixDateTime ); + pAppend->SetActionNumber( nAction ); + aMap.insert( ::std::make_pair( nAction, pAppend ) ); + // UpdateReference Inserts before Dependencies. + // Delete rejecting Insert which had UpdateReference with Delete Undo. + // UpdateReference also with pLast==NULL, as pAppend can be a Delete, + // which could have generated DelContents. + if ( pAppend->IsInsertType() && !pAppend->IsRejecting() ) + UpdateReference( pAppend, false ); + if ( !pLast ) + pFirst = pLast = pAppend; + else + { + pLast->pNext = pAppend; + pAppend->pPrev = pLast; + pLast = pAppend; + Dependencies( pAppend ); + } + // UpdateReference does not Insert() after Dependencies. + // Move rejecting Move, which had UpdateReference with Move Undo. + // Do not delete content in ToRange. + if ( !pAppend->IsInsertType() && + !(pAppend->GetType() == SC_CAT_MOVE && pAppend->IsRejecting()) ) + UpdateReference( pAppend, false ); + MasterLinks( pAppend ); + + if ( !aModifiedLink.IsSet() ) + return; + + NotifyModified( ScChangeTrackMsgType::Append, nAction, nAction ); + if ( pAppend->GetType() == SC_CAT_CONTENT ) + { + ScChangeActionContent* pContent = static_cast<ScChangeActionContent*>(pAppend); + if ( ( pContent = pContent->GetPrevContent() ) != nullptr ) + { + sal_uLong nMod = pContent->GetActionNumber(); + NotifyModified( ScChangeTrackMsgType::Change, nMod, nMod ); + } + } + else + NotifyModified( ScChangeTrackMsgType::Change, pFirst->GetActionNumber(), + pLast->GetActionNumber() ); +} + +void ScChangeTrack::Append( ScChangeAction* pAppend ) +{ + Append( pAppend, ++nActionMax ); +} + +void ScChangeTrack::AppendDeleteRange( const ScRange& rRange, + ScDocument* pRefDoc, sal_uLong& nStartAction, sal_uLong& nEndAction, SCTAB nDz ) +{ + nStartAction = GetActionMax() + 1; + AppendDeleteRange( rRange, pRefDoc, nDz, 0 ); + nEndAction = GetActionMax(); +} + +void ScChangeTrack::AppendDeleteRange( const ScRange& rRange, + ScDocument* pRefDoc, SCTAB nDz, sal_uLong nRejectingInsert ) +{ + SetInDeleteRange( rRange ); + StartBlockModify( ScChangeTrackMsgType::Append, GetActionMax() + 1 ); + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + for ( SCTAB nTab = nTab1; nTab <= nTab2; nTab++ ) + { + if ( !pRefDoc || nTab < pRefDoc->GetTableCount() ) + { + if ( nCol1 == 0 && nCol2 == rDoc.MaxCol() ) + { // Whole Row and/or Tables + if ( nRow1 == 0 && nRow2 == rDoc.MaxRow() ) + { // Whole Table + // TODO: Can't we do the whole Table as a whole? + ScRange aRange( 0, 0, nTab, 0, rDoc.MaxRow(), nTab ); + for ( SCCOL nCol = nCol1; nCol <= nCol2; nCol++ ) + { // Column by column is less than row by row + aRange.aStart.SetCol( nCol ); + aRange.aEnd.SetCol( nCol ); + if ( nCol == nCol2 ) + SetInDeleteTop( true ); + AppendOneDeleteRange( aRange, pRefDoc, nCol-nCol1, 0, + nTab-nTab1 + nDz, nRejectingInsert ); + } + // Still InDeleteTop! + AppendOneDeleteRange( rRange, pRefDoc, 0, 0, + nTab-nTab1 + nDz, nRejectingInsert ); + } + else + { // Whole rows + ScRange aRange( 0, 0, nTab, rDoc.MaxCol(), 0, nTab ); + for ( SCROW nRow = nRow1; nRow <= nRow2; nRow++ ) + { + aRange.aStart.SetRow( nRow ); + aRange.aEnd.SetRow( nRow ); + if ( nRow == nRow2 ) + SetInDeleteTop( true ); + AppendOneDeleteRange( aRange, pRefDoc, 0, nRow-nRow1, + 0, nRejectingInsert ); + } + } + } + else if ( nRow1 == 0 && nRow2 == rDoc.MaxRow() ) + { // Whole columns + ScRange aRange( 0, 0, nTab, 0, rDoc.MaxRow(), nTab ); + for ( SCCOL nCol = nCol1; nCol <= nCol2; nCol++ ) + { + aRange.aStart.SetCol( nCol ); + aRange.aEnd.SetCol( nCol ); + if ( nCol == nCol2 ) + SetInDeleteTop( true ); + AppendOneDeleteRange( aRange, pRefDoc, nCol-nCol1, 0, + 0, nRejectingInsert ); + } + } + else + { + OSL_FAIL( "ScChangeTrack::AppendDeleteRange: Block not supported!" ); + } + SetInDeleteTop( false ); + } + } + EndBlockModify( GetActionMax() ); +} + +void ScChangeTrack::AppendOneDeleteRange( const ScRange& rOrgRange, + ScDocument* pRefDoc, SCCOL nDx, SCROW nDy, SCTAB nDz, + sal_uLong nRejectingInsert ) +{ + ScRange aTrackRange( rOrgRange ); + if ( nDx ) + { + aTrackRange.aStart.IncCol( -nDx ); + aTrackRange.aEnd.IncCol( -nDx ); + } + if ( nDy ) + { + aTrackRange.aStart.IncRow( -nDy ); + aTrackRange.aEnd.IncRow( -nDy ); + } + if ( nDz ) + { + aTrackRange.aStart.IncTab( -nDz ); + aTrackRange.aEnd.IncTab( -nDz ); + } + ScChangeActionDel* pAct = new ScChangeActionDel( &rDoc, aTrackRange, nDx, nDy, + this ); + // TabDelete not Contents; they are in separate columns + if ( !(rOrgRange.aStart.Col() == 0 && rOrgRange.aStart.Row() == 0 && + rOrgRange.aEnd.Col() == rDoc.MaxCol() && rOrgRange.aEnd.Row() == rDoc.MaxRow()) ) + LookUpContents( rOrgRange, pRefDoc, -nDx, -nDy, -nDz ); + if ( nRejectingInsert ) + { + pAct->SetRejectAction( nRejectingInsert ); + pAct->SetState( SC_CAS_ACCEPTED ); + } + Append( pAct ); +} + +void ScChangeTrack::LookUpContents( const ScRange& rOrgRange, + ScDocument* pRefDoc, SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + if (!pRefDoc) + return; + + ScAddress aPos; + ScBigAddress aBigPos; + ScCellIterator aIter( *pRefDoc, rOrgRange ); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + if (!ScChangeActionContent::GetContentCellType(aIter.getRefCellValue())) + continue; + + aBigPos.Set( aIter.GetPos().Col() + nDx, aIter.GetPos().Row() + nDy, + aIter.GetPos().Tab() + nDz ); + ScChangeActionContent* pContent = SearchContentAt( aBigPos, nullptr ); + if (pContent) + continue; + + // Untracked Contents + aPos.Set( aIter.GetPos().Col() + nDx, aIter.GetPos().Row() + nDy, + aIter.GetPos().Tab() + nDz ); + + GenerateDelContent(aPos, aIter.getCellValue(), pRefDoc); + // The Content is _not_ added with AddContent here, but in UpdateReference. + // We do this in order to e.g. handle intersecting Deletes correctly + } +} + +void ScChangeTrack::AppendMove( const ScRange& rFromRange, + const ScRange& rToRange, ScDocument* pRefDoc ) +{ + ScChangeActionMove* pAct = new ScChangeActionMove( rFromRange, rToRange, this ); + LookUpContents( rToRange, pRefDoc, 0, 0, 0 ); // Overwritten Contents + Append( pAct ); +} + +bool ScChangeTrack::IsMatrixFormulaRangeDifferent( + const ScCellValue& rOldCell, const ScCellValue& rNewCell ) +{ + SCCOL nC1, nC2; + SCROW nR1, nR2; + nC1 = nC2 = 0; + nR1 = nR2 = 0; + + if (rOldCell.meType == CELLTYPE_FORMULA && rOldCell.mpFormula->GetMatrixFlag() == ScMatrixMode::Formula) + rOldCell.mpFormula->GetMatColsRows(nC1, nR1); + + if (rNewCell.meType == CELLTYPE_FORMULA && rNewCell.mpFormula->GetMatrixFlag() == ScMatrixMode::Formula) + rNewCell.mpFormula->GetMatColsRows(nC1, nR1); + + return nC1 != nC2 || nR1 != nR2; +} + +void ScChangeTrack::AppendContent( + const ScAddress& rPos, const ScCellValue& rOldCell, sal_uLong nOldFormat, ScDocument* pRefDoc ) +{ + if ( !pRefDoc ) + pRefDoc = &rDoc; + + OUString aOldValue = ScChangeActionContent::GetStringOfCell(rOldCell, pRefDoc, nOldFormat); + + ScCellValue aNewCell; + aNewCell.assign(rDoc, rPos); + OUString aNewValue = ScChangeActionContent::GetStringOfCell(aNewCell, &rDoc, rPos); + + if (aOldValue != aNewValue || IsMatrixFormulaRangeDifferent(rOldCell, aNewCell)) + { // Only track real changes + ScRange aRange( rPos ); + ScChangeActionContent* pAct = new ScChangeActionContent( aRange ); + pAct->SetOldValue(rOldCell, pRefDoc, &rDoc, nOldFormat); + pAct->SetNewValue(aNewCell, &rDoc); + Append( pAct ); + } +} + +void ScChangeTrack::AppendContent( const ScAddress& rPos, + const ScDocument* pRefDoc ) +{ + ScCellValue aOldCell; + aOldCell.assign(*pRefDoc, rPos); + OUString aOldValue = ScChangeActionContent::GetStringOfCell(aOldCell, pRefDoc, rPos); + + ScCellValue aNewCell; + aNewCell.assign(rDoc, rPos); + OUString aNewValue = ScChangeActionContent::GetStringOfCell(aNewCell, &rDoc, rPos); + + if (aOldValue != aNewValue || IsMatrixFormulaRangeDifferent(aOldCell, aNewCell)) + { // Only track real changes + ScRange aRange( rPos ); + ScChangeActionContent* pAct = new ScChangeActionContent( aRange ); + pAct->SetOldValue(aOldCell, pRefDoc, &rDoc); + pAct->SetNewValue(aNewCell, &rDoc); + Append( pAct ); + } +} + +void ScChangeTrack::AppendContent( const ScAddress& rPos, const ScCellValue& rOldCell ) +{ + if (ScChangeActionContent::NeedsNumberFormat(rOldCell)) + AppendContent(rPos, rOldCell, rDoc.GetNumberFormat(rPos), &rDoc); + else + AppendContent(rPos, rOldCell, 0, &rDoc); +} + +void ScChangeTrack::SetLastCutMoveRange( const ScRange& rRange, + ScDocument* pRefDoc ) +{ + if ( !pLastCutMove ) + return; + + // Do not link ToRange with Deletes and don't change its size + // This is actually unnecessary, as a delete triggers a ResetLastCut + // in ScViewFunc::PasteFromClip before that + ScBigRange& r = pLastCutMove->GetBigRange(); + r.aEnd.SetCol( -1 ); + r.aEnd.SetRow( -1 ); + r.aEnd.SetTab( -1 ); + r.aStart.SetCol( -1 - (rRange.aEnd.Col() - rRange.aStart.Col()) ); + r.aStart.SetRow( -1 - (rRange.aEnd.Row() - rRange.aStart.Row()) ); + r.aStart.SetTab( -1 - (rRange.aEnd.Tab() - rRange.aStart.Tab()) ); + // Contents in FromRange we should overwrite + LookUpContents( rRange, pRefDoc, 0, 0, 0 ); +} + +void ScChangeTrack::AppendContentRange( const ScRange& rRange, + ScDocument* pRefDoc, sal_uLong& nStartAction, sal_uLong& nEndAction, + ScChangeActionClipMode eClipMode ) +{ + if ( eClipMode == SC_CACM_CUT ) + { + ResetLastCut(); + pLastCutMove.reset(new ScChangeActionMove( rRange, rRange, this )); + SetLastCutMoveRange( rRange, pRefDoc ); + } + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + bool bDoContents; + if ( eClipMode == SC_CACM_PASTE && HasLastCut() ) + { + bDoContents = false; + SetInPasteCut( true ); + // Adjust Paste and Cut; Paste can be larger a Range + ScRange aRange( rRange ); + ScBigRange& r = pLastCutMove->GetBigRange(); + SCCOL nTmpCol; + if ( (nTmpCol = static_cast<SCCOL>(r.aEnd.Col() - r.aStart.Col())) != (nCol2 - nCol1) ) + { + aRange.aEnd.SetCol( aRange.aStart.Col() + nTmpCol ); + nCol1 += nTmpCol + 1; + bDoContents = true; + } + SCROW nTmpRow; + if ( (nTmpRow = static_cast<SCROW>(r.aEnd.Row() - r.aStart.Row())) != (nRow2 - nRow1) ) + { + aRange.aEnd.SetRow( aRange.aStart.Row() + nTmpRow ); + nRow1 += nTmpRow + 1; + bDoContents = true; + } + SCTAB nTmpTab; + if ( (nTmpTab = static_cast<SCTAB>(r.aEnd.Tab() - r.aStart.Tab())) != (nTab2 - nTab1) ) + { + aRange.aEnd.SetTab( aRange.aStart.Tab() + nTmpTab ); + nTab1 += nTmpTab + 1; + bDoContents = true; + } + r = aRange; + Undo( nStartLastCut, nEndLastCut ); // Remember Cuts here + // StartAction only after Undo! + nStartAction = GetActionMax() + 1; + StartBlockModify( ScChangeTrackMsgType::Append, nStartAction ); + // Contents to overwrite in ToRange + LookUpContents( aRange, pRefDoc, 0, 0, 0 ); + pLastCutMove->SetStartLastCut( nStartLastCut ); + pLastCutMove->SetEndLastCut( nEndLastCut ); + Append( pLastCutMove.release() ); + ResetLastCut(); + SetInPasteCut( false ); + } + else + { + bDoContents = true; + nStartAction = GetActionMax() + 1; + StartBlockModify( ScChangeTrackMsgType::Append, nStartAction ); + } + if ( bDoContents ) + { + ScAddress aPos; + for ( SCTAB nTab = nTab1; nTab <= nTab2; nTab++ ) + { + aPos.SetTab( nTab ); + // AppendContent() is a no-op if both cells are empty. + SCCOL lastCol = std::max( pRefDoc->ClampToAllocatedColumns( nTab, nCol2 ), + rDoc.ClampToAllocatedColumns( nTab, nCol2 )); + for ( SCCOL nCol = nCol1; nCol <= lastCol; nCol++ ) + { + aPos.SetCol( nCol ); + SCROW lastRow = std::max( pRefDoc->GetLastDataRow( nTab, nCol, nCol, nRow2 ), + rDoc.GetLastDataRow( nTab, nCol, nCol, nRow2 )); + for ( SCROW nRow = nRow1; nRow <= lastRow; nRow++ ) + { + aPos.SetRow( nRow ); + AppendContent( aPos, pRefDoc ); + } + } + } + } + nEndAction = GetActionMax(); + EndBlockModify( nEndAction ); + if ( eClipMode == SC_CACM_CUT ) + { + nStartLastCut = nStartAction; + nEndLastCut = nEndAction; + } +} + +void ScChangeTrack::AppendContentsIfInRefDoc( ScDocument& rRefDoc, + sal_uLong& nStartAction, sal_uLong& nEndAction ) +{ + ScCellIterator aIter(rRefDoc, ScRange(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB)); + if (aIter.first()) + { + nStartAction = GetActionMax() + 1; + StartBlockModify( ScChangeTrackMsgType::Append, nStartAction ); + SvNumberFormatter* pFormatter = rRefDoc.GetFormatTable(); + do + { + const ScAddress& rPos = aIter.GetPos(); + const ScPatternAttr* pPat = rRefDoc.GetPattern(rPos); + AppendContent( + rPos, aIter.getCellValue(), pPat->GetNumberFormat(pFormatter), &rRefDoc); + } + while (aIter.next()); + + nEndAction = GetActionMax(); + EndBlockModify( nEndAction ); + } + else + nStartAction = nEndAction = 0; +} + +ScChangeActionContent* ScChangeTrack::AppendContentOnTheFly( + const ScAddress& rPos, const ScCellValue& rOldCell, const ScCellValue& rNewCell, + sal_uLong nOldFormat, sal_uLong nNewFormat ) +{ + ScRange aRange( rPos ); + ScChangeActionContent* pAct = new ScChangeActionContent( aRange ); + pAct->SetOldNewCells(rOldCell, nOldFormat, rNewCell, nNewFormat, &rDoc); + Append( pAct ); + return pAct; +} + +void ScChangeTrack::AppendInsert( const ScRange& rRange, bool bEndOfList ) +{ + ScChangeActionIns* pAct = new ScChangeActionIns(&rDoc, rRange, bEndOfList); + Append( pAct ); +} + +void ScChangeTrack::DeleteCellEntries( std::vector<ScChangeActionContent*>& rCellList, + const ScChangeAction* pDeletor ) +{ + for (ScChangeActionContent* pContent : rCellList) + { + pContent->RemoveDeletedIn( pDeletor ); + if ( IsGenerated( pContent->GetActionNumber() ) && + !pContent->IsDeletedIn() ) + DeleteGeneratedDelContent( pContent ); + } + rCellList.clear(); +} + +ScChangeActionContent* ScChangeTrack::GenerateDelContent( + const ScAddress& rPos, const ScCellValue& rCell, const ScDocument* pFromDoc ) +{ + ScChangeActionContent* pContent = new ScChangeActionContent( + ScRange( rPos ) ); + pContent->SetActionNumber( --nGeneratedMin ); + // Only NewValue + ScChangeActionContent::SetValue( pContent->maNewValue, pContent->maNewCell, + rPos, rCell, pFromDoc, &rDoc ); + // pNextContent and pPrevContent are not set + if ( pFirstGeneratedDelContent ) + { // Insert at front + pFirstGeneratedDelContent->pPrev = pContent; + pContent->pNext = pFirstGeneratedDelContent; + } + pFirstGeneratedDelContent = pContent; + aGeneratedMap.insert( std::make_pair( nGeneratedMin, pContent ) ); + NotifyModified( ScChangeTrackMsgType::Append, nGeneratedMin, nGeneratedMin ); + return pContent; +} + +void ScChangeTrack::DeleteGeneratedDelContent( ScChangeActionContent* pContent ) +{ + sal_uLong nAct = pContent->GetActionNumber(); + aGeneratedMap.erase( nAct ); + if ( pFirstGeneratedDelContent == pContent ) + pFirstGeneratedDelContent = static_cast<ScChangeActionContent*>(pContent->pNext); + if ( pContent->pNext ) + pContent->pNext->pPrev = pContent->pPrev; + if ( pContent->pPrev ) + pContent->pPrev->pNext = pContent->pNext; + delete pContent; + NotifyModified( ScChangeTrackMsgType::Remove, nAct, nAct ); + if ( nAct == nGeneratedMin ) + ++nGeneratedMin; // Only after NotifyModified due to IsGenerated! +} + +ScChangeActionContent* ScChangeTrack::SearchContentAt( + const ScBigAddress& rPos, const ScChangeAction* pButNotThis ) const +{ + SCSIZE nSlot = ComputeContentSlot( rPos.Row() ); + for ( ScChangeActionContent* p = ppContentSlots[nSlot]; p; + p = p->GetNextInSlot() ) + { + if ( p != pButNotThis && !p->IsDeletedIn() && + p->GetBigRange().aStart == rPos ) + { + ScChangeActionContent* pContent = p->GetTopContent(); + if ( !pContent->IsDeletedIn() ) + return pContent; + } + } + return nullptr; +} + +void ScChangeTrack::AddDependentWithNotify( ScChangeAction* pParent, + ScChangeAction* pDependent ) +{ + ScChangeActionLinkEntry* pLink = pParent->AddDependent( pDependent ); + pDependent->AddLink( pParent, pLink ); + if ( aModifiedLink.IsSet() ) + { + sal_uLong nMod = pParent->GetActionNumber(); + NotifyModified( ScChangeTrackMsgType::Parent, nMod, nMod ); + } +} + +void ScChangeTrack::Dependencies( ScChangeAction* pAct ) +{ + // Find the last dependency for Col/Row/Tab each + // Concatenate Content at the same position + // Move dependencies + ScChangeActionType eActType = pAct->GetType(); + if ( eActType == SC_CAT_REJECT || + (eActType == SC_CAT_MOVE && pAct->IsRejecting()) ) + return ; // These Rejects are not dependent + + if ( eActType == SC_CAT_CONTENT ) + { + if ( !(static_cast<ScChangeActionContent*>(pAct)->GetNextContent() || + static_cast<ScChangeActionContent*>(pAct)->GetPrevContent()) ) + { // Concatenate Contents at same position + ScChangeActionContent* pContent = SearchContentAt( + pAct->GetBigRange().aStart, pAct ); + if ( pContent ) + { + pContent->SetNextContent( static_cast<ScChangeActionContent*>(pAct) ); + static_cast<ScChangeActionContent*>(pAct)->SetPrevContent( pContent ); + } + } + const ScCellValue& rCell = static_cast<ScChangeActionContent*>(pAct)->GetNewCell(); + if ( ScChangeActionContent::GetContentCellType(rCell) == SC_CACCT_MATREF ) + { + ScAddress aOrg; + bool bOrgFound = rCell.mpFormula->GetMatrixOrigin(rDoc, aOrg); + ScChangeActionContent* pContent = (bOrgFound ? SearchContentAt( aOrg, pAct ) : nullptr); + if ( pContent && pContent->IsMatrixOrigin() ) + { + AddDependentWithNotify( pContent, pAct ); + } + else + { + OSL_FAIL( "ScChangeTrack::Dependencies: MatOrg not found" ); + } + } + } + + if ( !(pLinkInsertCol || pLinkInsertRow || pLinkInsertTab || pLinkMove) ) + return ; // No Dependencies + if ( pAct->IsRejecting() ) + return ; // Except for Content no Dependencies + + // Insert in a corresponding Insert depends on it or else we would need + // to split the preceding one. + // Intersecting Inserts and Deletes are not dependent, everything else + // is dependent. + // The Insert last linked in is at the beginning of a chain, just the way we need it + + const ScBigRange& rRange = pAct->GetBigRange(); + bool bActNoInsert = !pAct->IsInsertType(); + bool bActColDel = ( eActType == SC_CAT_DELETE_COLS ); + bool bActRowDel = ( eActType == SC_CAT_DELETE_ROWS ); + bool bActTabDel = ( eActType == SC_CAT_DELETE_TABS ); + + if ( pLinkInsertCol && (eActType == SC_CAT_INSERT_COLS || + (bActNoInsert && !bActRowDel && !bActTabDel)) ) + { + for ( ScChangeActionLinkEntry* pL = pLinkInsertCol; pL; pL = pL->GetNext() ) + { + ScChangeActionIns* pTest = static_cast<ScChangeActionIns*>(pL->GetAction()); + if ( !pTest->IsRejected() && + pTest->GetBigRange().Intersects( rRange ) ) + { + AddDependentWithNotify( pTest, pAct ); + break; // for + } + } + } + if ( pLinkInsertRow && (eActType == SC_CAT_INSERT_ROWS || + (bActNoInsert && !bActColDel && !bActTabDel)) ) + { + for ( ScChangeActionLinkEntry* pL = pLinkInsertRow; pL; pL = pL->GetNext() ) + { + ScChangeActionIns* pTest = static_cast<ScChangeActionIns*>(pL->GetAction()); + if ( !pTest->IsRejected() && + pTest->GetBigRange().Intersects( rRange ) ) + { + AddDependentWithNotify( pTest, pAct ); + break; // for + } + } + } + if ( pLinkInsertTab && (eActType == SC_CAT_INSERT_TABS || + (bActNoInsert && !bActColDel && !bActRowDel)) ) + { + for ( ScChangeActionLinkEntry* pL = pLinkInsertTab; pL; pL = pL->GetNext() ) + { + ScChangeActionIns* pTest = static_cast<ScChangeActionIns*>(pL->GetAction()); + if ( !pTest->IsRejected() && + pTest->GetBigRange().Intersects( rRange ) ) + { + AddDependentWithNotify( pTest, pAct ); + break; // for + } + } + } + + if ( !pLinkMove ) + return; + + if ( eActType == SC_CAT_CONTENT ) + { // Content is depending on FromRange + const ScBigAddress& rPos = rRange.aStart; + for ( ScChangeActionLinkEntry* pL = pLinkMove; pL; pL = pL->GetNext() ) + { + ScChangeActionMove* pTest = static_cast<ScChangeActionMove*>(pL->GetAction()); + if ( !pTest->IsRejected() && + pTest->GetFromRange().Contains( rPos ) ) + { + AddDependentWithNotify( pTest, pAct ); + } + } + } + else if ( eActType == SC_CAT_MOVE ) + { // Move FromRange is depending on ToRange + const ScBigRange& rFromRange = static_cast<ScChangeActionMove*>(pAct)->GetFromRange(); + for ( ScChangeActionLinkEntry* pL = pLinkMove; pL; pL = pL->GetNext() ) + { + ScChangeActionMove* pTest = static_cast<ScChangeActionMove*>(pL->GetAction()); + if ( !pTest->IsRejected() && + pTest->GetBigRange().Intersects( rFromRange ) ) + { + AddDependentWithNotify( pTest, pAct ); + } + } + } + else + { // Inserts and Deletes are depending as soon as they cross FromRange or + // ToRange + for ( ScChangeActionLinkEntry* pL = pLinkMove; pL; pL = pL->GetNext() ) + { + ScChangeActionMove* pTest = static_cast<ScChangeActionMove*>(pL->GetAction()); + if ( !pTest->IsRejected() && + (pTest->GetFromRange().Intersects( rRange ) || + pTest->GetBigRange().Intersects( rRange )) ) + { + AddDependentWithNotify( pTest, pAct ); + } + } + } +} + +void ScChangeTrack::Remove( ScChangeAction* pRemove ) +{ + // Remove from Track + sal_uLong nAct = pRemove->GetActionNumber(); + aMap.erase( nAct ); + if ( nAct == nActionMax ) + --nActionMax; + if ( pRemove == pLast ) + pLast = pRemove->pPrev; + if ( pRemove == pFirst ) + pFirst = pRemove->pNext; + if ( nAct == nMarkLastSaved ) + nMarkLastSaved = + ( pRemove->pPrev ? pRemove->pPrev->GetActionNumber() : 0 ); + + // Remove from global chain + if ( pRemove->pNext ) + pRemove->pNext->pPrev = pRemove->pPrev; + if ( pRemove->pPrev ) + pRemove->pPrev->pNext = pRemove->pNext; + + // Don't delete Dependencies + // That happens automatically on delete by LinkEntry without traversing lists + if ( aModifiedLink.IsSet() ) + { + NotifyModified( ScChangeTrackMsgType::Remove, nAct, nAct ); + if ( pRemove->GetType() == SC_CAT_CONTENT ) + { + ScChangeActionContent* pContent = static_cast<ScChangeActionContent*>(pRemove); + if ( ( pContent = pContent->GetPrevContent() ) != nullptr ) + { + sal_uLong nMod = pContent->GetActionNumber(); + NotifyModified( ScChangeTrackMsgType::Change, nMod, nMod ); + } + } + else if ( pLast ) + NotifyModified( ScChangeTrackMsgType::Change, pFirst->GetActionNumber(), + pLast->GetActionNumber() ); + } + + if ( IsInPasteCut() && pRemove->GetType() == SC_CAT_CONTENT ) + { // Content is reused! + ScChangeActionContent* pContent = static_cast<ScChangeActionContent*>(pRemove); + pContent->RemoveAllLinks(); + pContent->ClearTrack(); + pContent->pNext = pContent->pPrev = nullptr; + pContent->pNextContent = pContent->pPrevContent = nullptr; + } +} + +void ScChangeTrack::Undo( sal_uLong nStartAction, sal_uLong nEndAction, bool bMerge ) +{ + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + if ( bMerge ) + { + SetMergeState( SC_CTMS_UNDO ); + } + + if ( nStartAction == 0 ) + ++nStartAction; + if ( nEndAction > nActionMax ) + nEndAction = nActionMax; + if ( nEndAction && nStartAction <= nEndAction ) + { + if ( nStartAction == nStartLastCut && nEndAction == nEndLastCut && + !IsInPasteCut() ) + ResetLastCut(); + StartBlockModify( ScChangeTrackMsgType::Remove, nStartAction ); + for ( sal_uLong j = nEndAction; j >= nStartAction; --j ) + { // Traverse backwards to recycle nActionMax and for faster access via pLast + // Deletes are in right order + ScChangeAction* pAct = IsLastAction(j) ? pLast : GetAction(j); + + if (!pAct) + continue; + + if ( pAct->IsDeleteType() ) + { + if (j == nEndAction || (pAct != pLast && static_cast<ScChangeActionDel*>(pAct)->IsTopDelete())) + { + SetInDeleteTop( true ); + SetInDeleteRange( static_cast<ScChangeActionDel*>(pAct)->GetOverAllRange().MakeRange( rDoc ) ); + } + } + UpdateReference( pAct, true ); + SetInDeleteTop( false ); + Remove( pAct ); + if ( IsInPasteCut() ) + { + aPasteCutMap.insert( ::std::make_pair( pAct->GetActionNumber(), pAct ) ); + continue; + } + + if ( j == nStartAction && pAct->GetType() == SC_CAT_MOVE ) + { + ScChangeActionMove* pMove = static_cast<ScChangeActionMove*>(pAct); + sal_uLong nStart = pMove->GetStartLastCut(); + sal_uLong nEnd = pMove->GetEndLastCut(); + if ( nStart && nStart <= nEnd ) + { // Recover LastCut + // Break Links before Cut Append! + pMove->RemoveAllLinks(); + StartBlockModify( ScChangeTrackMsgType::Append, nStart ); + for ( sal_uLong nCut = nStart; nCut <= nEnd; nCut++ ) + { + ScChangeActionMap::iterator itCut = aPasteCutMap.find( nCut ); + + if ( itCut != aPasteCutMap.end() ) + { + OSL_ENSURE( aMap.find( nCut ) == aMap.end(), "ScChangeTrack::Undo: nCut dup" ); + Append( itCut->second, nCut ); + aPasteCutMap.erase( itCut ); + } + else + { + OSL_FAIL( "ScChangeTrack::Undo: nCut not found" ); + } + } + EndBlockModify( nEnd ); + ResetLastCut(); + nStartLastCut = nStart; + nEndLastCut = nEnd; + pLastCutMove.reset(pMove); + SetLastCutMoveRange( + pMove->GetFromRange().MakeRange( rDoc ), &rDoc ); + } + else + delete pMove; + } + else + delete pAct; + } + EndBlockModify( nEndAction ); + } + + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + if ( bMerge ) + { + SetMergeState( SC_CTMS_OTHER ); + } +} + +bool ScChangeTrack::MergeIgnore( const ScChangeAction& rAction, sal_uLong nFirstMerge ) +{ + if ( rAction.IsRejected() ) + return true; // There's still a suitable Reject Action coming + + if ( rAction.IsRejecting() && rAction.GetRejectAction() >= nFirstMerge ) + return true; // There it is + + return false; // Everything else +} + +void ScChangeTrack::MergePrepare( const ScChangeAction* pFirstMerge, bool bShared ) +{ + SetMergeState( SC_CTMS_PREPARE ); + sal_uLong nFirstMerge = pFirstMerge->GetActionNumber(); + ScChangeAction* pAct = GetLast(); + if ( pAct ) + { + SetLastMerge( pAct->GetActionNumber() ); + while ( pAct ) + { // Traverse backwards; Deletes in right order + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + if ( bShared || !ScChangeTrack::MergeIgnore( *pAct, nFirstMerge ) ) + { + if ( pAct->IsDeleteType() ) + { + if ( static_cast<ScChangeActionDel*>(pAct)->IsTopDelete() ) + { + SetInDeleteTop( true ); + SetInDeleteRange( static_cast<ScChangeActionDel*>(pAct)-> + GetOverAllRange().MakeRange( rDoc ) ); + } + } + UpdateReference( pAct, true ); + SetInDeleteTop( false ); + pAct->DeleteCellEntries(); // Else segfault in Track Clear() + } + pAct = ( pAct == pFirstMerge ? nullptr : pAct->GetPrev() ); + } + } + SetMergeState( SC_CTMS_OTHER ); // Preceding by default MergeOther! +} + +void ScChangeTrack::MergeOwn( ScChangeAction* pAct, sal_uLong nFirstMerge, bool bShared ) +{ + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + if ( !bShared && ScChangeTrack::MergeIgnore( *pAct, nFirstMerge ) ) + return; + + SetMergeState( SC_CTMS_OWN ); + if ( pAct->IsDeleteType() ) + { + if ( static_cast<ScChangeActionDel*>(pAct)->IsTopDelete() ) + { + SetInDeleteTop( true ); + SetInDeleteRange( static_cast<ScChangeActionDel*>(pAct)-> + GetOverAllRange().MakeRange( rDoc ) ); + } + } + UpdateReference( pAct, false ); + SetInDeleteTop( false ); + SetMergeState( SC_CTMS_OTHER ); // Preceding by default MergeOther! +} + +void ScChangeTrack::UpdateReference( ScChangeAction* pAct, bool bUndo ) +{ + ScChangeActionType eActType = pAct->GetType(); + if ( eActType == SC_CAT_CONTENT || eActType == SC_CAT_REJECT ) + return ; + + // Formula cells are not in the Document! + bool bOldAutoCalc = rDoc.GetAutoCalc(); + rDoc.SetAutoCalc( false ); + bool bOldNoListening = rDoc.GetNoListening(); + rDoc.SetNoListening( true ); + + // Formula cells ExpandRefs synchronized to the ones in the Document! + bool bOldExpandRefs = rDoc.IsExpandRefs(); + if ( (!bUndo && pAct->IsInsertType()) || (bUndo && pAct->IsDeleteType()) ) + rDoc.SetExpandRefs( SC_MOD()->GetInputOptions().GetExpandRefs() ); + + if ( pAct->IsDeleteType() ) + { + SetInDeleteUndo( bUndo ); + SetInDelete( true ); + } + else if ( GetMergeState() == SC_CTMS_OWN ) + { + // Recover references of formula cells + // Previous MergePrepare behaved like a Delete when Inserting + if ( pAct->IsInsertType() ) + SetInDeleteUndo( true ); + } + + // First the generated ones, as if they were tracked previously! + if ( pFirstGeneratedDelContent ) + UpdateReference( reinterpret_cast<ScChangeAction**>(&pFirstGeneratedDelContent), pAct, + bUndo ); + UpdateReference( &pFirst, pAct, bUndo ); + + SetInDelete( false ); + SetInDeleteUndo( false ); + + rDoc.SetExpandRefs( bOldExpandRefs ); + rDoc.SetNoListening( bOldNoListening ); + rDoc.SetAutoCalc( bOldAutoCalc ); +} + +void ScChangeTrack::UpdateReference( ScChangeAction** ppFirstAction, + ScChangeAction* pAct, bool bUndo ) +{ + ScChangeActionType eActType = pAct->GetType(); + bool bGeneratedDelContents = + ( ppFirstAction == reinterpret_cast<ScChangeAction**>(&pFirstGeneratedDelContent) ); + const ScBigRange& rOrgRange = pAct->GetBigRange(); + ScBigRange aRange( rOrgRange ); + ScBigRange aDelRange( rOrgRange ); + sal_Int32 nDx, nDy, nDz; + nDx = nDy = nDz = 0; + UpdateRefMode eMode = URM_INSDEL; + bool bDel = false; + switch ( eActType ) + { + case SC_CAT_INSERT_COLS : + aRange.aEnd.SetCol( ScBigRange::nRangeMax ); + nDx = rOrgRange.aEnd.Col() - rOrgRange.aStart.Col() + 1; + break; + case SC_CAT_INSERT_ROWS : + aRange.aEnd.SetRow( ScBigRange::nRangeMax ); + nDy = rOrgRange.aEnd.Row() - rOrgRange.aStart.Row() + 1; + break; + case SC_CAT_INSERT_TABS : + aRange.aEnd.SetTab( ScBigRange::nRangeMax ); + nDz = rOrgRange.aEnd.Tab() - rOrgRange.aStart.Tab() + 1; + break; + case SC_CAT_DELETE_COLS : + aRange.aEnd.SetCol( ScBigRange::nRangeMax ); + nDx = -(rOrgRange.aEnd.Col() - rOrgRange.aStart.Col() + 1); + aDelRange.aEnd.SetCol( aDelRange.aStart.Col() - nDx - 1 ); + bDel = true; + break; + case SC_CAT_DELETE_ROWS : + aRange.aEnd.SetRow( ScBigRange::nRangeMax ); + nDy = -(rOrgRange.aEnd.Row() - rOrgRange.aStart.Row() + 1); + aDelRange.aEnd.SetRow( aDelRange.aStart.Row() - nDy - 1 ); + bDel = true; + break; + case SC_CAT_DELETE_TABS : + aRange.aEnd.SetTab( ScBigRange::nRangeMax ); + nDz = -(rOrgRange.aEnd.Tab() - rOrgRange.aStart.Tab() + 1); + aDelRange.aEnd.SetTab( aDelRange.aStart.Tab() - nDz - 1 ); + bDel = true; + break; + case SC_CAT_MOVE : + eMode = URM_MOVE; + static_cast<ScChangeActionMove*>(pAct)->GetDelta( nDx, nDy, nDz ); + break; + default: + OSL_FAIL( "ScChangeTrack::UpdateReference: unknown Type" ); + } + if ( bUndo ) + { + nDx = -nDx; + nDy = -nDy; + nDz = -nDz; + } + if ( bDel ) + { // For this mechanism we assume: + // There's only a whole, simple deleted row/column + ScChangeActionDel* pActDel = static_cast<ScChangeActionDel*>(pAct); + if ( !bUndo ) + { // Delete + ScChangeActionType eInsType = SC_CAT_NONE; // for Insert Undo "Deletes" + switch ( eActType ) + { + case SC_CAT_DELETE_COLS : + eInsType = SC_CAT_INSERT_COLS; + break; + case SC_CAT_DELETE_ROWS : + eInsType = SC_CAT_INSERT_ROWS; + break; + case SC_CAT_DELETE_TABS : + eInsType = SC_CAT_INSERT_TABS; + break; + default: + { + // added to avoid warnings + } + } + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + bool bUpdate = true; + if ( GetMergeState() == SC_CTMS_OTHER && + p->GetActionNumber() <= GetLastMerge() ) + { // Delete in merged Document, Action in the one to be merged + if ( p->IsInsertType() ) + { + // On Insert only adjust references if the Delete does + // not intersect the Insert + if ( !aDelRange.Intersects( p->GetBigRange() ) ) + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + bUpdate = false; + } + else if ( p->GetType() == SC_CAT_CONTENT && + p->IsDeletedInDelType( eInsType ) ) + { // Content in Insert Undo "Delete" + // Do not adjust if this Delete would be in the Insert "Delete" (was just moved) + if ( aDelRange.Contains( p->GetBigRange().aStart ) ) + bUpdate = false; + else + { + const ScChangeActionLinkEntry* pLink = p->GetDeletedIn(); + while ( pLink && bUpdate ) + { + const ScChangeAction* pDel = pLink->GetAction(); + if ( pDel && pDel->GetType() == eInsType && + pDel->GetBigRange().Contains( aDelRange ) ) + bUpdate = false; + pLink = pLink->GetNext(); + } + } + } + if ( !bUpdate ) + continue; // for + } + if ( aDelRange.Contains( p->GetBigRange() ) ) + { + // Do not adjust within a just deleted range, + // instead assign the range. + // Stack up ranges that have been deleted multiple times. + // Intersecting Deletes cause "multiple delete" to be set. + if ( !p->IsDeletedInDelType( eActType ) ) + { + p->SetDeletedIn( pActDel ); + // Add GeneratedDelContent to the to-be-deleted list + if ( bGeneratedDelContents ) + pActDel->AddContent( static_cast<ScChangeActionContent*>(p) ); + } + bUpdate = false; + } + else + { + // Cut off inserted ranges, if Start/End is within the Delete, + // but the Insert is not completely within the Delete or + // the Delete is not completely within the Insert. + // The Delete remembers which Insert it has cut off from; + // it can also just be a single Insert (because Delete has + // a single column/is a single row). + // There can be a lot of cut-off Moves. + // + // ! A Delete is always a single column/a single row, therefore + // ! 1 without calculating the intersection. + switch ( p->GetType() ) + { + case SC_CAT_INSERT_COLS : + if ( eActType == SC_CAT_DELETE_COLS ) + { + if ( aDelRange.Contains( p->GetBigRange().aStart ) ) + { + pActDel->SetCutOffInsert( + static_cast<ScChangeActionIns*>(p), 1 ); + p->GetBigRange().aStart.IncCol(); + } + else if ( aDelRange.Contains( p->GetBigRange().aEnd ) ) + { + pActDel->SetCutOffInsert( + static_cast<ScChangeActionIns*>(p), -1 ); + p->GetBigRange().aEnd.IncCol( -1 ); + } + } + break; + case SC_CAT_INSERT_ROWS : + if ( eActType == SC_CAT_DELETE_ROWS ) + { + if ( aDelRange.Contains( p->GetBigRange().aStart ) ) + { + pActDel->SetCutOffInsert( + static_cast<ScChangeActionIns*>(p), 1 ); + p->GetBigRange().aStart.IncRow(); + } + else if ( aDelRange.Contains( p->GetBigRange().aEnd ) ) + { + pActDel->SetCutOffInsert( + static_cast<ScChangeActionIns*>(p), -1 ); + p->GetBigRange().aEnd.IncRow( -1 ); + } + } + break; + case SC_CAT_INSERT_TABS : + if ( eActType == SC_CAT_DELETE_TABS ) + { + if ( aDelRange.Contains( p->GetBigRange().aStart ) ) + { + pActDel->SetCutOffInsert( + static_cast<ScChangeActionIns*>(p), 1 ); + p->GetBigRange().aStart.IncTab(); + } + else if ( aDelRange.Contains( p->GetBigRange().aEnd ) ) + { + pActDel->SetCutOffInsert( + static_cast<ScChangeActionIns*>(p), -1 ); + p->GetBigRange().aEnd.IncTab( -1 ); + } + } + break; + case SC_CAT_MOVE : + { + ScChangeActionMove* pMove = static_cast<ScChangeActionMove*>(p); + short nFrom = 0; + short nTo = 0; + if ( aDelRange.Contains( pMove->GetBigRange().aStart ) ) + nTo = 1; + else if ( aDelRange.Contains( pMove->GetBigRange().aEnd ) ) + nTo = -1; + if ( aDelRange.Contains( pMove->GetFromRange().aStart ) ) + nFrom = 1; + else if ( aDelRange.Contains( pMove->GetFromRange().aEnd ) ) + nFrom = -1; + if ( nFrom ) + { + switch ( eActType ) + { + case SC_CAT_DELETE_COLS : + if ( nFrom > 0 ) + pMove->GetFromRange().aStart.IncCol( nFrom ); + else + pMove->GetFromRange().aEnd.IncCol( nFrom ); + break; + case SC_CAT_DELETE_ROWS : + if ( nFrom > 0 ) + pMove->GetFromRange().aStart.IncRow( nFrom ); + else + pMove->GetFromRange().aEnd.IncRow( nFrom ); + break; + case SC_CAT_DELETE_TABS : + if ( nFrom > 0 ) + pMove->GetFromRange().aStart.IncTab( nFrom ); + else + pMove->GetFromRange().aEnd.IncTab( nFrom ); + break; + default: + { + // added to avoid warnings + } + } + } + if ( nTo ) + { + switch ( eActType ) + { + case SC_CAT_DELETE_COLS : + if ( nTo > 0 ) + pMove->GetBigRange().aStart.IncCol( nTo ); + else + pMove->GetBigRange().aEnd.IncCol( nTo ); + break; + case SC_CAT_DELETE_ROWS : + if ( nTo > 0 ) + pMove->GetBigRange().aStart.IncRow( nTo ); + else + pMove->GetBigRange().aEnd.IncRow( nTo ); + break; + case SC_CAT_DELETE_TABS : + if ( nTo > 0 ) + pMove->GetBigRange().aStart.IncTab( nTo ); + else + pMove->GetBigRange().aEnd.IncTab( nTo ); + break; + default: + { + // added to avoid warnings + } + } + } + if ( nFrom || nTo ) + { + ScChangeActionDelMoveEntry* pLink = + pActDel->AddCutOffMove( pMove, nFrom, nTo ); + pMove->AddLink( pActDel, pLink ); + } + } + break; + default: + { + // added to avoid warnings + } + } + } + if ( bUpdate ) + { + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + if ( p->GetType() == eActType && !p->IsRejected() && + !pActDel->IsDeletedIn() && + p->GetBigRange().Contains( aDelRange ) ) + pActDel->SetDeletedIn( p ); // Slipped underneath it + } + } + } + else + { // Undo Delete + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + bool bUpdate = true; + if ( aDelRange.Contains( p->GetBigRange() ) ) + { + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + if ( GetMergeState() == SC_CTMS_UNDO && !p->IsDeletedIn( pAct ) && pAct->IsDeleteType() && + ( p->GetType() == SC_CAT_CONTENT || + p->GetType() == SC_CAT_DELETE_ROWS || p->GetType() == SC_CAT_DELETE_COLS || + p->GetType() == SC_CAT_INSERT_ROWS || p->GetType() == SC_CAT_INSERT_COLS ) ) + { + p->SetDeletedIn( pAct ); + } + + if ( p->IsDeletedInDelType( eActType ) ) + { + if ( p->IsDeletedIn( pActDel ) ) + { + if ( p->GetType() != SC_CAT_CONTENT || + static_cast<ScChangeActionContent*>(p)->IsTopContent() ) + { // First really remove the TopContent + p->RemoveDeletedIn( pActDel ); + // Do NOT delete GeneratedDelContent from the list, we might need + // it later on for Reject; we delete in DeleteCellEntries + } + } + bUpdate = false; + } + else if ( eActType != SC_CAT_DELETE_TABS && + p->IsDeletedInDelType( SC_CAT_DELETE_TABS ) ) + { // Do not update in deleted Tables except for when moving Tables + bUpdate = false; + } + if ( p->GetType() == eActType && pActDel->IsDeletedIn( p ) ) + { + pActDel->RemoveDeletedIn( p );// Slipped underneath + bUpdate = true; + } + } + if ( bUpdate ) + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + } + if ( !bGeneratedDelContents ) + { // These are else also needed for the real Undo + pActDel->UndoCutOffInsert(); + pActDel->UndoCutOffMoves(); + } + } + } + else if ( eActType == SC_CAT_MOVE ) + { + ScChangeActionMove* pActMove = static_cast<ScChangeActionMove*>(pAct); + bool bLastCutMove = ( pActMove == pLastCutMove.get() ); + const ScBigRange& rTo = pActMove->GetBigRange(); + const ScBigRange& rFrom = pActMove->GetFromRange(); + if ( !bUndo ) + { // Move + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + if ( p->GetType() == SC_CAT_CONTENT ) + { + // Delete content in Target (Move Content to Source) + if ( rTo.Contains( p->GetBigRange() ) ) + { + if ( !p->IsDeletedIn( pActMove ) ) + { + p->SetDeletedIn( pActMove ); + // Add GeneratedDelContent to the to-be-deleted list + if ( bGeneratedDelContents ) + pActMove->AddContent( static_cast<ScChangeActionContent*>(p) ); + } + } + else if ( bLastCutMove && + p->GetActionNumber() > nEndLastCut && + rFrom.Contains( p->GetBigRange() ) ) + { // Paste Cut: insert new Content inserted after stays + // Split up the ContentChain + ScChangeActionContent *pHere, *pTmp; + pHere = static_cast<ScChangeActionContent*>(p); + for (;;) + { + pTmp = pHere->GetPrevContent(); + if (!pTmp || pTmp->GetActionNumber() <= nEndLastCut) + break; + pHere = pTmp; + } + if ( pTmp ) + { // Becomes TopContent of the Move + pTmp->SetNextContent( nullptr ); + pHere->SetPrevContent( nullptr ); + } + do + { // Recover dependency from FromRange + AddDependentWithNotify( pActMove, pHere ); + } while ( ( pHere = pHere->GetNextContent() ) != nullptr ); + } + // #i87003# [Collaboration] Move range and insert content in FromRange is not merged correctly + else if ( ( GetMergeState() != SC_CTMS_PREPARE && GetMergeState() != SC_CTMS_OWN ) || p->GetActionNumber() <= pAct->GetActionNumber() ) + p->UpdateReference( this, eMode, rFrom, nDx, nDy, nDz ); + } + } + } + else + { // Undo Move + bool bActRejected = pActMove->IsRejected(); + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + if ( p->GetType() == SC_CAT_CONTENT ) + { + // Move Content into Target if not deleted else to delete (FIXME: What?) + if ( p->IsDeletedIn( pActMove ) ) + { + if ( static_cast<ScChangeActionContent*>(p)->IsTopContent() ) + { // First really remove the TopContent + p->RemoveDeletedIn( pActMove ); + // Do NOT delete GeneratedDelContent from the list, we might need + // it later on for Reject; we delete in DeleteCellEntries + } + } + // #i87003# [Collaboration] Move range and insert content in FromRange is not merged correctly + else if ( ( GetMergeState() != SC_CTMS_PREPARE && GetMergeState() != SC_CTMS_OWN ) || p->GetActionNumber() <= pAct->GetActionNumber() ) + p->UpdateReference( this, eMode, rTo, nDx, nDy, nDz ); + if ( bActRejected && + static_cast<ScChangeActionContent*>(p)->IsTopContent() && + rFrom.Contains( p->GetBigRange() ) ) + { // Recover dependency to write Content + ScChangeActionLinkEntry* pLink = + pActMove->AddDependent( p ); + p->AddLink( pActMove, pLink ); + } + } + } + } + } + else + { // Insert/Undo Insert + switch ( GetMergeState() ) + { + case SC_CTMS_NONE : + case SC_CTMS_OTHER : + { + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + } + } + break; + case SC_CTMS_PREPARE : + { + // "Delete" in Insert-Undo + const ScChangeActionLinkEntry* pLink = pAct->GetFirstDependentEntry(); + while ( pLink ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pLink->GetAction()); + if ( p ) + p->SetDeletedIn( pAct ); + pLink = pLink->GetNext(); + } + + // #i87049# [Collaboration] Conflict between delete row and insert content is not merged correctly + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( !p->IsDeletedIn( pAct ) && pAct->IsInsertType() && + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + ( p->GetType() == SC_CAT_CONTENT || + p->GetType() == SC_CAT_DELETE_ROWS || p->GetType() == SC_CAT_DELETE_COLS || + p->GetType() == SC_CAT_INSERT_ROWS || p->GetType() == SC_CAT_INSERT_COLS ) && + pAct->GetBigRange().Intersects( p->GetBigRange() ) ) + { + p->SetDeletedIn( pAct ); + } + } + + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + if ( !p->IsDeletedIn( pAct ) + // #i95212# [Collaboration] Bad handling of row insertion in shared spreadsheet + && p->GetActionNumber() <= pAct->GetActionNumber() ) + { + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + } + } + } + break; + case SC_CTMS_OWN : + { + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + if ( !p->IsDeletedIn( pAct ) + // #i95212# [Collaboration] Bad handling of row insertion in shared spreadsheet + && p->GetActionNumber() <= pAct->GetActionNumber() ) + { + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + } + } + // Undo "Delete" in Insert-Undo + const ScChangeActionLinkEntry* pLink = pAct->GetFirstDependentEntry(); + while ( pLink ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pLink->GetAction()); + if ( p ) + p->RemoveDeletedIn( pAct ); + pLink = pLink->GetNext(); + } + + // #i87049# [Collaboration] Conflict between delete row and insert content is not merged correctly + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p->IsDeletedIn( pAct ) && pAct->IsInsertType() && + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + ( p->GetType() == SC_CAT_CONTENT || + p->GetType() == SC_CAT_DELETE_ROWS || p->GetType() == SC_CAT_DELETE_COLS || + p->GetType() == SC_CAT_INSERT_ROWS || p->GetType() == SC_CAT_INSERT_COLS ) && + pAct->GetBigRange().Intersects( p->GetBigRange() ) ) + { + p->RemoveDeletedIn( pAct ); + } + } + } + break; + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + case SC_CTMS_UNDO : + { + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( !p->IsDeletedIn( pAct ) && pAct->IsInsertType() && + ( p->GetType() == SC_CAT_CONTENT || + p->GetType() == SC_CAT_DELETE_ROWS || p->GetType() == SC_CAT_DELETE_COLS || + p->GetType() == SC_CAT_INSERT_ROWS || p->GetType() == SC_CAT_INSERT_COLS ) && + pAct->GetBigRange().Intersects( p->GetBigRange() ) ) + { + p->SetDeletedIn( pAct ); + } + } + + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + { + continue; + } + if ( !p->IsDeletedIn( pAct ) && p->GetActionNumber() <= pAct->GetActionNumber() ) + { + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + } + } + } + break; + } + } +} + +void ScChangeTrack::GetDependents( ScChangeAction* pAct, + ScChangeActionMap& rMap, bool bListMasterDelete, bool bAllFlat ) const +{ + //TODO: bAllFlat==TRUE: called internally from Accept or Reject + //TODO: => Generated will not be added + bool bIsDelete = pAct->IsDeleteType(); + bool bIsMasterDelete = ( bListMasterDelete && pAct->IsMasterDelete() ); + + const ScChangeAction* pCur = nullptr; + ::std::stack<ScChangeAction*> cStack; + cStack.push(pAct); + + while ( !cStack.empty() ) + { + pCur = cStack.top(); + cStack.pop(); + + if ( pCur->IsInsertType() ) + { + const ScChangeActionLinkEntry* pL = pCur->GetFirstDependentEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p != pAct ) + { + if ( bAllFlat ) + { + sal_uLong n = p->GetActionNumber(); + if ( !IsGenerated( n ) && rMap.insert( ::std::make_pair( n, p ) ).second ) + if ( p->HasDependent() ) + cStack.push( p ); + } + else + { + if ( p->GetType() == SC_CAT_CONTENT ) + { + if ( static_cast<ScChangeActionContent*>(p)->IsTopContent() ) + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + else + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + } + pL = pL->GetNext(); + } + } + else if ( pCur->IsDeleteType() ) + { + if ( bIsDelete ) + { // Contents of deleted Ranges are only of interest on Delete + ScChangeActionDel* pDel = const_cast<ScChangeActionDel*>(static_cast<const ScChangeActionDel*>(pCur)); + if ( !bAllFlat && bIsMasterDelete && pCur == pAct ) + { + // Corresponding Deletes to this Delete to the same level, + // if this Delete is at the top of a Row + ScChangeActionType eType = pDel->GetType(); + ScChangeAction* p = pDel; + for (;;) + { + p = p->GetPrev(); + if (!p || p->GetType() != eType || + static_cast<ScChangeActionDel*>(p)->IsTopDelete() ) + break; + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + // delete this in the map too + rMap.insert( ::std::make_pair( pAct->GetActionNumber(), pAct ) ); + } + else + { + const ScChangeActionLinkEntry* pL = pCur->GetFirstDeletedEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p != pAct ) + { + if ( bAllFlat ) + { + // Only a TopContent of a chain is in LinkDeleted + sal_uLong n = p->GetActionNumber(); + if ( !IsGenerated( n ) && rMap.insert( ::std::make_pair( n, p ) ).second ) + if ( p->HasDeleted() || + p->GetType() == SC_CAT_CONTENT ) + cStack.push( p ); + } + else + { + if ( p->IsDeleteType() ) + { // Further TopDeletes to same level: it's not rejectable + if ( static_cast<ScChangeActionDel*>(p)->IsTopDelete() ) + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + else + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + } + pL = pL->GetNext(); + } + } + } + } + else if ( pCur->GetType() == SC_CAT_MOVE ) + { + // Deleted Contents in ToRange + const ScChangeActionLinkEntry* pL = pCur->GetFirstDeletedEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p != pAct && rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ).second ) + { + // Only one TopContent of a chain is in LinkDeleted + if ( bAllFlat && (p->HasDeleted() || + p->GetType() == SC_CAT_CONTENT) ) + cStack.push( p ); + } + pL = pL->GetNext(); + } + // New Contents in FromRange or new FromRange in ToRange + // or Inserts/Deletes in FromRange/ToRange + pL = pCur->GetFirstDependentEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p != pAct ) + { + if ( bAllFlat ) + { + sal_uLong n = p->GetActionNumber(); + if ( !IsGenerated( n ) && rMap.insert( ::std::make_pair( n, p ) ).second ) + if ( p->HasDependent() || p->HasDeleted() ) + cStack.push( p ); + } + else + { + if ( p->GetType() == SC_CAT_CONTENT ) + { + if ( static_cast<ScChangeActionContent*>(p)->IsTopContent() ) + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + else + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + } + pL = pL->GetNext(); + } + } + else if ( pCur->GetType() == SC_CAT_CONTENT ) + { // All changes at same position + ScChangeActionContent* pContent = const_cast<ScChangeActionContent*>(static_cast<const ScChangeActionContent*>(pCur)); + // All preceding ones + while ( ( pContent = pContent->GetPrevContent() ) != nullptr ) + { + if ( !pContent->IsRejected() ) + rMap.insert( ::std::make_pair( pContent->GetActionNumber(), pContent ) ); + } + pContent = const_cast<ScChangeActionContent*>(static_cast<const ScChangeActionContent*>(pCur)); + // All succeeding ones + while ( ( pContent = pContent->GetNextContent() ) != nullptr ) + { + if ( !pContent->IsRejected() ) + rMap.insert( ::std::make_pair( pContent->GetActionNumber(), pContent ) ); + } + // all MatrixReferences of a MatrixOrigin + const ScChangeActionLinkEntry* pL = pCur->GetFirstDependentEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p != pAct ) + { + if ( bAllFlat ) + { + sal_uLong n = p->GetActionNumber(); + if ( !IsGenerated( n ) && rMap.insert( ::std::make_pair( n, p ) ).second ) + if ( p->HasDependent() ) + cStack.push( p ); + } + else + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + pL = pL->GetNext(); + } + } + else if ( pCur->GetType() == SC_CAT_REJECT ) + { + if ( bAllFlat ) + { + ScChangeAction* p = GetAction( + static_cast<const ScChangeActionReject*>(pCur)->GetRejectAction() ); + if (p != pAct && rMap.find( p->GetActionNumber() ) == rMap.end()) + cStack.push( p ); + } + } + } +} + +bool ScChangeTrack::SelectContent( ScChangeAction* pAct, bool bOldest ) +{ + if ( pAct->GetType() != SC_CAT_CONTENT ) + return false; + + ScChangeActionContent* pContent = static_cast<ScChangeActionContent*>(pAct); + if ( bOldest ) + { + pContent = pContent->GetTopContent(); + for (;;) + { + ScChangeActionContent* pPrevContent = pContent->GetPrevContent(); + if ( !pPrevContent || !pPrevContent->IsVirgin() ) + break; + pContent = pPrevContent; + } + } + + if ( !pContent->IsClickable() ) + return false; + + ScBigRange aBigRange( pContent->GetBigRange() ); + const ScCellValue& rCell = (bOldest ? pContent->GetOldCell() : pContent->GetNewCell()); + if ( ScChangeActionContent::GetContentCellType(rCell) == SC_CACCT_MATORG ) + { + SCCOL nC; + SCROW nR; + rCell.mpFormula->GetMatColsRows(nC, nR); + aBigRange.aEnd.IncCol( nC-1 ); + aBigRange.aEnd.IncRow( nR-1 ); + } + + if ( !aBigRange.IsValid( rDoc ) ) + return false; + + ScRange aRange( aBigRange.MakeRange( rDoc ) ); + if ( !rDoc.IsBlockEditable( aRange.aStart.Tab(), aRange.aStart.Col(), + aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row() ) ) + return false; + + if ( pContent->HasDependent() ) + { + bool bOk = true; + ::std::stack<ScChangeActionContent*> aRejectActions; + const ScChangeActionLinkEntry* pL = pContent->GetFirstDependentEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p != pContent ) + { + if ( p->GetType() == SC_CAT_CONTENT ) + { + // we don't need no recursion here, do we? + bOk &= static_cast<ScChangeActionContent*>(p)->Select( rDoc, this, + bOldest, &aRejectActions ); + } + else + { + OSL_FAIL( "ScChangeTrack::SelectContent: content dependent no content" ); + } + } + pL = pL->GetNext(); + } + + bOk &= pContent->Select( rDoc, this, bOldest, nullptr ); + // now the matrix is inserted and new content values are ready + + while ( !aRejectActions.empty() ) + { + ScChangeActionContent* pNew = aRejectActions.top(); + aRejectActions.pop(); + ScAddress aPos( pNew->GetBigRange().aStart.MakeAddress( rDoc ) ); + ScCellValue aCell; + aCell.assign(rDoc, aPos); + pNew->SetNewValue(aCell, &rDoc); + Append( pNew ); + } + return bOk; + } + else + return pContent->Select( rDoc, this, bOldest, nullptr ); +} + +void ScChangeTrack::AcceptAll() +{ + for ( ScChangeAction* p = GetFirst(); p; p = p->GetNext() ) + { + p->Accept(); + } +} + +bool ScChangeTrack::Accept( ScChangeAction* pAct ) +{ + if ( !pAct->IsClickable() ) + return false; + + if ( pAct->IsDeleteType() || pAct->GetType() == SC_CAT_CONTENT ) + { + ScChangeActionMap aActionMap; + + GetDependents( pAct, aActionMap, false, true ); + + for( auto& rEntry : aActionMap ) + { + rEntry.second->Accept(); + } + } + pAct->Accept(); + return true; +} + +bool ScChangeTrack::RejectAll() +{ + bool bOk = true; + for ( ScChangeAction* p = GetLast(); p && bOk; p = p->GetPrev() ) + { //TODO: Traverse backwards as dependencies attached to RejectActions + if ( p->IsInternalRejectable() ) + bOk = Reject( p ); + } + return bOk; +} + +bool ScChangeTrack::Reject( ScChangeAction* pAct, bool bShared ) +{ + // #i100895# When collaboration changes are reversed, it must be possible + // to reject a deleted row above another deleted row. + if ( bShared && pAct->IsDeletedIn() ) + pAct->RemoveAllDeletedIn(); + + if ( !pAct->IsRejectable() ) + return false; + + std::unique_ptr<ScChangeActionMap> pMap; + if ( pAct->HasDependent() ) + { + pMap.reset(new ScChangeActionMap); + GetDependents( pAct, *pMap, false, true ); + } + bool bRejected = Reject( pAct, pMap.get(), false ); + return bRejected; +} + +bool ScChangeTrack::Reject( + ScChangeAction* pAct, ScChangeActionMap* pMap, bool bRecursion ) +{ + if ( !pAct->IsInternalRejectable() ) + return false; + + bool bOk = true; + bool bRejected = false; + if ( pAct->IsInsertType() ) + { + if ( pAct->HasDependent() && !bRecursion ) + { + OSL_ENSURE( pMap, "ScChangeTrack::Reject: Insert without map" ); + ScChangeActionMap::reverse_iterator itChangeAction; + for (itChangeAction = pMap->rbegin(); + itChangeAction != pMap->rend() && bOk; ++itChangeAction) + { + // Do not restore Contents which would end up being deleted anyways + if ( itChangeAction->second->GetType() == SC_CAT_CONTENT ) + itChangeAction->second->SetRejected(); + else if ( itChangeAction->second->IsDeleteType() ) + itChangeAction->second->Accept(); // Deleted to Nirvana + else + bOk = Reject( itChangeAction->second, nullptr, true ); // Recursion! + } + } + if ( bOk ) + { + bRejected = pAct->Reject( rDoc ); + if ( bRejected ) + { + // pRefDoc NULL := Do not save deleted Cells + AppendDeleteRange( pAct->GetBigRange().MakeRange( rDoc ), nullptr, short(0), + pAct->GetActionNumber() ); + } + } + } + else if ( pAct->IsDeleteType() ) + { + OSL_ENSURE( !pMap, "ScChangeTrack::Reject: Delete with map" ); + ScBigRange aDelRange; + sal_uLong nRejectAction = pAct->GetActionNumber(); + bool bTabDel, bTabDelOk; + if ( pAct->GetType() == SC_CAT_DELETE_TABS ) + { + bTabDel = true; + aDelRange = pAct->GetBigRange(); + bTabDelOk = pAct->Reject( rDoc ); + bOk = bTabDelOk; + if ( bOk ) + { + pAct = pAct->GetPrev(); + bOk = ( pAct && pAct->GetType() == SC_CAT_DELETE_COLS ); + } + } + else + bTabDel = bTabDelOk = false; + ScChangeActionDel* pDel = static_cast<ScChangeActionDel*>(pAct); + if ( bOk ) + { + aDelRange = pDel->GetOverAllRange(); + bOk = aDelRange.IsValid( rDoc ); + } + bool bOneOk = false; + if ( bOk ) + { + ScChangeActionType eActType = pAct->GetType(); + switch ( eActType ) + { + case SC_CAT_DELETE_COLS : + aDelRange.aStart.SetCol( aDelRange.aEnd.Col() ); + break; + case SC_CAT_DELETE_ROWS : + aDelRange.aStart.SetRow( aDelRange.aEnd.Row() ); + break; + case SC_CAT_DELETE_TABS : + aDelRange.aStart.SetTab( aDelRange.aEnd.Tab() ); + break; + default: + { + // added to avoid warnings + } + } + ScChangeAction* p = pAct; + bool bLoop = true; + do + { + pDel = static_cast<ScChangeActionDel*>(p); + bOk = pDel->Reject( rDoc ); + if ( bOk ) + { + if ( bOneOk ) + { + switch ( pDel->GetType() ) + { + case SC_CAT_DELETE_COLS : + aDelRange.aStart.IncCol( -1 ); + break; + case SC_CAT_DELETE_ROWS : + aDelRange.aStart.IncRow( -1 ); + break; + case SC_CAT_DELETE_TABS : + aDelRange.aStart.IncTab( -1 ); + break; + default: + { + // added to avoid warnings + } + } + } + else + bOneOk = true; + } + if ( pDel->IsBaseDelete() ) + bLoop = false; + else + p = p->GetPrev(); + } while ( bOk && bLoop && p && p->GetType() == eActType && + !static_cast<ScChangeActionDel*>(p)->IsTopDelete() ); + } + bRejected = bOk; + if ( bOneOk || (bTabDel && bTabDelOk) ) + { + // Delete Reject made UpdateReference Undo + ScChangeActionIns* pReject = new ScChangeActionIns( &rDoc, + aDelRange.MakeRange( rDoc ) ); + pReject->SetRejectAction( nRejectAction ); + pReject->SetState( SC_CAS_ACCEPTED ); + Append( pReject ); + } + } + else if ( pAct->GetType() == SC_CAT_MOVE ) + { + if ( pAct->HasDependent() && !bRecursion ) + { + OSL_ENSURE( pMap, "ScChangeTrack::Reject: Move without Map" ); + ScChangeActionMap::reverse_iterator itChangeAction; + + for( itChangeAction = pMap->rbegin(); itChangeAction != pMap->rend() && bOk; ++itChangeAction ) + { + bOk = Reject( itChangeAction->second, nullptr, true ); // Recursion! + } + } + if ( bOk ) + { + bRejected = pAct->Reject( rDoc ); + if ( bRejected ) + { + ScChangeActionMove* pReject = new ScChangeActionMove( + pAct->GetBigRange().MakeRange( rDoc ), + static_cast<ScChangeActionMove*>(pAct)->GetFromRange().MakeRange( rDoc ), this ); + pReject->SetRejectAction( pAct->GetActionNumber() ); + pReject->SetState( SC_CAS_ACCEPTED ); + Append( pReject ); + } + } + } + else if ( pAct->GetType() == SC_CAT_CONTENT ) + { + ScRange aRange; + ScChangeActionContent* pReject; + if ( bRecursion ) + pReject = nullptr; + else + { + aRange = pAct->GetBigRange().aStart.MakeAddress( rDoc ); + pReject = new ScChangeActionContent( aRange ); + ScCellValue aCell; + aCell.assign(rDoc, aRange.aStart); + pReject->SetOldValue(aCell, &rDoc, &rDoc); + } + bRejected = pAct->Reject( rDoc ); + if ( bRejected && !bRecursion ) + { + ScCellValue aCell; + aCell.assign(rDoc, aRange.aStart); + pReject->SetNewValue(aCell, &rDoc); + pReject->SetRejectAction( pAct->GetActionNumber() ); + pReject->SetState( SC_CAS_ACCEPTED ); + Append( pReject ); + } + else + delete pReject; + } + else + { + OSL_FAIL( "ScChangeTrack::Reject: say what?" ); + } + + return bRejected; +} + +bool ScChangeTrack::IsLastAction( sal_uLong nNum ) const +{ + return nNum == nActionMax && pLast && pLast->GetActionNumber() == nNum; +} + +sal_uLong ScChangeTrack::AddLoadedGenerated( + const ScCellValue& rNewCell, const ScBigRange& aBigRange, const OUString& sNewValue ) +{ + ScChangeActionContent* pAct = new ScChangeActionContent( --nGeneratedMin, rNewCell, aBigRange, &rDoc, sNewValue ); + if ( pFirstGeneratedDelContent ) + pFirstGeneratedDelContent->pPrev = pAct; + pAct->pNext = pFirstGeneratedDelContent; + pFirstGeneratedDelContent = pAct; + aGeneratedMap.insert( ::std::make_pair( pAct->GetActionNumber(), pAct ) ); + return pAct->GetActionNumber(); +} + +void ScChangeTrack::AppendCloned( ScChangeAction* pAppend ) +{ + aMap.insert( ::std::make_pair( pAppend->GetActionNumber(), pAppend ) ); + if ( !pLast ) + pFirst = pLast = pAppend; + else + { + pLast->pNext = pAppend; + pAppend->pPrev = pLast; + pLast = pAppend; + } +} + +ScChangeTrack* ScChangeTrack::Clone( ScDocument* pDocument ) const +{ + if ( !pDocument ) + { + return nullptr; + } + + std::unique_ptr<ScChangeTrack> pClonedTrack(new ScChangeTrack( *pDocument )); + pClonedTrack->SetTimeNanoSeconds( IsTimeNanoSeconds() ); + + // clone generated actions + ::std::stack< const ScChangeAction* > aGeneratedStack; + const ScChangeAction* pGenerated = GetFirstGenerated(); + while ( pGenerated ) + { + aGeneratedStack.push( pGenerated ); + pGenerated = pGenerated->GetNext(); + } + while ( !aGeneratedStack.empty() ) + { + pGenerated = aGeneratedStack.top(); + aGeneratedStack.pop(); + const ScChangeActionContent& rContent = dynamic_cast<const ScChangeActionContent&>(*pGenerated); + const ScCellValue& rNewCell = rContent.GetNewCell(); + if (!rNewCell.isEmpty()) + { + ScCellValue aClonedNewCell; + aClonedNewCell.assign(rNewCell, *pDocument); + OUString aNewValue = rContent.GetNewString( pDocument ); + pClonedTrack->nGeneratedMin = pGenerated->GetActionNumber() + 1; + pClonedTrack->AddLoadedGenerated(aClonedNewCell, pGenerated->GetBigRange(), aNewValue); + } + } + + // clone actions + const ScChangeAction* pAction = GetFirst(); + while ( pAction ) + { + ScChangeAction* pClonedAction = nullptr; + + switch ( pAction->GetType() ) + { + case SC_CAT_INSERT_COLS: + case SC_CAT_INSERT_ROWS: + case SC_CAT_INSERT_TABS: + { + bool bEndOfList = static_cast<const ScChangeActionIns*>(pAction)->IsEndOfList(); + pClonedAction = new ScChangeActionIns( + pAction->GetActionNumber(), + pAction->GetState(), + pAction->GetRejectAction(), + pAction->GetBigRange(), + pAction->GetUser(), + pAction->GetDateTimeUTC(), + pAction->GetComment(), + pAction->GetType(), + bEndOfList ); + } + break; + case SC_CAT_DELETE_COLS: + case SC_CAT_DELETE_ROWS: + case SC_CAT_DELETE_TABS: + { + const ScChangeActionDel& rDelete = dynamic_cast<const ScChangeActionDel&>(*pAction); + + SCCOLROW nD = 0; + ScChangeActionType eType = pAction->GetType(); + if ( eType == SC_CAT_DELETE_COLS ) + { + nD = static_cast< SCCOLROW >( rDelete.GetDx() ); + } + else if ( eType == SC_CAT_DELETE_ROWS ) + { + nD = static_cast< SCCOLROW >( rDelete.GetDy() ); + } + + pClonedAction = new ScChangeActionDel( + pAction->GetActionNumber(), + pAction->GetState(), + pAction->GetRejectAction(), + pAction->GetBigRange(), + pAction->GetUser(), + pAction->GetDateTimeUTC(), + pAction->GetComment(), + eType, + nD, + pClonedTrack.get() ); + } + break; + case SC_CAT_MOVE: + { + auto pMove = dynamic_cast<const ScChangeActionMove*>(pAction); + assert(pMove && "ScChangeTrack::Clone: pMove is null!"); + + pClonedAction = new ScChangeActionMove( + pAction->GetActionNumber(), + pAction->GetState(), + pAction->GetRejectAction(), + pAction->GetBigRange(), + pAction->GetUser(), + pAction->GetDateTimeUTC(), + pAction->GetComment(), + pMove->GetFromRange(), + pClonedTrack.get() ); + } + break; + case SC_CAT_CONTENT: + { + const ScChangeActionContent& rContent = dynamic_cast<const ScChangeActionContent&>(*pAction); + const ScCellValue& rOldCell = rContent.GetOldCell(); + ScCellValue aClonedOldCell; + aClonedOldCell.assign(rOldCell, *pDocument); + OUString aOldValue = rContent.GetOldString( pDocument ); + + ScChangeActionContent* pClonedContent = new ScChangeActionContent( + pAction->GetActionNumber(), + pAction->GetState(), + pAction->GetRejectAction(), + pAction->GetBigRange(), + pAction->GetUser(), + pAction->GetDateTimeUTC(), + pAction->GetComment(), + aClonedOldCell, + pDocument, + aOldValue ); + + const ScCellValue& rNewCell = rContent.GetNewCell(); + if (!rNewCell.isEmpty()) + { + ScCellValue aClonedNewCell; + aClonedNewCell.assign(rNewCell, *pDocument); + pClonedContent->SetNewValue(aClonedNewCell, pDocument); + } + + pClonedAction = pClonedContent; + } + break; + case SC_CAT_REJECT: + { + pClonedAction = new ScChangeActionReject( + pAction->GetActionNumber(), + pAction->GetState(), + pAction->GetRejectAction(), + pAction->GetBigRange(), + pAction->GetUser(), + pAction->GetDateTimeUTC(), + pAction->GetComment() ); + } + break; + default: + { + } + break; + } + + if ( pClonedAction ) + { + pClonedTrack->AppendCloned( pClonedAction ); + } + + pAction = pAction->GetNext(); + } + + if ( pClonedTrack->GetLast() ) + { + pClonedTrack->SetActionMax( pClonedTrack->GetLast()->GetActionNumber() ); + } + + // set dependencies for Deleted/DeletedIn + pAction = GetFirst(); + while ( pAction ) + { + if ( pAction->HasDeleted() ) + { + ::std::stack< sal_uLong > aStack; + const ScChangeActionLinkEntry* pL = pAction->GetFirstDeletedEntry(); + while ( pL ) + { + const ScChangeAction* pDeleted = pL->GetAction(); + if ( pDeleted ) + { + aStack.push( pDeleted->GetActionNumber() ); + } + pL = pL->GetNext(); + } + ScChangeAction* pClonedAction = pClonedTrack->GetAction( pAction->GetActionNumber() ); + if ( pClonedAction ) + { + while ( !aStack.empty() ) + { + ScChangeAction* pClonedDeleted = pClonedTrack->GetActionOrGenerated( aStack.top() ); + aStack.pop(); + if ( pClonedDeleted ) + { + pClonedDeleted->SetDeletedIn( pClonedAction ); + } + } + } + } + pAction = pAction->GetNext(); + } + + // set dependencies for Dependent/Any + pAction = GetLast(); + while ( pAction ) + { + if ( pAction->HasDependent() ) + { + ::std::stack< sal_uLong > aStack; + const ScChangeActionLinkEntry* pL = pAction->GetFirstDependentEntry(); + while ( pL ) + { + const ScChangeAction* pDependent = pL->GetAction(); + if ( pDependent ) + { + aStack.push( pDependent->GetActionNumber() ); + } + pL = pL->GetNext(); + } + ScChangeAction* pClonedAction = pClonedTrack->GetAction( pAction->GetActionNumber() ); + if ( pClonedAction ) + { + while ( !aStack.empty() ) + { + ScChangeAction* pClonedDependent = pClonedTrack->GetActionOrGenerated( aStack.top() ); + aStack.pop(); + if ( pClonedDependent ) + { + ScChangeActionLinkEntry* pLink = pClonedAction->AddDependent( pClonedDependent ); + pClonedDependent->AddLink( pClonedAction, pLink ); + } + } + } + } + pAction = pAction->GetPrev(); + } + + // masterlinks + ScChangeAction* pClonedAction = pClonedTrack->GetFirst(); + while ( pClonedAction ) + { + pClonedTrack->MasterLinks( pClonedAction ); + pClonedAction = pClonedAction->GetNext(); + } + + if ( IsProtected() ) + { + pClonedTrack->SetProtection( GetProtection() ); + } + + if ( pClonedTrack->GetLast() ) + { + pClonedTrack->SetLastSavedActionNumber( pClonedTrack->GetLast()->GetActionNumber() ); + } + + auto tmp = pClonedTrack.get(); + pDocument->SetChangeTrack( std::move(pClonedTrack) ); + + return tmp; +} + +void ScChangeTrack::MergeActionState( ScChangeAction* pAct, const ScChangeAction* pOtherAct ) +{ + if ( !pAct->IsVirgin() ) + return; + + if ( pOtherAct->IsAccepted() ) + { + pAct->Accept(); + if ( pOtherAct->IsRejecting() ) + { + pAct->SetRejectAction( pOtherAct->GetRejectAction() ); + } + } + else if ( pOtherAct->IsRejected() ) + { + pAct->SetRejected(); + } +} + +/// Get info about a single ScChangeAction element. +static void lcl_getTrackedChange(ScDocument& rDoc, int nIndex, const ScChangeAction* pAction, tools::JsonWriter& rRedlines) +{ + if (pAction->GetType() != SC_CAT_CONTENT) + return; + + auto redlinesNode = rRedlines.startStruct(); + rRedlines.put("index", static_cast<sal_Int64>(nIndex)); + + rRedlines.put("author", pAction->GetUser()); + + rRedlines.put("type", "Modify"); + + rRedlines.put("comment", pAction->GetComment()); + + OUString aDescription = pAction->GetDescription(rDoc, true); + rRedlines.put("description", aDescription); + + OUString sDateTime = utl::toISO8601(pAction->GetDateTimeUTC().GetUNODateTime()); + rRedlines.put("dateTime", sDateTime); +} + +void ScChangeTrack::GetChangeTrackInfo(tools::JsonWriter& aRedlines) +{ + auto redlinesNode = aRedlines.startArray("redlines"); + + ScChangeAction* pAction = GetFirst(); + if (pAction) + { + int i = 0; + lcl_getTrackedChange(rDoc, i++, pAction, aRedlines); + ScChangeAction* pLastAction = GetLast(); + while (pAction != pLastAction) + { + pAction = pAction->GetNext(); + lcl_getTrackedChange(rDoc, i++, pAction, aRedlines); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |