4678 lines
160 KiB
C++
4678 lines
160 KiB
C++
/* -*- 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 <docsh.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>
|
|
#include <strings.hrc>
|
|
#include <utility>
|
|
|
|
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, ScBigRange aRange,
|
|
const sal_uLong nTempAction, const sal_uLong nTempRejectAction,
|
|
const ScChangeActionState eTempState, const DateTime& aTempDateTime,
|
|
OUString aTempUser, OUString aTempComment) :
|
|
aBigRange(std::move( aRange )),
|
|
aDateTime( aTempDateTime ),
|
|
aUser(std::move( aTempUser )),
|
|
aComment(std::move( 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, ScBigRange aRange,
|
|
const sal_uLong nTempAction)
|
|
:
|
|
aBigRange(std::move( aRange )),
|
|
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(ScColToAlpha(aTmpRange.aStart.Col())
|
|
+ ":" + 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(OUString::number(static_cast<sal_Int64>(aTmpRange.aStart.Row()+1))
|
|
+ ":" + OUString::number(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, 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(std::move(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,
|
|
ScCellValue aOldCell, const ScDocument* pDoc, const OUString& sOldValue ) :
|
|
ScChangeAction(SC_CAT_CONTENT, aBigRangeP, nActionNumber, nRejectingNumber, eStateP, aDateTimeP, aUserP, sComment),
|
|
maOldCell(std::move(aOldCell)),
|
|
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,
|
|
ScCellValue aNewCell, const ScBigRange& aBigRangeP,
|
|
const ScDocument* pDoc, const OUString& sNewValue ) :
|
|
ScChangeAction(SC_CAT_CONTENT, aBigRangeP, nActionNumber),
|
|
maNewCell(std::move(aNewCell)),
|
|
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.set(new ScFormulaCell(
|
|
*pDoc, aBigRange.aStart.MakeAddress(*pDoc), rStr,
|
|
pDoc->GetGrammar() ));
|
|
rCell.getFormula()->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.getFormula()->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(ScRange(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.getType())
|
|
{
|
|
case CELLTYPE_VALUE:
|
|
return pDoc->GetFormatTable()->GetInputLineString(rCell.getDouble(), nFormat);
|
|
case CELLTYPE_STRING:
|
|
return rCell.getSharedString()->getString();
|
|
case CELLTYPE_EDIT:
|
|
if (rCell.getEditText())
|
|
return ScEditUtil::GetString(*rCell.getEditText(), pDoc);
|
|
return OUString();
|
|
case CELLTYPE_FORMULA:
|
|
return rCell.getFormula()->GetFormula();
|
|
default:
|
|
return OUString();
|
|
}
|
|
}
|
|
|
|
ScChangeActionContentCellType ScChangeActionContent::GetContentCellType( const ScCellValue& rCell )
|
|
{
|
|
switch (rCell.getType())
|
|
{
|
|
case CELLTYPE_VALUE :
|
|
case CELLTYPE_STRING :
|
|
case CELLTYPE_EDIT :
|
|
return SC_CACCT_NORMAL;
|
|
case CELLTYPE_FORMULA :
|
|
switch (rCell.getFormula()->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.getType())
|
|
{
|
|
case CELLTYPE_VALUE:
|
|
case CELLTYPE_STRING:
|
|
case CELLTYPE_EDIT:
|
|
return SC_CACCT_NORMAL;
|
|
case CELLTYPE_FORMULA:
|
|
{
|
|
const ScFormulaCell* pCell = rCell.getFormula();
|
|
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.getType() == 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(ScRange(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.getType())
|
|
{
|
|
case CELLTYPE_VALUE :
|
|
{ // E.g.: Remember date as such
|
|
rStr = pFromDoc->GetFormatTable()->GetInputLineString(
|
|
rOrgCell.getDouble(), nFormat);
|
|
}
|
|
break;
|
|
case CELLTYPE_FORMULA :
|
|
rCell.getFormula()->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.getType())
|
|
{
|
|
case CELLTYPE_VALUE :
|
|
// e.g. remember date as date string
|
|
rStr = pDoc->GetFormatTable()->GetInputLineString(rCell.getDouble(), nFormat);
|
|
break;
|
|
case CELLTYPE_FORMULA :
|
|
rCell.getFormula()->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.getType())
|
|
{
|
|
case CELLTYPE_STRING :
|
|
return rCell.getSharedString()->getString();
|
|
case CELLTYPE_EDIT :
|
|
if (rCell.getEditText())
|
|
return ScEditUtil::GetString(*rCell.getEditText(), pDoc);
|
|
return OUString();
|
|
case CELLTYPE_VALUE : // Is always in rValue
|
|
return rValue;
|
|
case CELLTYPE_FORMULA :
|
|
return GetFormulaString(rCell.getFormula());
|
|
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.getType() == CELLTYPE_VALUE)
|
|
{
|
|
pDoc->SetString( aPos.Col(), aPos.Row(), aPos.Tab(), rValue );
|
|
return;
|
|
}
|
|
|
|
switch (GetContentCellType(rCell))
|
|
{
|
|
case SC_CACCT_MATORG :
|
|
{
|
|
SCCOL nC;
|
|
SCROW nR;
|
|
rCell.getFormula()->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.getFormula()->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.getType() == CELLTYPE_FORMULA;
|
|
bool bNewFormula = maNewCell.getType() == 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.getFormula()->aPos = aBigRange.aStart.MakeAddress(pTrack->GetDocument());
|
|
if ( bNewFormula )
|
|
maNewCell.getFormula()->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.getFormula()->UpdateReference(aRefCxt);
|
|
if ( bNewFormula )
|
|
maNewCell.getFormula()->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.getFormula()->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.getFormula()->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();
|
|
ScModule::get()->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();
|
|
ScModule::get()->GetUserOptions().AddListener(this);
|
|
ppContentSlots.reset( new ScChangeActionContent* [ mnContentSlots ] );
|
|
memset( ppContentSlots.get(), 0, mnContentSlots * sizeof( ScChangeActionContent* ) );
|
|
}
|
|
|
|
ScChangeTrack::~ScChangeTrack()
|
|
{
|
|
ScModule::get()->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;
|
|
|
|
CreateAuthorName();
|
|
}
|
|
|
|
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;
|
|
|
|
size_t nOldCount = maUserCollection.size();
|
|
|
|
CreateAuthorName();
|
|
|
|
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)
|
|
|
|
ScDocShell* pDocSh = rDoc.GetDocumentShell();
|
|
if (pDocSh)
|
|
pDocSh->Broadcast( ScPaintHint( ScRange(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB), PaintPartFlags::Grid ) );
|
|
}
|
|
}
|
|
|
|
void ScChangeTrack::CreateAuthorName()
|
|
{
|
|
const SvtUserOptions& rUserOptions = ScModule::get()->GetUserOptions();
|
|
OUString aFirstName(rUserOptions.GetFirstName());
|
|
OUString aLastName(rUserOptions.GetLastName());
|
|
if (aFirstName.isEmpty() && aLastName.isEmpty())
|
|
SetUser(ScResId(STR_CHG_UNKNOWN_AUTHOR));
|
|
else if(!aFirstName.isEmpty() && aLastName.isEmpty())
|
|
SetUser(aFirstName);
|
|
else if(aFirstName.isEmpty() && !aLastName.isEmpty())
|
|
SetUser(aLastName);
|
|
else
|
|
SetUser(aFirstName + " " + aLastName);
|
|
}
|
|
|
|
|
|
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.getType() == CELLTYPE_FORMULA && rOldCell.getFormula()->GetMatrixFlag() == ScMatrixMode::Formula)
|
|
rOldCell.getFormula()->GetMatColsRows(nC1, nR1);
|
|
|
|
if (rNewCell.getType() == CELLTYPE_FORMULA && rNewCell.getFormula()->GetMatrixFlag() == ScMatrixMode::Formula)
|
|
rNewCell.getFormula()->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(ScRange(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.getFormula()->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(ScModule::get()->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.contains( p->GetActionNumber() ))
|
|
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.getFormula()->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(),
|
|
std::move(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: */
|