summaryrefslogtreecommitdiffstats
path: root/sc/source/core/data/column3.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/data/column3.cxx')
-rw-r--r--sc/source/core/data/column3.cxx3726
1 files changed, 3726 insertions, 0 deletions
diff --git a/sc/source/core/data/column3.cxx b/sc/source/core/data/column3.cxx
new file mode 100644
index 000000000..ead02920a
--- /dev/null
+++ b/sc/source/core/data/column3.cxx
@@ -0,0 +1,3726 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <column.hxx>
+
+#include <scitems.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <cellform.hxx>
+#include <typedstrdata.hxx>
+#include <formula/errorcodes.hxx>
+#include <formula/token.hxx>
+#include <brdcst.hxx>
+#include <docoptio.hxx>
+#include <subtotal.hxx>
+#include <markdata.hxx>
+#include <stringutil.hxx>
+#include <docpool.hxx>
+#include <cellvalue.hxx>
+#include <tokenarray.hxx>
+#include <clipcontext.hxx>
+#include <columnspanset.hxx>
+#include <mtvcellfunc.hxx>
+#include <scopetools.hxx>
+#include <editutil.hxx>
+#include <sharedformula.hxx>
+#include <listenercontext.hxx>
+#include <filterentries.hxx>
+#include <conditio.hxx>
+#include <colorscale.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/colritem.hxx>
+
+#include <com/sun/star/i18n/LocaleDataItem2.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+#include <memory>
+
+#include <o3tl/deleter.hxx>
+
+#include <rtl/tencinfo.h>
+
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <osl/diagnose.h>
+
+#include <cstdio>
+#include <refdata.hxx>
+
+using ::com::sun::star::i18n::LocaleDataItem2;
+
+using namespace formula;
+
+void ScColumn::Broadcast( SCROW nRow )
+{
+ ScHint aHint(SfxHintId::ScDataChanged, ScAddress(nCol, nRow, nTab));
+ GetDoc().Broadcast(aHint);
+}
+
+void ScColumn::BroadcastCells( const std::vector<SCROW>& rRows, SfxHintId nHint )
+{
+ if (rRows.empty())
+ return;
+
+ // Broadcast the changes.
+ ScDocument& rDocument = GetDoc();
+ ScHint aHint(nHint, ScAddress(nCol, 0, nTab));
+ for (const auto& rRow : rRows)
+ {
+ aHint.SetAddressRow(rRow);
+ rDocument.Broadcast(aHint);
+ }
+}
+
+void ScColumn::BroadcastRows( SCROW nStartRow, SCROW nEndRow, SfxHintId nHint )
+{
+ if( nStartRow > GetLastDataPos())
+ return;
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(*this, nStartRow, nEndRow);
+ std::vector<SCROW> aRows;
+ aSpanSet.getRows(aRows);
+ BroadcastCells(aRows, nHint);
+}
+
+namespace {
+
+class CellInterpreterBase
+{
+protected:
+ void Interpret(ScFormulaCell* p)
+ {
+ // Interpret() takes a range in a formula group, so group those together.
+ if( !groupCells.empty() && p->GetCellGroup() == groupCells.back()->GetCellGroup()
+ && p->aPos.Row() == groupCells.back()->aPos.Row() + 1 )
+ {
+ assert( p->aPos.Tab() == groupCells.back()->aPos.Tab()
+ && p->aPos.Col() == groupCells.back()->aPos.Col());
+ groupCells.push_back(p); // Extend range.
+ return;
+ }
+ flushPending();
+ if( !p->GetCellGroup())
+ {
+ p->Interpret();
+ return;
+ }
+ groupCells.push_back(p);
+
+ }
+ ~CellInterpreterBase()
+ {
+ suppress_fun_call_w_exception(flushPending());
+ }
+private:
+ void flushPending()
+ {
+ if(groupCells.empty())
+ return;
+ SCROW firstRow = groupCells.front()->GetCellGroup()->mpTopCell->aPos.Row();
+ if(!groupCells.front()->Interpret(
+ groupCells.front()->aPos.Row() - firstRow, groupCells.back()->aPos.Row() - firstRow))
+ {
+ // Interpret() will try to group-interpret the given cell range if possible, but if that
+ // is not possible, it will interpret just the given cell. So if group-interpreting
+ // wasn't possible, interpret them one by one.
+ for(ScFormulaCell* cell : groupCells)
+ cell->Interpret();
+ }
+ groupCells.clear();
+ }
+ std::vector<ScFormulaCell*> groupCells;
+};
+
+class DirtyCellInterpreter : public CellInterpreterBase
+{
+public:
+ void operator() (size_t, ScFormulaCell* p)
+ {
+ if(p->GetDirty())
+ Interpret(p);
+ }
+};
+
+class NeedsInterpretCellInterpreter : public CellInterpreterBase
+{
+public:
+ void operator() (size_t, ScFormulaCell* p)
+ {
+ if(p->NeedsInterpret())
+ {
+ Interpret(p);
+ // In some cases such as circular dependencies Interpret()
+ // will not reset the dirty flag, check that in order to tell
+ // the caller that the cell range may trigger Interpret() again.
+ if(p->NeedsInterpret())
+ allInterpreted = false;
+ }
+ }
+ bool allInterpreted = true;
+};
+
+}
+
+void ScColumn::InterpretDirtyCells( SCROW nRow1, SCROW nRow2 )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return;
+
+ DirtyCellInterpreter aFunc;
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+}
+
+bool ScColumn::InterpretCellsIfNeeded( SCROW nRow1, SCROW nRow2 )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return false;
+
+ NeedsInterpretCellInterpreter aFunc;
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+ return aFunc.allInterpreted;
+}
+
+void ScColumn::DeleteContent( SCROW nRow, bool bBroadcast )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it == maCells.end())
+ return;
+
+ if (it->type == sc::element_type_formula)
+ {
+ ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ p->EndListeningTo(GetDoc());
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, *p);
+ }
+ maCells.set_empty(nRow, nRow);
+
+ if (bBroadcast)
+ {
+ Broadcast(nRow);
+ CellStorageModified();
+ }
+}
+
+void ScColumn::Delete( SCROW nRow )
+{
+ DeleteContent(nRow, false);
+ maCellTextAttrs.set_empty(nRow, nRow);
+ maCellNotes.set_empty(nRow, nRow);
+ maSparklines.set_empty(nRow, nRow);
+
+ Broadcast(nRow);
+ CellStorageModified();
+}
+
+void ScColumn::FreeAll()
+{
+ maCells.event_handler().stop();
+
+ auto maxRowCount = GetDoc().GetMaxRowCount();
+ // Keep a logical empty range of 0-rDoc.MaxRow() at all times.
+ maCells.clear();
+ maCells.resize(maxRowCount);
+ maCellTextAttrs.clear();
+ maCellTextAttrs.resize(maxRowCount);
+ maCellNotes.clear();
+ maCellNotes.resize(maxRowCount);
+ maSparklines.clear();
+ maSparklines.resize(maxRowCount);
+ CellStorageModified();
+}
+
+void ScColumn::FreeNotes()
+{
+ maCellNotes.clear();
+ maCellNotes.resize(GetDoc().GetMaxRowCount());
+}
+
+namespace {
+
+class ShiftFormulaPosHandler
+{
+public:
+
+ void operator() (size_t nRow, ScFormulaCell* pCell)
+ {
+ pCell->aPos.SetRow(nRow);
+ }
+};
+
+}
+
+void ScColumn::DeleteRow( SCROW nStartRow, SCSIZE nSize, std::vector<ScAddress>* pGroupPos )
+{
+ pAttrArray->DeleteRow( nStartRow, nSize );
+
+ SCROW nEndRow = nStartRow + nSize - 1;
+
+ maBroadcasters.erase(nStartRow, nEndRow);
+ maBroadcasters.resize(GetDoc().GetMaxRowCount());
+
+ CellNotesDeleting(nStartRow, nEndRow, false);
+ maCellNotes.erase(nStartRow, nEndRow);
+ maCellNotes.resize(GetDoc().GetMaxRowCount());
+
+ // See if we have any cells that would get deleted or shifted by deletion.
+ sc::CellStoreType::position_type aPos = maCells.position(nStartRow);
+ sc::CellStoreType::iterator itCell = aPos.first;
+ if (itCell->type == sc::element_type_empty)
+ {
+ // This is an empty block. If this is the last block, then there is no cells to delete or shift.
+ sc::CellStoreType::iterator itTest = itCell;
+ ++itTest;
+ if (itTest == maCells.end())
+ {
+ // No cells are affected by this deletion. Bail out.
+ CellStorageModified(); // broadcast array has been modified.
+ return;
+ }
+ }
+
+ // Check if there are any cells below the end row that will get shifted.
+ bool bShiftCells = false;
+ if (nEndRow < GetDoc().MaxRow()) //only makes sense to do this if there *is* a row after the end row
+ {
+ aPos = maCells.position(itCell, nEndRow+1);
+ itCell = aPos.first;
+ if (itCell->type == sc::element_type_empty)
+ {
+ // This block is empty. See if there is any block that follows.
+ sc::CellStoreType::iterator itTest = itCell;
+ ++itTest;
+ if (itTest != maCells.end())
+ // Non-empty block follows -> cells that will get shifted.
+ bShiftCells = true;
+ }
+ else
+ bShiftCells = true;
+ }
+
+ sc::SingleColumnSpanSet aNonEmptySpans(GetDoc().GetSheetLimits());
+ if (bShiftCells)
+ {
+ // Mark all non-empty cell positions below the end row.
+ sc::ColumnBlockConstPosition aBlockPos;
+ aBlockPos.miCellPos = itCell;
+ aNonEmptySpans.scan(aBlockPos, *this, nEndRow+1, GetDoc().MaxRow());
+ }
+
+ sc::AutoCalcSwitch aACSwitch(GetDoc(), false);
+
+ // Remove the cells.
+ maCells.erase(nStartRow, nEndRow);
+ maCells.resize(GetDoc().GetMaxRowCount());
+
+ // Get the position again after the container change.
+ aPos = maCells.position(nStartRow);
+
+ // Shift the formula cell positions below the start row.
+ ShiftFormulaPosHandler aShiftFormulaFunc;
+ sc::ProcessFormula(aPos.first, maCells, nStartRow, GetDoc().MaxRow(), aShiftFormulaFunc);
+
+ bool bJoined = sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ if (bJoined && pGroupPos)
+ pGroupPos->push_back(ScAddress(nCol, nStartRow, nTab));
+
+ // Shift the text attribute array too (before the broadcast).
+ maCellTextAttrs.erase(nStartRow, nEndRow);
+ maCellTextAttrs.resize(GetDoc().GetMaxRowCount());
+
+ CellStorageModified();
+}
+
+sc::CellStoreType::iterator ScColumn::GetPositionToInsert( SCROW nRow, std::vector<SCROW>& rNewSharedRows,
+ bool bInsertFormula )
+{
+ return GetPositionToInsert(maCells.begin(), nRow, rNewSharedRows, bInsertFormula);
+}
+
+void ScColumn::JoinNewFormulaCell(
+ const sc::CellStoreType::position_type& aPos, ScFormulaCell& rCell )
+{
+ // Check the previous row position for possible grouping.
+ if (aPos.first->type == sc::element_type_formula && aPos.second > 0)
+ {
+ ScFormulaCell& rPrev = *sc::formula_block::at(*aPos.first->data, aPos.second-1);
+ sc::CellStoreType::position_type aPosPrev = aPos;
+ --aPosPrev.second;
+ sc::SharedFormulaUtil::joinFormulaCells(aPosPrev, rPrev, rCell);
+ }
+
+ // Check the next row position for possible grouping.
+ if (aPos.first->type == sc::element_type_formula && aPos.second+1 < aPos.first->size)
+ {
+ ScFormulaCell& rNext = *sc::formula_block::at(*aPos.first->data, aPos.second+1);
+ sc::SharedFormulaUtil::joinFormulaCells(aPos, rCell, rNext);
+ }
+}
+
+void ScColumn::DetachFormulaCell(
+ const sc::CellStoreType::position_type& aPos, ScFormulaCell& rCell, std::vector<SCROW>& rNewSharedRows )
+{
+ if (!GetDoc().IsClipOrUndo())
+ {
+#if USE_FORMULA_GROUP_LISTENER
+ if (rCell.IsShared() && rCell.GetSharedLength() > 1)
+ {
+ // Record new spans (shared or remaining single) that will result
+ // from unsharing to reestablish listeners.
+ // Same cases as in unshareFormulaCell().
+ // XXX NOTE: this is not part of unshareFormulaCell() because that
+ // is called in other contexts as well, for which passing and
+ // determining the rows vector would be superfluous. If that was
+ // needed, move it there.
+ const SCROW nSharedTopRow = rCell.GetSharedTopRow();
+ const SCROW nSharedLength = rCell.GetSharedLength();
+ if (rCell.aPos.Row() == nSharedTopRow)
+ {
+ // Top cell.
+ // Next row will be new shared top or single cell.
+ rNewSharedRows.push_back( nSharedTopRow + 1);
+ rNewSharedRows.push_back( nSharedTopRow + nSharedLength - 1);
+ }
+ else if (rCell.aPos.Row() == nSharedTopRow + nSharedLength - 1)
+ {
+ // Bottom cell.
+ // Current shared top row will be new shared top again or
+ // single cell.
+ rNewSharedRows.push_back( nSharedTopRow);
+ rNewSharedRows.push_back( rCell.aPos.Row() - 1);
+ }
+ else
+ {
+ // Some mid cell.
+ // Current shared top row will be new shared top again or
+ // single cell, plus a new shared top below or single cell.
+ rNewSharedRows.push_back( nSharedTopRow);
+ rNewSharedRows.push_back( rCell.aPos.Row() - 1);
+ rNewSharedRows.push_back( rCell.aPos.Row() + 1);
+ rNewSharedRows.push_back( nSharedTopRow + nSharedLength - 1);
+ }
+ }
+#endif
+
+ // Have the dying formula cell stop listening.
+ // If in a shared formula group this ends the group listening.
+ rCell.EndListeningTo(GetDoc());
+ }
+
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, rCell);
+}
+
+void ScColumn::StartListeningUnshared( const std::vector<SCROW>& rNewSharedRows )
+{
+ assert(rNewSharedRows.empty() || rNewSharedRows.size() == 2 || rNewSharedRows.size() == 4);
+ ScDocument& rDoc = GetDoc();
+ if (rNewSharedRows.empty() || rDoc.IsDelayedFormulaGrouping())
+ return;
+
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(rDoc);
+ sc::StartListeningContext aStartCxt(rDoc, pPosSet);
+ sc::EndListeningContext aEndCxt(rDoc, pPosSet);
+ if (rNewSharedRows.size() >= 2)
+ {
+ if(!rDoc.CanDelayStartListeningFormulaCells( this, rNewSharedRows[0], rNewSharedRows[1]))
+ StartListeningFormulaCells(aStartCxt, aEndCxt, rNewSharedRows[0], rNewSharedRows[1]);
+ }
+ if (rNewSharedRows.size() >= 4)
+ {
+ if(!rDoc.CanDelayStartListeningFormulaCells( this, rNewSharedRows[2], rNewSharedRows[3]))
+ StartListeningFormulaCells(aStartCxt, aEndCxt, rNewSharedRows[2], rNewSharedRows[3]);
+ }
+}
+
+namespace {
+
+class AttachFormulaCellsHandler
+{
+ sc::StartListeningContext& mrCxt;
+
+public:
+ explicit AttachFormulaCellsHandler(sc::StartListeningContext& rCxt)
+ : mrCxt(rCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->StartListeningTo(mrCxt);
+ }
+};
+
+class DetachFormulaCellsHandler
+{
+ ScDocument& mrDoc;
+ sc::EndListeningContext* mpCxt;
+
+public:
+ DetachFormulaCellsHandler( ScDocument& rDoc, sc::EndListeningContext* pCxt ) :
+ mrDoc(rDoc), mpCxt(pCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ if (mpCxt)
+ pCell->EndListeningTo(*mpCxt);
+ else
+ pCell->EndListeningTo(mrDoc);
+ }
+};
+
+}
+
+void ScColumn::DetachFormulaCells(
+ const sc::CellStoreType::position_type& aPos, size_t nLength, std::vector<SCROW>* pNewSharedRows )
+{
+ const size_t nRow = aPos.first->position + aPos.second;
+ const size_t nNextTopRow = nRow + nLength; // start row of next formula group.
+
+ bool bLowerSplitOff = false;
+ if (pNewSharedRows && !GetDoc().IsClipOrUndo())
+ {
+ const ScFormulaCell* pFC = sc::SharedFormulaUtil::getSharedTopFormulaCell(aPos);
+ if (pFC)
+ {
+ const SCROW nTopRow = pFC->GetSharedTopRow();
+ const SCROW nBotRow = nTopRow + pFC->GetSharedLength() - 1;
+ // nTopRow <= nRow <= nBotRow, because otherwise pFC would not exist.
+ if (nTopRow < static_cast<SCROW>(nRow))
+ {
+ // Upper part will be split off.
+ pNewSharedRows->push_back(nTopRow);
+ pNewSharedRows->push_back(nRow - 1);
+ }
+ if (static_cast<SCROW>(nNextTopRow) <= nBotRow)
+ {
+ // Lower part will be split off.
+ pNewSharedRows->push_back(nNextTopRow);
+ pNewSharedRows->push_back(nBotRow);
+ bLowerSplitOff = true;
+ }
+ }
+ }
+
+ // Split formula grouping at the top and bottom boundaries.
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
+
+ if (nLength > 0 && GetDoc().ValidRow(nNextTopRow))
+ {
+ if (pNewSharedRows && !bLowerSplitOff && !GetDoc().IsClipOrUndo())
+ {
+ sc::CellStoreType::position_type aPos2 = maCells.position(aPos.first, nNextTopRow-1);
+ const ScFormulaCell* pFC = sc::SharedFormulaUtil::getSharedTopFormulaCell(aPos2);
+ if (pFC)
+ {
+ const SCROW nTopRow = pFC->GetSharedTopRow();
+ const SCROW nBotRow = nTopRow + pFC->GetSharedLength() - 1;
+ // nRow < nTopRow < nNextTopRow <= nBotRow
+ if (static_cast<SCROW>(nNextTopRow) <= nBotRow)
+ {
+ // Lower part will be split off.
+ pNewSharedRows->push_back(nNextTopRow);
+ pNewSharedRows->push_back(nBotRow);
+ }
+ }
+ }
+
+ sc::CellStoreType::position_type aPos2 = maCells.position(aPos.first, nNextTopRow);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos2, nullptr);
+ }
+
+ if (GetDoc().IsClipOrUndo())
+ return;
+
+ DetachFormulaCellsHandler aFunc(GetDoc(), nullptr);
+ sc::ProcessFormula(aPos.first, maCells, nRow, nNextTopRow-1, aFunc);
+}
+
+void ScColumn::AttachFormulaCells( sc::StartListeningContext& rCxt, SCROW nRow1, SCROW nRow2 )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow1);
+ sc::CellStoreType::iterator it = aPos.first;
+
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ if (GetDoc().ValidRow(nRow2+1))
+ {
+ aPos = maCells.position(it, nRow2+1);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+
+ if (GetDoc().IsClipOrUndo())
+ return;
+
+ // Need to process (start listening) entire shared formula groups, not just
+ // a slice thereof.
+ bool bEnlargedDown = false;
+ aPos = maCells.position(nRow1);
+ it = aPos.first;
+ if (it->type == sc::element_type_formula)
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*it->data, aPos.second);
+ if (rCell.IsShared())
+ {
+ nRow1 = std::min( nRow1, rCell.GetSharedTopRow());
+ if (nRow2 < rCell.GetSharedTopRow() + rCell.GetSharedLength())
+ {
+ nRow2 = rCell.GetSharedTopRow() + rCell.GetSharedLength() - 1;
+ bEnlargedDown = true;
+ // Same end row is also enlarged, i.e. doesn't need to be
+ // checked for another group.
+ }
+ }
+ }
+ if (!bEnlargedDown)
+ {
+ aPos = maCells.position(it, nRow2);
+ it = aPos.first;
+ if (it->type == sc::element_type_formula)
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*it->data, aPos.second);
+ if (rCell.IsShared())
+ {
+ nRow2 = std::max( nRow2, rCell.GetSharedTopRow() + rCell.GetSharedLength() - 1);
+ }
+ }
+ }
+
+ AttachFormulaCellsHandler aFunc(rCxt);
+ sc::ProcessFormula(it, maCells, nRow1, nRow2, aFunc);
+}
+
+void ScColumn::DetachFormulaCells( sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2 )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow1);
+ sc::CellStoreType::iterator it = aPos.first;
+
+ // Split formula grouping at the top and bottom boundaries.
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, &rCxt);
+ if (GetDoc().ValidRow(nRow2+1))
+ {
+ aPos = maCells.position(it, nRow2+1);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, &rCxt);
+ }
+
+ if (GetDoc().IsClipOrUndo())
+ return;
+
+ DetachFormulaCellsHandler aFunc(GetDoc(), &rCxt);
+ sc::ProcessFormula(it, maCells, nRow1, nRow2, aFunc);
+}
+
+static void lcl_AddFormulaGroupBoundaries(const sc::CellStoreType::position_type& rPos,
+ std::vector<SCROW>& rNewSharedRows )
+{
+ sc::CellStoreType::iterator itRet = rPos.first;
+ if (itRet->type != sc::element_type_formula)
+ return;
+
+ ScFormulaCell& rFC = *sc::formula_block::at(*itRet->data, rPos.second);
+ if ( rFC.IsShared() )
+ {
+ const SCROW nSharedTopRow = rFC.GetSharedTopRow();
+ const SCROW nSharedLength = rFC.GetSharedLength();
+ rNewSharedRows.push_back( nSharedTopRow);
+ rNewSharedRows.push_back( nSharedTopRow + nSharedLength - 1);
+ }
+ else
+ {
+ const SCROW nRow = rFC.aPos.Row();
+ rNewSharedRows.push_back( nRow);
+ rNewSharedRows.push_back( nRow);
+ }
+}
+
+sc::CellStoreType::iterator ScColumn::GetPositionToInsert( const sc::CellStoreType::iterator& it, SCROW nRow,
+ std::vector<SCROW>& rNewSharedRows, bool bInsertFormula )
+{
+ // See if we are overwriting an existing formula cell.
+ sc::CellStoreType::position_type aPos = maCells.position(it, nRow);
+ sc::CellStoreType::iterator itRet = aPos.first;
+
+ if (itRet->type == sc::element_type_formula)
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*itRet->data, aPos.second);
+ DetachFormulaCell(aPos, rCell, rNewSharedRows);
+ }
+ else if (bInsertFormula && !GetDoc().IsClipOrUndo())
+ {
+ if (nRow > 0)
+ {
+ sc::CellStoreType::position_type aPosBefore = maCells.position(maCells.begin(), nRow-1);
+ lcl_AddFormulaGroupBoundaries(aPosBefore, rNewSharedRows);
+ }
+ if (nRow < GetDoc().MaxRow())
+ {
+ sc::CellStoreType::position_type aPosAfter = maCells.position(maCells.begin(), nRow+1);
+ lcl_AddFormulaGroupBoundaries(aPosAfter, rNewSharedRows);
+ }
+ }
+
+ return itRet;
+}
+
+void ScColumn::AttachNewFormulaCell(
+ const sc::CellStoreType::iterator& itPos, SCROW nRow, ScFormulaCell& rCell,
+ const std::vector<SCROW>& rNewSharedRows,
+ bool bJoin, sc::StartListeningType eListenType )
+{
+ AttachNewFormulaCell(maCells.position(itPos, nRow), rCell, rNewSharedRows, bJoin, eListenType);
+}
+
+void ScColumn::AttachNewFormulaCell(
+ const sc::CellStoreType::position_type& aPos, ScFormulaCell& rCell,
+ const std::vector<SCROW>& rNewSharedRows,
+ bool bJoin, sc::StartListeningType eListenType )
+{
+ if (bJoin)
+ // See if this new formula cell can join an existing shared formula group.
+ JoinNewFormulaCell(aPos, rCell);
+
+ // When we insert from the Clipboard we still have wrong (old) References!
+ // First they are rewired in CopyBlockFromClip via UpdateReference and the
+ // we call StartListeningFromClip and BroadcastFromClip.
+ // If we insert into the Clipboard/andoDoc, we do not use a Broadcast.
+ // After Import we call CalcAfterLoad and in there Listening.
+ ScDocument& rDocument = GetDoc();
+ if (rDocument.IsClipOrUndo() || rDocument.IsInsertingFromOtherDoc())
+ return;
+
+ switch (eListenType)
+ {
+ case sc::ConvertToGroupListening:
+ {
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(rDocument);
+ sc::StartListeningContext aStartCxt(rDocument, pPosSet);
+ sc::EndListeningContext aEndCxt(rDocument, pPosSet);
+ SCROW nStartRow, nEndRow;
+ nStartRow = nEndRow = aPos.first->position + aPos.second;
+ for (const SCROW nR : rNewSharedRows)
+ {
+ if (nStartRow > nR)
+ nStartRow = nR;
+ if (nEndRow < nR)
+ nEndRow = nR;
+ }
+ StartListeningFormulaCells(aStartCxt, aEndCxt, nStartRow, nEndRow);
+ }
+ break;
+ case sc::SingleCellListening:
+ rCell.StartListeningTo(rDocument);
+ StartListeningUnshared( rNewSharedRows);
+ break;
+ case sc::NoListening:
+ default:
+ if (!rNewSharedRows.empty())
+ {
+ assert(rNewSharedRows.size() == 2 || rNewSharedRows.size() == 4);
+ // Calling SetNeedsListeningGroup() with a top row sets it to
+ // all affected formula cells of that group.
+ const ScFormulaCell* pFC = GetFormulaCell( rNewSharedRows[0]);
+ assert(pFC); // that *is* supposed to be a top row
+ if (pFC && !pFC->NeedsListening())
+ SetNeedsListeningGroup( rNewSharedRows[0]);
+ if (rNewSharedRows.size() > 2)
+ {
+ pFC = GetFormulaCell( rNewSharedRows[2]);
+ assert(pFC); // that *is* supposed to be a top row
+ if (pFC && !pFC->NeedsListening())
+ SetNeedsListeningGroup( rNewSharedRows[2]);
+ }
+ }
+ break;
+ }
+
+ if (!rDocument.IsCalcingAfterLoad())
+ rCell.SetDirty();
+}
+
+void ScColumn::AttachNewFormulaCells( const sc::CellStoreType::position_type& aPos, size_t nLength,
+ std::vector<SCROW>& rNewSharedRows )
+{
+ // Make sure the whole length consists of formula cells.
+ if (aPos.first->type != sc::element_type_formula)
+ return;
+
+ if (aPos.first->size < aPos.second + nLength)
+ // Block is shorter than specified length.
+ return;
+
+ // Join the top and bottom cells only.
+ ScFormulaCell* pCell1 = sc::formula_block::at(*aPos.first->data, aPos.second);
+ JoinNewFormulaCell(aPos, *pCell1);
+
+ sc::CellStoreType::position_type aPosLast = aPos;
+ aPosLast.second += nLength - 1;
+ ScFormulaCell* pCell2 = sc::formula_block::at(*aPosLast.first->data, aPosLast.second);
+ JoinNewFormulaCell(aPosLast, *pCell2);
+
+ ScDocument& rDocument = GetDoc();
+ if (rDocument.IsClipOrUndo() || rDocument.IsInsertingFromOtherDoc())
+ return;
+
+ const bool bShared = pCell1->IsShared() || pCell2->IsShared();
+ if (bShared)
+ {
+ const SCROW nTopRow = (pCell1->IsShared() ? pCell1->GetSharedTopRow() : pCell1->aPos.Row());
+ const SCROW nBotRow = (pCell2->IsShared() ?
+ pCell2->GetSharedTopRow() + pCell2->GetSharedLength() - 1 : pCell2->aPos.Row());
+ if (rNewSharedRows.empty())
+ {
+ rNewSharedRows.push_back( nTopRow);
+ rNewSharedRows.push_back( nBotRow);
+ }
+ else if (rNewSharedRows.size() == 2)
+ {
+ // Combine into one span.
+ if (rNewSharedRows[0] > nTopRow)
+ rNewSharedRows[0] = nTopRow;
+ if (rNewSharedRows[1] < nBotRow)
+ rNewSharedRows[1] = nBotRow;
+ }
+ else if (rNewSharedRows.size() == 4)
+ {
+ // Merge into one span.
+ // The original two spans are ordered from top to bottom.
+ std::vector<SCROW> aRows { std::min( rNewSharedRows[0], nTopRow), std::max( rNewSharedRows[3], nBotRow) };
+ rNewSharedRows.swap( aRows);
+ }
+ else
+ {
+ assert(!"rNewSharedRows?");
+ }
+ }
+ StartListeningUnshared( rNewSharedRows);
+
+ sc::StartListeningContext aCxt(rDocument);
+ ScFormulaCell** pp = &sc::formula_block::at(*aPos.first->data, aPos.second);
+ ScFormulaCell** ppEnd = pp + nLength;
+ for (; pp != ppEnd; ++pp)
+ {
+ if (!bShared)
+ (*pp)->StartListeningTo(aCxt);
+ if (!rDocument.IsCalcingAfterLoad())
+ (*pp)->SetDirty();
+ }
+}
+
+void ScColumn::BroadcastNewCell( SCROW nRow )
+{
+ // When we insert from the Clipboard we still have wrong (old) References!
+ // First they are rewired in CopyBlockFromClip via UpdateReference and the
+ // we call StartListeningFromClip and BroadcastFromClip.
+ // If we insert into the Clipboard/andoDoc, we do not use a Broadcast.
+ // After Import we call CalcAfterLoad and in there Listening.
+ if (GetDoc().IsClipOrUndo() || GetDoc().IsInsertingFromOtherDoc() || GetDoc().IsCalcingAfterLoad())
+ return;
+
+ Broadcast(nRow);
+}
+
+bool ScColumn::UpdateScriptType( sc::CellTextAttr& rAttr, SCROW nRow, sc::CellStoreType::iterator& itr )
+{
+ if (rAttr.mnScriptType != SvtScriptType::UNKNOWN)
+ // Already updated. Nothing to do.
+ return false;
+
+ // Script type not yet determined. Determine the real script
+ // type, and store it.
+ const ScPatternAttr* pPattern = GetPattern(nRow);
+ if (!pPattern)
+ return false;
+
+ sc::CellStoreType::position_type pos = maCells.position(itr, nRow);
+ itr = pos.first;
+ size_t nOffset = pos.second;
+ ScRefCellValue aCell = GetCellValue( itr, nOffset );
+ ScAddress aPos(nCol, nRow, nTab);
+
+ ScDocument& rDocument = GetDoc();
+ const SfxItemSet* pCondSet = nullptr;
+ ScConditionalFormatList* pCFList = rDocument.GetCondFormList(nTab);
+ if (pCFList)
+ {
+ const ScCondFormatItem& rItem =
+ pPattern->GetItem(ATTR_CONDITIONAL);
+ const ScCondFormatIndexes& rData = rItem.GetCondFormatData();
+ pCondSet = rDocument.GetCondResult(aCell, aPos, *pCFList, rData);
+ }
+
+ SvNumberFormatter* pFormatter = rDocument.GetFormatTable();
+
+ const Color* pColor;
+ sal_uInt32 nFormat = pPattern->GetNumberFormat(pFormatter, pCondSet);
+ OUString aStr = ScCellFormat::GetString(aCell, nFormat, &pColor, *pFormatter, rDocument);
+
+ // Store the real script type to the array.
+ rAttr.mnScriptType = rDocument.GetStringScriptType(aStr);
+ return true;
+}
+
+namespace {
+
+class DeleteAreaHandler
+{
+ ScDocument& mrDoc;
+ std::vector<ScFormulaCell*> maFormulaCells;
+ sc::SingleColumnSpanSet maDeleteRanges;
+
+ bool mbNumeric:1;
+ bool mbString:1;
+ bool mbFormula:1;
+ bool mbDateTime:1;
+ ScColumn& mrCol;
+
+public:
+ DeleteAreaHandler(ScDocument& rDoc, InsertDeleteFlags nDelFlag, ScColumn& rCol) :
+ mrDoc(rDoc),
+ maDeleteRanges(rDoc.GetSheetLimits()),
+ mbNumeric(nDelFlag & InsertDeleteFlags::VALUE),
+ mbString(nDelFlag & InsertDeleteFlags::STRING),
+ mbFormula(nDelFlag & InsertDeleteFlags::FORMULA),
+ mbDateTime(nDelFlag & InsertDeleteFlags::DATETIME),
+ mrCol(rCol) {}
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ // Numeric type target datetime and number, thus we have a dedicated function
+ if (!mbNumeric && !mbDateTime)
+ return;
+
+ // If numeric and datetime selected, delete full range
+ if (mbNumeric && mbDateTime)
+ break;
+
+ deleteNumeric(node, nOffset, nDataSize);
+ return;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ if (!mbString)
+ return;
+ break;
+ case sc::element_type_formula:
+ {
+ if (!mbFormula)
+ return;
+
+ sc::formula_block::iterator it = sc::formula_block::begin(*node.data) + nOffset;
+ sc::formula_block::iterator itEnd = it + nDataSize;
+ maFormulaCells.insert(maFormulaCells.end(), it, itEnd);
+ }
+ break;
+ case sc::element_type_empty:
+ default:
+ return;
+ }
+
+ // Tag these cells for deletion.
+ SCROW nRow1 = node.position + nOffset;
+ SCROW nRow2 = nRow1 + nDataSize - 1;
+ maDeleteRanges.set(nRow1, nRow2, true);
+ }
+
+ void deleteNumeric(const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ size_t nStart = node.position + nOffset;
+ size_t nElements = 1;
+ bool bLastTypeDateTime = isDateTime(nStart); // true = datetime, false = numeric
+ size_t nCount = nStart + nDataSize;
+
+ for (size_t i = nStart + 1; i < nCount; i++)
+ {
+ bool bIsDateTime = isDateTime(i);
+
+ // same type as previous
+ if (bIsDateTime == bLastTypeDateTime)
+ {
+ nElements++;
+ }
+ // type switching
+ else
+ {
+ deleteNumberOrDateTime(nStart, nStart + nElements - 1, bLastTypeDateTime);
+ nStart += nElements;
+ nElements = 1;
+ }
+
+ bLastTypeDateTime = bIsDateTime;
+ }
+
+ // delete last cells
+ deleteNumberOrDateTime(nStart, nStart + nElements - 1, bLastTypeDateTime);
+ }
+
+ void deleteNumberOrDateTime(SCROW nRow1, SCROW nRow2, bool dateTime)
+ {
+ if (!dateTime && !mbNumeric) // numeric flag must be selected
+ return;
+ if (dateTime && !mbDateTime) // datetime flag must be selected
+ return;
+ maDeleteRanges.set(nRow1, nRow2, true);
+ }
+
+ bool isDateTime(size_t position)
+ {
+ SvNumFormatType nType = mrDoc.GetFormatTable()->GetType(
+ mrCol.GetAttr(position, ATTR_VALUE_FORMAT).GetValue());
+
+ return (nType == SvNumFormatType::DATE) || (nType == SvNumFormatType::TIME) ||
+ (nType == SvNumFormatType::DATETIME);
+ }
+
+ /**
+ * Query the formula ranges that may have stopped listening, accounting for
+ * the formula groups.
+ */
+ std::vector<std::pair<SCROW, SCROW>> getFormulaRanges()
+ {
+ std::vector<std::pair<SCROW, SCROW>> aRet;
+
+ for (const ScFormulaCell* pFC : maFormulaCells)
+ {
+ SCROW nTopRow = pFC->aPos.Row();
+ SCROW nBottomRow = pFC->aPos.Row();
+
+ auto xGroup = pFC->GetCellGroup();
+ if (xGroup)
+ {
+ pFC = xGroup->mpTopCell;
+ nTopRow = pFC->aPos.Row();
+ nBottomRow = nTopRow + xGroup->mnLength - 1;
+ }
+
+ aRet.emplace_back(nTopRow, nBottomRow);
+ }
+
+ return aRet;
+ }
+
+ void endFormulas()
+ {
+ mrDoc.EndListeningFormulaCells(maFormulaCells);
+ }
+
+ sc::SingleColumnSpanSet& getSpans()
+ {
+ return maDeleteRanges;
+ }
+};
+
+class EmptyCells
+{
+ ScColumn& mrColumn;
+ sc::ColumnBlockPosition& mrPos;
+
+ static void splitFormulaGrouping(const sc::CellStoreType::position_type& rPos)
+ {
+ if (rPos.first->type == sc::element_type_formula)
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*rPos.first->data, rPos.second);
+ sc::SharedFormulaUtil::unshareFormulaCell(rPos, rCell);
+ }
+ }
+
+public:
+ EmptyCells( sc::ColumnBlockPosition& rPos, ScColumn& rColumn ) :
+ mrColumn(rColumn), mrPos(rPos) {}
+
+ void operator() (const sc::RowSpan& rSpan)
+ {
+ sc::CellStoreType& rCells = mrColumn.GetCellStore();
+
+ // First, split formula grouping at the top and bottom boundaries
+ // before emptying the cells.
+ sc::CellStoreType::position_type aPos = rCells.position(mrPos.miCellPos, rSpan.mnRow1);
+ splitFormulaGrouping(aPos);
+ aPos = rCells.position(aPos.first, rSpan.mnRow2);
+ splitFormulaGrouping(aPos);
+
+ mrPos.miCellPos = rCells.set_empty(mrPos.miCellPos, rSpan.mnRow1, rSpan.mnRow2);
+ mrPos.miCellTextAttrPos = mrColumn.GetCellAttrStore().set_empty(mrPos.miCellTextAttrPos, rSpan.mnRow1, rSpan.mnRow2);
+ }
+};
+
+}
+
+ScColumn::DeleteCellsResult::DeleteCellsResult( const ScDocument& rDoc ) :
+ aDeletedRows( rDoc.GetSheetLimits() )
+{
+}
+
+std::unique_ptr<ScColumn::DeleteCellsResult> ScColumn::DeleteCells(
+ sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2, InsertDeleteFlags nDelFlag )
+{
+ std::unique_ptr<DeleteCellsResult> xResult = std::make_unique<DeleteCellsResult>(GetDoc());
+
+ // Determine which cells to delete based on the deletion flags.
+ DeleteAreaHandler aFunc(GetDoc(), nDelFlag, *this);
+ sc::CellStoreType::iterator itPos = maCells.position(rBlockPos.miCellPos, nRow1).first;
+ sc::ProcessBlock(itPos, maCells, aFunc, nRow1, nRow2);
+ xResult->aFormulaRanges = aFunc.getFormulaRanges();
+ aFunc.endFormulas(); // Have the formula cells stop listening.
+
+ // Get the deletion spans.
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aFunc.getSpans().getSpans(aSpans);
+
+ // Delete the cells for real.
+ // tdf#139820: Deleting in reverse order is more efficient.
+ std::for_each(aSpans.rbegin(), aSpans.rend(), EmptyCells(rBlockPos, *this));
+ CellStorageModified();
+
+ aFunc.getSpans().swap(xResult->aDeletedRows);
+
+ return xResult;
+}
+
+void ScColumn::DeleteArea(
+ SCROW nStartRow, SCROW nEndRow, InsertDeleteFlags nDelFlag, bool bBroadcast,
+ sc::ColumnSpanSet* pBroadcastSpans )
+{
+ InsertDeleteFlags nContMask = InsertDeleteFlags::CONTENTS;
+ // InsertDeleteFlags::NOCAPTIONS needs to be passed too, if InsertDeleteFlags::NOTE is set
+ if( nDelFlag & InsertDeleteFlags::NOTE )
+ nContMask |= InsertDeleteFlags::NOCAPTIONS;
+ InsertDeleteFlags nContFlag = nDelFlag & nContMask;
+
+ sc::ColumnBlockPosition aBlockPos;
+ InitBlockPosition(aBlockPos);
+ std::unique_ptr<DeleteCellsResult> xResult;
+
+ if (!IsEmptyData() && nContFlag != InsertDeleteFlags::NONE)
+ {
+ xResult = DeleteCells(aBlockPos, nStartRow, nEndRow, nDelFlag);
+ if (pBroadcastSpans)
+ {
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ xResult->aDeletedRows.getSpans(aSpans);
+ for (const auto& rSpan : aSpans)
+ pBroadcastSpans->set(GetDoc(), nTab, nCol, rSpan.mnRow1, rSpan.mnRow2, true);
+ }
+ }
+
+ if (nDelFlag & InsertDeleteFlags::NOTE)
+ {
+ bool bForgetCaptionOwnership = ((nDelFlag & InsertDeleteFlags::FORGETCAPTIONS) != InsertDeleteFlags::NONE);
+ DeleteCellNotes(aBlockPos, nStartRow, nEndRow, bForgetCaptionOwnership);
+ }
+
+ if (nDelFlag & InsertDeleteFlags::SPARKLINES)
+ {
+ DeleteSparklineCells(aBlockPos, nStartRow, nEndRow);
+ }
+
+ if ( nDelFlag & InsertDeleteFlags::EDITATTR )
+ {
+ OSL_ENSURE( nContFlag == InsertDeleteFlags::NONE, "DeleteArea: Wrong Flags" );
+ RemoveEditAttribs(aBlockPos, nStartRow, nEndRow);
+ }
+
+ // Delete attributes just now
+ if ((nDelFlag & InsertDeleteFlags::ATTRIB) == InsertDeleteFlags::ATTRIB)
+ pAttrArray->DeleteArea( nStartRow, nEndRow );
+ else if ((nDelFlag & InsertDeleteFlags::HARDATTR) == InsertDeleteFlags::HARDATTR)
+ pAttrArray->DeleteHardAttr( nStartRow, nEndRow );
+
+ if (xResult && bBroadcast)
+ {
+ // Broadcast on only cells that were deleted; no point broadcasting on
+ // cells that were already empty before the deletion.
+ std::vector<SCROW> aRows;
+ xResult->aDeletedRows.getRows(aRows);
+ BroadcastCells(aRows, SfxHintId::ScDataChanged);
+ }
+}
+
+void ScColumn::InitBlockPosition( sc::ColumnBlockPosition& rBlockPos )
+{
+ rBlockPos.miBroadcasterPos = maBroadcasters.begin();
+ rBlockPos.miCellNotePos = maCellNotes.begin();
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.begin();
+ rBlockPos.miCellPos = maCells.begin();
+ rBlockPos.miSparklinePos = maSparklines.begin();
+}
+
+void ScColumn::InitBlockPosition( sc::ColumnBlockConstPosition& rBlockPos ) const
+{
+ rBlockPos.miCellNotePos = maCellNotes.begin();
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.begin();
+ rBlockPos.miCellPos = maCells.begin();
+}
+
+namespace {
+
+class CopyAttrArrayByRange
+{
+ ScAttrArray& mrDestAttrArray;
+ ScAttrArray& mrSrcAttrArray;
+ tools::Long mnRowOffset;
+public:
+ CopyAttrArrayByRange(ScAttrArray& rDestAttrArray, ScAttrArray& rSrcAttrArray, tools::Long nRowOffset) :
+ mrDestAttrArray(rDestAttrArray), mrSrcAttrArray(rSrcAttrArray), mnRowOffset(nRowOffset) {}
+
+ void operator() (const sc::RowSpan& rSpan)
+ {
+ mrDestAttrArray.CopyAreaSafe(
+ rSpan.mnRow1+mnRowOffset, rSpan.mnRow2+mnRowOffset, mnRowOffset, mrSrcAttrArray);
+ }
+};
+
+class CopyCellsFromClipHandler
+{
+ sc::CopyFromClipContext& mrCxt;
+ ScColumn& mrSrcCol;
+ ScColumn& mrDestCol;
+ SCTAB mnTab;
+ SCCOL mnCol;
+ SCTAB mnSrcTab;
+ SCCOL mnSrcCol;
+ tools::Long mnRowOffset;
+ sc::ColumnBlockPosition maDestBlockPos;
+ sc::ColumnBlockPosition* mpDestBlockPos; // to save it for next iteration.
+ svl::SharedStringPool* mpSharedStringPool;
+
+ void insertRefCell(SCROW nSrcRow, SCROW nDestRow)
+ {
+ ScAddress aSrcPos(mnSrcCol, nSrcRow, mnSrcTab);
+ ScAddress aDestPos(mnCol, nDestRow, mnTab);
+ ScSingleRefData aRef;
+ aRef.InitAddress(aSrcPos);
+ aRef.SetFlag3D(true);
+
+ ScTokenArray aArr(*mrCxt.getDestDoc());
+ aArr.AddSingleReference(aRef);
+
+ mrDestCol.SetFormulaCell(
+ maDestBlockPos, nDestRow, new ScFormulaCell(mrDestCol.GetDoc(), aDestPos, aArr));
+ }
+
+ void duplicateNotes(SCROW nStartRow, size_t nDataSize, bool bCloneCaption )
+ {
+ mrSrcCol.DuplicateNotes(nStartRow, nDataSize, mrDestCol, maDestBlockPos, bCloneCaption, mnRowOffset);
+ }
+
+ void duplicateSparklines(SCROW nStartRow, size_t nDataSize)
+ {
+ mrSrcCol.DuplicateSparklines(nStartRow, nDataSize, mrDestCol, maDestBlockPos, mnRowOffset);
+ }
+
+public:
+ CopyCellsFromClipHandler(sc::CopyFromClipContext& rCxt, ScColumn& rSrcCol, ScColumn& rDestCol, SCTAB nDestTab, SCCOL nDestCol, tools::Long nRowOffset, svl::SharedStringPool* pSharedStringPool) :
+ mrCxt(rCxt),
+ mrSrcCol(rSrcCol),
+ mrDestCol(rDestCol),
+ mnTab(nDestTab),
+ mnCol(nDestCol),
+ mnSrcTab(rSrcCol.GetTab()),
+ mnSrcCol(rSrcCol.GetCol()),
+ mnRowOffset(nRowOffset),
+ mpDestBlockPos(mrCxt.getBlockPosition(nDestTab, nDestCol)),
+ mpSharedStringPool(pSharedStringPool)
+ {
+ if (mpDestBlockPos)
+ {
+ {
+ // Re-initialize the broadcaster position hint, which may have
+ // become invalid by the time it gets here...
+ sc::ColumnBlockPosition aTempPos;
+ mrDestCol.InitBlockPosition(aTempPos);
+ mpDestBlockPos->miBroadcasterPos = aTempPos.miBroadcasterPos;
+ }
+ maDestBlockPos = *mpDestBlockPos;
+ }
+ else
+ mrDestCol.InitBlockPosition(maDestBlockPos);
+ }
+
+ ~CopyCellsFromClipHandler()
+ {
+ if (mpDestBlockPos)
+ // Don't forget to save this to the context!
+ *mpDestBlockPos = maDestBlockPos;
+ }
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ SCROW nSrcRow1 = node.position + nOffset;
+ bool bCopyCellNotes = mrCxt.isCloneNotes();
+ bool bCopySparklines = mrCxt.isCloneSparklines();
+
+ InsertDeleteFlags nFlags = mrCxt.getInsertFlag();
+
+ if (node.type == sc::element_type_empty)
+ {
+ if (bCopyCellNotes && !mrCxt.isSkipEmptyCells())
+ {
+ bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ duplicateNotes(nSrcRow1, nDataSize, bCloneCaption );
+ }
+ if (bCopySparklines) // If there is a sparkline is it empty?
+ {
+ duplicateSparklines(nSrcRow1, nDataSize);
+ }
+ return;
+ }
+
+ bool bNumeric = (nFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE;
+ bool bDateTime = (nFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE;
+ bool bString = (nFlags & InsertDeleteFlags::STRING) != InsertDeleteFlags::NONE;
+ bool bBoolean = (nFlags & InsertDeleteFlags::SPECIAL_BOOLEAN) != InsertDeleteFlags::NONE;
+ bool bFormula = (nFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE;
+
+ bool bAsLink = mrCxt.isAsLink();
+
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ {
+ // We need to copy numeric cells individually because of date type check.
+ 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 (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow)
+ {
+ bool bCopy = mrCxt.isDateCell(mrSrcCol, nSrcRow) ? bDateTime : bNumeric;
+ if (!bCopy)
+ continue;
+
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ mrDestCol.SetValue(maDestBlockPos, nSrcRow + mnRowOffset, *it);
+ }
+ }
+ break;
+ case sc::element_type_string:
+ {
+ if (!bString)
+ break;
+
+ 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 (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow)
+ {
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else if (mpSharedStringPool)
+ {
+ // Re-intern the string if source is a different document.
+ svl::SharedString aInterned = mpSharedStringPool->intern( (*it).getString());
+ mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, aInterned);
+ }
+ else
+ mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, *it);
+ }
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ if (!bString)
+ break;
+
+ sc::edittext_block::const_iterator it = sc::edittext_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::edittext_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow)
+ {
+
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ mrDestCol.SetEditText(maDestBlockPos, nSrcRow + mnRowOffset, **it);
+ }
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ 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 (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow)
+ {
+ ScFormulaCell& rSrcCell = **it;
+ bool bForceFormula = false;
+ if (bBoolean)
+ {
+ // See if the formula consists of =TRUE() or =FALSE().
+ const ScTokenArray* pCode = rSrcCell.GetCode();
+ if (pCode && pCode->GetLen() == 1)
+ {
+ const formula::FormulaToken* p = pCode->FirstToken();
+ if (p->GetOpCode() == ocTrue || p->GetOpCode() == ocFalse)
+ // This is a boolean formula.
+ bForceFormula = true;
+ }
+ }
+
+ ScAddress aDestPos(mnCol, nSrcRow + mnRowOffset, mnTab);
+ if (bFormula || bForceFormula)
+ {
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ {
+ mrDestCol.SetFormulaCell(
+ maDestBlockPos, nSrcRow + mnRowOffset,
+ new ScFormulaCell(rSrcCell, mrDestCol.GetDoc(), aDestPos),
+ sc::SingleCellListening,
+ rSrcCell.NeedsNumberFormat());
+ }
+ }
+ else if (bNumeric || bDateTime || bString)
+ {
+ // Always just copy the original row to the Undo Document;
+ // do not create Value/string cells from formulas
+
+ FormulaError nErr = rSrcCell.GetErrCode();
+ if (nErr != FormulaError::NONE)
+ {
+ // error codes are cloned with values
+ if (bNumeric)
+ {
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ {
+ ScFormulaCell* pErrCell = new ScFormulaCell(mrDestCol.GetDoc(), aDestPos);
+ pErrCell->SetErrCode(nErr);
+ mrDestCol.SetFormulaCell(
+ maDestBlockPos, nSrcRow + mnRowOffset, pErrCell);
+ }
+ }
+ }
+ else if (rSrcCell.IsEmptyDisplayedAsString())
+ {
+ // Empty stays empty and doesn't become 0.
+ continue;
+ }
+ else if (rSrcCell.IsValue())
+ {
+ bool bCopy = mrCxt.isDateCell(mrSrcCol, nSrcRow) ? bDateTime : bNumeric;
+ if (!bCopy)
+ continue;
+
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ mrDestCol.SetValue(maDestBlockPos, nSrcRow + mnRowOffset, rSrcCell.GetValue());
+ }
+ else if (bString)
+ {
+ svl::SharedString aStr = rSrcCell.GetString();
+ if (aStr.isEmpty())
+ // do not clone empty string
+ continue;
+
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else if (rSrcCell.IsMultilineResult())
+ {
+ // Clone as an edit text object.
+ ScFieldEditEngine& rEngine = mrDestCol.GetDoc().GetEditEngine();
+ rEngine.SetTextCurrentDefaults(aStr.getString());
+ mrDestCol.SetEditText(maDestBlockPos, nSrcRow + mnRowOffset, rEngine.CreateTextObject());
+ }
+ else if (mpSharedStringPool)
+ {
+ // Re-intern the string if source is a different document.
+ svl::SharedString aInterned = mpSharedStringPool->intern( aStr.getString());
+ mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, aInterned);
+ }
+ else
+ {
+ mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, aStr);
+ }
+ }
+ }
+ }
+ }
+ break;
+ default:
+ ;
+ }
+ if (bCopyCellNotes)
+ {
+ bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ duplicateNotes(nSrcRow1, nDataSize, bCloneCaption );
+ }
+ if (bCopySparklines)
+ {
+ duplicateSparklines(nSrcRow1, nDataSize);
+ }
+ }
+};
+
+class CopyTextAttrsFromClipHandler
+{
+ sc::CellTextAttrStoreType& mrAttrs;
+ size_t mnDelta;
+ sc::ColumnBlockPosition maDestBlockPos;
+ sc::ColumnBlockPosition* mpDestBlockPos; // to save it for next iteration.
+
+public:
+ CopyTextAttrsFromClipHandler( sc::CopyFromClipContext& rCxt, sc::CellTextAttrStoreType& rAttrs,
+ ScColumn& rDestCol, SCTAB nDestTab, SCCOL nDestCol, size_t nDelta ) :
+ mrAttrs(rAttrs),
+ mnDelta(nDelta),
+ mpDestBlockPos(rCxt.getBlockPosition(nDestTab, nDestCol))
+ {
+ if (mpDestBlockPos)
+ maDestBlockPos.miCellTextAttrPos = mpDestBlockPos->miCellTextAttrPos;
+ else
+ rDestCol.InitBlockPosition(maDestBlockPos);
+ }
+
+ ~CopyTextAttrsFromClipHandler()
+ {
+ if (mpDestBlockPos)
+ // Don't forget to save this to the context!
+ mpDestBlockPos->miCellTextAttrPos = maDestBlockPos.miCellTextAttrPos;
+ }
+
+ void operator() ( const sc::CellTextAttrStoreType::value_type& aNode, size_t nOffset, size_t nDataSize )
+ {
+ if (aNode.type != sc::element_type_celltextattr)
+ return;
+
+ sc::celltextattr_block::const_iterator it = sc::celltextattr_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::celltextattr_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ size_t nPos = aNode.position + nOffset + mnDelta;
+ maDestBlockPos.miCellTextAttrPos = mrAttrs.set(maDestBlockPos.miCellTextAttrPos, nPos, it, itEnd);
+ }
+};
+
+}
+
+// rColumn = source
+// nRow1, nRow2 = target position
+
+void ScColumn::CopyFromClip(
+ sc::CopyFromClipContext& rCxt, SCROW nRow1, SCROW nRow2, tools::Long nDy, ScColumn& rColumn )
+{
+ sc::ColumnBlockPosition* pBlockPos = rCxt.getBlockPosition(nTab, nCol);
+ if (!pBlockPos)
+ return;
+
+ if ((rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB) != InsertDeleteFlags::NONE)
+ {
+ if (rCxt.isSkipEmptyCells())
+ {
+ // copy only attributes for non-empty cells between nRow1-nDy and nRow2-nDy.
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(rColumn, nRow1-nDy, nRow2-nDy);
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aSpanSet.getSpans(aSpans);
+ std::for_each(
+ aSpans.begin(), aSpans.end(), CopyAttrArrayByRange(*rColumn.pAttrArray, *pAttrArray, nDy));
+ }
+ else
+ rColumn.pAttrArray->CopyAreaSafe( nRow1, nRow2, nDy, *pAttrArray );
+ }
+ if ((rCxt.getInsertFlag() & InsertDeleteFlags::CONTENTS) == InsertDeleteFlags::NONE)
+ return;
+
+ ScDocument& rDocument = GetDoc();
+ if (rCxt.isAsLink() && rCxt.getInsertFlag() == InsertDeleteFlags::ALL)
+ {
+ // We also reference empty cells for "ALL"
+ // InsertDeleteFlags::ALL must always contain more flags when compared to "Insert contents" as
+ // contents can be selected one by one!
+
+ ScAddress aDestPos( nCol, 0, nTab ); // Adapt Row
+
+ // Create reference (Source Position)
+ ScSingleRefData aRef;
+ aRef.InitFlags(); // -> All absolute
+ aRef.SetAbsCol(rColumn.nCol);
+ aRef.SetAbsTab(rColumn.nTab);
+ aRef.SetFlag3D(true);
+
+ for (SCROW nDestRow = nRow1; nDestRow <= nRow2; nDestRow++)
+ {
+ aRef.SetAbsRow(nDestRow - nDy); // Source row
+ aDestPos.SetRow( nDestRow );
+
+ ScTokenArray aArr(GetDoc());
+ aArr.AddSingleReference( aRef );
+ SetFormulaCell(*pBlockPos, nDestRow, new ScFormulaCell(rDocument, aDestPos, aArr));
+ }
+
+ // Don't forget to copy the cell text attributes.
+ CopyTextAttrsFromClipHandler aFunc(rCxt, maCellTextAttrs, *this, nTab, nCol, nDy);
+ sc::ParseBlock(rColumn.maCellTextAttrs.begin(), rColumn.maCellTextAttrs, aFunc, nRow1-nDy, nRow2-nDy);
+
+ return;
+ }
+
+ // Compare the ScDocumentPool* to determine if we are copying within the
+ // same document. If not, re-intern shared strings.
+ svl::SharedStringPool* pSharedStringPool = (rColumn.GetDoc().GetPool() != rDocument.GetPool()) ?
+ &rDocument.GetSharedStringPool() : nullptr;
+
+ // nRow1 to nRow2 is for destination (this) column. Subtract nDy to get the source range.
+ // Copy all cells in the source column (rColumn) from nRow1-nDy to nRow2-nDy to this column.
+ {
+ CopyCellsFromClipHandler aFunc(rCxt, rColumn, *this, nTab, nCol, nDy, pSharedStringPool);
+ sc::ParseBlock(rColumn.maCells.begin(), rColumn.maCells, aFunc, nRow1-nDy, nRow2-nDy);
+ }
+
+ {
+ // Don't forget to copy the cell text attributes.
+ CopyTextAttrsFromClipHandler aFunc(rCxt, maCellTextAttrs, *this, nTab, nCol, nDy);
+ sc::ParseBlock(rColumn.maCellTextAttrs.begin(), rColumn.maCellTextAttrs, aFunc, nRow1-nDy, nRow2-nDy);
+ }
+}
+
+void ScColumn::MixMarked(
+ sc::MixDocContext& rCxt, const ScMarkData& rMark, ScPasteFunc nFunction,
+ bool bSkipEmpty, const ScColumn& rSrcCol )
+{
+ SCROW nRow1, nRow2;
+
+ if (rMark.IsMultiMarked())
+ {
+ ScMultiSelIter aIter( rMark.GetMultiSelData(), nCol );
+ while (aIter.Next( nRow1, nRow2 ))
+ MixData(rCxt, nRow1, nRow2, nFunction, bSkipEmpty, rSrcCol);
+ }
+}
+
+namespace {
+
+// Result in rVal1
+bool lcl_DoFunction( double& rVal1, double nVal2, ScPasteFunc nFunction )
+{
+ bool bOk = false;
+ switch (nFunction)
+ {
+ case ScPasteFunc::ADD:
+ bOk = SubTotal::SafePlus( rVal1, nVal2 );
+ break;
+ case ScPasteFunc::SUB:
+ nVal2 = -nVal2; // FIXME: Can we do this always without error?
+ bOk = SubTotal::SafePlus( rVal1, nVal2 );
+ break;
+ case ScPasteFunc::MUL:
+ bOk = SubTotal::SafeMult( rVal1, nVal2 );
+ break;
+ case ScPasteFunc::DIV:
+ bOk = SubTotal::SafeDiv( rVal1, nVal2 );
+ break;
+ default: break;
+ }
+ return bOk;
+}
+
+void lcl_AddCode( ScTokenArray& rArr, const ScFormulaCell* pCell )
+{
+ rArr.AddOpCode(ocOpen);
+
+ const ScTokenArray* pCode = pCell->GetCode();
+ if (pCode)
+ {
+ FormulaTokenArrayPlainIterator aIter(*pCode);
+ const formula::FormulaToken* pToken = aIter.First();
+ while (pToken)
+ {
+ rArr.AddToken( *pToken );
+ pToken = aIter.Next();
+ }
+ }
+
+ rArr.AddOpCode(ocClose);
+}
+
+class MixDataHandler
+{
+ ScColumn& mrDestColumn;
+ sc::ColumnBlockPosition& mrBlockPos;
+
+ sc::CellStoreType maNewCells;
+ sc::CellStoreType::iterator miNewCellsPos;
+
+ size_t mnRowOffset;
+ ScPasteFunc mnFunction;
+
+ bool mbSkipEmpty;
+
+ void doFunction( size_t nDestRow, double fVal1, double fVal2 )
+ {
+ bool bOk = lcl_DoFunction(fVal1, fVal2, mnFunction);
+
+ if (bOk)
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nDestRow-mnRowOffset, fVal1);
+ else
+ {
+ ScAddress aPos(mrDestColumn.GetCol(), nDestRow, mrDestColumn.GetTab());
+
+ ScFormulaCell* pFC = new ScFormulaCell(mrDestColumn.GetDoc(), aPos);
+ pFC->SetErrCode(FormulaError::NoValue);
+
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nDestRow-mnRowOffset, pFC);
+ }
+ }
+
+public:
+ MixDataHandler(
+ sc::ColumnBlockPosition& rBlockPos,
+ ScColumn& rDestColumn,
+ SCROW nRow1, SCROW nRow2,
+ ScPasteFunc nFunction, bool bSkipEmpty) :
+ mrDestColumn(rDestColumn),
+ mrBlockPos(rBlockPos),
+ maNewCells(nRow2 - nRow1 + 1),
+ miNewCellsPos(maNewCells.begin()),
+ mnRowOffset(nRow1),
+ mnFunction(nFunction),
+ mbSkipEmpty(bSkipEmpty)
+ {
+ }
+
+ void operator() (size_t nRow, double f)
+ {
+ sc::CellStoreType::position_type aPos = mrDestColumn.GetCellStore().position(mrBlockPos.miCellPos, nRow);
+ mrBlockPos.miCellPos = aPos.first;
+ switch (aPos.first->type)
+ {
+ case sc::element_type_empty:
+ case sc::element_type_numeric:
+ {
+ double fSrcVal = 0.0;
+ if (aPos.first->type == sc::element_type_numeric)
+ fSrcVal = sc::numeric_block::at(*aPos.first->data, aPos.second);
+
+ // Both src and dest are of numeric type.
+ doFunction(nRow, f, fSrcVal);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ // Combination of value and at least one formula -> Create formula
+ ScTokenArray aArr(mrDestColumn.GetDoc());
+
+ // First row
+ aArr.AddDouble(f);
+
+ // Operator
+ OpCode eOp = ocAdd;
+ switch (mnFunction)
+ {
+ case ScPasteFunc::ADD: eOp = ocAdd; break;
+ case ScPasteFunc::SUB: eOp = ocSub; break;
+ case ScPasteFunc::MUL: eOp = ocMul; break;
+ case ScPasteFunc::DIV: eOp = ocDiv; break;
+ default: break;
+ }
+ aArr.AddOpCode(eOp); // Function
+
+ // Second row
+ ScFormulaCell* pDest = sc::formula_block::at(*aPos.first->data, aPos.second);
+ lcl_AddCode(aArr, pDest);
+
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nRow-mnRowOffset,
+ new ScFormulaCell(
+ mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab()), aArr));
+ }
+ break;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ {
+ // Destination cell is not a number. Just take the source cell.
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nRow-mnRowOffset, f);
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rStr)
+ {
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nRow-mnRowOffset, rStr);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* p)
+ {
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nRow-mnRowOffset, p->Clone().release());
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ sc::CellStoreType::position_type aPos = mrDestColumn.GetCellStore().position(mrBlockPos.miCellPos, nRow);
+ mrBlockPos.miCellPos = aPos.first;
+ switch (aPos.first->type)
+ {
+ case sc::element_type_numeric:
+ {
+ // Source is formula, and dest is value.
+ ScTokenArray aArr(mrDestColumn.GetDoc());
+
+ // First row
+ lcl_AddCode(aArr, p);
+
+ // Operator
+ OpCode eOp = ocAdd;
+ switch (mnFunction)
+ {
+ case ScPasteFunc::ADD: eOp = ocAdd; break;
+ case ScPasteFunc::SUB: eOp = ocSub; break;
+ case ScPasteFunc::MUL: eOp = ocMul; break;
+ case ScPasteFunc::DIV: eOp = ocDiv; break;
+ default: break;
+ }
+ aArr.AddOpCode(eOp); // Function
+
+ // Second row
+ aArr.AddDouble(sc::numeric_block::at(*aPos.first->data, aPos.second));
+
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nRow-mnRowOffset,
+ new ScFormulaCell(
+ mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab()), aArr));
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ // Both are formulas.
+ ScTokenArray aArr(mrDestColumn.GetDoc());
+
+ // First row
+ lcl_AddCode(aArr, p);
+
+ // Operator
+ OpCode eOp = ocAdd;
+ switch (mnFunction)
+ {
+ case ScPasteFunc::ADD: eOp = ocAdd; break;
+ case ScPasteFunc::SUB: eOp = ocSub; break;
+ case ScPasteFunc::MUL: eOp = ocMul; break;
+ case ScPasteFunc::DIV: eOp = ocDiv; break;
+ default: break;
+ }
+ aArr.AddOpCode(eOp); // Function
+
+ // Second row
+ ScFormulaCell* pDest = sc::formula_block::at(*aPos.first->data, aPos.second);
+ lcl_AddCode(aArr, pDest);
+
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nRow-mnRowOffset,
+ new ScFormulaCell(
+ mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab()), aArr));
+ }
+ break;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ case sc::element_type_empty:
+ {
+ // Destination cell is not a number. Just take the source cell.
+ ScAddress aDestPos(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab());
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nRow-mnRowOffset, new ScFormulaCell(*p, mrDestColumn.GetDoc(), aDestPos));
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ /**
+ * Empty cell series in the source (clip) document.
+ */
+ void operator() (mdds::mtv::element_t, size_t nTopRow, size_t nDataSize)
+ {
+ if (mbSkipEmpty)
+ return;
+
+ // Source cells are empty. Treat them as if they have a value of 0.0.
+ for (size_t i = 0; i < nDataSize; ++i)
+ {
+ size_t nDestRow = nTopRow + i;
+ sc::CellStoreType::position_type aPos = mrDestColumn.GetCellStore().position(mrBlockPos.miCellPos, nDestRow);
+ mrBlockPos.miCellPos = aPos.first;
+ switch (aPos.first->type)
+ {
+ case sc::element_type_numeric:
+ {
+ double fVal2 = sc::numeric_block::at(*aPos.first->data, aPos.second);
+ doFunction(nDestRow, 0.0, fVal2);
+ }
+ break;
+ case sc::element_type_string:
+ {
+ const svl::SharedString& aVal = sc::string_block::at(*aPos.first->data, aPos.second);
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nDestRow-mnRowOffset, aVal);
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ EditTextObject* pObj = sc::edittext_block::at(*aPos.first->data, aPos.second);
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nDestRow-mnRowOffset, pObj->Clone().release());
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ ScTokenArray aArr(mrDestColumn.GetDoc());
+
+ // First row
+ ScFormulaCell* pSrc = sc::formula_block::at(*aPos.first->data, aPos.second);
+ lcl_AddCode( aArr, pSrc);
+
+ // Operator
+ OpCode eOp = ocAdd;
+ switch (mnFunction)
+ {
+ case ScPasteFunc::ADD: eOp = ocAdd; break;
+ case ScPasteFunc::SUB: eOp = ocSub; break;
+ case ScPasteFunc::MUL: eOp = ocMul; break;
+ case ScPasteFunc::DIV: eOp = ocDiv; break;
+ default: break;
+ }
+
+ aArr.AddOpCode(eOp); // Function
+ aArr.AddDouble(0.0);
+
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nDestRow-mnRowOffset,
+ new ScFormulaCell(
+ mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nDestRow, mrDestColumn.GetTab()), aArr));
+ }
+ break;
+ default:
+ ;
+ }
+ }
+ }
+
+ /**
+ * Set the new cells to the destination (this) column.
+ */
+ void commit()
+ {
+ sc::CellStoreType& rDestCells = mrDestColumn.GetCellStore();
+
+ // Stop all formula cells in the destination range first.
+ sc::CellStoreType::position_type aPos = rDestCells.position(mrBlockPos.miCellPos, mnRowOffset);
+ mrDestColumn.DetachFormulaCells(aPos, maNewCells.size(), nullptr);
+
+ // Move the new cells to the destination range.
+ sc::CellStoreType::iterator& itDestPos = mrBlockPos.miCellPos;
+ sc::CellTextAttrStoreType::iterator& itDestAttrPos = mrBlockPos.miCellTextAttrPos;
+
+ for (const auto& rNewCell : maNewCells)
+ {
+ bool bHasContent = true;
+ size_t nDestRow = mnRowOffset + rNewCell.position;
+
+ switch (rNewCell.type)
+ {
+ case sc::element_type_numeric:
+ {
+ sc::numeric_block::iterator itData = sc::numeric_block::begin(*rNewCell.data);
+ sc::numeric_block::iterator itDataEnd = sc::numeric_block::end(*rNewCell.data);
+ itDestPos = mrDestColumn.GetCellStore().set(itDestPos, nDestRow, itData, itDataEnd);
+ }
+ break;
+ case sc::element_type_string:
+ {
+ sc::string_block::iterator itData = sc::string_block::begin(*rNewCell.data);
+ sc::string_block::iterator itDataEnd = sc::string_block::end(*rNewCell.data);
+ itDestPos = rDestCells.set(itDestPos, nDestRow, itData, itDataEnd);
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ sc::edittext_block::iterator itData = sc::edittext_block::begin(*rNewCell.data);
+ sc::edittext_block::iterator itDataEnd = sc::edittext_block::end(*rNewCell.data);
+ itDestPos = rDestCells.set(itDestPos, nDestRow, itData, itDataEnd);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::iterator itData = sc::formula_block::begin(*rNewCell.data);
+ sc::formula_block::iterator itDataEnd = sc::formula_block::end(*rNewCell.data);
+
+ // Group new formula cells before inserting them.
+ sc::SharedFormulaUtil::groupFormulaCells(itData, itDataEnd);
+
+ // Insert the formula cells to the column.
+ itDestPos = rDestCells.set(itDestPos, nDestRow, itData, itDataEnd);
+
+ // Merge with the previous formula group (if any).
+ aPos = rDestCells.position(itDestPos, nDestRow);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+
+ // Merge with the next formula group (if any).
+ size_t nNextRow = nDestRow + rNewCell.size;
+ if (mrDestColumn.GetDoc().ValidRow(nNextRow))
+ {
+ aPos = rDestCells.position(aPos.first, nNextRow);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+
+ // Start listening on cells to get them updated by changes of referenced cells
+ std::vector<SCROW> aNewSharedRows;
+ aPos = rDestCells.position(itDestPos, nDestRow);
+ size_t nFormulaCells = std::distance(itData, itDataEnd);
+ mrDestColumn.AttachNewFormulaCells(aPos, nFormulaCells, aNewSharedRows);
+ }
+ break;
+ case sc::element_type_empty:
+ {
+ itDestPos = rDestCells.set_empty(itDestPos, nDestRow, nDestRow+rNewCell.size-1);
+ bHasContent = false;
+ }
+ break;
+ default:
+ ;
+ }
+
+ sc::CellTextAttrStoreType& rDestAttrs = mrDestColumn.GetCellAttrStore();
+ if (bHasContent)
+ {
+ std::vector<sc::CellTextAttr> aAttrs(rNewCell.size, sc::CellTextAttr());
+ itDestAttrPos = rDestAttrs.set(itDestAttrPos, nDestRow, aAttrs.begin(), aAttrs.end());
+ }
+ else
+ itDestAttrPos = rDestAttrs.set_empty(itDestAttrPos, nDestRow, nDestRow+rNewCell.size-1);
+ }
+
+ maNewCells.release();
+ }
+};
+
+}
+
+void ScColumn::MixData(
+ sc::MixDocContext& rCxt, SCROW nRow1, SCROW nRow2, ScPasteFunc nFunction,
+ bool bSkipEmpty, const ScColumn& rSrcCol )
+{
+ // destination (this column) block position.
+
+ sc::ColumnBlockPosition* p = rCxt.getBlockPosition(nTab, nCol);
+ if (!p)
+ return;
+
+ MixDataHandler aFunc(*p, *this, nRow1, nRow2, nFunction, bSkipEmpty);
+ sc::ParseAll(rSrcCol.maCells.begin(), rSrcCol.maCells, nRow1, nRow2, aFunc, aFunc);
+
+ aFunc.commit();
+ CellStorageModified();
+}
+
+std::unique_ptr<ScAttrIterator> ScColumnData::CreateAttrIterator( SCROW nStartRow, SCROW nEndRow ) const
+{
+ return std::make_unique<ScAttrIterator>( pAttrArray.get(), nStartRow, nEndRow, GetDoc().GetDefPattern() );
+}
+
+namespace {
+
+class StartListenersHandler
+{
+ sc::StartListeningContext* mpCxt;
+ bool mbAllListeners;
+
+public:
+ StartListenersHandler( sc::StartListeningContext& rCxt, bool bAllListeners ) :
+ mpCxt(&rCxt), mbAllListeners(bAllListeners) {}
+
+ void operator() ( sc::CellStoreType::value_type& aBlk )
+ {
+ if (aBlk.type != sc::element_type_formula)
+ return;
+
+ ScFormulaCell** pp = &sc::formula_block::at(*aBlk.data, 0);
+ ScFormulaCell** ppEnd = pp + aBlk.size;
+
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell& rFC = **pp;
+ if (!mbAllListeners && !rFC.NeedsListening())
+ continue;
+
+ if (rFC.IsSharedTop())
+ {
+ sc::SharedFormulaUtil::startListeningAsGroup(*mpCxt, pp);
+ pp += rFC.GetSharedLength() - 1; // Move to the last cell in the group.
+ }
+ else
+ rFC.StartListeningTo(*mpCxt);
+ }
+ }
+};
+
+}
+
+void ScColumn::StartListeners( sc::StartListeningContext& rCxt, bool bAll )
+{
+ std::for_each(maCells.begin(), maCells.end(), StartListenersHandler(rCxt, bAll));
+}
+
+namespace {
+
+void applyTextNumFormat( ScColumn& rCol, SCROW nRow, SvNumberFormatter* pFormatter )
+{
+ sal_uInt32 nFormat = pFormatter->GetStandardFormat(SvNumFormatType::TEXT);
+ ScPatternAttr aNewAttrs(rCol.GetDoc().GetPool());
+ SfxItemSet& rSet = aNewAttrs.GetItemSet();
+ rSet.Put(SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
+ rCol.ApplyPattern(nRow, aNewAttrs);
+}
+
+}
+
+bool ScColumn::ParseString(
+ ScCellValue& rCell, SCROW nRow, SCTAB nTabP, const OUString& rString,
+ formula::FormulaGrammar::AddressConvention eConv,
+ const ScSetStringParam* pParam )
+{
+ if (rString.isEmpty())
+ return false;
+
+ bool bNumFmtSet = false;
+
+ ScSetStringParam aParam;
+
+ if (pParam)
+ aParam = *pParam;
+
+ sal_uInt32 nIndex = 0;
+ sal_uInt32 nOldIndex = 0;
+ SvNumFormatType eNumFormatType = SvNumFormatType::ALL;
+ if (!aParam.mpNumFormatter)
+ aParam.mpNumFormatter = GetDoc().GetFormatTable();
+
+ sal_Unicode cFirstChar = 0; // Text
+ nIndex = nOldIndex = GetNumberFormat( GetDoc().GetNonThreadedContext(), nRow );
+ if ( rString.getLength() > 1 )
+ {
+ eNumFormatType = aParam.mpNumFormatter->GetType(nIndex);
+ if ( eNumFormatType != SvNumFormatType::TEXT )
+ cFirstChar = rString[0];
+ }
+
+ svl::SharedStringPool& rPool = GetDoc().GetSharedStringPool();
+
+ if ( cFirstChar == '=' )
+ {
+ if ( rString.getLength() == 1 ) // = Text
+ {
+ rCell.set(rPool.intern(rString));
+ }
+ else if (aParam.meSetTextNumFormat == ScSetStringParam::Always)
+ {
+ // Set the cell format type to Text.
+ applyTextNumFormat(*this, nRow, aParam.mpNumFormatter);
+ rCell.set(rPool.intern(rString));
+ }
+ else // = Formula
+ {
+ ScFormulaCell* pFormulaCell = new ScFormulaCell(
+ GetDoc(), ScAddress(nCol, nRow, nTabP), rString,
+ formula::FormulaGrammar::mergeToGrammar(formula::FormulaGrammar::GRAM_DEFAULT, eConv),
+ ScMatrixMode::NONE);
+ if (aParam.mbCheckLinkFormula)
+ GetDoc().CheckLinkFormulaNeedingCheck( *pFormulaCell->GetCode());
+ rCell.set( pFormulaCell);
+ }
+ }
+ else if ( cFirstChar == '\'') // 'Text
+ {
+ bool bNumeric = false;
+ if (aParam.mbHandleApostrophe)
+ {
+ // Cell format is not 'Text', and the first char is an apostrophe.
+ // Check if the input is considered a number with all leading
+ // apostrophes removed. All because ''1 should produce '1 not ''1,
+ // thus '''1 be ''1 and so on.
+ // NOTE: this corresponds with sc/source/ui/view/tabvwsha.cxx
+ // ScTabViewShell::UpdateInputHandler() prepending an apostrophe if
+ // necessary.
+ sal_Int32 i = 1;
+ while (i < rString.getLength() && rString[i] == '\'')
+ ++i;
+ if (i < rString.getLength())
+ {
+ OUString aTest = rString.copy(i);
+ double fTest;
+ bNumeric = aParam.mpNumFormatter->IsNumberFormat(aTest, nIndex, fTest);
+ if (bNumeric)
+ // This is a number. Strip out the first apostrophe.
+ rCell.set(rPool.intern(rString.copy(1)));
+ }
+ }
+ if (!bNumeric)
+ // This is normal text. Take it as-is.
+ rCell.set(rPool.intern(rString));
+ }
+ else
+ {
+ double nVal;
+
+ do
+ {
+ if (aParam.mbDetectNumberFormat)
+ {
+ // Editing a date prefers the format's locale's edit date
+ // format's date acceptance patterns and YMD order.
+ /* TODO: this could be determined already far above when
+ * starting to edit a date "cell" and passed down. A problem
+ * could also be if a new date was typed over or written by a
+ * macro assuming the current locale if that conflicts somehow.
+ * You can't have everything. See tdf#116579 and tdf#125109. */
+ if (eNumFormatType == SvNumFormatType::ALL)
+ eNumFormatType = aParam.mpNumFormatter->GetType(nIndex);
+ bool bForceFormatDate = (eNumFormatType == SvNumFormatType::DATE
+ || eNumFormatType == SvNumFormatType::DATETIME);
+ const SvNumberformat* pOldFormat = nullptr;
+ NfEvalDateFormat eEvalDateFormat = NF_EVALDATEFORMAT_INTL_FORMAT;
+ if (bForceFormatDate)
+ {
+ ScRefCellValue aCell = GetCellValue(nRow);
+ if (aCell.meType == CELLTYPE_VALUE)
+ {
+ // Only for an actual date (serial number), not an
+ // arbitrary string or formula or empty cell.
+ // Also ensure the edit date format's pattern is used,
+ // not the display format's.
+ pOldFormat = aParam.mpNumFormatter->GetEntry( nOldIndex);
+ if (!pOldFormat)
+ bForceFormatDate = false;
+ else
+ {
+ nIndex = aParam.mpNumFormatter->GetEditFormat(
+ aCell.getValue(), nOldIndex, eNumFormatType, pOldFormat);
+ eEvalDateFormat = aParam.mpNumFormatter->GetEvalDateFormat();
+ aParam.mpNumFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL);
+ }
+ }
+ else
+ {
+ bForceFormatDate = false;
+ }
+ }
+
+ const bool bIsNumberFormat = aParam.mpNumFormatter->IsNumberFormat(rString, nIndex, nVal);
+
+ if (bForceFormatDate)
+ aParam.mpNumFormatter->SetEvalDateFormat( eEvalDateFormat);
+
+ if (!bIsNumberFormat)
+ break;
+
+ // If we have bForceFormatDate, the pOldFormat was/is of
+ // nOldIndex date(+time) type already, if detected type is
+ // compatible keep the original format.
+ if (bForceFormatDate && SvNumberFormatter::IsCompatible(
+ eNumFormatType, aParam.mpNumFormatter->GetType( nIndex)))
+ {
+ nIndex = nOldIndex;
+ }
+ else
+ {
+ // convert back to the original language if a built-in format was detected
+ if (!pOldFormat)
+ pOldFormat = aParam.mpNumFormatter->GetEntry( nOldIndex );
+ if (pOldFormat)
+ nIndex = aParam.mpNumFormatter->GetFormatForLanguageIfBuiltIn(
+ nIndex, pOldFormat->GetLanguage());
+ }
+
+ rCell.set(nVal);
+ if ( nIndex != nOldIndex)
+ {
+ // #i22345# New behavior: Apply the detected number format only if
+ // the old one was the default number, date, time or boolean format.
+ // Exception: If the new format is boolean, always apply it.
+
+ bool bOverwrite = false;
+ if ( pOldFormat )
+ {
+ SvNumFormatType nOldType = pOldFormat->GetMaskedType();
+ if ( nOldType == SvNumFormatType::NUMBER || nOldType == SvNumFormatType::DATE ||
+ nOldType == SvNumFormatType::TIME || nOldType == SvNumFormatType::LOGICAL )
+ {
+ if ( nOldIndex == aParam.mpNumFormatter->GetStandardFormat(
+ nOldType, pOldFormat->GetLanguage() ) )
+ {
+ bOverwrite = true; // default of these types can be overwritten
+ }
+ }
+ }
+ if ( !bOverwrite && aParam.mpNumFormatter->GetType( nIndex ) == SvNumFormatType::LOGICAL )
+ {
+ bOverwrite = true; // overwrite anything if boolean was detected
+ }
+
+ if ( bOverwrite )
+ {
+ ApplyAttr( nRow, SfxUInt32Item( ATTR_VALUE_FORMAT,
+ nIndex) );
+ bNumFmtSet = true;
+ }
+ }
+ }
+ else if (aParam.meSetTextNumFormat == ScSetStringParam::Never ||
+ aParam.meSetTextNumFormat == ScSetStringParam::SpecialNumberOnly)
+ {
+ // Only check if the string is a regular number.
+ const LocaleDataWrapper* pLocale = aParam.mpNumFormatter->GetLocaleData();
+ if (!pLocale)
+ break;
+
+ const LocaleDataItem2& aLocaleItem = pLocale->getLocaleItem();
+ const OUString& rDecSep = aLocaleItem.decimalSeparator;
+ const OUString& rGroupSep = aLocaleItem.thousandSeparator;
+ const OUString& rDecSepAlt = aLocaleItem.decimalSeparatorAlternative;
+ if (rDecSep.getLength() != 1 || rGroupSep.getLength() != 1 || rDecSepAlt.getLength() > 1)
+ break;
+
+ sal_Unicode dsep = rDecSep[0];
+ sal_Unicode gsep = rGroupSep[0];
+ sal_Unicode dsepa = rDecSepAlt.toChar();
+
+ if (!ScStringUtil::parseSimpleNumber(rString, dsep, gsep, dsepa, nVal))
+ break;
+
+ rCell.set(nVal);
+ }
+ }
+ while (false);
+
+ if (rCell.meType == CELLTYPE_NONE)
+ {
+ // If we reach here with ScSetStringParam::SpecialNumberOnly it
+ // means a simple number was not detected above, so test for
+ // special numbers. In any case ScSetStringParam::Always does not
+ // mean always, but only always for content that could be any
+ // numeric.
+ if ((aParam.meSetTextNumFormat == ScSetStringParam::Always ||
+ aParam.meSetTextNumFormat == ScSetStringParam::SpecialNumberOnly) &&
+ aParam.mpNumFormatter->IsNumberFormat(rString, nIndex, nVal))
+ {
+ // Set the cell format type to Text.
+ applyTextNumFormat(*this, nRow, aParam.mpNumFormatter);
+ }
+
+ rCell.set(rPool.intern(rString));
+ }
+ }
+
+ return bNumFmtSet;
+}
+
+/**
+ * Returns true if the cell format was set as well
+ */
+bool ScColumn::SetString( SCROW nRow, SCTAB nTabP, const OUString& rString,
+ formula::FormulaGrammar::AddressConvention eConv,
+ const ScSetStringParam* pParam )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return false;
+
+ ScCellValue aNewCell;
+ bool bNumFmtSet = ParseString(aNewCell, nRow, nTabP, rString, eConv, pParam);
+ if (pParam)
+ aNewCell.release(*this, nRow, pParam->meStartListening);
+ else
+ aNewCell.release(*this, nRow);
+
+ // Do not set Formats and Formulas here anymore!
+ // These are queried during output
+
+ return bNumFmtSet;
+}
+
+void ScColumn::SetEditText( SCROW nRow, std::unique_ptr<EditTextObject> pEditText )
+{
+ pEditText->NormalizeString(GetDoc().GetSharedStringPool());
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, false);
+ maCells.set(it, nRow, pEditText.release());
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetEditText( sc::ColumnBlockPosition& rBlockPos, SCROW nRow, std::unique_ptr<EditTextObject> pEditText )
+{
+ pEditText->NormalizeString(GetDoc().GetSharedStringPool());
+ std::vector<SCROW> aNewSharedRows;
+ rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, false);
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, pEditText.release());
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetEditText( sc::ColumnBlockPosition& rBlockPos, SCROW nRow, const EditTextObject& rEditText )
+{
+ if (GetDoc().GetEditPool() == rEditText.GetPool())
+ {
+ SetEditText(rBlockPos, nRow, rEditText.Clone());
+ return;
+ }
+
+ // rats, yet another "spool"
+ // Sadly there is no other way to change the Pool than to
+ // "spool" the Object through a corresponding Engine
+ EditEngine& rEngine = GetDoc().GetEditEngine();
+ rEngine.SetText(rEditText);
+ SetEditText(rBlockPos, nRow, rEngine.CreateTextObject());
+}
+
+void ScColumn::SetEditText( SCROW nRow, const EditTextObject& rEditText, const SfxItemPool* pEditPool )
+{
+ if (pEditPool && GetDoc().GetEditPool() == pEditPool)
+ {
+ SetEditText(nRow, rEditText.Clone());
+ return;
+ }
+
+ // rats, yet another "spool"
+ // Sadly there is no other way to change the Pool than to
+ // "spool" the Object through a corresponding Engine
+ EditEngine& rEngine = GetDoc().GetEditEngine();
+ rEngine.SetText(rEditText);
+ SetEditText(nRow, rEngine.CreateTextObject());
+}
+
+void ScColumn::SetFormula( SCROW nRow, const ScTokenArray& rArray, formula::FormulaGrammar::Grammar eGram )
+{
+ ScAddress aPos(nCol, nRow, nTab);
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true);
+ ScFormulaCell* pCell = new ScFormulaCell(GetDoc(), aPos, rArray, eGram);
+ sal_uInt32 nCellFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ pCell->SetNeedNumberFormat(true);
+ it = maCells.set(it, nRow, pCell);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows);
+}
+
+void ScColumn::SetFormula( SCROW nRow, const OUString& rFormula, formula::FormulaGrammar::Grammar eGram )
+{
+ ScAddress aPos(nCol, nRow, nTab);
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true);
+ ScFormulaCell* pCell = new ScFormulaCell(GetDoc(), aPos, rFormula, eGram);
+ sal_uInt32 nCellFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ pCell->SetNeedNumberFormat(true);
+ it = maCells.set(it, nRow, pCell);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows);
+}
+
+ScFormulaCell* ScColumn::SetFormulaCell(
+ SCROW nRow, ScFormulaCell* pCell, sc::StartListeningType eListenType,
+ bool bInheritNumFormatIfNeeded )
+{
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true);
+ sal_uInt32 nCellFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0 && bInheritNumFormatIfNeeded )
+ pCell->SetNeedNumberFormat(true);
+ it = maCells.set(it, nRow, pCell);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows, true, eListenType);
+
+ return pCell;
+}
+
+void ScColumn::SetFormulaCell(
+ sc::ColumnBlockPosition& rBlockPos, SCROW nRow, ScFormulaCell* pCell,
+ sc::StartListeningType eListenType,
+ bool bInheritNumFormatIfNeeded )
+{
+ std::vector<SCROW> aNewSharedRows;
+ rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, true);
+ sal_uInt32 nCellFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0 && bInheritNumFormatIfNeeded )
+ pCell->SetNeedNumberFormat(true);
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, pCell);
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(rBlockPos.miCellPos, nRow, *pCell, aNewSharedRows, true, eListenType);
+}
+
+bool ScColumn::SetFormulaCells( SCROW nRow, std::vector<ScFormulaCell*>& rCells )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return false;
+
+ SCROW nEndRow = nRow + rCells.size() - 1;
+ if (!GetDoc().ValidRow(nEndRow))
+ return false;
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+
+ // Detach all formula cells that will be overwritten.
+ std::vector<SCROW> aNewSharedRows;
+ DetachFormulaCells(aPos, rCells.size(), &aNewSharedRows);
+
+ if (!GetDoc().IsClipOrUndo())
+ {
+ for (size_t i = 0, n = rCells.size(); i < n; ++i)
+ {
+ SCROW nThisRow = nRow + i;
+ sal_uInt32 nFmt = GetNumberFormat(GetDoc().GetNonThreadedContext(), nThisRow);
+ if ((nFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ rCells[i]->SetNeedNumberFormat(true);
+ }
+ }
+
+ std::vector<sc::CellTextAttr> aDefaults(rCells.size(), sc::CellTextAttr());
+ maCellTextAttrs.set(nRow, aDefaults.begin(), aDefaults.end());
+
+ maCells.set(aPos.first, nRow, rCells.begin(), rCells.end());
+
+ CellStorageModified();
+
+ // Reget position_type as the type may have changed to formula, block and
+ // block size changed, ...
+ aPos = maCells.position(nRow);
+ AttachNewFormulaCells(aPos, rCells.size(), aNewSharedRows);
+
+ return true;
+}
+
+svl::SharedString ScColumn::GetSharedString( SCROW nRow ) const
+{
+ sc::CellStoreType::const_position_type aPos = maCells.position(nRow);
+ switch (aPos.first->type)
+ {
+ case sc::element_type_string:
+ return sc::string_block::at(*aPos.first->data, aPos.second);
+ case sc::element_type_edittext:
+ {
+ const EditTextObject* pObj = sc::edittext_block::at(*aPos.first->data, aPos.second);
+ std::vector<svl::SharedString> aSSs = pObj->GetSharedStrings();
+ if (aSSs.size() != 1)
+ // We don't handle multiline content for now.
+ return svl::SharedString();
+
+ return aSSs[0];
+ }
+ break;
+ default:
+ ;
+ }
+ return svl::SharedString();
+}
+
+namespace {
+
+class FilterEntriesHandler
+{
+ ScColumn& mrColumn;
+ ScFilterEntries& mrFilterEntries;
+
+ void processCell(const ScColumn& rColumn, SCROW nRow, ScRefCellValue& rCell)
+ {
+ SvNumberFormatter* pFormatter = mrColumn.GetDoc().GetFormatTable();
+ sal_uLong nFormat = mrColumn.GetNumberFormat(mrColumn.GetDoc().GetNonThreadedContext(), nRow);
+ OUString aStr = ScCellFormat::GetInputString(rCell, nFormat, *pFormatter, mrColumn.GetDoc(), mrColumn.HasFiltering());
+
+ // Colors
+ ScAddress aPos(rColumn.GetCol(), nRow, rColumn.GetTab());
+
+ Color backgroundColor;
+ bool bHasConditionalBackgroundColor = false;
+
+ Color textColor;
+ bool bHasConditionalTextColor = false;
+ // Check text & background color from cond. formatting
+ const ScPatternAttr* pPattern
+ = mrColumn.GetDoc().GetPattern(aPos.Col(), aPos.Row(), aPos.Tab());
+ if (pPattern)
+ {
+ if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ {
+ const SfxItemSet* pCondSet
+ = mrColumn.GetDoc().GetCondResult(aPos.Col(), aPos.Row(), aPos.Tab());
+ const SvxColorItem* pColor = &pPattern->GetItem(ATTR_FONT_COLOR, pCondSet);
+ textColor = pColor->GetValue();
+ bHasConditionalTextColor = true;
+
+ const SvxBrushItem* pBackgroundColor = &pPattern->GetItem(ATTR_BACKGROUND, pCondSet);
+ backgroundColor = pBackgroundColor->GetColor();
+ bHasConditionalBackgroundColor = true;
+ }
+ }
+
+ if (!bHasConditionalTextColor)
+ {
+ const SvxColorItem* pColor = rColumn.GetDoc().GetAttr(aPos, ATTR_FONT_COLOR);
+ textColor = pColor->GetValue();
+ }
+ mrFilterEntries.addTextColor(textColor);
+
+ // Color scale needs a different handling
+ ScConditionalFormat* pCondFormat
+ = rColumn.GetDoc().GetCondFormat(aPos.Col(), aPos.Row(), aPos.Tab());
+ if (pCondFormat)
+ {
+ for (size_t i = 0; i < pCondFormat->size(); i++)
+ {
+ auto aEntry = pCondFormat->GetEntry(i);
+ if (aEntry->GetType() == ScFormatEntry::Type::Colorscale)
+ {
+ const ScColorScaleFormat* pColFormat
+ = static_cast<const ScColorScaleFormat*>(aEntry);
+ std::optional<Color> oColor = pColFormat->GetColor(aPos);
+ if (oColor)
+ {
+ backgroundColor = *oColor;
+ bHasConditionalBackgroundColor = true;
+ }
+ }
+ }
+ }
+ if (!bHasConditionalBackgroundColor)
+ {
+ const SvxBrushItem* pBrush = rColumn.GetDoc().GetAttr(aPos, ATTR_BACKGROUND);
+ backgroundColor = pBrush->GetColor();
+ }
+ mrFilterEntries.addBackgroundColor(backgroundColor);
+
+ if (rCell.hasString())
+ {
+ mrFilterEntries.push_back(ScTypedStrData(std::move(aStr)));
+ return;
+ }
+
+ double fVal = 0.0;
+
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ fVal = rCell.mfValue;
+ break;
+
+ case CELLTYPE_FORMULA:
+ {
+ ScFormulaCell* pFC = rCell.mpFormula;
+ FormulaError nErr = pFC->GetErrCode();
+ if (nErr != FormulaError::NONE)
+ {
+ // Error cell is evaluated as string (for now).
+ OUString aErr = ScGlobal::GetErrorString(nErr);
+ if (!aErr.isEmpty())
+ {
+ mrFilterEntries.push_back(ScTypedStrData(std::move(aErr)));
+ return;
+ }
+ }
+ else
+ fVal = pFC->GetValue();
+ }
+ break;
+ default:
+ ;
+ }
+
+ SvNumFormatType nType = pFormatter->GetType(nFormat);
+ bool bDate = false;
+ if ((nType & SvNumFormatType::DATE) && !(nType & SvNumFormatType::TIME))
+ {
+ // special case for date values. Disregard the time
+ // element if the number format is of date type.
+ fVal = rtl::math::approxFloor(fVal);
+ mrFilterEntries.mbHasDates = true;
+ bDate = true;
+ // Convert string representation to ISO 8601 date to eliminate
+ // locale dependent behaviour later when filtering for dates.
+ sal_uInt32 nIndex = pFormatter->GetFormatIndex( NF_DATE_DIN_YYYYMMDD);
+ pFormatter->GetInputLineString( fVal, nIndex, aStr);
+ }
+ else if (nType == SvNumFormatType::DATETIME)
+ {
+ // special case for datetime values.
+ // Convert string representation to ISO 8601 (with blank instead of T) datetime
+ // to eliminate locale dependent behaviour later when filtering for datetimes.
+ sal_uInt32 nIndex = pFormatter->GetFormatIndex(NF_DATETIME_ISO_YYYYMMDD_HHMMSS);
+ pFormatter->GetInputLineString(fVal, nIndex, aStr);
+ }
+ // store the formatted/rounded value for filtering
+ if ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0 && !bDate)
+ mrFilterEntries.push_back(ScTypedStrData(std::move(aStr), fVal, rColumn.GetDoc().RoundValueAsShown(fVal, nFormat), ScTypedStrData::Value, bDate));
+ else
+ mrFilterEntries.push_back(ScTypedStrData(std::move(aStr), fVal, fVal, ScTypedStrData::Value, bDate));
+ }
+
+public:
+ FilterEntriesHandler(ScColumn& rColumn, ScFilterEntries& rFilterEntries) :
+ mrColumn(rColumn), mrFilterEntries(rFilterEntries) {}
+
+ void operator() (size_t nRow, double fVal)
+ {
+ ScRefCellValue aCell(fVal);
+ processCell(mrColumn, nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rStr)
+ {
+ ScRefCellValue aCell(&rStr);
+ processCell(mrColumn, nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* p)
+ {
+ ScRefCellValue aCell(p);
+ processCell(mrColumn, nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(p));
+ processCell(mrColumn, nRow, aCell);
+ }
+
+ void operator() (const int nElemType, size_t nRow, size_t /* nDataSize */)
+ {
+ if ( nElemType == sc::element_type_empty )
+ {
+ if (!mrFilterEntries.mbHasEmpties)
+ {
+ mrFilterEntries.push_back(ScTypedStrData(OUString()));
+ mrFilterEntries.mbHasEmpties = true;
+ }
+ return;
+ }
+ ScRefCellValue aCell = mrColumn.GetCellValue(nRow);
+ processCell(mrColumn, nRow, aCell);
+ }
+};
+
+}
+
+void ScColumn::GetFilterEntries(
+ sc::ColumnBlockConstPosition& rBlockPos, SCROW nStartRow, SCROW nEndRow,
+ ScFilterEntries& rFilterEntries, bool bFiltering )
+{
+ mbFiltering = bFiltering;
+ FilterEntriesHandler aFunc(*this, rFilterEntries);
+ rBlockPos.miCellPos =
+ sc::ParseAll(rBlockPos.miCellPos, maCells, nStartRow, nEndRow, aFunc, aFunc);
+}
+
+namespace {
+
+/**
+ * Iterate over only string and edit-text cells.
+ */
+class StrCellIterator
+{
+ typedef std::pair<sc::CellStoreType::const_iterator,size_t> PosType;
+ PosType maPos;
+ sc::CellStoreType::const_iterator miBeg;
+ sc::CellStoreType::const_iterator miEnd;
+ const ScDocument* mpDoc;
+public:
+ StrCellIterator(const sc::CellStoreType& rCells, SCROW nStart, const ScDocument* pDoc) :
+ miBeg(rCells.begin()), miEnd(rCells.end()), mpDoc(pDoc)
+ {
+ if (pDoc->ValidRow(nStart))
+ maPos = rCells.position(nStart);
+ else
+ // Make this iterator invalid.
+ maPos.first = miEnd;
+ }
+
+ bool valid() const { return (maPos.first != miEnd); }
+
+ bool has() const
+ {
+ return (maPos.first->type == sc::element_type_string || maPos.first->type == sc::element_type_edittext);
+ }
+
+ bool prev()
+ {
+ if (!has())
+ {
+ // Not in a string block. Move back until we hit a string block.
+ while (!has())
+ {
+ if (maPos.first == miBeg)
+ return false;
+
+ --maPos.first; // move to the preceding block.
+ maPos.second = maPos.first->size - 1; // last cell in the block.
+ }
+ return true;
+ }
+
+ // We are in a string block.
+ if (maPos.second > 0)
+ {
+ // Move back one cell in the same block.
+ --maPos.second;
+ }
+ else
+ {
+ // Move back to the preceding string block.
+ while (true)
+ {
+ if (maPos.first == miBeg)
+ return false;
+
+ // Move to the last cell of the previous block.
+ --maPos.first;
+ maPos.second = maPos.first->size - 1;
+ if (has())
+ break;
+ }
+ }
+ return true;
+ }
+
+ bool next()
+ {
+ if (!has())
+ {
+ // Not in a string block. Move forward until we hit a string block.
+ while (!has())
+ {
+ ++maPos.first;
+ if (maPos.first == miEnd)
+ return false;
+
+ maPos.second = 0; // First cell in this block.
+ }
+ return true;
+ }
+
+ // We are in a string block.
+ ++maPos.second;
+ if (maPos.second >= maPos.first->size)
+ {
+ // Move to the next string block.
+ while (true)
+ {
+ ++maPos.first;
+ if (maPos.first == miEnd)
+ return false;
+
+ maPos.second = 0;
+ if (has())
+ break;
+ }
+ }
+ return true;
+ }
+
+ OUString get() const
+ {
+ switch (maPos.first->type)
+ {
+ case sc::element_type_string:
+ return sc::string_block::at(*maPos.first->data, maPos.second).getString();
+ case sc::element_type_edittext:
+ {
+ const EditTextObject* p = sc::edittext_block::at(*maPos.first->data, maPos.second);
+ return ScEditUtil::GetString(*p, mpDoc);
+ }
+ default:
+ ;
+ }
+ return OUString();
+ }
+};
+
+}
+
+// GetDataEntries - Strings from continuous Section around nRow
+bool ScColumn::GetDataEntries(
+ SCROW nStartRow, std::set<ScTypedStrData>& rStrings) const
+{
+ // Start at the specified row position, and collect all string values
+ // going upward and downward directions in parallel. The start position
+ // cell must be skipped.
+
+ StrCellIterator aItrUp(maCells, nStartRow, &GetDoc());
+ StrCellIterator aItrDown(maCells, nStartRow+1, &GetDoc());
+
+ bool bMoveUp = aItrUp.valid();
+ if (!bMoveUp)
+ // Current cell is invalid.
+ return false;
+
+ // Skip the start position cell.
+ bMoveUp = aItrUp.prev(); // Find the previous string cell position.
+
+ bool bMoveDown = aItrDown.valid();
+ if (bMoveDown && !aItrDown.has())
+ bMoveDown = aItrDown.next(); // Find the next string cell position.
+
+ bool bFound = false;
+ while (bMoveUp)
+ {
+ // Get the current string and move up.
+ OUString aStr = aItrUp.get();
+ if (!aStr.isEmpty())
+ {
+ if (rStrings.insert(ScTypedStrData(std::move(aStr))).second)
+ bFound = true;
+ }
+
+ bMoveUp = aItrUp.prev();
+ }
+
+ while (bMoveDown)
+ {
+ // Get the current string and move down.
+ OUString aStr = aItrDown.get();
+ if (!aStr.isEmpty())
+ {
+ if (rStrings.insert(ScTypedStrData(std::move(aStr))).second)
+ bFound = true;
+ }
+
+ bMoveDown = aItrDown.next();
+ }
+
+ return bFound;
+}
+
+namespace {
+
+class FormulaToValueHandler
+{
+ struct Entry
+ {
+ SCROW mnRow;
+ ScCellValue maValue;
+
+ Entry(SCROW nRow, double f) : mnRow(nRow), maValue(f) {}
+ Entry(SCROW nRow, const svl::SharedString& rStr) : mnRow(nRow), maValue(rStr) {}
+ };
+
+ typedef std::vector<Entry> EntriesType;
+ EntriesType maEntries;
+
+public:
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ ScFormulaCell* p2 = const_cast<ScFormulaCell*>(p);
+ if (p2->IsValue())
+ maEntries.emplace_back(nRow, p2->GetValue());
+ else
+ maEntries.emplace_back(nRow, p2->GetString());
+ }
+
+ void commitCells(ScColumn& rColumn)
+ {
+ sc::ColumnBlockPosition aBlockPos;
+ rColumn.InitBlockPosition(aBlockPos);
+
+ for (const Entry& r : maEntries)
+ {
+ switch (r.maValue.meType)
+ {
+ case CELLTYPE_VALUE:
+ rColumn.SetValue(aBlockPos, r.mnRow, r.maValue.mfValue, false);
+ break;
+ case CELLTYPE_STRING:
+ rColumn.SetRawString(aBlockPos, r.mnRow, *r.maValue.mpString, false);
+ break;
+ default:
+ ;
+ }
+ }
+ }
+};
+
+}
+
+void ScColumn::RemoveProtected( SCROW nStartRow, SCROW nEndRow )
+{
+ FormulaToValueHandler aFunc;
+ sc::CellStoreType::const_iterator itPos = maCells.begin();
+
+ ScAttrIterator aAttrIter( pAttrArray.get(), nStartRow, nEndRow, GetDoc().GetDefPattern() );
+ SCROW nTop = -1;
+ SCROW nBottom = -1;
+ const ScPatternAttr* pPattern = aAttrIter.Next( nTop, nBottom );
+ while (pPattern)
+ {
+ const ScProtectionAttr* pAttr = &pPattern->GetItem(ATTR_PROTECTION);
+ if ( pAttr->GetHideCell() )
+ DeleteArea( nTop, nBottom, InsertDeleteFlags::CONTENTS );
+ else if ( pAttr->GetHideFormula() )
+ {
+ // Replace all formula cells between nTop and nBottom with raw value cells.
+ itPos = sc::ParseFormula(itPos, maCells, nTop, nBottom, aFunc);
+ }
+
+ pPattern = aAttrIter.Next( nTop, nBottom );
+ }
+
+ aFunc.commitCells(*this);
+}
+
+void ScColumn::SetError( SCROW nRow, const FormulaError nError)
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ ScFormulaCell* pCell = new ScFormulaCell(GetDoc(), ScAddress(nCol, nRow, nTab));
+ pCell->SetErrCode(nError);
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true);
+ it = maCells.set(it, nRow, pCell);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows);
+}
+
+void ScColumn::SetRawString( SCROW nRow, const OUString& rStr )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ svl::SharedString aSS = GetDoc().GetSharedStringPool().intern(rStr);
+ if (!aSS.getData())
+ return;
+
+ SetRawString(nRow, aSS);
+}
+
+void ScColumn::SetRawString( SCROW nRow, const svl::SharedString& rStr )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, false);
+ maCells.set(it, nRow, rStr);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetRawString(
+ sc::ColumnBlockPosition& rBlockPos, SCROW nRow, const svl::SharedString& rStr, bool bBroadcast )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ std::vector<SCROW> aNewSharedRows;
+ rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, false);
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, rStr);
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ if (bBroadcast)
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetValue( SCROW nRow, double fVal )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, false);
+ maCells.set(it, nRow, fVal);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetValue(
+ sc::ColumnBlockPosition& rBlockPos, SCROW nRow, double fVal, bool bBroadcast )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ std::vector<SCROW> aNewSharedRows;
+ rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, false);
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, fVal);
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ if (bBroadcast)
+ BroadcastNewCell(nRow);
+}
+
+OUString ScColumn::GetString( const ScRefCellValue& aCell, SCROW nRow, const ScInterpreterContext* pContext ) const
+{
+ // ugly hack for ordering problem with GetNumberFormat and missing inherited formats
+ if (aCell.meType == CELLTYPE_FORMULA)
+ aCell.mpFormula->MaybeInterpret();
+
+ sal_uInt32 nFormat = GetNumberFormat( pContext ? *pContext : GetDoc().GetNonThreadedContext(), nRow);
+ const Color* pColor = nullptr;
+ return ScCellFormat::GetString(aCell, nFormat, &pColor,
+ pContext ? *(pContext->GetFormatTable()) : *(GetDoc().GetFormatTable()), GetDoc());
+}
+
+double* ScColumn::GetValueCell( SCROW nRow )
+{
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it == maCells.end())
+ return nullptr;
+
+ if (it->type != sc::element_type_numeric)
+ return nullptr;
+
+ return &sc::numeric_block::at(*it->data, aPos.second);
+}
+
+OUString ScColumn::GetInputString( const ScRefCellValue& aCell, SCROW nRow, const svl::SharedString** pShared, bool bForceSystemLocale ) const
+{
+ sal_uLong nFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ return ScCellFormat::GetInputString(aCell, nFormat, *(GetDoc().GetFormatTable()), GetDoc(), pShared, false, bForceSystemLocale);
+}
+
+double ScColumn::GetValue( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ switch (it->type)
+ {
+ case sc::element_type_numeric:
+ return sc::numeric_block::at(*it->data, aPos.second);
+ case sc::element_type_formula:
+ {
+ const ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ ScFormulaCell* p2 = const_cast<ScFormulaCell*>(p);
+ return p2->IsValue() ? p2->GetValue() : 0.0;
+ }
+ default:
+ ;
+ }
+
+ return 0.0;
+}
+
+const EditTextObject* ScColumn::GetEditText( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return nullptr;
+
+ if (it->type != sc::element_type_edittext)
+ return nullptr;
+
+ return sc::edittext_block::at(*it->data, aPos.second);
+}
+
+void ScColumn::RemoveEditTextCharAttribs( SCROW nRow, const ScPatternAttr& rAttr )
+{
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it == maCells.end())
+ return;
+
+ if (it->type != sc::element_type_edittext)
+ return;
+
+ EditTextObject* p = sc::edittext_block::at(*it->data, aPos.second);
+ ScEditUtil::RemoveCharAttribs(*p, rAttr);
+}
+
+OUString ScColumn::GetFormula( SCROW nRow ) const
+{
+ const ScFormulaCell* p = FetchFormulaCell(nRow);
+ if (p)
+ return p->GetFormula();
+ return OUString();
+}
+
+const ScFormulaCell* ScColumn::GetFormulaCell( SCROW nRow ) const
+{
+ return FetchFormulaCell(nRow);
+}
+
+ScFormulaCell* ScColumn::GetFormulaCell( SCROW nRow )
+{
+ return const_cast<ScFormulaCell*>(FetchFormulaCell(nRow));
+}
+
+CellType ScColumn::GetCellType( SCROW nRow ) const
+{
+ switch (maCells.get_type(nRow))
+ {
+ case sc::element_type_numeric:
+ return CELLTYPE_VALUE;
+ case sc::element_type_string:
+ return CELLTYPE_STRING;
+ case sc::element_type_edittext:
+ return CELLTYPE_EDIT;
+ case sc::element_type_formula:
+ return CELLTYPE_FORMULA;
+ default:
+ ;
+ }
+ return CELLTYPE_NONE;
+}
+
+namespace {
+
+/**
+ * Count the number of all non-empty cells.
+ */
+class CellCounter
+{
+ size_t mnCount;
+public:
+ CellCounter() : mnCount(0) {}
+
+ void operator() (const sc::CellStoreType::value_type& node)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ mnCount += node.size;
+ }
+
+ size_t getCount() const { return mnCount; }
+};
+
+}
+
+SCSIZE ScColumn::GetCellCount() const
+{
+ CellCounter aFunc;
+ aFunc = std::for_each(maCells.begin(), maCells.end(), aFunc);
+ return aFunc.getCount();
+}
+
+FormulaError ScColumn::GetErrCode( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return FormulaError::NONE;
+
+ if (it->type != sc::element_type_formula)
+ return FormulaError::NONE;
+
+ const ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ return const_cast<ScFormulaCell*>(p)->GetErrCode();
+}
+
+bool ScColumn::HasStringData( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ switch (aPos.first->type)
+ {
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ return true;
+ case sc::element_type_formula:
+ {
+ const ScFormulaCell* p = sc::formula_block::at(*aPos.first->data, aPos.second);
+ return !const_cast<ScFormulaCell*>(p)->IsValue();
+ }
+ default:
+ ;
+ }
+
+ return false;
+}
+
+bool ScColumn::HasValueData( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ switch (aPos.first->type)
+ {
+ case sc::element_type_numeric:
+ return true;
+ case sc::element_type_formula:
+ {
+ const ScFormulaCell* p = sc::formula_block::at(*aPos.first->data, aPos.second);
+ return const_cast<ScFormulaCell*>(p)->IsValue();
+ }
+ default:
+ ;
+ }
+
+ return false;
+}
+
+/**
+ * Return true if there is a string or editcell in the range
+ */
+bool ScColumn::HasStringCells( SCROW nStartRow, SCROW nEndRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ size_t nOffset = aPos.second;
+ SCROW nRow = nStartRow;
+ for (; it != maCells.end() && nRow <= nEndRow; ++it)
+ {
+ if (it->type == sc::element_type_string || it->type == sc::element_type_edittext)
+ return true;
+
+ nRow += it->size - nOffset;
+ nOffset = 0;
+ }
+
+ return false;
+}
+
+namespace {
+
+class MaxStringLenHandler
+{
+ sal_Int32 mnMaxLen;
+ const ScColumn& mrColumn;
+ SvNumberFormatter* mpFormatter;
+ rtl_TextEncoding meCharSet;
+ bool mbOctetEncoding;
+
+ void processCell(size_t nRow, const ScRefCellValue& rCell)
+ {
+ const Color* pColor;
+ sal_uInt32 nFormat = mrColumn.GetAttr(nRow, ATTR_VALUE_FORMAT).GetValue();
+ OUString aString = ScCellFormat::GetString(rCell, nFormat, &pColor, *mpFormatter, mrColumn.GetDoc());
+ sal_Int32 nLen = 0;
+ if (mbOctetEncoding)
+ {
+ OString aOString;
+ if (!aString.convertToString(&aOString, meCharSet,
+ RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
+ RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR))
+ {
+ // TODO: anything? this is used by the dBase export filter
+ // that throws an error anyway, but in case of another
+ // context we might want to indicate a conversion error
+ // early.
+ }
+ nLen = aOString.getLength();
+ }
+ else
+ nLen = aString.getLength() * sizeof(sal_Unicode);
+
+ if (mnMaxLen < nLen)
+ mnMaxLen = nLen;
+ }
+
+public:
+ MaxStringLenHandler(const ScColumn& rColumn, rtl_TextEncoding eCharSet) :
+ mnMaxLen(0),
+ mrColumn(rColumn),
+ mpFormatter(rColumn.GetDoc().GetFormatTable()),
+ meCharSet(eCharSet),
+ mbOctetEncoding(rtl_isOctetTextEncoding(eCharSet))
+ {
+ }
+
+ void operator() (size_t nRow, double fVal)
+ {
+ ScRefCellValue aCell(fVal);
+ processCell(nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rStr)
+ {
+ ScRefCellValue aCell(&rStr);
+ processCell(nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* p)
+ {
+ ScRefCellValue aCell(p);
+ processCell(nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(p));
+ processCell(nRow, aCell);
+ }
+
+ sal_Int32 getMaxLen() const { return mnMaxLen; }
+};
+
+}
+
+sal_Int32 ScColumn::GetMaxStringLen( SCROW nRowStart, SCROW nRowEnd, rtl_TextEncoding eCharSet ) const
+{
+ MaxStringLenHandler aFunc(*this, eCharSet);
+ sc::ParseAllNonEmpty(maCells.begin(), maCells, nRowStart, nRowEnd, aFunc);
+ return aFunc.getMaxLen();
+}
+
+namespace {
+
+class MaxNumStringLenHandler
+{
+ const ScColumn& mrColumn;
+ SvNumberFormatter* mpFormatter;
+ sal_Int32 mnMaxLen;
+ sal_uInt16 mnPrecision;
+ sal_uInt16 mnMaxGeneralPrecision;
+ bool mbHaveSigned;
+
+ void processCell(size_t nRow, ScRefCellValue& rCell)
+ {
+ sal_uInt16 nCellPrecision = mnMaxGeneralPrecision;
+ if (rCell.meType == CELLTYPE_FORMULA)
+ {
+ if (!rCell.mpFormula->IsValue())
+ return;
+
+ // Limit unformatted formula cell precision to precision
+ // encountered so far, if any, otherwise we'd end up with 15 just
+ // because of =1/3 ... If no precision yet then arbitrarily limit
+ // to a maximum of 4 unless a maximum general precision is set.
+ if (mnPrecision)
+ nCellPrecision = mnPrecision;
+ else
+ nCellPrecision = (mnMaxGeneralPrecision >= 15) ? 4 : mnMaxGeneralPrecision;
+ }
+
+ double fVal = rCell.getValue();
+ if (!mbHaveSigned && fVal < 0.0)
+ mbHaveSigned = true;
+
+ OUString aString;
+ OUString aSep;
+ sal_uInt16 nPrec;
+ sal_uInt32 nFormat =
+ mrColumn.GetAttr(nRow, ATTR_VALUE_FORMAT).GetValue();
+ if (nFormat % SV_COUNTRY_LANGUAGE_OFFSET)
+ {
+ aSep = mpFormatter->GetFormatDecimalSep(nFormat);
+ aString = ScCellFormat::GetInputString(rCell, nFormat, *mpFormatter, mrColumn.GetDoc());
+ const SvNumberformat* pEntry = mpFormatter->GetEntry(nFormat);
+ if (pEntry)
+ {
+ bool bThousand, bNegRed;
+ sal_uInt16 nLeading;
+ pEntry->GetFormatSpecialInfo(bThousand, bNegRed, nPrec, nLeading);
+ }
+ else
+ nPrec = mpFormatter->GetFormatPrecision(nFormat);
+ }
+ else
+ {
+ if (mnPrecision >= mnMaxGeneralPrecision)
+ return; // early bail out for nothing changes here
+
+ if (!fVal)
+ {
+ // 0 doesn't change precision, but set a maximum length if none yet.
+ if (!mnMaxLen)
+ mnMaxLen = 1;
+ return;
+ }
+
+ // Simple number string with at most 15 decimals and trailing
+ // decimal zeros eliminated.
+ aSep = ".";
+ aString = rtl::math::doubleToUString( fVal, rtl_math_StringFormat_F, nCellPrecision, '.', true);
+ nPrec = SvNumberFormatter::UNLIMITED_PRECISION;
+ }
+
+ sal_Int32 nLen = aString.getLength();
+ if (nLen <= 0)
+ // Ignore empty string.
+ return;
+
+ if (nPrec == SvNumberFormatter::UNLIMITED_PRECISION && mnPrecision < mnMaxGeneralPrecision)
+ {
+ if (nFormat % SV_COUNTRY_LANGUAGE_OFFSET)
+ {
+ // For some reason we couldn't obtain a precision from the
+ // format, retry with simple number string.
+ aSep = ".";
+ aString = rtl::math::doubleToUString( fVal, rtl_math_StringFormat_F, nCellPrecision, '.', true);
+ nLen = aString.getLength();
+ }
+ sal_Int32 nSep = aString.indexOf( aSep);
+ if (nSep != -1)
+ nPrec = aString.getLength() - nSep - 1;
+
+ }
+
+ if (nPrec != SvNumberFormatter::UNLIMITED_PRECISION && nPrec > mnPrecision)
+ mnPrecision = nPrec;
+
+ if (mnPrecision)
+ { // less than mnPrecision in string => widen it
+ // more => shorten it
+ sal_Int32 nTmp = aString.indexOf(aSep);
+ if ( nTmp == -1 )
+ nLen += mnPrecision + aSep.getLength();
+ else
+ {
+ nTmp = aString.getLength() - (nTmp + aSep.getLength());
+ if (nTmp != mnPrecision)
+ nLen += mnPrecision - nTmp;
+ // nPrecision > nTmp : nLen + Diff
+ // nPrecision < nTmp : nLen - Diff
+ }
+ }
+
+ // Enlarge for sign if necessary. Bear in mind that
+ // GetMaxNumberStringLen() is for determining dBase decimal field width
+ // and precision where the overall field width must include the sign.
+ // Fitting -1 into "#.##" (width 4, 2 decimals) does not work.
+ if (mbHaveSigned && fVal >= 0.0)
+ ++nLen;
+
+ if (mnMaxLen < nLen)
+ mnMaxLen = nLen;
+ }
+
+public:
+ MaxNumStringLenHandler(const ScColumn& rColumn, sal_uInt16 nMaxGeneralPrecision) :
+ mrColumn(rColumn), mpFormatter(rColumn.GetDoc().GetFormatTable()),
+ mnMaxLen(0), mnPrecision(0), mnMaxGeneralPrecision(nMaxGeneralPrecision),
+ mbHaveSigned(false)
+ {
+ // Limit the decimals passed to doubleToUString().
+ // Also, the dBaseIII maximum precision is 15.
+ if (mnMaxGeneralPrecision > 15)
+ mnMaxGeneralPrecision = 15;
+ }
+
+ void operator() (size_t nRow, double fVal)
+ {
+ ScRefCellValue aCell(fVal);
+ processCell(nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(p));
+ processCell(nRow, aCell);
+ }
+
+ sal_Int32 getMaxLen() const { return mnMaxLen; }
+
+ sal_uInt16 getPrecision() const { return mnPrecision; }
+};
+
+}
+
+sal_Int32 ScColumn::GetMaxNumberStringLen(
+ sal_uInt16& nPrecision, SCROW nRowStart, SCROW nRowEnd ) const
+{
+ sal_uInt16 nMaxGeneralPrecision = GetDoc().GetDocOptions().GetStdPrecision();
+ MaxNumStringLenHandler aFunc(*this, nMaxGeneralPrecision);
+ sc::ParseFormulaNumeric(maCells.begin(), maCells, nRowStart, nRowEnd, aFunc);
+ nPrecision = aFunc.getPrecision();
+ return aFunc.getMaxLen();
+}
+
+namespace {
+
+class GroupFormulaCells
+{
+ std::vector<ScAddress>* mpGroupPos;
+
+public:
+ explicit GroupFormulaCells(std::vector<ScAddress>* pGroupPos)
+ : mpGroupPos(pGroupPos) {}
+
+ void operator() (sc::CellStoreType::value_type& node)
+ {
+ if (node.type != sc::element_type_formula)
+ // We are only interested in formula cells.
+ return;
+
+ size_t nRow = node.position; // start row position.
+
+ sc::formula_block::iterator it = sc::formula_block::begin(*node.data);
+ sc::formula_block::iterator itEnd = sc::formula_block::end(*node.data);
+
+ // This block should never be empty.
+
+ ScFormulaCell* pPrev = *it;
+ ScFormulaCellGroupRef xPrevGrp = pPrev->GetCellGroup();
+ if (xPrevGrp)
+ {
+ // Move to the cell after the last cell of the current group.
+ std::advance(it, xPrevGrp->mnLength);
+ nRow += xPrevGrp->mnLength;
+ }
+ else
+ {
+ ++it;
+ ++nRow;
+ }
+
+ ScFormulaCell* pCur = nullptr;
+ ScFormulaCellGroupRef xCurGrp;
+ for (; it != itEnd; pPrev = pCur, xPrevGrp = xCurGrp)
+ {
+ pCur = *it;
+ xCurGrp = pCur->GetCellGroup();
+
+ ScFormulaCell::CompareState eCompState = pPrev->CompareByTokenArray(*pCur);
+ if (eCompState == ScFormulaCell::NotEqual)
+ {
+ // different formula tokens.
+ if (xCurGrp)
+ {
+ // Move to the cell after the last cell of the current group.
+ if (xCurGrp->mnLength > std::distance(it, itEnd))
+ throw css::lang::IllegalArgumentException();
+ std::advance(it, xCurGrp->mnLength);
+ nRow += xCurGrp->mnLength;
+ }
+ else
+ {
+ ++it;
+ ++nRow;
+ }
+
+ continue;
+ }
+
+ // Formula tokens equal those of the previous formula cell or cell group.
+ if (xPrevGrp)
+ {
+ // Previous cell is a group.
+ if (xCurGrp)
+ {
+ // The current cell is a group. Merge these two groups.
+ xPrevGrp->mnLength += xCurGrp->mnLength;
+ pCur->SetCellGroup(xPrevGrp);
+ sc::formula_block::iterator itGrpEnd = it;
+ if (xCurGrp->mnLength > std::distance(itGrpEnd, itEnd))
+ throw css::lang::IllegalArgumentException();
+ std::advance(itGrpEnd, xCurGrp->mnLength);
+ for (++it; it != itGrpEnd; ++it)
+ {
+ ScFormulaCell* pCell = *it;
+ pCell->SetCellGroup(xPrevGrp);
+ }
+ nRow += xCurGrp->mnLength;
+ }
+ else
+ {
+ // Add this cell to the previous group.
+ pCur->SetCellGroup(xPrevGrp);
+ ++xPrevGrp->mnLength;
+ ++nRow;
+ ++it;
+ }
+
+ }
+ else if (xCurGrp)
+ {
+ // Previous cell is a regular cell and current cell is a group.
+ nRow += xCurGrp->mnLength;
+ if (xCurGrp->mnLength > std::distance(it, itEnd))
+ throw css::lang::IllegalArgumentException();
+ std::advance(it, xCurGrp->mnLength);
+ pPrev->SetCellGroup(xCurGrp);
+ xCurGrp->mpTopCell = pPrev;
+ ++xCurGrp->mnLength;
+ xPrevGrp = xCurGrp;
+ }
+ else
+ {
+ // Both previous and current cells are regular cells.
+ assert(pPrev->aPos.Row() == static_cast<SCROW>(nRow - 1));
+ xPrevGrp = pPrev->CreateCellGroup(2, eCompState == ScFormulaCell::EqualInvariant);
+ pCur->SetCellGroup(xPrevGrp);
+ ++nRow;
+ ++it;
+ }
+
+ if (mpGroupPos)
+ mpGroupPos->push_back(pCur->aPos);
+
+ pCur = pPrev;
+ xCurGrp = xPrevGrp;
+ }
+ }
+};
+
+}
+
+void ScColumn::RegroupFormulaCells( std::vector<ScAddress>* pGroupPos )
+{
+ // re-build formula groups.
+ std::for_each(maCells.begin(), maCells.end(), GroupFormulaCells(pGroupPos));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */