blob: 0d24a17b0a54ecff2993295d92c7a4412a558edf [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sc.hxx"
// INCLUDE ---------------------------------------------------------------
#include <vcl/svapp.hxx>
#if defined( WNT ) && defined( erBEEP )
#include <svwin.h>
#define erBEEPER() Beep( 666, 66 )
#else
#define erBEEPER()
#endif
#include "document.hxx"
#include "brdcst.hxx"
#include "bcaslot.hxx"
#include "cell.hxx"
#include "formula/errorcodes.hxx" // errCircularReference
#include "scerrors.hxx"
#include "docoptio.hxx"
#include "refupdat.hxx"
#include "table.hxx"
#include "progress.hxx"
#include "scmod.hxx" // SC_MOD
#include "inputopt.hxx" // GetExpandRefs
#include "conditio.hxx"
#include "sheetevents.hxx"
#include <tools/shl.hxx>
#include "globstr.hrc"
extern const ScFormulaCell* pLastFormulaTreeTop; // cellform.cxx Err527 WorkAround
// STATIC DATA -----------------------------------------------------------
#ifdef erDEBUG
sal_uLong erCountBCAInserts = 0;
sal_uLong erCountBCAFinds = 0;
#endif
// -----------------------------------------------------------------------
void ScDocument::StartListeningArea( const ScRange& rRange,
SvtListener* pListener
)
{
if ( pBASM )
pBASM->StartListeningArea( rRange, pListener );
}
void ScDocument::EndListeningArea( const ScRange& rRange,
SvtListener* pListener
)
{
if ( pBASM )
pBASM->EndListeningArea( rRange, pListener );
}
void ScDocument::Broadcast( sal_uLong nHint, const ScAddress& rAddr,
ScBaseCell* pCell
)
{
if ( !pBASM )
return ; // Clipboard or Undo
ScHint aHint( nHint, rAddr, pCell );
Broadcast( aHint );
}
void ScDocument::Broadcast( const ScHint& rHint )
{
if ( !pBASM )
return ; // Clipboard or Undo
if ( !nHardRecalcState )
{
ScBulkBroadcast aBulkBroadcast( pBASM); // scoped bulk broadcast
sal_Bool bIsBroadcasted = sal_False;
ScBaseCell* pCell = rHint.GetCell();
if ( pCell )
{
SvtBroadcaster* pBC = pCell->GetBroadcaster();
if ( pBC )
{
pBC->Broadcast( rHint );
bIsBroadcasted = sal_True;
}
}
if ( pBASM->AreaBroadcast( rHint ) || bIsBroadcasted )
TrackFormulas( rHint.GetId() );
}
// Repaint fuer bedingte Formate mit relativen Referenzen:
if ( pCondFormList && rHint.GetAddress() != BCA_BRDCST_ALWAYS )
pCondFormList->SourceChanged( rHint.GetAddress() );
if ( rHint.GetAddress() != BCA_BRDCST_ALWAYS )
{
SCTAB nTab = rHint.GetAddress().Tab();
if (pTab[nTab] && pTab[nTab]->IsStreamValid())
pTab[nTab]->SetStreamValid(sal_False);
}
}
void ScDocument::AreaBroadcast( const ScHint& rHint )
{
if ( !pBASM )
return ; // Clipboard or Undo
if ( !nHardRecalcState )
{
ScBulkBroadcast aBulkBroadcast( pBASM); // scoped bulk broadcast
if ( pBASM->AreaBroadcast( rHint ) )
TrackFormulas( rHint.GetId() );
}
// Repaint fuer bedingte Formate mit relativen Referenzen:
if ( pCondFormList && rHint.GetAddress() != BCA_BRDCST_ALWAYS )
pCondFormList->SourceChanged( rHint.GetAddress() );
}
void ScDocument::AreaBroadcastInRange( const ScRange& rRange, const ScHint& rHint )
{
if ( !pBASM )
return ; // Clipboard or Undo
if ( !nHardRecalcState )
{
ScBulkBroadcast aBulkBroadcast( pBASM); // scoped bulk broadcast
if ( pBASM->AreaBroadcastInRange( rRange, rHint ) )
TrackFormulas( rHint.GetId() );
}
// Repaint for conditional formats containing relative references.
//! This is _THE_ bottle neck!
if ( pCondFormList )
{
SCCOL nCol;
SCROW nRow;
SCTAB nTab;
SCCOL nCol1;
SCROW nRow1;
SCTAB nTab1;
SCCOL nCol2;
SCROW nRow2;
SCTAB nTab2;
rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
ScAddress aAddress( rRange.aStart );
for ( nTab = nTab1; nTab <= nTab2; ++nTab )
{
aAddress.SetTab( nTab );
for ( nCol = nCol1; nCol <= nCol2; ++nCol )
{
aAddress.SetCol( nCol );
for ( nRow = nRow1; nRow <= nRow2; ++nRow )
{
aAddress.SetRow( nRow );
pCondFormList->SourceChanged( aAddress );
}
}
}
}
}
void ScDocument::DelBroadcastAreasInRange( const ScRange& rRange )
{
if ( pBASM )
pBASM->DelBroadcastAreasInRange( rRange );
}
void ScDocument::StartListeningCell( const ScAddress& rAddress,
SvtListener* pListener )
{
DBG_ASSERT(pListener, "StartListeningCell: pListener Null");
SCTAB nTab = rAddress.Tab();
if (pTab[nTab])
pTab[nTab]->StartListening( rAddress, pListener );
}
void ScDocument::EndListeningCell( const ScAddress& rAddress,
SvtListener* pListener )
{
DBG_ASSERT(pListener, "EndListeningCell: pListener Null");
SCTAB nTab = rAddress.Tab();
if (pTab[nTab])
pTab[nTab]->EndListening( rAddress, pListener );
}
void ScDocument::PutInFormulaTree( ScFormulaCell* pCell )
{
DBG_ASSERT( pCell, "PutInFormulaTree: pCell Null" );
RemoveFromFormulaTree( pCell );
// anhaengen
if ( pEOFormulaTree )
pEOFormulaTree->SetNext( pCell );
else
pFormulaTree = pCell; // kein Ende, kein Anfang..
pCell->SetPrevious( pEOFormulaTree );
pCell->SetNext( 0 );
pEOFormulaTree = pCell;
nFormulaCodeInTree += pCell->GetCode()->GetCodeLen();
}
void ScDocument::RemoveFromFormulaTree( ScFormulaCell* pCell )
{
DBG_ASSERT( pCell, "RemoveFromFormulaTree: pCell Null" );
ScFormulaCell* pPrev = pCell->GetPrevious();
// wenn die Zelle die erste oder sonstwo ist
if ( pPrev || pFormulaTree == pCell )
{
ScFormulaCell* pNext = pCell->GetNext();
if ( pPrev )
pPrev->SetNext( pNext ); // gibt Vorlaeufer
else
pFormulaTree = pNext; // ist erste Zelle
if ( pNext )
pNext->SetPrevious( pPrev ); // gibt Nachfolger
else
pEOFormulaTree = pPrev; // ist letzte Zelle
pCell->SetPrevious( 0 );
pCell->SetNext( 0 );
sal_uInt16 nRPN = pCell->GetCode()->GetCodeLen();
if ( nFormulaCodeInTree >= nRPN )
nFormulaCodeInTree -= nRPN;
else
{
DBG_ERRORFILE( "RemoveFromFormulaTree: nFormulaCodeInTree < nRPN" );
nFormulaCodeInTree = 0;
}
}
else if ( !pFormulaTree && nFormulaCodeInTree )
{
DBG_ERRORFILE( "!pFormulaTree && nFormulaCodeInTree != 0" );
nFormulaCodeInTree = 0;
}
}
sal_Bool ScDocument::IsInFormulaTree( ScFormulaCell* pCell ) const
{
return pCell->GetPrevious() || pFormulaTree == pCell;
}
void ScDocument::CalcFormulaTree( sal_Bool bOnlyForced, sal_Bool bNoProgress )
{
DBG_ASSERT( !IsCalculatingFormulaTree(), "CalcFormulaTree recursion" );
// never ever recurse into this, might end up lost in infinity
if ( IsCalculatingFormulaTree() )
return ;
bCalculatingFormulaTree = sal_True;
SetForcedFormulaPending( sal_False );
sal_Bool bOldIdleDisabled = IsIdleDisabled();
DisableIdle( sal_True );
sal_Bool bOldAutoCalc = GetAutoCalc();
//! _nicht_ SetAutoCalc( sal_True ) weil das evtl. CalcFormulaTree( sal_True )
//! aufruft, wenn vorher disabled war und bHasForcedFormulas gesetzt ist
bAutoCalc = sal_True;
if ( nHardRecalcState )
CalcAll();
else
{
ScFormulaCell* pCell = pFormulaTree;
while ( pCell )
{
if ( pCell->GetDirty() )
pCell = pCell->GetNext(); // alles klar
else
{
if ( pCell->GetCode()->IsRecalcModeAlways() )
{
// pCell wird im SetDirty neu angehaengt!
ScFormulaCell* pNext = pCell->GetNext();
pCell->SetDirty();
// falls pNext==0 und neue abhaengige hinten angehaengt
// wurden, so macht das nichts, da die alle bDirty sind
pCell = pNext;
}
else
{ // andere simpel berechnen
pCell->SetDirtyVar();
pCell = pCell->GetNext();
}
}
}
sal_Bool bProgress = !bOnlyForced && nFormulaCodeInTree && !bNoProgress;
if ( bProgress )
ScProgress::CreateInterpretProgress( this, sal_True );
pCell = pFormulaTree;
ScFormulaCell* pLastNoGood = 0;
while ( pCell )
{
// Interpret setzt bDirty zurueck und callt Remove, auch der referierten!
// bei RECALCMODE_ALWAYS bleibt die Zelle
if ( bOnlyForced )
{
if ( pCell->GetCode()->IsRecalcModeForced() )
pCell->Interpret();
}
else
{
pCell->Interpret();
}
if ( pCell->GetPrevious() || pCell == pFormulaTree )
{ // (IsInFormulaTree(pCell)) kein Remove gewesen => next
pLastNoGood = pCell;
pCell = pCell->GetNext();
}
else
{
if ( pFormulaTree )
{
if ( pFormulaTree->GetDirty() && !bOnlyForced )
{
pCell = pFormulaTree;
pLastNoGood = 0;
}
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 = 0;
}
if ( ScProgress::IsUserBreak() )
pCell = 0;
}
if ( bProgress )
ScProgress::DeleteInterpretProgress();
}
bAutoCalc = bOldAutoCalc;
DisableIdle( bOldIdleDisabled );
bCalculatingFormulaTree = sal_False;
}
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 )
{
DBG_ASSERT( pCell, "AppendToFormulaTrack: pCell Null" );
// Zelle kann nicht in beiden Listen gleichzeitig sein
RemoveFromFormulaTrack( pCell );
RemoveFromFormulaTree( pCell );
if ( pEOFormulaTrack )
pEOFormulaTrack->SetNextTrack( pCell );
else
pFormulaTrack = pCell; // kein Ende, kein Anfang..
pCell->SetPreviousTrack( pEOFormulaTrack );
pCell->SetNextTrack( 0 );
pEOFormulaTrack = pCell;
++nFormulaTrackCount;
}
void ScDocument::RemoveFromFormulaTrack( ScFormulaCell* pCell )
{
DBG_ASSERT( pCell, "RemoveFromFormulaTrack: pCell Null" );
ScFormulaCell* pPrev = pCell->GetPreviousTrack();
// wenn die Zelle die erste oder sonstwo ist
if ( pPrev || pFormulaTrack == pCell )
{
ScFormulaCell* pNext = pCell->GetNextTrack();
if ( pPrev )
pPrev->SetNextTrack( pNext ); // gibt Vorlaeufer
else
pFormulaTrack = pNext; // ist erste Zelle
if ( pNext )
pNext->SetPreviousTrack( pPrev ); // gibt Nachfolger
else
pEOFormulaTrack = pPrev; // ist letzte Zelle
pCell->SetPreviousTrack( 0 );
pCell->SetNextTrack( 0 );
--nFormulaTrackCount;
}
}
sal_Bool ScDocument::IsInFormulaTrack( ScFormulaCell* pCell ) const
{
return pCell->GetPreviousTrack() || pFormulaTrack == pCell;
}
/*
Der erste wird gebroadcastet,
die dadurch entstehenden werden durch das Notify an den Track gehaengt.
Der nachfolgende broadcastet wieder usw.
View stoesst Interpret an.
*/
void ScDocument::TrackFormulas( sal_uLong nHintId )
{
if ( pFormulaTrack )
{
erBEEPER();
// outside the loop, check if any sheet has a "calculate" event script
bool bCalcEvent = HasAnySheetEventScript( SC_SHEETEVENT_CALCULATE, true );
SvtBroadcaster* pBC;
ScFormulaCell* pTrack;
ScFormulaCell* pNext;
pTrack = pFormulaTrack;
do
{
ScHint aHint( nHintId, pTrack->aPos, pTrack );
if ( ( pBC = pTrack->GetBroadcaster() ) != NULL )
pBC->Broadcast( aHint );
pBASM->AreaBroadcast( aHint );
// Repaint fuer bedingte Formate mit relativen Referenzen:
if ( pCondFormList )
pCondFormList->SourceChanged( pTrack->aPos );
// for "calculate" event, keep track of which sheets are affected by tracked formulas
if ( bCalcEvent )
SetCalcNotification( pTrack->aPos.Tab() );
pTrack = pTrack->GetNextTrack();
} while ( pTrack );
pTrack = pFormulaTrack;
sal_Bool bHaveForced = sal_False;
do
{
pNext = pTrack->GetNextTrack();
RemoveFromFormulaTrack( pTrack );
PutInFormulaTree( pTrack );
if ( pTrack->GetCode()->IsRecalcModeForced() )
bHaveForced = sal_True;
pTrack = pNext;
} while ( pTrack );
if ( bHaveForced )
{
SetForcedFormulas( sal_True );
if ( bAutoCalc && !IsAutoCalcShellDisabled() && !IsInInterpreter()
&& !IsCalculatingFormulaTree() )
CalcFormulaTree( sal_True );
else
SetForcedFormulaPending( sal_True );
}
}
DBG_ASSERT( nFormulaTrackCount==0, "TrackFormulas: nFormulaTrackCount!=0" );
}
void ScDocument::StartAllListeners()
{
for ( SCTAB i = 0; i <= MAXTAB; ++i )
if ( pTab[i] )
pTab[i]->StartAllListeners();
}
void ScDocument::UpdateBroadcastAreas( UpdateRefMode eUpdateRefMode,
const ScRange& rRange, SCsCOL nDx, SCsROW nDy, SCsTAB nDz
)
{
sal_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( sal_Bool bNewAutoCalc )
{
sal_Bool bOld = bAutoCalc;
bAutoCalc = bNewAutoCalc;
if ( !bOld && bNewAutoCalc && bHasForcedFormulas )
{
if ( IsAutoCalcShellDisabled() )
SetForcedFormulaPending( sal_True );
else if ( !IsInInterpreter() )
CalcFormulaTree( sal_True );
}
}