summaryrefslogtreecommitdiffstats
path: root/sc/source/core/data/documen7.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/data/documen7.cxx')
-rw-r--r--sc/source/core/data/documen7.cxx621
1 files changed, 621 insertions, 0 deletions
diff --git a/sc/source/core/data/documen7.cxx b/sc/source/core/data/documen7.cxx
new file mode 100644
index 000000000..61f6b68f0
--- /dev/null
+++ b/sc/source/core/data/documen7.cxx
@@ -0,0 +1,621 @@
+/* -*- 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 < static_cast<SCTAB>(maTabs.size()) && 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 (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->StartListening( rAddress, pListener );
+}
+
+void ScDocument::EndListeningCell( const ScAddress& rAddress,
+ SvtListener* pListener )
+{
+ OSL_ENSURE(pListener, "EndListeningCell: pListener Null");
+ SCTAB nTab = rAddress.Tab();
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->EndListening( rAddress, pListener );
+}
+
+void ScDocument::StartListeningCell(
+ sc::StartListeningContext& rCxt, const ScAddress& rPos, SvtListener& rListener )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->StartListening(rCxt, rPos, rListener);
+}
+
+void ScDocument::EndListeningCell(
+ sc::EndListeningContext& rCxt, const ScAddress& rPos, SvtListener& rListener )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->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);
+ OSL_ENSURE( 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 )
+{
+ OSL_ENSURE( 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: */