/* -*- 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 bEmptyAttrA
rray)
{
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++)
{
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=94 H=88 G=90
¤ Dauer der Verarbeitung: 0.20 Sekunden
¤
*© Formatika GbR, Deutschland