/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using ::editeng::SvxBorderLine; using namespace ::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; namespace { std::pair getMarkedTableRange(const std::vector& rTables, const ScMarkData& rMark) { SCTAB nTabStart = MAXTAB; SCTAB nTabEnd = 0; SCTAB nMax = static_cast(rTables.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (!rTables[rTab]) continue; if (rTab < nTabStart) nTabStart = rTab; nTabEnd = rTab; } return std::pair(nTabStart,nTabEnd); } void collectUIInformation(std::map&& aParameters, const OUString& rAction) { EventDescription aDescription; aDescription.aID = "grid_window"; aDescription.aAction = rAction; aDescription.aParameters = std::move(aParameters); aDescription.aParent = "MainWindow"; aDescription.aKeyWord = "ScGridWinUIObject"; UITestLogger::getInstance().logEvent(aDescription); } struct ScDefaultAttr { const ScPatternAttr* pAttr; SCROW nFirst; SCSIZE nCount; explicit ScDefaultAttr(const ScPatternAttr* pPatAttr) : pAttr(pPatAttr), nFirst(0), nCount(0) {} }; struct ScLessDefaultAttr { bool operator() (const ScDefaultAttr& rValue1, const ScDefaultAttr& rValue2) const { return rValue1.pAttr < rValue2.pAttr; } }; } typedef std::set ScDefaultAttrSet; void ScDocument::MakeTable( SCTAB nTab,bool _bNeedsNameCheck ) { if ( !(ValidTab(nTab) && ( nTab >= static_cast(maTabs.size()) ||!maTabs[nTab])) ) return; // Get Custom prefix const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions(); OUString aString = rOpt.GetInitTabPrefix() + OUString::number(nTab+1); if ( _bNeedsNameCheck ) CreateValidTabName( aString ); // no doubles if (nTab < static_cast(maTabs.size())) { maTabs[nTab].reset( new ScTable(*this, nTab, aString) ); } else { while(nTab > static_cast(maTabs.size())) maTabs.push_back(nullptr); maTabs.emplace_back( new ScTable(*this, nTab, aString) ); } maTabs[nTab]->SetLoadingMedium(bLoadingMedium); } bool ScDocument::HasTable( SCTAB nTab ) const { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) if (maTabs[nTab]) return true; return false; } bool ScDocument::GetHashCode( SCTAB nTab, sal_Int64& rHashCode ) const { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) { if (maTabs[nTab]) { rHashCode = maTabs[nTab]->GetHashCode(); return true; } } return false; } bool ScDocument::GetName( SCTAB nTab, OUString& rName ) const { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) { if (maTabs[nTab]) { rName = maTabs[nTab]->GetName(); return true; } } rName.clear(); return false; } OUString ScDocument::GetCopyTabName( SCTAB nTab ) const { if (ValidTab(nTab) && nTab < static_cast(maTabNames.size())) return maTabNames[nTab]; return OUString(); } bool ScDocument::SetCodeName( SCTAB nTab, const OUString& rName ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) { if (maTabs[nTab]) { maTabs[nTab]->SetCodeName( rName ); return true; } } SAL_WARN("sc", "can't set code name " << rName ); return false; } bool ScDocument::GetCodeName( SCTAB nTab, OUString& rName ) const { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) if (maTabs[nTab]) { rName = maTabs[nTab]->GetCodeName(); return true; } rName.clear(); return false; } bool ScDocument::GetTable( const OUString& rName, SCTAB& rTab ) const { static OUString aCacheName, aCacheUpperName; assert(!IsThreadedGroupCalcInProgress()); if (aCacheName != rName) { aCacheName = rName; // surprisingly slow ... aCacheUpperName = ScGlobal::getCharClass().uppercase(rName); } const OUString aUpperName = aCacheUpperName; for (SCTAB i=0; i< static_cast(maTabs.size()); i++) if (maTabs[i]) { if (aUpperName == maTabs[i]->GetUpperName()) { rTab = i; return true; } } rTab = 0; return false; } std::vector ScDocument::GetAllTableNames() const { std::vector aNames; aNames.reserve(maTabs.size()); for (const auto& 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); } return aNames; } ScDBData* ScDocument::GetAnonymousDBData(SCTAB nTab) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) return maTabs[nTab]->GetAnonymousDBData(); return nullptr; } SCTAB ScDocument::GetTableCount() const { return static_cast(maTabs.size()); } void ScDocument::SetAnonymousDBData(SCTAB nTab, std::unique_ptr pDBData) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) maTabs[nTab]->SetAnonymousDBData(std::move(pDBData)); } void ScDocument::SetAnonymousDBData( std::unique_ptr pDBData ) { mpAnonymousDBData = std::move(pDBData); } ScDBData* ScDocument::GetAnonymousDBData() { return mpAnonymousDBData.get(); } bool ScDocument::ValidTabName( const OUString& rName ) { if (rName.isEmpty()) return false; sal_Int32 nLen = rName.getLength(); #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. return false; case '\'': if (i == 0 || i == nLen - 1) // single quote is not allowed at the first or last // character position. return false; break; } } #endif return true; } bool ScDocument::ValidNewTabName( const OUString& rName ) const { bool bValid = ValidTabName(rName); if (!bValid) return false; OUString aUpperName = ScGlobal::getCharClass().uppercase(rName); for (const auto& 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 // Get Custom prefix const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions(); const OUString& aStrTable = rOpt.GetInitTabPrefix(); bool bOk = false; // 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 = static_cast(maTabs.size())+1; !bOk ; i++ ) { rName = aStrTable + OUString::number(static_cast(i)); if (bPrefix) bOk = ValidNewTabName( rName ); else bOk = !GetTable( rName, nDummy ); } } else { // testing the supplied Name if ( !ValidNewTabName(rName) ) { SCTAB i = 1; OUStringBuffer aName; do { i++; aName = rName; aName.append('_'); aName.append(static_cast(i)); } while (!ValidNewTabName(aName.toString()) && (i < MAXTAB+1)); rName = aName.makeStringAndClear(); } } } void ScDocument::CreateValidTabNames(std::vector& aNames, SCTAB nCount) const { aNames.clear();//ensure that the vector is empty // Get Custom prefix const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions(); const OUString& aStrTable = rOpt.GetInitTabPrefix(); OUStringBuffer rName; // 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 = static_cast(maTabs.size())+1; for (SCTAB j = 0; j < nCount; ++j) { bool bOk = false; while(!bOk) { rName = aStrTable; rName.append(static_cast(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 = static_cast(maTabs.size()); if (!ValidTab(nTabCount)) // max table count reached. No more tables. return; OUString aName = rName; CreateValidTabName(aName); maTabs.emplace_back( new ScTable(*this, nTabCount, aName) ); } void ScDocument::SetTabNameOnLoad(SCTAB nTab, const OUString& rName) { if (!ValidTab(nTab) || static_cast(maTabs.size()) <= nTab) return; if (!ValidTabName(rName)) return; maTabs[nTab]->SetName(rName); } void ScDocument::InvalidateStreamOnSave() { for (const auto& a : maTabs) { if (a) a->SetStreamValid(false); } } bool ScDocument::InsertTab( SCTAB nPos, const OUString& rName, bool bExternalDocument, bool bUndoDeleteTab ) { SCTAB nTabCount = static_cast(maTabs.size()); 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); ScRange aRange( 0,0,nPos, MaxCol(),MaxRow(),MAXTAB ); xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 ); xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 ); if (pRangeName) pRangeName->UpdateInsertTab(aCxt); pDBCollection->UpdateReference( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 ); if (pDPCollection) pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,1 ); if (pDetOpList) pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,1 ); UpdateChartRef( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 ); UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,1 ); if ( pUnoBroadcaster ) pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,1 ) ); for (const auto& 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 (const auto& 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; aCxt.mbClearTabDeletedFlag = bUndoDeleteTab; aCxt.mnTabDeletedStart = nPos; aCxt.mnTabDeletedEnd = nPos; SetAllFormulasDirty(aCxt); if (comphelper::LibreOfficeKit::isActive() && GetDrawLayer()) { ScModelObj* pModel = comphelper::getFromUnoTunnel(this->GetDocumentShell()->GetModel()); SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel); } } return bValid; } bool ScDocument::InsertTabs( SCTAB nPos, const std::vector& rNames, bool bNamesValid ) { SCTAB nNewSheets = static_cast(rNames.size()); SCTAB nTabCount = static_cast(maTabs.size()); bool bValid = bNamesValid || ValidTab(nTabCount+nNewSheets); 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 (const auto& 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 (const auto& 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); } return bValid; } bool ScDocument::DeleteTab( SCTAB nTab ) { bool bValid = false; if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) { if (maTabs[nTab]) { SCTAB nTabCount = static_cast(maTabs.size()); if (nTabCount > 1) { sc::AutoCalcSwitch aACSwitch(*this, false); sc::RefUpdateDeleteTabContext aCxt( *this, nTab, 1); sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this); ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTab ); DelBroadcastAreasInRange( aRange ); // #i8180# remove database ranges etc. that are on the deleted tab // (restored in undo with ScRefUndoData) xColNameRanges->DeleteOnTab( nTab ); xRowNameRanges->DeleteOnTab( nTab ); pDBCollection->DeleteOnTab( nTab ); if (pDPCollection) pDPCollection->DeleteOnTab( nTab ); if (pDetOpList) pDetOpList->DeleteOnTab( nTab ); DeleteAreaLinksOnTab( nTab ); // normal reference update aRange.aEnd.SetTab( static_cast(maTabs.size())-1 ); xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1 ); xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1 ); if (pRangeName) pRangeName->UpdateDeleteTab(aCxt); pDBCollection->UpdateReference( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1 ); if (pDPCollection) pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,-1 ); if (pDetOpList) pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,-1 ); UpdateChartRef( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1 ); UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,-1 ); if (pValidationList) { ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE); pValidationList->UpdateDeleteTab(aCxt); } if ( pUnoBroadcaster ) pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,-1 ) ); 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 (const auto& 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(); sc::SetFormulaDirtyContext aFormulaDirtyCxt; SetAllFormulasDirty(aFormulaDirtyCxt); } if (comphelper::LibreOfficeKit::isActive()) { ScModelObj* pModel = comphelper::getFromUnoTunnel(this->GetDocumentShell()->GetModel()); SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel); } bValid = true; } } } return bValid; } bool ScDocument::DeleteTabs( SCTAB nTab, SCTAB nSheets ) { bool bValid = false; if (ValidTab(nTab) && (nTab + nSheets) <= static_cast(maTabs.size())) { if (maTabs[nTab]) { SCTAB nTabCount = static_cast(maTabs.size()); if (nTabCount > nSheets) { sc::AutoCalcSwitch aACSwitch(*this, false); sc::RefUpdateDeleteTabContext aCxt( *this, nTab, nSheets); sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this); for (SCTAB aTab = 0; aTab < nSheets; ++aTab) { ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTab + aTab ); DelBroadcastAreasInRange( aRange ); // #i8180# remove database ranges etc. that are on the deleted tab // (restored in undo with ScRefUndoData) xColNameRanges->DeleteOnTab( nTab + aTab ); xRowNameRanges->DeleteOnTab( nTab + aTab ); pDBCollection->DeleteOnTab( nTab + aTab ); if (pDPCollection) pDPCollection->DeleteOnTab( nTab + aTab ); if (pDetOpList) pDetOpList->DeleteOnTab( nTab + aTab ); DeleteAreaLinksOnTab( nTab + aTab ); } if (pRangeName) pRangeName->UpdateDeleteTab(aCxt); // normal reference update ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTabCount - 1 ); xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1*nSheets ); xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1*nSheets ); pDBCollection->UpdateReference( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1*nSheets ); if (pDPCollection) pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,-1*nSheets ); if (pDetOpList) pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,-1*nSheets ); UpdateChartRef( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1*nSheets ); UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,-1*nSheets ); if (pValidationList) { ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE); pValidationList->UpdateDeleteTab(aCxt); } if ( pUnoBroadcaster ) pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,-1*nSheets ) ); 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 (const auto& 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(); sc::SetFormulaDirtyContext aFormulaDirtyCxt; SetAllFormulasDirty(aFormulaDirtyCxt); } if (comphelper::LibreOfficeKit::isActive()) { ScModelObj* pModel = comphelper::getFromUnoTunnel(this->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 (ValidTab(nTab)) { if (maTabs[nTab]) { if ( bExternalDocument ) bValid = true; // composed name else bValid = ValidTabName(rName); for (i=0; (i< static_cast(maTabs.size())) && 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 (const auto& a : maTabs) { if (a) a->SetStreamValid( false ); } if (comphelper::LibreOfficeKit::isActive() && GetDrawLayer()) { ScModelObj* pModel = comphelper::getFromUnoTunnel(this->GetDocumentShell()->GetModel()); SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel); } } } } collectUIInformation({{"NewName", rName}}, "Rename_Sheet"); return bValid; } void ScDocument::SetVisible( SCTAB nTab, bool bVisible ) { if (ValidTab(nTab) && nTab < static_cast (maTabs.size())) if (maTabs[nTab]) maTabs[nTab]->SetVisible(bVisible); } bool ScDocument::IsVisible( SCTAB nTab ) const { if (ValidTab(nTab) && nTab < static_cast (maTabs.size())) if (maTabs[nTab]) return maTabs[nTab]->IsVisible(); return false; } bool ScDocument::IsStreamValid( SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->IsStreamValid(); return false; } void ScDocument::SetStreamValid( SCTAB nTab, bool bSet, bool bIgnoreLock ) { if ( ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->SetStreamValid( bSet, bIgnoreLock ); } void ScDocument::LockStreamValid( bool bLock ) { mbStreamValidLocked = bLock; } bool ScDocument::IsPendingRowHeights( SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->IsPendingRowHeights(); return false; } void ScDocument::SetPendingRowHeights( SCTAB nTab, bool bSet ) { if ( ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->SetPendingRowHeights( bSet ); } void ScDocument::SetLayoutRTL( SCTAB nTab, bool bRTL, ScObjectHandling eObjectHandling) { if ( !(ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab]) ) return; 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. maTabs[nTab]->SetLoadingRTL( bRTL ); return; } maTabs[nTab]->SetLayoutRTL( bRTL ); // only sets the flag maTabs[nTab]->SetDrawPageSize(true, true, eObjectHandling); // objects are already repositioned via SetDrawPageSize, only writing mode is missing if (!mpDrawLayer) return; SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); OSL_ENSURE(pPage,"Page ?"); if (!pPage) return; SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); SdrObject* pObject = aIter.Next(); while (pObject) { pObject->SetContextWritingMode( bRTL ? WritingMode2::RL_TB : WritingMode2::LR_TB ); pObject = aIter.Next(); } } bool ScDocument::IsLayoutRTL( SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->IsLayoutRTL(); return false; } 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 ---------------------------------------------------------------------------- */ bool ScDocument::GetCellArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow ) const { if (ValidTab(nTab) && nTab < static_cast (maTabs.size())) if (maTabs[nTab]) return maTabs[nTab]->GetCellArea( rEndCol, rEndRow ); rEndCol = 0; rEndRow = 0; return false; } bool ScDocument::GetTableArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow, bool bCalcHiddens) const { if (ValidTab(nTab) && nTab < static_cast (maTabs.size())) if (maTabs[nTab]) return maTabs[nTab]->GetTableArea( rEndCol, rEndRow, bCalcHiddens); rEndCol = 0; rEndRow = 0; return false; } bool ScDocument::ShrinkToDataArea(SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow) const { if (!ValidTab(nTab) || nTab >= static_cast (maTabs.size()) || !maTabs[nTab]) return false; SCCOL nCol1, nCol2; SCROW nRow1, nRow2; maTabs[nTab]->GetFirstDataPos(nCol1, nRow1); maTabs[nTab]->GetLastDataPos(nCol2, nRow2); if (nCol1 > nCol2 || nRow1 > nRow2) // invalid range. return false; // 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; if (rStartCol > rEndCol || rStartRow > rEndRow) // invalid range. return false; return true; // success! } bool ScDocument::ShrinkToUsedDataArea( bool& o_bShrunk, SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly, bool bStickyTopRow, bool bStickyLeftCol, ScDataAreaExtras* pDataAreaExtras ) const { if (!ValidTab(nTab) || nTab >= static_cast (maTabs.size()) || !maTabs[nTab]) { o_bShrunk = false; return false; } return maTabs[nTab]->ShrinkToUsedDataArea( o_bShrunk, rStartCol, rStartRow, rEndCol, rEndRow, bColumnsOnly, bStickyTopRow, bStickyLeftCol, pDataAreaExtras); } SCROW ScDocument::GetLastDataRow( SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nLastRow ) const { const ScTable* pTab = FetchTable(nTab); if (!pTab) return -1; return pTab->GetLastDataRow(nCol1, nCol2, nLastRow); } // connected area void ScDocument::GetDataArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow, bool bIncludeOld, bool bOnlyDown ) const { if (ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab]) maTabs[nTab]->GetDataArea( rStartCol, rStartRow, rEndCol, rEndRow, bIncludeOld, bOnlyDown ); } bool ScDocument::GetDataAreaSubrange(ScRange& rRange) const { SCTAB nTab = rRange.aStart.Tab(); if (nTab != rRange.aEnd.Tab()) return true; if (ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab]) return maTabs[nTab]->GetDataAreaSubrange(rRange); return true; } void ScDocument::LimitChartArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow ) { if (ValidTab(nTab) && nTab < static_cast (maTabs.size())) if (maTabs[nTab]) maTabs[nTab]->LimitChartArea( rStartCol, rStartRow, rEndCol, rEndRow ); } void ScDocument::LimitChartIfAll( ScRangeListRef& rRangeList ) { ScRangeListRef aNew = new ScRangeList; if (rRangeList.is()) { for ( size_t i = 0, nCount = rRangeList->size(); i < nCount; i++ ) { ScRange aRange( (*rRangeList)[i] ); if ( ( aRange.aStart.Col() == 0 && aRange.aEnd.Col() == MaxCol() ) || ( aRange.aStart.Row() == 0 && aRange.aEnd.Row() == MaxRow() ) ) { SCCOL nStartCol = aRange.aStart.Col(); SCROW nStartRow = aRange.aStart.Row(); SCCOL nEndCol = aRange.aEnd.Col(); SCROW nEndRow = aRange.aEnd.Row(); SCTAB nTab = aRange.aStart.Tab(); if ( nTab < static_cast (maTabs.size()) && maTabs[nTab]) maTabs[nTab]->LimitChartArea(nStartCol, nStartRow, nEndCol, nEndRow); aRange.aStart.SetCol( nStartCol ); aRange.aStart.SetRow( nStartRow ); aRange.aEnd.SetCol( nEndCol ); aRange.aEnd.SetRow( nEndRow ); } aNew->push_back(aRange); } } else { OSL_FAIL("LimitChartIfAll: Ref==0"); } rRangeList = aNew; } static void lcl_GetFirstTabRange( SCTAB& rTabRangeStart, SCTAB& rTabRangeEnd, const ScMarkData* pTabMark, SCTAB aMaxTab ) { // without ScMarkData, leave start/end unchanged if ( !pTabMark ) return; for (SCTAB nTab=0; nTab< aMaxTab; ++nTab) if (pTabMark->GetTableSelect(nTab)) { // find first range of consecutive selected sheets rTabRangeStart = pTabMark->GetFirstSelected(); while ( nTab+1 < aMaxTab && pTabMark->GetTableSelect(nTab+1) ) ++nTab; rTabRangeEnd = nTab; return; } } static bool lcl_GetNextTabRange( SCTAB& rTabRangeStart, SCTAB& rTabRangeEnd, const ScMarkData* pTabMark, SCTAB aMaxTab ) { if ( pTabMark ) { // find next range of consecutive selected sheets after rTabRangeEnd for (SCTAB nTab=rTabRangeEnd+1; nTab< aMaxTab; ++nTab) if (pTabMark->GetTableSelect(nTab)) { rTabRangeStart = nTab; while ( nTab+1 < aMaxTab && pTabMark->GetTableSelect(nTab+1) ) ++nTab; rTabRangeEnd = nTab; return true; } } return false; } bool ScDocument::CanInsertRow( const ScRange& rRange ) const { SCCOL nStartCol = rRange.aStart.Col(); SCROW nStartRow = rRange.aStart.Row(); SCTAB nStartTab = rRange.aStart.Tab(); SCCOL nEndCol = rRange.aEnd.Col(); SCROW nEndRow = rRange.aEnd.Row(); SCTAB nEndTab = rRange.aEnd.Tab(); PutInOrder( nStartCol, nEndCol ); PutInOrder( nStartRow, nEndRow ); PutInOrder( nStartTab, nEndTab ); SCSIZE nSize = static_cast(nEndRow - nStartRow + 1); bool bTest = true; for (SCTAB i=nStartTab; i<=nEndTab && bTest && i < static_cast(maTabs.size()); i++) if (maTabs[i]) bTest &= maTabs[i]->TestInsertRow( nStartCol, nEndCol, nStartRow, nSize ); return bTest; } namespace { struct SetDirtyIfPostponedHandler { void operator() (const ScTableUniquePtr & p) { if (p) p->SetDirtyIfPostponed(); } }; struct BroadcastRecalcOnRefMoveHandler { void operator() (const ScTableUniquePtr & p) { if (p) p->BroadcastRecalcOnRefMove(); } }; struct BroadcastRecalcOnRefMoveGuard { explicit BroadcastRecalcOnRefMoveGuard( ScDocument* pDoc ) : aSwitch( *pDoc, false), aBulk( pDoc->GetBASM(), SfxHintId::ScDataChanged) { } private: sc::AutoCalcSwitch aSwitch; // first for ctor/dtor order, destroy second ScBulkBroadcast aBulk; // second for ctor/dtor order, destroy first }; } bool ScDocument::InsertRow( SCCOL nStartCol, SCTAB nStartTab, SCCOL nEndCol, SCTAB nEndTab, SCROW nStartRow, SCSIZE nSize, ScDocument* pRefUndoDoc, const ScMarkData* pTabMark ) { SCTAB i; PutInOrder( nStartCol, nEndCol ); PutInOrder( nStartTab, nEndTab ); if ( pTabMark ) { nStartTab = 0; nEndTab = static_cast(maTabs.size()) -1; } 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 < static_cast(maTabs.size()); 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 // handle chunks of consecutive selected sheets together SCTAB nTabRangeStart = nStartTab; SCTAB nTabRangeEnd = nEndTab; lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); ScRange aShiftedRange(nStartCol, nStartRow, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd); sc::EndListeningContext aEndListenCxt(*this); std::vector 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); UpdateBroadcastAreas(URM_INSDEL, aShiftedRange, 0, static_cast(nSize), 0); } while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); 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, static_cast(maTabs.size()) ) ); // 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 < static_cast(maTabs.size()); i++) if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i))) maTabs[i]->InsertRow( nStartCol, nEndCol, nStartRow, nSize ); // UpdateRef for drawing layer must be after inserting, // when the new row heights are known. for (i=nStartTab; i<=nEndTab && i < static_cast(maTabs.size()); i++) if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i))) maTabs[i]->UpdateDrawRef( URM_INSDEL, nStartCol, nStartRow, nStartTab, nEndCol, MaxRow(), nEndTab, 0, static_cast(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 (const auto& a : maTabs) { if (a) a->SetDirtyIfPostponed(); } { BroadcastRecalcOnRefMoveGuard g(this); std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler()); } } bRet = true; } EnableDelayDeletingBroadcasters( oldDelayedDeleteBroadcasters ); SetAutoCalc( bOldAutoCalc ); if ( bRet && pChartListenerCollection ) pChartListenerCollection->UpdateDirtyCharts(); return bRet; } bool ScDocument::InsertRow( const ScRange& rRange ) { return InsertRow( rRange.aStart.Col(), rRange.aStart.Tab(), rRange.aEnd.Col(), rRange.aEnd.Tab(), rRange.aStart.Row(), static_cast(rRange.aEnd.Row()-rRange.aStart.Row()+1) ); } void ScDocument::DeleteRow( SCCOL nStartCol, SCTAB nStartTab, SCCOL nEndCol, SCTAB nEndTab, SCROW nStartRow, SCSIZE nSize, ScDocument* pRefUndoDoc, bool* pUndoOutline, const ScMarkData* pTabMark ) { SCTAB i; PutInOrder( nStartCol, nEndCol ); PutInOrder( nStartTab, nEndTab ); if ( pTabMark ) { nStartTab = 0; nEndTab = static_cast(maTabs.size())-1; } sc::AutoCalcSwitch aACSwitch(*this, false); // avoid multiple calculations // handle chunks of consecutive selected sheets together SCTAB nTabRangeStart = nStartTab; SCTAB nTabRangeEnd = nEndTab; lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); do { if ( ValidRow(nStartRow+nSize) ) { DelBroadcastAreasInRange( ScRange( ScAddress( nStartCol, nStartRow, nTabRangeStart ), ScAddress( nEndCol, nStartRow+nSize-1, nTabRangeEnd ) ) ); UpdateBroadcastAreas( URM_INSDEL, ScRange( ScAddress( nStartCol, nStartRow+nSize, nTabRangeStart ), ScAddress( nEndCol, MaxRow(), nTabRangeEnd )), 0, -static_cast(nSize), 0 ); } else DelBroadcastAreasInRange( ScRange( ScAddress( nStartCol, nStartRow, nTabRangeStart ), ScAddress( nEndCol, MaxRow(), nTabRangeEnd ) ) ); } while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); sc::RefUpdateContext aCxt(*this); const bool bLastRowIncluded = (static_cast(nStartRow + nSize) == GetMaxRowCount() && ValidRow(nStartRow)); if ( ValidRow(nStartRow+nSize) || bLastRowIncluded ) { lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); aCxt.meMode = URM_INSDEL; aCxt.mnRowDelta = -static_cast(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, static_cast(maTabs.size()) ) ); } if (pUndoOutline) *pUndoOutline = false; // Keep track of the positions of all formula groups that have been joined // during row deletion. std::vector aGroupPos; for ( i = nStartTab; i <= nEndTab && i < static_cast(maTabs.size()); i++) if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i))) maTabs[i]->DeleteRow(aCxt.maRegroupCols, nStartCol, nEndCol, nStartRow, nSize, pUndoOutline, &aGroupPos); // 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 (const auto& a : maTabs) { if (a) a->SetDirtyIfPostponed(); } { BroadcastRecalcOnRefMoveGuard g(this); std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler()); } } if (pChartListenerCollection) pChartListenerCollection->UpdateDirtyCharts(); } void ScDocument::DeleteRow( const ScRange& rRange ) { DeleteRow( rRange.aStart.Col(), rRange.aStart.Tab(), rRange.aEnd.Col(), rRange.aEnd.Tab(), rRange.aStart.Row(), static_cast(rRange.aEnd.Row()-rRange.aStart.Row()+1) ); } bool ScDocument::CanInsertCol( const ScRange& rRange ) const { SCCOL nStartCol = rRange.aStart.Col(); SCROW nStartRow = rRange.aStart.Row(); SCTAB nStartTab = rRange.aStart.Tab(); SCCOL nEndCol = rRange.aEnd.Col(); SCROW nEndRow = rRange.aEnd.Row(); SCTAB nEndTab = rRange.aEnd.Tab(); PutInOrder( nStartCol, nEndCol ); PutInOrder( nStartRow, nEndRow ); PutInOrder( nStartTab, nEndTab ); SCSIZE nSize = static_cast(nEndCol - nStartCol + 1); bool bTest = true; for (SCTAB i=nStartTab; i<=nEndTab && bTest && i < static_cast(maTabs.size()); i++) if (maTabs[i]) bTest &= maTabs[i]->TestInsertCol( nStartRow, nEndRow, nSize ); return bTest; } bool ScDocument::InsertCol( SCROW nStartRow, SCTAB nStartTab, SCROW nEndRow, SCTAB nEndTab, SCCOL nStartCol, SCSIZE nSize, ScDocument* pRefUndoDoc, const ScMarkData* pTabMark ) { SCTAB i; PutInOrder( nStartRow, nEndRow ); PutInOrder( nStartTab, nEndTab ); if ( pTabMark ) { nStartTab = 0; nEndTab = static_cast(maTabs.size())-1; } 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 < static_cast(maTabs.size()); i++) if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i))) bTest &= maTabs[i]->TestInsertCol( nStartRow, nEndRow, nSize ); if (bTest) { // handle chunks of consecutive selected sheets together SCTAB nTabRangeStart = nStartTab; SCTAB nTabRangeEnd = nEndTab; lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); do { UpdateBroadcastAreas( URM_INSDEL, ScRange( ScAddress( nStartCol, nStartRow, nTabRangeStart ), ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), static_cast(nSize), 0, 0 ); } while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); sc::RefUpdateContext aCxt(*this); aCxt.meMode = URM_INSDEL; aCxt.maRange = ScRange(nStartCol, nStartRow, nTabRangeStart, MaxCol(), nEndRow, nTabRangeEnd); aCxt.mnColDelta = nSize; do { UpdateReference(aCxt, pRefUndoDoc, true, false); } while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); for (i=nStartTab; i<=nEndTab && i < static_cast(maTabs.size()); 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; } bool ScDocument::InsertCol( const ScRange& rRange ) { return InsertCol( rRange.aStart.Row(), rRange.aStart.Tab(), rRange.aEnd.Row(), rRange.aEnd.Tab(), rRange.aStart.Col(), static_cast(rRange.aEnd.Col()-rRange.aStart.Col()+1) ); } void ScDocument::DeleteCol(SCROW nStartRow, SCTAB nStartTab, SCROW nEndRow, SCTAB nEndTab, SCCOL nStartCol, SCSIZE nSize, ScDocument* pRefUndoDoc, bool* pUndoOutline, const ScMarkData* pTabMark ) { SCTAB i; PutInOrder( nStartRow, nEndRow ); PutInOrder( nStartTab, nEndTab ); if ( pTabMark ) { nStartTab = 0; nEndTab = static_cast(maTabs.size())-1; } sc::AutoCalcSwitch aACSwitch(*this, false); // avoid multiple calculations ScBulkBroadcast aBulkBroadcast(GetBASM(), SfxHintId::ScDataChanged); // handle chunks of consecutive selected sheets together SCTAB nTabRangeStart = nStartTab; SCTAB nTabRangeEnd = nEndTab; lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); do { if ( ValidCol(sal::static_int_cast(nStartCol+nSize)) ) { DelBroadcastAreasInRange( ScRange( ScAddress( nStartCol, nStartRow, nTabRangeStart ), ScAddress( sal::static_int_cast(nStartCol+nSize-1), nEndRow, nTabRangeEnd ) ) ); UpdateBroadcastAreas( URM_INSDEL, ScRange( ScAddress( sal::static_int_cast(nStartCol+nSize), nStartRow, nTabRangeStart ), ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), -static_cast(nSize), 0, 0 ); } else DelBroadcastAreasInRange( ScRange( ScAddress( nStartCol, nStartRow, nTabRangeStart ), ScAddress( MaxCol(), nEndRow, nTabRangeEnd ) ) ); } while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); sc::RefUpdateContext aCxt(*this); const bool bLastColIncluded = (static_cast(nStartCol + nSize) == GetMaxColCount() && ValidCol(nStartCol)); if ( ValidCol(sal::static_int_cast(nStartCol+nSize)) || bLastColIncluded ) { lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); aCxt.meMode = URM_INSDEL; aCxt.mnColDelta = -static_cast(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(nStartCol+nSize), nStartRow, nTabRangeStart, MaxCol(), nEndRow, nTabRangeEnd); } do { UpdateReference(aCxt, pRefUndoDoc, true, false); } while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); } if (pUndoOutline) *pUndoOutline = false; for (i = nStartTab; i <= nEndTab && i < static_cast(maTabs.size()); ++i) { if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i))) maTabs[i]->DeleteCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize, pUndoOutline); } if ( ValidCol(sal::static_int_cast(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 (const auto& a : maTabs) { if (a) a->SetDirtyIfPostponed(); } { BroadcastRecalcOnRefMoveGuard g(this); std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler()); } } if (pChartListenerCollection) pChartListenerCollection->UpdateDirtyCharts(); } void ScDocument::DeleteCol( const ScRange& rRange ) { DeleteCol( rRange.aStart.Row(), rRange.aStart.Tab(), rRange.aEnd.Row(), rRange.aEnd.Tab(), rRange.aStart.Col(), static_cast(rRange.aEnd.Col()-rRange.aStart.Col()+1) ); } // for Area-Links: Insert/delete cells, when the range is changed. // (without Paint) static void lcl_GetInsDelRanges( const ScRange& rOld, const ScRange& rNew, ScRange& rColRange, bool& rInsCol, bool& rDelCol, ScRange& rRowRange, bool& rInsRow, bool& rDelRow ) { OSL_ENSURE( rOld.aStart == rNew.aStart, "FitBlock: Beginning is different" ); rInsCol = rDelCol = rInsRow = rDelRow = false; SCCOL nStartX = rOld.aStart.Col(); SCROW nStartY = rOld.aStart.Row(); SCCOL nOldEndX = rOld.aEnd.Col(); SCROW nOldEndY = rOld.aEnd.Row(); SCCOL nNewEndX = rNew.aEnd.Col(); SCROW nNewEndY = rNew.aEnd.Row(); SCTAB nTab = rOld.aStart.Tab(); // if more rows, columns are inserted/deleted at the old height. bool bGrowY = ( nNewEndY > nOldEndY ); SCROW nColEndY = bGrowY ? nOldEndY : nNewEndY; SCCOL nRowEndX = bGrowY ? nNewEndX : nOldEndX; // Columns if ( nNewEndX > nOldEndX ) // Insert columns { rColRange = ScRange( nOldEndX+1, nStartY, nTab, nNewEndX, nColEndY, nTab ); rInsCol = true; } else if ( nNewEndX < nOldEndX ) // Delete columns { rColRange = ScRange( nNewEndX+1, nStartY, nTab, nOldEndX, nColEndY, nTab ); rDelCol = true; } // Rows if ( nNewEndY > nOldEndY ) // Insert rows { rRowRange = ScRange( nStartX, nOldEndY+1, nTab, nRowEndX, nNewEndY, nTab ); rInsRow = true; } else if ( nNewEndY < nOldEndY ) // Delete rows { rRowRange = ScRange( nStartX, nNewEndY+1, nTab, nRowEndX, nOldEndY, nTab ); rDelRow = true; } } bool ScDocument::HasPartOfMerged( const ScRange& rRange ) { bool bPart = false; SCTAB nTab = rRange.aStart.Tab(); SCCOL nStartX = rRange.aStart.Col(); SCROW nStartY = rRange.aStart.Row(); SCCOL nEndX = rRange.aEnd.Col(); SCROW nEndY = rRange.aEnd.Row(); if (HasAttrib( nStartX, nStartY, nTab, nEndX, nEndY, nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped )) { ExtendMerge( nStartX, nStartY, nEndX, nEndY, nTab ); ExtendOverlapped( nStartX, nStartY, nEndX, nEndY, nTab ); bPart = ( nStartX != rRange.aStart.Col() || nEndX != rRange.aEnd.Col() || nStartY != rRange.aStart.Row() || nEndY != rRange.aEnd.Row() ); } return bPart; } formula::FormulaTokenRef ScDocument::ResolveStaticReference( const ScAddress& rPos ) { SCTAB nTab = rPos.Tab(); if (!TableExists(nTab)) return formula::FormulaTokenRef(); return maTabs[nTab]->ResolveStaticReference(rPos.Col(), rPos.Row()); } formula::FormulaTokenRef ScDocument::ResolveStaticReference( const ScRange& rRange ) { SCTAB nTab = rRange.aStart.Tab(); if (nTab != rRange.aEnd.Tab() || !TableExists(nTab)) return formula::FormulaTokenRef(); return maTabs[nTab]->ResolveStaticReference( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()); } formula::VectorRefArray ScDocument::FetchVectorRefArray( const ScAddress& rPos, SCROW nLength ) { SCTAB nTab = rPos.Tab(); if (!TableExists(nTab)) return formula::VectorRefArray(); return maTabs[nTab]->FetchVectorRefArray(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1); } #ifdef DBG_UTIL void ScDocument::AssertNoInterpretNeeded( const ScAddress& rPos, SCROW nLength ) { SCTAB nTab = rPos.Tab(); assert(TableExists(nTab)); return maTabs[nTab]->AssertNoInterpretNeeded(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1); } #endif void ScDocument::UnlockAdjustHeight() { assert(nAdjustHeightLock > 0); if(nAdjustHeightLock > 0) --nAdjustHeightLock; } bool ScDocument::HandleRefArrayForParallelism( const ScAddress& rPos, SCROW nLength, const ScFormulaCellGroupRef& mxGroup ) { SCTAB nTab = rPos.Tab(); if (!TableExists(nTab)) return false; return maTabs[nTab]->HandleRefArrayForParallelism(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1, mxGroup); } bool ScDocument::CanFitBlock( const ScRange& rOld, const ScRange& rNew ) { if ( rOld == rNew ) return true; bool bOk = true; bool bInsCol,bDelCol,bInsRow,bDelRow; ScRange aColRange,aRowRange; lcl_GetInsDelRanges( rOld, rNew, aColRange,bInsCol,bDelCol, aRowRange,bInsRow,bDelRow ); 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; } return bOk; } void ScDocument::FitBlock( const ScRange& rOld, const ScRange& rNew, bool bClear ) { if (bClear) DeleteAreaTab( rOld, InsertDeleteFlags::ALL ); bool bInsCol,bDelCol,bInsRow,bDelRow; ScRange aColRange,aRowRange; lcl_GetInsDelRanges( rOld, rNew, aColRange,bInsCol,bDelCol, aRowRange,bInsRow,bDelRow ); if ( bInsCol ) InsertCol( aColRange ); // First insert columns if ( bInsRow ) InsertRow( aRowRange ); if ( bDelRow ) DeleteRow( aRowRange ); // First delete rows if ( bDelCol ) DeleteCol( aColRange ); // Expand references to inserted rows if ( bInsCol || bInsRow ) { ScRange aGrowSource = rOld; aGrowSource.aEnd.SetCol(std::min( rOld.aEnd.Col(), rNew.aEnd.Col() )); aGrowSource.aEnd.SetRow(std::min( rOld.aEnd.Row(), rNew.aEnd.Row() )); SCCOL nGrowX = bInsCol ? ( rNew.aEnd.Col() - rOld.aEnd.Col() ) : 0; SCROW nGrowY = bInsRow ? ( rNew.aEnd.Row() - rOld.aEnd.Row() ) : 0; UpdateGrow( aGrowSource, nGrowX, nGrowY ); } } void ScDocument::DeleteArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark, InsertDeleteFlags nDelFlag, bool bBroadcast, sc::ColumnSpanSet* pBroadcastSpans ) { sc::AutoCalcSwitch aACSwitch(*this, false); PutInOrder( nCol1, nCol2 ); PutInOrder( nRow1, nRow2 ); std::vector 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 < static_cast(maTabs.size()); i++) { if (rMark.GetTableSelect(i)) { aRange.aStart.SetTab(i); aRange.aEnd.SetTab(i); EndListeningIntersectedGroups(aCxt, aRange, &aGroupPos); } } aCxt.purgeEmptyBroadcasters(); } for (SCTAB i = 0; i < static_cast(maTabs.size()); 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 < static_cast(maTabs.size()); i++) { if (rMark.GetTableSelect(i)) { aRange.aStart.SetTab(i); aRange.aEnd.SetTab(i); SetDirty( aRange, true); } } } void ScDocument::DeleteAreaTab(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, SCTAB nTab, InsertDeleteFlags nDelFlag) { PutInOrder( nCol1, nCol2 ); PutInOrder( nRow1, nRow2 ); if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) { bool bOldAutoCalc = GetAutoCalc(); SetAutoCalc( false ); // avoid multiple calculations maTabs[nTab]->DeleteArea(nCol1, nRow1, nCol2, nRow2, nDelFlag); SetAutoCalc( bOldAutoCalc ); } } void ScDocument::DeleteAreaTab( const ScRange& rRange, InsertDeleteFlags nDelFlag ) { for ( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); nTab++ ) DeleteAreaTab( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), nTab, nDelFlag ); } void ScDocument::InitUndoSelected(const ScDocument& rSrcDoc, const ScMarkData& rTabSelection, bool bColInfo, bool bRowInfo ) { if (bIsUndo) { Clear(); SharePooledResources(&rSrcDoc); for (SCTAB nTab = 0; nTab <= rTabSelection.GetLastSelected(); nTab++) if ( rTabSelection.GetTableSelect( nTab ) ) { ScTableUniquePtr pTable(new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo)); if (nTab < static_cast(maTabs.size())) maTabs[nTab] = std::move(pTable); else maTabs.push_back(std::move(pTable)); } else { if (nTab < static_cast(maTabs.size())) maTabs[nTab]=nullptr; else maTabs.push_back(nullptr); } } else { OSL_FAIL("InitUndo"); } } void ScDocument::InitUndo( const ScDocument& rSrcDoc, SCTAB nTab1, SCTAB nTab2, bool bColInfo, bool bRowInfo ) { if (!bIsUndo) { OSL_FAIL("InitUndo"); return; } Clear(); // Undo document shares its pooled resources with the source document. SharePooledResources(&rSrcDoc); if (rSrcDoc.mpShell->GetMedium()) maFileURL = rSrcDoc.mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri); if ( nTab2 >= static_cast(maTabs.size())) maTabs.resize(nTab2 + 1); for (SCTAB nTab = nTab1; nTab <= nTab2; nTab++) { maTabs[nTab].reset(new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo)); } } void ScDocument::AddUndoTab( SCTAB nTab1, SCTAB nTab2, bool bColInfo, bool bRowInfo ) { if (!bIsUndo) { OSL_FAIL("AddUndoTab"); return; } if (nTab2 >= static_cast(maTabs.size())) { maTabs.resize(nTab2+1); } for (SCTAB nTab = nTab1; nTab <= nTab2; nTab++) if (!maTabs[nTab]) { maTabs[nTab].reset( new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo) ); } } void ScDocument::SetCutMode( bool bVal ) { if (bIsClip) GetClipParam().mbCutMode = bVal; else { OSL_FAIL("SetCutMode without bIsClip"); } } bool ScDocument::IsCutMode() { if (bIsClip) return GetClipParam().mbCutMode; else { OSL_FAIL("IsCutMode without bIsClip"); return false; } } void ScDocument::CopyToDocument(SCCOL nCol1, SCROW nRow1, SCTAB nTab1, SCCOL nCol2, SCROW nRow2, SCTAB nTab2, InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc, const ScMarkData* pMarks, bool bColRowFlags ) { if (ValidTab(nTab1) && ValidTab(nTab2)) { ScRange aThisRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); CopyToDocument(aThisRange, nFlags, bOnlyMarked, rDestDoc, pMarks, bColRowFlags); } } void ScDocument::UndoToDocument(SCCOL nCol1, SCROW nRow1, SCTAB nTab1, SCCOL nCol2, SCROW nRow2, SCTAB nTab2, InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc) { PutInOrder( nCol1, nCol2 ); PutInOrder( nRow1, nRow2 ); PutInOrder( nTab1, nTab2 ); if (!(ValidTab(nTab1) && ValidTab(nTab2))) return; sc::AutoCalcSwitch aACSwitch(rDestDoc, false); // avoid multiple calculations if (nTab1 > 0) CopyToDocument(0, 0, 0, MaxCol(), MaxRow(), nTab1-1, InsertDeleteFlags::FORMULA, false, rDestDoc); sc::CopyToDocContext aCxt(rDestDoc); assert( nTab2 < static_cast(maTabs.size()) && nTab2 < static_cast(rDestDoc.maTabs.size())); for (SCTAB i = nTab1; i <= nTab2; i++) { if (maTabs[i] && rDestDoc.maTabs[i]) maTabs[i]->UndoToTable(aCxt, nCol1, nRow1, nCol2, nRow2, nFlags, bOnlyMarked, rDestDoc.maTabs[i].get()); } if (nTab2 < MAXTAB) CopyToDocument(0, 0, nTab2+1, MaxCol(), MaxRow(), MAXTAB, InsertDeleteFlags::FORMULA, false, rDestDoc); } void ScDocument::CopyToDocument(const ScRange& rRange, InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc, const ScMarkData* pMarks, bool bColRowFlags) { ScRange aNewRange = rRange; aNewRange.PutInOrder(); if (rDestDoc.aDocName.isEmpty()) rDestDoc.aDocName = aDocName; sc::AutoCalcSwitch aACSwitch(rDestDoc, false); // avoid multiple calculations ScBulkBroadcast aBulkBroadcast(rDestDoc.GetBASM(), SfxHintId::ScDataChanged); sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this); sc::CopyToDocContext aCxt(rDestDoc); aCxt.setStartListening(false); SCTAB nMinSizeBothTabs = static_cast(std::min(maTabs.size(), rDestDoc.maTabs.size())); for (SCTAB i = aNewRange.aStart.Tab(); i <= aNewRange.aEnd.Tab() && i < nMinSizeBothTabs; i++) { ScTable* pTab = FetchTable(i); ScTable* pDestTab = rDestDoc.FetchTable(i); if (!pTab || !pDestTab) continue; pTab->CopyToTable( aCxt, aNewRange.aStart.Col(), aNewRange.aStart.Row(), aNewRange.aEnd.Col(), aNewRange.aEnd.Row(), nFlags, bOnlyMarked, pDestTab, pMarks, false, bColRowFlags, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true); } rDestDoc.StartAllListeners(aNewRange); } void ScDocument::UndoToDocument(const ScRange& rRange, InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc) { sc::AutoCalcSwitch aAutoCalcSwitch(*this, false); ScRange aNewRange = rRange; aNewRange.PutInOrder(); SCTAB nTab1 = aNewRange.aStart.Tab(); SCTAB nTab2 = aNewRange.aEnd.Tab(); sc::CopyToDocContext aCxt(rDestDoc); if (nTab1 > 0) CopyToDocument(0, 0, 0, MaxCol(), MaxRow(), nTab1-1, InsertDeleteFlags::FORMULA, false, rDestDoc); SCTAB nMinSizeBothTabs = static_cast(std::min(maTabs.size(), rDestDoc.maTabs.size())); for (SCTAB i = nTab1; i <= nTab2 && i < nMinSizeBothTabs; i++) { if (maTabs[i] && rDestDoc.maTabs[i]) maTabs[i]->UndoToTable(aCxt, aNewRange.aStart.Col(), aNewRange.aStart.Row(), aNewRange.aEnd.Col(), aNewRange.aEnd.Row(), nFlags, bOnlyMarked, rDestDoc.maTabs[i].get()); } if (nTab2 < static_cast(maTabs.size())) CopyToDocument(0, 0 , nTab2+1, MaxCol(), MaxRow(), maTabs.size(), InsertDeleteFlags::FORMULA, false, rDestDoc); } void ScDocument::CopyToClip(const ScClipParam& rClipParam, ScDocument* pClipDoc, const ScMarkData* pMarks, bool bKeepScenarioFlags, bool bIncludeObjects ) { OSL_ENSURE( pMarks, "CopyToClip: ScMarkData fails" ); if (bIsClip) return; 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(); } //init maTabNames for (const auto& rxTab : maTabs) { if( rxTab ) { OUString aTabName = rxTab->GetName(); pClipDoc->maTabNames.push_back(aTabName); } else pClipDoc->maTabNames.emplace_back(); } pClipDoc->aDocName = aDocName; pClipDoc->SetClipParam(rClipParam); ScRange aClipRange = rClipParam.getWholeRange(); SCTAB nEndTab = static_cast(maTabs.size()); pClipDoc->ResetClip(this, pMarks); sc::CopyToClipContext aCxt(*pClipDoc, bKeepScenarioFlags); CopyRangeNamesToClip(pClipDoc, aClipRange, pMarks); for (SCTAB i = 0; i < nEndTab; ++i) { if (!maTabs[i] || i >= static_cast(pClipDoc->maTabs.size()) || !pClipDoc->maTabs[i]) continue; if ( pMarks && !pMarks->GetTableSelect(i) ) continue; maTabs[i]->CopyToClip(aCxt, rClipParam.maRanges, pClipDoc->maTabs[i].get()); if (mpDrawLayer && bIncludeObjects) { // also copy drawing objects 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); } void ScDocument::CopyStaticToDocument(const ScRange& rSrcRange, SCTAB nDestTab, ScDocument& rDestDoc) { ScTable* pSrcTab = rSrcRange.aStart.Tab() < static_cast(maTabs.size()) ? maTabs[rSrcRange.aStart.Tab()].get() : nullptr; ScTable* pDestTab = nDestTab < static_cast(rDestDoc.maTabs.size()) ? rDestDoc.maTabs[nDestTab].get() : nullptr; if (!pSrcTab || !pDestTab) return; rDestDoc.GetFormatTable()->MergeFormatter(*GetFormatTable()); SvNumberFormatterMergeMap aMap = rDestDoc.GetFormatTable()->ConvertMergeTableToMap(); pSrcTab->CopyStaticToDocument( rSrcRange.aStart.Col(), rSrcRange.aStart.Row(), rSrcRange.aEnd.Col(), rSrcRange.aEnd.Row(), aMap, pDestTab); } void ScDocument::CopyCellToDocument( const ScAddress& rSrcPos, const ScAddress& rDestPos, ScDocument& rDestDoc ) { if (!TableExists(rSrcPos.Tab()) || !rDestDoc.TableExists(rDestPos.Tab())) return; ScTable& rSrcTab = *maTabs[rSrcPos.Tab()]; ScTable& rDestTab = *rDestDoc.maTabs[rDestPos.Tab()]; rSrcTab.CopyCellToDocument(rSrcPos.Col(), rSrcPos.Row(), rDestPos.Col(), rDestPos.Row(), rDestTab); } void ScDocument::CopyTabToClip(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, SCTAB nTab, ScDocument* pClipDoc) { if (bIsClip) return; 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(); } //init maTabNames for (const auto& rxTab : maTabs) { if( rxTab ) { OUString aTabName = rxTab->GetName(); pClipDoc->maTabNames.push_back(aTabName); } else pClipDoc->maTabNames.emplace_back(); } PutInOrder( nCol1, nCol2 ); PutInOrder( nRow1, nRow2 ); ScClipParam& rClipParam = pClipDoc->GetClipParam(); pClipDoc->aDocName = aDocName; rClipParam.maRanges.RemoveAll(); rClipParam.maRanges.push_back(ScRange(nCol1, nRow1, 0, nCol2, nRow2, 0)); pClipDoc->ResetClip( this, nTab ); sc::CopyToClipContext aCxt(*pClipDoc, false); if (nTab < static_cast(maTabs.size()) && nTab < static_cast(pClipDoc->maTabs.size())) if (maTabs[nTab] && pClipDoc->maTabs[nTab]) maTabs[nTab]->CopyToClip(aCxt, nCol1, nRow1, nCol2, nRow2, pClipDoc->maTabs[nTab].get()); pClipDoc->GetClipParam().mbCutMode = false; } void ScDocument::TransposeClip(ScDocument* pTransClip, InsertDeleteFlags nFlags, bool bAsLink, bool bIncludeFiltered) { OSL_ENSURE( bIsClip && pTransClip && pTransClip->bIsClip, "TransposeClip with wrong Document" ); // initialize // -> pTransClip has to be deleted before the original document! pTransClip->ResetClip(this, nullptr); // all // Take over range if (pRangeName) { pTransClip->GetRangeName()->clear(); for (const auto& rEntry : *pRangeName) { sal_uInt16 nIndex = rEntry.second->GetIndex(); ScRangeData* pData = new ScRangeData(*rEntry.second); if (pTransClip->pRangeName->insert(pData)) pData->SetIndex(nIndex); } } ScRange aCombinedClipRange = GetClipParam().getWholeRange(); 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. const bool bIsMultiRangeRowFilteredTranspose = !bIncludeFiltered && GetClipParam().isMultiRange() && HasFilteredRows(aCombinedClipRange.aStart.Row(), aCombinedClipRange.aEnd.Row(), aCombinedClipRange.aStart.Tab()) && GetClipParam().meDirection == ScClipParam::Row; ScRangeList aClipRanges; if (bIsMultiRangeRowFilteredTranspose) aClipRanges = GetClipParam().maRanges; else aClipRanges = ScRangeList(aCombinedClipRange); // 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 < static_cast(maTabs.size()); 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) pTransClip->InitDrawLayer(); tools::Rectangle aSourceRect = GetMMRect( aClipRange.aStart.Col(), aClipRange.aStart.Row(), aClipRange.aEnd.Col(), aClipRange.aEnd.Row(), i ); tools::Rectangle aDestRect = pTransClip->GetMMRect( 0, 0, static_cast(aClipRange.aEnd.Row() - aClipRange.aStart.Row()), static_cast(aClipRange.aEnd.Col() - aClipRange.aStart.Col()), i ); pTransClip->mpDrawLayer->CopyFromClip( mpDrawLayer.get(), i, aSourceRect, ScAddress(0,0,i), aDestRect ); } } } } pTransClip->SetClipParam(GetClipParam()); pTransClip->GetClipParam().transpose(*this, bIncludeFiltered, bIsMultiRangeRowFilteredTranspose); // This happens only when inserting... GetClipParam().mbCutMode = false; } namespace { void copyUsedNamesToClip(ScRangeName* pClipRangeName, ScRangeName* pRangeName, const sc::UpdatedRangeNames::NameIndicesType& rUsedNames) { pClipRangeName->clear(); for (const auto& rEntry : *pRangeName) //TODO: also DB and Pivot regions!!! { sal_uInt16 nIndex = rEntry.second->GetIndex(); bool bInUse = (rUsedNames.count(nIndex) > 0); if (!bInUse) continue; ScRangeData* pData = new ScRangeData(*rEntry.second); if (pClipRangeName->insert(pData)) pData->SetIndex(nIndex); } } } void ScDocument::CopyRangeNamesToClip(ScDocument* pClipDoc, const ScRange& rClipRange, const ScMarkData* pMarks) { if (!pRangeName || pRangeName->empty()) return; sc::UpdatedRangeNames aUsedNames; // indexes of named ranges that are used in the copied cells SCTAB nMinSizeBothTabs = static_cast(std::min(maTabs.size(), pClipDoc->maTabs.size())); 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); /* TODO: handle also sheet-local names */ sc::UpdatedRangeNames::NameIndicesType aUsedGlobalNames( aUsedNames.getUpdatedNames(-1)); copyUsedNamesToClip(pClipDoc->GetRangeName(), pRangeName.get(), aUsedGlobalNames); } ScDocument::NumFmtMergeHandler::NumFmtMergeHandler(ScDocument& rDoc, const ScDocument& rSrcDoc) : mrDoc(rDoc) { mrDoc.MergeNumberFormatter(rSrcDoc); } ScDocument::NumFmtMergeHandler::~NumFmtMergeHandler() { ScMutationGuard aGuard(mrDoc, ScMutationGuardFlags::CORE); mrDoc.pFormatExchangeList = nullptr; } void ScDocument::PrepareFormulaCalc() { ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE); mpFormulaGroupCxt.reset(); } SvtBroadcaster* ScDocument::GetBroadcaster( const ScAddress& rPos ) { ScTable* pTab = FetchTable(rPos.Tab()); if (!pTab) return nullptr; return pTab->GetBroadcaster(rPos.Col(), rPos.Row()); } const SvtBroadcaster* ScDocument::GetBroadcaster( const ScAddress& rPos ) const { const ScTable* pTab = FetchTable(rPos.Tab()); if (!pTab) return nullptr; return pTab->GetBroadcaster(rPos.Col(), rPos.Row()); } void ScDocument::DeleteBroadcasters( sc::ColumnBlockPosition& rBlockPos, const ScAddress& rTopPos, SCROW nLength ) { ScTable* pTab = FetchTable(rTopPos.Tab()); if (!pTab || nLength <= 0) return; pTab->DeleteBroadcasters(rBlockPos, rTopPos.Col(), rTopPos.Row(), rTopPos.Row()+nLength-1); } #if DUMP_COLUMN_STORAGE void ScDocument::DumpColumnStorage( SCTAB nTab, SCCOL nCol ) const { const ScTable* pTab = FetchTable(nTab); if (!pTab) return; pTab->DumpColumnStorage(nCol); } #endif #if DEBUG_AREA_BROADCASTER void ScDocument::DumpAreaBroadcasters() const { if (pBASM) pBASM->Dump(); } #endif bool ScDocument::TableExists( SCTAB nTab ) const { return ValidTab(nTab) && o3tl::make_unsigned(nTab) < maTabs.size() && maTabs[nTab]; } ScTable* ScDocument::FetchTable( SCTAB nTab ) { if (!TableExists(nTab)) return nullptr; return maTabs[nTab].get(); } const ScTable* ScDocument::FetchTable( SCTAB nTab ) const { if (!TableExists(nTab)) return nullptr; return maTabs[nTab].get(); } ScColumnsRange ScDocument::GetWritableColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd) { if (!TableExists(nTab)) { SAL_WARN("sc", "GetWritableColumnsRange() called for non-existent table"); return ScColumnsRange(-1, -1); } return maTabs[nTab]->GetWritableColumnsRange(nColBegin, nColEnd); } ScColumnsRange ScDocument::GetAllocatedColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd) const { if (!TableExists(nTab)) return ScColumnsRange(-1, -1); return maTabs[nTab]->GetAllocatedColumnsRange(nColBegin, nColEnd); } ScColumnsRange ScDocument::GetColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd) const { if (!TableExists(nTab)) return ScColumnsRange(-1, -1); return maTabs[nTab]->GetColumnsRange(nColBegin, nColEnd); } void ScDocument::MergeNumberFormatter(const ScDocument& rSrcDoc) { SvNumberFormatter* pThisFormatter = mxPoolHelper->GetFormTable(); SvNumberFormatter* pOtherFormatter = rSrcDoc.mxPoolHelper->GetFormTable(); if (pOtherFormatter && pOtherFormatter != pThisFormatter) { SvNumberFormatterIndexTable* pExchangeList = pThisFormatter->MergeFormatter(*pOtherFormatter); if (!pExchangeList->empty()) { ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE); pFormatExchangeList = pExchangeList; } } } ScClipParam& ScDocument::GetClipParam() { if (!mpClipParam) mpClipParam.reset(new ScClipParam); return *mpClipParam; } void ScDocument::SetClipParam(const ScClipParam& rParam) { mpClipParam.reset(new ScClipParam(rParam)); } bool ScDocument::IsClipboardSource() const { if (bIsClip || mpShell == nullptr || mpShell->IsLoading()) return false; ScDocument* pClipDoc = ScModule::GetClipDoc(); return pClipDoc && pClipDoc->bIsClip && pClipDoc->mxPoolHelper.is() && mxPoolHelper.is() && mxPoolHelper->GetDocPool() == pClipDoc->mxPoolHelper->GetDocPool(); } void ScDocument::StartListeningFromClip( sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt, SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) { ScTable* pTab = FetchTable(nTab); if (!pTab) return; pTab->StartListeningFormulaCells(rStartCxt, rEndCxt, nCol1, nRow1, nCol2, nRow2); } void ScDocument::StartListeningFromClip( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark, InsertDeleteFlags nInsFlag ) { if (!(nInsFlag & InsertDeleteFlags::CONTENTS)) return; auto pSet = std::make_shared(*this); sc::StartListeningContext aStartCxt(*this, pSet); sc::EndListeningContext aEndCxt(*this, pSet, nullptr); for (SCTAB nTab : rMark) StartListeningFromClip(aStartCxt, aEndCxt, nTab, nCol1, nRow1, nCol2, nRow2); } void ScDocument::SetDirtyFromClip( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark, InsertDeleteFlags nInsFlag, sc::ColumnSpanSet& rBroadcastSpans ) { if (nInsFlag & InsertDeleteFlags::CONTENTS) { SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) maTabs[rTab]->SetDirtyFromClip(nCol1, nRow1, nCol2, nRow2, rBroadcastSpans); } } } bool ScDocument::InitColumnBlockPosition( sc::ColumnBlockPosition& rBlockPos, SCTAB nTab, SCCOL nCol ) { if (!TableExists(nTab)) return false; return maTabs[nTab]->InitColumnBlockPosition(rBlockPos, nCol); } void ScDocument::CopyBlockFromClip( sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark, SCCOL nDx, SCROW nDy ) { TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs; SCTAB nTabEnd = rCxt.getTabEnd(); SCTAB nClipTab = 0; for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < static_cast(maTabs.size()); i++) { if (maTabs[i] && rMark.GetTableSelect(i) ) { while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast(rClipTabs.size()); maTabs[i]->CopyFromClip( rCxt, nCol1, nRow1, nCol2, nRow2, nDx, nDy, rClipTabs[nClipTab].get()); 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). tools::Rectangle aSourceRect = rCxt.getClipDoc()->GetMMRect( nCol1-nDx, nRow1-nDy, nCol2-nDx, nRow2-nDy, nClipTab ); tools::Rectangle aDestRect = GetMMRect( nCol1, nRow1, nCol2, nRow2, i ); mpDrawLayer->CopyFromClip(rCxt.getClipDoc()->mpDrawLayer.get(), nClipTab, aSourceRect, ScAddress( nCol1, nRow1, i ), aDestRect ); } } nClipTab = (nClipTab+1) % static_cast(rClipTabs.size()); } } if (!(rCxt.getInsertFlag() & InsertDeleteFlags::CONTENTS)) return; nClipTab = 0; for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < static_cast(maTabs.size()); i++) { if (maTabs[i] && rMark.GetTableSelect(i) ) { while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast(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(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( aRefCxt.maRegroupCols); StartNeededListeners( pColSet); } SetInsertingFromOtherDoc( bOldInserting); } } else { aRefCxt.meMode = URM_COPY; UpdateReference(aRefCxt, rCxt.getUndoDoc(), false); } nClipTab = (nClipTab+nFollow+1) % static_cast(rClipTabs.size()); i = sal::static_int_cast( 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(rClipTabs.size()) && !rClipTabs[nFlagTab] ) ++nFlagTab; SCROW nSourceRow = rClipStartRow; SCROW nSourceEnd = nClipEndRow; SCROW nDestRow = nRow1; SCROW nFilteredRows = 0; while ( nSourceRow <= nSourceEnd && nDestRow <= nRow2 ) { // skip filtered rows SCROW nSourceRowOriginal = nSourceRow; nSourceRow = rCxt.getClipDoc()->FirstNonFilteredRow(nSourceRow, nSourceEnd, nFlagTab); nFilteredRows += nSourceRow - nSourceRowOriginal; if ( nSourceRow <= nSourceEnd ) { // look for more non-filtered rows following SCROW nLastRow = nSourceRow; (void)rCxt.getClipDoc()->RowFiltered(nSourceRow, nFlagTab, nullptr, &nLastRow); SCROW nFollow = nLastRow - nSourceRow; if (nFollow > nSourceEnd - nSourceRow) nFollow = nSourceEnd - nSourceRow; if (nFollow > nRow2 - nDestRow) nFollow = nRow2 - nDestRow; SCROW nNewDy = nDestRow - nSourceRow; CopyBlockFromClip( rCxt, nCol1, nDestRow, nCol2, nDestRow + nFollow, rMark, nDx, nNewDy); nSourceRow += nFollow + 1; nDestRow += nFollow + 1; } } rClipStartRow = nSourceRow; return nFilteredRows; } namespace { class BroadcastAction : public sc::ColumnSpanSet::ColumnAction { ScDocument& mrDoc; ScColumn* mpCol; public: explicit BroadcastAction( ScDocument& rDoc ) : mrDoc(rDoc), mpCol(nullptr) {} virtual void startColumn( ScColumn* pCol ) override { mpCol = pCol; } virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override { if (!bVal) return; assert(mpCol); ScRange aRange(mpCol->GetCol(), nRow1, mpCol->GetTab()); aRange.aEnd.SetRow(nRow2); mrDoc.BroadcastCells(aRange, SfxHintId::ScDataChanged); }; }; } void ScDocument::CopyFromClip( const ScRange& rDestRange, const ScMarkData& rMark, InsertDeleteFlags nInsFlag, ScDocument* pRefUndoDoc, ScDocument* pClipDoc, bool bResetCut, bool bAsLink, bool bIncludeFiltered, bool bSkipEmptyCells, const ScRangeList * pDestRanges ) { if (bIsClip) return; if (!pClipDoc) { OSL_FAIL("CopyFromClip: no ClipDoc"); pClipDoc = ScModule::GetClipDoc(); } if (!pClipDoc->bIsClip || !pClipDoc->GetTableCount()) return; sc::AutoCalcSwitch aACSwitch(*this, false); // temporarily turn off auto calc. NumFmtMergeHandler aNumFmtMergeHdl(*this, *pClipDoc); SCCOL nAllCol1 = rDestRange.aStart.Col(); SCROW nAllRow1 = rDestRange.aStart.Row(); SCCOL nAllCol2 = rDestRange.aEnd.Col(); SCROW nAllRow2 = rDestRange.aEnd.Row(); SCCOL nXw = 0; SCROW nYw = 0; ScRange aClipRange = pClipDoc->GetClipParam().getWholeRange(); for (SCTAB nTab = 0; nTab < static_cast(pClipDoc->maTabs.size()); nTab++) // find largest merge overlap if (pClipDoc->maTabs[nTab]) // all sheets of the clipboard content { SCCOL nThisEndX = aClipRange.aEnd.Col(); SCROW nThisEndY = aClipRange.aEnd.Row(); pClipDoc->ExtendMerge( aClipRange.aStart.Col(), aClipRange.aStart.Row(), nThisEndX, nThisEndY, nTab ); // only extra value from ExtendMerge nThisEndX = sal::static_int_cast( nThisEndX - aClipRange.aEnd.Col() ); nThisEndY = sal::static_int_cast( nThisEndY - aClipRange.aEnd.Row() ); if ( nThisEndX > nXw ) nXw = nThisEndX; if ( nThisEndY > nYw ) nYw = nThisEndY; } SCCOL nDestAddX; SCROW nDestAddY; pClipDoc->GetClipArea( nDestAddX, nDestAddY, bIncludeFiltered ); nXw = sal::static_int_cast( nXw + nDestAddX ); nYw = sal::static_int_cast( nYw + nDestAddY ); // ClipArea, plus ExtendMerge value /* 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 = InsertDeleteFlags::NONE; if ( (nInsFlag & (InsertDeleteFlags::CONTENTS | InsertDeleteFlags::ADDNOTES)) == (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES) ) nDelFlag |= InsertDeleteFlags::NOTE; else if ( nInsFlag & InsertDeleteFlags::CONTENTS ) nDelFlag |= InsertDeleteFlags::CONTENTS; if (nInsFlag & InsertDeleteFlags::ATTRIB) nDelFlag |= InsertDeleteFlags::ATTRIB; sc::CopyFromClipContext aCxt(*this, pRefUndoDoc, pClipDoc, nInsFlag, bAsLink, bSkipEmptyCells); std::pair aTabRanges = getMarkedTableRange(maTabs, rMark); aCxt.setTabRange(aTabRanges.first, aTabRanges.second); aCxt.setDeleteFlag(nDelFlag); ScRangeList aLocalRangeList; if (!pDestRanges) { aLocalRangeList.push_back( rDestRange); pDestRanges = &aLocalRangeList; } bInsertingFromOtherDoc = true; // No Broadcast/Listener created at Insert sc::ColumnSpanSet aBroadcastSpans; SCCOL nClipStartCol = aClipRange.aStart.Col(); SCROW nClipStartRow = aClipRange.aStart.Row(); SCROW nClipEndRow = aClipRange.aEnd.Row(); for ( size_t nRange = 0; nRange < pDestRanges->size(); ++nRange ) { const ScRange & rRange = (*pDestRanges)[nRange]; SCCOL nCol1 = rRange.aStart.Col(); SCROW nRow1 = rRange.aStart.Row(); SCCOL nCol2 = rRange.aEnd.Col(); SCROW nRow2 = rRange.aEnd.Row(); aCxt.setDestRange(nCol1, nRow1, nCol2, nRow2); DeleteBeforeCopyFromClip(aCxt, rMark, aBroadcastSpans); // <- this removes existing formula listeners if (CopyOneCellFromClip(aCxt, nCol1, nRow1, nCol2, nRow2)) continue; SCCOL nC1 = nCol1; SCROW nR1 = nRow1; SCCOL nC2 = nC1 + nXw; if (nC2 > nCol2) nC2 = nCol2; SCROW nR2 = nR1 + nYw; if (nR2 > nRow2) nR2 = nRow2; const SCCOLROW nThreshold = 8192; bool bPreallocatePattern = ((nInsFlag & InsertDeleteFlags::ATTRIB) && (nRow2 - nRow1 > nThreshold)); std::vector< SCTAB > vTables; 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(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; } nR1 = nR2 + 1; nR2 = std::min(static_cast(nR1 + nYw), nRow2); } while (nR1 <= nRow2); } bInsertingFromOtherDoc = false; if (nInsFlag & InsertDeleteFlags::CONTENTS) { for (SCTAB nTab : rMark) aCxt.setListeningFormulaSpans(nTab, nAllCol1, nAllRow1, nAllCol2, nAllRow2); } // Create Listener after everything has been inserted aCxt.startListeningFormulas(); { ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged); // 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); BroadcastAction aAction(*this); aBroadcastSpans.executeColumnAction(*this, aAction); } if (bResetCut) pClipDoc->GetClipParam().mbCutMode = false; } void ScDocument::CopyMultiRangeFromClip(const ScAddress& rDestPos, const ScMarkData& rMark, InsertDeleteFlags nInsFlag, ScDocument* pClipDoc, bool bResetCut, bool bAsLink, bool bIncludeFiltered, bool bSkipAttrForEmpty) { if (bIsClip) return; if (!pClipDoc->bIsClip || !pClipDoc->GetTableCount()) // There is nothing in the clip doc to copy. return; // Right now, we don't allow pasting into filtered rows, so we don't even handle it here. sc::AutoCalcSwitch aACSwitch(*this, false); // turn of auto calc temporarily. NumFmtMergeHandler aNumFmtMergeHdl(*this, *pClipDoc); const ScRange& aDestRange = rMark.GetMarkArea(); bInsertingFromOtherDoc = true; // No Broadcast/Listener created at Insert SCCOL nCol1 = rDestPos.Col(); SCROW nRow1 = rDestPos.Row(); ScClipParam& rClipParam = pClipDoc->GetClipParam(); sc::ColumnSpanSet aBroadcastSpans; if (!bSkipAttrForEmpty) { // Do the deletion first. SCCOL nColSize = rClipParam.getPasteColSize(); SCROW nRowSize = rClipParam.getPasteRowSize(*pClipDoc, bIncludeFiltered); DeleteArea(nCol1, nRow1, nCol1+nColSize-1, nRow1+nRowSize-1, rMark, InsertDeleteFlags::CONTENTS, false, &aBroadcastSpans); } sc::CopyFromClipContext aCxt(*this, nullptr, pClipDoc, nInsFlag, bAsLink, bSkipAttrForEmpty); std::pair aTabRanges = getMarkedTableRange(maTabs, rMark); aCxt.setTabRange(aTabRanges.first, aTabRanges.second); for (size_t i = 0, n = rClipParam.maRanges.size(); i < n; ++i) { const ScRange & rRange = rClipParam.maRanges[i]; SCROW nRowCount = rRange.aEnd.Row() - rRange.aStart.Row() + 1; SCCOL nDx = static_cast(nCol1 - rRange.aStart.Col()); SCROW nDy = static_cast(nRow1 - rRange.aStart.Row()); SCCOL nCol2 = nCol1 + rRange.aEnd.Col() - rRange.aStart.Col(); SCROW nEndRow = nRow1 + nRowCount - 1; SCROW nFilteredRows = 0; if (bIncludeFiltered) { CopyBlockFromClip(aCxt, nCol1, nRow1, nCol2, nEndRow, rMark, nDx, nDy); } else { SCROW nClipStartRow = rRange.aStart.Row(); SCROW nClipEndRow = rRange.aEnd.Row(); nFilteredRows += CopyNonFilteredFromClip(aCxt, nCol1, nRow1, nCol2, nEndRow, rMark, nDx, nClipStartRow, nClipEndRow); nRowCount -= nFilteredRows; } 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 ); { ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged); // Set formula cells dirty and collect non-formula cells. SetDirtyFromClip( aDestRange.aStart.Col(), aDestRange.aStart.Row(), aDestRange.aEnd.Col(), aDestRange.aEnd.Row(), rMark, nInsFlag, aBroadcastSpans); BroadcastAction aAction(*this); aBroadcastSpans.executeColumnAction(*this, aAction); } if (bResetCut) pClipDoc->GetClipParam().mbCutMode = false; } void ScDocument::SetClipArea( const ScRange& rArea, bool bCut ) { if (bIsClip) { ScClipParam& rClipParam = GetClipParam(); rClipParam.maRanges.RemoveAll(); rClipParam.maRanges.push_back(rArea); rClipParam.mbCutMode = bCut; } else { OSL_FAIL("SetClipArea: No Clip"); } } void ScDocument::GetClipArea(SCCOL& nClipX, SCROW& nClipY, bool bIncludeFiltered) { if (!bIsClip) { OSL_FAIL("GetClipArea: No Clip"); return; } ScRangeList& rClipRanges = GetClipParam().maRanges; if (rClipRanges.empty()) // No clip range. Bail out. return; ScRange const & rRange = rClipRanges.front(); SCCOL nStartCol = rRange.aStart.Col(); SCCOL nEndCol = rRange.aEnd.Col(); SCROW nStartRow = rRange.aStart.Row(); SCROW nEndRow = rRange.aEnd.Row(); for ( size_t i = 1, n = rClipRanges.size(); i < n; ++i ) { ScRange const & rRange2 = rClipRanges[ i ]; if (rRange2.aStart.Col() < nStartCol) nStartCol = rRange2.aStart.Col(); if (rRange2.aStart.Row() < nStartRow) nStartRow = rRange2.aStart.Row(); if (rRange2.aEnd.Col() > nEndCol) nEndCol = rRange2.aEnd.Col(); if (rRange2.aEnd.Row() > nEndRow) nEndRow = rRange2.aEnd.Row(); } nClipX = nEndCol - nStartCol; if ( bIncludeFiltered ) nClipY = nEndRow - nStartRow; else { // count non-filtered rows // count on first used table in clipboard SCTAB nCountTab = 0; while ( nCountTab < static_cast(maTabs.size()) && !maTabs[nCountTab] ) ++nCountTab; SCROW nResult = CountNonFilteredRows(nStartRow, nEndRow, nCountTab); if ( nResult > 0 ) nClipY = nResult - 1; else nClipY = 0; // always return at least 1 row } } void ScDocument::GetClipStart(SCCOL& nClipX, SCROW& nClipY) { if (bIsClip) { ScRangeList& rClipRanges = GetClipParam().maRanges; if ( !rClipRanges.empty() ) { nClipX = rClipRanges.front().aStart.Col(); nClipY = rClipRanges.front().aStart.Row(); } } else { OSL_FAIL("GetClipStart: No Clip"); } } bool ScDocument::HasClipFilteredRows() { // count on first used table in clipboard SCTAB nCountTab = 0; while ( nCountTab < static_cast(maTabs.size()) && !maTabs[nCountTab] ) ++nCountTab; ScRangeList& rClipRanges = GetClipParam().maRanges; if ( rClipRanges.empty() ) return false; 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) return true; } return false; } void ScDocument::MixDocument( const ScRange& rRange, ScPasteFunc nFunction, bool bSkipEmpty, ScDocument& rSrcDoc ) { SCTAB nTab1 = rRange.aStart.Tab(); SCTAB nTab2 = rRange.aEnd.Tab(); sc::MixDocContext aCxt(*this); SCTAB nMinSizeBothTabs = static_cast(std::min(maTabs.size(), rSrcDoc.maTabs.size())); for (SCTAB i = nTab1; i <= nTab2 && i < nMinSizeBothTabs; i++) { ScTable* pTab = FetchTable(i); const ScTable* pSrcTab = rSrcDoc.FetchTable(i); if (!pTab || !pSrcTab) continue; pTab->MixData( aCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), nFunction, bSkipEmpty, pSrcTab); } } void ScDocument::FillTab( const ScRange& rSrcArea, const ScMarkData& rMark, InsertDeleteFlags nFlags, ScPasteFunc nFunction, bool bSkipEmpty, bool bAsLink ) { InsertDeleteFlags nDelFlags = nFlags; if (nDelFlags & InsertDeleteFlags::CONTENTS) nDelFlags |= InsertDeleteFlags::CONTENTS; // Either all contents or delete nothing! SCTAB nSrcTab = rSrcArea.aStart.Tab(); if (ValidTab(nSrcTab) && nSrcTab < static_cast(maTabs.size()) && maTabs[nSrcTab]) { SCCOL nStartCol = rSrcArea.aStart.Col(); SCROW nStartRow = rSrcArea.aStart.Row(); SCCOL nEndCol = rSrcArea.aEnd.Col(); SCROW nEndRow = rSrcArea.aEnd.Row(); ScDocumentUniquePtr pMixDoc; bool bDoMix = ( bSkipEmpty || nFunction != ScPasteFunc::NONE ) && ( nFlags & InsertDeleteFlags::CONTENTS ); bool bOldAutoCalc = GetAutoCalc(); SetAutoCalc( false ); // avoid multiple calculations sc::CopyToDocContext aCxt(*this); sc::MixDocContext aMixDocCxt(*this); SCTAB nCount = static_cast(maTabs.size()); 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 ); // context used for copying content to the temporary mix document. sc::CopyToDocContext aMixCxt(*pMixDoc); maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow, InsertDeleteFlags::CONTENTS, false, pMixDoc->maTabs[i].get(), /*pMarkData*/nullptr, /*bAsLink*/false, /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true ); } maTabs[i]->DeleteArea( nStartCol,nStartRow, nEndCol,nEndRow, nDelFlags); maTabs[nSrcTab]->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow, nFlags, false, maTabs[i].get(), nullptr, bAsLink, /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true ); if (bDoMix) maTabs[i]->MixData(aMixDocCxt, nStartCol,nStartRow, nEndCol,nEndRow, nFunction, bSkipEmpty, pMixDoc->maTabs[i].get() ); } } SetAutoCalc( bOldAutoCalc ); } else { OSL_FAIL("wrong table"); } } void ScDocument::FillTabMarked( SCTAB nSrcTab, const ScMarkData& rMark, InsertDeleteFlags nFlags, ScPasteFunc nFunction, bool bSkipEmpty, bool bAsLink ) { InsertDeleteFlags nDelFlags = nFlags; if (nDelFlags & InsertDeleteFlags::CONTENTS) nDelFlags |= InsertDeleteFlags::CONTENTS; // Either all contents or delete nothing! if (ValidTab(nSrcTab) && nSrcTab < static_cast(maTabs.size()) && maTabs[nSrcTab]) { ScDocumentUniquePtr pMixDoc; bool bDoMix = ( bSkipEmpty || nFunction != ScPasteFunc::NONE ) && ( nFlags & InsertDeleteFlags::CONTENTS ); bool bOldAutoCalc = GetAutoCalc(); SetAutoCalc( false ); // avoid multiple calculations const ScRange& aArea = rMark.GetMultiMarkArea(); SCCOL nStartCol = aArea.aStart.Col(); SCROW nStartRow = aArea.aStart.Row(); SCCOL nEndCol = aArea.aEnd.Col(); SCROW nEndRow = aArea.aEnd.Row(); sc::CopyToDocContext aCxt(*this); sc::MixDocContext aMixDocCxt(*this); SCTAB nCount = static_cast(maTabs.size()); 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 aMixCxt(*pMixDoc); maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow, InsertDeleteFlags::CONTENTS, true, pMixDoc->maTabs[i].get(), &rMark, /*bAsLink*/false, /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true ); } maTabs[i]->DeleteSelection( nDelFlags, rMark ); maTabs[nSrcTab]->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow, nFlags, true, maTabs[i].get(), &rMark, bAsLink, /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true ); if (bDoMix) maTabs[i]->MixMarked(aMixDocCxt, rMark, nFunction, bSkipEmpty, pMixDoc->maTabs[i].get()); } } SetAutoCalc( bOldAutoCalc ); } else { OSL_FAIL("wrong table"); } } bool ScDocument::SetString( SCCOL nCol, SCROW nRow, SCTAB nTab, const OUString& rString, const ScSetStringParam* pParam ) { ScTable* pTab = FetchTable(nTab); if (!pTab) return false; 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(). sc::EndListeningContext aCxt(*this); ScAddress aPos(nCol, nRow, nTab); EndListeningIntersectedGroup(aCxt, aPos, nullptr); aCxt.purgeEmptyBroadcasters(); } return pTab->SetString(nCol, nRow, nTab, rString, pParam); } bool ScDocument::SetString( const ScAddress& rPos, const OUString& rString, const ScSetStringParam* pParam ) { return SetString(rPos.Col(), rPos.Row(), rPos.Tab(), rString, pParam); } bool ScDocument::SetEditText( const ScAddress& rPos, std::unique_ptr pEditText ) { if (!TableExists(rPos.Tab())) { return false; } return maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), std::move(pEditText)); } void ScDocument::SetEditText( const ScAddress& rPos, const EditTextObject& rEditText, const SfxItemPool* pEditPool ) { if (!TableExists(rPos.Tab())) return; maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), rEditText, pEditPool); } void ScDocument::SetEditText( const ScAddress& rPos, const OUString& rStr ) { if (!TableExists(rPos.Tab())) return; ScFieldEditEngine& rEngine = GetEditEngine(); rEngine.SetTextCurrentDefaults(rStr); maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), rEngine.CreateTextObject()); } SCROW ScDocument::GetFirstEditTextRow( const ScRange& rRange ) const { const ScTable* pTab = FetchTable(rRange.aStart.Tab()); if (!pTab) return -1; return pTab->GetFirstEditTextRow(rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()); } void ScDocument::SetTextCell( const ScAddress& rPos, const OUString& rStr ) { if (!TableExists(rPos.Tab())) return; if (ScStringUtil::isMultiline(rStr)) { ScFieldEditEngine& rEngine = GetEditEngine(); rEngine.SetTextCurrentDefaults(rStr); maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), rEngine.CreateTextObject()); } else { ScSetStringParam aParam; aParam.setTextInput(); maTabs[rPos.Tab()]->SetString(rPos.Col(), rPos.Row(), rPos.Tab(), rStr, &aParam); } } void ScDocument::SetEmptyCell( const ScAddress& rPos ) { if (!TableExists(rPos.Tab())) return; maTabs[rPos.Tab()]->SetEmptyCell(rPos.Col(), rPos.Row()); } void ScDocument::SetValue( SCCOL nCol, SCROW nRow, SCTAB nTab, const double& rVal ) { SetValue(ScAddress(nCol, nRow, nTab), rVal); } void ScDocument::SetValue( const ScAddress& rPos, double fVal ) { ScTable* pTab = FetchTable(rPos.Tab()); if (!pTab) return; 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(). sc::EndListeningContext aCxt(*this); EndListeningIntersectedGroup(aCxt, rPos, nullptr); aCxt.purgeEmptyBroadcasters(); } pTab->SetValue(rPos.Col(), rPos.Row(), fVal); } OUString ScDocument::GetString( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScInterpreterContext* pContext ) const { if (TableExists(nTab)) return maTabs[nTab]->GetString(nCol, nRow, pContext); return OUString(); } OUString ScDocument::GetString( const ScAddress& rPos, const ScInterpreterContext* pContext ) const { if (!TableExists(rPos.Tab())) return OUString(); return maTabs[rPos.Tab()]->GetString(rPos.Col(), rPos.Row(), pContext); } double* ScDocument::GetValueCell( const ScAddress& rPos ) { if (!TableExists(rPos.Tab())) return nullptr; return maTabs[rPos.Tab()]->GetValueCell(rPos.Col(), rPos.Row()); } svl::SharedString ScDocument::GetSharedString( const ScAddress& rPos ) const { if (!TableExists(rPos.Tab())) return svl::SharedString(); return maTabs[rPos.Tab()]->GetSharedString(rPos.Col(), rPos.Row()); } std::shared_ptr& ScDocument::GetFormulaGroupContext() { ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE); if (!mpFormulaGroupCxt) mpFormulaGroupCxt = std::make_shared(); return mpFormulaGroupCxt; } void ScDocument::DiscardFormulaGroupContext() { assert(!IsThreadedGroupCalcInProgress()); if( !mbFormulaGroupCxtBlockDiscard ) mpFormulaGroupCxt.reset(); } OUString ScDocument::GetInputString(SCCOL nCol, SCROW nRow, SCTAB nTab, bool bForceSystemLocale ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetInputString( nCol, nRow, nullptr, bForceSystemLocale ); else return OUString(); } 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; } FormulaError nErr = FormulaError::NONE; OUString aStr; SvNumberFormatter* pFormatter = GetFormatTable(); switch (aCell.meType) { case CELLTYPE_STRING: case CELLTYPE_EDIT: aStr = aCell.getString(this); break; case CELLTYPE_FORMULA: { ScFormulaCell* pFCell = aCell.mpFormula; nErr = pFCell->GetErrCode(); if (pFCell->IsValue()) { double fVal = pFCell->GetValue(); sal_uInt32 nIndex = pFormatter->GetStandardFormat( SvNumFormatType::NUMBER, ScGlobal::eLnge); pFormatter->GetInputLineString(fVal, nIndex, aStr); } else aStr = pFCell->GetString().getString(); } break; case CELLTYPE_VALUE: { double fVal = aCell.mfValue; sal_uInt32 nIndex = pFormatter->GetStandardFormat( SvNumFormatType::NUMBER, ScGlobal::eLnge); pFormatter->GetInputLineString(fVal, nIndex, aStr); } break; default: ; } rString = aStr; return nErr; } const EditTextObject* ScDocument::GetEditText( const ScAddress& rPos ) const { SCTAB nTab = rPos.Tab(); if (!TableExists(nTab)) return nullptr; return maTabs[nTab]->GetEditText(rPos.Col(), rPos.Row()); } void ScDocument::RemoveEditTextCharAttribs( const ScAddress& rPos, const ScPatternAttr& rAttr ) { if (!TableExists(rPos.Tab())) return; return maTabs[rPos.Tab()]->RemoveEditTextCharAttribs(rPos.Col(), rPos.Row(), rAttr); } double ScDocument::GetValue( const ScAddress& rPos ) const { SCTAB nTab = rPos.Tab(); if ( nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetValue(rPos.Col(), rPos.Row()); return 0.0; } double ScDocument::GetValue( SCCOL nCol, SCROW nRow, SCTAB nTab ) const { ScAddress aAdr(nCol, nRow, nTab); return GetValue(aAdr); } sal_uInt32 ScDocument::GetNumberFormat( SCCOL nCol, SCROW nRow, SCTAB nTab ) const { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) if (maTabs[nTab]) { return maTabs[nTab]->GetNumberFormat( nCol, nRow ); } return 0; } sal_uInt32 ScDocument::GetNumberFormat( const ScRange& rRange ) const { SCTAB nTab1 = rRange.aStart.Tab(), nTab2 = rRange.aEnd.Tab(); SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col(); SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row(); if (!TableExists(nTab1) || !TableExists(nTab2)) return 0; sal_uInt32 nFormat = 0; bool bFirstItem = true; for (SCTAB nTab = nTab1; nTab <= nTab2 && nTab < static_cast(maTabs.size()) ; ++nTab) for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) { sal_uInt32 nThisFormat = maTabs[nTab]->GetNumberFormat(nCol, nRow1, nRow2); if (bFirstItem) { nFormat = nThisFormat; bFirstItem = false; } else if (nThisFormat != nFormat) return 0; } return nFormat; } sal_uInt32 ScDocument::GetNumberFormat( const ScInterpreterContext& rContext, const ScAddress& rPos ) const { SCTAB nTab = rPos.Tab(); if (!TableExists(nTab)) return 0; return maTabs[nTab]->GetNumberFormat( rContext, rPos ); } void ScDocument::SetNumberFormat( const ScAddress& rPos, sal_uInt32 nNumberFormat ) { assert(!IsThreadedGroupCalcInProgress()); SCTAB nTab = rPos.Tab(); if (!TableExists(nTab)) return; maTabs[nTab]->SetNumberFormat(rPos.Col(), rPos.Row(), nNumberFormat); } void ScDocument::GetNumberFormatInfo( const ScInterpreterContext& rContext, SvNumFormatType& nType, sal_uInt32& nIndex, const ScAddress& rPos ) const { SCTAB nTab = rPos.Tab(); if ( nTab < static_cast(maTabs.size()) && maTabs[nTab] ) { nIndex = maTabs[nTab]->GetNumberFormat( rContext, rPos ); nType = rContext.GetNumberFormatType( nIndex ); } else { nType = SvNumFormatType::UNDEFINED; nIndex = 0; } } OUString ScDocument::GetFormula( SCCOL nCol, SCROW nRow, SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetFormula( nCol, nRow ); return OUString(); } const ScFormulaCell* ScDocument::GetFormulaCell( const ScAddress& rPos ) const { if (!TableExists(rPos.Tab())) return nullptr; return maTabs[rPos.Tab()]->GetFormulaCell(rPos.Col(), rPos.Row()); } ScFormulaCell* ScDocument::GetFormulaCell( const ScAddress& rPos ) { if (!TableExists(rPos.Tab())) return nullptr; return maTabs[rPos.Tab()]->GetFormulaCell(rPos.Col(), rPos.Row()); } CellType ScDocument::GetCellType( const ScAddress& rPos ) const { SCTAB nTab = rPos.Tab(); if ( nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetCellType( rPos ); return CELLTYPE_NONE; } CellType ScDocument::GetCellType( SCCOL nCol, SCROW nRow, SCTAB nTab ) const { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) return maTabs[nTab]->GetCellType( nCol, nRow ); return CELLTYPE_NONE; } bool ScDocument::HasStringData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && nCol < maTabs[nTab]->GetAllocatedColumnsCount()) return maTabs[nTab]->HasStringData( nCol, nRow ); else return false; } bool ScDocument::HasValueData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && nCol < maTabs[nTab]->GetAllocatedColumnsCount()) return maTabs[nTab]->HasValueData( nCol, nRow ); else return false; } bool ScDocument::HasValueData( const ScAddress& rPos ) const { return HasValueData(rPos.Col(), rPos.Row(), rPos.Tab()); } bool ScDocument::HasStringCells( const ScRange& rRange ) const { // true, if String- or Edit cells in range SCCOL nStartCol = rRange.aStart.Col(); SCROW nStartRow = rRange.aStart.Row(); SCTAB nStartTab = rRange.aStart.Tab(); SCCOL nEndCol = rRange.aEnd.Col(); SCROW nEndRow = rRange.aEnd.Row(); SCTAB nEndTab = rRange.aEnd.Tab(); for ( SCTAB nTab=nStartTab; nTab<=nEndTab && nTab < static_cast(maTabs.size()); nTab++ ) if ( maTabs[nTab] && maTabs[nTab]->HasStringCells( nStartCol, nStartRow, nEndCol, nEndRow ) ) return true; return false; } bool ScDocument::HasSelectionData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const { sal_uInt32 nValidation = GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue(); if( nValidation ) { const ScValidationData* pData = GetValidationEntry( nValidation ); if( pData && pData->HasSelectionList() ) return true; } return HasStringCells( ScRange( nCol, 0, nTab, nCol, MaxRow(), nTab ) ); } bool ScDocument::HasValidationData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const { sal_uInt32 nValidation = GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue(); if( nValidation ) { const ScValidationData* pData = GetValidationEntry( nValidation ); if( pData && pData->GetDataMode() != ScValidationMode::SC_VALID_ANY ) return true; } return false; } void ScDocument::CheckVectorizationState() { bool bOldAutoCalc = GetAutoCalc(); bAutoCalc = false; // no multiple calculations for (const auto& 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 (const auto& 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 < static_cast(maTabs.size()); 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 < static_cast(maTabs.size()); i++) if (maTabs[i]) maTabs[i]->SetTableOpDirty( rRange ); SetAutoCalc( bOldAutoCalc ); } void ScDocument::InterpretDirtyCells( const ScRangeList& rRanges ) { if (!GetAutoCalc()) return; PrepareFormulaCalc(); for (size_t nPos=0, nRangeCount = rRanges.size(); nPos < nRangeCount; nPos++) { const ScRange& rRange = rRanges[nPos]; for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab) { ScTable* pTab = FetchTable(nTab); if (!pTab) return; pTab->InterpretDirtyCells( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()); } } ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE); mpFormulaGroupCxt.reset(); } bool ScDocument::InterpretCellsIfNeeded( const ScRangeList& rRanges ) { bool allInterpreted = true; for (size_t nPos=0, nRangeCount = rRanges.size(); nPos < nRangeCount; nPos++) { const ScRange& rRange = rRanges[nPos]; for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab) { ScTable* pTab = FetchTable(nTab); if (!pTab) break; if( !pTab->InterpretCellsIfNeeded( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row())) { allInterpreted = false; } } } return allInterpreted; } 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 (const auto& a : maTabs) { if (a) a->SetDirtyVar(); } for (const auto& 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 (const auto& a : maTabs) { if (a) a->CompileAll(aCxt); } sc::SetFormulaDirtyContext aFormulaDirtyCxt; SetAllFormulasDirty(aFormulaDirtyCxt); } void ScDocument::CompileXML() { bool bOldAutoCalc = GetAutoCalc(); SetAutoCalc( false ); ScProgress aProgress( GetDocumentShell(), ScResId( STR_PROGRESS_CALCULATING ), GetXMLImportedFormulaCount(), true ); sc::CompileFormulaContext aCxt(*this); // set AutoNameCache to speed up automatic name lookup OSL_ENSURE( !pAutoNameCache, "AutoNameCache already set" ); pAutoNameCache.reset( new ScAutoNameCache( *this ) ); if (pRangeName) pRangeName->CompileUnresolvedXML(aCxt); std::for_each(maTabs.begin(), maTabs.end(), [&](ScTableUniquePtr & pTab) { if (pTab) pTab->CompileXML(aCxt, aProgress); } ); StartAllListeners(); pAutoNameCache.reset(); // valid only during CompileXML, where cell contents don't change if ( pValidationList ) { ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE); pValidationList->CompileXML(); } // Track all formula cells that were appended to the FormulaTrack during // import or CompileXML(). TrackFormulas(); SetAutoCalc( bOldAutoCalc ); } bool ScDocument::CompileErrorCells(FormulaError nErrCode) { bool bCompiled = false; sc::CompileFormulaContext aCxt(*this); for (const auto& a : maTabs) { if (!a) continue; if (a->CompileErrorCells(aCxt, nErrCode)) bCompiled = true; } return bCompiled; } 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 (const auto& a : maTabs) { if (a) a->CalcAfterLoad(aCxt, bStartListening); } for (const auto& a : maTabs) { if (a) a->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 (auto const& it : rListeners) { const ScChartListener *const p = it.second.get(); InterpretDirtyCells(*p->GetRangeList()); } } } FormulaError ScDocument::GetErrCode( const ScAddress& rPos ) const { SCTAB nTab = rPos.Tab(); if ( nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetErrCode( rPos ); return FormulaError::NONE; } void ScDocument::ResetChanged( const ScRange& rRange ) { SCTAB nTabSize = static_cast(maTabs.size()); SCTAB nTab1 = rRange.aStart.Tab(); SCTAB nTab2 = rRange.aEnd.Tab(); for (SCTAB nTab = nTab1; nTab1 <= nTab2 && nTab < nTabSize; ++nTab) if (maTabs[nTab]) maTabs[nTab]->ResetChanged(rRange); } // Column widths / Row heights -------------------------------------- void ScDocument::SetColWidth( SCCOL nCol, SCTAB nTab, sal_uInt16 nNewWidth ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->SetColWidth( nCol, nNewWidth ); } void ScDocument::SetColWidthOnly( SCCOL nCol, SCTAB nTab, sal_uInt16 nNewWidth ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->SetColWidthOnly( nCol, nNewWidth ); } void ScDocument::SetRowHeight( SCROW nRow, SCTAB nTab, sal_uInt16 nNewHeight ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->SetRowHeight( nRow, nNewHeight ); } void ScDocument::SetRowHeightRange( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, sal_uInt16 nNewHeight ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->SetRowHeightRange ( nStartRow, nEndRow, nNewHeight, 1.0, true ); } void ScDocument::SetRowHeightOnly( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, sal_uInt16 nNewHeight ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->SetRowHeightOnly( nStartRow, nEndRow, nNewHeight ); } void ScDocument::SetManualHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bManual ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->SetManualHeight( nStartRow, nEndRow, bManual ); } sal_uInt16 ScDocument::GetColWidth( SCCOL nCol, SCTAB nTab, bool bHiddenAsZero ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetColWidth( nCol, bHiddenAsZero ); OSL_FAIL("wrong table number"); return 0; } tools::Long ScDocument::GetColWidth( SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab ) const { const ScTable* pTab = FetchTable(nTab); if (!pTab) return 0; return pTab->GetColWidth(nStartCol, nEndCol); } sal_uInt16 ScDocument::GetOriginalWidth( SCCOL nCol, SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetOriginalWidth( nCol ); OSL_FAIL("wrong table number"); return 0; } sal_uInt16 ScDocument::GetCommonWidth( SCCOL nEndCol, SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetCommonWidth( nEndCol ); OSL_FAIL("Wrong table number"); return 0; } sal_uInt16 ScDocument::GetOriginalHeight( SCROW nRow, SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetOriginalHeight( nRow ); OSL_FAIL("Wrong table number"); return 0; } sal_uInt16 ScDocument::GetRowHeight( SCROW nRow, SCTAB nTab, bool bHiddenAsZero ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetRowHeight( nRow, nullptr, nullptr, bHiddenAsZero ); OSL_FAIL("Wrong sheet number"); return 0; } sal_uInt16 ScDocument::GetRowHeight( SCROW nRow, SCTAB nTab, SCROW* pStartRow, SCROW* pEndRow, bool bHiddenAsZero ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetRowHeight( nRow, pStartRow, pEndRow, bHiddenAsZero ); OSL_FAIL("Wrong sheet number"); return 0; } tools::Long ScDocument::GetRowHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bHiddenAsZero ) const { if (nStartRow == nEndRow) return GetRowHeight( nStartRow, nTab, bHiddenAsZero ); // faster for a single row // check bounds because this method replaces former for(i=start;i<=end;++i) loops if (nStartRow > nEndRow) return 0; if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetRowHeight( nStartRow, nEndRow, bHiddenAsZero ); OSL_FAIL("wrong sheet number"); return 0; } SCROW ScDocument::GetRowForHeight( SCTAB nTab, tools::Long nHeight ) const { return maTabs[nTab]->GetRowForHeight(nHeight); } tools::Long ScDocument::GetScaledRowHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, double fScale ) const { // faster for a single row if (nStartRow == nEndRow) return static_cast(GetRowHeight( nStartRow, nTab) * fScale); // check bounds because this method replaces former for(i=start;i<=end;++i) loops if (nStartRow > nEndRow) return 0; if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetScaledRowHeight( nStartRow, nEndRow, fScale); OSL_FAIL("wrong sheet number"); return 0; } SCROW ScDocument::GetHiddenRowCount( SCROW nRow, SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetHiddenRowCount( nRow ); OSL_FAIL("wrong table number"); return 0; } tools::Long ScDocument::GetColOffset( SCCOL nCol, SCTAB nTab, bool bHiddenAsZero ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetColOffset( nCol, bHiddenAsZero ); OSL_FAIL("wrong table number"); return 0; } tools::Long ScDocument::GetRowOffset( SCROW nRow, SCTAB nTab, bool bHiddenAsZero ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetRowOffset( nRow, bHiddenAsZero ); OSL_FAIL("wrong table number"); return 0; } sal_uInt16 ScDocument::GetOptimalColWidth( SCCOL nCol, SCTAB nTab, OutputDevice* pDev, double nPPTX, double nPPTY, const Fraction& rZoomX, const Fraction& rZoomY, bool bFormula, const ScMarkData* pMarkData, const ScColWidthParam* pParam ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetOptimalColWidth( nCol, pDev, nPPTX, nPPTY, rZoomX, rZoomY, bFormula, pMarkData, pParam ); OSL_FAIL("wrong table number"); return 0; } tools::Long ScDocument::GetNeededSize( SCCOL nCol, SCROW nRow, SCTAB nTab, OutputDevice* pDev, double nPPTX, double nPPTY, const Fraction& rZoomX, const Fraction& rZoomY, bool bWidth, bool bTotalSize, bool bInPrintTwips ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetNeededSize ( nCol, nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, bWidth, bTotalSize, bInPrintTwips ); OSL_FAIL("wrong table number"); return 0; } bool ScDocument::SetOptimalHeight( sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bApi ) { ScTable* pTab = FetchTable(nTab); if (!pTab) return false; return pTab->SetOptimalHeight(rCxt, nStartRow, nEndRow, bApi); } void ScDocument::UpdateAllRowHeights( sc::RowHeightContext& rCxt, const ScMarkData* pTabMark ) { // one progress across all (selected) sheets sal_uInt64 nCellCount = 0; for ( SCTAB nTab=0; nTab< static_cast(maTabs.size()); nTab++ ) if ( maTabs[nTab] && ( !pTabMark || pTabMark->GetTableSelect(nTab) ) ) nCellCount += maTabs[nTab]->GetWeightedCount(); ScProgress aProgress( GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true ); sal_uInt64 nProgressStart = 0; for ( SCTAB nTab=0; nTab< static_cast(maTabs.size()); nTab++ ) if ( maTabs[nTab] && ( !pTabMark || pTabMark->GetTableSelect(nTab) ) ) { maTabs[nTab]->SetOptimalHeightOnly(rCxt, 0, MaxRow(), &aProgress, nProgressStart); maTabs[nTab]->SetDrawPageSize(); nProgressStart += maTabs[nTab]->GetWeightedCount(); } } // Column/Row - Flags ---------------------------------------------- void ScDocument::ShowCol(SCCOL nCol, SCTAB nTab, bool bShow) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->ShowCol( nCol, bShow ); } void ScDocument::ShowRow(SCROW nRow, SCTAB nTab, bool bShow) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->ShowRow( nRow, bShow ); } void ScDocument::ShowRows(SCROW nRow1, SCROW nRow2, SCTAB nTab, bool bShow) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->ShowRows( nRow1, nRow2, bShow ); } void ScDocument::SetRowFlags( SCROW nRow, SCTAB nTab, CRFlags nNewFlags ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->SetRowFlags( nRow, nNewFlags ); } void ScDocument::SetRowFlags( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, CRFlags nNewFlags ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->SetRowFlags( nStartRow, nEndRow, nNewFlags ); } CRFlags ScDocument::GetColFlags( SCCOL nCol, SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetColFlags( nCol ); OSL_FAIL("wrong table number"); return CRFlags::NONE; } CRFlags ScDocument::GetRowFlags( SCROW nRow, SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetRowFlags( nRow ); OSL_FAIL("wrong table number"); return CRFlags::NONE; } void ScDocument::GetAllRowBreaks(set& rBreaks, SCTAB nTab, bool bPage, bool bManual) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return; maTabs[nTab]->GetAllRowBreaks(rBreaks, bPage, bManual); } void ScDocument::GetAllColBreaks(set& rBreaks, SCTAB nTab, bool bPage, bool bManual) const { if (!ValidTab(nTab) || !maTabs[nTab]) return; maTabs[nTab]->GetAllColBreaks(rBreaks, bPage, bManual); } ScBreakType ScDocument::HasRowBreak(SCROW nRow, SCTAB nTab) const { ScBreakType nType = ScBreakType::NONE; if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab] || !ValidRow(nRow)) return nType; if (maTabs[nTab]->HasRowPageBreak(nRow)) nType |= ScBreakType::Page; if (maTabs[nTab]->HasRowManualBreak(nRow)) nType |= ScBreakType::Manual; return nType; } ScBreakType ScDocument::HasColBreak(SCCOL nCol, SCTAB nTab) const { ScBreakType nType = ScBreakType::NONE; if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab] || !ValidCol(nCol)) return nType; if (maTabs[nTab]->HasColPageBreak(nCol)) nType |= ScBreakType::Page; if (maTabs[nTab]->HasColManualBreak(nCol)) nType |= ScBreakType::Manual; return nType; } void ScDocument::SetRowBreak(SCROW nRow, SCTAB nTab, bool bPage, bool bManual) { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab] || !ValidRow(nRow)) return; maTabs[nTab]->SetRowBreak(nRow, bPage, bManual); } void ScDocument::SetColBreak(SCCOL nCol, SCTAB nTab, bool bPage, bool bManual) { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab] || !ValidCol(nCol)) return; maTabs[nTab]->SetColBreak(nCol, bPage, bManual); } void ScDocument::RemoveRowBreak(SCROW nRow, SCTAB nTab, bool bPage, bool bManual) { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab] || !ValidRow(nRow)) return; maTabs[nTab]->RemoveRowBreak(nRow, bPage, bManual); } void ScDocument::RemoveColBreak(SCCOL nCol, SCTAB nTab, bool bPage, bool bManual) { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab] || !ValidCol(nCol)) return; maTabs[nTab]->RemoveColBreak(nCol, bPage, bManual); } Sequence ScDocument::GetRowBreakData(SCTAB nTab) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return Sequence(); return maTabs[nTab]->GetRowBreakData(); } bool ScDocument::RowHidden(SCROW nRow, SCTAB nTab, SCROW* pFirstRow, SCROW* pLastRow) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return false; return maTabs[nTab]->RowHidden(nRow, pFirstRow, pLastRow); } bool ScDocument::HasHiddenRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return false; return maTabs[nTab]->HasHiddenRows(nStartRow, nEndRow); } bool ScDocument::ColHidden(SCCOL nCol, SCTAB nTab, SCCOL* pFirstCol, SCCOL* pLastCol) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) { if (pFirstCol) *pFirstCol = nCol; if (pLastCol) *pLastCol = nCol; return false; } return maTabs[nTab]->ColHidden(nCol, pFirstCol, pLastCol); } void ScDocument::SetRowHidden(SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bHidden) { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return; maTabs[nTab]->SetRowHidden(nStartRow, nEndRow, bHidden); } void ScDocument::SetColHidden(SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab, bool bHidden) { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return; maTabs[nTab]->SetColHidden(nStartCol, nEndCol, bHidden); } SCROW ScDocument::FirstVisibleRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return ::std::numeric_limits::max(); return maTabs[nTab]->FirstVisibleRow(nStartRow, nEndRow); } SCROW ScDocument::LastVisibleRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return ::std::numeric_limits::max(); return maTabs[nTab]->LastVisibleRow(nStartRow, nEndRow); } SCROW ScDocument::CountVisibleRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return 0; return maTabs[nTab]->CountVisibleRows(nStartRow, nEndRow); } bool ScDocument::RowFiltered(SCROW nRow, SCTAB nTab, SCROW* pFirstRow, SCROW* pLastRow) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return false; return maTabs[nTab]->RowFiltered(nRow, pFirstRow, pLastRow); } bool ScDocument::HasFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return false; return maTabs[nTab]->HasFilteredRows(nStartRow, nEndRow); } bool ScDocument::ColFiltered(SCCOL nCol, SCTAB nTab) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return false; return maTabs[nTab]->ColFiltered(nCol); } void ScDocument::SetRowFiltered(SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bFiltered) { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return; maTabs[nTab]->SetRowFiltered(nStartRow, nEndRow, bFiltered); } SCROW ScDocument::FirstNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return ::std::numeric_limits::max(); return maTabs[nTab]->FirstNonFilteredRow(nStartRow, nEndRow); } SCROW ScDocument::LastNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return ::std::numeric_limits::max(); return maTabs[nTab]->LastNonFilteredRow(nStartRow, nEndRow); } SCROW ScDocument::CountNonFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return 0; return maTabs[nTab]->CountNonFilteredRows(nStartRow, nEndRow); } bool ScDocument::IsManualRowHeight(SCROW nRow, SCTAB nTab) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return false; return maTabs[nTab]->IsManualRowHeight(nRow); } void ScDocument::SyncColRowFlags() { for (const auto& a : maTabs) { if (a) a->SyncColRowFlags(); } } SCROW ScDocument::GetLastFlaggedRow( SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetLastFlaggedRow(); return 0; } SCCOL ScDocument::GetLastChangedColFlagsWidth( SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetLastChangedColFlagsWidth(); return 0; } SCROW ScDocument::GetLastChangedRowFlagsWidth( SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetLastChangedRowFlagsWidth(); return 0; } SCCOL ScDocument::GetNextDifferentChangedColFlagsWidth( SCTAB nTab, SCCOL nStart) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) { CRFlags nStartFlags = maTabs[nTab]->GetColFlags(nStart); sal_uInt16 nStartWidth = maTabs[nTab]->GetOriginalWidth(nStart); for (SCCOL nCol : maTabs[nTab]->GetColumnsRange( nStart + 1, MaxCol())) { if (((nStartFlags & CRFlags::ManualBreak) != (maTabs[nTab]->GetColFlags(nCol) & CRFlags::ManualBreak)) || (nStartWidth != maTabs[nTab]->GetOriginalWidth(nCol)) || ((nStartFlags & CRFlags::Hidden) != (maTabs[nTab]->GetColFlags(nCol) & CRFlags::Hidden)) ) return nCol; } return MaxCol()+1; } return 0; } SCROW ScDocument::GetNextDifferentChangedRowFlagsWidth( SCTAB nTab, SCROW nStart) const { if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) return 0; const ScBitMaskCompressedArray* pRowFlagsArray = maTabs[nTab]->GetRowFlagsArray(); if (!pRowFlagsArray) return 0; if (!maTabs[nTab]->mpRowHeights || !maTabs[nTab]->mpHiddenRows) return 0; size_t nIndex; // ignored SCROW nFlagsEndRow; SCROW nHiddenEndRow; SCROW nHeightEndRow; CRFlags nFlags; bool bHidden; sal_uInt16 nHeight; CRFlags nStartFlags = nFlags = pRowFlagsArray->GetValue( nStart, nIndex, nFlagsEndRow); bool bStartHidden = bHidden = maTabs[nTab]->RowHidden( nStart, nullptr, &nHiddenEndRow); sal_uInt16 nStartHeight = nHeight = maTabs[nTab]->GetRowHeight( nStart, nullptr, &nHeightEndRow, false); SCROW nRow; while ((nRow = std::min( nHiddenEndRow, std::min( nFlagsEndRow, nHeightEndRow)) + 1) <= MaxRow()) { if (nFlagsEndRow < nRow) nFlags = pRowFlagsArray->GetValue( nRow, nIndex, nFlagsEndRow); if (nHiddenEndRow < nRow) bHidden = maTabs[nTab]->RowHidden( nRow, nullptr, &nHiddenEndRow); if (nHeightEndRow < nRow) nHeight = maTabs[nTab]->GetRowHeight( nRow, nullptr, &nHeightEndRow, false); if (((nStartFlags & CRFlags::ManualBreak) != (nFlags & CRFlags::ManualBreak)) || ((nStartFlags & CRFlags::ManualSize) != (nFlags & CRFlags::ManualSize)) || (bStartHidden != bHidden) || (nStartHeight != nHeight)) return nRow; } return MaxRow()+1; } void ScDocument::GetColDefault( SCTAB nTab, SCCOL nCol, SCROW nLastRow, SCROW& nDefault) { nDefault = 0; ScDocAttrIterator aDocAttrItr(*this, nTab, nCol, 0, nCol, nLastRow); SCCOL nColumn; SCROW nStartRow; SCROW nEndRow; const ScPatternAttr* pAttr = aDocAttrItr.GetNext(nColumn, nStartRow, nEndRow); if (nEndRow >= nLastRow) return; ScDefaultAttrSet aSet; ScDefaultAttrSet::iterator aItr = aSet.end(); while (pAttr) { ScDefaultAttr aAttr(pAttr); aItr = aSet.find(aAttr); if (aItr == aSet.end()) { aAttr.nCount = static_cast(nEndRow - nStartRow + 1); aAttr.nFirst = nStartRow; aSet.insert(aAttr); } else { aAttr.nCount = aItr->nCount + static_cast(nEndRow - nStartRow + 1); aAttr.nFirst = aItr->nFirst; aSet.erase(aItr); aSet.insert(aAttr); } pAttr = aDocAttrItr.GetNext(nColumn, nStartRow, nEndRow); } ScDefaultAttrSet::iterator aDefaultItr = aSet.begin(); aItr = aDefaultItr; ++aItr; while (aItr != aSet.end()) { // for entries with equal count, use the one with the lowest start row, // don't use the random order of pointer comparisons if ( aItr->nCount > aDefaultItr->nCount || ( aItr->nCount == aDefaultItr->nCount && aItr->nFirst < aDefaultItr->nFirst ) ) aDefaultItr = aItr; ++aItr; } nDefault = aDefaultItr->nFirst; } void ScDocument::StripHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2, SCTAB nTab ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->StripHidden( rX1, rY1, rX2, rY2 ); } void ScDocument::ExtendHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2, SCTAB nTab ) { if ( ValidTab(nTab) && maTabs[nTab] ) maTabs[nTab]->ExtendHidden( rX1, rY1, rX2, rY2 ); } // Attribute ---------------------------------------------------------- const SfxPoolItem* ScDocument::GetAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) { const SfxPoolItem* pTemp = maTabs[nTab]->GetAttr( nCol, nRow, nWhich ); if (pTemp) return pTemp; else { OSL_FAIL( "Attribute Null" ); } } return &mxPoolHelper->GetDocPool()->GetDefaultItem( nWhich ); } const SfxPoolItem* ScDocument::GetAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich, SCROW& nStartRow, SCROW& nEndRow ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) { const SfxPoolItem* pTemp = maTabs[nTab]->GetAttr( nCol, nRow, nWhich, nStartRow, nEndRow ); if (pTemp) return pTemp; else { OSL_FAIL( "Attribute Null" ); } } return &mxPoolHelper->GetDocPool()->GetDefaultItem( nWhich ); } const SfxPoolItem* ScDocument::GetAttr( const ScAddress& rPos, sal_uInt16 nWhich ) const { return GetAttr(rPos.Col(), rPos.Row(), rPos.Tab(), nWhich); } const ScPatternAttr* ScDocument::GetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab ) const { if (TableExists(nTab)) return maTabs[nTab]->GetPattern( nCol, nRow ); return nullptr; } const ScPatternAttr* ScDocument::GetPattern( const ScAddress& rPos ) const { if (TableExists(rPos.Tab())) return maTabs[rPos.Tab()]->GetPattern(rPos.Col(), rPos.Row()); return nullptr; } const ScPatternAttr* ScDocument::GetMostUsedPattern( SCCOL nCol, SCROW nStartRow, SCROW nEndRow, SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetMostUsedPattern( nCol, nStartRow, nEndRow ); return nullptr; } void ScDocument::ApplyAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, const SfxPoolItem& rAttr ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->ApplyAttr( nCol, nRow, rAttr ); } void ScDocument::ApplyPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScPatternAttr& rAttr ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->ApplyPattern( nCol, nRow, rAttr ); } void ScDocument::ApplyPatternArea( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, const ScMarkData& rMark, const ScPatternAttr& rAttr, ScEditDataArray* pDataArray, bool* const pIsChanged ) { SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) maTabs[rTab]->ApplyPatternArea( nStartCol, nStartRow, nEndCol, nEndRow, rAttr, pDataArray, pIsChanged ); } } void ScDocument::ApplyPatternAreaTab( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, const ScPatternAttr& rAttr ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) if (maTabs[nTab]) maTabs[nTab]->ApplyPatternArea( nStartCol, nStartRow, nEndCol, nEndRow, rAttr ); } void ScDocument::ApplyPatternIfNumberformatIncompatible( const ScRange& rRange, const ScMarkData& rMark, const ScPatternAttr& rPattern, SvNumFormatType nNewType ) { SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) maTabs[rTab]->ApplyPatternIfNumberformatIncompatible( rRange, rPattern, nNewType ); } } void ScDocument::AddCondFormatData( const ScRangeList& rRange, SCTAB nTab, sal_uInt32 nIndex ) { if(o3tl::make_unsigned(nTab) >= maTabs.size()) return; if(!maTabs[nTab]) return; maTabs[nTab]->AddCondFormatData(rRange, nIndex); } void ScDocument::RemoveCondFormatData( const ScRangeList& rRange, SCTAB nTab, sal_uInt32 nIndex ) { if(o3tl::make_unsigned(nTab) >= maTabs.size()) return; if(!maTabs[nTab]) return; maTabs[nTab]->RemoveCondFormatData(rRange, nIndex); } void ScDocument::ApplyStyle( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScStyleSheet& rStyle) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) if (maTabs[nTab]) maTabs[nTab]->ApplyStyle( nCol, nRow, &rStyle ); } void ScDocument::ApplyStyleArea( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, const ScMarkData& rMark, const ScStyleSheet& rStyle) { SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) maTabs[rTab]->ApplyStyleArea( nStartCol, nStartRow, nEndCol, nEndRow, rStyle ); } } void ScDocument::ApplyStyleAreaTab( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, const ScStyleSheet& rStyle) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) if (maTabs[nTab]) maTabs[nTab]->ApplyStyleArea( nStartCol, nStartRow, nEndCol, nEndRow, rStyle ); } void ScDocument::ApplySelectionStyle(const ScStyleSheet& rStyle, const ScMarkData& rMark) { // ApplySelectionStyle needs multi mark if ( rMark.IsMarked() && !rMark.IsMultiMarked() ) { const ScRange& aRange = rMark.GetMarkArea(); ApplyStyleArea( aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row(), rMark, rStyle ); } else { SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if ( maTabs[rTab] ) maTabs[rTab]->ApplySelectionStyle( rStyle, rMark ); } } } void ScDocument::ApplySelectionLineStyle( const ScMarkData& rMark, const SvxBorderLine* pLine, bool bColorOnly ) { if ( bColorOnly && !pLine ) return; SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) maTabs[rTab]->ApplySelectionLineStyle( rMark, pLine, bColorOnly ); } } const ScStyleSheet* ScDocument::GetStyle( SCCOL nCol, SCROW nRow, SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetStyle(nCol, nRow); else return nullptr; } const ScStyleSheet* ScDocument::GetSelectionStyle( const ScMarkData& rMark ) const { bool bEqual = true; bool bFound; const ScStyleSheet* pStyle = nullptr; const ScStyleSheet* pNewStyle; if ( rMark.IsMultiMarked() ) { SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) { pNewStyle = maTabs[rTab]->GetSelectionStyle( rMark, bFound ); if (bFound) { if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) ) bEqual = false; // different pStyle = pNewStyle; } } } } if ( rMark.IsMarked() ) { const ScRange& aRange = rMark.GetMarkArea(); for (SCTAB i=aRange.aStart.Tab(); i<=aRange.aEnd.Tab() && bEqual && i < static_cast(maTabs.size()); i++) if (maTabs[i] && rMark.GetTableSelect(i)) { pNewStyle = maTabs[i]->GetAreaStyle( bFound, aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row() ); if (bFound) { if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) ) bEqual = false; // different pStyle = pNewStyle; } } } return bEqual ? pStyle : nullptr; } void ScDocument::StyleSheetChanged( const SfxStyleSheetBase* pStyleSheet, bool bRemoved, OutputDevice* pDev, double nPPTX, double nPPTY, const Fraction& rZoomX, const Fraction& rZoomY ) { for (const auto& a : maTabs) { if (a) a->StyleSheetChanged ( pStyleSheet, bRemoved, pDev, nPPTX, nPPTY, rZoomX, rZoomY ); } if ( pStyleSheet && pStyleSheet->GetName() == ScResId(STR_STYLENAME_STANDARD) ) { // update attributes for all note objects ScDetectiveFunc::UpdateAllComments( *this ); } } bool ScDocument::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const { if ( bStyleSheetUsageInvalid || rStyle.GetUsage() == ScStyleSheet::Usage::UNKNOWN ) { SfxStyleSheetIterator aIter( mxPoolHelper->GetStylePool(), SfxStyleFamily::Para ); for ( const SfxStyleSheetBase* pStyle = aIter.First(); pStyle; pStyle = aIter.Next() ) { if (pStyle->isScStyleSheet()) { const ScStyleSheet* pScStyle = static_cast( pStyle ); pScStyle->SetUsage( ScStyleSheet::Usage::NOTUSED ); } } bool bIsUsed = false; for (const auto& a : maTabs) { if (a && a->IsStyleSheetUsed( rStyle ) ) bIsUsed = true; } bStyleSheetUsageInvalid = false; return bIsUsed; } return rStyle.GetUsage() == ScStyleSheet::Usage::USED; } bool ScDocument::ApplyFlagsTab( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, ScMF nFlags ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) if (maTabs[nTab]) return maTabs[nTab]->ApplyFlags( nStartCol, nStartRow, nEndCol, nEndRow, nFlags ); OSL_FAIL("ApplyFlags: wrong table"); return false; } bool ScDocument::RemoveFlagsTab( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, ScMF nFlags ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) if (maTabs[nTab]) return maTabs[nTab]->RemoveFlags( nStartCol, nStartRow, nEndCol, nEndRow, nFlags ); OSL_FAIL("RemoveFlags: wrong table"); return false; } const ScPatternAttr* ScDocument::SetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, std::unique_ptr pAttr ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) if (maTabs[nTab]) return maTabs[nTab]->SetPattern( nCol, nRow, std::move(pAttr) ); return nullptr; } const ScPatternAttr* ScDocument::SetPattern( const ScAddress& rPos, std::unique_ptr pAttr ) { return SetPattern(rPos.Col(), rPos.Row(), rPos.Tab(), std::move(pAttr)); } void ScDocument::SetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScPatternAttr& rAttr ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) if (maTabs[nTab]) maTabs[nTab]->SetPattern( nCol, nRow, rAttr ); } void ScDocument::SetPattern( const ScAddress& rPos, const ScPatternAttr& rAttr ) { SCTAB nTab = rPos.Tab(); if ( nTab < static_cast(maTabs.size()) && maTabs[nTab]) maTabs[nTab]->SetPattern( rPos, rAttr ); } std::unique_ptr ScDocument::CreateSelectionPattern( const ScMarkData& rMark, bool bDeep ) { ScMergePatternState aState; if ( rMark.IsMultiMarked() ) // multi selection { SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) maTabs[rTab]->MergeSelectionPattern( aState, rMark, bDeep ); } } if ( rMark.IsMarked() ) // single selection { const ScRange& aRange = rMark.GetMarkArea(); SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) maTabs[rTab]->MergePatternArea( aState, aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row(), bDeep ); } } OSL_ENSURE( aState.pItemSet, "SelectionPattern Null" ); if (aState.pItemSet) { std::unique_ptr pPattern(new ScPatternAttr( std::move(*aState.pItemSet) )); if (aState.mbValidPatternId) pPattern->SetKey(aState.mnPatternId); return pPattern; } else return std::unique_ptr(new ScPatternAttr( GetPool() )); // empty } const ScPatternAttr* ScDocument::GetSelectionPattern( const ScMarkData& rMark ) { pSelectionAttr = CreateSelectionPattern( rMark ); return pSelectionAttr.get(); } void ScDocument::GetSelectionFrame( const ScMarkData& rMark, SvxBoxItem& rLineOuter, SvxBoxInfoItem& rLineInner ) { rLineOuter.SetLine(nullptr, SvxBoxItemLine::TOP); rLineOuter.SetLine(nullptr, SvxBoxItemLine::BOTTOM); rLineOuter.SetLine(nullptr, SvxBoxItemLine::LEFT); rLineOuter.SetLine(nullptr, SvxBoxItemLine::RIGHT); rLineOuter.SetAllDistances(0); rLineInner.SetLine(nullptr, SvxBoxInfoItemLine::HORI); rLineInner.SetLine(nullptr, SvxBoxInfoItemLine::VERT); rLineInner.SetTable(true); rLineInner.SetDist(true); rLineInner.SetMinDist(false); ScLineFlags aFlags; if( rMark.IsMultiMarked() ) { ScRangeList aRangeList; rMark.FillRangeListWithMarks( &aRangeList, false ); size_t nRangeCount = aRangeList.size(); bool bMultipleRows = false, bMultipleCols = false; for( size_t nRangeIdx = 0; nRangeIdx < nRangeCount; ++nRangeIdx ) { const ScRange & rRange = aRangeList[ nRangeIdx ]; bMultipleRows = ( bMultipleRows || ( rRange.aStart.Row() != rRange.aEnd.Row() ) ); bMultipleCols = ( bMultipleCols || ( rRange.aStart.Col() != rRange.aEnd.Col() ) ); SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) maTabs[rTab]->MergeBlockFrame( &rLineOuter, &rLineInner, aFlags, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row() ); } } rLineInner.EnableHor( bMultipleRows ); rLineInner.EnableVer( bMultipleCols ); } else if( rMark.IsMarked() ) { const ScRange& aRange = rMark.GetMarkArea(); rLineInner.EnableHor( aRange.aStart.Row() != aRange.aEnd.Row() ); rLineInner.EnableVer( aRange.aStart.Col() != aRange.aEnd.Col() ); SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) maTabs[rTab]->MergeBlockFrame( &rLineOuter, &rLineInner, aFlags, aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row() ); } } // Evaluate don't care Status rLineInner.SetValid( SvxBoxInfoItemValidFlags::LEFT, ( aFlags.nLeft != SC_LINE_DONTCARE ) ); rLineInner.SetValid( SvxBoxInfoItemValidFlags::RIGHT, ( aFlags.nRight != SC_LINE_DONTCARE ) ); rLineInner.SetValid( SvxBoxInfoItemValidFlags::TOP, ( aFlags.nTop != SC_LINE_DONTCARE ) ); rLineInner.SetValid( SvxBoxInfoItemValidFlags::BOTTOM, ( aFlags.nBottom != SC_LINE_DONTCARE ) ); rLineInner.SetValid( SvxBoxInfoItemValidFlags::HORI, ( aFlags.nHori != SC_LINE_DONTCARE ) ); rLineInner.SetValid( SvxBoxInfoItemValidFlags::VERT, ( aFlags.nVert != SC_LINE_DONTCARE ) ); } static HasAttrFlags OptimizeHasAttrib( HasAttrFlags nMask, const ScDocumentPool* pPool ) { if ( nMask & HasAttrFlags::Rotate ) { // Is attribute used in document? // (as in fillinfo) bool bAnyItem = false; for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_ROTATE_VALUE)) { // 90 or 270 degrees is former SvxOrientationItem - only look for other values // (see ScPatternAttr::GetCellOrientation) Degree100 nAngle = static_cast(pItem)->GetValue(); if ( nAngle && nAngle != 9000_deg100 && nAngle != 27000_deg100 ) { bAnyItem = true; break; } } if (!bAnyItem) nMask &= ~HasAttrFlags::Rotate; } return nMask; } bool ScDocument::HasAttrib( SCCOL nCol1, SCROW nRow1, SCTAB nTab1, SCCOL nCol2, SCROW nRow2, SCTAB nTab2, HasAttrFlags nMask ) const { nMask = OptimizeHasAttrib( nMask, mxPoolHelper->GetDocPool()); if (nMask == HasAttrFlags::NONE) return false; for (SCTAB i=nTab1; i<=nTab2 && i < static_cast(maTabs.size()); 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 ( IsLayoutRTL(i) ) return true; } if( maTabs[i]->HasAttrib( nCol1, nRow1, nCol2, nRow2, nMask )) return true; } return false; } bool ScDocument::HasAttrib( SCCOL nCol, SCROW nRow, SCTAB nTab, HasAttrFlags nMask, SCROW* nStartRow, SCROW* nEndRow ) const { nMask = OptimizeHasAttrib( nMask, mxPoolHelper->GetDocPool()); if (nMask == HasAttrFlags::NONE || nTab >= static_cast(maTabs.size())) { if( nStartRow ) *nStartRow = 0; if( nEndRow ) *nEndRow = MaxRow(); return false; } 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 ( IsLayoutRTL(nTab) ) { if( nStartRow ) *nStartRow = 0; if( nEndRow ) *nEndRow = MaxRow(); return true; } } return maTabs[nTab]->HasAttrib( nCol, nRow, nMask, nStartRow, nEndRow ); } bool ScDocument::HasAttrib( const ScRange& rRange, HasAttrFlags nMask ) const { return HasAttrib( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(), rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), nMask ); } void ScDocument::FindMaxRotCol( SCTAB nTab, RowInfo* pRowInfo, SCSIZE nArrCount, SCCOL nX1, SCCOL nX2 ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->FindMaxRotCol( pRowInfo, nArrCount, nX1, nX2 ); else { OSL_FAIL("FindMaxRotCol: wrong table"); } } void ScDocument::GetBorderLines( SCCOL nCol, SCROW nRow, SCTAB nTab, const SvxBorderLine** ppLeft, const SvxBorderLine** ppTop, const SvxBorderLine** ppRight, const SvxBorderLine** ppBottom ) const { //TODO: consider page limits for printing !!!!! const SvxBoxItem* pThisAttr = GetEffItem( nCol, nRow, nTab, ATTR_BORDER ); OSL_ENSURE(pThisAttr,"where is the attribute?"); const SvxBorderLine* pLeftLine = pThisAttr->GetLeft(); const SvxBorderLine* pTopLine = pThisAttr->GetTop(); const SvxBorderLine* pRightLine = pThisAttr->GetRight(); const SvxBorderLine* pBottomLine = pThisAttr->GetBottom(); if ( nCol > 0 ) { const SvxBorderLine* pOther = GetEffItem( nCol-1, nRow, nTab, ATTR_BORDER )->GetRight(); if ( ScHasPriority( pOther, pLeftLine ) ) pLeftLine = pOther; } if ( nRow > 0 ) { const SvxBorderLine* pOther = GetEffItem( nCol, nRow-1, nTab, ATTR_BORDER )->GetBottom(); if ( ScHasPriority( pOther, pTopLine ) ) pTopLine = pOther; } if ( nCol < MaxCol() ) { const SvxBorderLine* pOther = GetEffItem( nCol+1, nRow, nTab, ATTR_BORDER )->GetLeft(); if ( ScHasPriority( pOther, pRightLine ) ) pRightLine = pOther; } if ( nRow < MaxRow() ) { const SvxBorderLine* pOther = GetEffItem( nCol, nRow+1, nTab, ATTR_BORDER )->GetTop(); if ( ScHasPriority( pOther, pBottomLine ) ) pBottomLine = pOther; } if (ppLeft) *ppLeft = pLeftLine; if (ppTop) *ppTop = pTopLine; if (ppRight) *ppRight = pRightLine; if (ppBottom) *ppBottom = pBottomLine; } bool ScDocument::IsBlockEmpty(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab) const { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) if (maTabs[nTab]) return maTabs[nTab]->IsBlockEmpty( nStartCol, nStartRow, nEndCol, nEndRow ); OSL_FAIL("wrong table number"); return false; } void ScDocument::LockTable(SCTAB nTab) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->LockTable(); else { OSL_FAIL("wrong table number"); } } void ScDocument::UnlockTable(SCTAB nTab) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->UnlockTable(); else { OSL_FAIL("wrong table number"); } } bool ScDocument::IsBlockEditable( SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool* pOnlyNotBecauseOfMatrix /* = NULL */, bool bNoMatrixAtAll ) const { // import into read-only document is possible if (!bImportingXML && !mbChangeReadOnlyEnabled && mpShell && mpShell->IsReadOnly()) { if ( pOnlyNotBecauseOfMatrix ) *pOnlyNotBecauseOfMatrix = false; return false; } if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) if (maTabs[nTab]) return maTabs[nTab]->IsBlockEditable( nStartCol, nStartRow, nEndCol, nEndRow, pOnlyNotBecauseOfMatrix, bNoMatrixAtAll ); OSL_FAIL("wrong table number"); if ( pOnlyNotBecauseOfMatrix ) *pOnlyNotBecauseOfMatrix = false; return false; } bool ScDocument::IsSelectionEditable( const ScMarkData& rMark, bool* pOnlyNotBecauseOfMatrix /* = NULL */ ) const { // import into read-only document is possible if ( !bImportingXML && !mbChangeReadOnlyEnabled && mpShell && mpShell->IsReadOnly() ) { if ( pOnlyNotBecauseOfMatrix ) *pOnlyNotBecauseOfMatrix = false; return false; } const ScRange& aRange = rMark.GetMarkArea(); bool bOk = true; bool bMatrix = ( pOnlyNotBecauseOfMatrix != nullptr ); SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if ( maTabs[rTab] ) { if (rMark.IsMarked()) { if ( !maTabs[rTab]->IsBlockEditable( aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row(), pOnlyNotBecauseOfMatrix ) ) { bOk = false; if ( pOnlyNotBecauseOfMatrix ) bMatrix = *pOnlyNotBecauseOfMatrix; } } if (rMark.IsMultiMarked()) { if ( !maTabs[rTab]->IsSelectionEditable( rMark, pOnlyNotBecauseOfMatrix ) ) { bOk = false; if ( pOnlyNotBecauseOfMatrix ) bMatrix = *pOnlyNotBecauseOfMatrix; } } } if (!bOk && !bMatrix) break; } if ( pOnlyNotBecauseOfMatrix ) *pOnlyNotBecauseOfMatrix = ( !bOk && bMatrix ); return bOk; } bool ScDocument::HasSelectedBlockMatrixFragment( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, const ScMarkData& rMark ) const { bool bOk = true; SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; 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. return false; ScAddress aOrigin = rCellPos; if (!pFCell->GetMatrixOrigin(*this, aOrigin)) // Failed to get the address of the matrix origin. return false; if (aOrigin != rCellPos) { pFCell = GetFormulaCell(aOrigin); if (!pFCell) // The matrix origin cell is not a formula cell !? Something is up... return false; } 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. return false; ScAddress aEnd( aOrigin.Col() + nSizeX - 1, aOrigin.Row() + nSizeY - 1, aOrigin.Tab() ); rMatrix.aStart = aOrigin; rMatrix.aEnd = aEnd; return true; } void ScDocument::ExtendOverlapped( SCCOL& rStartCol, SCROW& rStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab ) const { if ( ValidColRow(rStartCol,rStartRow) && ValidColRow(nEndCol,nEndRow) && ValidTab(nTab) ) { if (nTab < static_cast(maTabs.size()) && maTabs[nTab]) { SCCOL nCol; SCCOL nOldCol = rStartCol; SCROW nOldRow = rStartRow; for (nCol=nOldCol; nCol<=nEndCol; nCol++) while (GetAttr(nCol,rStartRow,nTab,ATTR_MERGE_FLAG)->IsVerOverlapped()) --rStartRow; //TODO: pass on ? const ScAttrArray& pAttrArray = maTabs[nTab]->ColumnData(nOldCol).AttrArray(); SCSIZE nIndex; if ( pAttrArray.Count() ) pAttrArray.Search( nOldRow, nIndex ); else nIndex = 0; SCROW nAttrPos = nOldRow; while (nAttrPos<=nEndRow) { OSL_ENSURE( nIndex < pAttrArray.Count(), "Wrong index in AttrArray" ); bool bHorOverlapped; if ( pAttrArray.Count() ) bHorOverlapped = pAttrArray.mvData[nIndex].pPattern->GetItem(ATTR_MERGE_FLAG).IsHorOverlapped(); else bHorOverlapped = GetDefPattern()->GetItem(ATTR_MERGE_FLAG).IsHorOverlapped(); if ( bHorOverlapped ) { SCROW nEndRowSeg = (pAttrArray.Count()) ? pAttrArray.mvData[nIndex].nEndRow : MaxRow(); SCROW nLoopEndRow = std::min( nEndRow, nEndRowSeg ); for (SCROW nAttrRow = nAttrPos; nAttrRow <= nLoopEndRow; nAttrRow++) { SCCOL nTempCol = nOldCol; do --nTempCol; while (GetAttr(nTempCol,nAttrRow,nTab,ATTR_MERGE_FLAG)->IsHorOverlapped()); if (nTempCol < rStartCol) rStartCol = nTempCol; } } if ( pAttrArray.Count() ) { nAttrPos = pAttrArray.mvData[nIndex].nEndRow + 1; ++nIndex; } else nAttrPos = MaxRow() + 1; } } } else { OSL_FAIL("ExtendOverlapped: invalid range"); } } void ScDocument::ExtendMergeSel( SCCOL nStartCol, SCROW nStartRow, SCCOL& rEndCol, SCROW& rEndRow, const ScMarkData& rMark, bool bRefresh ) { // use all selected sheets from rMark SCCOL nOldEndCol = rEndCol; SCROW nOldEndRow = rEndRow; SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if ( maTabs[rTab] ) { SCCOL nThisEndCol = nOldEndCol; SCROW nThisEndRow = nOldEndRow; ExtendMerge( nStartCol, nStartRow, nThisEndCol, nThisEndRow, rTab, bRefresh ); if ( nThisEndCol > rEndCol ) rEndCol = nThisEndCol; if ( nThisEndRow > rEndRow ) rEndRow = nThisEndRow; } } } bool ScDocument::ExtendMerge( SCCOL nStartCol, SCROW nStartRow, SCCOL& rEndCol, SCROW& rEndRow, SCTAB nTab, bool bRefresh ) { bool bFound = false; if ( ValidColRow(nStartCol,nStartRow) && ValidColRow(rEndCol,rEndRow) && ValidTab(nTab) ) { if (nTab < static_cast(maTabs.size()) && maTabs[nTab]) bFound = maTabs[nTab]->ExtendMerge( nStartCol, nStartRow, rEndCol, rEndRow, bRefresh ); if (bRefresh) RefreshAutoFilter( nStartCol, nStartRow, rEndCol, rEndRow, nTab ); } else { OSL_FAIL("ExtendMerge: invalid range"); } return bFound; } bool ScDocument::ExtendMerge( ScRange& rRange, bool bRefresh ) { bool bFound = false; SCTAB nStartTab = rRange.aStart.Tab(); SCTAB nEndTab = rRange.aEnd.Tab(); SCCOL nEndCol = rRange.aEnd.Col(); SCROW nEndRow = rRange.aEnd.Row(); PutInOrder( nStartTab, nEndTab ); for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < static_cast(maTabs.size()); nTab++ ) { SCCOL nExtendCol = rRange.aEnd.Col(); SCROW nExtendRow = rRange.aEnd.Row(); if (ExtendMerge( rRange.aStart.Col(), rRange.aStart.Row(), nExtendCol, nExtendRow, nTab, bRefresh ) ) { bFound = true; if (nExtendCol > nEndCol) nEndCol = nExtendCol; if (nExtendRow > nEndRow) nEndRow = nExtendRow; } } rRange.aEnd.SetCol(nEndCol); rRange.aEnd.SetRow(nEndRow); return bFound; } 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(this)->ExtendMerge( aExt )) return; if ( aExt.aEnd.Row() > rRange.aEnd.Row() ) { ScRange aTest = aExt; aTest.aStart.SetRow( rRange.aEnd.Row() + 1 ); if ( HasAttrib( aTest, HasAttrFlags::NotOverlapped ) ) aExt.aEnd.SetRow(rRange.aEnd.Row()); } if ( aExt.aEnd.Col() > rRange.aEnd.Col() ) { ScRange aTest = aExt; aTest.aStart.SetCol( rRange.aEnd.Col() + 1 ); if ( HasAttrib( aTest, HasAttrFlags::NotOverlapped ) ) aExt.aEnd.SetCol(rRange.aEnd.Col()); } rRange = aExt; } void ScDocument::ExtendOverlapped( ScRange& rRange ) const { SCTAB nStartTab = rRange.aStart.Tab(); SCTAB nEndTab = rRange.aEnd.Tab(); SCCOL nStartCol = rRange.aStart.Col(); SCROW nStartRow = rRange.aStart.Row(); PutInOrder( nStartTab, nEndTab ); for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < static_cast(maTabs.size()); nTab++ ) { SCCOL nExtendCol = rRange.aStart.Col(); SCROW nExtendRow = rRange.aStart.Row(); ExtendOverlapped( nExtendCol, nExtendRow, rRange.aEnd.Col(), rRange.aEnd.Row(), nTab ); if (nExtendCol < nStartCol) { nStartCol = nExtendCol; } if (nExtendRow < nStartRow) { nStartRow = nExtendRow; } } rRange.aStart.SetCol(nStartCol); rRange.aStart.SetRow(nStartRow); } bool ScDocument::RefreshAutoFilter( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab ) { SCTAB nDBTab; SCCOL nDBStartCol; SCROW nDBStartRow; SCCOL nDBEndCol; SCROW nDBEndRow; // Delete Autofilter bool bChange = RemoveFlagsTab( nStartCol,nStartRow, nEndCol,nEndRow, nTab, ScMF::Auto ); // Set Autofilter const ScDBData* pData = nullptr; ScDBCollection::NamedDBs& rDBs = pDBCollection->getNamedDBs(); for (const auto& rxDB : rDBs) { if (rxDB->HasAutoFilter()) { rxDB->GetArea(nDBTab, nDBStartCol,nDBStartRow, nDBEndCol,nDBEndRow); if ( nDBTab==nTab && nDBStartRow<=nEndRow && nDBEndRow>=nStartRow && nDBStartCol<=nEndCol && nDBEndCol>=nStartCol ) { if (ApplyFlagsTab( nDBStartCol,nDBStartRow, nDBEndCol,nDBStartRow, nDBTab, ScMF::Auto )) bChange = true; } } } if (nTab < static_cast(maTabs.size()) && maTabs[nTab]) pData = maTabs[nTab]->GetAnonymousDBData(); else pData=nullptr; if (pData && pData->HasAutoFilter()) { pData->GetArea( nDBTab, nDBStartCol,nDBStartRow, nDBEndCol,nDBEndRow ); if ( nDBTab==nTab && nDBStartRow<=nEndRow && nDBEndRow>=nStartRow && nDBStartCol<=nEndCol && nDBEndCol>=nStartCol ) { if (ApplyFlagsTab( nDBStartCol,nDBStartRow, nDBEndCol,nDBStartRow, nDBTab, ScMF::Auto )) bChange = true; } } return bChange; } void ScDocument::SkipOverlapped( SCCOL& rCol, SCROW& rRow, SCTAB nTab ) const { while (IsHorOverlapped(rCol, rRow, nTab)) --rCol; while (IsVerOverlapped(rCol, rRow, nTab)) --rRow; } bool ScDocument::IsHorOverlapped( SCCOL nCol, SCROW nRow, SCTAB nTab ) const { const ScMergeFlagAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG ); if (pAttr) return pAttr->IsHorOverlapped(); else { OSL_FAIL("Overlapped: Attr==0"); return false; } } bool ScDocument::IsVerOverlapped( SCCOL nCol, SCROW nRow, SCTAB nTab, SCROW* nStartRow, SCROW* nEndRow ) const { SCROW dummy; const ScMergeFlagAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG, nStartRow ? *nStartRow : dummy, nEndRow ? *nEndRow : dummy ); if (pAttr) return pAttr->IsVerOverlapped(); else { OSL_FAIL("Overlapped: Attr==0"); return false; } } void ScDocument::ApplySelectionFrame( const ScMarkData& rMark, const SvxBoxItem& rLineOuter, const SvxBoxInfoItem* pLineInner ) { ScRangeList aRangeList; rMark.FillRangeListWithMarks( &aRangeList, false ); size_t nRangeCount = aRangeList.size(); SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) { for ( size_t j=0; j < nRangeCount; j++ ) { const ScRange & rRange = aRangeList[ j ]; maTabs[rTab]->ApplyBlockFrame( rLineOuter, pLineInner, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row() ); } } } if (!rLineOuter.IsRemoveAdjacentCellBorder()) return; SvxBoxItem aTmp0(rLineOuter); aTmp0.SetLine( nullptr, SvxBoxItemLine::TOP ); aTmp0.SetLine( nullptr, SvxBoxItemLine::BOTTOM ); aTmp0.SetLine( nullptr, SvxBoxItemLine::LEFT ); aTmp0.SetLine( nullptr, SvxBoxItemLine::RIGHT ); SvxBoxItem aLeft( aTmp0 ); SvxBoxItem aRight( aTmp0 ); SvxBoxItem aTop( aTmp0 ); SvxBoxItem aBottom( aTmp0 ); SvxBoxInfoItem aTmp1( *pLineInner ); aTmp1.SetTable( false ); aTmp1.SetLine( nullptr, SvxBoxInfoItemLine::HORI ); aTmp1.SetLine( nullptr, SvxBoxInfoItemLine::VERT ); aTmp1.SetValid( SvxBoxInfoItemValidFlags::ALL, false ); aTmp1.SetValid( SvxBoxInfoItemValidFlags::DISTANCE ); SvxBoxInfoItem aLeftInfo( aTmp1 ); SvxBoxInfoItem aRightInfo( aTmp1 ); SvxBoxInfoItem aTopInfo( aTmp1 ); SvxBoxInfoItem aBottomInfo( aTmp1 ); if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::TOP ) && !rLineOuter.GetTop()) aTopInfo.SetValid( SvxBoxInfoItemValidFlags::BOTTOM ); if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::BOTTOM ) && !rLineOuter.GetBottom()) aBottomInfo.SetValid( SvxBoxInfoItemValidFlags::TOP ); if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::LEFT ) && !rLineOuter.GetLeft()) aLeftInfo.SetValid( SvxBoxInfoItemValidFlags::RIGHT ); if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::RIGHT ) && !rLineOuter.GetRight()) aRightInfo.SetValid( SvxBoxInfoItemValidFlags::LEFT ); const ScRangeList& rRangeListTopEnvelope = rMark.GetTopEnvelope(); const ScRangeList& rRangeListBottomEnvelope = rMark.GetBottomEnvelope(); const ScRangeList& rRangeListLeftEnvelope = rMark.GetLeftEnvelope(); const ScRangeList& rRangeListRightEnvelope = rMark.GetRightEnvelope(); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if ( maTabs[rTab] ) { size_t nEnvelopeRangeCount = rRangeListTopEnvelope.size(); for ( size_t j=0; j < nEnvelopeRangeCount; j++ ) { const ScRange & rRange = rRangeListTopEnvelope[ j ]; maTabs[rTab]->ApplyBlockFrame( aTop, &aTopInfo, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row() ); } nEnvelopeRangeCount = rRangeListBottomEnvelope.size(); for ( size_t j=0; j < nEnvelopeRangeCount; j++ ) { const ScRange & rRange = rRangeListBottomEnvelope[ j ]; maTabs[rTab]->ApplyBlockFrame( aBottom, &aBottomInfo, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row() ); } nEnvelopeRangeCount = rRangeListLeftEnvelope.size(); for ( size_t j=0; j < nEnvelopeRangeCount; j++ ) { const ScRange & rRange = rRangeListLeftEnvelope[ j ]; maTabs[rTab]->ApplyBlockFrame( aLeft, &aLeftInfo, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row() ); } nEnvelopeRangeCount = rRangeListRightEnvelope.size(); for ( size_t j=0; j < nEnvelopeRangeCount; j++ ) { const ScRange & rRange = rRangeListRightEnvelope[ j ]; maTabs[rTab]->ApplyBlockFrame( aRight, &aRightInfo, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row() ); } } } } void ScDocument::ApplyFrameAreaTab(const ScRange& rRange, const SvxBoxItem& rLineOuter, const SvxBoxInfoItem& rLineInner) { SCTAB nStartTab = rRange.aStart.Tab(); SCTAB nEndTab = rRange.aStart.Tab(); for (SCTAB nTab=nStartTab; nTab<=nEndTab && nTab < static_cast(maTabs.size()); nTab++) if (maTabs[nTab]) maTabs[nTab]->ApplyBlockFrame(rLineOuter, &rLineInner, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()); } void ScDocument::ApplySelectionPattern( const ScPatternAttr& rAttr, const ScMarkData& rMark, ScEditDataArray* pDataArray, bool* const pIsChanged ) { const SfxItemSet* pSet = &rAttr.GetItemSet(); bool bSet = false; sal_uInt16 i; for (i=ATTR_PATTERN_START; i<=ATTR_PATTERN_END && !bSet; i++) if (pSet->GetItemState(i) == SfxItemState::SET) bSet = true; if (!bSet) return; // ApplySelectionCache needs multi mark if ( rMark.IsMarked() && !rMark.IsMultiMarked() ) { const ScRange& aRange = rMark.GetMarkArea(); ApplyPatternArea( aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row(), rMark, rAttr, pDataArray, pIsChanged ); } else { SfxItemPoolCache aCache( mxPoolHelper->GetDocPool(), pSet ); SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) maTabs[rTab]->ApplySelectionCache( &aCache, rMark, pDataArray, pIsChanged ); } } } void ScDocument::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark ) { SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) maTabs[rTab]->ChangeSelectionIndent( bIncrement, rMark ); } } void ScDocument::ClearSelectionItems( const sal_uInt16* pWhich, const ScMarkData& rMark ) { SCTAB nMax = static_cast(maTabs.size()); for (const auto& rTab : rMark) { if (rTab >= nMax) break; if (maTabs[rTab]) maTabs[rTab]->ClearSelectionItems( pWhich, rMark ); } } void ScDocument::DeleteSelection( InsertDeleteFlags nDelFlag, const ScMarkData& rMark, bool bBroadcast ) { sc::AutoCalcSwitch aACSwitch(*this, false); std::vector 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 = static_cast(maTabs.size()); for (const auto& 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); } } void ScDocument::DeleteSelectionTab( SCTAB nTab, InsertDeleteFlags nDelFlag, const ScMarkData& rMark ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) { sc::AutoCalcSwitch aACSwitch(*this, false); std::vector 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(); } maTabs[nTab]->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"); } } ScPatternAttr* ScDocument::GetDefPattern() const { return const_cast(&mxPoolHelper->GetDocPool()->GetDefaultItem(ATTR_PATTERN)); } ScDocumentPool* ScDocument::GetPool() { return mxPoolHelper ? mxPoolHelper->GetDocPool() : nullptr; } ScStyleSheetPool* ScDocument::GetStyleSheetPool() const { return mxPoolHelper ? mxPoolHelper->GetStylePool() : nullptr; } bool ScDocument::IsEmptyData(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab) const { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) return maTabs[nTab]->IsEmptyData(nStartCol, nStartRow, nEndCol, nEndRow); return true; } SCSIZE ScDocument::GetEmptyLinesInBlock( SCCOL nStartCol, SCROW nStartRow, SCTAB nStartTab, SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab, ScDirection eDir ) { PutInOrder(nStartCol, nEndCol); PutInOrder(nStartRow, nEndRow); PutInOrder(nStartTab, nEndTab); if (ValidTab(nStartTab) && nStartTab < static_cast(maTabs.size())) { if (maTabs[nStartTab]) return maTabs[nStartTab]->GetEmptyLinesInBlock(nStartCol, nStartRow, nEndCol, nEndRow, eDir); else return 0; } else return 0; } void ScDocument::FindAreaPos( SCCOL& rCol, SCROW& rRow, SCTAB nTab, ScMoveDirection eDirection ) const { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) maTabs[nTab]->FindAreaPos( rCol, rRow, eDirection ); } void ScDocument::GetNextPos( SCCOL& rCol, SCROW& rRow, SCTAB nTab, SCCOL nMovX, SCROW nMovY, bool bMarked, bool bUnprotected, const ScMarkData& rMark, SCCOL nTabStartCol ) const { OSL_ENSURE( !nMovX || !nMovY, "GetNextPos: only X or Y" ); ScMarkData aCopyMark = rMark; aCopyMark.SetMarking(false); aCopyMark.MarkToMulti(); if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) maTabs[nTab]->GetNextPos( rCol, rRow, nMovX, nMovY, bMarked, bUnprotected, aCopyMark, nTabStartCol ); } // Data operations void ScDocument::UpdStlShtPtrsFrmNms() { ScDocumentPool* pPool = mxPoolHelper->GetDocPool(); for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_PATTERN)) { auto pPattern = const_cast(dynamic_cast(pItem)); if (pPattern) pPattern->UpdateStyleSheet(*this); } const_cast(pPool->GetDefaultItem(ATTR_PATTERN)).UpdateStyleSheet(*this); } void ScDocument::StylesToNames() { ScDocumentPool* pPool = mxPoolHelper->GetDocPool(); for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_PATTERN)) { auto pPattern = const_cast(dynamic_cast(pItem)); if (pPattern) pPattern->StyleToName(); } const_cast(pPool->GetDefaultItem(ATTR_PATTERN)).StyleToName(); } sal_uInt64 ScDocument::GetCellCount() const { sal_uInt64 nCellCount = 0; for (const auto& a : maTabs) { if (a) nCellCount += a->GetCellCount(); } return nCellCount; } sal_uInt64 ScDocument::GetFormulaGroupCount() const { sal_uInt64 nFormulaGroupCount = 0; ScFormulaGroupIterator aIter( *const_cast(this) ); for ( sc::FormulaGroupEntry* ptr = aIter.first(); ptr; ptr = aIter.next()) { nFormulaGroupCount++; } return nFormulaGroupCount; } sal_uInt64 ScDocument::GetCodeCount() const { sal_uInt64 nCodeCount = 0; for (const auto& a : maTabs) { if (a) nCodeCount += a->GetCodeCount(); } return nCodeCount; } void ScDocument::PageStyleModified( SCTAB nTab, const OUString& rNewName ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->PageStyleModified( rNewName ); } void ScDocument::SetPageStyle( SCTAB nTab, const OUString& rName ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->SetPageStyle( rName ); } OUString ScDocument::GetPageStyle( SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetPageStyle(); return OUString(); } void ScDocument::SetPageSize( SCTAB nTab, const Size& rSize ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->SetPageSize( rSize ); } Size ScDocument::GetPageSize( SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->GetPageSize(); OSL_FAIL("invalid tab"); return Size(); } void ScDocument::SetRepeatArea( SCTAB nTab, SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCROW nEndRow ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->SetRepeatArea( nStartCol, nEndCol, nStartRow, nEndRow ); } void ScDocument::InvalidatePageBreaks(SCTAB nTab) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) maTabs[nTab]->InvalidatePageBreaks(); } void ScDocument::UpdatePageBreaks( SCTAB nTab, const ScRange* pUserArea ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->UpdatePageBreaks( pUserArea ); } void ScDocument::RemoveManualBreaks( SCTAB nTab ) { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) maTabs[nTab]->RemoveManualBreaks(); } bool ScDocument::HasManualBreaks( SCTAB nTab ) const { if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) return maTabs[nTab]->HasManualBreaks(); OSL_FAIL("invalid tab"); return false; } void ScDocument::GetDocStat( ScDocStat& rDocStat ) { rDocStat.nTableCount = GetTableCount(); rDocStat.aDocName = aDocName; rDocStat.nFormulaCount = GetFormulaGroupCount(); rDocStat.nCellCount = GetCellCount(); } bool ScDocument::HasPrintRange() { bool bResult = false; for (const auto& a : maTabs) { if (!a) continue; bResult = a->IsPrintEntireSheet() || (a->GetPrintRangeCount() > 0); if (bResult) break; } return bResult; } bool ScDocument::IsPrintEntireSheet( SCTAB nTab ) const { return (ValidTab(nTab) ) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsPrintEntireSheet(); } sal_uInt16 ScDocument::GetPrintRangeCount( SCTAB nTab ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) return maTabs[nTab]->GetPrintRangeCount(); return 0; } const ScRange* ScDocument::GetPrintRange( SCTAB nTab, sal_uInt16 nPos ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) return maTabs[nTab]->GetPrintRange(nPos); return nullptr; } std::optional ScDocument::GetRepeatColRange( SCTAB nTab ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) return maTabs[nTab]->GetRepeatColRange(); return std::nullopt; } std::optional ScDocument::GetRepeatRowRange( SCTAB nTab ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) return maTabs[nTab]->GetRepeatRowRange(); return std::nullopt; } void ScDocument::ClearPrintRanges( SCTAB nTab ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) maTabs[nTab]->ClearPrintRanges(); } void ScDocument::AddPrintRange( SCTAB nTab, const ScRange& rNew ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) maTabs[nTab]->AddPrintRange( rNew ); } void ScDocument::SetPrintEntireSheet( SCTAB nTab ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) maTabs[nTab]->SetPrintEntireSheet(); } void ScDocument::SetRepeatColRange( SCTAB nTab, std::optional oNew ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) maTabs[nTab]->SetRepeatColRange( std::move(oNew) ); } void ScDocument::SetRepeatRowRange( SCTAB nTab, std::optional oNew ) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) maTabs[nTab]->SetRepeatRowRange( std::move(oNew) ); } std::unique_ptr ScDocument::CreatePrintRangeSaver() const { const SCTAB nCount = static_cast(maTabs.size()); std::unique_ptr pNew(new ScPrintRangeSaver( nCount )); for (SCTAB i=0; iFillPrintSaver( pNew->GetTabData(i) ); return pNew; } void ScDocument::RestorePrintRanges( const ScPrintRangeSaver& rSaver ) { const SCTAB nCount = rSaver.GetTabCount(); const SCTAB maxIndex = std::min(nCount, static_cast(maTabs.size())); for (SCTAB i=0; iRestorePrintRanges( rSaver.GetTabData(i) ); } 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 < static_cast(maTabs.size()) && 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 ) return true; // Specify page number in new template } } } return false; // otherwise not } SfxUndoManager* ScDocument::GetUndoManager() { if (!mpUndoManager) { // to support enhanced text edit for draw objects, use an SdrUndoManager ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE); SdrUndoManager* pUndoManager = new SdrUndoManager; pUndoManager->SetDocShell(GetDocumentShell()); mpUndoManager = pUndoManager; } return mpUndoManager; } ScRowBreakIterator* ScDocument::GetRowBreakIterator(SCTAB nTab) const { if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) return new ScRowBreakIterator(maTabs[nTab]->maRowPageBreaks); return nullptr; } void ScDocument::AddSubTotalCell(ScFormulaCell* pCell) { maSubTotalCells.insert(pCell); } void ScDocument::RemoveSubTotalCell(ScFormulaCell* pCell) { maSubTotalCells.erase(pCell); } namespace { bool lcl_hasDirtyRange(const ScDocument& rDoc, ScFormulaCell* pCell, const ScRange& rDirtyRange) { ScDetectiveRefIter aRefIter(rDoc, pCell); ScRange aRange; while (aRefIter.GetNextRef(aRange)) { if (aRange.Intersects(rDirtyRange)) return true; } return false; } } void ScDocument::SetSubTotalCellsDirty(const ScRange& rDirtyRange) { // to update the list by skipping cells that no longer contain subtotal function. set 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. } sal_uInt16 ScDocument::GetTextWidth( const ScAddress& rPos ) const { SCTAB nTab = rPos.Tab(); if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) return maTabs[nTab]->GetTextWidth(rPos.Col(), rPos.Row()); return 0; } SvtScriptType ScDocument::GetScriptType( const ScAddress& rPos ) const { SCTAB nTab = rPos.Tab(); if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) return maTabs[nTab]->GetScriptType(rPos.Col(), rPos.Row()); return SvtScriptType::NONE; } void ScDocument::SetScriptType( const ScAddress& rPos, SvtScriptType nType ) { SCTAB nTab = rPos.Tab(); if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) maTabs[nTab]->SetScriptType(rPos.Col(), rPos.Row(), nType); } 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 (bVal != GetUndoManager()->IsUndoEnabled()) { GetUndoManager()->EnableUndo(bVal); if( mpDrawLayer ) mpDrawLayer->EnableUndo(bVal); } mbUndoEnabled = bVal; } void ScDocument::EnableUserInteraction( bool bVal ) { mbUserInteractionEnabled = bVal; } bool ScDocument::IsInVBAMode() const { if (!mpShell) return false; try { uno::Reference xVBA( mpShell->GetBasicContainer(), uno::UNO_QUERY); return xVBA.is() && xVBA->getVBACompatibilityMode(); } catch (const lang::NotInitializedException&) {} return false; } // Sparklines std::shared_ptr ScDocument::GetSparkline(ScAddress const& rPosition) { SCTAB nTab = rPosition.Tab(); if (ValidTab(nTab) && nTab < SCTAB(maTabs.size())) { return maTabs[nTab]->GetSparkline(rPosition.Col(), rPosition.Row()); } return std::shared_ptr(); } bool ScDocument::HasSparkline(ScAddress const & rPosition) { return bool(GetSparkline(rPosition)); } sc::Sparkline* ScDocument::CreateSparkline(ScAddress const& rPosition, std::shared_ptr const& pSparklineGroup) { SCTAB nTab = rPosition.Tab(); if (ValidTab(nTab) && nTab < SCTAB(maTabs.size())) { return maTabs[nTab]->CreateSparkline(rPosition.Col(), rPosition.Row(), pSparklineGroup); } return nullptr; } bool ScDocument::DeleteSparkline(ScAddress const & rPosition) { SCTAB nTab = rPosition.Tab(); if (TableExists(nTab)) { return maTabs[nTab]->DeleteSparkline(rPosition.Col(), rPosition.Row()); } return false; } sc::SparklineList* ScDocument::GetSparklineList(SCTAB nTab) { if (TableExists(nTab)) { return &maTabs[nTab]->GetSparklineList(); } return nullptr; } bool ScDocument::HasOneSparklineGroup(ScRange const& rRange) { std::shared_ptr pSparklineGroup; return GetSparklineGroupInRange(rRange, pSparklineGroup); } bool ScDocument::GetSparklineGroupInRange(ScRange const& rRange, std::shared_ptr& rGroup) { std::shared_ptr pFoundGroup; SCTAB nTab = rRange.aStart.Tab(); for (SCCOL nX = rRange.aStart.Col(); nX <= rRange.aEnd.Col(); nX++) { for (SCROW nY = rRange.aStart.Row(); nY <= rRange.aEnd.Row(); nY++) { auto pSparkline = GetSparkline(ScAddress(nX, nY, nTab)); if (!pSparkline) { return false; } else if (!pFoundGroup) { pFoundGroup = pSparkline->getSparklineGroup(); } else if (pFoundGroup != pSparkline->getSparklineGroup()) { return false; } } } rGroup = pFoundGroup; return true; } std::shared_ptr ScDocument::SearchSparklineGroup(tools::Guid const& rGuid) { for (auto const& rTable : maTabs) { if (!rTable) continue; auto& rSparklineList = rTable->GetSparklineList(); for (auto const& pSparklineGroup : rSparklineList.getSparklineGroups()) { if (pSparklineGroup->getID() == rGuid) return pSparklineGroup; } } return std::shared_ptr(); } // Notes ScPostIt* ScDocument::GetNote(const ScAddress& rPos) { return GetNote(rPos.Col(), rPos.Row(), rPos.Tab()); } ScPostIt* ScDocument::GetNote(SCCOL nCol, SCROW nRow, SCTAB nTab) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) return maTabs[nTab]->GetNote(nCol, nRow); else return nullptr; } void ScDocument::SetNote(const ScAddress& rPos, std::unique_ptr pNote) { return SetNote(rPos.Col(), rPos.Row(), rPos.Tab(), std::move(pNote)); } void ScDocument::SetNote(SCCOL nCol, SCROW nRow, SCTAB nTab, std::unique_ptr pNote) { if (ValidTab(nTab) && nTab < static_cast(maTabs.size())) maTabs[nTab]->SetNote(nCol, nRow, std::move(pNote)); } bool ScDocument::HasNote(const ScAddress& rPos) const { return HasNote(rPos.Col(), rPos.Row(), rPos.Tab()); } bool ScDocument::HasNote(SCCOL nCol, SCROW nRow, SCTAB nTab) const { if (!ValidColRow(nCol, nRow)) return false; const ScTable* pTab = FetchTable(nTab); if (!pTab) return false; if (nCol >= pTab->GetAllocatedColumnsCount()) return false; const ScPostIt* pNote = pTab->aCol[nCol].GetCellNote(nRow); return pNote != nullptr; } bool ScDocument::HasNote(SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow) const { const ScTable* pTab = FetchTable(nTab); if (!pTab) return false; nStartCol = pTab->ClampToAllocatedColumns(nStartCol); nEndCol = pTab->ClampToAllocatedColumns(nEndCol); for (SCCOL nCol = nStartCol; nCol < nEndCol; ++nCol) if (pTab->aCol[nCol].HasCellNote(nStartRow, nEndRow)) return true; return false; } bool ScDocument::HasColNotes(SCCOL nCol, SCTAB nTab) const { if (!ValidCol(nCol)) return false; const ScTable* pTab = FetchTable(nTab); if (!pTab) return false; if (nCol >= pTab->GetAllocatedColumnsCount()) return false; return pTab->aCol[nCol].HasCellNotes(); } bool ScDocument::HasTabNotes(SCTAB nTab) const { const ScTable* pTab = FetchTable(nTab); if ( !pTab ) return false; for (SCCOL nCol=0, nColSize = pTab->aCol.size(); nCol < nColSize; ++nCol) if ( HasColNotes(nCol, nTab) ) return true; return false; } bool ScDocument::HasNotes() const { for (SCTAB i = 0; i <= MAXTAB; ++i) { if (HasTabNotes(i)) return true; } return false; } std::unique_ptr ScDocument::ReleaseNote(const ScAddress& rPos) { ScTable* pTab = FetchTable(rPos.Tab()); if (!pTab) return nullptr; return pTab->ReleaseNote(rPos.Col(), rPos.Row()); } ScPostIt* ScDocument::GetOrCreateNote(const ScAddress& rPos) { if (HasNote(rPos)) return GetNote(rPos); else return CreateNote(rPos); } ScPostIt* ScDocument::CreateNote(const ScAddress& rPos) { ScPostIt* pPostIt = new ScPostIt(*this, rPos); SetNote(rPos, std::unique_ptr(pPostIt)); return pPostIt; } size_t ScDocument::GetNoteCount( SCTAB nTab, SCCOL nCol ) const { const ScTable* pTab = FetchTable(nTab); if (!pTab) return 0; return pTab->GetNoteCount(nCol); } void ScDocument::CreateAllNoteCaptions() { for (const auto& a : maTabs) { if (a) a->CreateAllNoteCaptions(); } } void ScDocument::ForgetNoteCaptions( const ScRangeList& rRanges, bool bPreserveData ) { for (size_t i = 0, n = rRanges.size(); i < n; ++i) { const ScRange & rRange = rRanges[i]; const ScAddress& s = rRange.aStart; const ScAddress& e = rRange.aEnd; for (SCTAB nTab = s.Tab(); nTab <= e.Tab(); ++nTab) { ScTable* pTab = FetchTable(nTab); if (!pTab) continue; pTab->ForgetNoteCaptions(s.Col(), s.Row(), e.Col(), e.Row(), bPreserveData); } } } CommentCaptionState ScDocument::GetAllNoteCaptionsState( const ScRangeList& rRanges ) { CommentCaptionState aTmpState = CommentCaptionState::ALLHIDDEN; CommentCaptionState aState = CommentCaptionState::ALLHIDDEN; bool bFirstControl = true; std::vector aNotes; for (size_t i = 0, n = rRanges.size(); i < n; ++i) { const ScRange & rRange = rRanges[i]; for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab ) { aState = maTabs[nTab]->GetAllNoteCaptionsState( rRange, aNotes ); 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; } else if(aTmpState != aState) { aState = CommentCaptionState::MIXED; return aState; } } } return aState; } ScAddress ScDocument::GetNotePosition( size_t nIndex ) const { for (size_t nTab = 0; nTab < maTabs.size(); ++nTab) { for (SCCOL nCol : GetAllocatedColumnsRange(nTab, 0, MaxCol())) { size_t nColNoteCount = GetNoteCount(nTab, nCol); if (!nColNoteCount) continue; if (nIndex >= nColNoteCount) { nIndex -= nColNoteCount; continue; } SCROW nRow = GetNotePosition(nTab, nCol, nIndex); if (nRow >= 0) return ScAddress(nCol, nRow, nTab); OSL_FAIL("note not found"); return ScAddress::INITIALIZE_INVALID; } } OSL_FAIL("note not found"); return ScAddress::INITIALIZE_INVALID; } ScAddress ScDocument::GetNotePosition( size_t nIndex, SCTAB nTab ) const { for (SCCOL nCol : GetAllocatedColumnsRange(nTab, 0, MaxCol())) { size_t nColNoteCount = GetNoteCount(nTab, nCol); if (!nColNoteCount) continue; if (nIndex >= nColNoteCount) { nIndex -= nColNoteCount; continue; } SCROW nRow = GetNotePosition(nTab, nCol, nIndex); if (nRow >= 0) return ScAddress(nCol, nRow, nTab); OSL_FAIL("note not found"); return ScAddress::INITIALIZE_INVALID; } OSL_FAIL("note not found"); return ScAddress::INITIALIZE_INVALID; } SCROW ScDocument::GetNotePosition( SCTAB nTab, SCCOL nCol, size_t nIndex ) const { const ScTable* pTab = FetchTable(nTab); if (!pTab) return -1; return pTab->GetNotePosition(nCol, nIndex); } void ScDocument::GetAllNoteEntries( std::vector& rNotes ) const { for (const auto & pTab : maTabs) { if (!pTab) continue; pTab->GetAllNoteEntries(rNotes); } } void ScDocument::GetAllNoteEntries( SCTAB nTab, std::vector& rNotes ) const { const ScTable* pTab = FetchTable(nTab); if (!pTab) return; return pTab->GetAllNoteEntries( rNotes ); } void ScDocument::GetNotesInRange( const ScRangeList& rRangeList, std::vector& rNotes ) const { for( size_t i = 0; i < rRangeList.size(); ++i) { const ScRange & rRange = rRangeList[i]; for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab ) { maTabs[nTab]->GetNotesInRange( rRange, rNotes ); } } } void ScDocument::GetUnprotectedCells( ScRangeList& rRangeList, SCTAB nTab ) const { maTabs[nTab]->GetUnprotectedCells( rRangeList ); } bool ScDocument::ContainsNotesInRange( const ScRangeList& rRangeList ) const { for( size_t i = 0; i < rRangeList.size(); ++i) { const ScRange & rRange = rRangeList[i]; for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab ) { bool bContainsNote = maTabs[nTab]->ContainsNotesInRange( rRange ); if(bContainsNote) return true; } } return false; } void ScDocument::SetAutoNameCache( std::unique_ptr pCache ) { pAutoNameCache = std::move(pCache); } thread_local ScDocumentThreadSpecific ScDocument::maThreadSpecific; ScRecursionHelper& ScDocument::GetRecursionHelper() { if (!IsThreadedGroupCalcInProgress()) { if (!maNonThreaded.xRecursionHelper) maNonThreaded.xRecursionHelper = std::make_unique(); return *maNonThreaded.xRecursionHelper; } else { if (!maThreadSpecific.xRecursionHelper) maThreadSpecific.xRecursionHelper = std::make_unique(); 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 } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */