1
0
Fork 0
libreoffice/sc/source/core/data/column4.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

2284 lines
76 KiB
C++

/* -*- 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/.
*/
#include <column.hxx>
#include <clipparam.hxx>
#include <cellvalue.hxx>
#include <attarray.hxx>
#include <document.hxx>
#include <cellvalues.hxx>
#include <columnspanset.hxx>
#include <columniterator.hxx>
#include <mtvcellfunc.hxx>
#include <clipcontext.hxx>
#include <attrib.hxx>
#include <patattr.hxx>
#include <conditio.hxx>
#include <formulagroup.hxx>
#include <tokenarray.hxx>
#include <scitems.hxx>
#include <cellform.hxx>
#include <sharedformula.hxx>
#include <drwlayer.hxx>
#include <compiler.hxx>
#include <recursionhelper.hxx>
#include <docsh.hxx>
#include <broadcast.hxx>
#include <SparklineGroup.hxx>
#include <o3tl/safeint.hxx>
#include <svl/sharedstringpool.hxx>
#include <sal/log.hxx>
#include <tools/stream.hxx>
#include <numeric>
#include <vector>
#include <cassert>
sc::MultiDataCellState::StateType ScColumn::HasDataCellsInRange(
SCROW nRow1, SCROW nRow2, SCROW* pRow1 ) const
{
sc::CellStoreType::const_position_type aPos = maCells.position(nRow1);
sc::CellStoreType::const_iterator it = aPos.first;
size_t nOffset = aPos.second;
SCROW nRow = nRow1;
bool bHasOne = false; // whether or not we have found a non-empty block of size one.
for (; it != maCells.end() && nRow <= nRow2; ++it)
{
if (it->type != sc::element_type_empty)
{
// non-empty block found.
assert(it->size > 0); // mtv should never contain a block of zero length.
size_t nSize = it->size - nOffset;
SCROW nLastRow = nRow + nSize - 1;
if (nLastRow > nRow2)
// shrink the size to avoid exceeding the specified last row position.
nSize -= nLastRow - nRow2;
if (nSize == 1)
{
// this block is of size one.
if (bHasOne)
return sc::MultiDataCellState::HasMultipleCells;
bHasOne = true;
if (pRow1)
*pRow1 = nRow;
}
else
{
// size of this block is greater than one.
if (pRow1)
*pRow1 = nRow;
return sc::MultiDataCellState::HasMultipleCells;
}
}
nRow += it->size - nOffset;
nOffset = 0;
}
return bHasOne ? sc::MultiDataCellState::HasOneCell : sc::MultiDataCellState::Empty;
}
void ScColumn::DeleteBeforeCopyFromClip(
sc::CopyFromClipContext& rCxt, const ScColumn& rClipCol, sc::ColumnSpanSet& rBroadcastSpans )
{
ScDocument& rDocument = GetDoc();
sc::CopyFromClipContext::Range aRange = rCxt.getDestRange();
if (!rDocument.ValidRow(aRange.mnRow1) || !rDocument.ValidRow(aRange.mnRow2))
return;
sc::ColumnBlockPosition* pBlockPos = rCxt.getBlockPosition(nTab, nCol);
if (!pBlockPos)
return;
InsertDeleteFlags nDelFlag = rCxt.getDeleteFlag();
if (!rCxt.isSkipEmptyCells())
{
// Delete the whole destination range.
if (nDelFlag & InsertDeleteFlags::CONTENTS)
{
auto xResult = DeleteCells(*pBlockPos, aRange.mnRow1, aRange.mnRow2, nDelFlag);
rBroadcastSpans.set(GetDoc(), nTab, nCol, xResult->aDeletedRows, true);
for (const auto& rRange : xResult->aFormulaRanges)
rCxt.setListeningFormulaSpans(
nTab, nCol, rRange.first, nCol, rRange.second);
}
if (nDelFlag & InsertDeleteFlags::NOTE)
DeleteCellNotes(*pBlockPos, aRange.mnRow1, aRange.mnRow2, false);
if (nDelFlag & InsertDeleteFlags::SPARKLINES)
DeleteSparklineCells(*pBlockPos, aRange.mnRow1, aRange.mnRow2);
if (nDelFlag & InsertDeleteFlags::EDITATTR)
RemoveEditAttribs(*pBlockPos, aRange.mnRow1, aRange.mnRow2);
if (nDelFlag & InsertDeleteFlags::ATTRIB)
{
pAttrArray->DeleteArea(aRange.mnRow1, aRange.mnRow2);
if (rCxt.isTableProtected())
{
ScPatternAttr aPattern(rDocument.getCellAttributeHelper());
aPattern.GetItemSet().Put(ScProtectionAttr(false));
ApplyPatternArea(aRange.mnRow1, aRange.mnRow2, aPattern);
}
ScConditionalFormatList* pCondList = rCxt.getCondFormatList();
if (pCondList)
pCondList->DeleteArea(nCol, aRange.mnRow1, nCol, aRange.mnRow2);
}
else if ((nDelFlag & InsertDeleteFlags::HARDATTR) == InsertDeleteFlags::HARDATTR)
pAttrArray->DeleteHardAttr(aRange.mnRow1, aRange.mnRow2);
return;
}
ScRange aClipRange = rCxt.getClipDoc()->GetClipParam().getWholeRange();
SCROW nClipRow1 = aClipRange.aStart.Row();
SCROW nClipRow2 = aClipRange.aEnd.Row();
SCROW nClipRowLen = nClipRow2 - nClipRow1 + 1;
// Check for non-empty cell ranges in the clip column.
sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
aSpanSet.scan(rClipCol, nClipRow1, nClipRow2);
sc::SingleColumnSpanSet::SpansType aSpans;
aSpanSet.getSpans(aSpans);
if (aSpans.empty())
// All cells in the range in the clip are empty. Nothing to delete.
return;
// Translate the clip column spans into the destination column, and repeat as needed.
std::vector<sc::RowSpan> aDestSpans;
SCROW nDestOffset = aRange.mnRow1 - nClipRow1;
bool bContinue = true;
while (bContinue)
{
for (const sc::RowSpan& r : aSpans)
{
SCROW nDestRow1 = r.mnRow1 + nDestOffset;
SCROW nDestRow2 = r.mnRow2 + nDestOffset;
if (nDestRow1 > aRange.mnRow2)
{
// We're done.
bContinue = false;
break;
}
if (nDestRow2 > aRange.mnRow2)
{
// Truncate this range, and set it as the last span.
nDestRow2 = aRange.mnRow2;
bContinue = false;
}
aDestSpans.emplace_back(nDestRow1, nDestRow2);
if (!bContinue)
break;
}
nDestOffset += nClipRowLen;
}
for (const auto& rDestSpan : aDestSpans)
{
SCROW nRow1 = rDestSpan.mnRow1;
SCROW nRow2 = rDestSpan.mnRow2;
if (nDelFlag & InsertDeleteFlags::CONTENTS)
{
auto xResult = DeleteCells(*pBlockPos, nRow1, nRow2, nDelFlag);
rBroadcastSpans.set(GetDoc(), nTab, nCol, xResult->aDeletedRows, true);
for (const auto& rRange : xResult->aFormulaRanges)
rCxt.setListeningFormulaSpans(
nTab, nCol, rRange.first, nCol, rRange.second);
}
if (nDelFlag & InsertDeleteFlags::NOTE)
DeleteCellNotes(*pBlockPos, nRow1, nRow2, false);
if (nDelFlag & InsertDeleteFlags::SPARKLINES)
DeleteSparklineCells(*pBlockPos, nRow1, nRow2);
if (nDelFlag & InsertDeleteFlags::EDITATTR)
RemoveEditAttribs(*pBlockPos, nRow1, nRow2);
// Delete attributes just now
if (nDelFlag & InsertDeleteFlags::ATTRIB)
{
pAttrArray->DeleteArea(nRow1, nRow2);
if (rCxt.isTableProtected())
{
ScPatternAttr aPattern(rDocument.getCellAttributeHelper());
aPattern.GetItemSet().Put(ScProtectionAttr(false));
ApplyPatternArea(nRow1, nRow2, aPattern);
}
ScConditionalFormatList* pCondList = rCxt.getCondFormatList();
if (pCondList)
pCondList->DeleteArea(nCol, nRow1, nCol, nRow2);
}
else if ((nDelFlag & InsertDeleteFlags::HARDATTR) == InsertDeleteFlags::HARDATTR)
pAttrArray->DeleteHardAttr(nRow1, nRow2);
}
}
void ScColumn::CopyOneCellFromClip( sc::CopyFromClipContext& rCxt, SCROW nRow1, SCROW nRow2, size_t nColOffset )
{
assert(nRow1 <= nRow2);
size_t nDestSize = nRow2 - nRow1 + 1;
sc::ColumnBlockPosition* pBlockPos = rCxt.getBlockPosition(nTab, nCol);
if (!pBlockPos)
return;
ScDocument& rDocument = GetDoc();
bool bSameDocPool = (rCxt.getClipDoc()->GetPool() == rDocument.GetPool());
ScCellValue& rSrcCell = rCxt.getSingleCell(nColOffset);
sc::CellTextAttr& rSrcAttr = rCxt.getSingleCellAttr(nColOffset);
InsertDeleteFlags nFlags = rCxt.getInsertFlag();
if ((nFlags & InsertDeleteFlags::ATTRIB) != InsertDeleteFlags::NONE)
{
if (!rCxt.isSkipEmptyCells() || rSrcCell.getType() != CELLTYPE_NONE)
{
CellAttributeHolder aNewPattern;
if (bSameDocPool)
aNewPattern.setScPatternAttr(rCxt.getSingleCellPattern(nColOffset));
else
aNewPattern = rCxt.getSingleCellPattern(nColOffset)->MigrateToDocument( &rDocument, rCxt.getClipDoc());
pAttrArray->SetPatternArea(nRow1, nRow2, aNewPattern);
}
}
if ((nFlags & InsertDeleteFlags::CONTENTS) != InsertDeleteFlags::NONE)
{
std::vector<sc::CellTextAttr> aTextAttrs(nDestSize, rSrcAttr);
switch (rSrcCell.getType())
{
case CELLTYPE_VALUE:
{
std::vector<double> aVals(nDestSize, rSrcCell.getDouble());
pBlockPos->miCellPos =
maCells.set(pBlockPos->miCellPos, nRow1, aVals.begin(), aVals.end());
pBlockPos->miCellTextAttrPos =
maCellTextAttrs.set(pBlockPos->miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end());
CellStorageModified();
}
break;
case CELLTYPE_STRING:
{
// Compare the ScDocumentPool* to determine if we are copying within the
// same document. If not, re-intern shared strings.
svl::SharedStringPool* pSharedStringPool = (bSameDocPool ? nullptr : &rDocument.GetSharedStringPool());
svl::SharedString aStr = (pSharedStringPool ?
pSharedStringPool->intern( rSrcCell.getSharedString()->getString()) :
*rSrcCell.getSharedString());
std::vector<svl::SharedString> aStrs(nDestSize, aStr);
pBlockPos->miCellPos =
maCells.set(pBlockPos->miCellPos, nRow1, aStrs.begin(), aStrs.end());
pBlockPos->miCellTextAttrPos =
maCellTextAttrs.set(pBlockPos->miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end());
CellStorageModified();
}
break;
case CELLTYPE_EDIT:
{
std::vector<EditTextObject*> aStrs;
aStrs.reserve(nDestSize);
for (size_t i = 0; i < nDestSize; ++i)
aStrs.push_back(rSrcCell.getEditText()->Clone().release());
pBlockPos->miCellPos =
maCells.set(pBlockPos->miCellPos, nRow1, aStrs.begin(), aStrs.end());
pBlockPos->miCellTextAttrPos =
maCellTextAttrs.set(pBlockPos->miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end());
CellStorageModified();
}
break;
case CELLTYPE_FORMULA:
{
std::vector<sc::RowSpan> aRanges;
aRanges.reserve(1);
aRanges.emplace_back(nRow1, nRow2);
CloneFormulaCell(*pBlockPos, *rSrcCell.getFormula(), rSrcAttr, aRanges);
}
break;
default:
;
}
}
ScAddress aDestPosition(nCol, nRow1, nTab);
duplicateSparkline(rCxt, pBlockPos, nColOffset, nDestSize, aDestPosition);
// Notes
const ScPostIt* pNote = rCxt.getSingleCellNote(nColOffset);
if (!(pNote && (nFlags & (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES)) != InsertDeleteFlags::NONE))
return;
// Duplicate the cell note over the whole pasted range.
ScDocument* pClipDoc = rCxt.getClipDoc();
const ScAddress aSrcPos = pClipDoc->GetClipParam().getWholeRange().aStart;
std::vector<ScPostIt*> aNotes;
aNotes.reserve(nDestSize);
for (size_t i = 0; i < nDestSize; ++i)
{
bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
aNotes.push_back(pNote->Clone(aSrcPos, rDocument, aDestPosition, bCloneCaption).release());
aDestPosition.IncRow();
}
pBlockPos->miCellNotePos =
maCellNotes.set(
pBlockPos->miCellNotePos, nRow1, aNotes.begin(), aNotes.end());
// Notify our LOK clients.
aDestPosition.SetRow(nRow1);
for (size_t i = 0; i < nDestSize; ++i)
{
ScDocShell::LOKCommentNotify(LOKCommentNotificationType::Add, rDocument, aDestPosition, aNotes[i]);
aDestPosition.IncRow();
}
}
void ScColumn::duplicateSparkline(sc::CopyFromClipContext& rContext, sc::ColumnBlockPosition* pBlockPos,
size_t nColOffset, size_t nDestSize, ScAddress aDestPosition)
{
if ((rContext.getInsertFlag() & InsertDeleteFlags::SPARKLINES) == InsertDeleteFlags::NONE)
return;
const auto& pSparkline = rContext.getSingleSparkline(nColOffset);
if (pSparkline)
{
auto const& pSparklineGroup = pSparkline->getSparklineGroup();
auto pDuplicatedGroup = GetDoc().SearchSparklineGroup(pSparklineGroup->getID());
if (!pDuplicatedGroup)
pDuplicatedGroup = std::make_shared<sc::SparklineGroup>(*pSparklineGroup);
std::vector<sc::SparklineCell*> aSparklines(nDestSize, nullptr);
ScAddress aCurrentPosition = aDestPosition;
for (size_t i = 0; i < nDestSize; ++i)
{
auto pNewSparkline = std::make_shared<sc::Sparkline>(aCurrentPosition.Col(), aCurrentPosition.Row(), pDuplicatedGroup);
pNewSparkline->setInputRange(pSparkline->getInputRange());
aSparklines[i] = new sc::SparklineCell(std::move(pNewSparkline));
aCurrentPosition.IncRow();
}
pBlockPos->miSparklinePos = maSparklines.set(pBlockPos->miSparklinePos, aDestPosition.Row(), aSparklines.begin(), aSparklines.end());
}
}
void ScColumn::SetValues( const SCROW nRow, const std::vector<double>& rVals )
{
if (!GetDoc().ValidRow(nRow))
return;
SCROW nLastRow = nRow + rVals.size() - 1;
if (nLastRow > GetDoc().MaxRow())
// Out of bound. Do nothing.
return;
sc::CellStoreType::position_type aPos = maCells.position(nRow);
std::vector<SCROW> aNewSharedRows;
DetachFormulaCells(aPos, rVals.size(), &aNewSharedRows);
maCells.set(nRow, rVals.begin(), rVals.end());
std::vector<sc::CellTextAttr> aDefaults(rVals.size());
maCellTextAttrs.set(nRow, aDefaults.begin(), aDefaults.end());
CellStorageModified();
StartListeningUnshared( aNewSharedRows);
std::vector<SCROW> aRows;
aRows.reserve(rVals.size());
for (SCROW i = nRow; i <= nLastRow; ++i)
aRows.push_back(i);
BroadcastCells(aRows, SfxHintId::ScDataChanged);
}
void ScColumn::TransferCellValuesTo( SCROW nRow, size_t nLen, sc::CellValues& rDest )
{
if (!GetDoc().ValidRow(nRow))
return;
SCROW nLastRow = nRow + nLen - 1;
if (nLastRow > GetDoc().MaxRow())
// Out of bound. Do nothing.
return;
sc::CellStoreType::position_type aPos = maCells.position(nRow);
DetachFormulaCells(aPos, nLen, nullptr);
rDest.transferFrom(*this, nRow, nLen);
CellStorageModified();
std::vector<SCROW> aRows;
aRows.reserve(nLen);
for (SCROW i = nRow; i <= nLastRow; ++i)
aRows.push_back(i);
BroadcastCells(aRows, SfxHintId::ScDataChanged);
}
void ScColumn::CopyCellValuesFrom( SCROW nRow, const sc::CellValues& rSrc )
{
if (!GetDoc().ValidRow(nRow))
return;
SCROW nLastRow = nRow + rSrc.size() - 1;
if (nLastRow > GetDoc().MaxRow())
// Out of bound. Do nothing
return;
sc::CellStoreType::position_type aPos = maCells.position(nRow);
DetachFormulaCells(aPos, rSrc.size(), nullptr);
rSrc.copyTo(*this, nRow);
CellStorageModified();
std::vector<SCROW> aRows;
aRows.reserve(rSrc.size());
for (SCROW i = nRow; i <= nLastRow; ++i)
aRows.push_back(i);
BroadcastCells(aRows, SfxHintId::ScDataChanged);
}
namespace {
class ConvertFormulaToValueHandler
{
sc::CellValues maResValues;
ScDocument& mrDoc;
bool mbModified;
public:
ConvertFormulaToValueHandler(ScDocument& rDoc) :
mrDoc(rDoc),
mbModified(false)
{
maResValues.reset(mrDoc.GetSheetLimits().GetMaxRowCount());
}
void operator() ( size_t nRow, const ScFormulaCell* pCell )
{
sc::FormulaResultValue aRes = pCell->GetResult();
switch (aRes.meType)
{
case sc::FormulaResultValue::Value:
maResValues.setValue(nRow, aRes.mfValue);
break;
case sc::FormulaResultValue::String:
if (aRes.mbMultiLine)
{
std::unique_ptr<EditTextObject> pObj(mrDoc.CreateSharedStringTextObject(aRes.maString));
maResValues.setValue(nRow, std::move(pObj));
}
else
{
maResValues.setValue(nRow, aRes.maString);
}
break;
case sc::FormulaResultValue::Error:
case sc::FormulaResultValue::Invalid:
default:
maResValues.setValue(nRow, svl::SharedString::getEmptyString());
}
mbModified = true;
}
bool isModified() const { return mbModified; }
sc::CellValues& getResValues() { return maResValues; }
};
}
void ScColumn::ConvertFormulaToValue(
sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2, sc::TableValues* pUndo )
{
if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
return;
std::vector<SCROW> aBounds { nRow1 };
if (nRow2 < GetDoc().MaxRow()-1)
aBounds.push_back(nRow2+1);
// Split formula cell groups at top and bottom boundaries (if applicable).
sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
// Parse all formulas within the range and store their results into temporary storage.
ConvertFormulaToValueHandler aFunc(GetDoc());
sc::ParseFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
if (!aFunc.isModified())
// No formula cells encountered.
return;
DetachFormulaCells(rCxt, nRow1, nRow2);
// Undo storage to hold static values which will get swapped to the cell storage later.
sc::CellValues aUndoCells;
aFunc.getResValues().swap(aUndoCells);
aUndoCells.swapNonEmpty(*this);
if (pUndo)
pUndo->swap(nTab, nCol, aUndoCells);
}
namespace {
class StartListeningHandler
{
sc::StartListeningContext& mrCxt;
public:
explicit StartListeningHandler( sc::StartListeningContext& rCxt ) :
mrCxt(rCxt) {}
void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
{
pCell->StartListeningTo(mrCxt);
}
};
class EndListeningHandler
{
sc::EndListeningContext& mrCxt;
public:
explicit EndListeningHandler( sc::EndListeningContext& rCxt ) :
mrCxt(rCxt) {}
void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
{
pCell->EndListeningTo(mrCxt);
}
};
}
void ScColumn::SwapNonEmpty(
sc::TableValues& rValues, sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt )
{
const ScRange& rRange = rValues.getRange();
std::vector<SCROW> aBounds { rRange.aStart.Row() };
if (rRange.aEnd.Row() < GetDoc().MaxRow()-1)
aBounds.push_back(rRange.aEnd.Row()+1);
// Split formula cell groups at top and bottom boundaries (if applicable).
sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
std::vector<sc::CellValueSpan> aSpans = rValues.getNonEmptySpans(nTab, nCol);
// Detach formula cells within the spans (if any).
EndListeningHandler aEndLisFunc(rEndCxt);
sc::CellStoreType::iterator itPos = maCells.begin();
for (const auto& rSpan : aSpans)
{
SCROW nRow1 = rSpan.mnRow1;
SCROW nRow2 = rSpan.mnRow2;
itPos = sc::ProcessFormula(itPos, maCells, nRow1, nRow2, aEndLisFunc);
}
rValues.swapNonEmpty(nTab, nCol, *this);
RegroupFormulaCells();
// Attach formula cells within the spans (if any).
StartListeningHandler aStartLisFunc(rStartCxt);
itPos = maCells.begin();
for (const auto& rSpan : aSpans)
{
SCROW nRow1 = rSpan.mnRow1;
SCROW nRow2 = rSpan.mnRow2;
itPos = sc::ProcessFormula(itPos, maCells, nRow1, nRow2, aStartLisFunc);
}
CellStorageModified();
}
void ScColumn::DeleteRanges( const std::vector<sc::RowSpan>& rRanges, InsertDeleteFlags nDelFlag )
{
for (const auto& rSpan : rRanges)
DeleteArea(rSpan.mnRow1, rSpan.mnRow2, nDelFlag, false/*bBroadcast*/);
}
void ScColumn::CloneFormulaCell(
sc::ColumnBlockPosition& rBlockPos,
const ScFormulaCell& rSrc, const sc::CellTextAttr& rAttr,
const std::vector<sc::RowSpan>& rRanges )
{
SCCOL nMatrixCols = 0;
SCROW nMatrixRows = 0;
ScMatrixMode nMatrixFlag = rSrc.GetMatrixFlag();
if (nMatrixFlag == ScMatrixMode::Formula)
{
rSrc.GetMatColsRows( nMatrixCols, nMatrixRows);
SAL_WARN_IF( nMatrixCols != 1 || nMatrixRows != 1, "sc.core",
"ScColumn::CloneFormulaCell - cloning array/matrix with not exactly one column or row as single cell");
}
ScDocument& rDocument = GetDoc();
std::vector<ScFormulaCell*> aFormulas;
for (const auto& rSpan : rRanges)
{
SCROW nRow1 = rSpan.mnRow1, nRow2 = rSpan.mnRow2;
size_t nLen = nRow2 - nRow1 + 1;
assert(nLen > 0);
aFormulas.clear();
aFormulas.reserve(nLen);
ScAddress aPos(nCol, nRow1, nTab);
if (nLen == 1 || !rSrc.GetCode()->IsShareable())
{
// Single, ungrouped formula cell, or create copies for
// non-shareable token arrays.
for (size_t i = 0; i < nLen; ++i, aPos.IncRow())
{
ScFormulaCell* pCell = new ScFormulaCell(rSrc, rDocument, aPos);
aFormulas.push_back(pCell);
}
}
else
{
// Create a group of formula cells.
ScFormulaCellGroupRef xGroup(new ScFormulaCellGroup);
xGroup->setCode(*rSrc.GetCode());
xGroup->compileCode(rDocument, aPos, rDocument.GetGrammar());
for (size_t i = 0; i < nLen; ++i, aPos.IncRow())
{
ScFormulaCell* pCell = new ScFormulaCell(rDocument, aPos, xGroup, rDocument.GetGrammar(), nMatrixFlag);
if (nMatrixFlag == ScMatrixMode::Formula)
pCell->SetMatColsRows( nMatrixCols, nMatrixRows);
if (i == 0)
{
xGroup->mpTopCell = pCell;
xGroup->mnLength = nLen;
}
aFormulas.push_back(pCell);
}
}
rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow1, aFormulas.begin(), aFormulas.end());
// Join the top and bottom of the pasted formula cells as needed.
sc::CellStoreType::position_type aPosObj = maCells.position(rBlockPos.miCellPos, nRow1);
assert(aPosObj.first->type == sc::element_type_formula);
ScFormulaCell* pCell = sc::formula_block::at(*aPosObj.first->data, aPosObj.second);
JoinNewFormulaCell(aPosObj, *pCell);
aPosObj = maCells.position(aPosObj.first, nRow2);
assert(aPosObj.first->type == sc::element_type_formula);
pCell = sc::formula_block::at(*aPosObj.first->data, aPosObj.second);
JoinNewFormulaCell(aPosObj, *pCell);
std::vector<sc::CellTextAttr> aTextAttrs(nLen, rAttr);
rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
rBlockPos.miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end());
}
CellStorageModified();
}
void ScColumn::CloneFormulaCell(
const ScFormulaCell& rSrc, const sc::CellTextAttr& rAttr,
const std::vector<sc::RowSpan>& rRanges )
{
sc::ColumnBlockPosition aBlockPos;
InitBlockPosition(aBlockPos);
CloneFormulaCell(aBlockPos, rSrc, rAttr, rRanges);
}
std::unique_ptr<ScPostIt> ScColumn::ReleaseNote( SCROW nRow )
{
if (!GetDoc().ValidRow(nRow))
return nullptr;
ScPostIt* p = nullptr;
maCellNotes.release(nRow, p);
return std::unique_ptr<ScPostIt>(p);
}
size_t ScColumn::GetNoteCount() const
{
return std::accumulate(maCellNotes.begin(), maCellNotes.end(), size_t(0),
[](const size_t& rCount, const auto& rCellNote) {
if (rCellNote.type != sc::element_type_cellnote)
return rCount;
return rCount + rCellNote.size;
});
}
namespace {
class NoteCaptionCreator
{
ScAddress maPos;
public:
NoteCaptionCreator( SCTAB nTab, SCCOL nCol ) : maPos(nCol,0,nTab) {}
void operator() ( size_t nRow, const ScPostIt* p )
{
maPos.SetRow(nRow);
p->GetOrCreateCaption(maPos);
}
};
class NoteCaptionCleaner
{
bool mbPreserveData;
public:
explicit NoteCaptionCleaner( bool bPreserveData ) : mbPreserveData(bPreserveData) {}
void operator() ( size_t /*nRow*/, ScPostIt* p )
{
p->ForgetCaption(mbPreserveData);
}
};
}
void ScColumn::CreateAllNoteCaptions()
{
NoteCaptionCreator aFunc(nTab, nCol);
sc::ProcessNote(maCellNotes, aFunc);
}
void ScColumn::ForgetNoteCaptions( SCROW nRow1, SCROW nRow2, bool bPreserveData )
{
if (maCellNotes.empty())
return;
if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2))
return;
NoteCaptionCleaner aFunc(bPreserveData);
sc::CellNoteStoreType::iterator it = maCellNotes.begin();
sc::ProcessNote(it, maCellNotes, nRow1, nRow2, aFunc);
}
SCROW ScColumn::GetNotePosition( size_t nIndex ) const
{
// Return the row position of the nth note in the column.
size_t nCount = 0; // Number of notes encountered so far.
for (const auto& rCellNote : maCellNotes)
{
if (rCellNote.type != sc::element_type_cellnote)
// Skip the empty blocks.
continue;
if (nIndex < nCount + rCellNote.size)
{
// Index falls within this block.
size_t nOffset = nIndex - nCount;
return rCellNote.position + nOffset;
}
nCount += rCellNote.size;
}
return -1;
}
namespace {
class NoteEntryCollector
{
std::vector<sc::NoteEntry>& mrNotes;
SCTAB mnTab;
SCCOL mnCol;
SCROW mnStartRow;
SCROW mnEndRow;
public:
NoteEntryCollector( std::vector<sc::NoteEntry>& rNotes, SCTAB nTab, SCCOL nCol,
SCROW nStartRow, SCROW nEndRow) :
mrNotes(rNotes), mnTab(nTab), mnCol(nCol),
mnStartRow(nStartRow), mnEndRow(nEndRow) {}
void operator() (const sc::CellNoteStoreType::value_type& node) const
{
if (node.type != sc::element_type_cellnote)
return;
size_t nTopRow = node.position;
sc::cellnote_block::const_iterator it = sc::cellnote_block::begin(*node.data);
sc::cellnote_block::const_iterator itEnd = sc::cellnote_block::end(*node.data);
size_t nOffset = 0;
if(nTopRow < o3tl::make_unsigned(mnStartRow))
{
std::advance(it, mnStartRow - nTopRow);
nOffset = mnStartRow - nTopRow;
}
for (; it != itEnd && nTopRow + nOffset <= o3tl::make_unsigned(mnEndRow);
++it, ++nOffset)
{
ScAddress aPos(mnCol, nTopRow + nOffset, mnTab);
mrNotes.emplace_back(aPos, *it);
}
}
};
}
void ScColumn::GetAllNoteEntries( std::vector<sc::NoteEntry>& rNotes ) const
{
if (HasCellNotes())
std::for_each(maCellNotes.begin(), maCellNotes.end(),
NoteEntryCollector(rNotes, nTab, nCol, 0, GetDoc().MaxRow()));
}
void ScColumn::GetNotesInRange(SCROW nStartRow, SCROW nEndRow,
std::vector<sc::NoteEntry>& rNotes ) const
{
std::pair<sc::CellNoteStoreType::const_iterator,size_t> aPos = maCellNotes.position(nStartRow);
if (aPos.first == maCellNotes.end())
// Invalid row number.
return;
std::pair<sc::CellNoteStoreType::const_iterator,size_t> aEndPos =
maCellNotes.position(nEndRow);
sc::CellNoteStoreType::const_iterator itEnd = aEndPos.first;
std::for_each(aPos.first, ++itEnd, NoteEntryCollector(rNotes, nTab, nCol, nStartRow, nEndRow));
}
bool ScColumn::HasCellNote(SCROW nStartRow, SCROW nEndRow) const
{
std::pair<sc::CellNoteStoreType::const_iterator,size_t> aStartPos =
maCellNotes.position(nStartRow);
if (aStartPos.first == maCellNotes.end())
// Invalid row number.
return false;
std::pair<sc::CellNoteStoreType::const_iterator,size_t> aEndPos =
maCellNotes.position(nEndRow);
for (sc::CellNoteStoreType::const_iterator it = aStartPos.first; it != aEndPos.first; ++it)
{
if (it->type != sc::element_type_cellnote)
continue;
size_t nTopRow = it->position;
sc::cellnote_block::const_iterator blockIt = sc::cellnote_block::begin(*(it->data));
sc::cellnote_block::const_iterator blockItEnd = sc::cellnote_block::end(*(it->data));
size_t nOffset = 0;
if(nTopRow < o3tl::make_unsigned(nStartRow))
{
std::advance(blockIt, nStartRow - nTopRow);
nOffset = nStartRow - nTopRow;
}
if (blockIt != blockItEnd && nTopRow + nOffset <= o3tl::make_unsigned(nEndRow))
return true;
}
return false;
}
void ScColumn::GetUnprotectedCells( SCROW nStartRow, SCROW nEndRow, ScRangeList& rRangeList ) const
{
SCROW nTmpStartRow = nStartRow, nTmpEndRow = nEndRow;
const ScPatternAttr* pPattern = pAttrArray->GetPatternRange(nTmpStartRow, nTmpEndRow, nStartRow);
bool bProtection = pPattern->GetItem(ATTR_PROTECTION).GetProtection();
if (!bProtection)
{
// Limit the span to the range in question.
if (nTmpStartRow < nStartRow)
nTmpStartRow = nStartRow;
if (nTmpEndRow > nEndRow)
nTmpEndRow = nEndRow;
rRangeList.Join( ScRange( nCol, nTmpStartRow, nTab, nCol, nTmpEndRow, nTab));
}
while (nEndRow > nTmpEndRow)
{
nStartRow = nTmpEndRow + 1;
pPattern = pAttrArray->GetPatternRange(nTmpStartRow, nTmpEndRow, nStartRow);
bool bTmpProtection = pPattern->GetItem(ATTR_PROTECTION).GetProtection();
if (!bTmpProtection)
{
// Limit the span to the range in question.
// Only end row needs to be checked as we enter here only for spans
// below the original nStartRow.
if (nTmpEndRow > nEndRow)
nTmpEndRow = nEndRow;
rRangeList.Join( ScRange( nCol, nTmpStartRow, nTab, nCol, nTmpEndRow, nTab));
}
}
}
namespace {
class RecompileByOpcodeHandler
{
ScDocument* mpDoc;
const formula::unordered_opcode_set& mrOps;
sc::EndListeningContext& mrEndListenCxt;
sc::CompileFormulaContext& mrCompileFormulaCxt;
public:
RecompileByOpcodeHandler(
ScDocument* pDoc, const formula::unordered_opcode_set& rOps,
sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt ) :
mpDoc(pDoc),
mrOps(rOps),
mrEndListenCxt(rEndListenCxt),
mrCompileFormulaCxt(rCompileCxt) {}
void operator() ( sc::FormulaGroupEntry& rEntry )
{
// Perform end listening, remove from formula tree, and set them up
// for re-compilation.
ScFormulaCell* pTop = nullptr;
if (rEntry.mbShared)
{
// Only inspect the code from the top cell.
pTop = *rEntry.mpCells;
}
else
pTop = rEntry.mpCell;
ScTokenArray* pCode = pTop->GetCode();
bool bRecompile = pCode->HasOpCodes(mrOps);
if (!bRecompile)
return;
// Get the formula string.
OUString aFormula = pTop->GetFormula(mrCompileFormulaCxt);
sal_Int32 n = aFormula.getLength();
if (pTop->GetMatrixFlag() != ScMatrixMode::NONE && n > 0)
{
if (aFormula[0] == '{' && aFormula[n-1] == '}')
aFormula = aFormula.copy(1, n-2);
}
if (rEntry.mbShared)
{
ScFormulaCell** pp = rEntry.mpCells;
ScFormulaCell** ppEnd = pp + rEntry.mnLength;
for (; pp != ppEnd; ++pp)
{
ScFormulaCell* p = *pp;
p->EndListeningTo(mrEndListenCxt);
mpDoc->RemoveFromFormulaTree(p);
}
}
else
{
rEntry.mpCell->EndListeningTo(mrEndListenCxt);
mpDoc->RemoveFromFormulaTree(rEntry.mpCell);
}
pCode->Clear();
pTop->SetHybridFormula(aFormula, mpDoc->GetGrammar());
}
};
class CompileHybridFormulaHandler
{
ScDocument& mrDoc;
sc::StartListeningContext& mrStartListenCxt;
sc::CompileFormulaContext& mrCompileFormulaCxt;
public:
CompileHybridFormulaHandler(ScDocument& rDoc, sc::StartListeningContext& rStartListenCxt, sc::CompileFormulaContext& rCompileCxt ) :
mrDoc(rDoc),
mrStartListenCxt(rStartListenCxt),
mrCompileFormulaCxt(rCompileCxt) {}
void operator() ( sc::FormulaGroupEntry& rEntry )
{
if (rEntry.mbShared)
{
ScFormulaCell* pTop = *rEntry.mpCells;
OUString aFormula = pTop->GetHybridFormula();
if (!aFormula.isEmpty())
{
// Create a new token array from the hybrid formula string, and
// set it to the group.
ScCompiler aComp(mrCompileFormulaCxt, pTop->aPos);
std::unique_ptr<ScTokenArray> pNewCode = aComp.CompileString(aFormula);
ScFormulaCellGroupRef xGroup = pTop->GetCellGroup();
assert(xGroup);
xGroup->setCode(std::move(*pNewCode));
xGroup->compileCode(mrDoc, pTop->aPos, mrDoc.GetGrammar());
// Propagate the new token array to all formula cells in the group.
ScFormulaCell** pp = rEntry.mpCells;
ScFormulaCell** ppEnd = pp + rEntry.mnLength;
for (; pp != ppEnd; ++pp)
{
ScFormulaCell* p = *pp;
p->SyncSharedCode();
p->StartListeningTo(mrStartListenCxt);
p->SetDirty();
}
}
}
else
{
ScFormulaCell* pCell = rEntry.mpCell;
OUString aFormula = pCell->GetHybridFormula();
if (!aFormula.isEmpty())
{
// Create token array from formula string.
ScCompiler aComp(mrCompileFormulaCxt, pCell->aPos);
std::unique_ptr<ScTokenArray> pNewCode = aComp.CompileString(aFormula);
// Generate RPN tokens.
ScCompiler aComp2(mrDoc, pCell->aPos, *pNewCode, formula::FormulaGrammar::GRAM_UNSPECIFIED,
true, pCell->GetMatrixFlag() != ScMatrixMode::NONE);
aComp2.CompileTokenArray();
pCell->SetCode(std::move(pNewCode));
pCell->StartListeningTo(mrStartListenCxt);
pCell->SetDirty();
}
}
}
};
}
void ScColumn::PreprocessRangeNameUpdate(
sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt )
{
// Collect all formula groups.
std::vector<sc::FormulaGroupEntry> aGroups = GetFormulaGroupEntries();
formula::unordered_opcode_set aOps;
aOps.insert(ocBad);
aOps.insert(ocColRowName);
aOps.insert(ocName);
RecompileByOpcodeHandler aFunc(&GetDoc(), aOps, rEndListenCxt, rCompileCxt);
std::for_each(aGroups.begin(), aGroups.end(), aFunc);
}
void ScColumn::PreprocessDBDataUpdate(
sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt )
{
// Collect all formula groups.
std::vector<sc::FormulaGroupEntry> aGroups = GetFormulaGroupEntries();
formula::unordered_opcode_set aOps;
aOps.insert(ocBad);
aOps.insert(ocColRowName);
aOps.insert(ocDBArea);
aOps.insert(ocTableRef);
RecompileByOpcodeHandler aFunc(&GetDoc(), aOps, rEndListenCxt, rCompileCxt);
std::for_each(aGroups.begin(), aGroups.end(), aFunc);
}
void ScColumn::CompileHybridFormula(
sc::StartListeningContext& rStartListenCxt, sc::CompileFormulaContext& rCompileCxt )
{
// Collect all formula groups.
std::vector<sc::FormulaGroupEntry> aGroups = GetFormulaGroupEntries();
CompileHybridFormulaHandler aFunc(GetDoc(), rStartListenCxt, rCompileCxt);
std::for_each(aGroups.begin(), aGroups.end(), aFunc);
}
namespace {
class ScriptTypeUpdater
{
ScColumn& mrCol;
sc::CellTextAttrStoreType& mrTextAttrs;
sc::CellTextAttrStoreType::iterator miPosAttr;
ScConditionalFormatList* mpCFList;
ScInterpreterContext& mrContext;
ScAddress maPos;
bool mbUpdated;
private:
void updateScriptType( size_t nRow, ScRefCellValue& rCell )
{
sc::CellTextAttrStoreType::position_type aAttrPos = mrTextAttrs.position(miPosAttr, nRow);
miPosAttr = aAttrPos.first;
if (aAttrPos.first->type != sc::element_type_celltextattr)
return;
sc::CellTextAttr& rAttr = sc::celltextattr_block::at(*aAttrPos.first->data, aAttrPos.second);
if (rAttr.mnScriptType != SvtScriptType::UNKNOWN)
// Script type already determined. Skip it.
return;
const ScPatternAttr* pPat = mrCol.GetPattern(nRow);
if (!pPat)
// In theory this should never return NULL. But let's be safe.
return;
const SfxItemSet* pCondSet = nullptr;
if (mpCFList)
{
maPos.SetRow(nRow);
const ScCondFormatItem& rItem = pPat->GetItem(ATTR_CONDITIONAL);
const ScCondFormatIndexes& rData = rItem.GetCondFormatData();
pCondSet = mrCol.GetDoc().GetCondResult(rCell, maPos, *mpCFList, rData);
}
const Color* pColor;
sal_uInt32 nFormat = pPat->GetNumberFormat(mrContext, pCondSet);
OUString aStr = ScCellFormat::GetString(rCell, nFormat, &pColor, &mrContext, mrCol.GetDoc());
rAttr.mnScriptType = mrCol.GetDoc().GetStringScriptType(aStr);
mbUpdated = true;
}
public:
explicit ScriptTypeUpdater( ScColumn& rCol ) :
mrCol(rCol),
mrTextAttrs(rCol.GetCellAttrStore()),
miPosAttr(mrTextAttrs.begin()),
mpCFList(rCol.GetDoc().GetCondFormList(rCol.GetTab())),
mrContext(rCol.GetDoc().GetNonThreadedContext()),
maPos(rCol.GetCol(), 0, rCol.GetTab()),
mbUpdated(false)
{}
void operator() ( size_t nRow, double fVal )
{
ScRefCellValue aCell(fVal);
updateScriptType(nRow, aCell);
}
void operator() ( size_t nRow, const svl::SharedString& rStr )
{
ScRefCellValue aCell(&rStr);
updateScriptType(nRow, aCell);
}
void operator() ( size_t nRow, const EditTextObject* pText )
{
ScRefCellValue aCell(pText);
updateScriptType(nRow, aCell);
}
void operator() ( size_t nRow, const ScFormulaCell* pCell )
{
ScRefCellValue aCell(const_cast<ScFormulaCell*>(pCell));
updateScriptType(nRow, aCell);
}
bool isUpdated() const { return mbUpdated; }
};
}
void ScColumn::UpdateScriptTypes( SCROW nRow1, SCROW nRow2 )
{
if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
return;
ScriptTypeUpdater aFunc(*this);
sc::ParseAllNonEmpty(maCells.begin(), maCells, nRow1, nRow2, aFunc);
if (aFunc.isUpdated())
CellStorageModified();
}
void ScColumn::Swap( ScColumn& rOther, SCROW nRow1, SCROW nRow2, bool bPattern )
{
maCells.swap(nRow1, nRow2, rOther.maCells, nRow1);
maCellTextAttrs.swap(nRow1, nRow2, rOther.maCellTextAttrs, nRow1);
maCellNotes.swap(nRow1, nRow2, rOther.maCellNotes, nRow1);
maBroadcasters.swap(nRow1, nRow2, rOther.maBroadcasters, nRow1);
// Update draw object anchors
ScDrawLayer* pDrawLayer = GetDoc().GetDrawLayer();
if (pDrawLayer)
{
std::map<SCROW, std::vector<SdrObject*>> aThisColRowDrawObjects
= pDrawLayer->GetObjectsAnchoredToRange(GetTab(), GetCol(), nRow1, nRow2);
std::map<SCROW, std::vector<SdrObject*>> aOtherColRowDrawObjects
= pDrawLayer->GetObjectsAnchoredToRange(GetTab(), rOther.GetCol(), nRow1, nRow2);
for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
{
std::vector<SdrObject*>& rThisCellDrawObjects = aThisColRowDrawObjects[nRow];
if (!rThisCellDrawObjects.empty())
UpdateDrawObjectsForRow(rThisCellDrawObjects, rOther.GetCol(), nRow);
std::vector<SdrObject*>& rOtherCellDrawObjects = aOtherColRowDrawObjects[nRow];
if (!rOtherCellDrawObjects.empty())
rOther.UpdateDrawObjectsForRow(rOtherCellDrawObjects, GetCol(), nRow);
}
}
if (bPattern)
{
for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
{
const ScPatternAttr* pPat1 = GetPattern(nRow);
const ScPatternAttr* pPat2 = rOther.GetPattern(nRow);
if (!ScPatternAttr::areSame(pPat1, pPat2))
{
CellAttributeHolder aTemp(pPat1);
SetPattern(nRow, *pPat2);
rOther.SetPattern(nRow, aTemp);
}
}
}
CellStorageModified();
rOther.CellStorageModified();
}
namespace {
class FormulaColPosSetter
{
SCCOL mnCol;
bool mbUpdateRefs;
public:
FormulaColPosSetter( SCCOL nCol, bool bUpdateRefs ) : mnCol(nCol), mbUpdateRefs(bUpdateRefs) {}
void operator() ( size_t nRow, ScFormulaCell* pCell )
{
if (!pCell->IsShared() || pCell->IsSharedTop())
{
// Ensure that the references still point to the same locations
// after the position change.
ScAddress aOldPos = pCell->aPos;
pCell->aPos.SetCol(mnCol);
pCell->aPos.SetRow(nRow);
if (mbUpdateRefs)
pCell->GetCode()->AdjustReferenceOnMovedOrigin(aOldPos, pCell->aPos);
else
pCell->GetCode()->AdjustReferenceOnMovedOriginIfOtherSheet(aOldPos, pCell->aPos);
}
else
{
pCell->aPos.SetCol(mnCol);
pCell->aPos.SetRow(nRow);
}
}
};
}
void ScColumn::ResetFormulaCellPositions( SCROW nRow1, SCROW nRow2, bool bUpdateRefs )
{
FormulaColPosSetter aFunc(nCol, bUpdateRefs);
sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
}
namespace {
class RelativeRefBoundChecker
{
std::vector<SCROW> maBounds;
ScRange maBoundRange;
public:
explicit RelativeRefBoundChecker( const ScRange& rBoundRange ) :
maBoundRange(rBoundRange) {}
void operator() ( size_t /*nRow*/, ScFormulaCell* pCell )
{
if (!pCell->IsSharedTop())
return;
pCell->GetCode()->CheckRelativeReferenceBounds(
pCell->aPos, pCell->GetSharedLength(), maBoundRange, maBounds);
}
void swapBounds( std::vector<SCROW>& rBounds )
{
rBounds.swap(maBounds);
}
};
}
void ScColumn::SplitFormulaGroupByRelativeRef( const ScRange& rBoundRange )
{
if (rBoundRange.aStart.Row() >= GetDoc().MaxRow())
// Nothing to split.
return;
std::vector<SCROW> aBounds;
// Cut at row boundaries first.
aBounds.push_back(rBoundRange.aStart.Row());
if (rBoundRange.aEnd.Row() < GetDoc().MaxRow())
aBounds.push_back(rBoundRange.aEnd.Row()+1);
sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
RelativeRefBoundChecker aFunc(rBoundRange);
sc::ProcessFormula(
maCells.begin(), maCells, rBoundRange.aStart.Row(), rBoundRange.aEnd.Row(), aFunc);
aFunc.swapBounds(aBounds);
sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
}
namespace {
class ListenerCollector
{
std::vector<SvtListener*>& mrListeners;
public:
explicit ListenerCollector( std::vector<SvtListener*>& rListener ) :
mrListeners(rListener) {}
void operator() ( size_t /*nRow*/, SvtBroadcaster* p )
{
SvtBroadcaster::ListenersType& rLis = p->GetAllListeners();
mrListeners.insert(mrListeners.end(), rLis.begin(), rLis.end());
}
};
class FormulaCellCollector
{
std::vector<ScFormulaCell*>& mrCells;
public:
explicit FormulaCellCollector( std::vector<ScFormulaCell*>& rCells ) : mrCells(rCells) {}
void operator() ( size_t /*nRow*/, ScFormulaCell* p )
{
mrCells.push_back(p);
}
};
}
void ScColumn::CollectListeners( std::vector<SvtListener*>& rListeners, SCROW nRow1, SCROW nRow2 )
{
if (nRow2 < nRow1 || !GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2))
return;
ListenerCollector aFunc(rListeners);
sc::ProcessBroadcaster(maBroadcasters.begin(), maBroadcasters, nRow1, nRow2, aFunc);
}
void ScColumn::CollectFormulaCells( std::vector<ScFormulaCell*>& rCells, SCROW nRow1, SCROW nRow2 )
{
if (nRow2 < nRow1 || !GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2))
return;
FormulaCellCollector aFunc(rCells);
sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
}
bool ScColumn::HasFormulaCell() const
{
return mnBlkCountFormula != 0;
}
namespace {
struct FindAnyFormula
{
bool operator() ( size_t /*nRow*/, const ScFormulaCell* /*pCell*/ ) const
{
return true;
}
};
}
bool ScColumn::HasFormulaCell( SCROW nRow1, SCROW nRow2 ) const
{
if (!mnBlkCountFormula)
return false;
if (nRow2 < nRow1 || !GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2))
return false;
if (nRow1 == 0 && nRow2 == GetDoc().MaxRow())
return HasFormulaCell();
FindAnyFormula aFunc;
std::pair<sc::CellStoreType::const_iterator, size_t> aRet =
sc::FindFormula(maCells, nRow1, nRow2, aFunc);
return aRet.first != maCells.end();
}
namespace {
void endListening( sc::EndListeningContext& rCxt, ScFormulaCell** pp, ScFormulaCell** ppEnd )
{
for (; pp != ppEnd; ++pp)
{
ScFormulaCell& rFC = **pp;
rFC.EndListeningTo(rCxt);
}
}
class StartListeningFormulaCellsHandler
{
sc::StartListeningContext& mrStartCxt;
sc::EndListeningContext& mrEndCxt;
SCROW mnStartRow;
public:
StartListeningFormulaCellsHandler( sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt ) :
mrStartCxt(rStartCxt), mrEndCxt(rEndCxt), mnStartRow(-1) {}
void operator() ( const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize )
{
if (node.type != sc::element_type_formula)
// We are only interested in formulas.
return;
mnStartRow = node.position + nOffset;
ScFormulaCell** ppBeg = &sc::formula_block::at(*node.data, nOffset);
ScFormulaCell** ppEnd = ppBeg + nDataSize;
ScFormulaCell** pp = ppBeg;
// If the first formula cell belongs to a group and it's not the top
// cell, move up to the top cell of the group, and have all the extra
// formula cells stop listening.
ScFormulaCell* pFC = *pp;
if (pFC->IsShared() && !pFC->IsSharedTop())
{
SCROW nBackTrackSize = pFC->aPos.Row() - pFC->GetSharedTopRow();
if (nBackTrackSize > 0)
{
assert(o3tl::make_unsigned(nBackTrackSize) <= nOffset);
for (SCROW i = 0; i < nBackTrackSize; ++i)
--pp;
endListening(mrEndCxt, pp, ppBeg);
mnStartRow -= nBackTrackSize;
}
}
for (; pp != ppEnd; ++pp)
{
pFC = *pp;
if (!pFC->IsSharedTop())
{
assert(!pFC->IsShared());
pFC->StartListeningTo(mrStartCxt);
continue;
}
// If This is the last group in the range, see if the group
// extends beyond the range, in which case have the excess
// formula cells stop listening.
size_t nEndGroupPos = (pp - ppBeg) + pFC->GetSharedLength();
if (nEndGroupPos > nDataSize)
{
size_t nExcessSize = nEndGroupPos - nDataSize;
ScFormulaCell** ppGrpEnd = pp + pFC->GetSharedLength();
ScFormulaCell** ppGrp = ppGrpEnd - nExcessSize;
endListening(mrEndCxt, ppGrp, ppGrpEnd);
// Register formula cells as a group.
sc::SharedFormulaUtil::startListeningAsGroup(mrStartCxt, pp);
pp = ppEnd - 1; // Move to the one before the end position.
}
else
{
// Register formula cells as a group.
sc::SharedFormulaUtil::startListeningAsGroup(mrStartCxt, pp);
pp += pFC->GetSharedLength() - 1; // Move to the last one in the group.
}
}
}
};
class EndListeningFormulaCellsHandler
{
sc::EndListeningContext& mrEndCxt;
SCROW mnStartRow;
SCROW mnEndRow;
public:
explicit EndListeningFormulaCellsHandler( sc::EndListeningContext& rEndCxt ) :
mrEndCxt(rEndCxt), mnStartRow(-1), mnEndRow(-1) {}
void operator() ( const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize )
{
if (node.type != sc::element_type_formula)
// We are only interested in formulas.
return;
mnStartRow = node.position + nOffset;
ScFormulaCell** ppBeg = &sc::formula_block::at(*node.data, nOffset);
ScFormulaCell** ppEnd = ppBeg + nDataSize;
ScFormulaCell** pp = ppBeg;
// If the first formula cell belongs to a group and it's not the top
// cell, move up to the top cell of the group.
ScFormulaCell* pFC = *pp;
if (pFC->IsShared() && !pFC->IsSharedTop())
{
SCROW nBackTrackSize = pFC->aPos.Row() - pFC->GetSharedTopRow();
if (nBackTrackSize > 0)
{
assert(o3tl::make_unsigned(nBackTrackSize) <= nOffset);
for (SCROW i = 0; i < nBackTrackSize; ++i)
--pp;
mnStartRow -= nBackTrackSize;
}
}
for (; pp != ppEnd; ++pp)
{
pFC = *pp;
if (!pFC->IsSharedTop())
{
assert(!pFC->IsShared());
pFC->EndListeningTo(mrEndCxt);
continue;
}
size_t nEndGroupPos = (pp - ppBeg) + pFC->GetSharedLength();
mnEndRow = node.position + nOffset + nEndGroupPos - 1; // absolute row position of the last one in the group.
ScFormulaCell** ppGrpEnd = pp + pFC->GetSharedLength();
endListening(mrEndCxt, pp, ppGrpEnd);
if (nEndGroupPos > nDataSize)
{
// The group goes beyond the specified end row. Move to the
// one before the end position to finish the loop.
pp = ppEnd - 1;
}
else
{
// Move to the last one in the group.
pp += pFC->GetSharedLength() - 1;
}
}
}
SCROW getStartRow() const
{
return mnStartRow;
}
SCROW getEndRow() const
{
return mnEndRow;
}
};
}
void ScColumn::StartListeningFormulaCells(
sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt,
SCROW nRow1, SCROW nRow2 )
{
if (!HasFormulaCell())
return;
StartListeningFormulaCellsHandler aFunc(rStartCxt, rEndCxt);
sc::ProcessBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
}
void ScColumn::EndListeningFormulaCells(
sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2,
SCROW* pStartRow, SCROW* pEndRow )
{
if (!HasFormulaCell())
return;
EndListeningFormulaCellsHandler aFunc(rCxt);
sc::ProcessBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
if (pStartRow)
*pStartRow = aFunc.getStartRow();
if (pEndRow)
*pEndRow = aFunc.getEndRow();
}
void ScColumn::EndListeningIntersectedGroup(
sc::EndListeningContext& rCxt, SCROW nRow, std::vector<ScAddress>* pGroupPos )
{
if (!GetDoc().ValidRow(nRow))
return;
sc::CellStoreType::position_type aPos = maCells.position(nRow);
sc::CellStoreType::iterator it = aPos.first;
if (it->type != sc::element_type_formula)
// Only interested in a formula block.
return;
ScFormulaCell* pFC = sc::formula_block::at(*it->data, aPos.second);
ScFormulaCellGroupRef xGroup = pFC->GetCellGroup();
if (!xGroup)
// Not a formula group.
return;
// End listening.
pFC->EndListeningTo(rCxt);
if (pGroupPos)
{
if (!pFC->IsSharedTop())
// Record the position of the top cell of the group.
pGroupPos->push_back(xGroup->mpTopCell->aPos);
SCROW nGrpLastRow = pFC->GetSharedTopRow() + pFC->GetSharedLength() - 1;
if (nRow < nGrpLastRow)
// Record the last position of the group.
pGroupPos->push_back(ScAddress(nCol, nGrpLastRow, nTab));
}
}
void ScColumn::EndListeningIntersectedGroups(
sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2, std::vector<ScAddress>* pGroupPos )
{
// Only end the intersected group.
sc::CellStoreType::position_type aPos = maCells.position(nRow1);
sc::CellStoreType::iterator it = aPos.first;
if (it->type == sc::element_type_formula)
{
ScFormulaCell* pFC = sc::formula_block::at(*it->data, aPos.second);
ScFormulaCellGroupRef xGroup = pFC->GetCellGroup();
if (xGroup)
{
if (!pFC->IsSharedTop())
// End listening.
pFC->EndListeningTo(rCxt);
if (pGroupPos)
// Record the position of the top cell of the group.
pGroupPos->push_back(xGroup->mpTopCell->aPos);
}
}
aPos = maCells.position(it, nRow2);
it = aPos.first;
if (it->type != sc::element_type_formula)
return;
ScFormulaCell* pFC = sc::formula_block::at(*it->data, aPos.second);
ScFormulaCellGroupRef xGroup = pFC->GetCellGroup();
if (!xGroup)
return;
if (!pFC->IsSharedTop())
// End listening.
pFC->EndListeningTo(rCxt);
if (pGroupPos)
{
// Record the position of the bottom cell of the group.
ScAddress aPosLast = xGroup->mpTopCell->aPos;
aPosLast.IncRow(xGroup->mnLength-1);
pGroupPos->push_back(aPosLast);
}
}
void ScColumn::EndListeningGroup( sc::EndListeningContext& rCxt, SCROW nRow )
{
sc::CellStoreType::position_type aPos = maCells.position(nRow);
if (aPos.first->type != sc::element_type_formula)
// not a formula cell.
return;
ScFormulaCell** pp = &sc::formula_block::at(*aPos.first->data, aPos.second);
ScFormulaCellGroupRef xGroup = (*pp)->GetCellGroup();
if (!xGroup)
{
// not a formula group.
(*pp)->EndListeningTo(rCxt);
return;
}
// Move back to the top cell.
SCROW nTopDelta = (*pp)->aPos.Row() - xGroup->mpTopCell->aPos.Row();
assert(nTopDelta >= 0);
if (nTopDelta > 0)
pp -= nTopDelta;
// Set the needs listening flag to all cells in the group.
assert(*pp == xGroup->mpTopCell);
ScFormulaCell** ppEnd = pp + xGroup->mnLength;
for (; pp != ppEnd; ++pp)
(*pp)->EndListeningTo(rCxt);
}
void ScColumn::SetNeedsListeningGroup( SCROW nRow )
{
sc::CellStoreType::position_type aPos = maCells.position(nRow);
if (aPos.first->type != sc::element_type_formula)
// not a formula cell.
return;
ScFormulaCell** pp = &sc::formula_block::at(*aPos.first->data, aPos.second);
ScFormulaCellGroupRef xGroup = (*pp)->GetCellGroup();
if (!xGroup)
{
// not a formula group.
(*pp)->SetNeedsListening(true);
return;
}
// Move back to the top cell.
SCROW nTopDelta = (*pp)->aPos.Row() - xGroup->mpTopCell->aPos.Row();
assert(nTopDelta >= 0);
if (nTopDelta > 0)
pp -= nTopDelta;
// Set the needs listening flag to all cells in the group.
assert(*pp == xGroup->mpTopCell);
ScFormulaCell** ppEnd = pp + xGroup->mnLength;
for (; pp != ppEnd; ++pp)
(*pp)->SetNeedsListening(true);
}
std::optional<sc::ColumnIterator> ScColumn::GetColumnIterator( SCROW nRow1, SCROW nRow2 ) const
{
if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
return {};
return sc::ColumnIterator(maCells, nRow1, nRow2);
}
static bool lcl_InterpretSpan(sc::formula_block::const_iterator& rSpanIter, SCROW nStartOffset, SCROW nEndOffset,
const ScFormulaCellGroupRef& mxParentGroup, bool& bAllowThreading, ScDocument& rDoc)
{
bAllowThreading = true;
ScFormulaCell* pCellStart = nullptr;
SCROW nSpanStart = -1;
SCROW nSpanEnd = -1;
sc::formula_block::const_iterator itSpanStart;
bool bAnyDirty = false;
for (SCROW nFGOffset = nStartOffset; nFGOffset <= nEndOffset; ++rSpanIter, ++nFGOffset)
{
bool bThisDirty = (*rSpanIter)->NeedsInterpret();
if (!pCellStart && bThisDirty)
{
pCellStart = *rSpanIter;
itSpanStart = rSpanIter;
nSpanStart = nFGOffset;
bAnyDirty = true;
}
if (pCellStart && (!bThisDirty || nFGOffset == nEndOffset))
{
nSpanEnd = bThisDirty ? nFGOffset : nFGOffset - 1;
assert(nSpanStart >= nStartOffset && nSpanStart <= nSpanEnd && nSpanEnd <= nEndOffset);
// Found a completely dirty sub span [nSpanStart, nSpanEnd] inside the required span [nStartOffset, nEndOffset]
bool bGroupInterpreted = pCellStart->Interpret(nSpanStart, nSpanEnd);
if (bGroupInterpreted)
for (SCROW nIdx = nSpanStart; nIdx <= nSpanEnd; ++nIdx, ++itSpanStart)
assert(!(*itSpanStart)->NeedsInterpret());
ScRecursionHelper& rRecursionHelper = rDoc.GetRecursionHelper();
// child cell's Interpret could result in calling dependency calc
// and that could detect a cycle involving mxGroup
// and do early exit in that case.
// OR
// this call resulted from a dependency calculation for a multi-formula-group-threading and
// if intergroup dependency is found, return early.
if ((mxParentGroup && mxParentGroup->mbPartOfCycle) || !rRecursionHelper.AreGroupsIndependent())
{
bAllowThreading = false;
return bAnyDirty;
}
if (!bGroupInterpreted)
{
// Evaluate from second cell in non-grouped style (no point in trying group-interpret again).
++itSpanStart;
for (SCROW nIdx = nSpanStart+1; nIdx <= nSpanEnd; ++nIdx, ++itSpanStart)
{
(*itSpanStart)->Interpret(); // We know for sure that this cell is dirty so directly call Interpret().
if ((*itSpanStart)->NeedsInterpret())
{
SAL_WARN("sc.core.formulagroup", "Internal error, cell " << (*itSpanStart)->aPos
<< " failed running Interpret(), not allowing threading");
bAllowThreading = false;
return bAnyDirty;
}
// Allow early exit like above.
if ((mxParentGroup && mxParentGroup->mbPartOfCycle) || !rRecursionHelper.AreGroupsIndependent())
{
// Set this cell as dirty as this may be interpreted in InterpretTail()
pCellStart->SetDirtyVar();
bAllowThreading = false;
return bAnyDirty;
}
}
}
pCellStart = nullptr; // For next sub span start detection.
}
}
return bAnyDirty;
}
static void lcl_EvalDirty(sc::CellStoreType& rCells, SCROW nRow1, SCROW nRow2, ScDocument& rDoc,
const ScFormulaCellGroupRef& mxGroup, bool bThreadingDepEval, bool bSkipRunning,
bool& bIsDirty, bool& bAllowThreading, ScAddress* pDirtiedAddress)
{
ScRecursionHelper& rRecursionHelper = rDoc.GetRecursionHelper();
std::pair<sc::CellStoreType::const_iterator,size_t> aPos = rCells.position(nRow1);
sc::CellStoreType::const_iterator it = aPos.first;
size_t nOffset = aPos.second;
SCROW nRow = nRow1;
bIsDirty = false;
for (;it != rCells.end() && nRow <= nRow2; ++it, nOffset = 0)
{
switch( it->type )
{
case sc::element_type_edittext:
// These require EditEngine (in ScEditUtils::GetString()), which is probably
// too complex for use in threads.
if (bThreadingDepEval)
{
bAllowThreading = false;
return;
}
break;
case sc::element_type_formula:
{
size_t nRowsToRead = nRow2 - nRow + 1;
const size_t nEnd = std::min(it->size, nOffset+nRowsToRead); // last row + 1
sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data);
std::advance(itCell, nOffset);
// Loop inside the formula block.
size_t nCellIdx = nOffset;
while (nCellIdx < nEnd)
{
const ScFormulaCellGroupRef& mxGroupChild = (*itCell)->GetCellGroup();
ScFormulaCell* pChildTopCell = mxGroupChild ? mxGroupChild->mpTopCell : *itCell;
// Check if itCell is already in path.
// If yes use a cycle guard to mark all elements of the cycle
// and return false
if (bThreadingDepEval && pChildTopCell->GetSeenInPath())
{
ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pChildTopCell);
bAllowThreading = false;
return;
}
if (bSkipRunning && (*itCell)->IsRunning())
{
++itCell;
nCellIdx += 1;
nRow += 1;
nRowsToRead -= 1;
continue;
}
if (mxGroupChild)
{
// It is a Formula-group, evaluate the necessary parts of it (spans).
const SCROW nFGStartOffset = (*itCell)->aPos.Row() - pChildTopCell->aPos.Row();
const SCROW nFGEndOffset = std::min(nFGStartOffset + static_cast<SCROW>(nRowsToRead) - 1, mxGroupChild->mnLength - 1);
assert(nFGEndOffset >= nFGStartOffset);
const SCROW nSpanLen = nFGEndOffset - nFGStartOffset + 1;
// The (main) span required to be evaluated is [nFGStartOffset, nFGEndOffset], but this span may contain
// non-dirty cells, so split this into sets of completely-dirty spans and try evaluate each of them in grouped-style.
bool bAnyDirtyInSpan = lcl_InterpretSpan(itCell, nFGStartOffset, nFGEndOffset, mxGroup, bAllowThreading, rDoc);
if (!bAllowThreading)
return;
// itCell will now point to cell just after the end of span [nFGStartOffset, nFGEndOffset].
bIsDirty = bIsDirty || bAnyDirtyInSpan;
// update the counters by nSpanLen.
// itCell already got updated.
nCellIdx += nSpanLen;
nRow += nSpanLen;
nRowsToRead -= nSpanLen;
}
else
{
// No formula-group here.
bool bDirtyFlag = false;
if( (*itCell)->NeedsInterpret())
{
bDirtyFlag = true;
(*itCell)->Interpret();
if ((*itCell)->NeedsInterpret())
{
SAL_WARN("sc.core.formulagroup", "Internal error, cell " << (*itCell)->aPos
<< " failed running Interpret(), not allowing threading");
bAllowThreading = false;
return;
}
}
bIsDirty = bIsDirty || bDirtyFlag;
// child cell's Interpret could result in calling dependency calc
// and that could detect a cycle involving mxGroup
// and do early exit in that case.
// OR
// we are trying multi-formula-group-threading, but found intergroup dependency.
if (bThreadingDepEval && mxGroup &&
(mxGroup->mbPartOfCycle || !rRecursionHelper.AreGroupsIndependent()))
{
// Set itCell as dirty as itCell may be interpreted in InterpretTail()
(*itCell)->SetDirtyVar();
if (pDirtiedAddress)
pDirtiedAddress->SetRow(nRow);
bAllowThreading = false;
return;
}
// update the counters by 1.
nCellIdx += 1;
nRow += 1;
nRowsToRead -= 1;
++itCell;
}
}
break;
}
default:
// Skip this block.
nRow += it->size - nOffset;
continue;
}
}
if (bThreadingDepEval)
bAllowThreading = true;
}
// Returns true if at least one FC is dirty.
bool ScColumn::EnsureFormulaCellResults( SCROW nRow1, SCROW nRow2, bool bSkipRunning )
{
if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
return false;
if (!HasFormulaCell(nRow1, nRow2))
return false;
bool bAnyDirty = false, bTmp = false;
lcl_EvalDirty(maCells, nRow1, nRow2, GetDoc(), nullptr, false, bSkipRunning, bAnyDirty, bTmp, nullptr);
return bAnyDirty;
}
bool ScColumn::HandleRefArrayForParallelism( SCROW nRow1, SCROW nRow2, const ScFormulaCellGroupRef& mxGroup, ScAddress* pDirtiedAddress )
{
if (nRow1 > nRow2)
return false;
bool bAllowThreading = true, bTmp = false;
lcl_EvalDirty(maCells, nRow1, nRow2, GetDoc(), mxGroup, true, false, bTmp, bAllowThreading, pDirtiedAddress);
return bAllowThreading;
}
namespace {
class StoreToCacheFunc
{
SvStream& mrStrm;
public:
StoreToCacheFunc(SvStream& rStrm):
mrStrm(rStrm)
{
}
void operator() ( const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize )
{
SCROW nStartRow = node.position + nOffset;
mrStrm.WriteUInt64(nStartRow);
mrStrm.WriteUInt64(nDataSize);
switch (node.type)
{
case sc::element_type_empty:
{
mrStrm.WriteUChar(0);
}
break;
case sc::element_type_numeric:
{
mrStrm.WriteUChar(1);
sc::numeric_block::const_iterator it = sc::numeric_block::begin(*node.data);
std::advance(it, nOffset);
sc::numeric_block::const_iterator itEnd = it;
std::advance(itEnd, nDataSize);
for (; it != itEnd; ++it)
{
mrStrm.WriteDouble(*it);
}
}
break;
case sc::element_type_string:
{
mrStrm.WriteUChar(2);
sc::string_block::const_iterator it = sc::string_block::begin(*node.data);
std::advance(it, nOffset);
sc::string_block::const_iterator itEnd = it;
std::advance(itEnd, nDataSize);
for (; it != itEnd; ++it)
{
OString aStr = OUStringToOString(it->getString(), RTL_TEXTENCODING_UTF8);
sal_Int32 nStrLength = aStr.getLength();
mrStrm.WriteInt32(nStrLength);
mrStrm.WriteOString(aStr);
}
}
break;
case sc::element_type_formula:
{
mrStrm.WriteUChar(3);
sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data);
std::advance(it, nOffset);
sc::formula_block::const_iterator itEnd = it;
std::advance(itEnd, nDataSize);
for (; it != itEnd; /* incrementing through std::advance*/)
{
const ScFormulaCell* pCell = *it;
OUString aFormula = pCell->GetFormula(formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);
const auto& xCellGroup = pCell->GetCellGroup();
sal_uInt64 nGroupLength = 0;
if (xCellGroup)
{
nGroupLength = xCellGroup->mnLength;
}
else
{
nGroupLength = 1;
}
mrStrm.WriteUInt64(nGroupLength);
mrStrm.WriteInt32(aFormula.getLength());
mrStrm.WriteOString(OUStringToOString(aFormula, RTL_TEXTENCODING_UTF8));
// incrementing the iterator
std::advance(it, nGroupLength);
}
}
break;
}
}
};
}
void ScColumn::StoreToCache(SvStream& rStrm) const
{
rStrm.WriteUInt64(nCol);
SCROW nLastRow = GetLastDataPos();
rStrm.WriteUInt64(nLastRow + 1); // the rows are zero based
StoreToCacheFunc aFunc(rStrm);
sc::ParseBlock(maCells.begin(), maCells, aFunc, SCROW(0), nLastRow);
}
void ScColumn::RestoreFromCache(SvStream& rStrm)
{
sal_uInt64 nStoredCol = 0;
rStrm.ReadUInt64(nStoredCol);
if (nStoredCol != static_cast<sal_uInt64>(nCol))
throw std::exception();
sal_uInt64 nLastRow = 0;
rStrm.ReadUInt64(nLastRow);
sal_uInt64 nReadRow = 0;
ScDocument& rDocument = GetDoc();
while (nReadRow < nLastRow)
{
sal_uInt64 nStartRow = 0;
sal_uInt64 nDataSize = 0;
rStrm.ReadUInt64(nStartRow);
rStrm.ReadUInt64(nDataSize);
sal_uInt8 nType = 0;
rStrm.ReadUChar(nType);
switch (nType)
{
case 0:
// nothing to do
maCells.set_empty(nStartRow, nDataSize);
break;
case 1:
{
// nDataSize double values
std::vector<double> aValues(nDataSize);
for (auto& rValue : aValues)
{
rStrm.ReadDouble(rValue);
}
maCells.set(nStartRow, aValues.begin(), aValues.end());
}
break;
case 2:
{
std::vector<svl::SharedString> aStrings(nDataSize);
svl::SharedStringPool& rPool = rDocument.GetSharedStringPool();
for (auto& rString : aStrings)
{
sal_Int32 nStrLength = 0;
rStrm.ReadInt32(nStrLength);
std::unique_ptr<char[]> pStr(new char[nStrLength]);
rStrm.ReadBytes(pStr.get(), nStrLength);
std::string_view aOStr(pStr.get(), nStrLength);
OUString aStr = OStringToOUString(aOStr, RTL_TEXTENCODING_UTF8);
rString = rPool.intern(aStr);
}
maCells.set(nStartRow, aStrings.begin(), aStrings.end());
}
break;
case 3:
{
std::vector<ScFormulaCell*> aFormulaCells(nDataSize);
ScAddress aAddr(nCol, nStartRow, nTab);
const formula::FormulaGrammar::Grammar eGrammar = formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1;
for (SCROW nRow = 0; nRow < static_cast<SCROW>(nDataSize);)
{
sal_uInt64 nFormulaGroupSize = 0;
rStrm.ReadUInt64(nFormulaGroupSize);
sal_Int32 nStrLength = 0;
rStrm.ReadInt32(nStrLength);
std::unique_ptr<char[]> pStr(new char[nStrLength]);
rStrm.ReadBytes(pStr.get(), nStrLength);
std::string_view aOStr(pStr.get(), nStrLength);
OUString aStr = OStringToOUString(aOStr, RTL_TEXTENCODING_UTF8);
for (sal_uInt64 i = 0; i < nFormulaGroupSize; ++i)
{
aFormulaCells[nRow + i] = new ScFormulaCell(rDocument, aAddr, aStr, eGrammar);
aAddr.IncRow();
}
nRow += nFormulaGroupSize;
}
maCells.set(nStartRow, aFormulaCells.begin(), aFormulaCells.end());
}
break;
}
nReadRow += nDataSize;
}
}
void ScColumn::CheckIntegrity() const
{
auto checkEventHandlerColumnRef = [this](const auto& rStore, std::string_view pStoreName)
{
if (const ScColumn* pColTest = rStore.event_handler().getColumn(); pColTest != this)
{
std::ostringstream os;
os << pStoreName << "'s event handler references wrong column instance (this=" << this
<< "; stored=" << pColTest << ")";
throw std::runtime_error(os.str());
}
};
auto countBlocks = [](const auto& rStore, mdds::mtv::element_t nBlockType)
{
std::size_t nCount = std::count_if(rStore.cbegin(), rStore.cend(),
[nBlockType](const auto& blk) { return blk.type == nBlockType; }
);
return nCount;
};
auto checkCachedBlockCount = [countBlocks](
const auto& rStore, mdds::mtv::element_t nBlockType, std::size_t nCachedBlkCount,
std::string_view pName)
{
std::size_t nCount = countBlocks(rStore, nBlockType);
if (nCachedBlkCount != nCount)
{
std::ostringstream os;
os << "incorrect cached " << pName << " block count (expected=" << nCount << "; actual="
<< nCachedBlkCount << ")";
throw std::runtime_error(os.str());
}
};
checkEventHandlerColumnRef(maCells, "cell store");
checkEventHandlerColumnRef(maCellNotes, "cell-note store");
checkCachedBlockCount(maCells, sc::element_type_formula, mnBlkCountFormula, "formula");
checkCachedBlockCount(maCellNotes, sc::element_type_cellnote, mnBlkCountCellNotes, "cell note");
}
void ScColumn::CollectBroadcasterState(sc::BroadcasterState& rState) const
{
for (const auto& block : maBroadcasters)
{
if (block.type != sc::element_type_broadcaster)
continue;
auto itBeg = sc::broadcaster_block::begin(*block.data);
auto itEnd = sc::broadcaster_block::end(*block.data);
for (auto it = itBeg; it != itEnd; ++it)
{
ScAddress aBCPos(nCol, block.position + std::distance(itBeg, it), nTab);
auto aRes = rState.aCellListenerStore.try_emplace(aBCPos);
auto& rLisStore = aRes.first->second;
const SvtBroadcaster& rBC = **it;
for (const SvtListener* pLis : rBC.GetAllListeners())
{
const auto* pFC = dynamic_cast<const ScFormulaCell*>(pLis);
if (pFC)
rLisStore.emplace_back(pFC);
else
rLisStore.emplace_back(pLis);
}
}
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */