2284 lines
76 KiB
C++
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: */
|