/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * 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 .
*/
if (!maRowManualBreaks.empty())
{ // Copy all breaks up to nStartRow (non-inclusive).
::std::set<SCROW>::iterator itr1 = maRowManualBreaks.lower_bound(nStartRow);
::std::set<SCROW> aNewBreaks(maRowManualBreaks.begin(), itr1);
// Copy all breaks from nStartRow (inclusive) to the last element, // but add nSize to each value.
::std::set<SCROW>::iterator itr2 = maRowManualBreaks.end(); for (; itr1 != itr2; ++itr1)
aNewBreaks.insert(static_cast<SCROW>(*itr1 + nSize));
if (!maRowManualBreaks.empty())
{ // Erase all manual breaks between nStartRow and nStartRow + nSize - 1 (inclusive).
std::set<SCROW>::iterator itr1 = maRowManualBreaks.lower_bound(nStartRow);
std::set<SCROW>::iterator itr2 = maRowManualBreaks.upper_bound(static_cast<SCROW>(nStartRow + nSize - 1));
maRowManualBreaks.erase(itr1, itr2);
// Copy all breaks from the 1st element up to nStartRow to the new container.
itr1 = maRowManualBreaks.lower_bound(nStartRow);
::std::set<SCROW> aNewBreaks(maRowManualBreaks.begin(), itr1);
// Copy all breaks from nStartRow to the last element, but subtract each value by nSize.
itr2 = maRowManualBreaks.end(); for (; itr1 != itr2; ++itr1)
aNewBreaks.insert(static_cast<SCROW>(*itr1 - nSize));
maRowManualBreaks.swap(aNewBreaks);
}
}
{ // scope for bulk broadcast
ScBulkBroadcast aBulkBroadcast( rDocument.GetBASM(), SfxHintId::ScDataChanged); for (SCCOL j=nStartCol; j<=ClampToAllocatedColumns(nEndCol); j++)
aCol[j].DeleteRow(nStartRow, nSize, pGroupPos);
}
auto range = GetAllocatedColumnsRange( rDocument.MaxCol() - static_cast<SCCOL>(nSize) + 1, rDocument.MaxCol() ); for (auto it = range.rbegin(); it != range.rend(); ++it ) if (! aCol[*it].TestInsertCol(nStartRow, nEndRow)) returnfalse;
returntrue;
}
void ScTable::InsertCol( const sc::ColumnSet& rRegroupCols, SCCOL nStartCol, SCROW nStartRow, SCROW nEndRow, SCSIZE nSize )
{ if (nStartRow==0 && nEndRow==rDocument.MaxRow())
{ if (mpColWidth && mpColFlags)
{
mpColWidth->InsertPreservingSize(nStartCol, nSize, STD_COL_WIDTH); // The inserted columns have the same widths as the columns, which were selected for insert. for (SCSIZE i=0; i < std::min(rDocument.MaxCol()-nSize-nStartCol, nSize); ++i)
mpColWidth->SetValue(nStartCol + i, mpColWidth->GetValue(nStartCol+i+nSize));
mpColFlags->InsertPreservingSize(nStartCol, nSize, CRFlags::NONE);
} if (pOutlineTable)
pOutlineTable->InsertCol( nStartCol, nSize );
if (!maColManualBreaks.empty())
{ // Copy all breaks up to nStartCol (non-inclusive).
::std::set<SCCOL>::iterator itr1 = maColManualBreaks.lower_bound(nStartCol);
::std::set<SCCOL> aNewBreaks(maColManualBreaks.begin(), itr1);
// Copy all breaks from nStartCol (inclusive) to the last element, // but add nSize to each value.
::std::set<SCCOL>::iterator itr2 = maColManualBreaks.end(); for (; itr1 != itr2; ++itr1)
aNewBreaks.insert(static_cast<SCCOL>(*itr1 + nSize));
maColManualBreaks.swap(aNewBreaks);
}
}
// Make sure there are enough columns at the end.
CreateColumnIfNotExists(std::min<SCCOL>(rDocument.MaxCol(), std::max(nStartCol, aCol.size()) + nSize - 1 )); if ((nStartRow == 0) && (nEndRow == rDocument.MaxRow()))
{ // Move existing columns back, this will swap last empty columns in the inserted place. for (SCCOL nCol = aCol.size() - 1 - nSize; nCol >= nStartCol; --nCol)
aCol[nCol].SwapCol(aCol[nCol+nSize]);
} else
{ for (SCSIZE i=0; static_cast<SCCOL>(i+nSize)+nStartCol < aCol.size(); i++)
aCol[aCol.size() - 1 - nSize - i].MoveTo(nStartRow, nEndRow, aCol[aCol.size() - 1 - i]);
}
if (!maColManualBreaks.empty())
{ // Erase all manual breaks between nStartCol and nStartCol + nSize - 1 (inclusive).
std::set<SCCOL>::iterator itr1 = maColManualBreaks.lower_bound(nStartCol);
std::set<SCCOL>::iterator itr2 = maColManualBreaks.upper_bound(static_cast<SCCOL>(nStartCol + nSize - 1));
maColManualBreaks.erase(itr1, itr2);
// Copy all breaks from the 1st element up to nStartCol to the new container.
itr1 = maColManualBreaks.lower_bound(nStartCol);
::std::set<SCCOL> aNewBreaks(maColManualBreaks.begin(), itr1);
// Copy all breaks from nStartCol to the last element, but subtract each value by nSize.
itr2 = maColManualBreaks.end(); for (; itr1 != itr2; ++itr1)
aNewBreaks.insert(static_cast<SCCOL>(*itr1 - nSize));
maColManualBreaks.swap(aNewBreaks);
}
}
for (SCCOL col = nStartCol; col <= ClampToAllocatedColumns(nStartCol + nSize - 1); ++col)
aCol[col].DeleteArea(nStartRow, nEndRow, InsertDeleteFlags::ALL, false);
for (size_t i = 0; i < aRangeList.size(); ++i)
{ const ScRange & rRange = aRangeList[i];
if((nDelFlag & InsertDeleteFlags::ATTRIB) && rRange.aStart.Tab() == nTab)
mpCondFormatList->DeleteArea( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row() );
} // TODO: In the future we may want to check if the table has been // really modified before setting the stream invalid.
SetStreamValid(false);
}
// copy content //local range names need to be copied first for formula cells if (!pTable->mpRangeName && mpRangeName)
pTable->mpRangeName.reset( new ScRangeName(*mpRangeName) );
nCol2 = ClampToAllocatedColumns(nCol2);
pTable->CreateColumnIfNotExists(nCol2); // prevent repeated resizing for ( SCCOL i = nCol1; i <= nCol2; i++)
aCol[i].CopyToClip(rCxt, nRow1, nRow2, pTable->CreateColumnIfNotExists(i)); // notes are handled at column level
// copy widths/heights, and only "hidden", "filtered" and "manual" flags // also for all preceding columns/rows, to have valid positions for drawing objects
if (mpColWidth && pTable->mpColWidth)
pTable->mpColWidth->CopyFrom(*mpColWidth, 0, nCol2);
pTable->CopyColHidden(*this, 0, nCol2);
pTable->CopyColFiltered(*this, 0, nCol2); if (pDBDataNoName)
pTable->SetAnonymousDBData(std::unique_ptr<ScDBData>(new ScDBData(*pDBDataNoName)));
bool isFormatDependentOnRange(const ScConditionalFormat& rFormat)
{ for (size_t i = 0; i < rFormat.size(); ++i) if (auto* entry = rFormat.GetEntry(i)) if (auto type = entry->GetType(); type == ScFormatEntry::Type::Colorscale
|| type == ScFormatEntry::Type::Databar
|| type == ScFormatEntry::Type::Iconset) returntrue; returnfalse;
}
bool isRangeDependentFormatNeedDeduplication(const ScRangeList& rOld, const ScRangeList& rNew)
{ // Are they two adjacent vectors? if (rOld.size() == 1 && rNew.size() == 1)
{ // Test vertical vectors if (rOld[0].aStart.Col() == rOld[0].aEnd.Col() && rNew[0].aStart.Col() == rNew[0].aEnd.Col()
&& rNew[0].aStart.Col() == rOld[0].aStart.Col())
{ if (rOld[0].aEnd.Row() == rNew[0].aStart.Row() - 1
|| rNew[0].aEnd.Row() == rOld[0].aStart.Row() - 1)
{ returntrue; // Two joining vertical vectors -> merge
}
} // Test horizontal vectors if (rOld[0].aStart.Row() == rOld[0].aEnd.Row() && rNew[0].aStart.Row() == rNew[0].aEnd.Row()
&& rNew[0].aStart.Row() == rOld[0].aStart.Row())
{ if (rOld[0].aEnd.Col() == rNew[0].aStart.Col() - 1
|| rNew[0].aEnd.Col() == rOld[0].aStart.Col() - 1)
{ returntrue; // Two joining horizontal vectors -> merge
}
}
}
// Is the new one fully included into the old one? for (auto& range : rNew) if (!rOld.Contains(range)) returnfalse; // Different ranges, no deduplication
returntrue; // New is completely inside old -> merge (in fact, this means "nothing to do")
}
if (isFormatDependentOnRange(*pOldFormat)
&& !isRangeDependentFormatNeedDeduplication(rDstRangeList, rNewRangeList)) returnfalse; // No deduplication, create new format
for (size_t i = 0; i < rNewRangeList.size(); ++i)
{
rDstRangeList.Join(rNewRangeList[i]);
}
rDocument.AddCondFormatData(rNewRangeList, nTab, pOldFormat->GetKey()); returntrue;
}
returnfalse;
}
}
void ScTable::CopyConditionalFormat( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
SCCOL nDx, SCROW nDy, const ScTable* pTable)
{
ScRange aOldRange( nCol1 - nDx, nRow1 - nDy, pTable->nTab, nCol2 - nDx, nRow2 - nDy, pTable->nTab);
ScRange aNewRange( nCol1, nRow1, nTab, nCol2, nRow2, nTab ); // Don't deduplicate when undoing or creating an Undo document! It would disallow correct undo bool bUndoContext = rDocument.IsUndo() || pTable->rDocument.IsUndo(); // Note that Undo documents use same pool as the original document bool bSameDoc = rDocument.GetStyleSheetPool() == pTable->rDocument.GetStyleSheetPool();
if (!bUndoContext && bSameDoc && pTable->nTab == nTab && CheckAndDeduplicateCondFormat(rDocument, mpCondFormatList->GetFormat(rxCondFormat->GetKey()), pNewFormat.get(), nTab))
{ continue;
}
sal_uInt32 nMax = 0; bool bDuplicate = false; for(constauto& rxCond : *mpCondFormatList)
{ // Check if there is the same format in the destination // If there is, then simply expand its range if (!bUndoContext && CheckAndDeduplicateCondFormat(rDocument, rxCond.get(), pNewFormat.get(), nTab))
{
bDuplicate = true; break;
}
if (rxCond->GetKey() > nMax)
nMax = rxCond->GetKey();
} // Do not add duplicate entries if (bDuplicate)
{ continue;
}
pNewFormat->SetKey(nMax + 1); auto pNewFormatTmp = pNewFormat.get();
mpCondFormatList->InsertNew(std::move(pNewFormat));
if (!(ValidColRow(nCol1, nRow1) && ValidColRow(nCol2, nRow2))) return;
CreateColumnIfNotExists(nCol2); for ( SCCOL i = nCol1; i <= nCol2; i++)
{
pTable->CreateColumnIfNotExists(i - nDx);
aCol[i].CopyFromClip(rCxt, nRow1, nRow2, nDy, pTable->aCol[i - nDx]); // notes are handles at column level
}
if (rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB)
{ // make sure that there are no old references to the cond formats
sal_uInt16 nWhichArray[2];
nWhichArray[0] = ATTR_CONDITIONAL;
nWhichArray[1] = 0; for ( SCCOL i = nCol1; i <= nCol2; ++i)
aCol[i].ClearItems(nRow1, nRow2, nWhichArray);
}
if ((rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB) == InsertDeleteFlags::NONE) return;
staticvoid lcl_SetTransposedPatternInRows(ScTable* pTransClip, SCROW nAttrRow1, SCROW nAttrRow2,
SCCOL nCol1, SCROW nRow1, SCROW nCombinedStartRow, SCCOL nCol, const CellAttributeHolder& rPatternHolder, bool bIncludeFiltered, const std::vector<SCROW>& rFilteredRows,
SCROW nRowDestOffset)
{ for (SCROW nRow = nAttrRow1; nRow <= nAttrRow2; nRow++)
{
size_t nFilteredRowAdjustment = 0; if (!bIncludeFiltered)
{ // aFilteredRows is sorted thus lower_bound() can be used. // lower_bound() has a logarithmic complexity O(log(n)) auto itRow1 = std::lower_bound(rFilteredRows.begin(), rFilteredRows.end(), nRow1); auto itRow = std::lower_bound(rFilteredRows.begin(), rFilteredRows.end(), nRow); bool bRefRowIsFiltered = itRow != rFilteredRows.end() && *itRow == nRow; if (bRefRowIsFiltered) continue;
// How many filtered rows are between the formula cell and the reference? // distance() has a constant complexity O(1) for vectors
nFilteredRowAdjustment = std::distance(itRow1, itRow);
}
if (bToUndoDoc && (nFlags & InsertDeleteFlags::ATTRIB) && nCol2 >= aCol.size())
{ // tdf#154044: Copy also the default column data
aDefaultColData.AttrArray().CopyArea(nRow1, nRow2, 0,
pDestTab->aDefaultColData.AttrArray());
}
if ((bToUndoDoc || bFromUndoDoc) && (nFlags & InsertDeleteFlags::CONTENTS) && mpRangeName)
{ // Copying formulas may create sheet-local named expressions on the // destination sheet. Add existing to Undo first. // During Undo restore the previous named expressions.
pDestTab->SetRangeName( std::unique_ptr<ScRangeName>( new ScRangeName( *GetRangeName()))); if (!pDestTab->rDocument.IsClipOrUndo())
{
ScDocShell* pDocSh = pDestTab->rDocument.GetDocumentShell(); if (pDocSh)
pDocSh->SetAreasChangedNeedBroadcast();
}
}
if (nFlags != InsertDeleteFlags::NONE)
{
InsertDeleteFlags nTempFlags( nFlags &
~InsertDeleteFlags( InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES)); // tdf#102364 - in some pathological cases CopyToTable() replacing cells with new cells // can lead to repetitive splitting and rejoining of the same formula group, which can get // quadratically expensive with large groups. So do the grouping just once at the end.
sc::DelayFormulaGroupingSwitch delayGrouping( pDestTab->rDocument, true );
pDestTab->CreateColumnIfNotExists(ClampToAllocatedColumns(nCol2)); // avoid repeated resizing for (SCCOL i = nCol1; i <= ClampToAllocatedColumns(nCol2); i++)
aCol[i].CopyToColumn(rCxt, nRow1, nRow2, bToUndoDoc ? nFlags : nTempFlags, bMarked,
pDestTab->CreateColumnIfNotExists(i), pMarkData, bAsLink, bGlobalNamesToLocal); // tdf#154044: Restore from the default column data if (bFromUndoDoc && (nFlags & InsertDeleteFlags::ATTRIB) && nCol2 >= aCol.size())
{
aDefaultColData.AttrArray().CopyArea(nRow1, nRow2, 0,
pDestTab->aDefaultColData.AttrArray());
SCCOL nMaxSetDefault = pDestTab->ClampToAllocatedColumns(nCol2); for (SCCOL i = aCol.size(); i <= nMaxSetDefault; i++)
aDefaultColData.AttrArray().CopyArea(nRow1, nRow2, 0,
pDestTab->aCol[i].AttrArray());
}
}
if (!bColRowFlags) // Column widths/Row heights/Flags return;
if (bToUndoDoc && (nFlags & InsertDeleteFlags::ATTRIB))
{
pDestTab->mpCondFormatList.reset(mpCondFormatList->Clone(pDestTab->rDocument));
}
if (pDBDataNoName)
{
std::unique_ptr<ScDBData> pNewDBData(new ScDBData(*pDBDataNoName));
SCCOL aCol1, aCol2;
SCROW aRow1, aRow2;
SCTAB aTab;
pNewDBData->GetArea(aTab, aCol1, aRow1, aCol2, aRow2);
pNewDBData->MoveTo(pDestTab->nTab, aCol1, aRow1, aCol2, aRow2);
pDestTab->SetAnonymousDBData(std::move(pNewDBData));
} // Charts have to be adjusted when hide/show
ScChartListenerCollection* pCharts = pDestTab->rDocument.GetChartListenerCollection();
bool bFlagChange = false;
if (nRow1 == 0 && nRow2 == rDocument.MaxRow() && mpColWidth && pDestTab->mpColWidth)
{ auto destTabColWidthIt = pDestTab->mpColWidth->begin() + nCol1; auto thisTabColWidthIt = mpColWidth->begin() + nCol1;
pDestTab->mpColWidth->CopyFrom(*mpColWidth, nCol1, nCol2);
pDestTab->mpColFlags->CopyFrom(*mpColFlags, nCol1, nCol2); for (SCCOL i = nCol1; i <= nCol2; ++i)
{ bool bThisHidden = ColHidden(i); bool bHiddenChange = (pDestTab->ColHidden(i) != bThisHidden); bool bChange = bHiddenChange || (*destTabColWidthIt != *thisTabColWidthIt);
pDestTab->SetColHidden(i, i, bThisHidden); //TODO: collect changes? if (bHiddenChange && pCharts)
pCharts->SetRangeDirty(ScRange(i, 0, nTab, i, rDocument.MaxRow(), nTab));
// Hidden flags. for (SCROW i = nRow1; i <= nRow2; ++i)
{
SCROW nLastRow; bool bHidden = RowHidden(i, nullptr, &nLastRow); if (nLastRow >= nRow2) // the last row shouldn't exceed the upper bound the caller specified.
nLastRow = nRow2;
bool bHiddenChanged = pDestTab->SetRowHidden(i, nLastRow, bHidden); if (bHiddenChanged && pCharts) // Hidden flags differ.
pCharts->SetRangeDirty(ScRange(0, i, nTab, rDocument.MaxCol(), nLastRow, nTab));
if (bHiddenChanged)
bFlagChange = true;
// Jump to the last row of the identical flag segment.
i = nLastRow;
}
// Filtered flags. for (SCROW i = nRow1; i <= nRow2; ++i)
{
SCROW nLastRow; bool bFiltered = RowFiltered(i, nullptr, &nLastRow); if (nLastRow >= nRow2) // the last row shouldn't exceed the upper bound the caller specified.
nLastRow = nRow2;
pDestTab->SetRowFiltered(i, nLastRow, bFiltered);
i = nLastRow;
}
pDestTab->SetRowManualBreaks(std::set(maRowManualBreaks));
}
if (bFlagChange)
pDestTab->InvalidatePageBreaks();
if ((nFlags & InsertDeleteFlags::CONTENTS) && mpRangeName)
{ // Undo sheet-local named expressions created during copying // formulas. If mpRangeName is not set then the Undo wasn't even // set to an empty ScRangeName map so don't "undo" that.
pDestTab->SetRangeName( std::unique_ptr<ScRangeName>( new ScRangeName( *GetRangeName()))); if (!pDestTab->rDocument.IsClipOrUndo())
{
ScDocShell* pDocSh = pDestTab->rDocument.GetDocumentShell(); if (pDocSh)
pDocSh->SetAreasChangedNeedBroadcast();
}
}
for ( SCCOL i = 0; i < aCol.size(); i++)
{ auto& rDestCol = pDestTab->CreateColumnIfNotExists(i); if ( i >= nCol1 && i <= nCol2 )
aCol[i].UndoToColumn(rCxt, nRow1, nRow2, nFlags, bMarked, rDestCol); else
aCol[i].CopyToColumn(rCxt, 0, rDocument.MaxRow(), InsertDeleteFlags::FORMULA, false, rDestCol);
}
if (nFlags & InsertDeleteFlags::ATTRIB)
pDestTab->mpCondFormatList.reset(mpCondFormatList->Clone(pDestTab->rDocument));
void ScTable::CreateAllNoteCaptions()
{ for (SCCOL i = 0; i < aCol.size(); ++i)
aCol[i].CreateAllNoteCaptions();
}
void ScTable::ForgetNoteCaptions( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, boolbPreserveData )
{ if (!ValidCol(nCol1) || !ValidCol(nCol2)) return; if ( nCol2 >= aCol.size() ) nCol2 = aCol.size() - 1; for (SCCOL i = nCol1; i <= nCol2; ++i)
aCol[i].ForgetNoteCaptions(nRow1, nRow2, bPreserveData);
}
void ScTable::CommentNotifyAddressChange( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
{ // Only in use in kit mode for now, but looks to me a good idea to revisit why (since OOo times) // on deleting/inserting a column that we generate all the captions, while on deleting/inserting // a row we do not. Presumably we should skip generating captions if we don't have to. if (!comphelper::LibreOfficeKit::isActive()) return;
if (!ValidCol(nCol1) || !ValidCol(nCol2)) return; if ( nCol2 >= aCol.size() ) nCol2 = aCol.size() - 1; for (SCCOL i = nCol1; i <= nCol2; ++i)
aCol[i].CommentNotifyAddressChange(nRow1, nRow2);
}
// Return value = new nArrY
SCSIZE ScTable::FillMaxRot( RowInfo* pRowInfo, SCSIZE nArrCount, SCCOL nX1, SCCOL nX2,
SCCOL nCol, SCROW nAttrRow1, SCROW nAttrRow2, SCSIZE nArrY, const ScPatternAttr* pPattern, const SfxItemSet* pCondSet,
FillMaxRotCacheMap* pCache )
{ // Use a cache to lookup nRotDir, because it gets expensive when painting large spreadsheets // with lots of conditional formatting.
ScRotateDir nRotDir; if (pCache)
{ auto aKey = std::make_pair(pPattern, pCondSet); auto it = pCache->find(aKey); if (it != pCache->end())
nRotDir = it->second; else
{
nRotDir = pPattern->GetRotateDir( pCondSet );
pCache->insert({aKey, nRotDir});
}
} else
nRotDir = pPattern->GetRotateDir( pCondSet );
if ( nRotDir != ScRotateDir::NONE )
{ bool bHit = true; if ( nCol+1 < nX1 ) // column to the left
bHit = ( nRotDir != ScRotateDir::Left ); elseif ( nCol > nX2+1 ) // column to the right
bHit = ( nRotDir != ScRotateDir::Right ); // ScRotateDir::Standard may now also be extended to the left
if ( nCol1 == nMaxCol2 )
{ // left and right column const MatrixEdge n = MatrixEdge::Left | MatrixEdge::Right;
nEdges = aCol[nCol1].GetBlockMatrixEdges( nRow1, nRow2, n, bNoMatrixAtAll ); if ((nEdges != MatrixEdge::Nothing) && (((nEdges & n)!=n) || (nEdges & (MatrixEdge::Inside|MatrixEdge::Open)))) returntrue; // left or right edge is missing or open
} else
{ // left column
nEdges = aCol[nCol1].GetBlockMatrixEdges(nRow1, nRow2, MatrixEdge::Left, bNoMatrixAtAll); if ((nEdges != MatrixEdge::Nothing) && ((!(nEdges & MatrixEdge::Left)) || (nEdges & (MatrixEdge::Inside|MatrixEdge::Open)))) returntrue; // left edge missing or open // right column
nEdges = aCol[nMaxCol2].GetBlockMatrixEdges(nRow1, nRow2, MatrixEdge::Right, bNoMatrixAtAll); if ((nEdges != MatrixEdge::Nothing) && ((!(nEdges & MatrixEdge::Right)) || (nEdges & (MatrixEdge::Inside|MatrixEdge::Open)))) returntrue; // right edge is missing or open
}
if (bNoMatrixAtAll)
{ for (SCCOL i=nCol1; i<=nMaxCol2; i++)
{
nEdges = aCol[i].GetBlockMatrixEdges( nRow1, nRow2, MatrixEdge::Nothing, bNoMatrixAtAll); if (nEdges != MatrixEdge::Nothing
&& (nEdges != (MatrixEdge::Top | MatrixEdge::Left | MatrixEdge::Bottom | MatrixEdge::Right))) returntrue;
}
} elseif ( nRow1 == nRow2 )
{ // Row on top and on bottom bool bOpen = false; const MatrixEdge n = MatrixEdge::Bottom | MatrixEdge::Top; for ( SCCOL i=nCol1; i<=nMaxCol2; i++)
{
nEdges = aCol[i].GetBlockMatrixEdges( nRow1, nRow1, n, bNoMatrixAtAll ); if (nEdges != MatrixEdge::Nothing)
{ if ( (nEdges & n) != n ) returntrue; // Top or bottom edge missing if (nEdges & MatrixEdge::Left)
bOpen = true; // left edge open, continue elseif ( !bOpen ) returntrue; // Something exist that has not been opened if (nEdges & MatrixEdge::Right)
bOpen = false; // Close right edge
}
} if ( bOpen ) returntrue;
} else
{ int j;
MatrixEdge n;
SCROW nR; // first top row, then bottom row for ( j=0, n = MatrixEdge::Top, nR=nRow1; j<2;
j++, n = MatrixEdge::Bottom, nR=nRow2)
{ bool bOpen = false; for ( SCCOL i=nCol1; i<=nMaxCol2; i++)
{
nEdges = aCol[i].GetBlockMatrixEdges( nR, nR, n, bNoMatrixAtAll ); if ( nEdges != MatrixEdge::Nothing)
{ // in top row no top edge respectively // in bottom row no bottom edge if ( (nEdges & n) != n ) returntrue; if (nEdges & MatrixEdge::Left)
bOpen = true; // open left edge, continue elseif ( !bOpen ) returntrue; // Something exist that has not been opened if (nEdges & MatrixEdge::Right)
bOpen = false; // Close right edge
}
} if ( bOpen ) returntrue;
}
} returnfalse;
}
bool bIsEditable = true; if ( nLockCount )
bIsEditable = false; elseif ( IsProtected() && !rDocument.IsScenario(nTab) )
{
bIsEditable = !HasAttrib( nCol1, nRow1, nCol2, nRow2, HasAttrFlags::Protected ); if (!bIsEditable)
{ // An enhanced protection permission may override the attribute.
bIsEditable = pTabProtection->isBlockEditable( ScRange( nCol1, nRow1, nTab, nCol2, nRow2, nTab));
} if (bIsEditable)
{ // If Sheet is protected and cells are not protected then // check the active scenario protect flag if this range is // on the active scenario range. Note the 'copy back' must also // be set to apply protection.
sal_uInt16 nScenTab = nTab+1; while(rDocument.IsScenario(nScenTab))
{
ScRange aEditRange(nCol1, nRow1, nScenTab, nCol2, nRow2, nScenTab); if(rDocument.IsActiveScenario(nScenTab) && rDocument.HasScenarioRange(nScenTab, aEditRange))
{
ScScenarioFlags nFlags;
rDocument.GetScenarioFlags(nScenTab,nFlags);
bIsEditable = !((nFlags & ScScenarioFlags::Protected) && (nFlags & ScScenarioFlags::TwoWay)); break;
}
nScenTab++;
}
}
} elseif (rDocument.IsScenario(nTab))
{ // Determine if the preceding sheet is protected
SCTAB nActualTab = nTab; do
{
nActualTab--;
} while(rDocument.IsScenario(nActualTab));
void ScTable::ApplyPatternArea( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, const ScPatternAttr& rAttr, ScEditDataArray* pDataArray, bool* const pIsChanged )
{ if (!ValidColRow(nStartCol, nStartRow) || !ValidColRow(nEndCol, nEndRow)) return;
PutInOrder(nStartCol, nEndCol);
PutInOrder(nStartRow, nEndRow);
SCCOL maxCol = nEndCol; if( nEndCol == GetDoc().MaxCol())
{ // For the same unallocated columns until the end we can change just the default.
maxCol = std::max( nStartCol, aCol.size()) - 1; if( maxCol >= 0 )
CreateColumnIfNotExists(maxCol); // Allocate needed different columns before changing the default.
aDefaultColData.ApplyPatternArea(nStartRow, nEndRow, rAttr, pDataArray, pIsChanged);
} for (SCCOL i = nStartCol; i <= maxCol; i++)
CreateColumnIfNotExists(i).ApplyPatternArea(nStartRow, nEndRow, rAttr, pDataArray, pIsChanged);
}
namespace
{
std::vector<ScAttrEntry> duplicateScAttrEntries(const std::vector<ScAttrEntry>& rOrigData)
{ // this can now just be copied, will do the right thing with the // ref-counted ScAttrEntry instances
std::vector<ScAttrEntry> aData(rOrigData); return aData;
}
}
void ScTable::SetAttrEntries( SCCOL nStartCol, SCCOL nEndCol, std::vector<ScAttrEntry> && vNewData)
{ if (!ValidCol(nStartCol) || !ValidCol(nEndCol)) return; if ( nEndCol == rDocument.MaxCol() )
{ if ( nStartCol < aCol.size() )
{ // If we would like set all columns to same attrs, then change only attrs for not existing columns
nEndCol = aCol.size() - 1; for (SCCOL i = nStartCol; i <= nEndCol; i++)
aCol[i].SetAttrEntries(duplicateScAttrEntries(vNewData));
aDefaultColData.SetAttrEntries(std::move(vNewData));
} else
{
CreateColumnIfNotExists( nStartCol - 1 );
aDefaultColData.SetAttrEntries(std::move(vNewData));
}
} else
{
CreateColumnIfNotExists( nEndCol ); for (SCCOL i = nStartCol; i < nEndCol; i++) // all but last need a copy
aCol[i].SetAttrEntries(duplicateScAttrEntries(vNewData));
aCol[nEndCol].SetAttrEntries( std::move(vNewData));
}
}
void ScTable::ApplyStyle( SCCOL nCol, SCROW nRow, const ScStyleSheet* rStyle )
{ if (ValidColRow(nCol,nRow)) // If column not exists then we need to create it
CreateColumnIfNotExists( nCol ).ApplyStyle( nRow, rStyle );
}
PutInOrder(nStartCol, nEndCol);
PutInOrder(nStartRow, nEndRow); if ( nEndCol == rDocument.MaxCol() )
{ if ( nStartCol < aCol.size() )
{ // If we would like set all columns to specific style, then change only default style for not existing columns
nEndCol = aCol.size() - 1; for (SCCOL i = nStartCol; i <= nEndCol; i++)
aCol[i].ApplyStyleArea(nStartRow, nEndRow, rStyle);
aDefaultColData.ApplyStyleArea(nStartRow, nEndRow, rStyle );
} else
{
CreateColumnIfNotExists( nStartCol - 1 );
aDefaultColData.ApplyStyleArea(nStartRow, nEndRow, rStyle );
}
} else
{
CreateColumnIfNotExists( nEndCol ); for (SCCOL i = nStartCol; i <= nEndCol; i++)
aCol[i].ApplyStyleArea(nStartRow, nEndRow, rStyle);
}
}
sal_uInt16 nOldHeight = mpRowHeights->getValue(nRow); if ( nNewHeight != nOldHeight )
{
mpRowHeights->setValue(nRow, nRow, nNewHeight);
InvalidatePageBreaks();
}
} else
{
OSL_FAIL("Invalid row number or no heights");
}
}
namespace {
/** * Check if the new pixel size is different from the old size between * specified ranges.
*/ bool lcl_pixelSizeChanged(
ScFlatUInt16RowSegments& rRowHeights, SCROW nStartRow, SCROW nEndRow,
sal_uInt16 nNewHeight, double nPPTY, bool bApi)
{
tools::Long nNewPix = static_cast<tools::Long>(nNewHeight * nPPTY);
ScFlatUInt16RowSegments::ForwardIterator aFwdIter(rRowHeights); for (SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow)
{
sal_uInt16 nHeight; if (!aFwdIter.getValue(nRow, nHeight)) break;
bool bSingle = false; // true = process every row for its own
ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer(); if (pDrawLayer) if (pDrawLayer->HasObjectsInRows( nTab, nStartRow, nEndRow ))
bSingle = true;
if (bSingle)
{
ScFlatUInt16RowSegments::RangeData aData; if (mpRowHeights->getRangeData(nStartRow, aData) &&
nNewHeight == aData.mnValue && nEndRow <= aData.mnRow2)
{
bSingle = false; // no difference in this range
}
}
// No idea why 20 is used here if (!bSingle || nEndRow - nStartRow < 20)
{
bChanged = lcl_pixelSizeChanged(*mpRowHeights, nStartRow, nEndRow, nNewHeight, nPPTY, bApi); if (bChanged)
mpRowHeights->setValue(nStartRow, nEndRow, nNewHeight);
} else
{
SCROW nMid = (nStartRow + nEndRow) / 2; // No idea why nPPTY is ignored in these recursive calls and instead 1.0 is used if (SetRowHeightRange(nStartRow, nMid, nNewHeight, 1.0, bApi))
bChanged = true; if (SetRowHeightRange(nMid + 1, nEndRow, nNewHeight, 1.0, bApi))
bChanged = true;
}
if (bChanged)
InvalidatePageBreaks();
} else
{
OSL_FAIL("Invalid row number or no heights");
}
if (ValidRow(nRow) && mpRowHeights)
{ if (bHiddenAsZero && RowHidden( nRow, pStartRow, pEndRow)) return 0; else
{
ScFlatUInt16RowSegments::RangeData aData; if (!mpRowHeights->getRangeData(nRow, aData))
{ if (pStartRow)
*pStartRow = nRow; if (pEndRow)
*pEndRow = nRow; // TODO: What should we return in case the search fails? return 0;
}
// If bHiddenAsZero, pStartRow and pEndRow were initialized to // boundaries of a non-hidden segment. Assume that the previous and // next segment are hidden then and limit the current height // segment. if (pStartRow)
*pStartRow = (bHiddenAsZero ? std::max( *pStartRow, aData.mnRow1) : aData.mnRow1); if (pEndRow)
*pEndRow = (bHiddenAsZero ? std::min( *pEndRow, aData.mnRow2) : aData.mnRow2); return aData.mnValue;
}
} else
{ if (pStartRow)
*pStartRow = nRow; if (pEndRow)
*pEndRow = nRow; return GetOptimalMinRowHeight();
}
}
if (ValidRow(nStartRow) && ValidRow(nEndRow) && mpRowHeights)
{
tools::Long nHeight = 0;
SCROW nRow = nStartRow; while (nRow <= nEndRow)
{
SCROW nLastRow = -1; if (!RowHidden(nRow, nullptr, &nLastRow))
{ if (nLastRow > nEndRow)
nLastRow = nEndRow;
// #i117315# can't use getSumValue, because individual values must be rounded
ScFlatUInt16RowSegments::ForwardIterator aSegmentIter(*mpRowHeights); while (nRow <= nLastRow)
{
sal_uInt16 nRowVal; if (!aSegmentIter.getValue(nRow, nRowVal)) return nHeight; // shouldn't happen
// #i12341# For Show/Hide rows, the outlines are updated separately from the outside. // For filtering, the changes aren't visible to the caller, so UpdateOutlineRow has // to be done here. if (pOutlineTable)
UpdateOutlineRow( nRow1, nRow2, bShow );
}
// #i116164# if there are no drawing objects within the row range, a single HeightChanged call is enough
ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer(); bool bHasObjects = pDrawLayer && pDrawLayer->HasObjectsInRows( nTab, nRow1, nRow2 );
if ( !bHasObjects )
{ // #i116164# set the flags for the whole range at once
SetRowHidden(nRow1, nRow2, !bShow); if (bShow)
SetRowFiltered(nRow1, nRow2, false);
}
}
bool ScTable::IsDataFiltered(SCCOL nColStart, SCROW nRowStart, SCCOL nColEnd, SCROW nRowEnd) const
{
assert(nColStart <= nColEnd && nRowStart <= nRowEnd
&& "range must be normalized to obtain a valid result"); for (SCROW i = nRowStart; i <= nRowEnd; ++i)
{ if (RowHidden(i)) returntrue;
} for (SCCOL i = nColStart; i <= nColEnd; ++i)
{ if (ColHidden(i)) returntrue;
} returnfalse;
}
SCROW ScTable::GetLastChangedRowFlagsWidth() const
{ if ( !pRowFlags ) return 0;
SCROW nLastFlags = GetLastFlaggedRow();
// Find the last row position where the height is NOT the standard row // height. // KOHEI: Test this to make sure it does what it's supposed to.
SCROW nLastHeight = mpRowHeights->findLastTrue(GetOptimalMinRowHeight()); if (!ValidRow(nLastHeight))
nLastHeight = 0;
// Calculate the size of the sheet and set the size on DrawPage
void ScTable::SetDrawPageSize(bool bResetStreamValid, bool bUpdateNoteCaptionPos,
ScObjectHandling eObjectHandling)
{
ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer(); if( pDrawLayer )
{ const sal_Int64 nMax = ::std::numeric_limits<tools::Long>::max(); // #i113884# Avoid int32 overflow with possible negative results than can cause bad effects. // If the draw page size is smaller than all rows, only the bottom of the sheet is affected.
tools::Long x = std::min(o3tl::convert(GetColOffset(rDocument.MaxCol() + 1),
o3tl::Length::twip, o3tl::Length::mm100),
nMax);
tools::Long y = std::min(o3tl::convert(GetRowOffset(rDocument.MaxRow() + 1),
o3tl::Length::twip, o3tl::Length::mm100),
nMax);
// #i102616# actions that modify the draw page size count as sheet modification // (exception: InitDrawLayer) if (bResetStreamValid)
SetStreamValid(false);
}
for (SCROW nRow = 0; nRow <= rDocument.MaxRow(); ++nRow)
{ // fetch hidden data range if necessary if (aHiddenRange.mnRow2 < nRow)
{ if (!mpHiddenRows->getRangeData(nRow, aHiddenRange)) // Failed to fetch the range data for whatever reason. break;
}
if (aHiddenRange.mbValue)
{ // This row is hidden. Skip ahead all hidden rows.
nRow = aHiddenRange.mnRow2; continue;
}
// fetch height data range if necessary if (aRowHeightRange.mnRow2 < nRow)
{ if (!mpRowHeights->getRangeData(nRow, aRowHeightRange)) // Failed to fetch the range data for whatever reason. break;
}
assert(aHiddenRange.mnRow1 <= nRow && aHiddenRange.mnRow2 >= nRow && "the current hidden-row span should overlap the current row");
assert(!aHiddenRange.mbValue && "the current hidden-row span should have visible==true");
assert(aRowHeightRange.mnRow1 <= nRow && aRowHeightRange.mnRow2 >= nRow && "the current height span should overlap the current row");
// find the last common row between hidden & height spans
SCROW nLastCommon = std::min(aHiddenRange.mnRow2, aRowHeightRange.mnRow2);
SCROW nCommonRows = nLastCommon - nRow + 1; // height of common span
tools::Long nCommonPixels = static_cast<tools::Long>(aRowHeightRange.mnValue) * nCommonRows;
// is the target height inside the common span ? if (nSum + nCommonPixels > nHeight)
{ // calculate how many rows to skip inside the common span
nRow += (nHeight - nSum) / aRowHeightRange.mnValue;
assert(aHiddenRange.mnRow1 <= nRow && aHiddenRange.mnRow2 >= nRow && "the current hidden-row span should overlap the current row");
assert(aRowHeightRange.mnRow1 <= nRow && aRowHeightRange.mnRow2 >= nRow && "the current height span should overlap the current row");
return nRow;
}
// skip the range and keep hunting
nSum += nCommonPixels;
nRow = nLastCommon;
} return -1;
}
// same as the one in viewdata.hxx static tools::Long ToPixel( sal_uInt16 nTwips, double nFactor )
{
tools::Long nRet = static_cast<tools::Long>( nTwips * nFactor ); if ( !nRet && nTwips )
nRet = 1; return nRet;
}
// We are iterating over two data arrays here, each of which // is a range/compressed view of the underlying data.
tools::Long nSumPx = rStartRowHeightPx;
for (SCROW nRow = nStartRow + 1; nRow <= rDocument.MaxRow(); ++nRow)
{ // fetch hidden data range if necessary if (aHiddenRange.mnRow2 < nRow)
{ if (!mpHiddenRows->getRangeData(nRow, aHiddenRange)) // Failed to fetch the range data for whatever reason. break;
}
if (aHiddenRange.mbValue)
{ // This row is hidden. Skip ahead all hidden rows.
nRow = aHiddenRange.mnRow2; continue;
}
// fetch height data range if necessary if (aRowHeightRange.mnRow2 < nRow)
{ if (!mpRowHeights->getRangeData(nRow, aRowHeightRange)) // Failed to fetch the range data for whatever reason. break;
}
assert(aHiddenRange.mnRow1 <= nRow && aHiddenRange.mnRow2 >= nRow && "the current hidden-row span should overlap the current row");
assert(!aHiddenRange.mbValue && "the current hidden-row span should have visible==true");
assert(aRowHeightRange.mnRow1 <= nRow && aRowHeightRange.mnRow2 >= nRow && "the current height span should overlap the current row");
// find the last common row between hidden & height spans
SCROW nLastCommon = std::min(aHiddenRange.mnRow2, aRowHeightRange.mnRow2);
SCROW nCommonRows = nLastCommon - nRow + 1; // height of common span
tools::Long nRowHeightPx = ToPixel(aRowHeightRange.mnValue, fPPTY);
tools::Long nCommonPixels = nRowHeightPx * nCommonRows;
// is the target height inside the common span ? if (nSumPx + nCommonPixels > nHeightPx)
{
assert(nRowHeightPx != 0); // calculate how many rows to skip inside the common span
SCROW nRowsInside = (nHeightPx - nSumPx) / nRowHeightPx;
nRow += nRowsInside;
assert(aHiddenRange.mnRow1 <= nRow && aHiddenRange.mnRow2 >= nRow && "the current hidden-row span should overlap the current row");
assert(aRowHeightRange.mnRow1 <= nRow && aRowHeightRange.mnRow2 >= nRow && "the current height span should overlap the current row");
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.74Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-04-26)
¤
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.