/* -*- 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 <sal/log.hxx>
#include <osl/diagnose.h>
#include <document.hxx>
#include <brdcst.hxx>
#include <bcaslot.hxx>
#include <formulacell.hxx>
#include <table.hxx>
#include <progress.hxx>
#include <scmod.hxx>
#include <inputopt.hxx>
#include <sheetevents.hxx>
#include <tokenarray.hxx>
#include <listenercontext.hxx>
void ScDocument::StartListeningArea(
const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
{
if (!pBASM)
return;
// Ensure sane ranges for the slots, specifically don't attempt to listen
// to more sheets than the document has. The slot machine handles it but
// with memory waste. Binary import filters can set out-of-bounds ranges
// in formula expressions' references, so all middle layers would have to
// check it, rather have this central point here.
ScRange aLimitedRange( ScAddress::UNINITIALIZED );
bool bEntirelyOut;
if (!LimitRangeToAvailableSheets( rRange, aLimitedRange, bEntirelyOut))
{
pBASM->StartListeningArea(rRange, bGroupListening, pListener);
return;
}
// If both sheets are out-of-bounds in the same direction then just bail out.
if (bEntirelyOut)
return;
pBASM->StartListeningArea( aLimitedRange, bGroupListening, pListener);
}
void ScDocument::EndListeningArea( const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
{
if (!pBASM)
return;
// End listening has to limit the range exactly the same as in
// StartListeningArea(), otherwise the range would not be found.
ScRange aLimitedRange( ScAddress::UNINITIALIZED );
bool bEntirelyOut;
if (!LimitRangeToAvailableSheets( rRange, aLimitedRange, bEntirelyOut))
{
pBASM->EndListeningArea(rRange, bGroupListening, pListener);
return;
}
// If both sheets are out-of-bounds in the same direction then just bail out.
if (bEntirelyOut)
return;
pBASM->EndListeningArea( aLimitedRange, bGroupListening, pListener);
}
bool ScDocument::LimitRangeToAvailableSheets( const ScRange& rRange, ScRange& o_rRange,
bool& o_bEntirelyOutOfBounds ) const
{
const SCTAB nMaxTab = GetTableCount() - 1;
if (ValidTab( rRange.aStart.Tab(), nMaxTab) && ValidTab( rRange.aEnd.Tab(), nMaxTab))
return false;
// Originally BCA_LISTEN_ALWAYS uses an implicit tab 0 and should had been
// valid already, but in case that would change...
if (rRange == BCA_LISTEN_ALWAYS)
return false;
SCTAB nTab1 = rRange.aStart.Tab();
SCTAB nTab2 = rRange.aEnd.Tab();
SAL_WARN("sc.core","ScDocument::LimitRangeToAvailableSheets - bad sheet range: " << nTab1 << ".." << nTab2 <<
", sheets: 0.." << nMaxTab);
// Both sheets are out-of-bounds in the same direction.
if ((nTab1 < 0 && nTab2 < 0) || (nMaxTab < nTab1 && nMaxTab < nTab2))
{
o_bEntirelyOutOfBounds = true;
return true;
}
// Limit the sheet range to bounds.
o_bEntirelyOutOfBounds = false;
nTab1 = std::clamp<SCTAB>( nTab1, 0, nMaxTab);
nTab2 = std::clamp<SCTAB>( nTab2, 0, nMaxTab);
o_rRange = rRange;
o_rRange.aStart.SetTab(nTab1);
o_rRange.aEnd.SetTab(nTab2);
return true;
}
void ScDocument::Broadcast( const ScHint& rHint )
{
if ( !pBASM )
return ; // Clipboard or Undo
if ( eHardRecalcState == HardRecalcState::OFF )
{
ScBulkBroadcast aBulkBroadcast( pBASM.get(), rHint.GetId()); // scoped bulk broadcast
bool bIsBroadcasted = BroadcastHintInternal(rHint);
if ( pBASM->AreaBroadcast( rHint ) || bIsBroadcasted )
TrackFormulas( rHint.GetId() );
}
if ( rHint.GetStartAddress() != BCA_BRDCST_ALWAYS )
{
SCTAB nTab = rHint.GetStartAddress().Tab();
if (nTab < GetTableCount() && maTabs[nTab])
maTabs[nTab]->SetStreamValid(false);
}
}
bool ScDocument::BroadcastHintInternal( const ScHint& rHint )
{
bool bIsBroadcasted = false;
const ScAddress& address(rHint.GetStartAddress());
SvtBroadcaster* pLastBC = nullptr;
// Process all broadcasters for the given row range.
for( SCROW nRow = 0; nRow < rHint.GetRowCount(); ++nRow )
{
ScAddress a(address);
a.SetRow(address.Row() + nRow);
SvtBroadcaster* pBC = GetBroadcaster(a);
if ( pBC && pBC != pLastBC )
{
pBC->Broadcast( rHint );
bIsBroadcasted = true;
pLastBC = pBC;
}
}
return bIsBroadcasted;
}
void ScDocument::BroadcastCells( const ScRange& rRange, SfxHintId nHint, bool bBroadcastSingleBroadcasters )
{
PrepareFormulaCalc();
if (!pBASM)
return; // Clipboard or Undo
SCTAB nTab1 = rRange.aStart.Tab();
SCTAB nTab2 = rRange.aEnd.Tab();
SCROW nRow1 = rRange.aStart.Row();
SCROW nRow2 = rRange.aEnd.Row();
SCCOL nCol1 = rRange.aStart.Col();
SCCOL nCol2 = rRange.aEnd.Col();
if (eHardRecalcState == HardRecalcState::OFF)
{
ScBulkBroadcast aBulkBroadcast( pBASM.get(), nHint); // scoped bulk broadcast
bool bIsBroadcasted = false;
if (bBroadcastSingleBroadcasters)
{
for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab)
{
ScTable* pTab = FetchTable(nTab);
if (!pTab)
continue;
bIsBroadcasted |= pTab->BroadcastBroadcasters( nCol1, nRow1, nCol2, nRow2, nHint);
}
}
if (pBASM->AreaBroadcast(rRange, nHint) || bIsBroadcasted)
TrackFormulas(nHint);
}
for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab)
{
ScTable* pTab = FetchTable(nTab);
if (pTab)
pTab->SetStreamValid(false);
}
BroadcastUno(SfxHint(SfxHintId::ScDataChanged));
}
void ScDocument::AreaBroadcast( const ScHint& rHint )
{
if ( !pBASM )
return ; // Clipboard or Undo
if (eHardRecalcState == HardRecalcState::OFF)
{
ScBulkBroadcast aBulkBroadcast( pBASM.get(), rHint.GetId()); // scoped bulk broadcast
if ( pBASM->AreaBroadcast( rHint ) )
TrackFormulas( rHint.GetId() );
}
}
void ScDocument::DelBroadcastAreasInRange( const ScRange& rRange )
{
if ( pBASM )
pBASM->DelBroadcastAreasInRange( rRange );
}
void ScDocument::StartListeningCell( const ScAddress& rAddress,
SvtListener* pListener )
{
OSL_ENSURE(pListener, "StartListeningCell: pListener Null");
SCTAB nTab = rAddress.Tab();
if (ScTable* pTable = FetchTable(nTab))
pTable->StartListening(rAddress, pListener);
}
void ScDocument::EndListeningCell( const ScAddress& rAddress,
SvtListener* pListener )
{
OSL_ENSURE(pListener, "EndListeningCell: pListener Null");
SCTAB nTab = rAddress.Tab();
if (ScTable* pTable = FetchTable(nTab))
pTable->EndListening( rAddress, pListener );
}
void ScDocument::StartListeningCell(
sc::StartListeningContext& rCxt, const ScAddress& rPos, SvtListener& rListener )
{
if (ScTable* pTable = FetchTable(rPos.Tab()))
pTable->StartListening(rCxt, rPos, rListener);
}
void ScDocument::EndListeningCell(
sc::EndListeningContext& rCxt, const ScAddress& rPos, SvtListener& rListener )
{
if (ScTable* pTable = FetchTable(rPos.Tab()))
pTable->EndListening(rCxt, rPos, rListener);
}
void ScDocument::EndListeningFormulaCells( std::vector<ScFormulaCell*>& rCells )
{
if (rCells.empty())
return;
sc::EndListeningContext aCxt(*this);
for (auto& pCell : rCells)
pCell->EndListeningTo(aCxt);
aCxt.purgeEmptyBroadcasters();
}
void ScDocument::PutInFormulaTree( ScFormulaCell* pCell )
{
OSL_ENSURE( pCell, "PutInFormulaTree: pCell Null" );
RemoveFromFormulaTree( pCell );
// append
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
if ( pEOFormulaTree )
pEOFormulaTree->SetNext( pCell );
else
pFormulaTree = pCell; // No end, no beginning...
pCell->SetPrevious( pEOFormulaTree );
pCell->SetNext( nullptr );
pEOFormulaTree = pCell;
nFormulaCodeInTree += pCell->GetCode()->GetCodeLen();
}
void ScDocument::RemoveFromFormulaTree( ScFormulaCell* pCell )
{
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
assert(pCell && "RemoveFromFormulaTree: pCell Null");
ScFormulaCell* pPrev = pCell->GetPrevious();
assert(pPrev != pCell); // pointing to itself?!?
// if the cell is first or somewhere in chain
if ( pPrev || pFormulaTree == pCell )
{
ScFormulaCell* pNext = pCell->GetNext();
assert(pNext != pCell); // pointing to itself?!?
if ( pPrev )
{
assert(pFormulaTree != pCell); // if this cell is also head something's wrong
pPrev->SetNext( pNext ); // predecessor exists, set successor
}
else
{
pFormulaTree = pNext; // this cell was first cell
}
if ( pNext )
{
assert(pEOFormulaTree != pCell); // if this cell is also tail something's wrong
pNext->SetPrevious( pPrev ); // successor exists, set predecessor
}
else
{
pEOFormulaTree = pPrev; // this cell was last cell
}
pCell->SetPrevious( nullptr );
pCell->SetNext( nullptr );
sal_uInt16 nRPN = pCell->GetCode()->GetCodeLen();
if ( nFormulaCodeInTree >= nRPN )
nFormulaCodeInTree -= nRPN;
else
{
OSL_FAIL( "RemoveFromFormulaTree: nFormulaCodeInTree < nRPN" );
nFormulaCodeInTree = 0;
}
}
else if ( !pFormulaTree && nFormulaCodeInTree )
{
OSL_FAIL( "!pFormulaTree && nFormulaCodeInTree != 0" );
nFormulaCodeInTree = 0;
}
}
void ScDocument::CalcFormulaTree( bool bOnlyForced, bool bProgressBar, bool bSetAllDirty )
{
OSL_ENSURE( !IsCalculatingFormulaTree(), "CalcFormulaTree recursion" );
// never ever recurse into this, might end up lost in infinity
if ( IsCalculatingFormulaTree() )
return ;
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
mpFormulaGroupCxt.reset();
bCalculatingFormulaTree = true;
SetForcedFormulaPending( false );
bool bOldIdleEnabled = IsIdleEnabled();
EnableIdle(false);
bool bOldAutoCalc = GetAutoCalc();
//ATTENTION: _not_ SetAutoCalc( true ) because this might call CalcFormulaTree( true )
//ATTENTION: if it was disabled before and bHasForcedFormulas is set
bAutoCalc = true;
if (eHardRecalcState == HardRecalcState::ETERNAL)
CalcAll();
else
{
::std::vector<ScFormulaCell*> vAlwaysDirty;
ScFormulaCell* pCell = pFormulaTree;
while ( pCell )
{
if ( pCell->GetDirty() )
; // nothing to do
else if ( pCell->GetCode()->IsRecalcModeAlways() )
{
// pCell and dependents are to be set dirty again, collect
// them first and broadcast afterwards to not break the
// FormulaTree chain here.
vAlwaysDirty.push_back( pCell);
}
else if ( bSetAllDirty )
{
// Force calculating all in tree, without broadcasting.
pCell->SetDirtyVar();
}
pCell = pCell->GetNext();
}
for (const auto& rpCell : vAlwaysDirty)
{
pCell = rpCell;
if (!pCell->GetDirty())
pCell->SetDirty();
}
bool bProgress = !bOnlyForced && nFormulaCodeInTree && bProgressBar;
if ( bProgress )
ScProgress::CreateInterpretProgress( this );
pCell = pFormulaTree;
ScFormulaCell* pLastNoGood = nullptr;
while ( pCell )
{
// Interpret resets bDirty and calls Remove, also the referenced!
// the Cell remains when ScRecalcMode::ALWAYS.
if ( bOnlyForced )
{
if ( pCell->GetCode()->IsRecalcModeForced() )
pCell->Interpret();
}
else
{
pCell->Interpret();
}
if ( pCell->GetPrevious() || pCell == pFormulaTree )
{ // (IsInFormulaTree(pCell)) no Remove was called => next
pLastNoGood = pCell;
pCell = pCell->GetNext();
}
else
{
if ( pFormulaTree )
{
if ( pFormulaTree->GetDirty() && !bOnlyForced )
{
pCell = pFormulaTree;
pLastNoGood = nullptr;
}
else
{
// IsInFormulaTree(pLastNoGood)
if ( pLastNoGood && (pLastNoGood->GetPrevious() ||
pLastNoGood == pFormulaTree) )
pCell = pLastNoGood->GetNext();
else
{
pCell = pFormulaTree;
while ( pCell && !pCell->GetDirty() )
pCell = pCell->GetNext();
if ( pCell )
pLastNoGood = pCell->GetPrevious();
}
}
}
else
pCell = nullptr;
}
}
if ( bProgress )
ScProgress::DeleteInterpretProgress();
}
bAutoCalc = bOldAutoCalc;
EnableIdle(bOldIdleEnabled);
bCalculatingFormulaTree = false;
mpFormulaGroupCxt.reset();
}
void ScDocument::ClearFormulaTree()
{
ScFormulaCell* pCell;
ScFormulaCell* pTree = pFormulaTree;
while ( pTree )
{
pCell = pTree;
pTree = pCell->GetNext();
if ( !pCell->GetCode()->IsRecalcModeAlways() )
RemoveFromFormulaTree( pCell );
}
}
void ScDocument::AppendToFormulaTrack( ScFormulaCell* pCell )
{
OSL_ENSURE( pCell, "AppendToFormulaTrack: pCell Null" );
// The cell can not be in both lists at the same time
RemoveFromFormulaTrack( pCell );
RemoveFromFormulaTree( pCell );
if ( pEOFormulaTrack )
pEOFormulaTrack->SetNextTrack( pCell );
else
pFormulaTrack = pCell; // No end, no beginning...
pCell->SetPreviousTrack( pEOFormulaTrack );
pCell->SetNextTrack( nullptr );
pEOFormulaTrack = pCell;
++nFormulaTrackCount;
}
void ScDocument::RemoveFromFormulaTrack( ScFormulaCell* pCell )
{
assert(pCell && "RemoveFromFormulaTrack: pCell Null");
ScFormulaCell* pPrev = pCell->GetPreviousTrack();
assert(pPrev != pCell); // pointing to itself?!?
// if the cell is first or somewhere in chain
if ( !(pPrev || pFormulaTrack == pCell) )
return;
ScFormulaCell* pNext = pCell->GetNextTrack();
assert(pNext != pCell); // pointing to itself?!?
if ( pPrev )
{
assert(pFormulaTrack != pCell); // if this cell is also head something's wrong
pPrev->SetNextTrack( pNext ); // predecessor exists, set successor
}
else
{
pFormulaTrack = pNext; // this cell was first cell
}
if ( pNext )
{
assert(pEOFormulaTrack != pCell); // if this cell is also tail something's wrong
pNext->SetPreviousTrack( pPrev ); // successor exists, set predecessor
}
else
{
pEOFormulaTrack = pPrev; // this cell was last cell
}
pCell->SetPreviousTrack( nullptr );
pCell->SetNextTrack( nullptr );
--nFormulaTrackCount;
}
void ScDocument::FinalTrackFormulas( SfxHintId nHintId )
{
mbTrackFormulasPending = false;
mbFinalTrackFormulas = true;
{
ScBulkBroadcast aBulk( GetBASM(), nHintId);
// Collect all pending formula cells in bulk.
TrackFormulas( nHintId );
}
// A final round not in bulk to track all remaining formula cells and their
// dependents that were collected during ScBulkBroadcast dtor.
TrackFormulas( nHintId );
mbFinalTrackFormulas = false;
}
/*
The first is broadcasted,
the ones that are created through this are appended to the Track by Notify.
The next is broadcasted again, and so on.
View initiates Interpret.
*/
void ScDocument::TrackFormulas( SfxHintId nHintId )
{
if (!pBASM)
return;
if (pBASM->IsInBulkBroadcast() && !IsFinalTrackFormulas() &&
(nHintId == SfxHintId::ScDataChanged || nHintId == SfxHintId::ScHiddenRowsChanged))
{
SetTrackFormulasPending();
return;
}
if ( pFormulaTrack )
{
// outside the loop, check if any sheet has a "calculate" event script
bool bCalcEvent = HasAnySheetEventScript( ScSheetEventId::CALCULATE, true );
for( ScFormulaCell* pTrack = pFormulaTrack; pTrack != nullptr; pTrack = pTrack->GetNextTrack())
{
SCROW rowCount = 1;
ScAddress address = pTrack->aPos;
// Compress to include all adjacent cells in the same column.
for(ScFormulaCell* pNext = pTrack->GetNextTrack(); pNext != nullptr; pNext = pNext->GetNextTrack())
{
if(pNext->aPos != ScAddress(address.Col(), address.Row() + rowCount, address.Tab()))
break;
++rowCount;
pTrack = pNext;
}
ScHint aHint( nHintId, address, rowCount );
BroadcastHintInternal( aHint );
pBASM->AreaBroadcast( aHint );
// for "calculate" event, keep track of which sheets are affected by tracked formulas
if ( bCalcEvent )
SetCalcNotification( address.Tab() );
}
bool bHaveForced = false;
for( ScFormulaCell* pTrack = pFormulaTrack; pTrack != nullptr;)
{
ScFormulaCell* pNext = pTrack->GetNextTrack();
RemoveFromFormulaTrack( pTrack );
PutInFormulaTree( pTrack );
if ( pTrack->GetCode()->IsRecalcModeForced() )
bHaveForced = true;
pTrack = pNext;
}
if ( bHaveForced )
{
SetForcedFormulas( true );
if ( bAutoCalc && !IsAutoCalcShellDisabled() && !IsInInterpreter()
&& !IsCalculatingFormulaTree() )
CalcFormulaTree( true );
else
SetForcedFormulaPending( true );
}
}
OSL_ENSURE( nFormulaTrackCount==0, "TrackFormulas: nFormulaTrackCount!=0" );
}
void ScDocument::StartAllListeners()
{
sc::StartListeningContext aCxt(*this);
for ( auto const & i: maTabs )
if ( i )
i->StartListeners(aCxt, true);
}
void ScDocument::UpdateBroadcastAreas( UpdateRefMode eUpdateRefMode,
const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz
)
{
bool bExpandRefsOld = IsExpandRefs();
if ( eUpdateRefMode == URM_INSDEL && (nDx > 0 || nDy > 0 || nDz > 0) )
SetExpandRefs( SC_MOD()->GetInputOptions().GetExpandRefs() );
if ( pBASM )
pBASM->UpdateBroadcastAreas( eUpdateRefMode, rRange, nDx, nDy, nDz );
SetExpandRefs( bExpandRefsOld );
}
void ScDocument::SetAutoCalc( bool bNewAutoCalc )
{
bool bOld = bAutoCalc;
bAutoCalc = bNewAutoCalc;
if ( !bOld && bNewAutoCalc && bHasForcedFormulas )
{
if ( IsAutoCalcShellDisabled() )
SetForcedFormulaPending( true );
else if ( !IsInInterpreter() )
CalcFormulaTree( true );
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V560 A part of conditional expression is always true: (((sal_Bool) 1)).