/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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.GetPool()); 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 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.GetPool()); 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) { const ScPatternAttr* pAttr = (bSameDocPool ? rCxt.getSingleCellPattern(nColOffset) : rCxt.getSingleCellPattern(nColOffset)->PutInPool( &rDocument, rCxt.getClipDoc())); pAttrArray->SetPatternArea(nRow1, nRow2, pAttr, true); } } if ((nFlags & InsertDeleteFlags::CONTENTS) != InsertDeleteFlags::NONE) { std::vector aTextAttrs(nDestSize, rSrcAttr); switch (rSrcCell.getType()) { case CELLTYPE_VALUE: { std::vector 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 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 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 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 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; auto pSparkline = rContext.getSingleSparkline(nColOffset); if (pSparkline) { auto const& pSparklineGroup = pSparkline->getSparklineGroup(); auto pDuplicatedGroup = GetDoc().SearchSparklineGroup(pSparklineGroup->getID()); if (!pDuplicatedGroup) pDuplicatedGroup = std::make_shared(*pSparklineGroup); std::vector aSparklines(nDestSize, nullptr); ScAddress aCurrentPosition = aDestPosition; for (size_t i = 0; i < nDestSize; ++i) { auto pNewSparkline = std::make_shared(aCurrentPosition.Col(), aCurrentPosition.Row(), pDuplicatedGroup); pNewSparkline->setInputRange(pSparkline->getInputRange()); aSparklines[i] = new sc::SparklineCell(pNewSparkline); aCurrentPosition.IncRow(); } pBlockPos->miSparklinePos = maSparklines.set(pBlockPos->miSparklinePos, aDestPosition.Row(), aSparklines.begin(), aSparklines.end()); } } void ScColumn::SetValues( const SCROW nRow, const std::vector& 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 aNewSharedRows; DetachFormulaCells(aPos, rVals.size(), &aNewSharedRows); maCells.set(nRow, rVals.begin(), rVals.end()); std::vector aDefaults(rVals.size()); maCellTextAttrs.set(nRow, aDefaults.begin(), aDefaults.end()); CellStorageModified(); StartListeningUnshared( aNewSharedRows); std::vector 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 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 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 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 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 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 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& 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& 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 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 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& rRanges ) { sc::ColumnBlockPosition aBlockPos; InitBlockPosition(aBlockPos); CloneFormulaCell(aBlockPos, rSrc, rAttr, rRanges); } std::unique_ptr ScColumn::ReleaseNote( SCROW nRow ) { if (!GetDoc().ValidRow(nRow)) return nullptr; ScPostIt* p = nullptr; maCellNotes.release(nRow, p); return std::unique_ptr(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& mrNotes; SCTAB mnTab; SCCOL mnCol; SCROW mnStartRow; SCROW mnEndRow; public: NoteEntryCollector( std::vector& 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& 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& rNotes ) const { std::pair aPos = maCellNotes.position(nStartRow); sc::CellNoteStoreType::const_iterator it = aPos.first; if (it == maCellNotes.end()) // Invalid row number. return; std::pair aEndPos = maCellNotes.position(nEndRow); sc::CellNoteStoreType::const_iterator itEnd = aEndPos.first; std::for_each(it, ++itEnd, NoteEntryCollector(rNotes, nTab, nCol, nStartRow, nEndRow)); } bool ScColumn::HasCellNote(SCROW nStartRow, SCROW nEndRow) const { std::pair aStartPos = maCellNotes.position(nStartRow); if (aStartPos.first == maCellNotes.end()) // Invalid row number. return false; std::pair 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 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 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 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 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 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; SvNumberFormatter* mpFormatter; 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(mpFormatter, pCondSet); OUString aStr = ScCellFormat::GetString(rCell, nFormat, &pColor, *mpFormatter, 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())), mpFormatter(rCol.GetDoc().GetFormatTable()), 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(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> aThisColRowDrawObjects = pDrawLayer->GetObjectsAnchoredToRange(GetTab(), GetCol(), nRow1, nRow2); std::map> aOtherColRowDrawObjects = pDrawLayer->GetObjectsAnchoredToRange(GetTab(), rOther.GetCol(), nRow1, nRow2); for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow) { std::vector& rThisCellDrawObjects = aThisColRowDrawObjects[nRow]; if (!rThisCellDrawObjects.empty()) UpdateDrawObjectsForRow(rThisCellDrawObjects, rOther.GetCol(), nRow); std::vector& 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 (!SfxPoolItem::areSame(pPat1, pPat2)) { if (pPat1->GetRefCount() == 1) pPat1 = &rOther.GetDoc().GetPool()->DirectPutItemInPool(*pPat1); SetPattern(nRow, *pPat2); rOther.SetPattern(nRow, *pPat1); } } } 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 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& rBounds ) { rBounds.swap(maBounds); } }; } void ScColumn::SplitFormulaGroupByRelativeRef( const ScRange& rBoundRange ) { if (rBoundRange.aStart.Row() >= GetDoc().MaxRow()) // Nothing to split. return; std::vector 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& mrListeners; public: explicit ListenerCollector( std::vector& 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& mrCells; public: explicit FormulaCellCollector( std::vector& rCells ) : mrCells(rCells) {} void operator() ( size_t /*nRow*/, ScFormulaCell* p ) { mrCells.push_back(p); } }; } void ScColumn::CollectListeners( std::vector& 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& 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 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* 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* 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 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 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(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(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 aValues(nDataSize); for (auto& rValue : aValues) { rStrm.ReadDouble(rValue); } maCells.set(nStartRow, aValues.begin(), aValues.end()); } break; case 2: { std::vector aStrings(nDataSize); svl::SharedStringPool& rPool = rDocument.GetSharedStringPool(); for (auto& rString : aStrings) { sal_Int32 nStrLength = 0; rStrm.ReadInt32(nStrLength); std::unique_ptr 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 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(nDataSize);) { sal_uInt64 nFormulaGroupSize = 0; rStrm.ReadUInt64(nFormulaGroupSize); sal_Int32 nStrLength = 0; rStrm.ReadInt32(nStrLength); std::unique_ptr 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 { const ScColumn* pColTest = maCells.event_handler().getColumn(); if (pColTest != this) { std::ostringstream os; os << "cell store's event handler references wrong column instance (this=" << this << "; stored=" << pColTest << ")"; throw std::runtime_error(os.str()); } size_t nCount = std::count_if(maCells.cbegin(), maCells.cend(), [](const auto& blk) { return blk.type == sc::element_type_formula; } ); if (mnBlkCountFormula != nCount) { std::ostringstream os; os << "incorrect cached formula block count (expected=" << nCount << "; actual=" << mnBlkCountFormula << ")"; throw std::runtime_error(os.str()); } } 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(pLis); if (pFC) rLisStore.emplace_back(pFC); else rLisStore.emplace_back(pLis); } } } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */