forked from amazingfate/loongoffice
if the stored filter values are in the visible cell format (e.g. rounded values) instead of the original (editing) values. Now AutoFilter popup window shows the items according to the visible cell format (e.g. 1.0 instead of 1.01 or 0.99), but still grouping them based on the "editing format" (e.g. not rounded values which visible during editing), i.e. there could be repeated values in the filtering conditions (e.g. two options "1.0" and "1.0" for 1.01 and 0.99). Note: Next step will be to group and filter based on the actual cell format, like MSO does, to simplify filtering of values rounded by the cell format (e.g. selecting the single AutoFilter condition "1.0" to filter both 1.01 and 0.99). Change-Id: I430da5e09794fc4ed8acf79b6485926f46b70277 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/112343 Tested-by: Jenkins Reviewed-by: László Németh <nemeth@numbertext.org>
3482 lines
115 KiB
C++
3482 lines
115 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 <document.hxx>
|
|
#include <table.hxx>
|
|
#include <docpool.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 <svl/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() :
|
|
pPattern(nullptr), bFormula(false), bSkipMerged(true), bGetFont(true), bTotalSize(false)
|
|
{
|
|
}
|
|
|
|
ScColumn::ScColumn(ScSheetLimits const & rSheetLimits) :
|
|
maCellTextAttrs(rSheetLimits.GetMaxRowCount()),
|
|
maCellNotes(rSheetLimits.GetMaxRowCount()),
|
|
maBroadcasters(rSheetLimits.GetMaxRowCount()),
|
|
maCellsEvent(this),
|
|
maCells(maCellsEvent),
|
|
mnBlkCountFormula(0),
|
|
nCol( 0 ),
|
|
nTab( 0 ),
|
|
mbFiltering( false )
|
|
{
|
|
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 )
|
|
pAttrArray.reset(new ScAttrArray( nCol, nTab, rDoc, nullptr ));
|
|
else
|
|
pAttrArray.reset(new ScAttrArray( nCol, nTab, rDoc, &rDoc.maTabs[nTab]->aDefaultColAttrArray ));
|
|
}
|
|
|
|
SCROW ScColumn::GetNextUnprotected( SCROW nRow, bool bUp ) const
|
|
{
|
|
return pAttrArray->GetNextUnprotected(nRow, bUp);
|
|
}
|
|
|
|
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
|
|
{
|
|
using namespace sc;
|
|
|
|
if (!rMark.IsMultiMarked())
|
|
return false;
|
|
|
|
ScAddress aOrigin(ScAddress::INITIALIZE_INVALID);
|
|
ScAddress aCurOrigin = aOrigin;
|
|
|
|
bool bOpen = false;
|
|
ScRangeList aRanges = rMark.GetMarkedRanges();
|
|
for (size_t i = 0, n = aRanges.size(); i < n; ++i)
|
|
{
|
|
const ScRange& r = aRanges[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::HasAttrib( SCROW nRow1, SCROW nRow2, HasAttrFlags nMask ) const
|
|
{
|
|
return pAttrArray->HasAttrib( nRow1, nRow2, nMask );
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool ScColumn::ExtendMerge( SCCOL nThisCol, SCROW nStartRow, SCROW nEndRow,
|
|
SCCOL& rPaintCol, SCROW& rPaintRow,
|
|
bool bRefresh )
|
|
{
|
|
return pAttrArray->ExtendMerge( nThisCol, nStartRow, nEndRow, rPaintCol, rPaintRow, bRefresh );
|
|
}
|
|
|
|
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 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScColumn::MergePatternArea( ScMergePatternState& rState, SCROW nRow1, SCROW nRow2, bool bDeep ) const
|
|
{
|
|
pAttrArray->MergePatternArea( nRow1, nRow2, rState, bDeep );
|
|
}
|
|
|
|
void ScColumn::MergeBlockFrame( SvxBoxItem* pLineOuter, SvxBoxInfoItem* pLineInner,
|
|
ScLineFlags& rFlags,
|
|
SCROW nStartRow, SCROW nEndRow, bool bLeft, SCCOL nDistRight ) const
|
|
{
|
|
pAttrArray->MergeBlockFrame( pLineOuter, pLineInner, rFlags, nStartRow, nEndRow, bLeft, nDistRight );
|
|
}
|
|
|
|
void ScColumn::ApplyBlockFrame(const SvxBoxItem& rLineOuter, const SvxBoxInfoItem* pLineInner,
|
|
SCROW nStartRow, SCROW nEndRow, bool bLeft, SCCOL nDistRight)
|
|
{
|
|
pAttrArray->ApplyBlockFrame(rLineOuter, pLineInner, nStartRow, nEndRow, bLeft, nDistRight);
|
|
}
|
|
|
|
const ScPatternAttr* ScColumn::GetPattern( SCROW nRow ) const
|
|
{
|
|
return pAttrArray->GetPattern( nRow );
|
|
}
|
|
|
|
const SfxPoolItem& ScColumn::GetAttr( SCROW nRow, sal_uInt16 nWhich ) const
|
|
{
|
|
return pAttrArray->GetPattern( nRow )->GetItemSet().Get(nWhich);
|
|
}
|
|
|
|
const ScPatternAttr* ScColumn::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().GetDefPattern() );
|
|
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 ScColumn::GetNumberFormat( SCROW nStartRow, SCROW nEndRow ) const
|
|
{
|
|
ScDocument& rDocument = GetDoc();
|
|
SCROW nPatStartRow, nPatEndRow;
|
|
const ScPatternAttr* pPattern = pAttrArray->GetPatternRange(nPatStartRow, nPatEndRow, nStartRow);
|
|
sal_uInt32 nFormat = pPattern->GetNumberFormat(rDocument.GetFormatTable());
|
|
while (nEndRow > nPatEndRow)
|
|
{
|
|
nStartRow = nPatEndRow + 1;
|
|
pPattern = pAttrArray->GetPatternRange(nPatStartRow, nPatEndRow, nStartRow);
|
|
sal_uInt32 nTmpFormat = pPattern->GetNumberFormat(rDocument.GetFormatTable());
|
|
if (nFormat != nTmpFormat)
|
|
return 0;
|
|
}
|
|
return nFormat;
|
|
}
|
|
|
|
sal_uInt32 ScColumn::GetNumberFormat( const ScInterpreterContext& rContext, SCROW nRow ) const
|
|
{
|
|
return pAttrArray->GetPattern( nRow )->GetNumberFormat( rContext.GetFormatTable() );
|
|
}
|
|
|
|
SCROW ScColumn::ApplySelectionCache( SfxItemPoolCache* pCache, const ScMarkData& rMark, ScEditDataArray* pDataArray, bool* const pIsChanged )
|
|
{
|
|
SCROW nTop = 0;
|
|
SCROW nBottom = 0;
|
|
bool bFound = false;
|
|
|
|
if ( rMark.IsMultiMarked() )
|
|
{
|
|
ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
|
|
while (aMultiIter.Next( nTop, nBottom ))
|
|
{
|
|
pAttrArray->ApplyCacheArea( nTop, nBottom, pCache, pDataArray, pIsChanged );
|
|
bFound = true;
|
|
}
|
|
}
|
|
|
|
if (!bFound)
|
|
return -1;
|
|
else if (nTop==0 && nBottom==GetDoc().MaxRow())
|
|
return 0;
|
|
else
|
|
return nBottom;
|
|
}
|
|
|
|
void ScColumn::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark )
|
|
{
|
|
SCROW nTop;
|
|
SCROW nBottom;
|
|
|
|
if ( pAttrArray && rMark.IsMultiMarked() )
|
|
{
|
|
ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
|
|
while (aMultiIter.Next( nTop, nBottom ))
|
|
pAttrArray->ChangeIndent(nTop, nBottom, bIncrement);
|
|
}
|
|
}
|
|
|
|
void ScColumn::ClearSelectionItems( const sal_uInt16* pWhich,const ScMarkData& rMark )
|
|
{
|
|
SCROW nTop;
|
|
SCROW nBottom;
|
|
|
|
if (!pAttrArray)
|
|
return;
|
|
|
|
if (rMark.IsMultiMarked() )
|
|
{
|
|
ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
|
|
while (aMultiIter.Next( nTop, nBottom ))
|
|
pAttrArray->ClearItems(nTop, nBottom, pWhich);
|
|
}
|
|
else if (rMark.IsMarked())
|
|
{
|
|
ScRange aRange;
|
|
rMark.GetMarkArea(aRange);
|
|
if (aRange.aStart.Col() <= nCol && nCol <= aRange.aEnd.Col())
|
|
{
|
|
pAttrArray->ClearItems(aRange.aStart.Row(), aRange.aEnd.Row(), 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();
|
|
SfxItemPoolCache aCache( GetDoc().GetPool(), pSet );
|
|
|
|
const ScPatternAttr* pPattern = pAttrArray->GetPattern( nRow );
|
|
|
|
// true = keep old content
|
|
|
|
const ScPatternAttr* pNewPattern = static_cast<const ScPatternAttr*>( &aCache.ApplyTo( *pPattern ) );
|
|
|
|
if (pNewPattern != pPattern)
|
|
pAttrArray->SetPattern( nRow, pNewPattern );
|
|
}
|
|
|
|
void ScColumn::ApplyPatternArea( SCROW nStartRow, SCROW nEndRow, const ScPatternAttr& rPatAttr,
|
|
ScEditDataArray* pDataArray, bool* const pIsChanged )
|
|
{
|
|
const SfxItemSet* pSet = &rPatAttr.GetItemSet();
|
|
SfxItemPoolCache aCache( GetDoc().GetPool(), pSet );
|
|
pAttrArray->ApplyCacheArea( nStartRow, nEndRow, &aCache, pDataArray, pIsChanged );
|
|
}
|
|
|
|
void ScColumn::ApplyPatternIfNumberformatIncompatible( const ScRange& rRange,
|
|
const ScPatternAttr& rPattern, SvNumFormatType nNewType )
|
|
{
|
|
const SfxItemSet* pSet = &rPattern.GetItemSet();
|
|
SfxItemPoolCache aCache( GetDoc().GetPool(), 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::AddCondFormat( SCROW nStartRow, SCROW nEndRow, sal_uInt32 nIndex )
|
|
{
|
|
pAttrArray->AddCondFormat( nStartRow, nEndRow, nIndex );
|
|
}
|
|
|
|
void ScColumn::RemoveCondFormat( SCROW nStartRow, SCROW nEndRow, sal_uInt32 nIndex )
|
|
{
|
|
pAttrArray->RemoveCondFormat( nStartRow, nEndRow, nIndex );
|
|
}
|
|
|
|
void ScColumn::ApplyStyle( SCROW nRow, const ScStyleSheet* rStyle )
|
|
{
|
|
const ScPatternAttr* pPattern = pAttrArray->GetPattern(nRow);
|
|
std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr(*pPattern));
|
|
pNewPattern->SetStyleSheet(const_cast<ScStyleSheet*>(rStyle));
|
|
pAttrArray->SetPattern(nRow, std::move(pNewPattern), true);
|
|
}
|
|
|
|
void ScColumn::ApplyStyleArea( SCROW nStartRow, SCROW nEndRow, const ScStyleSheet& rStyle )
|
|
{
|
|
pAttrArray->ApplyStyleArea(nStartRow, nEndRow, rStyle);
|
|
}
|
|
|
|
void ScColumn::ApplySelectionStyle(const ScStyleSheet& rStyle, const ScMarkData& rMark)
|
|
{
|
|
SCROW nTop;
|
|
SCROW nBottom;
|
|
|
|
if ( rMark.IsMultiMarked() )
|
|
{
|
|
ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
|
|
while (aMultiIter.Next( nTop, 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::GetStyle( SCROW nRow ) const
|
|
{
|
|
return pAttrArray->GetPattern( nRow )->GetStyleSheet();
|
|
}
|
|
|
|
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.GetDefPattern() );
|
|
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().GetDefPattern() );
|
|
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::FindStyleSheet( const SfxStyleSheetBase* pStyleSheet, ScFlatBoolRowSegments& rUsedRows, bool bReset )
|
|
{
|
|
pAttrArray->FindStyleSheet( pStyleSheet, rUsedRows, bReset );
|
|
}
|
|
|
|
bool ScColumn::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const
|
|
{
|
|
return pAttrArray->IsStyleSheetUsed( rStyle );
|
|
}
|
|
|
|
bool ScColumn::ApplyFlags( SCROW nStartRow, SCROW nEndRow, ScMF nFlags )
|
|
{
|
|
return pAttrArray->ApplyFlags( nStartRow, nEndRow, nFlags );
|
|
}
|
|
|
|
bool ScColumn::RemoveFlags( SCROW nStartRow, SCROW nEndRow, ScMF nFlags )
|
|
{
|
|
return pAttrArray->RemoveFlags( nStartRow, nEndRow, nFlags );
|
|
}
|
|
|
|
void ScColumn::ClearItems( SCROW nStartRow, SCROW nEndRow, const sal_uInt16* pWhich )
|
|
{
|
|
pAttrArray->ClearItems( nStartRow, nEndRow, pWhich );
|
|
}
|
|
|
|
const ScPatternAttr* ScColumn::SetPattern( SCROW nRow, std::unique_ptr<ScPatternAttr> pPatAttr )
|
|
{
|
|
return pAttrArray->SetPattern( nRow, std::move(pPatAttr), true/*bPutToPool*/ );
|
|
}
|
|
|
|
void ScColumn::SetPattern( SCROW nRow, const ScPatternAttr& rPatAttr )
|
|
{
|
|
pAttrArray->SetPattern( nRow, &rPatAttr, true/*bPutToPool*/ );
|
|
}
|
|
|
|
void ScColumn::SetPatternArea( SCROW nStartRow, SCROW nEndRow,
|
|
const ScPatternAttr& rPatAttr )
|
|
{
|
|
pAttrArray->SetPatternArea( nStartRow, nEndRow, &rPatAttr, true/*bPutToPool*/ );
|
|
}
|
|
|
|
void ScColumn::ApplyAttr( SCROW nRow, const SfxPoolItem& rAttr )
|
|
{
|
|
// in order to only create a new SetItem, we don't need SfxItemPoolCache.
|
|
//TODO: Warning: SfxItemPoolCache seems to create too many Refs for the new SetItem ??
|
|
|
|
ScDocumentPool* pDocPool = GetDoc().GetPool();
|
|
|
|
const ScPatternAttr* pOldPattern = pAttrArray->GetPattern( nRow );
|
|
std::unique_ptr<ScPatternAttr> pTemp(new ScPatternAttr(*pOldPattern));
|
|
pTemp->GetItemSet().Put(rAttr);
|
|
const ScPatternAttr* pNewPattern = &pDocPool->Put( *pTemp );
|
|
|
|
if ( pNewPattern != pOldPattern )
|
|
pAttrArray->SetPattern( nRow, pNewPattern );
|
|
else
|
|
pDocPool->Remove( *pNewPattern ); // free up resources
|
|
}
|
|
|
|
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 )
|
|
{
|
|
ScRefCellValue aVal; // Defaults to empty cell.
|
|
switch (itPos->type)
|
|
{
|
|
case sc::element_type_numeric:
|
|
// Numeric cell
|
|
aVal.mfValue = sc::numeric_block::at(*itPos->data, nOffset);
|
|
aVal.meType = CELLTYPE_VALUE;
|
|
break;
|
|
case sc::element_type_string:
|
|
// String cell
|
|
aVal.mpString = &sc::string_block::at(*itPos->data, nOffset);
|
|
aVal.meType = CELLTYPE_STRING;
|
|
break;
|
|
case sc::element_type_edittext:
|
|
// Edit cell
|
|
aVal.mpEditText = sc::edittext_block::at(*itPos->data, nOffset);
|
|
aVal.meType = CELLTYPE_EDIT;
|
|
break;
|
|
case sc::element_type_formula:
|
|
// Formula cell
|
|
aVal.mpFormula = sc::formula_block::at(*itPos->data, nOffset);
|
|
aVal.meType = CELLTYPE_FORMULA;
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
|
|
return aVal;
|
|
}
|
|
|
|
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().GetSheetLimits().GetMaxRowCount());
|
|
|
|
maBroadcasters.insert_empty(nStartRow, nSize);
|
|
maBroadcasters.resize(GetDoc().GetSheetLimits().GetMaxRowCount());
|
|
|
|
maCellTextAttrs.insert_empty(nStartRow, nSize);
|
|
maCellTextAttrs.resize(GetDoc().GetSheetLimits().GetMaxRowCount());
|
|
|
|
maCells.insert_empty(nStartRow, nSize);
|
|
maCells.resize(GetDoc().GetSheetLimits().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);
|
|
}
|
|
};
|
|
|
|
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
|
|
{
|
|
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);
|
|
}
|
|
|
|
{
|
|
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();
|
|
const ScPatternAttr* pPattern = pAttrArray->GetPattern( nRow );
|
|
std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr( *pPattern ));
|
|
pNewPattern->SetStyleSheet( const_cast<ScStyleSheet*>(pStyle) );
|
|
rColumn.pAttrArray->SetPattern( nRow, std::move(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& rPosCol, ScColumn& rDestCol ) const
|
|
{
|
|
// Copy cells from this column to the destination column only for those
|
|
// rows that are present in the position column (rPosCol).
|
|
|
|
// First, mark all the non-empty cell ranges from the position column.
|
|
sc::SingleColumnSpanSet aRangeSet(GetDoc().GetSheetLimits());
|
|
aRangeSet.scan(rPosCol);
|
|
|
|
// 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.GetDefPattern() );
|
|
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.GetDefPattern() );
|
|
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().GetDefPattern() );
|
|
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().GetDefPattern() );
|
|
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
|
|
{
|
|
SCCOL mnCol;
|
|
SCTAB mnTab;
|
|
public:
|
|
NoteCaptionUpdater( SCCOL nCol, SCTAB nTab ) : mnCol(nCol), mnTab(nTab) {}
|
|
|
|
void operator() ( size_t nRow, ScPostIt* p )
|
|
{
|
|
p->UpdateCaptionPos(ScAddress(mnCol,nRow,mnTab));
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
void ScColumn::UpdateNoteCaptions( SCROW nRow1, SCROW nRow2 )
|
|
{
|
|
NoteCaptionUpdater aFunc(nCol, nTab);
|
|
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);
|
|
|
|
// Swap all CellStoreEvent mdds event_func related.
|
|
std::swap( mnBlkCountFormula, rCol.mnBlkCountFormula);
|
|
|
|
// 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));
|
|
ScAddress& rPos = aHint.GetAddress();
|
|
for (const auto& rRange : aRanges)
|
|
{
|
|
for (SCROW nRow = rRange.mnRow1; nRow <= rRange.mnRow2; ++nRow)
|
|
{
|
|
rPos.SetRow(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);
|
|
processSharedTop(pp, nRow, pCell->GetSharedLength());
|
|
|
|
// Move to the last cell in the group, to get incremented to
|
|
// the next cell in the next iteration.
|
|
size_t nOffsetToLast = pCell->GetSharedLength() - 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();
|
|
std::unique_ptr<ScTokenArray> pOldCode(pCode->Clone());
|
|
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))
|
|
{
|
|
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, pOldCode.get());
|
|
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, *pOldCode);
|
|
}
|
|
|
|
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();
|
|
std::unique_ptr<ScTokenArray> pOldCode(pCode->Clone());
|
|
|
|
ScAddress aPos = pTop->aPos;
|
|
ScAddress aOldPos = aPos;
|
|
|
|
bool bCellMoved;
|
|
if (mpCxt->maRange.In(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.
|
|
auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(mpCxt->mrDoc);
|
|
|
|
sc::StartListeningContext aStartCxt(mpCxt->mrDoc, pPosSet);
|
|
sc::EndListeningContext aEndCxt(mpCxt->mrDoc, pPosSet, pOldCode.get());
|
|
|
|
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, *pOldCode);
|
|
}
|
|
|
|
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 (rCxt.meMode == URM_COPY)
|
|
return UpdateReferenceOnCopy(rCxt, pUndoDoc);
|
|
|
|
if (IsEmptyData() || GetDoc().IsClipOrUndo())
|
|
// Cells in this column are all empty, or clip or undo doc. No update needed.
|
|
return false;
|
|
|
|
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(), 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(), 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, const 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, const 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, const 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, const 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& mrHint;
|
|
ScAddress& mrAddress;
|
|
bool mbBroadcasted;
|
|
|
|
public:
|
|
explicit BroadcastBroadcastersHandler( ScHint& rHint )
|
|
: mrHint(rHint)
|
|
, mrAddress(mrHint.GetAddress())
|
|
, mbBroadcasted(false)
|
|
{
|
|
}
|
|
|
|
void operator() ( size_t nRow, SvtBroadcaster* pBroadcaster )
|
|
{
|
|
mrAddress.SetRow(nRow);
|
|
pBroadcaster->Broadcast(mrHint);
|
|
mbBroadcasted = true;
|
|
}
|
|
|
|
bool wasBroadcasted() { return mbBroadcasted; }
|
|
};
|
|
|
|
}
|
|
|
|
bool ScColumn::BroadcastBroadcasters( SCROW nRow1, SCROW nRow2, ScHint& rHint )
|
|
{
|
|
rHint.GetAddress().SetCol(nCol);
|
|
BroadcastBroadcastersHandler aBroadcasterHdl( rHint);
|
|
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.
|
|
ScHint aHint( SfxHintId::ScDataChanged, ScAddress( nCol, nRow1, nTab));
|
|
if (BroadcastBroadcasters( nRow1, nRow2, aHint))
|
|
{
|
|
// 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;
|
|
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: */
|