/* -*- 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 <docsh.hxx>
#include <document.hxx>
#include <table.hxx>
#include <attarray.hxx>
#include <patattr.hxx>
#include <compiler.hxx>
#include <brdcst.hxx>
#include <markdata.hxx>
#include <postit.hxx>
#include <cellvalue.hxx>
#include <tokenarray.hxx>
#include <clipcontext.hxx>
#include <types.hxx>
#include <editutil.hxx>
#include <mtvcellfunc.hxx>
#include <columnspanset.hxx>
#include <scopetools.hxx>
#include <sharedformula.hxx>
#include <refupdatecontext.hxx>
#include <listenercontext.hxx>
#include <formulagroup.hxx>
#include <drwlayer.hxx>
#include <mtvelements.hxx>
#include <bcaslot.hxx>
#include <svl/numformat.hxx>
#include <poolcach.hxx>
#include <svl/zforlist.hxx>
#include <svl/sharedstringpool.hxx>
#include <editeng/fieldupdater.hxx>
#include <formula/errorcodes.hxx>
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <map>
#include <cstdio>
#include <memory>
using ::editeng::SvxBorderLine;
using namespace formula;
namespace {
bool IsAmbiguousScriptNonZero( SvtScriptType nScript )
{
//TODO: move to a header file
return ( nScript != SvtScriptType::LATIN &&
nScript != SvtScriptType::ASIAN &&
nScript != SvtScriptType::COMPLEX &&
nScript != SvtScriptType::NONE );
}
}
ScNeededSizeOptions::ScNeededSizeOptions() :
aPattern(), bFormula(false), bSkipMerged(true), bGetFont(true), bTotalSize(false)
{
}
ScColumn::ScColumn(ScSheetLimits const & rSheetLimits) :
maCellTextAttrs(rSheetLimits.GetMaxRowCount()),
maCellNotes(sc::CellStoreEvent(this)),
maBroadcasters(rSheetLimits.GetMaxRowCount()),
maCells(sc::CellStoreEvent(this)),
maSparklines(rSheetLimits.GetMaxRowCount()),
mnBlkCountFormula(0),
mnBlkCountCellNotes(0),
nCol( 0 ),
nTab( 0 ),
mbEmptyBroadcastersPending( false )
{
maCellNotes.resize(rSheetLimits.GetMaxRowCount());
maCells.resize(rSheetLimits.GetMaxRowCount());
}
ScColumn::~ScColumn() COVERITY_NOEXCEPT_FALSE
{
FreeAll();
}
void ScColumn::Init(SCCOL nNewCol, SCTAB nNewTab, ScDocument& rDoc, bool bEmptyAttrArray)
{
nCol = nNewCol;
nTab = nNewTab;
if ( bEmptyAttrArray )
InitAttrArray(new ScAttrArray( nCol, nTab, rDoc, nullptr ));
else
InitAttrArray(new ScAttrArray( nCol, nTab, rDoc, &rDoc.maTabs[nTab]->aDefaultColData.AttrArray()));
}
sc::MatrixEdge ScColumn::GetBlockMatrixEdges( SCROW nRow1, SCROW nRow2, sc::MatrixEdge nMask,
bool bNoMatrixAtAll ) const
{
using namespace sc;
if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
return MatrixEdge::Nothing;
ScAddress aOrigin(ScAddress::INITIALIZE_INVALID);
if (nRow1 == nRow2)
{
std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow1);
if (aPos.first->type != sc::element_type_formula)
return MatrixEdge::Nothing;
const ScFormulaCell* pCell = sc::formula_block::at(*aPos.first->data, aPos.second);
if (pCell->GetMatrixFlag() == ScMatrixMode::NONE)
return MatrixEdge::Nothing;
return pCell->GetMatrixEdge(GetDoc(), aOrigin);
}
bool bOpen = false;
MatrixEdge nEdges = MatrixEdge::Nothing;
std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow1);
sc::CellStoreType::const_iterator it = aPos.first;
size_t nOffset = aPos.second;
SCROW nRow = nRow1;
for (;it != maCells.end() && nRow <= nRow2; ++it, nOffset = 0)
{
if (it->type != sc::element_type_formula)
{
// Skip this block.
nRow += it->size - nOffset;
continue;
}
size_t nRowsToRead = nRow2 - nRow + 1;
size_t nEnd = std::min(it->size, nOffset+nRowsToRead); // last row + 1
sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data);
std::advance(itCell, nOffset);
for (size_t i = nOffset; i < nEnd; ++itCell, ++i)
{
// Loop inside the formula block.
const ScFormulaCell* pCell = *itCell;
if (pCell->GetMatrixFlag() == ScMatrixMode::NONE)
continue;
nEdges = pCell->GetMatrixEdge(GetDoc(), aOrigin);
if (nEdges == MatrixEdge::Nothing)
continue;
// A 1x1 matrix array formula is OK even for no matrix at all.
if (bNoMatrixAtAll
&& (nEdges != (MatrixEdge::Top | MatrixEdge::Left | MatrixEdge::Bottom | MatrixEdge::Right)))
return MatrixEdge::Inside; // per convention Inside
if (nEdges & MatrixEdge::Top)
bOpen = true; // top edge opens, keep on looking
else if (!bOpen)
return nEdges | MatrixEdge::Open; // there's something that wasn't opened
else if (nEdges & MatrixEdge::Inside)
return nEdges; // inside
if (((nMask & MatrixEdge::Right) && (nEdges & MatrixEdge::Left) && !(nEdges & MatrixEdge::Right)) ||
((nMask & MatrixEdge::Left) && (nEdges & MatrixEdge::Right) && !(nEdges & MatrixEdge::Left)))
return nEdges; // only left/right edge
if (nEdges & MatrixEdge::Bottom)
bOpen = false; // bottom edge closes
}
nRow += nEnd - nOffset;
}
if (bOpen)
nEdges |= MatrixEdge::Open; // not closed, matrix continues
return nEdges;
}
bool ScColumn::HasSelectionMatrixFragment(const ScMarkData& rMark, const ScRangeList& rRangeList) const
{
using namespace sc;
if (!rMark.IsMultiMarked())
return false;
ScAddress aOrigin(ScAddress::INITIALIZE_INVALID);
ScAddress aCurOrigin = aOrigin;
bool bOpen = false;
for (size_t i = 0, n = rRangeList.size(); i < n; ++i)
{
const ScRange& r = rRangeList[i];
if (nTab < r.aStart.Tab() || r.aEnd.Tab() < nTab)
continue;
if (nCol < r.aStart.Col() || r.aEnd.Col() < nCol)
continue;
SCROW nTop = r.aStart.Row(), nBottom = r.aEnd.Row();
SCROW nRow = nTop;
std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
sc::CellStoreType::const_iterator it = aPos.first;
size_t nOffset = aPos.second;
for (;it != maCells.end() && nRow <= nBottom; ++it, nOffset = 0)
{
if (it->type != sc::element_type_formula)
{
// Skip this block.
nRow += it->size - nOffset;
continue;
}
// This is a formula cell block.
size_t nRowsToRead = nBottom - nRow + 1;
size_t nEnd = std::min(it->size, nRowsToRead);
sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data);
std::advance(itCell, nOffset);
for (size_t j = nOffset; j < nEnd; ++itCell, ++j)
{
// Loop inside the formula block.
const ScFormulaCell* pCell = *itCell;
if (pCell->GetMatrixFlag() == ScMatrixMode::NONE)
// cell is not a part of a matrix.
continue;
MatrixEdge nEdges = pCell->GetMatrixEdge(GetDoc(), aOrigin);
if (nEdges == MatrixEdge::Nothing)
continue;
bool bFound = false;
if (nEdges & MatrixEdge::Top)
bOpen = true; // top edge opens, keep on looking
else if (!bOpen)
return true; // there's something that wasn't opened
else if (nEdges & MatrixEdge::Inside)
bFound = true; // inside, all selected?
if (((nEdges & MatrixEdge::Left) | MatrixEdge::Right) ^ ((nEdges & MatrixEdge::Right) | MatrixEdge::Left))
// either left or right, but not both.
bFound = true; // only left/right edge, all selected?
if (nEdges & MatrixEdge::Bottom)
bOpen = false; // bottom edge closes
if (bFound)
{
// Check if the matrix is inside the selection in its entirety.
//
// TODO: It's more efficient to skip the matrix range if
// it's within selection, to avoid checking it again and
// again.
if (aCurOrigin != aOrigin)
{ // new matrix to check?
aCurOrigin = aOrigin;
const ScFormulaCell* pFCell;
if (pCell->GetMatrixFlag() == ScMatrixMode::Reference)
pFCell = GetDoc().GetFormulaCell(aOrigin);
else
pFCell = pCell;
SCCOL nC;
SCROW nR;
pFCell->GetMatColsRows(nC, nR);
ScRange aRange(aOrigin, ScAddress(aOrigin.Col()+nC-1, aOrigin.Row()+nR-1, aOrigin.Tab()));
if (rMark.IsAllMarked(aRange))
bFound = false;
}
else
bFound = false; // done already
}
if (bFound)
return true;
}
nRow += nEnd;
}
}
return bOpen;
}
bool ScColumn::HasAttribSelection( const ScMarkData& rMark, HasAttrFlags nMask ) const
{
bool bFound = false;
SCROW nTop;
SCROW nBottom;
if (rMark.IsMultiMarked())
{
ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
while (aMultiIter.Next( nTop, nBottom ) && !bFound)
{
if (pAttrArray->HasAttrib( nTop, nBottom, nMask ))
bFound = true;
}
}
return bFound;
}
void ScColumn::MergeSelectionPattern( ScMergePatternState& rState, const ScMarkData& rMark, bool bDeep ) const
{
SCROW nTop;
SCROW nBottom;
if ( rMark.IsMultiMarked() )
{
const ScMultiSel& rMultiSel = rMark.GetMultiSelData();
if ( rMultiSel.HasMarks( nCol ) )
{
ScMultiSelIter aMultiIter( rMultiSel, nCol );
while (aMultiIter.Next( nTop, nBottom ))
pAttrArray->MergePatternArea( nTop, nBottom, rState, bDeep );
}
}
}
const ScPatternAttr* ScColumnData::GetMostUsedPattern( SCROW nStartRow, SCROW nEndRow ) const
{
::std::map< const ScPatternAttr*, size_t > aAttrMap;
const ScPatternAttr* pMaxPattern = nullptr;
size_t nMaxCount = 0;
ScAttrIterator aAttrIter( pAttrArray.get(), nStartRow, nEndRow, &GetDoc().getCellAttributeHelper().getDefaultCellAttribute() );
const ScPatternAttr* pPattern;
SCROW nAttrRow1 = 0, nAttrRow2 = 0;
while( (pPattern = aAttrIter.Next( nAttrRow1, nAttrRow2 )) != nullptr )
{
size_t& rnCount = aAttrMap[ pPattern ];
rnCount += (nAttrRow2 - nAttrRow1 + 1);
if( rnCount > nMaxCount )
{
pMaxPattern = pPattern;
nMaxCount = rnCount;
}
}
return pMaxPattern;
}
sal_uInt32 ScColumnData::GetNumberFormat( SCROW nStartRow, SCROW nEndRow ) const
{
SCROW nPatStartRow, nPatEndRow;
const ScPatternAttr* pPattern = pAttrArray->GetPatternRange(nPatStartRow, nPatEndRow, nStartRow);
sal_uInt32 nFormat = pPattern->GetNumberFormat(GetDoc().GetFormatTable());
while (nEndRow > nPatEndRow)
{
nStartRow = nPatEndRow + 1;
pPattern = pAttrArray->GetPatternRange(nPatStartRow, nPatEndRow, nStartRow);
sal_uInt32 nTmpFormat = pPattern->GetNumberFormat(GetDoc().GetFormatTable());
if (nFormat != nTmpFormat)
return 0;
}
return nFormat;
}
void ScColumnData::ApplySelectionCache(ScItemPoolCache& rCache, SCROW nStartRow, SCROW nEndRow,
ScEditDataArray* pDataArray, bool* pIsChanged)
{
pAttrArray->ApplyCacheArea(nStartRow, nEndRow, rCache, pDataArray, pIsChanged);
}
void ScColumnData::ChangeSelectionIndent(bool bIncrement, SCROW nStartRow, SCROW nEndRow)
{
pAttrArray->ChangeIndent(nStartRow, nEndRow, bIncrement);
}
void ScColumnData::ClearSelectionItems(const sal_uInt16* pWhich, SCROW nStartRow, SCROW nEndRow)
{
if (!pAttrArray)
return;
pAttrArray->ClearItems(nStartRow, nEndRow, pWhich);
}
void ScColumn::DeleteSelection( InsertDeleteFlags nDelFlag, const ScMarkData& rMark, bool bBroadcast )
{
SCROW nTop;
SCROW nBottom;
if ( rMark.IsMultiMarked() )
{
ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
while (aMultiIter.Next( nTop, nBottom ))
DeleteArea(nTop, nBottom, nDelFlag, bBroadcast);
}
}
void ScColumn::ApplyPattern( SCROW nRow, const ScPatternAttr& rPatAttr )
{
const SfxItemSet* pSet = &rPatAttr.GetItemSet();
ScItemPoolCache aCache( GetDoc().getCellAttributeHelper(), *pSet );
const CellAttributeHolder aPattern(pAttrArray->GetPattern( nRow ));
// true = keep old content
const CellAttributeHolder& rNewPattern = aCache.ApplyTo( aPattern );
if (!CellAttributeHolder::areSame(&rNewPattern, &aPattern))
pAttrArray->SetPattern( nRow, rNewPattern );
}
void ScColumnData::ApplyPatternArea( SCROW nStartRow, SCROW nEndRow, const ScPatternAttr& rPatAttr,
ScEditDataArray* pDataArray, bool* const pIsChanged )
{
const SfxItemSet* pSet = &rPatAttr.GetItemSet();
ScItemPoolCache aCache( GetDoc().getCellAttributeHelper(), *pSet );
pAttrArray->ApplyCacheArea( nStartRow, nEndRow, aCache, pDataArray, pIsChanged );
}
void ScColumn::ApplyPatternIfNumberformatIncompatible( const ScRange& rRange,
const ScPatternAttr& rPattern, SvNumFormatType nNewType )
{
const SfxItemSet* pSet = &rPattern.GetItemSet();
ScItemPoolCache aCache( GetDoc().getCellAttributeHelper(), *pSet );
SvNumberFormatter* pFormatter = GetDoc().GetFormatTable();
SCROW nEndRow = rRange.aEnd.Row();
for ( SCROW nRow = rRange.aStart.Row(); nRow <= nEndRow; nRow++ )
{
SCROW nRow1, nRow2;
const ScPatternAttr* pPattern = pAttrArray->GetPatternRange(
nRow1, nRow2, nRow );
sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter );
SvNumFormatType nOldType = pFormatter->GetType( nFormat );
if ( nOldType == nNewType || SvNumberFormatter::IsCompatible( nOldType, nNewType ) )
nRow = nRow2;
else
{
SCROW nNewRow1 = std::max( nRow1, nRow );
SCROW nNewRow2 = std::min( nRow2, nEndRow );
pAttrArray->ApplyCacheArea( nNewRow1, nNewRow2, aCache );
nRow = nNewRow2;
}
}
}
void ScColumn::ApplyStyle( SCROW nRow, const ScStyleSheet* rStyle )
{
const ScPatternAttr* pPattern = pAttrArray->GetPattern(nRow);
ScPatternAttr* pNewPattern(new ScPatternAttr(*pPattern));
pNewPattern->SetStyleSheet(const_cast<ScStyleSheet*>(rStyle));
pAttrArray->SetPattern(nRow, CellAttributeHolder(pNewPattern, true));
}
void ScColumnData::ApplySelectionStyle(const ScStyleSheet& rStyle, SCROW nTop, SCROW nBottom)
{
pAttrArray->ApplyStyleArea(nTop, nBottom, rStyle);
}
void ScColumn::ApplySelectionLineStyle( const ScMarkData& rMark,
const SvxBorderLine* pLine, bool bColorOnly )
{
if ( bColorOnly && !pLine )
return;
SCROW nTop;
SCROW nBottom;
if (rMark.IsMultiMarked())
{
ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
while (aMultiIter.Next( nTop, nBottom ))
pAttrArray->ApplyLineStyleArea(nTop, nBottom, pLine, bColorOnly );
}
}
const ScStyleSheet* ScColumn::GetSelectionStyle( const ScMarkData& rMark, bool& rFound ) const
{
rFound = false;
if (!rMark.IsMultiMarked())
{
OSL_FAIL("No selection in ScColumn::GetSelectionStyle");
return nullptr;
}
bool bEqual = true;
const ScStyleSheet* pStyle = nullptr;
const ScStyleSheet* pNewStyle;
ScDocument& rDocument = GetDoc();
ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
SCROW nTop;
SCROW nBottom;
while (bEqual && aMultiIter.Next( nTop, nBottom ))
{
ScAttrIterator aAttrIter( pAttrArray.get(), nTop, nBottom, &rDocument.getCellAttributeHelper().getDefaultCellAttribute() );
SCROW nRow;
SCROW nDummy;
while (bEqual)
{
const ScPatternAttr* pPattern = aAttrIter.Next( nRow, nDummy );
if (!pPattern)
break;
pNewStyle = pPattern->GetStyleSheet();
rFound = true;
if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
bEqual = false; // difference
pStyle = pNewStyle;
}
}
return bEqual ? pStyle : nullptr;
}
const ScStyleSheet* ScColumn::GetAreaStyle( bool& rFound, SCROW nRow1, SCROW nRow2 ) const
{
rFound = false;
bool bEqual = true;
const ScStyleSheet* pStyle = nullptr;
const ScStyleSheet* pNewStyle;
ScAttrIterator aAttrIter( pAttrArray.get(), nRow1, nRow2, &GetDoc().getCellAttributeHelper().getDefaultCellAttribute() );
SCROW nRow;
SCROW nDummy;
while (bEqual)
{
const ScPatternAttr* pPattern = aAttrIter.Next( nRow, nDummy );
if (!pPattern)
break;
pNewStyle = pPattern->GetStyleSheet();
rFound = true;
if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
bEqual = false; // difference
pStyle = pNewStyle;
}
return bEqual ? pStyle : nullptr;
}
void ScColumn::ApplyAttr( SCROW nRow, const SfxPoolItem& rAttr )
{
// in order to only create a new SetItem, we don't need SfxItemPoolCache.
//TODO: Warning: ScItemPoolCache seems to create too many Refs for the new SetItem ??
const ScPatternAttr* pOldPattern(pAttrArray->GetPattern(nRow));
ScPatternAttr* pNewPattern(new ScPatternAttr(*pOldPattern));
pNewPattern->GetItemSet().Put(rAttr);
if (!ScPatternAttr::areSame( pNewPattern, pOldPattern ))
pAttrArray->SetPattern( nRow, CellAttributeHolder(pNewPattern, true) );
else
delete pNewPattern;
}
ScRefCellValue ScColumn::GetCellValue( SCROW nRow ) const
{
std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
if (aPos.first == maCells.end())
return ScRefCellValue();
return GetCellValue(aPos.first, aPos.second);
}
ScRefCellValue ScColumn::GetCellValue( sc::ColumnBlockPosition& rBlockPos, SCROW nRow )
{
std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
if (aPos.first == maCells.end())
return ScRefCellValue();
rBlockPos.miCellPos = aPos.first; // Store this for next call.
return GetCellValue(aPos.first, aPos.second);
}
ScRefCellValue ScColumn::GetCellValue( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) const
{
std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
if (aPos.first == maCells.end())
return ScRefCellValue();
rBlockPos.miCellPos = aPos.first; // Store this for next call.
return GetCellValue(aPos.first, aPos.second);
}
ScRefCellValue ScColumn::GetCellValue( const sc::CellStoreType::const_iterator& itPos, size_t nOffset )
{
switch (itPos->type)
{
case sc::element_type_numeric:
// Numeric cell
return ScRefCellValue(sc::numeric_block::at(*itPos->data, nOffset));
case sc::element_type_string:
// String cell
return ScRefCellValue(&sc::string_block::at(*itPos->data, nOffset));
case sc::element_type_edittext:
// Edit cell
return ScRefCellValue(sc::edittext_block::at(*itPos->data, nOffset));
case sc::element_type_formula:
// Formula cell
return ScRefCellValue(sc::formula_block::at(*itPos->data, nOffset));
default:
return ScRefCellValue(); // empty cell
}
}
const sc::CellTextAttr* ScColumn::GetCellTextAttr( SCROW nRow ) const
{
sc::ColumnBlockConstPosition aBlockPos;
aBlockPos.miCellTextAttrPos = maCellTextAttrs.begin();
return GetCellTextAttr(aBlockPos, nRow);
}
const sc::CellTextAttr* ScColumn::GetCellTextAttr( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) const
{
sc::CellTextAttrStoreType::const_position_type aPos = maCellTextAttrs.position(rBlockPos.miCellTextAttrPos, nRow);
if (aPos.first == maCellTextAttrs.end())
return nullptr;
rBlockPos.miCellTextAttrPos = aPos.first;
if (aPos.first->type != sc::element_type_celltextattr)
return nullptr;
return &sc::celltextattr_block::at(*aPos.first->data, aPos.second);
}
bool ScColumn::TestInsertCol( SCROW nStartRow, SCROW nEndRow) const
{
if (IsEmptyData() && IsEmptyAttr())
return true;
// Return false if we have any non-empty cells between nStartRow and nEndRow inclusive.
std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
sc::CellStoreType::const_iterator it = aPos.first;
if (it->type != sc::element_type_empty)
return false;
// Get the length of the remaining empty segment.
size_t nLen = it->size - aPos.second;
SCROW nNextNonEmptyRow = nStartRow + nLen;
if (nNextNonEmptyRow <= nEndRow)
return false;
// AttrArray only looks for merged cells
return pAttrArray == nullptr || pAttrArray->TestInsertCol(nStartRow, nEndRow);
}
bool ScColumn::TestInsertRow( SCROW nStartRow, SCSIZE nSize ) const
{
// AttrArray only looks for merged cells
{
std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
sc::CellStoreType::const_iterator it = aPos.first;
if (it->type == sc::element_type_empty && maCells.block_size() == 1)
// The entire cell array is empty.
return pAttrArray->TestInsertRow(nSize);
}
// See if there would be any non-empty cell that gets pushed out.
// Find the position of the last non-empty cell below nStartRow.
size_t nLastNonEmptyRow = GetDoc().MaxRow();
sc::CellStoreType::const_reverse_iterator it = maCells.rbegin();
if (it->type == sc::element_type_empty)
nLastNonEmptyRow -= it->size;
if (nLastNonEmptyRow < o3tl::make_unsigned(nStartRow))
// No cells would get pushed out.
return pAttrArray->TestInsertRow(nSize);
if (nLastNonEmptyRow + nSize > o3tl::make_unsigned(GetDoc().MaxRow()))
// At least one cell would get pushed out. Not good.
return false;
return pAttrArray->TestInsertRow(nSize);
}
void ScColumn::InsertRow( SCROW nStartRow, SCSIZE nSize )
{
pAttrArray->InsertRow( nStartRow, nSize );
maCellNotes.insert_empty(nStartRow, nSize);
maCellNotes.resize(GetDoc().GetMaxRowCount());
maSparklines.insert_empty(nStartRow, nSize);
maSparklines.resize(GetDoc().GetSheetLimits().GetMaxRowCount());
maBroadcasters.insert_empty(nStartRow, nSize);
maBroadcasters.resize(GetDoc().GetMaxRowCount());
maCellTextAttrs.insert_empty(nStartRow, nSize);
maCellTextAttrs.resize(GetDoc().GetMaxRowCount());
maCells.insert_empty(nStartRow, nSize);
maCells.resize(GetDoc().GetMaxRowCount());
CellStorageModified();
// We *probably* don't need to broadcast here since the parent call seems
// to take care of it.
}
namespace {
class CopyToClipHandler
{
const ScDocument& mrSrcDoc;
const ScColumn& mrSrcCol;
ScColumn& mrDestCol;
sc::ColumnBlockPosition maDestPos;
sc::ColumnBlockPosition* mpDestPos;
void setDefaultAttrsToDest(size_t nRow, size_t nSize)
{
std::vector<sc::CellTextAttr> aAttrs(nSize); // default values
maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
maDestPos.miCellTextAttrPos, nRow, aAttrs.begin(), aAttrs.end());
}
public:
CopyToClipHandler(const ScDocument& rSrcDoc, const ScColumn& rSrcCol, ScColumn& rDestCol, sc::ColumnBlockPosition* pDestPos) :
mrSrcDoc(rSrcDoc), mrSrcCol(rSrcCol), mrDestCol(rDestCol), mpDestPos(pDestPos)
{
if (mpDestPos)
maDestPos = *mpDestPos;
else
mrDestCol.InitBlockPosition(maDestPos);
}
~CopyToClipHandler()
{
if (mpDestPos)
*mpDestPos = maDestPos;
}
void operator() (const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize)
{
size_t nTopRow = aNode.position + nOffset;
bool bSet = true;
switch (aNode.type)
{
case sc::element_type_numeric:
{
sc::numeric_block::const_iterator it = sc::numeric_block::begin(*aNode.data);
std::advance(it, nOffset);
sc::numeric_block::const_iterator itEnd = it;
std::advance(itEnd, nDataSize);
maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nTopRow, it, itEnd);
}
break;
case sc::element_type_string:
{
sc::string_block::const_iterator it = sc::string_block::begin(*aNode.data);
std::advance(it, nOffset);
sc::string_block::const_iterator itEnd = it;
std::advance(itEnd, nDataSize);
maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nTopRow, it, itEnd);
}
break;
case sc::element_type_edittext:
{
sc::edittext_block::const_iterator it = sc::edittext_block::begin(*aNode.data);
std::advance(it, nOffset);
sc::edittext_block::const_iterator itEnd = it;
std::advance(itEnd, nDataSize);
std::vector<EditTextObject*> aCloned;
aCloned.reserve(nDataSize);
for (; it != itEnd; ++it)
aCloned.push_back(ScEditUtil::Clone(**it, mrDestCol.GetDoc()).release());
maDestPos.miCellPos = mrDestCol.GetCellStore().set(
maDestPos.miCellPos, nTopRow, aCloned.begin(), aCloned.end());
}
break;
case sc::element_type_formula:
{
sc::formula_block::const_iterator it = sc::formula_block::begin(*aNode.data);
std::advance(it, nOffset);
sc::formula_block::const_iterator itEnd = it;
std::advance(itEnd, nDataSize);
std::vector<ScFormulaCell*> aCloned;
aCloned.reserve(nDataSize);
ScAddress aDestPos(mrDestCol.GetCol(), nTopRow, mrDestCol.GetTab());
for (; it != itEnd; ++it, aDestPos.IncRow())
{
const ScFormulaCell& rOld = **it;
if (rOld.GetDirty() && mrSrcCol.GetDoc().GetAutoCalc())
const_cast<ScFormulaCell&>(rOld).Interpret();
aCloned.push_back(new ScFormulaCell(rOld, mrDestCol.GetDoc(), aDestPos));
}
// Group the cloned formula cells.
if (!aCloned.empty())
sc::SharedFormulaUtil::groupFormulaCells(aCloned.begin(), aCloned.end());
sc::CellStoreType& rDestCells = mrDestCol.GetCellStore();
maDestPos.miCellPos = rDestCells.set(
maDestPos.miCellPos, nTopRow, aCloned.begin(), aCloned.end());
// Merge adjacent formula cell groups (if applicable).
sc::CellStoreType::position_type aPos =
rDestCells.position(maDestPos.miCellPos, nTopRow);
maDestPos.miCellPos = aPos.first;
sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
size_t nLastRow = nTopRow + nDataSize;
if (nLastRow < o3tl::make_unsigned(mrSrcDoc.MaxRow()))
{
aPos = rDestCells.position(maDestPos.miCellPos, nLastRow+1);
sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
}
}
break;
default:
bSet = false;
}
if (bSet)
setDefaultAttrsToDest(nTopRow, nDataSize);
mrSrcCol.DuplicateNotes(nTopRow, nDataSize, mrDestCol, maDestPos, false);
mrSrcCol.DuplicateSparklines(nTopRow, nDataSize, mrDestCol, maDestPos);
}
};
class CopyTextAttrToClipHandler
{
sc::CellTextAttrStoreType& mrDestAttrs;
sc::CellTextAttrStoreType::iterator miPos;
public:
explicit CopyTextAttrToClipHandler( sc::CellTextAttrStoreType& rAttrs ) :
mrDestAttrs(rAttrs), miPos(mrDestAttrs.begin()) {}
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;
miPos = mrDestAttrs.set(miPos, nPos, it, itEnd);
}
};
}
void ScColumn::CopyToClip(
sc::CopyToClipContext& rCxt, SCROW nRow1, SCROW nRow2, ScColumn& rColumn ) const
{
if (!rCxt.isCopyChartRanges()) // No need to copy attributes for chart ranges
pAttrArray->CopyArea( nRow1, nRow2, 0, *rColumn.pAttrArray,
rCxt.isKeepScenarioFlags() ? (ScMF::All & ~ScMF::Scenario) : ScMF::All );
{
CopyToClipHandler aFunc(GetDoc(), *this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol));
sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
}
if (!rCxt.isCopyChartRanges()) // No need to copy attributes for chart ranges
{
CopyTextAttrToClipHandler aFunc(rColumn.maCellTextAttrs);
sc::ParseBlock(maCellTextAttrs.begin(), maCellTextAttrs, aFunc, nRow1, nRow2);
}
rColumn.CellStorageModified();
}
void ScColumn::CopyStaticToDocument(
SCROW nRow1, SCROW nRow2, const SvNumberFormatterMergeMap& rMap, ScColumn& rDestCol )
{
if (nRow1 > nRow2)
return;
sc::ColumnBlockPosition aDestPos;
CopyCellTextAttrsToDocument(nRow1, nRow2, rDestCol);
CopyCellNotesToDocument(nRow1, nRow2, rDestCol);
// First, clear the destination column for the specified row range.
rDestCol.maCells.set_empty(nRow1, nRow2);
aDestPos.miCellPos = rDestCol.maCells.begin();
ScDocument& rDocument = GetDoc();
std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow1);
sc::CellStoreType::const_iterator it = aPos.first;
size_t nOffset = aPos.second;
size_t nDataSize = 0;
size_t nCurRow = nRow1;
for (; it != maCells.end() && nCurRow <= o3tl::make_unsigned(nRow2); ++it, nOffset = 0, nCurRow += nDataSize)
{
bool bLastBlock = false;
nDataSize = it->size - nOffset;
if (nCurRow + nDataSize - 1 > o3tl::make_unsigned(nRow2))
{
// Truncate the block to copy to clipboard.
nDataSize = nRow2 - nCurRow + 1;
bLastBlock = true;
}
switch (it->type)
{
case sc::element_type_numeric:
{
sc::numeric_block::const_iterator itData = sc::numeric_block::begin(*it->data);
std::advance(itData, nOffset);
sc::numeric_block::const_iterator itDataEnd = itData;
std::advance(itDataEnd, nDataSize);
aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nCurRow, itData, itDataEnd);
}
break;
case sc::element_type_string:
{
sc::string_block::const_iterator itData = sc::string_block::begin(*it->data);
std::advance(itData, nOffset);
sc::string_block::const_iterator itDataEnd = itData;
std::advance(itDataEnd, nDataSize);
aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nCurRow, itData, itDataEnd);
}
break;
case sc::element_type_edittext:
{
sc::edittext_block::const_iterator itData = sc::edittext_block::begin(*it->data);
std::advance(itData, nOffset);
sc::edittext_block::const_iterator itDataEnd = itData;
std::advance(itDataEnd, nDataSize);
// Convert to simple strings.
std::vector<svl::SharedString> aConverted;
aConverted.reserve(nDataSize);
for (; itData != itDataEnd; ++itData)
{
const EditTextObject& rObj = **itData;
svl::SharedString aSS = rDocument.GetSharedStringPool().intern(ScEditUtil::GetString(rObj, &rDocument));
aConverted.push_back(aSS);
}
aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nCurRow, aConverted.begin(), aConverted.end());
}
break;
case sc::element_type_formula:
{
sc::formula_block::const_iterator itData = sc::formula_block::begin(*it->data);
std::advance(itData, nOffset);
sc::formula_block::const_iterator itDataEnd = itData;
std::advance(itDataEnd, nDataSize);
// Interpret and convert to raw values.
for (SCROW i = 0; itData != itDataEnd; ++itData, ++i)
{
SCROW nRow = nCurRow + i;
ScFormulaCell& rFC = **itData;
if (rFC.GetDirty() && rDocument.GetAutoCalc())
rFC.Interpret();
if (rFC.GetErrCode() != FormulaError::NONE)
// Skip cells with error.
continue;
if (rFC.IsValue())
aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nRow, rFC.GetValue());
else
{
svl::SharedString aSS = rFC.GetString();
if (aSS.isValid())
aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nRow, aSS);
}
}
}
break;
default:
;
}
if (bLastBlock)
break;
}
// Don't forget to copy the number formats over. Charts may reference them.
for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
{
sal_uInt32 nNumFmt = GetNumberFormat(rDocument.GetNonThreadedContext(), nRow);
SvNumberFormatterMergeMap::const_iterator itNum = rMap.find(nNumFmt);
if (itNum != rMap.end())
nNumFmt = itNum->second;
rDestCol.SetNumberFormat(nRow, nNumFmt);
}
rDestCol.CellStorageModified();
}
void ScColumn::CopyCellToDocument( SCROW nSrcRow, SCROW nDestRow, ScColumn& rDestCol )
{
ScDocument& rDocument = GetDoc();
std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nSrcRow);
sc::CellStoreType::const_iterator it = aPos.first;
bool bSet = true;
switch (it->type)
{
case sc::element_type_numeric:
rDestCol.maCells.set(nDestRow, sc::numeric_block::at(*it->data, aPos.second));
break;
case sc::element_type_string:
rDestCol.maCells.set(nDestRow, sc::string_block::at(*it->data, aPos.second));
break;
case sc::element_type_edittext:
{
EditTextObject* p = sc::edittext_block::at(*it->data, aPos.second);
if (&rDocument == &rDestCol.GetDoc())
rDestCol.maCells.set(nDestRow, p->Clone().release());
else
rDestCol.maCells.set(nDestRow, ScEditUtil::Clone(*p, rDestCol.GetDoc()).release());
}
break;
case sc::element_type_formula:
{
ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
if (p->GetDirty() && rDocument.GetAutoCalc())
p->Interpret();
ScAddress aDestPos = p->aPos;
aDestPos.SetRow(nDestRow);
ScFormulaCell* pNew = new ScFormulaCell(*p, rDestCol.GetDoc(), aDestPos);
rDestCol.SetFormulaCell(nDestRow, pNew);
}
break;
case sc::element_type_empty:
default:
// empty
rDestCol.maCells.set_empty(nDestRow, nDestRow);
bSet = false;
}
if (bSet)
{
rDestCol.maCellTextAttrs.set(nDestRow, maCellTextAttrs.get<sc::CellTextAttr>(nSrcRow));
ScPostIt* pNote = maCellNotes.get<ScPostIt*>(nSrcRow);
if (pNote)
{
pNote = pNote->Clone(ScAddress(nCol, nSrcRow, nTab),
rDestCol.GetDoc(),
ScAddress(rDestCol.nCol, nDestRow, rDestCol.nTab),
false).release();
rDestCol.maCellNotes.set(nDestRow, pNote);
pNote->UpdateCaptionPos(ScAddress(rDestCol.nCol, nDestRow, rDestCol.nTab));
}
else
rDestCol.maCellNotes.set_empty(nDestRow, nDestRow);
}
else
{
rDestCol.maCellTextAttrs.set_empty(nDestRow, nDestRow);
rDestCol.maCellNotes.set_empty(nDestRow, nDestRow);
}
rDestCol.CellStorageModified();
}
namespace {
bool canCopyValue(const ScDocument& rDoc, const ScAddress& rPos, InsertDeleteFlags nFlags)
{
sal_uInt32 nNumIndex = rDoc.GetAttr(rPos, ATTR_VALUE_FORMAT)->GetValue();
SvNumFormatType nType = rDoc.GetFormatTable()->GetType(nNumIndex);
if ((nType == SvNumFormatType::DATE) || (nType == SvNumFormatType::TIME) || (nType == SvNumFormatType::DATETIME))
return ((nFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE);
return (nFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE;
}
class CopyAsLinkHandler
{
const ScColumn& mrSrcCol;
ScColumn& mrDestCol;
sc::ColumnBlockPosition maDestPos;
sc::ColumnBlockPosition* mpDestPos;
InsertDeleteFlags mnCopyFlags;
sc::StartListeningType meListenType;
void setDefaultAttrToDest(size_t nRow)
{
maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
maDestPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
}
void setDefaultAttrsToDest(size_t nRow, size_t nSize)
{
std::vector<sc::CellTextAttr> aAttrs(nSize); // default values
maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
maDestPos.miCellTextAttrPos, nRow, aAttrs.begin(), aAttrs.end());
}
ScFormulaCell* createRefCell(size_t nRow)
{
ScSingleRefData aRef;
aRef.InitAddress(ScAddress(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab())); // Absolute reference.
aRef.SetFlag3D(true);
ScTokenArray aArr(mrDestCol.GetDoc());
aArr.AddSingleReference(aRef);
return new ScFormulaCell(mrDestCol.GetDoc(), ScAddress(mrDestCol.GetCol(), nRow, mrDestCol.GetTab()), aArr);
}
void createRefBlock(const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize)
{
size_t nTopRow = aNode.position + nOffset;
for (size_t i = 0; i < nDataSize; ++i)
{
SCROW nRow = nTopRow + i;
mrDestCol.SetFormulaCell(maDestPos, nRow, createRefCell(nRow), meListenType);
}
setDefaultAttrsToDest(nTopRow, nDataSize);
}
public:
CopyAsLinkHandler(const ScColumn& rSrcCol, ScColumn& rDestCol, sc::ColumnBlockPosition* pDestPos, InsertDeleteFlags nCopyFlags) :
mrSrcCol(rSrcCol),
mrDestCol(rDestCol),
mpDestPos(pDestPos),
mnCopyFlags(nCopyFlags),
meListenType(sc::SingleCellListening)
{
if (mpDestPos)
maDestPos = *mpDestPos;
}
~CopyAsLinkHandler()
{
if (mpDestPos)
{
// Similar to CopyByCloneHandler, don't copy a singular iterator.
{
sc::ColumnBlockPosition aTempBlock;
mrDestCol.InitBlockPosition(aTempBlock);
maDestPos.miBroadcasterPos = aTempBlock.miBroadcasterPos;
}
*mpDestPos = maDestPos;
}
}
void setStartListening( bool b )
{
meListenType = b ? sc::SingleCellListening : sc::NoListening;
}
void operator() (const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize)
{
size_t nRow = aNode.position + nOffset;
if (mnCopyFlags & (InsertDeleteFlags::NOTE|InsertDeleteFlags::ADDNOTES))
{
bool bCloneCaption = (mnCopyFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
mrSrcCol.DuplicateNotes(nRow, nDataSize, mrDestCol, maDestPos, bCloneCaption);
}
switch (aNode.type)
{
case sc::element_type_numeric:
{
if ((mnCopyFlags & (InsertDeleteFlags::DATETIME|InsertDeleteFlags::VALUE)) == InsertDeleteFlags::NONE)
return;
sc::numeric_block::const_iterator it = sc::numeric_block::begin(*aNode.data);
std::advance(it, nOffset);
sc::numeric_block::const_iterator itEnd = it;
std::advance(itEnd, nDataSize);
ScAddress aSrcPos(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab());
for (; it != itEnd; ++it, aSrcPos.IncRow(), ++nRow)
{
if (!canCopyValue(mrSrcCol.GetDoc(), aSrcPos, mnCopyFlags))
continue;
maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, createRefCell(nRow));
setDefaultAttrToDest(nRow);
}
}
break;
case sc::element_type_string:
case sc::element_type_edittext:
{
if (!(mnCopyFlags & InsertDeleteFlags::STRING))
return;
createRefBlock(aNode, nOffset, nDataSize);
}
break;
case sc::element_type_formula:
{
if (!(mnCopyFlags & InsertDeleteFlags::FORMULA))
return;
createRefBlock(aNode, nOffset, nDataSize);
}
break;
default:
;
}
}
};
class CopyByCloneHandler
{
const ScColumn& mrSrcCol;
ScColumn& mrDestCol;
sc::ColumnBlockPosition maDestPos;
sc::ColumnBlockPosition* mpDestPos;
svl::SharedStringPool* mpSharedStringPool;
InsertDeleteFlags mnCopyFlags;
sc::StartListeningType meListenType;
ScCloneFlags mnFormulaCellCloneFlags;
void setDefaultAttrToDest(size_t nRow)
{
maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
maDestPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
}
void setDefaultAttrsToDest(size_t nRow, size_t nSize)
{
std::vector<sc::CellTextAttr> aAttrs(nSize); // default values
maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
maDestPos.miCellTextAttrPos, nRow, aAttrs.begin(), aAttrs.end());
}
void cloneFormulaCell(size_t nRow, ScFormulaCell& rSrcCell)
{
ScAddress aDestPos(mrDestCol.GetCol(), nRow, mrDestCol.GetTab());
bool bCloneValue = (mnCopyFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE;
bool bCloneDateTime = (mnCopyFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE;
bool bCloneString = (mnCopyFlags & InsertDeleteFlags::STRING) != InsertDeleteFlags::NONE;
bool bCloneSpecialBoolean = (mnCopyFlags & InsertDeleteFlags::SPECIAL_BOOLEAN) != InsertDeleteFlags::NONE;
bool bCloneFormula = (mnCopyFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE;
bool bForceFormula = false;
if (bCloneSpecialBoolean)
{
// 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;
}
}
if (bForceFormula || bCloneFormula)
{
// Clone as formula cell.
ScFormulaCell* pCell = new ScFormulaCell(rSrcCell, mrDestCol.GetDoc(), aDestPos, mnFormulaCellCloneFlags);
pCell->SetDirtyVar();
mrDestCol.SetFormulaCell(maDestPos, nRow, pCell, meListenType, rSrcCell.NeedsNumberFormat());
setDefaultAttrToDest(nRow);
return;
}
if (mrDestCol.GetDoc().IsUndo())
return;
if (bCloneValue)
{
FormulaError nErr = rSrcCell.GetErrCode();
if (nErr != FormulaError::NONE)
{
// error codes are cloned with values
ScFormulaCell* pErrCell = new ScFormulaCell(mrDestCol.GetDoc(), aDestPos);
pErrCell->SetErrCode(nErr);
mrDestCol.SetFormulaCell(maDestPos, nRow, pErrCell, meListenType);
setDefaultAttrToDest(nRow);
return;
}
}
if (bCloneValue || bCloneDateTime)
{
if (rSrcCell.IsValue())
{
if (canCopyValue(mrSrcCol.GetDoc(), ScAddress(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab()), mnCopyFlags))
{
maDestPos.miCellPos = mrDestCol.GetCellStore().set(
maDestPos.miCellPos, nRow, rSrcCell.GetValue());
setDefaultAttrToDest(nRow);
}
return;
}
}
if (!bCloneString)
return;
svl::SharedString aStr = rSrcCell.GetString();
if (aStr.isEmpty())
// Don't create empty string cells.
return;
if (rSrcCell.IsMultilineResult())
{
// Clone as an edit text object.
EditEngine& rEngine = mrDestCol.GetDoc().GetEditEngine();
rEngine.SetText(aStr.getString());
maDestPos.miCellPos =
mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, rEngine.CreateTextObject().release());
}
else
{
maDestPos.miCellPos =
mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, aStr);
}
setDefaultAttrToDest(nRow);
}
public:
CopyByCloneHandler(const ScColumn& rSrcCol, ScColumn& rDestCol, sc::ColumnBlockPosition* pDestPos,
InsertDeleteFlags nCopyFlags, svl::SharedStringPool* pSharedStringPool, bool bGlobalNamesToLocal) :
mrSrcCol(rSrcCol),
mrDestCol(rDestCol),
mpDestPos(pDestPos),
mpSharedStringPool(pSharedStringPool),
mnCopyFlags(nCopyFlags),
meListenType(sc::SingleCellListening),
mnFormulaCellCloneFlags(bGlobalNamesToLocal ? ScCloneFlags::NamesToLocal : ScCloneFlags::Default)
{
if (mpDestPos)
maDestPos = *mpDestPos;
}
~CopyByCloneHandler()
{
if (!mpDestPos)
return;
// If broadcasters were setup in the same column,
// maDestPos.miBroadcasterPos doesn't match
// mrDestCol.maBroadcasters because it is never passed anywhere.
// Assign a corresponding iterator before copying all over.
// Otherwise this may result in wrongly copying a singular
// iterator.
{
/* XXX Using a temporary ColumnBlockPosition just for
* initializing from ScColumn::maBroadcasters.begin() is ugly,
* on the other hand we don't want to expose
* ScColumn::maBroadcasters to the outer world and have a
* getter. */
sc::ColumnBlockPosition aTempBlock;
mrDestCol.InitBlockPosition(aTempBlock);
maDestPos.miBroadcasterPos = aTempBlock.miBroadcasterPos;
}
*mpDestPos = maDestPos;
}
void setStartListening( bool b )
{
meListenType = b ? sc::SingleCellListening : sc::NoListening;
}
void operator() (const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize)
{
size_t nRow = aNode.position + nOffset;
if (mnCopyFlags & (InsertDeleteFlags::NOTE|InsertDeleteFlags::ADDNOTES))
{
bool bCloneCaption = (mnCopyFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
mrSrcCol.DuplicateNotes(nRow, nDataSize, mrDestCol, maDestPos, bCloneCaption);
}
switch (aNode.type)
{
case sc::element_type_numeric:
{
if ((mnCopyFlags & (InsertDeleteFlags::DATETIME|InsertDeleteFlags::VALUE)) == InsertDeleteFlags::NONE)
return;
sc::numeric_block::const_iterator it = sc::numeric_block::begin(*aNode.data);
std::advance(it, nOffset);
sc::numeric_block::const_iterator itEnd = it;
std::advance(itEnd, nDataSize);
ScAddress aSrcPos(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab());
for (; it != itEnd; ++it, aSrcPos.IncRow(), ++nRow)
{
if (!canCopyValue(mrSrcCol.GetDoc(), aSrcPos, mnCopyFlags))
continue;
maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, *it);
setDefaultAttrToDest(nRow);
}
}
break;
case sc::element_type_string:
{
if (!(mnCopyFlags & InsertDeleteFlags::STRING))
return;
sc::string_block::const_iterator it = sc::string_block::begin(*aNode.data);
std::advance(it, nOffset);
sc::string_block::const_iterator itEnd = it;
std::advance(itEnd, nDataSize);
for (; it != itEnd; ++it, ++nRow)
{
const svl::SharedString& rStr = *it;
if (rStr.isEmpty())
{
// String cell with empty value is used to special-case cell value removal.
maDestPos.miCellPos = mrDestCol.GetCellStore().set_empty(
maDestPos.miCellPos, nRow, nRow);
maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set_empty(
maDestPos.miCellTextAttrPos, nRow, nRow);
}
else
{
if (mpSharedStringPool)
{
// Re-intern the string if source is a different document.
svl::SharedString aInterned = mpSharedStringPool->intern( rStr.getString());
maDestPos.miCellPos =
mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, aInterned);
}
else
{
maDestPos.miCellPos =
mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, rStr);
}
setDefaultAttrToDest(nRow);
}
}
}
break;
case sc::element_type_edittext:
{
if (!(mnCopyFlags & InsertDeleteFlags::STRING))
return;
sc::edittext_block::const_iterator it = sc::edittext_block::begin(*aNode.data);
std::advance(it, nOffset);
sc::edittext_block::const_iterator itEnd = it;
std::advance(itEnd, nDataSize);
std::vector<EditTextObject*> aCloned;
aCloned.reserve(nDataSize);
for (; it != itEnd; ++it)
aCloned.push_back(ScEditUtil::Clone(**it, mrDestCol.GetDoc()).release());
maDestPos.miCellPos = mrDestCol.GetCellStore().set(
maDestPos.miCellPos, nRow, aCloned.begin(), aCloned.end());
setDefaultAttrsToDest(nRow, nDataSize);
}
break;
case sc::element_type_formula:
{
sc::formula_block::const_iterator it = sc::formula_block::begin(*aNode.data);
std::advance(it, nOffset);
sc::formula_block::const_iterator itEnd = it;
std::advance(itEnd, nDataSize);
sc::DelayStartListeningFormulaCells startDelay(mrDestCol); // disabled
if(nDataSize > 1024 && (mnCopyFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE)
{
// If the column to be replaced contains a long formula group (tdf#102364), there can
// be so many listeners in a single vector that the quadratic cost of repeatedly removing
// the first element becomes very high. Optimize this by removing them in one go.
sc::EndListeningContext context(mrDestCol.GetDoc());
mrDestCol.EndListeningFormulaCells( context, nRow, nRow + nDataSize - 1, nullptr, nullptr );
// There can be a similar problem with starting to listen to cells repeatedly (tdf#133302).
// Delay it.
startDelay.set();
}
for (; it != itEnd; ++it, ++nRow)
cloneFormulaCell(nRow, **it);
}
break;
default:
;
}
}
};
}
void ScColumn::CopyToColumn(
sc::CopyToDocContext& rCxt,
SCROW nRow1, SCROW nRow2, InsertDeleteFlags nFlags, bool bMarked, ScColumn& rColumn,
const ScMarkData* pMarkData, bool bAsLink, bool bGlobalNamesToLocal) const
{
if (bMarked)
{
SCROW nStart, nEnd;
if (pMarkData && pMarkData->IsMultiMarked())
{
ScMultiSelIter aIter( pMarkData->GetMultiSelData(), nCol );
while ( aIter.Next( nStart, nEnd ) && nStart <= nRow2 )
{
if ( nEnd >= nRow1 )
CopyToColumn(rCxt, std::max(nRow1,nStart), std::min(nRow2,nEnd),
nFlags, false, rColumn, pMarkData, bAsLink );
}
}
else
{
OSL_FAIL("CopyToColumn: bMarked, but no mark");
}
return;
}
if ( (nFlags & InsertDeleteFlags::ATTRIB) != InsertDeleteFlags::NONE )
{
if ( (nFlags & InsertDeleteFlags::STYLES) != InsertDeleteFlags::STYLES )
{ // keep the StyleSheets in the target document
// e.g. DIF and RTF Clipboard-Import
for ( SCROW nRow = nRow1; nRow <= nRow2; nRow++ )
{
const ScStyleSheet* pStyle(rColumn.pAttrArray->GetPattern( nRow )->GetStyleSheet());
ScPatternAttr* pNewPattern(new ScPatternAttr(*pAttrArray->GetPattern(nRow)));
pNewPattern->SetStyleSheet(const_cast<ScStyleSheet*>(pStyle));
rColumn.pAttrArray->SetPattern(nRow, CellAttributeHolder(pNewPattern, true));
}
}
else
pAttrArray->CopyArea( nRow1, nRow2, 0, *rColumn.pAttrArray);
}
if ((nFlags & InsertDeleteFlags::CONTENTS) == InsertDeleteFlags::NONE)
return;
if (bAsLink)
{
CopyAsLinkHandler aFunc(*this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol), nFlags);
aFunc.setStartListening(rCxt.isStartListening());
sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
}
else
{
// Compare the ScDocumentPool* to determine if we are copying
// within the same document. If not, re-intern shared strings.
svl::SharedStringPool* pSharedStringPool =
(GetDoc().GetPool() != rColumn.GetDoc().GetPool()) ?
&rColumn.GetDoc().GetSharedStringPool() : nullptr;
CopyByCloneHandler aFunc(*this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol), nFlags,
pSharedStringPool, bGlobalNamesToLocal);
aFunc.setStartListening(rCxt.isStartListening());
sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
}
rColumn.CellStorageModified();
}
void ScColumn::UndoToColumn(
sc::CopyToDocContext& rCxt, SCROW nRow1, SCROW nRow2, InsertDeleteFlags nFlags, bool bMarked,
ScColumn& rColumn ) const
{
if (nRow1 > 0)
CopyToColumn(rCxt, 0, nRow1-1, InsertDeleteFlags::FORMULA, false, rColumn);
CopyToColumn(rCxt, nRow1, nRow2, nFlags, bMarked, rColumn); //TODO: bMarked ????
if (nRow2 < GetDoc().MaxRow())
CopyToColumn(rCxt, nRow2+1, GetDoc().MaxRow(), InsertDeleteFlags::FORMULA, false, rColumn);
}
void ScColumn::CopyUpdated( const ScColumn* pPosCol, ScColumn& rDestCol ) const
{
// Copy cells from this column to the destination column only for those
// rows that are present in the position column (pPosCol).
// First, mark all the non-empty cell ranges from the position column.
sc::SingleColumnSpanSet aRangeSet(GetDoc().GetSheetLimits());
if(pPosCol)
aRangeSet.scan(*pPosCol);
// Now, copy cells from this column to the destination column for those
// marked row ranges.
sc::SingleColumnSpanSet::SpansType aRanges;
aRangeSet.getSpans(aRanges);
CopyToClipHandler aFunc(GetDoc(), *this, rDestCol, nullptr);
sc::CellStoreType::const_iterator itPos = maCells.begin();
for (const auto& rRange : aRanges)
itPos = sc::ParseBlock(itPos, maCells, aFunc, rRange.mnRow1, rRange.mnRow2);
rDestCol.CellStorageModified();
}
void ScColumn::CopyScenarioFrom( const ScColumn& rSrcCol )
{
// This is the scenario table, the data is copied into it
ScDocument& rDocument = GetDoc();
ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc().MaxRow(), &rDocument.getCellAttributeHelper().getDefaultCellAttribute() );
SCROW nStart = -1, nEnd = -1;
const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd );
while (pPattern)
{
if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() )
{
DeleteArea( nStart, nEnd, InsertDeleteFlags::CONTENTS );
sc::CopyToDocContext aCxt(rDocument);
rSrcCol.
CopyToColumn(aCxt, nStart, nEnd, InsertDeleteFlags::CONTENTS, false, *this);
// UpdateUsed not needed, already done in TestCopyScenario (obsolete comment ?)
sc::RefUpdateContext aRefCxt(rDocument);
aRefCxt.meMode = URM_COPY;
aRefCxt.maRange = ScRange(nCol, nStart, nTab, nCol, nEnd, nTab);
aRefCxt.mnTabDelta = nTab - rSrcCol.nTab;
UpdateReferenceOnCopy(aRefCxt);
UpdateCompile();
}
pPattern = aAttrIter.Next( nStart, nEnd );
}
}
void ScColumn::CopyScenarioTo( ScColumn& rDestCol ) const
{
// This is the scenario table, the data is copied to the other
ScDocument& rDocument = GetDoc();
ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc().MaxRow(), &rDocument.getCellAttributeHelper().getDefaultCellAttribute() );
SCROW nStart = -1, nEnd = -1;
const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd );
while (pPattern)
{
if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() )
{
rDestCol.DeleteArea( nStart, nEnd, InsertDeleteFlags::CONTENTS );
sc::CopyToDocContext aCxt(rDestCol.GetDoc());
CopyToColumn(aCxt, nStart, nEnd, InsertDeleteFlags::CONTENTS, false, rDestCol);
sc::RefUpdateContext aRefCxt(rDocument);
aRefCxt.meMode = URM_COPY;
aRefCxt.maRange = ScRange(rDestCol.nCol, nStart, rDestCol.nTab, rDestCol.nCol, nEnd, rDestCol.nTab);
aRefCxt.mnTabDelta = rDestCol.nTab - nTab;
rDestCol.UpdateReferenceOnCopy(aRefCxt);
rDestCol.UpdateCompile();
}
pPattern = aAttrIter.Next( nStart, nEnd );
}
}
bool ScColumn::TestCopyScenarioTo( const ScColumn& rDestCol ) const
{
bool bOk = true;
ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc().MaxRow(), &GetDoc().getCellAttributeHelper().getDefaultCellAttribute() );
SCROW nStart = 0, nEnd = 0;
const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd );
while (pPattern && bOk)
{
if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() )
if ( rDestCol.pAttrArray->HasAttrib( nStart, nEnd, HasAttrFlags::Protected ) )
bOk = false;
pPattern = aAttrIter.Next( nStart, nEnd );
}
return bOk;
}
void ScColumn::MarkScenarioIn( ScMarkData& rDestMark ) const
{
ScRange aRange( nCol, 0, nTab );
ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc().MaxRow(), &GetDoc().getCellAttributeHelper().getDefaultCellAttribute() );
SCROW nStart = -1, nEnd = -1;
const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd );
while (pPattern)
{
if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() )
{
aRange.aStart.SetRow( nStart );
aRange.aEnd.SetRow( nEnd );
rDestMark.SetMultiMarkArea( aRange );
}
pPattern = aAttrIter.Next( nStart, nEnd );
}
}
namespace {
void resetColumnPosition(sc::CellStoreType& rCells, SCCOL nCol)
{
for (auto& rCellItem : rCells)
{
if (rCellItem.type != sc::element_type_formula)
continue;
sc::formula_block::iterator itCell = sc::formula_block::begin(*rCellItem.data);
sc::formula_block::iterator itCellEnd = sc::formula_block::end(*rCellItem.data);
for (; itCell != itCellEnd; ++itCell)
{
ScFormulaCell& rCell = **itCell;
rCell.aPos.SetCol(nCol);
}
}
}
class NoteCaptionUpdater
{
const ScDocument& m_rDocument;
const ScAddress m_aAddress; // 'incomplete' address consisting of tab, column
bool m_bUpdateCaptionPos; // false if we want to skip updating the caption pos, only useful in kit mode
bool m_bAddressChanged; // false if the cell anchor address is unchanged
public:
NoteCaptionUpdater(const ScDocument& rDocument, const ScAddress& rPos, bool bUpdateCaptionPos, bool bAddressChanged)
: m_rDocument(rDocument)
, m_aAddress(rPos)
, m_bUpdateCaptionPos(bUpdateCaptionPos)
, m_bAddressChanged(bAddressChanged)
{
}
void operator() ( size_t nRow, ScPostIt* p )
{
// Create a 'complete' address object
ScAddress aAddr(m_aAddress);
aAddr.SetRow(nRow);
if (m_bUpdateCaptionPos)
p->UpdateCaptionPos(aAddr);
// Notify our LOK clients
if (m_bAddressChanged)
ScDocShell::LOKCommentNotify(LOKCommentNotificationType::Modify, m_rDocument, aAddr, p);
}
};
}
void ScColumn::UpdateNoteCaptions( SCROW nRow1, SCROW nRow2, bool bAddressChanged )
{
ScAddress aAddr(nCol, 0, nTab);
NoteCaptionUpdater aFunc(GetDoc(), aAddr, true, bAddressChanged);
sc::ProcessNote(maCellNotes.begin(), maCellNotes, nRow1, nRow2, aFunc);
}
void ScColumn::CommentNotifyAddressChange( SCROW nRow1, SCROW nRow2 )
{
ScAddress aAddr(nCol, 0, nTab);
NoteCaptionUpdater aFunc(GetDoc(), aAddr, false, true);
sc::ProcessNote(maCellNotes.begin(), maCellNotes, nRow1, nRow2, aFunc);
}
void ScColumn::UpdateDrawObjects(std::vector<std::vector<SdrObject*>>& pObjects, SCROW nRowStart, SCROW nRowEnd)
{
assert(static_cast<int>(pObjects.size()) >= nRowEnd - nRowStart + 1);
int nObj = 0;
for (SCROW nCurrentRow = nRowStart; nCurrentRow <= nRowEnd; nCurrentRow++, nObj++)
{
if (pObjects[nObj].empty())
continue; // No draw objects in this row
UpdateDrawObjectsForRow(pObjects[nObj], nCol, nCurrentRow);
}
}
void ScColumn::UpdateDrawObjectsForRow( std::vector<SdrObject*>& pObjects, SCCOL nTargetCol, SCROW nTargetRow )
{
for (auto &pObject : pObjects)
{
ScAddress aNewAddress(nTargetCol, nTargetRow, nTab);
// Update draw object according to new anchor
ScDrawLayer* pDrawLayer = GetDoc().GetDrawLayer();
if (pDrawLayer)
pDrawLayer->MoveObject(pObject, aNewAddress);
}
}
bool ScColumn::IsDrawObjectsEmptyBlock(SCROW nStartRow, SCROW nEndRow) const
{
ScDrawLayer* pDrawLayer = GetDoc().GetDrawLayer();
if (!pDrawLayer)
return true;
ScRange aRange(nCol, nStartRow, nTab, nCol, nEndRow, nTab);
return !pDrawLayer->HasObjectsAnchoredInRange(aRange);
}
void ScColumn::SwapCol(ScColumn& rCol)
{
maBroadcasters.swap(rCol.maBroadcasters);
maCells.swap(rCol.maCells);
maCellTextAttrs.swap(rCol.maCellTextAttrs);
maCellNotes.swap(rCol.maCellNotes);
maSparklines.swap(rCol.maSparklines);
// Swap all CellStoreEvent mdds event_func related.
maCells.event_handler().swap(rCol.maCells.event_handler());
maCellNotes.event_handler().swap(rCol.maCellNotes.event_handler());
std::swap( mnBlkCountFormula, rCol.mnBlkCountFormula);
std::swap(mnBlkCountCellNotes, rCol.mnBlkCountCellNotes);
// notes update caption
UpdateNoteCaptions(0, GetDoc().MaxRow());
rCol.UpdateNoteCaptions(0, GetDoc().MaxRow());
std::swap(pAttrArray, rCol.pAttrArray);
// AttrArray needs to have the right column number
pAttrArray->SetCol(nCol);
rCol.pAttrArray->SetCol(rCol.nCol);
// Reset column positions in formula cells.
resetColumnPosition(maCells, nCol);
resetColumnPosition(rCol.maCells, rCol.nCol);
CellStorageModified();
rCol.CellStorageModified();
}
void ScColumn::MoveTo(SCROW nStartRow, SCROW nEndRow, ScColumn& rCol)
{
pAttrArray->MoveTo(nStartRow, nEndRow, *rCol.pAttrArray);
// Mark the non-empty cells within the specified range, for later broadcasting.
sc::SingleColumnSpanSet aNonEmpties(GetDoc().GetSheetLimits());
aNonEmpties.scan(*this, nStartRow, nEndRow);
sc::SingleColumnSpanSet::SpansType aRanges;
aNonEmpties.getSpans(aRanges);
// Split the formula grouping at the top and bottom boundaries.
sc::CellStoreType::position_type aPos = maCells.position(nStartRow);
sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
if (GetDoc().ValidRow(nEndRow+1))
{
aPos = maCells.position(aPos.first, nEndRow+1);
sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
}
// Do the same with the destination column.
aPos = rCol.maCells.position(nStartRow);
sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
if (GetDoc().ValidRow(nEndRow+1))
{
aPos = rCol.maCells.position(aPos.first, nEndRow+1);
sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
}
// Move the broadcasters to the destination column.
maBroadcasters.transfer(nStartRow, nEndRow, rCol.maBroadcasters, nStartRow);
maCells.transfer(nStartRow, nEndRow, rCol.maCells, nStartRow);
maCellTextAttrs.transfer(nStartRow, nEndRow, rCol.maCellTextAttrs, nStartRow);
// move the notes to the destination column
maCellNotes.transfer(nStartRow, nEndRow, rCol.maCellNotes, nStartRow);
UpdateNoteCaptions(0, GetDoc().MaxRow());
// Re-group transferred formula cells.
aPos = rCol.maCells.position(nStartRow);
sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
if (GetDoc().ValidRow(nEndRow+1))
{
aPos = rCol.maCells.position(aPos.first, nEndRow+1);
sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
}
CellStorageModified();
rCol.CellStorageModified();
// Broadcast on moved ranges. Area-broadcast only.
ScDocument& rDocument = GetDoc();
ScHint aHint(SfxHintId::ScDataChanged, ScAddress(nCol, 0, nTab));
for (const auto& rRange : aRanges)
{
for (SCROW nRow = rRange.mnRow1; nRow <= rRange.mnRow2; ++nRow)
{
aHint.SetAddressRow(nRow);
rDocument.AreaBroadcast(aHint);
}
}
}
namespace {
class SharedTopFormulaCellPicker
{
public:
SharedTopFormulaCellPicker() = default;
SharedTopFormulaCellPicker(SharedTopFormulaCellPicker const &) = default;
SharedTopFormulaCellPicker(SharedTopFormulaCellPicker &&) = default;
SharedTopFormulaCellPicker & operator =(SharedTopFormulaCellPicker const &) = default;
SharedTopFormulaCellPicker & operator =(SharedTopFormulaCellPicker &&) = default;
virtual ~SharedTopFormulaCellPicker() {}
void operator() ( sc::CellStoreType::value_type& node )
{
if (node.type != sc::element_type_formula)
return;
size_t nTopRow = node.position;
sc::formula_block::iterator itBeg = sc::formula_block::begin(*node.data);
sc::formula_block::iterator itEnd = sc::formula_block::end(*node.data);
// Only pick shared formula cells that are the top cells of their
// respective shared ranges.
for (sc::formula_block::iterator it = itBeg; it != itEnd; ++it)
{
ScFormulaCell* pCell = *it;
size_t nRow = nTopRow + std::distance(itBeg, it);
if (!pCell->IsShared())
{
processNonShared(pCell, nRow);
continue;
}
if (pCell->IsSharedTop())
{
ScFormulaCell** pp = &(*it);
SCROW nCellLen = pCell->GetSharedLength();
assert(nCellLen > 0);
processSharedTop(pp, nRow, nCellLen);
// Move to the last cell in the group, to get incremented to
// the next cell in the next iteration.
size_t nOffsetToLast = nCellLen - 1;
std::advance(it, nOffsetToLast);
}
}
}
virtual void processNonShared( ScFormulaCell* /*pCell*/, size_t /*nRow*/ ) {}
virtual void processSharedTop( ScFormulaCell** /*ppCells*/, size_t /*nRow*/, size_t /*nLength*/ ) {}
};
class UpdateRefOnCopy
{
const sc::RefUpdateContext& mrCxt;
ScDocument* mpUndoDoc;
bool mbUpdated;
public:
UpdateRefOnCopy(const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc) :
mrCxt(rCxt), mpUndoDoc(pUndoDoc), mbUpdated(false) {}
bool isUpdated() const { return mbUpdated; }
void operator() (sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
{
if (node.type != sc::element_type_formula)
return;
sc::formula_block::iterator it = sc::formula_block::begin(*node.data);
std::advance(it, nOffset);
sc::formula_block::iterator itEnd = it;
std::advance(itEnd, nDataSize);
for (; it != itEnd; ++it)
{
ScFormulaCell& rCell = **it;
mbUpdated |= rCell.UpdateReference(mrCxt, mpUndoDoc);
}
}
};
class UpdateRefOnNonCopy
{
SCCOL mnCol;
SCROW mnTab;
const sc::RefUpdateContext* mpCxt;
ScDocument* mpUndoDoc;
bool mbUpdated;
bool mbClipboardSource;
void recompileTokenArray( ScFormulaCell& rTopCell )
{
// We need to re-compile the token array when a range name is
// modified, to correctly reflect the new references in the
// name.
ScCompiler aComp(mpCxt->mrDoc, rTopCell.aPos, *rTopCell.GetCode(), mpCxt->mrDoc.GetGrammar(),
true, rTopCell.GetMatrixFlag() != ScMatrixMode::NONE);
aComp.CompileTokenArray();
}
void updateRefOnShift( sc::FormulaGroupEntry& rGroup )
{
if (!rGroup.mbShared)
{
ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab);
mbUpdated |= rGroup.mpCell->UpdateReferenceOnShift(*mpCxt, mpUndoDoc, &aUndoPos);
return;
}
// Update references of a formula group.
ScFormulaCell** pp = rGroup.mpCells;
ScFormulaCell** ppEnd = pp + rGroup.mnLength;
ScFormulaCell* pTop = *pp;
ScTokenArray* pCode = pTop->GetCode();
ScTokenArray aOldCode(pCode->CloneValue());
ScAddress aOldPos = pTop->aPos;
// Run this before the position gets updated.
sc::RefUpdateResult aRes = pCode->AdjustReferenceOnShift(*mpCxt, aOldPos);
bool bGroupShifted = false;
if (pTop->UpdatePosOnShift(*mpCxt))
{
ScAddress aErrorPos( ScAddress::UNINITIALIZED );
// Update the positions of all formula cells.
for (++pp; pp != ppEnd; ++pp) // skip the top cell.
{
ScFormulaCell* pFC = *pp;
if (!pFC->aPos.Move(mpCxt->mnColDelta, mpCxt->mnRowDelta, mpCxt->mnTabDelta,
aErrorPos, mpCxt->mrDoc))
{
assert(!"can't move formula cell");
}
}
if (pCode->IsRecalcModeOnRefMove())
aRes.mbValueChanged = true;
// FormulaGroupAreaListener (contrary to ScBroadcastArea) is not
// updated but needs to be re-setup, else at least its mpColumn
// would indicate the old column to collect cells from. tdf#129396
/* TODO: investigate if that could be short-cut to avoid all the
* EndListeningTo() / StartListeningTo() overhead and is really
* only necessary when shifting the column, not also when shifting
* rows. */
bGroupShifted = true;
}
else if (aRes.mbReferenceModified && pCode->IsRecalcModeOnRefMove())
{
// The cell itself hasn't shifted. But it may have ROW or COLUMN
// referencing another cell that has.
aRes.mbValueChanged = true;
}
if (aRes.mbNameModified)
recompileTokenArray(*pTop);
if (aRes.mbReferenceModified || aRes.mbNameModified || bGroupShifted)
{
sc::EndListeningContext aEndCxt(mpCxt->mrDoc, &aOldCode);
aEndCxt.setPositionDelta(
ScAddress(-mpCxt->mnColDelta, -mpCxt->mnRowDelta, -mpCxt->mnTabDelta));
for (pp = rGroup.mpCells; pp != ppEnd; ++pp)
{
ScFormulaCell* p = *pp;
p->EndListeningTo(aEndCxt);
p->SetNeedsListening(true);
}
mbUpdated = true;
fillUndoDoc(aOldPos, rGroup.mnLength, aOldCode);
}
if (aRes.mbValueChanged)
{
for (pp = rGroup.mpCells; pp != ppEnd; ++pp)
{
ScFormulaCell* p = *pp;
p->SetNeedsDirty(true);
}
}
}
void updateRefOnMove( sc::FormulaGroupEntry& rGroup )
{
if (!rGroup.mbShared)
{
ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab);
mbUpdated |= rGroup.mpCell->UpdateReferenceOnMove(*mpCxt, mpUndoDoc, &aUndoPos);
return;
}
// Update references of a formula group.
ScFormulaCell** pp = rGroup.mpCells;
ScFormulaCell** ppEnd = pp + rGroup.mnLength;
ScFormulaCell* pTop = *pp;
ScTokenArray* pCode = pTop->GetCode();
ScTokenArray aOldCode(pCode->CloneValue());
ScAddress aPos = pTop->aPos;
ScAddress aOldPos = aPos;
bool bCellMoved;
if (mpCxt->maRange.Contains(aPos))
{
bCellMoved = true;
// The cell is being moved or copied to a new position. The
// position has already been updated prior to this call.
// Determine its original position before the move which will be
// used to adjust relative references later.
aOldPos.Set(
aPos.Col() - mpCxt->mnColDelta,
aPos.Row() - mpCxt->mnRowDelta,
aPos.Tab() - mpCxt->mnTabDelta);
}
else
{
bCellMoved = false;
}
bool bRecalcOnMove = pCode->IsRecalcModeOnRefMove();
if (bRecalcOnMove)
bRecalcOnMove = aPos != aOldPos;
sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMove(*mpCxt, aOldPos, aPos);
if (!(aRes.mbReferenceModified || aRes.mbNameModified || bRecalcOnMove))
return;
sc::AutoCalcSwitch aACSwitch(mpCxt->mrDoc, false);
if (aRes.mbNameModified)
recompileTokenArray(*pTop);
// Perform end-listening, start-listening, and dirtying on all
// formula cells in the group.
// Make sure that the start and end listening contexts share the
// same block position set, else an invalid iterator may ensue.
const auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(mpCxt->mrDoc);
sc::StartListeningContext aStartCxt(mpCxt->mrDoc, pPosSet);
sc::EndListeningContext aEndCxt(mpCxt->mrDoc, pPosSet, &aOldCode);
aEndCxt.setPositionDelta(
ScAddress(-mpCxt->mnColDelta, -mpCxt->mnRowDelta, -mpCxt->mnTabDelta));
for (; pp != ppEnd; ++pp)
{
ScFormulaCell* p = *pp;
p->EndListeningTo(aEndCxt);
p->StartListeningTo(aStartCxt);
p->SetDirty();
}
mbUpdated = true;
// Move from clipboard is Cut&Paste, then do not copy the original
// positions' formula cells to the Undo document.
if (!mbClipboardSource || !bCellMoved)
fillUndoDoc(aOldPos, rGroup.mnLength, aOldCode);
}
void fillUndoDoc( const ScAddress& rOldPos, SCROW nLength, const ScTokenArray& rOldCode )
{
if (!mpUndoDoc || nLength <= 0)
return;
// Insert the old formula group into the undo document.
ScAddress aUndoPos = rOldPos;
ScFormulaCell* pFC = new ScFormulaCell(*mpUndoDoc, aUndoPos, rOldCode.Clone());
if (nLength == 1)
{
mpUndoDoc->SetFormulaCell(aUndoPos, pFC);
return;
}
std::vector<ScFormulaCell*> aCells;
aCells.reserve(nLength);
ScFormulaCellGroupRef xGroup = pFC->CreateCellGroup(nLength, false);
aCells.push_back(pFC);
aUndoPos.IncRow();
for (SCROW i = 1; i < nLength; ++i, aUndoPos.IncRow())
{
pFC = new ScFormulaCell(*mpUndoDoc, aUndoPos, xGroup);
aCells.push_back(pFC);
}
if (!mpUndoDoc->SetFormulaCells(rOldPos, aCells))
// Insertion failed. Delete all formula cells.
std::for_each(aCells.begin(), aCells.end(), std::default_delete<ScFormulaCell>());
}
public:
UpdateRefOnNonCopy(
SCCOL nCol, SCTAB nTab, const sc::RefUpdateContext* pCxt,
ScDocument* pUndoDoc) :
mnCol(nCol), mnTab(nTab), mpCxt(pCxt),
mpUndoDoc(pUndoDoc), mbUpdated(false),
mbClipboardSource(pCxt->mrDoc.IsClipboardSource()){}
void operator() ( sc::FormulaGroupEntry& rGroup )
{
switch (mpCxt->meMode)
{
case URM_INSDEL:
updateRefOnShift(rGroup);
return;
case URM_MOVE:
updateRefOnMove(rGroup);
return;
default:
;
}
if (rGroup.mbShared)
{
ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab);
ScFormulaCell** pp = rGroup.mpCells;
ScFormulaCell** ppEnd = pp + rGroup.mnLength;
for (; pp != ppEnd; ++pp, aUndoPos.IncRow())
{
ScFormulaCell* p = *pp;
mbUpdated |= p->UpdateReference(*mpCxt, mpUndoDoc, &aUndoPos);
}
}
else
{
ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab);
mbUpdated |= rGroup.mpCell->UpdateReference(*mpCxt, mpUndoDoc, &aUndoPos);
}
}
bool isUpdated() const { return mbUpdated; }
};
class UpdateRefGroupBoundChecker : public SharedTopFormulaCellPicker
{
const sc::RefUpdateContext& mrCxt;
std::vector<SCROW>& mrBounds;
public:
UpdateRefGroupBoundChecker(const sc::RefUpdateContext& rCxt, std::vector<SCROW>& rBounds) :
mrCxt(rCxt), mrBounds(rBounds) {}
virtual void processSharedTop( ScFormulaCell** ppCells, size_t /*nRow*/, size_t /*nLength*/ ) override
{
// Check its tokens and record its reference boundaries.
ScFormulaCell& rCell = **ppCells;
const ScTokenArray& rCode = *rCell.GetCode();
rCode.CheckRelativeReferenceBounds(
mrCxt, rCell.aPos, rCell.GetSharedLength(), mrBounds);
}
};
class UpdateRefExpandGroupBoundChecker : public SharedTopFormulaCellPicker
{
const sc::RefUpdateContext& mrCxt;
std::vector<SCROW>& mrBounds;
public:
UpdateRefExpandGroupBoundChecker(const sc::RefUpdateContext& rCxt, std::vector<SCROW>& rBounds) :
mrCxt(rCxt), mrBounds(rBounds) {}
virtual void processSharedTop( ScFormulaCell** ppCells, size_t /*nRow*/, size_t /*nLength*/ ) override
{
// Check its tokens and record its reference boundaries.
ScFormulaCell& rCell = **ppCells;
const ScTokenArray& rCode = *rCell.GetCode();
rCode.CheckExpandReferenceBounds(
mrCxt, rCell.aPos, rCell.GetSharedLength(), mrBounds);
}
};
class FormulaGroupPicker : public SharedTopFormulaCellPicker
{
std::vector<sc::FormulaGroupEntry>& mrGroups;
public:
explicit FormulaGroupPicker( std::vector<sc::FormulaGroupEntry>& rGroups ) : mrGroups(rGroups) {}
virtual void processNonShared( ScFormulaCell* pCell, size_t nRow ) override
{
mrGroups.emplace_back(pCell, nRow);
}
virtual void processSharedTop( ScFormulaCell** ppCells, size_t nRow, size_t nLength ) override
{
mrGroups.emplace_back(ppCells, nRow, nLength);
}
};
}
bool ScColumn::UpdateReferenceOnCopy( sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc )
{
// When copying, the range equals the destination range where cells
// are pasted, and the dx, dy, dz refer to the distance from the
// source range.
UpdateRefOnCopy aHandler(rCxt, pUndoDoc);
sc::ColumnBlockPosition* blockPos = rCxt.getBlockPosition(nTab, nCol);
sc::CellStoreType::position_type aPos = blockPos
? maCells.position(blockPos->miCellPos, rCxt.maRange.aStart.Row())
: maCells.position(rCxt.maRange.aStart.Row());
sc::ProcessBlock(aPos.first, maCells, aHandler, rCxt.maRange.aStart.Row(), rCxt.maRange.aEnd.Row());
// The formula groups at the top and bottom boundaries are expected to
// have been split prior to this call. Here, we only do the joining.
sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
if (rCxt.maRange.aEnd.Row() < GetDoc().MaxRow())
{
aPos = maCells.position(aPos.first, rCxt.maRange.aEnd.Row()+1);
sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
}
return aHandler.isUpdated();
}
bool ScColumn::UpdateReference( sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc )
{
if (IsEmptyData() || GetDoc().IsClipOrUndo())
// Cells in this column are all empty, or clip or undo doc. No update needed.
return false;
if (rCxt.meMode == URM_COPY)
return UpdateReferenceOnCopy(rCxt, pUndoDoc);
std::vector<SCROW> aBounds;
bool bThisColShifted = (rCxt.maRange.aStart.Tab() <= nTab && nTab <= rCxt.maRange.aEnd.Tab() &&
rCxt.maRange.aStart.Col() <= nCol && nCol <= rCxt.maRange.aEnd.Col());
if (bThisColShifted)
{
// Cells in this column is being shifted. Split formula grouping at
// the top and bottom boundaries before they get shifted.
// Also, for deleted rows split at the top of the deleted area to adapt
// the affected group length.
SCROW nSplitPos;
if (rCxt.mnRowDelta < 0)
{
nSplitPos = rCxt.maRange.aStart.Row() + rCxt.mnRowDelta;
if (GetDoc().ValidRow(nSplitPos))
aBounds.push_back(nSplitPos);
}
nSplitPos = rCxt.maRange.aStart.Row();
if (GetDoc().ValidRow(nSplitPos))
{
aBounds.push_back(nSplitPos);
nSplitPos = rCxt.maRange.aEnd.Row() + 1;
if (GetDoc().ValidRow(nSplitPos))
aBounds.push_back(nSplitPos);
}
}
// Check the row positions at which the group must be split per relative
// references.
{
UpdateRefGroupBoundChecker aBoundChecker(rCxt, aBounds);
std::for_each(maCells.begin(), maCells.end(), std::move(aBoundChecker));
}
// If expand reference edges is on, splitting groups may happen anywhere
// where a reference points to an adjacent row of the insertion.
if (rCxt.mnRowDelta > 0 && rCxt.mrDoc.IsExpandRefs())
{
UpdateRefExpandGroupBoundChecker aExpandChecker(rCxt, aBounds);
std::for_each(maCells.begin(), maCells.end(), std::move(aExpandChecker));
}
// Do the actual splitting.
const bool bSplit = sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
// Collect all formula groups.
std::vector<sc::FormulaGroupEntry> aGroups = GetFormulaGroupEntries();
// Process all collected formula groups.
UpdateRefOnNonCopy aHandler(nCol, nTab, &rCxt, pUndoDoc);
aHandler = std::for_each(aGroups.begin(), aGroups.end(), aHandler);
if (bSplit || aHandler.isUpdated())
rCxt.maRegroupCols.set(nTab, nCol);
return aHandler.isUpdated();
}
std::vector<sc::FormulaGroupEntry> ScColumn::GetFormulaGroupEntries()
{
std::vector<sc::FormulaGroupEntry> aGroups;
std::for_each(maCells.begin(), maCells.end(), FormulaGroupPicker(aGroups));
return aGroups;
}
namespace {
class UpdateTransHandler
{
ScColumn& mrColumn;
sc::CellStoreType::iterator miPos;
ScRange maSource;
ScAddress maDest;
ScDocument* mpUndoDoc;
public:
UpdateTransHandler(ScColumn& rColumn, const ScRange& rSource, const ScAddress& rDest, ScDocument* pUndoDoc) :
mrColumn(rColumn),
miPos(rColumn.GetCellStore().begin()),
maSource(rSource), maDest(rDest), mpUndoDoc(pUndoDoc) {}
void operator() (size_t nRow, ScFormulaCell* pCell)
{
sc::CellStoreType::position_type aPos = mrColumn.GetCellStore().position(miPos, nRow);
miPos = aPos.first;
sc::SharedFormulaUtil::unshareFormulaCell(aPos, *pCell);
pCell->UpdateTranspose(maSource, maDest, mpUndoDoc);
ScColumn::JoinNewFormulaCell(aPos, *pCell);
}
};
class UpdateGrowHandler
{
ScColumn& mrColumn;
sc::CellStoreType::iterator miPos;
ScRange maArea;
SCCOL mnGrowX;
SCROW mnGrowY;
public:
UpdateGrowHandler(ScColumn& rColumn, const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY) :
mrColumn(rColumn),
miPos(rColumn.GetCellStore().begin()),
maArea(rArea), mnGrowX(nGrowX), mnGrowY(nGrowY) {}
void operator() (size_t nRow, ScFormulaCell* pCell)
{
sc::CellStoreType::position_type aPos = mrColumn.GetCellStore().position(miPos, nRow);
miPos = aPos.first;
sc::SharedFormulaUtil::unshareFormulaCell(aPos, *pCell);
pCell->UpdateGrow(maArea, mnGrowX, mnGrowY);
ScColumn::JoinNewFormulaCell(aPos, *pCell);
}
};
class InsertTabUpdater
{
sc::RefUpdateInsertTabContext& mrCxt;
sc::CellTextAttrStoreType& mrTextAttrs;
sc::CellTextAttrStoreType::iterator miAttrPos;
SCTAB mnTab;
bool mbModified;
public:
InsertTabUpdater(sc::RefUpdateInsertTabContext& rCxt, sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab) :
mrCxt(rCxt),
mrTextAttrs(rTextAttrs),
miAttrPos(rTextAttrs.begin()),
mnTab(nTab),
mbModified(false) {}
void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
{
pCell->UpdateInsertTab(mrCxt);
mbModified = true;
}
void operator() (size_t nRow, EditTextObject* pCell)
{
editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater();
aUpdater.updateTableFields(mnTab);
miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr());
mbModified = true;
}
bool isModified() const { return mbModified; }
};
class DeleteTabUpdater
{
sc::RefUpdateDeleteTabContext& mrCxt;
sc::CellTextAttrStoreType& mrTextAttrs;
sc::CellTextAttrStoreType::iterator miAttrPos;
SCTAB mnTab;
bool mbModified;
public:
DeleteTabUpdater(sc::RefUpdateDeleteTabContext& rCxt, sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab) :
mrCxt(rCxt),
mrTextAttrs(rTextAttrs),
miAttrPos(rTextAttrs.begin()),
mnTab(nTab),
mbModified(false) {}
void operator() (size_t, ScFormulaCell* pCell)
{
pCell->UpdateDeleteTab(mrCxt);
mbModified = true;
}
void operator() (size_t nRow, EditTextObject* pCell)
{
editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater();
aUpdater.updateTableFields(mnTab);
miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr());
mbModified = true;
}
bool isModified() const { return mbModified; }
};
class InsertAbsTabUpdater
{
sc::CellTextAttrStoreType& mrTextAttrs;
sc::CellTextAttrStoreType::iterator miAttrPos;
SCTAB mnTab;
SCTAB mnNewPos;
bool mbModified;
public:
InsertAbsTabUpdater(sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab, SCTAB nNewPos) :
mrTextAttrs(rTextAttrs),
miAttrPos(rTextAttrs.begin()),
mnTab(nTab),
mnNewPos(nNewPos),
mbModified(false) {}
void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
{
pCell->UpdateInsertTabAbs(mnNewPos);
mbModified = true;
}
void operator() (size_t nRow, EditTextObject* pCell)
{
editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater();
aUpdater.updateTableFields(mnTab);
miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr());
mbModified = true;
}
bool isModified() const { return mbModified; }
};
class MoveTabUpdater
{
sc::RefUpdateMoveTabContext& mrCxt;
sc::CellTextAttrStoreType& mrTextAttrs;
sc::CellTextAttrStoreType::iterator miAttrPos;
SCTAB mnTab;
bool mbModified;
public:
MoveTabUpdater(sc::RefUpdateMoveTabContext& rCxt, sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab) :
mrCxt(rCxt),
mrTextAttrs(rTextAttrs),
miAttrPos(rTextAttrs.begin()),
mnTab(nTab),
mbModified(false) {}
void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
{
pCell->UpdateMoveTab(mrCxt, mnTab);
mbModified = true;
}
void operator() (size_t nRow, EditTextObject* pCell)
{
editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater();
aUpdater.updateTableFields(mnTab);
miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr());
mbModified = true;
}
bool isModified() const { return mbModified; }
};
class UpdateCompileHandler
{
bool mbForceIfNameInUse:1;
public:
explicit UpdateCompileHandler(bool bForceIfNameInUse) :
mbForceIfNameInUse(bForceIfNameInUse) {}
void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
{
pCell->UpdateCompile(mbForceIfNameInUse);
}
};
class TabNoSetter
{
SCTAB mnTab;
public:
explicit TabNoSetter(SCTAB nTab) : mnTab(nTab) {}
void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
{
pCell->aPos.SetTab(mnTab);
}
};
class UsedRangeNameFinder
{
sc::UpdatedRangeNames& mrIndexes;
public:
explicit UsedRangeNameFinder(sc::UpdatedRangeNames& rIndexes) : mrIndexes(rIndexes) {}
void operator() (size_t /*nRow*/, const ScFormulaCell* pCell)
{
pCell->FindRangeNamesInUse(mrIndexes);
}
};
class CheckVectorizationHandler
{
public:
CheckVectorizationHandler()
{}
void operator() (size_t /*nRow*/, ScFormulaCell* p)
{
ScTokenArray* pCode = p->GetCode();
if (pCode && pCode->IsFormulaVectorDisabled())
{
pCode->ResetVectorState();
FormulaTokenArrayPlainIterator aIter(*pCode);
FormulaToken* pFT = aIter.First();
while (pFT)
{
pCode->CheckToken(*pFT);
pFT = aIter.Next();
}
}
}
};
struct SetDirtyVarHandler
{
void operator() (size_t /*nRow*/, ScFormulaCell* p)
{
p->SetDirtyVar();
}
};
class SetDirtyHandler
{
ScDocument& mrDoc;
const sc::SetFormulaDirtyContext& mrCxt;
public:
SetDirtyHandler( ScDocument& rDoc, const sc::SetFormulaDirtyContext& rCxt ) :
mrDoc(rDoc), mrCxt(rCxt) {}
void operator() (size_t /*nRow*/, ScFormulaCell* p)
{
if (mrCxt.mbClearTabDeletedFlag)
{
if (!p->IsShared() || p->IsSharedTop())
{
ScTokenArray* pCode = p->GetCode();
pCode->ClearTabDeleted(
p->aPos, mrCxt.mnTabDeletedStart, mrCxt.mnTabDeletedEnd);
}
}
p->SetDirtyVar();
if (!mrDoc.IsInFormulaTree(p))
mrDoc.PutInFormulaTree(p);
}
};
class SetDirtyOnRangeHandler
{
sc::SingleColumnSpanSet maValueRanges;
ScColumn& mrColumn;
public:
explicit SetDirtyOnRangeHandler(ScColumn& rColumn)
: maValueRanges(rColumn.GetDoc().GetSheetLimits()),
mrColumn(rColumn) {}
void operator() (size_t /*nRow*/, ScFormulaCell* p)
{
p->SetDirty();
}
void operator() (mdds::mtv::element_t type, size_t nTopRow, size_t nDataSize)
{
if (type == sc::element_type_empty)
// Ignore empty blocks.
return;
// Non-formula cells.
SCROW nRow1 = nTopRow;
SCROW nRow2 = nTopRow + nDataSize - 1;
maValueRanges.set(nRow1, nRow2, true);
}
void broadcast()
{
std::vector<SCROW> aRows;
maValueRanges.getRows(aRows);
mrColumn.BroadcastCells(aRows, SfxHintId::ScDataChanged);
}
void fillBroadcastSpans( sc::ColumnSpanSet& rBroadcastSpans ) const
{
SCCOL nCol = mrColumn.GetCol();
SCTAB nTab = mrColumn.GetTab();
sc::SingleColumnSpanSet::SpansType aSpans;
maValueRanges.getSpans(aSpans);
for (const auto& rSpan : aSpans)
rBroadcastSpans.set(mrColumn.GetDoc(), nTab, nCol, rSpan.mnRow1, rSpan.mnRow2, true);
}
};
class SetTableOpDirtyOnRangeHandler
{
sc::SingleColumnSpanSet maValueRanges;
ScColumn& mrColumn;
public:
explicit SetTableOpDirtyOnRangeHandler(ScColumn& rColumn)
: maValueRanges(rColumn.GetDoc().GetSheetLimits()),
mrColumn(rColumn) {}
void operator() (size_t /*nRow*/, ScFormulaCell* p)
{
p->SetTableOpDirty();
}
void operator() (mdds::mtv::element_t type, size_t nTopRow, size_t nDataSize)
{
if (type == sc::element_type_empty)
// Ignore empty blocks.
return;
// Non-formula cells.
SCROW nRow1 = nTopRow;
SCROW nRow2 = nTopRow + nDataSize - 1;
maValueRanges.set(nRow1, nRow2, true);
}
void broadcast()
{
std::vector<SCROW> aRows;
maValueRanges.getRows(aRows);
mrColumn.BroadcastCells(aRows, SfxHintId::ScTableOpDirty);
}
};
struct SetDirtyAfterLoadHandler
{
void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
{
#if 1
// Simply set dirty and append to FormulaTree, without broadcasting,
// which is a magnitude faster. This is used to calculate the entire
// document, e.g. when loading alien file formats.
pCell->SetDirtyAfterLoad();
#else
/* This was used with the binary file format that stored results, where only
* newly compiled and volatile functions and their dependents had to be
* recalculated, which was faster then. Since that was moved to 'binfilter' to
* convert to an XML file this isn't needed anymore, and not used for other
* file formats. Kept for reference in case mechanism needs to be reactivated
* for some file formats, we'd have to introduce a controlling parameter to
* this method here then.
*/
// If the cell was already dirty because of CalcAfterLoad,
// FormulaTracking has to take place.
if (pCell->GetDirty())
pCell->SetDirty();
#endif
}
};
struct SetDirtyIfPostponedHandler
{
void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
{
if (pCell->IsPostponedDirty() || (pCell->HasRelNameReference() != ScFormulaCell::RelNameRef::NONE))
pCell->SetDirty();
}
};
struct CalcAllHandler
{
#define DEBUG_SC_CHECK_FORMULATREE_CALCULATION 0
void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
{
#if DEBUG_SC_CHECK_FORMULATREE_CALCULATION
// after F9 ctrl-F9: check the calculation for each FormulaTree
double nOldVal, nNewVal;
nOldVal = pCell->GetValue();
#endif
pCell->Interpret();
#if DEBUG_SC_CHECK_FORMULATREE_CALCULATION
if (pCell->GetCode()->IsRecalcModeNormal())
nNewVal = pCell->GetValue();
else
nNewVal = nOldVal; // random(), jetzt() etc.
assert(nOldVal == nNewVal);
#endif
}
#undef DEBUG_SC_CHECK_FORMULATREE_CALCULATION
};
class CompileAllHandler
{
sc::CompileFormulaContext& mrCxt;
public:
explicit CompileAllHandler( sc::CompileFormulaContext& rCxt ) : mrCxt(rCxt) {}
void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
{
// for unconditional compilation
// bCompile=true and pCode->nError=0
pCell->GetCode()->SetCodeError(FormulaError::NONE);
pCell->SetCompile(true);
pCell->CompileTokenArray(mrCxt);
}
};
class CompileXMLHandler
{
sc::CompileFormulaContext& mrCxt;
ScProgress& mrProgress;
const ScColumn& mrCol;
public:
CompileXMLHandler( sc::CompileFormulaContext& rCxt, ScProgress& rProgress, const ScColumn& rCol) :
mrCxt(rCxt),
mrProgress(rProgress),
mrCol(rCol) {}
void operator() (size_t nRow, ScFormulaCell* pCell)
{
sal_uInt32 nFormat = mrCol.GetNumberFormat(mrCol.GetDoc().GetNonThreadedContext(), nRow);
if( (nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0)
// Non-default number format is set.
pCell->SetNeedNumberFormat(false);
else if (pCell->NeedsNumberFormat())
pCell->SetDirtyVar();
if (pCell->GetMatrixFlag() != ScMatrixMode::NONE)
pCell->SetDirtyVar();
pCell->CompileXML(mrCxt, mrProgress);
}
};
class CompileErrorCellsHandler
{
sc::CompileFormulaContext& mrCxt;
ScColumn& mrColumn;
sc::CellStoreType::iterator miPos;
FormulaError mnErrCode;
bool mbCompiled;
public:
CompileErrorCellsHandler( sc::CompileFormulaContext& rCxt, ScColumn& rColumn, FormulaError nErrCode ) :
mrCxt(rCxt),
mrColumn(rColumn),
miPos(mrColumn.GetCellStore().begin()),
mnErrCode(nErrCode),
mbCompiled(false)
{
}
void operator() (size_t nRow, ScFormulaCell* pCell)
{
FormulaError nCurError = pCell->GetRawError();
if (nCurError == FormulaError::NONE)
// It's not an error cell. Skip it.
return;
if (mnErrCode != FormulaError::NONE && nCurError != mnErrCode)
// Error code is specified, and it doesn't match. Skip it.
return;
sc::CellStoreType::position_type aPos = mrColumn.GetCellStore().position(miPos, nRow);
miPos = aPos.first;
sc::SharedFormulaUtil::unshareFormulaCell(aPos, *pCell);
pCell->GetCode()->SetCodeError(FormulaError::NONE);
OUString aFormula = pCell->GetFormula(mrCxt);
pCell->Compile(mrCxt, aFormula);
ScColumn::JoinNewFormulaCell(aPos, *pCell);
mbCompiled = true;
}
bool isCompiled() const { return mbCompiled; }
};
class CalcAfterLoadHandler
{
sc::CompileFormulaContext& mrCxt;
bool mbStartListening;
public:
CalcAfterLoadHandler( sc::CompileFormulaContext& rCxt, bool bStartListening ) :
mrCxt(rCxt), mbStartListening(bStartListening) {}
void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
{
pCell->CalcAfterLoad(mrCxt, mbStartListening);
}
};
struct ResetChangedHandler
{
void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
{
pCell->SetChanged(false);
}
};
/**
* Ambiguous script type counts as edit cell.
*/
class FindEditCellsHandler
{
ScColumn& mrColumn;
sc::CellTextAttrStoreType::iterator miAttrPos;
sc::CellStoreType::iterator miCellPos;
public:
explicit FindEditCellsHandler(ScColumn& rCol) :
mrColumn(rCol),
miAttrPos(rCol.GetCellAttrStore().begin()),
miCellPos(rCol.GetCellStore().begin()) {}
bool operator() (size_t, const EditTextObject*)
{
// This is definitely an edit text cell.
return true;
}
bool operator() (size_t nRow, const ScFormulaCell* p)
{
// With a formula cell, it's considered an edit text cell when either
// the result is multi-line or it has more than one script types.
SvtScriptType nScriptType = mrColumn.GetRangeScriptType(miAttrPos, nRow, nRow, miCellPos);
if (IsAmbiguousScriptNonZero(nScriptType))
return true;
return const_cast<ScFormulaCell*>(p)->IsMultilineResult();
}
/**
* Callback for a block of other types.
*/
std::pair<size_t,bool> operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
{
typedef std::pair<size_t,bool> RetType;
if (node.type == sc::element_type_empty)
// Ignore empty blocks.
return RetType(0, false);
// Check the script type of a non-empty element and see if it has
// multiple script types.
for (size_t i = 0; i < nDataSize; ++i)
{
SCROW nRow = node.position + i + nOffset;
SvtScriptType nScriptType = mrColumn.GetRangeScriptType(miAttrPos, nRow, nRow, miCellPos);
if (IsAmbiguousScriptNonZero(nScriptType))
// Return the offset from the first row.
return RetType(i+nOffset, true);
}
// No edit text cell found.
return RetType(0, false);
}
};
}
void ScColumn::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest,
ScDocument* pUndoDoc )
{
UpdateTransHandler aFunc(*this, rSource, rDest, pUndoDoc);
sc::ProcessFormula(maCells, aFunc);
}
void ScColumn::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY )
{
UpdateGrowHandler aFunc(*this, rArea, nGrowX, nGrowY);
sc::ProcessFormula(maCells, aFunc);
}
void ScColumn::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
{
if (nTab >= rCxt.mnInsertPos)
{
nTab += rCxt.mnSheets;
pAttrArray->SetTab(nTab);
}
UpdateInsertTabOnlyCells(rCxt);
}
void ScColumn::UpdateInsertTabOnlyCells( sc::RefUpdateInsertTabContext& rCxt )
{
InsertTabUpdater aFunc(rCxt, maCellTextAttrs, nTab);
sc::ProcessFormulaEditText(maCells, aFunc);
if (aFunc.isModified())
CellStorageModified();
}
void ScColumn::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
{
if (nTab > rCxt.mnDeletePos)
{
nTab -= rCxt.mnSheets;
pAttrArray->SetTab(nTab);
}
DeleteTabUpdater aFunc(rCxt, maCellTextAttrs, nTab);
sc::ProcessFormulaEditText(maCells, aFunc);
if (aFunc.isModified())
CellStorageModified();
}
void ScColumn::UpdateInsertTabAbs(SCTAB nNewPos)
{
InsertAbsTabUpdater aFunc(maCellTextAttrs, nTab, nNewPos);
sc::ProcessFormulaEditText(maCells, aFunc);
if (aFunc.isModified())
CellStorageModified();
}
void ScColumn::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt, SCTAB nTabNo )
{
nTab = nTabNo;
pAttrArray->SetTab( nTabNo );
MoveTabUpdater aFunc(rCxt, maCellTextAttrs, nTab);
sc::ProcessFormulaEditText(maCells, aFunc);
if (aFunc.isModified())
CellStorageModified();
}
void ScColumn::UpdateCompile( bool bForceIfNameInUse )
{
UpdateCompileHandler aFunc(bForceIfNameInUse);
sc::ProcessFormula(maCells, aFunc);
}
void ScColumn::SetTabNo(SCTAB nNewTab)
{
nTab = nNewTab;
pAttrArray->SetTab( nNewTab );
TabNoSetter aFunc(nTab);
sc::ProcessFormula(maCells, aFunc);
}
void ScColumn::FindRangeNamesInUse(SCROW nRow1, SCROW nRow2, sc::UpdatedRangeNames& rIndexes) const
{
UsedRangeNameFinder aFunc(rIndexes);
sc::ParseFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
}
void ScColumn::SetDirtyVar()
{
SetDirtyVarHandler aFunc;
sc::ProcessFormula(maCells, aFunc);
}
bool ScColumn::IsFormulaDirty( SCROW nRow ) const
{
if (!GetDoc().ValidRow(nRow))
return false;
std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
sc::CellStoreType::const_iterator it = aPos.first;
if (it->type != sc::element_type_formula)
// This is not a formula cell block.
return false;
const ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
return p->GetDirty();
}
void ScColumn::CheckVectorizationState()
{
sc::AutoCalcSwitch aSwitch(GetDoc(), false);
CheckVectorizationHandler aFunc;
sc::ProcessFormula(maCells, aFunc);
}
void ScColumn::SetAllFormulasDirty( const sc::SetFormulaDirtyContext& rCxt )
{
// is only done documentwide, no FormulaTracking
sc::AutoCalcSwitch aSwitch(GetDoc(), false);
SetDirtyHandler aFunc(GetDoc(), rCxt);
sc::ProcessFormula(maCells, aFunc);
}
void ScColumn::SetDirtyFromClip( SCROW nRow1, SCROW nRow2, sc::ColumnSpanSet& rBroadcastSpans )
{
// Set all formula cells in the range dirty, and pick up all non-formula
// cells for later broadcasting. We don't broadcast here.
sc::AutoCalcSwitch aSwitch(GetDoc(), false);
SetDirtyOnRangeHandler aHdl(*this);
sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl, aHdl);
aHdl.fillBroadcastSpans(rBroadcastSpans);
}
namespace {
class BroadcastBroadcastersHandler
{
ScHint maHint;
bool mbBroadcasted;
public:
explicit BroadcastBroadcastersHandler( SfxHintId nHint, SCTAB nTab, SCCOL nCol )
: maHint(nHint, ScAddress(nCol, 0, nTab))
, mbBroadcasted(false)
{
}
void operator() ( size_t nRow, SvtBroadcaster* pBroadcaster )
{
maHint.SetAddressRow(nRow);
pBroadcaster->Broadcast(maHint);
mbBroadcasted = true;
}
bool wasBroadcasted() { return mbBroadcasted; }
};
}
bool ScColumn::BroadcastBroadcasters( SCROW nRow1, SCROW nRow2, SfxHintId nHint )
{
BroadcastBroadcastersHandler aBroadcasterHdl(nHint, nTab, nCol);
sc::ProcessBroadcaster(maBroadcasters.begin(), maBroadcasters, nRow1, nRow2, aBroadcasterHdl);
return aBroadcasterHdl.wasBroadcasted();
}
void ScColumn::SetDirty( SCROW nRow1, SCROW nRow2, BroadcastMode eMode )
{
// broadcasts everything within the range, with FormulaTracking
sc::AutoCalcSwitch aSwitch(GetDoc(), false);
switch (eMode)
{
case BROADCAST_NONE:
{
// Handler only used with formula cells.
SetDirtyOnRangeHandler aHdl(*this);
sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl);
}
break;
case BROADCAST_DATA_POSITIONS:
{
// Handler used with both, formula and non-formula cells.
SetDirtyOnRangeHandler aHdl(*this);
sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl, aHdl);
aHdl.broadcast();
}
break;
case BROADCAST_BROADCASTERS:
{
// Handler only used with formula cells.
SetDirtyOnRangeHandler aHdl(*this);
sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl);
// Broadcast all broadcasters in range.
if (BroadcastBroadcasters( nRow1, nRow2, SfxHintId::ScDataChanged))
{
// SetDirtyOnRangeHandler implicitly tracks notified
// formulas via ScDocument::Broadcast(), which
// BroadcastBroadcastersHandler doesn't, so explicitly
// track them here.
GetDoc().TrackFormulas();
}
}
break;
}
}
void ScColumn::SetTableOpDirty( const ScRange& rRange )
{
sc::AutoCalcSwitch aSwitch(GetDoc(), false);
SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();
SetTableOpDirtyOnRangeHandler aHdl(*this);
sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl, aHdl);
aHdl.broadcast();
}
void ScColumn::SetDirtyAfterLoad()
{
sc::AutoCalcSwitch aSwitch(GetDoc(), false);
SetDirtyAfterLoadHandler aFunc;
sc::ProcessFormula(maCells, aFunc);
}
namespace {
class RecalcOnRefMoveCollector
{
std::vector<SCROW> maDirtyRows;
public:
void operator() (size_t nRow, ScFormulaCell* pCell)
{
if (pCell->GetDirty() && pCell->GetCode()->IsRecalcModeOnRefMove())
maDirtyRows.push_back(nRow);
}
const std::vector<SCROW>& getDirtyRows() const
{
return maDirtyRows;
}
};
}
void ScColumn::SetDirtyIfPostponed()
{
sc::AutoCalcSwitch aSwitch(GetDoc(), false);
SetDirtyIfPostponedHandler aFunc;
ScBulkBroadcast aBulkBroadcast( GetDoc().GetBASM(), SfxHintId::ScDataChanged);
sc::ProcessFormula(maCells, aFunc);
}
void ScColumn::BroadcastRecalcOnRefMove()
{
sc::AutoCalcSwitch aSwitch(GetDoc(), false);
RecalcOnRefMoveCollector aFunc;
sc::ProcessFormula(maCells, aFunc);
BroadcastCells(aFunc.getDirtyRows(), SfxHintId::ScDataChanged);
}
void ScColumn::CalcAll()
{
CalcAllHandler aFunc;
sc::ProcessFormula(maCells, aFunc);
}
void ScColumn::CompileAll( sc::CompileFormulaContext& rCxt )
{
CompileAllHandler aFunc(rCxt);
sc::ProcessFormula(maCells, aFunc);
}
void ScColumn::CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rProgress )
{
CompileXMLHandler aFunc(rCxt, rProgress, *this);
sc::ProcessFormula(maCells, aFunc);
RegroupFormulaCells();
}
bool ScColumn::CompileErrorCells( sc::CompileFormulaContext& rCxt, FormulaError nErrCode )
{
CompileErrorCellsHandler aHdl(rCxt, *this, nErrCode);
sc::ProcessFormula(maCells, aHdl);
return aHdl.isCompiled();
}
void ScColumn::CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartListening )
{
CalcAfterLoadHandler aFunc(rCxt, bStartListening);
sc::ProcessFormula(maCells, aFunc);
}
void ScColumn::ResetChanged( SCROW nStartRow, SCROW nEndRow )
{
ResetChangedHandler aFunc;
sc::ProcessFormula(maCells.begin(), maCells, nStartRow, nEndRow, aFunc);
}
bool ScColumn::HasEditCells(SCROW nStartRow, SCROW nEndRow, SCROW& rFirst)
{
// used in GetOptimalHeight - ambiguous script type counts as edit cell
FindEditCellsHandler aFunc(*this);
std::pair<sc::CellStoreType::const_iterator,size_t> aPos =
sc::FindFormulaEditText(maCells, nStartRow, nEndRow, aFunc);
if (aPos.first == maCells.end())
return false;
rFirst = aPos.first->position + aPos.second;
return true;
}
SCROW ScColumn::SearchStyle(
SCROW nRow, const ScStyleSheet* pSearchStyle, bool bUp, bool bInSelection,
const ScMarkData& rMark) const
{
if (bInSelection)
{
if (rMark.IsMultiMarked())
{
ScMarkArray aArray(rMark.GetMarkArray(nCol));
return pAttrArray->SearchStyle(nRow, pSearchStyle, bUp, &aArray);
}
else
return -1;
}
else
return pAttrArray->SearchStyle( nRow, pSearchStyle, bUp );
}
bool ScColumn::SearchStyleRange(
SCROW& rRow, SCROW& rEndRow, const ScStyleSheet* pSearchStyle, bool bUp,
bool bInSelection, const ScMarkData& rMark) const
{
if (bInSelection)
{
if (rMark.IsMultiMarked())
{
ScMarkArray aArray(rMark.GetMarkArray(nCol));
return pAttrArray->SearchStyleRange(
rRow, rEndRow, pSearchStyle, bUp, &aArray);
}
else
return false;
}
else
return pAttrArray->SearchStyleRange( rRow, rEndRow, pSearchStyle, bUp );
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V547 Expression '!"can't move formula cell"' is always false.