summaryrefslogtreecommitdiffstats
path: root/sc/source/core/tool/chgtrack.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/tool/chgtrack.cxx')
-rw-r--r--sc/source/core/tool/chgtrack.cxx4671
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: */