/* -*- 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 .
*/
using ::editeng::SvxBorderLine; usingnamespace ::com::sun::star;
namespace WritingMode2 = ::com::sun::star::text::WritingMode2; using ::com::sun::star::uno::Sequence; using ::com::sun::star::sheet::TablePageBreakData; using ::std::set;
for (SCTAB i = 0; i < GetTableCount(); i++) if (maTabs[i])
{ if (aUpperName == maTabs[i]->GetUpperName())
{
rTab = i; returntrue;
}
}
rTab = 0; returnfalse;
}
std::vector<OUString> ScDocument::GetAllTableNames() const
{
std::vector<OUString> aNames;
aNames.reserve(maTabs.size()); for (constauto& a : maTabs)
{ // Positions need to be preserved for ScCompiler and address convention // context, so still push an empty string for NULL tabs.
OUString aName; if (a)
{ const ScTable& rTab = *a;
aName = rTab.GetName();
}
aNames.push_back(aName);
}
#if 1 // Restrict sheet names to what Excel accepts. /* TODO: We may want to remove this restriction for full ODFF compliance. * Merely loading and calculating ODF documents using these characters in * sheet names is not affected by this, but all sheet name editing and
* copying functionality is, maybe falling back to "Sheet4" or similar. */ for (sal_Int32 i = 0; i < nLen; ++i)
{ const sal_Unicode c = rName[i]; switch (c)
{ case':': case'\\': case'/': case'?': case'*': case'[': case']': // these characters are not allowed to match XL's convention. returnfalse; case'\'': if (i == 0 || i == nLen - 1) // single quote is not allowed at the first or last // character position. returnfalse; break;
}
} #endif
returntrue;
}
bool ScDocument::ValidNewTabName( const OUString& rName ) const
{ bool bValid = ValidTabName(rName); if (!bValid) returnfalse;
OUString aUpperName = ScGlobal::getCharClass().uppercase(rName); for (constauto& a : maTabs)
{ if (!a) continue; const OUString& rOldName = a->GetUpperName();
bValid = rOldName != aUpperName; if (!bValid) break;
} return bValid;
}
void ScDocument::CreateValidTabName(OUString& rName) const
{ if ( !ValidTabName(rName) )
{ // Find new one
// First test if the prefix is valid, if so only avoid doubles bool bPrefix = ValidTabName( aStrTable );
OSL_ENSURE(bPrefix, "Invalid Table Name");
SCTAB nDummy;
for (SCTAB i = GetTableCount() + 1; !bOk ; i++)
{
rName = aStrTable + OUString::number(static_cast<sal_Int32>(i)); if (bPrefix)
bOk = ValidNewTabName( rName ); else
bOk = !GetTable( rName, nDummy );
}
} else
{ // testing the supplied Name
if ( !ValidNewTabName(rName) )
{
SCTAB i = 1;
OUString aName; do
{
i++;
aName = rName + "_" + OUString::number(static_cast<sal_Int32>(i));
} while (!ValidNewTabName(aName) && (i < MAXTAB+1));
rName = aName;
}
}
}
void ScDocument::CreateValidTabNames(std::vector<OUString>& aNames, SCTAB nCount) const
{
aNames.clear();//ensure that the vector is empty
// First test if the prefix is valid, if so only avoid doubles bool bPrefix = ValidTabName( aStrTable );
OSL_ENSURE(bPrefix, "Invalid Table Name");
SCTAB nDummy;
SCTAB i = GetTableCount() + 1;
for (SCTAB j = 0; j < nCount; ++j)
{ bool bOk = false; while(!bOk)
{
rName = aStrTable;
rName.append(static_cast<sal_Int32>(i)); if (bPrefix)
bOk = ValidNewTabName( rName.toString() ); else
bOk = !GetTable( rName.toString(), nDummy );
i++;
}
aNames.push_back(rName.makeStringAndClear());
}
}
void ScDocument::AppendTabOnLoad(const OUString& rName)
{
SCTAB nTabCount = GetTableCount(); if (!ValidTab(nTabCount)) // max table count reached. No more tables. return;
void ScDocument::InvalidateStreamOnSave()
{ for (constauto& a : maTabs)
{ if (a)
a->SetStreamValid(false);
}
}
bool ScDocument::InsertTab(
SCTAB nPos, const OUString& rName, bool bExternalDocument, bool bUndoDeleteTab )
{ // auto-accept any in-process input to prevent move the cell into next sheet in online. if (comphelper::LibreOfficeKit::isActive()) if (ScModule* mod = ScModule::get(); !mod->IsFormulaMode())
mod->InputEnterHandler();
SCTAB nTabCount = GetTableCount(); bool bValid = ValidTab(nTabCount); if ( !bExternalDocument ) // else test rName == "'Doc'!Tab" first
bValid = (bValid && ValidNewTabName(rName)); if (bValid)
{ if (nPos == SC_TAB_APPEND || nPos >= nTabCount)
{
nPos = maTabs.size();
maTabs.emplace_back( new ScTable(*this, nTabCount, rName) ); if ( bExternalDocument )
maTabs[nTabCount]->SetVisible( false );
} else
{ if (ValidTab(nPos) && (nPos < nTabCount))
{
sc::RefUpdateInsertTabContext aCxt( *this, nPos, 1);
for (constauto& a : maTabs)
{ if (a)
a->UpdateInsertTab(aCxt);
}
maTabs.emplace(maTabs.begin() + nPos, new ScTable(*this, nPos, rName));
// UpdateBroadcastAreas must be called between UpdateInsertTab, // which ends listening, and StartAllListeners, to not modify // areas that are to be inserted by starting listeners.
UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,1); for (constauto& a : maTabs)
{ if (a)
a->UpdateCompile();
}
StartAllListeners();
if (pValidationList)
{
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
pValidationList->UpdateInsertTab(aCxt);
}
if (bValid)
{ if (nPos == SC_TAB_APPEND || nPos >= nTabCount)
{ for ( SCTAB i = 0; i < nNewSheets; ++i )
{
maTabs.emplace_back( new ScTable(*this, nTabCount + i, rNames.at(i)) );
}
} else
{ if (ValidTab(nPos) && (nPos < nTabCount))
{
sc::RefUpdateInsertTabContext aCxt( *this, nPos, nNewSheets);
ScRange aRange( 0,0,nPos, MaxCol(),MaxRow(),MAXTAB );
xColNameRanges->UpdateReference( URM_INSDEL, *this, aRange, 0,0,nNewSheets );
xRowNameRanges->UpdateReference( URM_INSDEL, *this, aRange, 0,0,nNewSheets ); if (pRangeName)
pRangeName->UpdateInsertTab(aCxt);
pDBCollection->UpdateReference(
URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets ); if (pDPCollection)
pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,nNewSheets ); if (pDetOpList)
pDetOpList->UpdateReference( *this, URM_INSDEL, aRange, 0,0,nNewSheets );
UpdateChartRef( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets );
UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0, nNewSheets ); if ( pUnoBroadcaster )
pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,nNewSheets ) );
for (constauto& a : maTabs)
{ if (a)
a->UpdateInsertTab(aCxt);
} for (SCTAB i = 0; i < nNewSheets; ++i)
{
maTabs.emplace(maTabs.begin() + nPos + i, new ScTable(*this, nPos + i, rNames.at(i)) );
}
// UpdateBroadcastAreas must be called between UpdateInsertTab, // which ends listening, and StartAllListeners, to not modify // areas that are to be inserted by starting listeners.
UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,nNewSheets); for (constauto& a : maTabs)
{ if (a)
a->UpdateCompile();
}
StartAllListeners();
if (pValidationList)
{
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
pValidationList->UpdateInsertTab(aCxt);
}
bValid = true;
} else
bValid = false;
}
}
if (bValid)
{
sc::SetFormulaDirtyContext aCxt;
SetAllFormulasDirty(aCxt);
}
for (auto & pTab : maTabs) if (pTab)
pTab->UpdateDeleteTab(aCxt);
// tdf#149502 make sure ScTable destructor called after the erase is finished, when // maTabs[x].nTab==x is true again, as it should be always true. // In the end of maTabs.erase, maTabs indexes change, but nTab updated before erase. // ~ScTable expect that maTabs[x].nTab==x so it shouldn't be called during erase.
ScTableUniquePtr pErasedTab = std::move(maTabs[nTab]);
maTabs.erase(maTabs.begin() + nTab); delete pErasedTab.release();
// UpdateBroadcastAreas must be called between UpdateDeleteTab, // which ends listening, and StartAllListeners, to not modify // areas that are to be inserted by starting listeners.
UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1); for (constauto& a : maTabs)
{ if (a)
a->UpdateCompile();
} // Excel-Filter deletes some Tables while loading, Listeners will // only be triggered after the loading is done. if ( !bInsertingFromOtherDoc )
{
StartAllListeners();
for (auto & pTab : maTabs) if (pTab)
pTab->UpdateDeleteTab(aCxt);
maTabs.erase(maTabs.begin() + nTab, maTabs.begin() + nTab + nSheets); // UpdateBroadcastAreas must be called between UpdateDeleteTab, // which ends listening, and StartAllListeners, to not modify // areas that are to be inserted by starting listeners.
UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1*nSheets); for (constauto& a : maTabs)
{ if (a)
a->UpdateCompile();
} // Excel-Filter deletes some Tables while loading, Listeners will // only be triggered after the loading is done. if ( !bInsertingFromOtherDoc )
{
StartAllListeners();
if (comphelper::LibreOfficeKit::isActive())
{
ScModelObj* pModel = GetDocumentShell()->GetModel();
SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
}
bValid = true;
}
} return bValid;
}
bool ScDocument::RenameTab( SCTAB nTab, const OUString& rName, bool bExternalDocument )
{ bool bValid = false;
SCTAB i; if (HasTable(nTab))
{ if ( bExternalDocument )
bValid = true; // composed name else
bValid = ValidTabName(rName); for (i = 0; i < GetTableCount() && bValid; i++)
{ if (maTabs[i] && (i != nTab))
{
OUString aOldName = maTabs[i]->GetName();
bValid = !ScGlobal::GetTransliteration().isEqual( rName, aOldName );
}
} if (bValid)
{ // #i75258# update charts before renaming, so they can get their live data objects. // Once the charts are live, the sheet can be renamed without problems. if ( pChartListenerCollection )
pChartListenerCollection->UpdateChartsContainingTab( nTab );
maTabs[nTab]->SetName(rName);
// If formulas refer to the renamed sheet, the TokenArray remains valid, // but the XML stream must be re-generated. for (constauto& pTable : maTabs)
{ if (pTable)
{
pTable->SetStreamValid( false ); // tdf#156815 Reset solver settings so next time they're loaded they come with // the updated sheet name
pTable->ResetSolverSettings();
}
}
if ( bImportingXML )
{ // #i57869# only set the LoadingRTL flag, the real setting (including mirroring) // is applied in SetImportingXML(false). This is so the shapes can be loaded in // normal LTR mode.
pTable->SetLoadingRTL( bRTL ); return;
}
pTable->SetLayoutRTL( bRTL ); // only sets the flag
pTable->SetDrawPageSize(true, true, eObjectHandling);
// objects are already repositioned via SetDrawPageSize, only writing mode is missing if (!mpDrawLayer) return;
SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
OSL_ENSURE(pPage,"Page ?"); if (!pPage) return;
bool ScDocument::IsNegativePage( SCTAB nTab ) const
{ // Negative page area is always used for RTL layout. // The separate method is used to find all RTL handling of drawing objects. return IsLayoutRTL( nTab );
}
/* ---------------------------------------------------------------------------- used search area:
GetCellArea - Only Data GetTableArea - Data / Attributes GetPrintArea - intended for character objects, sweeps attributes all the way to bottom / right
---------------------------------------------------------------------------- */
// Make sure the area only shrinks, and doesn't grow. if (rStartCol < nCol1)
rStartCol = nCol1; if (nCol2 < rEndCol)
rEndCol = nCol2; if (rStartRow < nRow1)
rStartRow = nRow1; if (nRow2 < rEndRow)
rEndRow = nRow2;
private:
sc::AutoCalcSwitch aSwitch; // first for ctor/dtor order, destroy second
ScBulkBroadcast aBulk; // second for ctor/dtor order, destroy first
};
bool bTest = true; bool bRet = false; bool bOldAutoCalc = GetAutoCalc();
SetAutoCalc( false ); // avoid multiple calculations bool oldDelayedDeleteBroadcasters = IsDelayedDeletingBroadcasters();
EnableDelayDeletingBroadcasters( true ); for ( i = nStartTab; i <= nEndTab && bTest && i < GetTableCount(); i++) if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
bTest &= maTabs[i]->TestInsertRow(nStartCol, nEndCol, nStartRow, nSize); if (bTest)
{ // UpdateBroadcastAreas have to be called before UpdateReference, so that entries // aren't shifted that would be rebuild at UpdateReference
std::vector<ScAddress> aGroupPos; do
{
aShiftedRange.aStart.SetTab(nTabRangeStart);
aShiftedRange.aEnd.SetTab(nTabRangeEnd);
// Collect all formula groups that will get split by the shifting, // and end all their listening. Record the position of the top // cell of the topmost group, and the position of the bottom cell // of the bottommost group.
EndListeningIntersectedGroups(aEndListenCxt, aShiftedRange, &aGroupPos);
sc::RefUpdateContext aCxt(*this);
aCxt.meMode = URM_INSDEL;
aCxt.maRange = aShiftedRange;
aCxt.mnRowDelta = nSize; do
{
aCxt.maRange.aStart.SetTab(nTabRangeStart);
aCxt.maRange.aEnd.SetTab(nTabRangeEnd);
UpdateReference(aCxt, pRefUndoDoc, false); // without drawing objects
} while (lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
// UpdateReference should have set "needs listening" flags to those // whose references have been modified. We also need to set this flag // to those that were in the groups that got split by shifting.
SetNeedsListeningGroups(aGroupPos);
for (i=nStartTab; i<=nEndTab && i < GetTableCount(); i++)
{ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
{
maTabs[i]->InsertRow( nStartCol, nEndCol, nStartRow, nSize );
maTabs[i]->CommentNotifyAddressChange(nStartCol, nStartRow, nEndCol, MaxRow());
}
}
// UpdateRef for drawing layer must be after inserting, // when the new row heights are known. for (i=nStartTab; i<=nEndTab && i < GetTableCount(); i++) if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
maTabs[i]->UpdateDrawRef( URM_INSDEL,
nStartCol, nStartRow, nStartTab, nEndCol, MaxRow(), nEndTab,
0, static_cast<SCROW>(nSize), 0 );
if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() )
{ // A new Listening is needed when references to deleted ranges are restored, // previous Listeners were removed in FormulaCell UpdateReference.
StartAllListeners();
} else
{ // Listeners have been removed in UpdateReference
StartNeededListeners();
// At least all cells using range names pointing relative to the // moved range must be recalculated, and all cells marked postponed // dirty. for (constauto& a : maTabs)
{ if (a)
a->SetDirtyIfPostponed();
}
sc::RefUpdateContext aCxt(*this); constbool bLastRowIncluded = (static_cast<SCROW>(nStartRow + nSize) == GetMaxRowCount() && ValidRow(nStartRow)); if ( ValidRow(nStartRow+nSize) || bLastRowIncluded )
{
lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
aCxt.meMode = URM_INSDEL;
aCxt.mnRowDelta = -static_cast<SCROW>(nSize); if (bLastRowIncluded)
{ // Last row is included, shift a virtually non-existent row in.
aCxt.maRange = ScRange( nStartCol, GetMaxRowCount(), nTabRangeStart, nEndCol, GetMaxRowCount(), nTabRangeEnd);
} else
{
aCxt.maRange = ScRange( nStartCol, nStartRow+nSize, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd);
} do
{
UpdateReference(aCxt, pRefUndoDoc, true, false);
} while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
}
if (pUndoOutline)
*pUndoOutline = false;
// Keep track of the positions of all formula groups that have been joined // during row deletion.
std::vector<ScAddress> aGroupPos;
for ( i = nStartTab; i <= nEndTab && i < GetTableCount(); i++)
{ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
{
maTabs[i]->DeleteRow(aCxt.maRegroupCols, nStartCol, nEndCol, nStartRow, nSize, pUndoOutline, &aGroupPos);
maTabs[i]->CommentNotifyAddressChange(nStartCol, nStartRow, nEndCol, MaxRow());
}
}
// Newly joined groups have some of their members still listening. We // need to make sure none of them are listening.
EndListeningGroups(aGroupPos);
// Mark all joined groups for group listening.
SetNeedsListeningGroups(aGroupPos);
if ( ValidRow(nStartRow+nSize) || bLastRowIncluded )
{ // Listeners have been removed in UpdateReference
StartNeededListeners();
// At least all cells using range names pointing relative to the moved // range must be recalculated, and all cells marked postponed dirty. for (constauto& a : maTabs)
{ if (a)
a->SetDirtyIfPostponed();
}
for (i = nStartTab; i <= nEndTab && i < GetTableCount(); i++) if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
maTabs[i]->InsertCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize);
if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() )
{ // A new Listening is needed when references to deleted ranges are restored, // previous Listeners were removed in FormulaCell UpdateReference.
StartAllListeners();
} else
{ // Listeners have been removed in UpdateReference
StartNeededListeners(); // At least all cells using range names pointing relative to the // moved range must be recalculated, and all cells marked postponed // dirty.
{
ScBulkBroadcast aBulkBroadcast(GetBASM(), SfxHintId::ScDataChanged);
std::for_each(maTabs.begin(), maTabs.end(), SetDirtyIfPostponedHandler());
} // Cells containing functions such as CELL, COLUMN or ROW may have // changed their values on relocation. Broadcast them.
{
BroadcastRecalcOnRefMoveGuard g(this);
std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
}
}
bRet = true;
}
EnableDelayDeletingBroadcasters( oldDelayedDeleteBroadcasters );
SetAutoCalc( bOldAutoCalc ); if ( bRet && pChartListenerCollection )
pChartListenerCollection->UpdateDirtyCharts(); return bRet;
}
sc::RefUpdateContext aCxt(*this); constbool bLastColIncluded = (static_cast<SCCOL>(nStartCol + nSize) == GetMaxColCount() && ValidCol(nStartCol)); if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) || bLastColIncluded )
{
lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
aCxt.meMode = URM_INSDEL;
aCxt.mnColDelta = -static_cast<SCCOL>(nSize); if (bLastColIncluded)
{ // Last column is included, shift a virtually non-existent column in.
aCxt.maRange = ScRange( GetMaxColCount(), nStartRow, nTabRangeStart, GetMaxColCount(), nEndRow, nTabRangeEnd);
} else
{
aCxt.maRange = ScRange( sal::static_int_cast<SCCOL>(nStartCol+nSize), nStartRow, nTabRangeStart,
MaxCol(), nEndRow, nTabRangeEnd);
} do
{
UpdateReference(aCxt, pRefUndoDoc, true, false);
} while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
}
if (pUndoOutline)
*pUndoOutline = false;
for (i = nStartTab; i <= nEndTab && i < GetTableCount(); ++i)
{ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
maTabs[i]->DeleteCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize, pUndoOutline);
}
if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) || bLastColIncluded )
{ // Listeners have been removed in UpdateReference
StartNeededListeners();
// At least all cells using range names pointing relative to the moved // range must be recalculated, and all cells marked postponed dirty. for (constauto& a : maTabs)
{ if (a)
a->SetDirtyIfPostponed();
}
if ( bInsCol && !CanInsertCol( aColRange ) ) // Cells at the edge ?
bOk = false; if ( bInsRow && !CanInsertRow( aRowRange ) ) // Cells at the edge ?
bOk = false;
if ( bInsCol || bDelCol )
{
aColRange.aEnd.SetCol(MaxCol()); if ( HasPartOfMerged(aColRange) )
bOk = false;
} if ( bInsRow || bDelRow )
{
aRowRange.aEnd.SetRow(MaxRow()); if ( HasPartOfMerged(aRowRange) )
bOk = false;
}
std::vector<ScAddress> aGroupPos; // Destroy and reconstruct listeners only if content is affected. bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag); if (bDelContent)
{ // Record the positions of top and/or bottom formula groups that intersect // the area borders.
sc::EndListeningContext aCxt(*this);
ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0); for (SCTAB i = 0; i < GetTableCount(); i++)
{ if (rMark.GetTableSelect(i))
{
aRange.aStart.SetTab(i);
aRange.aEnd.SetTab(i);
for (SCTAB i = 0; i < GetTableCount(); i++) if (maTabs[i]) if ( rMark.GetTableSelect(i) || bIsUndo )
maTabs[i]->DeleteArea(nCol1, nRow1, nCol2, nRow2, nDelFlag, bBroadcast, pBroadcastSpans);
if (!bDelContent) return;
// Re-start listeners on those top bottom groups that have been split.
SetNeedsListeningGroups(aGroupPos);
StartNeededListeners();
// If formula groups were split their listeners were destroyed and may // need to be notified now that they're restored, ScTable::DeleteArea() // couldn't do that. if (aGroupPos.empty()) return;
ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0); for (SCTAB i = 0; i < GetTableCount(); i++)
{ if (rMark.GetTableSelect(i))
{
aRange.aStart.SetTab(i);
aRange.aEnd.SetTab(i);
SetDirty( aRange, true);
}
}
}
if (!pClipDoc)
{
SAL_WARN("sc", "CopyToClip: no ClipDoc");
pClipDoc = ScModule::GetClipDoc();
}
if (mpShell->GetMedium())
{
pClipDoc->maFileURL = mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri); // for unsaved files use the title name and adjust during save of file if (pClipDoc->maFileURL.isEmpty())
pClipDoc->maFileURL = mpShell->GetName();
} else
{
pClipDoc->maFileURL = mpShell->GetName();
}
// 2. Copy drawing objects in the selection. Do in after the first "copy cells" pass, because // the embedded objects (charts) could reference cells from tabs not (yet) copied; doing it now // allows to know what is already copied, to not overwrite attributes of already copied data. if (mpDrawLayer && bIncludeObjects)
{ for (SCTAB i = 0; i < nEndTab; ++i)
{
tools::Rectangle aObjRect = GetMMRect(aClipRange.aStart.Col(), aClipRange.aStart.Row(),
aClipRange.aEnd.Col(), aClipRange.aEnd.Row(), i);
mpDrawLayer->CopyToClip(pClipDoc, i, aObjRect);
}
}
// Make sure to mark overlapped cells.
pClipDoc->ExtendMerge(aClipRange, true);
}
if (!pClipDoc)
{
SAL_WARN("sc", "CopyTabToClip: no ClipDoc");
pClipDoc = ScModule::GetClipDoc();
}
if (mpShell->GetMedium())
{
pClipDoc->maFileURL = mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri); // for unsaved files use the title name and adjust during save of file if (pClipDoc->maFileURL.isEmpty())
pClipDoc->maFileURL = mpShell->GetName();
} else
{
pClipDoc->maFileURL = mpShell->GetName();
}
if (!ValidRow(aCombinedClipRange.aEnd.Row() - aCombinedClipRange.aStart.Row()))
{
SAL_WARN("sc", "TransposeClip: Too big"); return;
}
// Transpose of filtered multi range row selection is a special case since filtering // and selection are in the same dimension (i.e. row). // The filtered row status and the selection ranges are not available at the same time, // handle this case specially, do not use GetClipParam().getWholeRange(), // instead loop through the ranges, calculate the row offset and handle filtered rows and // create in ScClipParam::transpose() a unified range. constbool bIsMultiRangeRowFilteredTranspose
= !bIncludeFiltered && GetClipParam().isMultiRange()
&& HasFilteredRows(aCombinedClipRange.aStart.Row(), aCombinedClipRange.aEnd.Row(),
aCombinedClipRange.aStart.Tab())
&& GetClipParam().meDirection == ScClipParam::Row;
// The data
ScRange aClipRange;
SCROW nRowCount = 0; // next consecutive row for (size_t j = 0, n = aClipRanges.size(); j < n; ++j)
{
aClipRange = aClipRanges[j];
SCROW nRowOffset = 0; if (bIsMultiRangeRowFilteredTranspose)
{ // adjust for the rows that are filtered
nRowOffset = nRowCount;
// calculate filtered rows of current clip range
SCROW nRowCountNonFiltered = CountNonFilteredRows(
aClipRange.aStart.Row(), aClipRange.aEnd.Row(), aClipRange.aStart.Tab());
assert(!bIncludeFiltered && "bIsMultiRangeRowFilteredTranspose can only be true if bIncludeFiltered is false");
nRowCount += nRowCountNonFiltered; // for next iteration
}
for (SCTAB i = 0; i < GetTableCount(); i++)
{ if (maTabs[i])
{
OSL_ENSURE(pTransClip->maTabs[i], "TransposeClip: Table not there");
maTabs[i]->TransposeClip(
aClipRange.aStart.Col(), aClipRange.aStart.Row(), aClipRange.aEnd.Col(),
aClipRange.aEnd.Row(), aCombinedClipRange.aStart.Row(), nRowOffset,
pTransClip->maTabs[i].get(), nFlags, bAsLink, bIncludeFiltered);
if ( mpDrawLayer && ( nFlags & InsertDeleteFlags::OBJECTS ) )
{ // Drawing objects are copied to the new area without transposing. // CopyFromClip is used to adjust the objects to the transposed block's // cell range area. // (mpDrawLayer in the original clipboard document is set only if there // are drawing objects to copy)
// ToDo: Loop over blocks of non-filtered rows in case of filtered rows exist.
pTransClip->InitDrawLayer();
ScAddress aTransposedEnd( static_cast<SCCOL>(aClipRange.aEnd.Row() - aClipRange.aStart.Row() + aClipRange.aStart.Col()), static_cast<SCROW>(aClipRange.aEnd.Col() - aClipRange.aStart.Col() + aClipRange.aStart.Row()), i);
ScRange aDestRange(aClipRange.aStart, aTransposedEnd);
ScAddress aDestStart = aClipRange.aStart;
pTransClip->mpDrawLayer->CopyFromClip(mpDrawLayer.get(), i, aClipRange, aDestStart, aDestRange, true);
}
}
}
}
sc::UpdatedRangeNames aUsedNames; // indexes of named ranges that are used in the copied cells
SCTAB nMinSizeBothTabs = std::min(GetTableCount(), pClipDoc->GetTableCount()); for (SCTAB i = 0; i < nMinSizeBothTabs; ++i) if (maTabs[i] && pClipDoc->maTabs[i]) if ( !pMarks || pMarks->GetTableSelect(i) )
maTabs[i]->FindRangeNamesInUse(
rClipRange.aStart.Col(), rClipRange.aStart.Row(),
rClipRange.aEnd.Col(), rClipRange.aEnd.Row(), aUsedNames);
if (rCxt.getClipDoc()->mpDrawLayer && (rCxt.getInsertFlag() & InsertDeleteFlags::OBJECTS))
{ // also copy drawing objects
// drawing layer must be created before calling CopyFromClip // (ScDocShell::MakeDrawLayer also does InitItems etc.)
OSL_ENSURE( mpDrawLayer, "CopyBlockFromClip: No drawing layer" ); if ( mpDrawLayer )
{ // For GetMMRect, the row heights in the target document must already be valid // (copied in an extra step before pasting, or updated after pasting cells, but // before pasting objects).
ScRange aSourceRange(nCol1 - nDx, nRow1 - nDy, nClipTab, nCol2 - nDx, nRow2 - nDy, nClipTab);
ScRange aDestRange(nCol1, nRow1, i, nCol2, nRow2, i);
mpDrawLayer->CopyFromClip(rCxt.getClipDoc()->mpDrawLayer.get(), nClipTab, aSourceRange,
ScAddress( nCol1, nRow1, i ), aDestRange);
}
}
nClipTab = 0; for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < GetTableCount(); i++)
{ if (maTabs[i] && rMark.GetTableSelect(i) )
{ while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast<SCTAB>(rClipTabs.size());
SCTAB nDz = i - nClipTab;
// ranges of consecutive selected tables (in clipboard and dest. doc) // must be handled in one UpdateReference call
SCTAB nFollow = 0; while ( i + nFollow < nTabEnd
&& rMark.GetTableSelect( i + nFollow + 1 )
&& nClipTab + nFollow < MAXTAB
&& rClipTabs[(nClipTab + nFollow + 1) % static_cast<SCTAB>(rClipTabs.size())] )
++nFollow;
sc::RefUpdateContext aRefCxt(*this, rCxt.getClipDoc());
aRefCxt.maRange = ScRange(nCol1, nRow1, i, nCol2, nRow2, i+nFollow);
aRefCxt.mnColDelta = nDx;
aRefCxt.mnRowDelta = nDy;
aRefCxt.mnTabDelta = nDz;
aRefCxt.setBlockPositionReference(rCxt.getBlockPositionSet()); // share mdds position caching if (rCxt.getClipDoc()->GetClipParam().mbCutMode)
{ // Update references only if cut originates from the same // document we are pasting into. if (rCxt.getClipDoc()->GetPool() == GetPool())
{ bool bOldInserting = IsInsertingFromOtherDoc();
SetInsertingFromOtherDoc( true);
aRefCxt.meMode = URM_MOVE;
UpdateReference(aRefCxt, rCxt.getUndoDoc(), false);
// For URM_MOVE group listeners may have been removed, // re-establish them. if (!aRefCxt.maRegroupCols.empty())
{ /* TODO: holding the ColumnSet in a shared_ptr at * RefUpdateContext would eliminate the need of
* copying it here. */ auto pColSet = std::make_shared<sc::ColumnSet>( aRefCxt.maRegroupCols);
StartNeededListeners( pColSet);
}
nClipTab = (nClipTab+nFollow+1) % static_cast<SCTAB>(rClipTabs.size());
i = sal::static_int_cast<SCTAB>( i + nFollow );
}
}
}
SCROW ScDocument::CopyNonFilteredFromClip(sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1,
SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark,
SCCOL nDx, SCROW& rClipStartRow, SCROW nClipEndRow)
{ // call CopyBlockFromClip for ranges of consecutive non-filtered rows // nCol1/nRow1 etc. is in target doc
// filtered state is taken from first used table in clipboard (as in GetClipArea)
SCTAB nFlagTab = 0;
TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs; while ( nFlagTab < static_cast<SCTAB>(rClipTabs.size()) && !rClipTabs[nFlagTab] )
++nFlagTab;
/* Decide which contents to delete before copying. Delete all contents if nInsFlag contains any real content flag. #i102056# Notes are pasted from clipboard in a second pass, together with the special flag InsertDeleteFlags::ADDNOTES that states to not overwrite/delete existing cells but to insert the notes into these cells. In this case, just delete old notes from the
destination area. */
InsertDeleteFlags nDelFlag = nInsFlag; // tdf#163019 - remove formula of the cell to update formula listeners if (nInsFlag & InsertDeleteFlags::CONTENTS)
nDelFlag |= InsertDeleteFlags::FORMULA;
// tdf#161189 - remove the note deletion flag if no notes are included if ((nInsFlag & (InsertDeleteFlags::CONTENTS | InsertDeleteFlags::ADDNOTES))
== (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES))
nDelFlag &= ~InsertDeleteFlags::NOTE;
if (nInsFlag & InsertDeleteFlags::ATTRIB)
nDelFlag |= InsertDeleteFlags::ATTRIB;
if (bPreallocatePattern)
{ for (SCTAB i = aCxt.getTabStart(); i <= aCxt.getTabEnd(); ++i) if (maTabs[i] && rMark.GetTableSelect( i ) )
vTables.push_back( i );
}
do
{ // Pasting is done column-wise, when pasting to a filtered // area this results in partitioning and we have to // remember and reset the start row for each column until // it can be advanced for the next chunk of unfiltered // rows.
SCROW nSaveClipStartRow = nClipStartRow; do
{
nClipStartRow = nSaveClipStartRow;
SCCOL nDx = nC1 - nClipStartCol;
SCROW nDy = nR1 - nClipStartRow; if ( bIncludeFiltered )
{
CopyBlockFromClip(
aCxt, nC1, nR1, nC2, nR2, rMark, nDx, nDy);
nClipStartRow += nR2 - nR1 + 1;
} else
{
CopyNonFilteredFromClip(aCxt, nC1, nR1, nC2, nR2, rMark, nDx, nClipStartRow,
nClipEndRow);
}
nC1 = nC2 + 1;
nC2 = std::min(static_cast<SCCOL>(nC1 + nXw), nCol2);
} while (nC1 <= nCol2); if (nClipStartRow > nClipEndRow)
nClipStartRow = aClipRange.aStart.Row();
nC1 = nCol1;
nC2 = nC1 + nXw; if (nC2 > nCol2)
nC2 = nCol2;
// Preallocate pattern memory once if further chunks are to be pasted. if (bPreallocatePattern && (nR2+1) <= nRow2)
{
SCROW nR3 = nR2 + 1; for (SCTAB nTab : vTables)
{ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
{ // Pattern count of the first chunk pasted.
SCSIZE nChunk = GetPatternCount( nTab, nCol, nR1, nR2); // If it is only one pattern per chunk and chunks are // pasted consecutively then it will get its range // enlarged for each chunk and no further allocation // happens. For non-consecutive chunks we're out of // luck in this case. if (nChunk > 1)
{
SCSIZE nNeeded = nChunk * (nRow2 - nR3 + 1) / (nYw + 1);
SCSIZE nRemain = GetPatternCount( nTab, nCol, nR3, nRow2); if (nNeeded > nRemain)
{
SCSIZE nCurr = GetPatternCount( nTab, nCol);
ReservePatternCount( nTab, nCol, nCurr + nNeeded);
}
}
}
}
bPreallocatePattern = false;
}
// Set all formula cells dirty, and collect non-empty non-formula cell // positions so that we can broadcast on them below.
SetDirtyFromClip(nAllCol1, nAllRow1, nAllCol2, nAllRow2, rMark, nInsFlag, aBroadcastSpans);
switch (rClipParam.meDirection)
{ case ScClipParam::Row: // Begin row for the next range being pasted.
nRow1 += nRowCount; break; case ScClipParam::Column:
nCol1 += rRange.aEnd.Col() - rRange.aStart.Col() + 1; break; default:
;
}
}
bInsertingFromOtherDoc = false;
// Create Listener after everything has been inserted
StartListeningFromClip(aDestRange.aStart.Col(), aDestRange.aStart.Row(),
aDestRange.aEnd.Col(), aDestRange.aEnd.Row(), rMark, nInsFlag );
bool ScDocument::HasClipFilteredRows()
{ // count on first used table in clipboard
SCTAB nCountTab = 0; while (nCountTab < GetTableCount() && !maTabs[nCountTab])
++nCountTab;
ScRangeList& rClipRanges = GetClipParam().maRanges; if ( rClipRanges.empty() ) returnfalse;
if (maTabs.size() > 0)
{ for (size_t i = 0, n = rClipRanges.size(); i < n; ++i)
{
ScRange& rRange = rClipRanges[i]; bool bAnswer
= maTabs[nCountTab]->HasFilteredRows(rRange.aStart.Row(), rRange.aEnd.Row()); if (bAnswer) returntrue;
}
} returnfalse;
}
SCTAB nCount = GetTableCount(); for (const SCTAB& i : rMark)
{ if (i >= nCount) break; if (i != nSrcTab && maTabs[i])
{ if (bDoMix)
{ if (!pMixDoc)
{
pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO));
pMixDoc->InitUndo( *this, i, i );
} else
pMixDoc->AddUndoTab( i, i );
sc::CopyToDocContext aCxt(*this);
sc::MixDocContext aMixDocCxt(*this);
SCTAB nCount = GetTableCount(); for (const SCTAB& i : rMark)
{ if (i >= nCount) break; if ( i != nSrcTab && maTabs[i] )
{ if (bDoMix)
{ if (!pMixDoc)
{
pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO));
pMixDoc->InitUndo( *this, i, i );
} else
pMixDoc->AddUndoTab( i, i );
const ScFormulaCell* pCurCellFormula = pTab->GetFormulaCell(nCol, nRow); if (pCurCellFormula && pCurCellFormula->IsShared())
{ // In case setting this string affects an existing formula group, end // its listening to purge then empty cell broadcasters. Affected // remaining split group listeners will be set up again via // ScColumn::DetachFormulaCell() and // ScColumn::StartListeningUnshared().
const ScFormulaCell* pCurCellFormula = pTab->GetFormulaCell(rPos.Col(), rPos.Row()); if (pCurCellFormula && pCurCellFormula->IsShared())
{ // In case setting this value affects an existing formula group, end // its listening to purge then empty cell broadcasters. Affected // remaining split group listeners will be set up again via // ScColumn::DetachFormulaCell() and // ScColumn::StartListeningUnshared().
FormulaError ScDocument::GetStringForFormula( const ScAddress& rPos, OUString& rString )
{ // Used in formulas (add-in parameters etc), so it must use the same semantics as // ScInterpreter::GetCellString: always format values as numbers. // The return value is the error code.
ScRefCellValue aCell(*this, rPos); if (aCell.isEmpty())
{
rString.clear(); return FormulaError::NONE;
}
for (constauto& a : maTabs)
{ if (a)
a->CheckVectorizationState();
}
SetAutoCalc(bOldAutoCalc);
}
void ScDocument::SetAllFormulasDirty( const sc::SetFormulaDirtyContext& rCxt )
{ bool bOldAutoCalc = GetAutoCalc();
bAutoCalc = false; // no multiple calculations
{ // scope for bulk broadcast
ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged); for (constauto& a : maTabs)
{ if (a)
a->SetAllFormulasDirty(rCxt);
}
}
// Although Charts are also set to dirty in Tracking without AutoCalc // if all formulas are dirty, the charts can no longer be caught // (#45205#) - that is why all Charts have to be explicitly handled again if (pChartListenerCollection)
pChartListenerCollection->SetDirty();
SetAutoCalc( bOldAutoCalc );
}
void ScDocument::SetDirty( const ScRange& rRange, bool bIncludeEmptyCells )
{ bool bOldAutoCalc = GetAutoCalc();
bAutoCalc = false; // no multiple calculations
{ // scope for bulk broadcast
ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
SCTAB nTab2 = rRange.aEnd.Tab(); for (SCTAB i = rRange.aStart.Tab(); i <= nTab2 && i < GetTableCount(); i++) if (maTabs[i]) maTabs[i]->SetDirty( rRange,
(bIncludeEmptyCells ? ScColumn::BROADCAST_BROADCASTERS : ScColumn::BROADCAST_DATA_POSITIONS));
/* TODO: this now also notifies conditional formatting and does a UNO * broadcast, which wasn't done here before. Is that an actually * desired side effect, or should we come up with a method that
* doesn't? */ if (bIncludeEmptyCells)
BroadcastCells( rRange, SfxHintId::ScDataChanged, false);
}
SetAutoCalc( bOldAutoCalc );
}
void ScDocument::SetTableOpDirty( const ScRange& rRange )
{ bool bOldAutoCalc = GetAutoCalc();
bAutoCalc = false; // no multiple recalculation
SCTAB nTab2 = rRange.aEnd.Tab(); for (SCTAB i = rRange.aStart.Tab(); i <= nTab2 && i < GetTableCount(); i++) if (maTabs[i]) maTabs[i]->SetTableOpDirty( rRange );
SetAutoCalc( bOldAutoCalc );
}
void ScDocument::InterpretDirtyCells( const ScRangeList& rRanges )
{ if (!GetAutoCalc()) return;
void ScDocument::AddTableOpFormulaCell( ScFormulaCell* pCell )
{ if (m_TableOpList.empty()) return;
ScInterpreterTableOpParams *const p = m_TableOpList.back(); if ( p->bCollectNotifications )
{ if ( p->bRefresh )
{ // refresh pointers only
p->aNotifiedFormulaCells.push_back( pCell );
} else
{ // init both, address and pointer
p->aNotifiedFormulaCells.push_back( pCell );
p->aNotifiedFormulaPos.push_back( pCell->aPos );
}
}
}
void ScDocument::CalcAll()
{
PrepareFormulaCalc();
ClearLookupCaches(); // Ensure we don't deliver zombie data.
sc::AutoCalcSwitch aSwitch(*this, true); for (constauto& a : maTabs)
{ if (a)
a->SetDirtyVar();
} for (constauto& a : maTabs)
{ if (a)
a->CalcAll();
}
ClearFormulaTree();
// In eternal hard recalc state caches were not added as listeners, // invalidate them so the next non-CalcAll() normal lookup will not be // presented with outdated data. if (GetHardRecalcState() == HardRecalcState::ETERNAL)
ClearLookupCaches();
}
void ScDocument::CompileAll()
{
sc::CompileFormulaContext aCxt(*this); for (constauto& a : maTabs)
{ if (a)
a->CompileAll(aCxt);
}
// set AutoNameCache to speed up automatic name lookup
OSL_ENSURE( !pAutoNameCache, "AutoNameCache already set" );
pAutoNameCache.reset( new ScAutoNameCache( *this ) );
if (pRangeName)
pRangeName->CompileUnresolvedXML(aCxt);
void ScDocument::CalcAfterLoad( bool bStartListening )
{ if (bIsClip) // Excel data is loaded from the Clipboard to a Clip-Doc return; // the calculation is then only performed when inserting into the real document
bCalcingAfterLoad = true;
sc::CompileFormulaContext aCxt(*this);
{ for (constauto& pTable : maTabs)
{ if (pTable)
pTable->CalcAfterLoad(aCxt, bStartListening);
} for (constauto& pTable : maTabs)
{ if (pTable)
pTable->SetDirtyAfterLoad();
}
}
bCalcingAfterLoad = false;
SetDetectiveDirty(false); // No real changes yet
// #i112436# If formula cells are already dirty, they don't broadcast further changes. // So the source ranges of charts must be interpreted even if they are not visible, // similar to ScMyShapeResizer::CreateChartListener for loading own files (i104899). if (pChartListenerCollection)
{ const ScChartListenerCollection::ListenersType& rListeners = pChartListenerCollection->getListeners(); for (autoconst& it : rListeners)
{ const ScChartListener *const p = it.second.get();
InterpretDirtyCells(*p->GetRangeList());
}
}
}
for (SCTAB i = nTab1; i <= nTab2 && i < GetTableCount(); i++) if (maTabs[i])
{ if ( nMask & HasAttrFlags::RightOrCenter )
{ // On a RTL sheet, don't start to look for the default left value // (which is then logically right), instead always assume true. // That way, ScAttrArray::HasAttrib doesn't have to handle RTL sheets.
if ( nMask & HasAttrFlags::RightOrCenter )
{ // On a RTL sheet, don't start to look for the default left value // (which is then logically right), instead always assume true. // That way, ScAttrArray::HasAttrib doesn't have to handle RTL sheets.
if (maTabs[rTab] && maTabs[rTab]->HasBlockMatrixFragment( nStartCol, nStartRow, nEndCol, nEndRow ))
bOk = false;
if (!bOk) break;
}
return !bOk;
}
bool ScDocument::GetMatrixFormulaRange( const ScAddress& rCellPos, ScRange& rMatrix )
{ // if rCell is part of a matrix formula, return its complete range
ScFormulaCell* pFCell = GetFormulaCell(rCellPos); if (!pFCell) // not a formula cell. Bail out. returnfalse;
ScAddress aOrigin = rCellPos; if (!pFCell->GetMatrixOrigin(*this, aOrigin)) // Failed to get the address of the matrix origin. returnfalse;
if (aOrigin != rCellPos)
{
pFCell = GetFormulaCell(aOrigin); if (!pFCell) // The matrix origin cell is not a formula cell !? Something is up... returnfalse;
}
SCCOL nSizeX;
SCROW nSizeY;
pFCell->GetMatColsRows(nSizeX, nSizeY); if (nSizeX <= 0 || nSizeY <= 0)
{ // GetMatrixEdge computes also dimensions of the matrix // if not already done (may occur if document is loaded // from old file format). // Needs an "invalid" initialized address.
aOrigin.SetInvalid();
pFCell->GetMatrixEdge(*this, aOrigin);
pFCell->GetMatColsRows(nSizeX, nSizeY);
}
if (nSizeX <= 0 || nSizeY <= 0) // Matrix size is still invalid. Give up. returnfalse;
void ScDocument::ExtendTotalMerge( ScRange& rRange ) const
{ // Extend range to merged cells without including any new non-overlapped cells
ScRange aExt = rRange; // ExtendMerge() is non-const, but called without refresh. if (!const_cast<ScDocument*>(this)->ExtendMerge( aExt )) return;
std::vector<ScAddress> aGroupPos; // Destroy and reconstruct listeners only if content is affected. bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag); if (bDelContent)
{ // Record the positions of top and/or bottom formula groups that // intersect the area borders.
sc::EndListeningContext aCxt(*this);
ScRangeList aRangeList;
rMark.FillRangeListWithMarks( &aRangeList, false); for (size_t i = 0; i < aRangeList.size(); ++i)
{ const ScRange & rRange = aRangeList[i];
EndListeningIntersectedGroups( aCxt, rRange, &aGroupPos);
}
aCxt.purgeEmptyBroadcasters();
}
SCTAB nMax = GetTableCount(); for (constauto& rTab : rMark)
{ if (rTab >= nMax) break; if (maTabs[rTab])
maTabs[rTab]->DeleteSelection(nDelFlag, rMark, bBroadcast);
}
if (!bDelContent) return;
// Re-start listeners on those top bottom groups that have been split.
SetNeedsListeningGroups(aGroupPos);
StartNeededListeners();
// If formula groups were split their listeners were destroyed and may // need to be notified now that they're restored, // ScTable::DeleteSelection() couldn't do that. if (aGroupPos.empty()) return;
ScRangeList aRangeList;
rMark.FillRangeListWithMarks( &aRangeList, false); for (size_t i = 0; i < aRangeList.size(); ++i)
{
SetDirty( aRangeList[i], true);
} //Notify listeners on top and bottom of the group that has been split for (size_t i = 0; i < aGroupPos.size(); ++i) {
ScFormulaCell *pFormulaCell = GetFormulaCell(aGroupPos[i]); if (pFormulaCell)
pFormulaCell->SetDirty(true);
}
}
std::vector<ScAddress> aGroupPos; // Destroy and reconstruct listeners only if content is affected. bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag); if (bDelContent)
{ // Record the positions of top and/or bottom formula groups that // intersect the area borders.
sc::EndListeningContext aCxt(*this);
ScRangeList aRangeList;
rMark.FillRangeListWithMarks( &aRangeList, false); for (size_t i = 0; i < aRangeList.size(); ++i)
{ const ScRange & rRange = aRangeList[i]; if (rRange.aStart.Tab() <= nTab && nTab <= rRange.aEnd.Tab())
{
ScRange aRange( rRange);
aRange.aStart.SetTab( nTab);
aRange.aEnd.SetTab( nTab);
EndListeningIntersectedGroups( aCxt, aRange, &aGroupPos);
}
}
aCxt.purgeEmptyBroadcasters();
}
pTable->DeleteSelection(nDelFlag, rMark);
if (bDelContent)
{ // Re-start listeners on those top bottom groups that have been split.
SetNeedsListeningGroups(aGroupPos);
StartNeededListeners();
// If formula groups were split their listeners were destroyed and may // need to be notified now that they're restored, // ScTable::DeleteSelection() couldn't do that. if (!aGroupPos.empty())
{
ScRangeList aRangeList;
rMark.FillRangeListWithMarks( &aRangeList, false); for (size_t i = 0; i < aRangeList.size(); ++i)
{ const ScRange & rRange = aRangeList[i]; if (rRange.aStart.Tab() <= nTab && nTab <= rRange.aEnd.Tab())
{
ScRange aRange( rRange);
aRange.aStart.SetTab( nTab);
aRange.aEnd.SetTab( nTab);
SetDirty( aRange, true);
}
}
}
}
} else
{
OSL_FAIL("wrong table");
}
}
bool ScDocument::NeedPageResetAfterTab( SCTAB nTab ) const
{ // The page number count restarts at a sheet, if another template is set at // the preceding one (only compare names) and if a pagenumber is specified (not 0)
if (nTab + 1 < GetTableCount() && maTabs[nTab] && maTabs[nTab+1])
{ const OUString & rNew = maTabs[nTab+1]->GetPageStyle(); if ( rNew != maTabs[nTab]->GetPageStyle() )
{
SfxStyleSheetBase* pStyle = mxPoolHelper->GetStylePool()->Find( rNew, SfxStyleFamily::Page ); if ( pStyle )
{ const SfxItemSet& rSet = pStyle->GetItemSet();
sal_uInt16 nFirst = rSet.Get(ATTR_PAGE_FIRSTPAGENO).GetValue(); if ( nFirst != 0 ) returntrue; // Specify page number in new template
}
}
}
returnfalse; // otherwise not
}
ScUndoManager* ScDocument::GetUndoManager()
{ if (!mpUndoManager)
{ // to support enhanced text edit for draw objects, use an SdrUndoManager
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
ScUndoManager* pUndoManager = new ScUndoManager;
pUndoManager->SetDocShell(GetDocumentShell());
mpUndoManager = pUndoManager;
}
void ScDocument::SetSubTotalCellsDirty(const ScRange& rDirtyRange)
{ // to update the list by skipping cells that no longer contain subtotal function.
set<ScFormulaCell*> aNewSet;
bool bOldRecalc = GetAutoCalc();
SetAutoCalc(false); for (ScFormulaCell* pCell : maSubTotalCells)
{ if (pCell->IsSubTotal())
{
aNewSet.insert(pCell); if (lcl_hasDirtyRange(*this, pCell, rDirtyRange))
pCell->SetDirty();
}
}
SetAutoCalc(bOldRecalc);
maSubTotalCells.swap(aNewSet); // update the list.
}
void ScDocument::EnableUndo( bool bVal )
{ // The undo manager increases lock count every time undo is disabled. // Because of this, we shouldn't disable undo unless it's currently // enabled, or else re-enabling it may not actually re-enable undo unless // the lock count becomes zero.
if (aState == CommentCaptionState::MIXED) return aState;
if (bFirstControl) // it is possible that a range is ALLSHOWN, another range is ALLHIDDEN,
{ // we have to detect that situation as mixed.
aTmpState = aState;
bFirstControl = false;
} elseif(aTmpState != aState)
{
aState = CommentCaptionState::MIXED; return aState;
}
}
} return aState;
}
ScRecursionHelper& ScDocument::GetRecursionHelper()
{ if (!IsThreadedGroupCalcInProgress())
{ if (!maNonThreaded.xRecursionHelper)
maNonThreaded.xRecursionHelper = std::make_unique<ScRecursionHelper>(); return *maNonThreaded.xRecursionHelper;
} else
{ if (!maThreadSpecific.xRecursionHelper)
maThreadSpecific.xRecursionHelper = std::make_unique<ScRecursionHelper>(); return *maThreadSpecific.xRecursionHelper;
}
}
void ScDocument::SetupContextFromNonThreadedContext(ScInterpreterContext& /*threadedContext*/, int /*threadNumber*/)
{
(void)this; // lookup cache is now only in pooled ScInterpreterContext's
}
void ScDocument::MergeContextBackIntoNonThreadedContext(ScInterpreterContext& threadedContext, int/*threadNumber*/)
{ // Move data from a context used by a calculation thread to the main thread's context. // Called from the main thread after the calculation thread has already finished.
assert(!IsThreadedGroupCalcInProgress());
maInterpreterContext.maDelayedSetNumberFormat.insert(
maInterpreterContext.maDelayedSetNumberFormat.end(),
std::make_move_iterator(threadedContext.maDelayedSetNumberFormat.begin()),
std::make_move_iterator(threadedContext.maDelayedSetNumberFormat.end())); // lookup cache is now only in pooled ScInterpreterContext's
threadedContext.MergeDefaultFormatKeys(*GetFormatTable());
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.188Bemerkung:
(vorverarbeitet am 2026-05-06)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.