| /************************************************************** |
| * |
| * 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 ); |
| } |
| } |
| |
| |
| |