3341 lines
112 KiB
C++
3341 lines
112 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* 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: */
|