/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
using ::editeng::SvxBorderLine; usingnamespace ::com::sun::star;
namespace WritingMode2 = ::com::sun::star::text::WritingMode2; using ::com::sun::star::uno::Sequence; using ::com::sun::star::sheet::TablePageBreakData; using ::std::set;
for (SCTAB i = 0; i < GetTableCount(); i++) if (maTabs[i])
{ if (aUpperName == maTabs[i]->GetUpperName())
{
rTab = i; returntrue;
}
}
rTab = 0; returnfalse;
}
std::vector<OUString> ScDocument::GetAllTableNames() const
{
std::vector<OUString> aNames;
aNames.reserve(maTabs.size()); for (constauto& a : maTabs)
{ // Positions need to be preserved for ScCompiler and address convention // context, so still push an empty string for NULL tabs.
OUString aName; if (a)
{ const ScTable& rTab = *a;
aName = rTab.GetName();
}
aNames.push_back(aName);
}
#if 1 // Restrict sheet names to what Excel accepts. /* TODO: We may want to remove this restriction for full ODFF compliance. * Merely loading and calculating ODF documents using these characters in * sheet names is not affected by this, but all sheet name editing and
* copying functionality is, maybe falling back to "Sheet4" or similar. */ for (sal_Int32 i = 0; i < nLen; ++i)
{ const sal_Unicode c = rName[i]; switch (c)
{ case':': case'\\': case'/': case'?': case'*': case'[': case']': // these characters are not allowed to match XL's convention. returnfalse; case'\'': if (i == 0 || i == nLen - 1) // single quote is not allowed at the first or last // character position. returnfalse; break;
}
} #endif
returntrue;
}
bool ScDocument::ValidNewTabName( const OUString& rName ) const
{ bool bValid = ValidTabName(rName); if (!bValid) returnfalse;
OUString aUpperName = ScGlobal::getCharClass().uppercase(rName); for (constauto& a : maTabs)
{ if (!a) continue; const OUString& rOldName = a->GetUpperName();
bValid = rOldName != aUpperName; if (!bValid) break;
} return bValid;
}
void ScDocument::CreateValidTabName(OUString& rName) const
{ if ( !ValidTabName(rName) )
{ // Find new one
// First test if the prefix is valid, if so only avoid doubles bool bPrefix = ValidTabName( aStrTable );
OSL_ENSURE(bPrefix, "Invalid Table Name");
SCTAB nDummy;
for (SCTAB i = GetTableCount() + 1; !bOk ; i++)
{
rName = aStrTable + OUString::number(static_cast<sal_Int32>(i)); if (bPrefix)
bOk = ValidNewTabName( rName ); else
bOk = !GetTable( rName, nDummy );
}
} else
{ // testing the supplied Name
if ( !ValidNewTabName(rName) )
{
SCTAB i = 1;
OUString aName; do
{
i++;
aName = rName + "_" + OUString::number(static_cast<sal_Int32>(i));
} while (!ValidNewTabName(aName) && (i < MAXTAB+1));
rName = aName;
}
}
}
void ScDocument::CreateValidTabNames(std::vector<OUString>& aNames, SCTAB nCount) const
{
aNames.clear();//ensure that the vector is empty
// First test if the prefix is valid, if so only avoid doubles bool bPrefix = ValidTabName( aStrTable );
OSL_ENSURE(bPrefix, "Invalid Table Name");
SCTAB nDummy;
SCTAB i = GetTableCount() + 1;
for (SCTAB j = 0; j < nCount; ++j)
{ bool bOk = false; while(!bOk)
{
rName = aStrTable;
rName.append(static_cast<sal_Int32>(i)); if (bPrefix)
bOk = ValidNewTabName( rName.toString() ); else
bOk = !GetTable( rName.toString(), nDummy );
i++;
}
aNames.push_back(rName.makeStringAndClear());
}
}
void ScDocument::AppendTabOnLoad(const OUString& rName)
{
SCTAB nTabCount = GetTableCount(); if (!ValidTab(nTabCount)) // max table count reached. No more tables. return;
void ScDocument::InvalidateStreamOnSave()
{ for (constauto& a : maTabs)
{ if (a)
a->SetStreamValid(false);
}
}
bool ScDocument::InsertTab(
SCTAB nPos, const OUString& rName, bool bExternalDocument, bool bUndoDeleteTab )
{ // auto-accept any in-process input to prevent move the cell into next sheet in online. if (comphelper::LibreOfficeKit::isActive()) if (ScModule* mod = ScModule::get(); !mod->IsFormulaMode())
mod->InputEnterHandler();
SCTAB nTabCount = GetTableCount(); bool bValid = ValidTab(nTabCount); if ( !bExternalDocument ) // else test rName == "'Doc'!Tab" first
bValid = (bValid && ValidNewTabName(rName)); if (bValid)
{ if (nPos == SC_TAB_APPEND || nPos >= nTabCount)
{
nPos = maTabs.size();
maTabs.emplace_back( new ScTable(*this, nTabCount, rName) ); if ( bExternalDocument )
maTabs[nTabCount]->SetVisible( false );
} else
{ if (ValidTab(nPos) && (nPos < nTabCount))
{
sc::RefUpdateInsertTabContext aCxt( *this, nPos, 1);
for (constauto& a : maTabs)
{ if (a)
a->UpdateInsertTab(aCxt);
}
maTabs.emplace(maTabs.begin() + nPos, new ScTable(*this, nPos, rName));
// UpdateBroadcastAreas must be called between UpdateInsertTab, // which ends listening, and StartAllListeners, to not modify // areas that are to be inserted by starting listeners.
UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,1); for (constauto& a : maTabs)
{ if (a)
a->UpdateCompile();
}
StartAllListeners();
if (pValidationList)
{
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
pValidationList->UpdateInsertTab(aCxt);
}
if (bValid)
{ if (nPos == SC_TAB_APPEND || nPos >= nTabCount)
{ for ( SCTAB i = 0; i < nNewSheets; ++i )
{
maTabs.emplace_back( new ScTable(*this, nTabCount + i, rNames.at(i)) );
}
} else
{ if (ValidTab(nPos) && (nPos < nTabCount))
{
sc::RefUpdateInsertTabContext aCxt( *this, nPos, nNewSheets);
ScRange aRange( 0,0,nPos, MaxCol(),MaxRow(),MAXTAB );
xColNameRanges->UpdateReference( URM_INSDEL, *this, aRange, 0,0,nNewSheets );
xRowNameRanges->UpdateReference( URM_INSDEL, *this, aRange, 0,0,nNewSheets ); if (pRangeName)
pRangeName->UpdateInsertTab(aCxt);
pDBCollection->UpdateReference(
URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets ); if (pDPCollection)
pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,nNewSheets ); if (pDetOpList)
pDetOpList->UpdateReference( *this, URM_INSDEL, aRange, 0,0,nNewSheets );
UpdateChartRef( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets );
UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0, nNewSheets ); if ( pUnoBroadcaster )
pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,nNewSheets ) );
for (constauto& a : maTabs)
{ if (a)
a->UpdateInsertTab(aCxt);
} for (SCTAB i = 0; i < nNewSheets; ++i)
{
maTabs.emplace(maTabs.begin() + nPos + i, new ScTable(*this, nPos + i, rNames.at(i)) );
}
// UpdateBroadcastAreas must be called between UpdateInsertTab, // which ends listening, and StartAllListeners, to not modify // areas that are to be inserted by starting listeners.
UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,nNewSheets); for (constauto& a : maTabs)
{ if (a)
a->UpdateCompile();
}
StartAllListeners();
if (pValidationList)
{
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
pValidationList->UpdateInsertTab(aCxt);
}
bValid = true;
} else
bValid = false;
}
}
if (bValid)
{
sc::SetFormulaDirtyContext aCxt;
SetAllFormulasDirty(aCxt);
}
for (auto & pTab : maTabs) if (pTab)
pTab->UpdateDeleteTab(aCxt);
// tdf#149502 make sure ScTable destructor called after the erase is finished, when // maTabs[x].nTab==x is true again, as it should be always true. // In the end of maTabs.erase, maTabs indexes change, but nTab updated before erase. // ~ScTable expect that maTabs[x].nTab==x so it shouldn't be called during erase.
ScTableUniquePtr pErasedTab = std::move(maTabs[nTab]);
maTabs.erase(maTabs.begin() + nTab); delete pErasedTab.release();
// UpdateBroadcastAreas must be called between UpdateDeleteTab, // which ends listening, and StartAllListeners, to not modify // areas that are to be inserted by starting listeners.
UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1); for (constauto& a : maTabs)
{ if (a)
a->UpdateCompile();
} // Excel-Filter deletes some Tables while loading, Listeners will // only be triggered after the loading is done. if ( !bInsertingFromOtherDoc )
{
StartAllListeners();
for (auto & pTab : maTabs) if (pTab)
pTab->UpdateDeleteTab(aCxt);
maTabs.erase(maTabs.begin() + nTab, maTabs.begin() + nTab + nSheets); // UpdateBroadcastAreas must be called between UpdateDeleteTab, // which ends listening, and StartAllListeners, to not modify // areas that are to be inserted by starting listeners.
UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1*nSheets); for (constauto& a : maTabs)
{ if (a)
a->UpdateCompile();
} // Excel-Filter deletes some Tables while loading, Listeners will // only be triggered after the loading is done. if ( !bInsertingFromOtherDoc )
{
StartAllListeners();
if (comphelper::LibreOfficeKit::isActive())
{
ScModelObj* pModel = GetDocumentShell()->GetModel();
SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
}
bValid = true;
}
} return bValid;
}
bool ScDocument::RenameTab( SCTAB nTab, const OUString& rName, bool bExternalDocument )
{ bool bValid = false;
SCTAB i; if (HasTable(nTab))
{ if ( bExternalDocument )
bValid = true; // composed name else
bValid = ValidTabName(rName); for (i = 0; i < GetTableCount() && bValid; i++)
{ if (maTabs[i] && (i != nTab))
{
OUString aOldName = maTabs[i]->GetName();
bValid = !ScGlobal::GetTransliteration().isEqual( rName, aOldName );
}
} if (bValid)
{ // #i75258# update charts before renaming, so they can get their live data objects. // Once the charts are live, the sheet can be renamed without problems. if ( pChartListenerCollection )
pChartListenerCollection->UpdateChartsContainingTab( nTab );
maTabs[nTab]->SetName(rName);
// If formulas refer to the renamed sheet, the TokenArray remains valid, // but the XML stream must be re-generated. for (constauto& pTable : maTabs)
{ if (pTable)
{
pTable->SetStreamValid( false ); // tdf#156815 Reset solver settings so next time they're loaded they come with // the updated sheet name
pTable->ResetSolverSettings();
}
}
if ( bImportingXML )
{ // #i57869# only set the LoadingRTL flag, the real setting (including mirroring) // is applied in SetImportingXML(false). This is so the shapes can be loaded in // normal LTR mode.
pTable->SetLoadingRTL( bRTL ); return;
}
pTable->SetLayoutRTL( bRTL ); // only sets the flag
pTable->SetDrawPageSize(true, true, eObjectHandling);
// objects are already repositioned via SetDrawPageSize, only writing mode is missing if (!mpDrawLayer) return;
SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
OSL_ENSURE(pPage,"Page ?"); if (!pPage) return;
bool ScDocument::IsNegativePage( SCTAB nTab ) const
{ // Negative page area is always used for RTL layout. // The separate method is used to find all RTL handling of drawing objects. return IsLayoutRTL( nTab );
}
/* ---------------------------------------------------------------------------- used search area:
GetCellArea - Only Data GetTableArea - Data / Attributes GetPrintArea - intended for character objects, sweeps attributes all the way to bottom / right
---------------------------------------------------------------------------- */
// Make sure the area only shrinks, and doesn't grow. if (rStartCol < nCol1)
rStartCol = nCol1; if (nCol2 < rEndCol)
rEndCol = nCol2; if (rStartRow < nRow1)
rStartRow = nRow1; if (nRow2 < rEndRow)
rEndRow = nRow2;
private:
sc::AutoCalcSwitch aSwitch; // first for ctor/dtor order, destroy second
ScBulkBroadcast aBulk; // second for ctor/dtor order, destroy first
};
bool bTest = true; bool bRet = false; bool bOldAutoCalc = GetAutoCalc();
SetAutoCalc( false ); // avoid multiple calculations bool oldDelayedDeleteBroadcasters = IsDelayedDeletingBroadcasters();
EnableDelayDeletingBroadcasters( true ); for ( i = nStartTab; i <= nEndTab && bTest && i < GetTableCount(); i++) if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
bTest &= maTabs[i]->TestInsertRow(nStartCol, nEndCol, nStartRow, nSize); if (bTest)
{ // UpdateBroadcastAreas have to be called before UpdateReference, so that entries // aren't shifted that would be rebuild at UpdateReference
std::vector<ScAddress> aGroupPos; do
{
aShiftedRange.aStart.SetTab(nTabRangeStart);
aShiftedRange.aEnd.SetTab(nTabRangeEnd);
// Collect all formula groups that will get split by the shifting, // and end all their listening. Record the position of the top // cell of the topmost group, and the position of the bottom cell // of the bottommost group.
EndListeningIntersectedGroups(aEndListenCxt, aShiftedRange, &aGroupPos);
sc::RefUpdateContext aCxt(*this);
aCxt.meMode = URM_INSDEL;
aCxt.maRange = aShiftedRange;
aCxt.mnRowDelta = nSize; do
{
aCxt.maRange.aStart.SetTab(nTabRangeStart);
aCxt.maRange.aEnd.SetTab(nTabRangeEnd);
UpdateReference(aCxt, pRefUndoDoc, false); // without drawing objects
} while (lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
// UpdateReference should have set "needs listening" flags to those // whose references have been modified. We also need to set this flag // to those that were in the groups that got split by shifting.
SetNeedsListeningGroups(aGroupPos);
for (i=nStartTab; i<=nEndTab && i < GetTableCount(); i++)
{ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
{
maTabs[i]->InsertRow( nStartCol, nEndCol, nStartRow, nSize );
maTabs[i]->CommentNotifyAddressChange(nStartCol, nStartRow, nEndCol, MaxRow());
}
}
// UpdateRef for drawing layer must be after inserting, // when the new row heights are known. for (i=nStartTab; i<=nEndTab && i < GetTableCount(); i++) if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
maTabs[i]->UpdateDrawRef( URM_INSDEL,
nStartCol, nStartRow, nStartTab, nEndCol, MaxRow(), nEndTab,
0, static_cast<SCROW>(nSize), 0 );
if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() )
{ // A new Listening is needed when references to deleted ranges are restored, // previous Listeners were removed in FormulaCell UpdateReference.
StartAllListeners();
} else
{ // Listeners have been removed in UpdateReference
StartNeededListeners();
// At least all cells using range names pointing relative to the // moved range must be recalculated, and all cells marked postponed // dirty. for (constauto& a : maTabs)
{ if (a)
a->SetDirtyIfPostponed();
}
sc::RefUpdateContext aCxt(*this); constbool bLastRowIncluded = (static_cast<SCROW>(nStartRow + nSize) == GetMaxRowCount() && ValidRow(nStartRow)); if ( ValidRow(nStartRow+nSize) || bLastRowIncluded )
{
lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
aCxt.meMode = URM_INSDEL;
aCxt.mnRowDelta = -static_cast<SCROW>(nSize); if (bLastRowIncluded)
{ // Last row is included, shift a virtually non-existent row in.
aCxt.maRange = ScRange( nStartCol, GetMaxRowCount(), nTabRangeStart, nEndCol, GetMaxRowCount(), nTabRangeEnd);
} else
{
aCxt.maRange = ScRange( nStartCol, nStartRow+nSize, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd);
} do
{
UpdateReference(aCxt, pRefUndoDoc, true, false);
} while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
}
if (pUndoOutline)
*pUndoOutline = false;
// Keep track of the positions of all formula groups that have been joined // during row deletion.
std::vector<ScAddress> aGroupPos;
for ( i = nStartTab; i <= nEndTab && i < GetTableCount(); i++)
{ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
{
maTabs[i]->DeleteRow(aCxt.maRegroupCols, nStartCol, nEndCol, nStartRow, nSize, pUndoOutline, &aGroupPos);
maTabs[i]->CommentNotifyAddressChange(nStartCol, nStartRow, nEndCol, MaxRow());
}
}
// Newly joined groups have some of their members still listening. We // need to make sure none of them are listening.
EndListeningGroups(aGroupPos);
// Mark all joined groups for group listening.
SetNeedsListeningGroups(aGroupPos);
if ( ValidRow(nStartRow+nSize) || bLastRowIncluded )
{ // Listeners have been removed in UpdateReference
StartNeededListeners();
// At least all cells using range names pointing relative to the moved // range must be recalculated, and all cells marked postponed dirty. for (constauto& a : maTabs)
{ if (a)
a->SetDirtyIfPostponed();
}
for (i = nStartTab; i <= nEndTab && i < GetTableCount(); i++) if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
maTabs[i]->InsertCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize);
if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() )
{ // A new Listening is needed when references to deleted ranges are restored, // previous Listeners were removed in FormulaCell UpdateReference.
StartAllListeners();
} else
{ // Listeners have been removed in UpdateReference
StartNeededListeners(); // At least all cells using range names pointing relative to the // moved range must be recalculated, and all cells marked postponed // dirty.
{
ScBulkBroadcast aBulkBroadcast(GetBASM(), SfxHintId::ScDataChanged);
std::for_each(maTabs.begin(), maTabs.end(), SetDirtyIfPostponedHandler());
} // Cells containing functions such as CELL, COLUMN or ROW may have // changed their values on relocation. Broadcast them.
{
BroadcastRecalcOnRefMoveGuard g(this);
std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
}
}
bRet = true;
}
EnableDelayDeletingBroadcasters( oldDelayedDeleteBroadcasters );
SetAutoCalc( bOldAutoCalc ); if ( bRet && pChartListenerCollection )
pChartListenerCollection->UpdateDirtyCharts(); return bRet;
}
sc::RefUpdateContext aCxt(*this); constbool bLastColIncluded = (static_cast<SCCOL>(nStartCol + nSize) == GetMaxColCount() && ValidCol(nStartCol)); if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) || bLastColIncluded )
{
lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
aCxt.meMode = URM_INSDEL;
aCxt.mnColDelta = -static_cast<SCCOL>(nSize); if (bLastColIncluded)
{ // Last column is included, shift a virtually non-existent column in.
aCxt.maRange = ScRange( GetMaxColCount(), nStartRow, nTabRangeStart, GetMaxColCount(), nEndRow, nTabRangeEnd);
} else
{
aCxt.maRange = ScRange( sal::static_int_cast<SCCOL>(nStartCol+nSize), nStartRow, nTabRangeStart,
MaxCol(), nEndRow, nTabRangeEnd);
} do
{
UpdateReference(aCxt, pRefUndoDoc, true, false);
} while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
}
if (pUndoOutline)
*pUndoOutline = false;
for (i = nStartTab; i <= nEndTab && i < GetTableCount(); ++i)
{ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
maTabs[i]->DeleteCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize, pUndoOutline);
}
if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) || bLastColIncluded )
{ // Listeners have been removed in UpdateReference
StartNeededListeners();
// At least all cells using range names pointing relative to the moved // range must be recalculated, and all cells marked postponed dirty. for (constauto& a : maTabs)
{ if (a)
a->SetDirtyIfPostponed();
}
if ( bInsCol && !CanInsertCol( aColRange ) ) // Cells at the edge ?
bOk = false; if ( bInsRow && !CanInsertRow( aRowRange ) ) // Cells at the edge ?
bOk = false;
if ( bInsCol || bDelCol )
{
aColRange.aEnd.SetCol(MaxCol()); if ( HasPartOfMerged(aColRange) )
bOk = false;
} if ( bInsRow || bDelRow )
{
aRowRange.aEnd.SetRow(MaxRow()); if ( HasPartOfMerged(aRowRange) )
bOk = false;
}
std::vector<ScAddress> aGroupPos; // Destroy and reconstruct listeners only if content is affected. bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag); if (bDelContent)
{ // Record the positions of top and/or bottom formula groups that intersect // the area borders.
sc::EndListeningContext aCxt(*this);
ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0); for (SCTAB i = 0; i < GetTableCount(); i++)
{ if (rMark.GetTableSelect(i))
{
aRange.aStart.SetTab(i);
aRange.aEnd.SetTab(i);
for (SCTAB i = 0; i < GetTableCount(); i++) if (maTabs[i]) if ( rMark.GetTableSelect(i) || bIsUndo )
maTabs[i]->DeleteArea(nCol1, nRow1, nCol2, nRow2, nDelFlag, bBroadcast, pBroadcastSpans);
if (!bDelContent) return;
// Re-start listeners on those top bottom groups that have been split.
SetNeedsListeningGroups(aGroupPos);
StartNeededListeners();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.