Files
odfstudio/sc/source/core/data/attarray.cxx
T
Bayram Çiçek f2531aab9b tdf#145726: Calc: print empty rows that carry a border
- a form with a header row and a hundred empty bordered rows below it
printed only the header row. Calc drops a run of 84 or more equally
formatted rows below the last cell with content, and everything under
it, so the print range ended at the last row with data. The borders
were on screen all along, so only printing, the print preview and the
PDF export showed it.

- the run is measured from the last row with data, so typing changed
the result. On a form whose borders end at row 100, text in row 16 left
84 rows below it and the borders stayed away, text in row 17 left 83
and the whole form printed.

- ScDocument::IsPrintEmpty prints a page that carries a border and no
content, and suppresses one carrying only a colour. A bordered run is
therefore kept now, unless it is wider than SC_VISATTR_BORDER_STOP in
its own length or in its distance below the data, which is the whole
column case i30830 is about. The length stays the same as the sheet is
filled in, the distance stops a chain of shorter bordered ranges
reaching the sheet end. Neither measure applies to a run under
SC_VISATTR_STOP rows, because a run that short was never dropped.

- the print range also sets where Ctrl+End lands, how far the desktop
scrolls and what range is written out on save, so all three grow with
it. In the browser the scroll extent comes from the cell area, so it
stays where it was.

- a run has to meet one more condition than before to be dropped, so no
document prints less than it did, and the ones hitting this bug print
more.

- the tests say a bordered form is printed, a colour under the content
does not pull the print area down, a border too wide or too far down
does not either, a frame round a whole column leaves the printout
ending at the last row with content, and a form is printed wherever the
content ends, including when a font change splits it in two.

Signed-off-by: Bayram Çiçek <bayram.cicek@collabora.com>
Change-Id: I44d041cdbbcde7bfa69b53035f5bebc8d59f9bdd
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/14112
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Michael Stahl <michael.stahl@collabora.com>
(cherry picked from commit 00e5de0a8bd89e5f5c9f7b19fef85331354c4458)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211943
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-10-08 00:28:25 +02:00

2709 lines
92 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 <attarray.hxx>
#include <scitems.hxx>
#include <editeng/borderline.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/lineitem.hxx>
#include <editeng/shaditem.hxx>
#include <editeng/editobj.hxx>
#include <editeng/justifyitem.hxx>
#include <osl/diagnose.h>
#include <poolcach.hxx>
#include <global.hxx>
#include <document.hxx>
#include <docpool.hxx>
#include <docsh.hxx>
#include <patattr.hxx>
#include <stlsheet.hxx>
#include <stlpool.hxx>
#include <markarr.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <segmenttree.hxx>
#include <editdataarray.hxx>
#include <cellvalue.hxx>
#include <editutil.hxx>
#include <mtvelements.hxx>
#include <memory>
using ::editeng::SvxBorderLine;
ScAttrArray::ScAttrArray( SCCOL nNewCol, SCTAB nNewTab, ScDocument& rDoc, ScAttrArray* pDefaultColAttrArray ) :
nCol( nNewCol ),
nTab( nNewTab ),
rDocument( rDoc )
{
if ( nCol == -1 || !pDefaultColAttrArray || pDefaultColAttrArray->mvData.empty() )
return;
ScAddress aAdrStart( nCol, 0, nTab );
ScAddress aAdrEnd( nCol, 0, nTab );
mvData.resize( pDefaultColAttrArray->mvData.size() );
for ( size_t nIdx = 0; nIdx < pDefaultColAttrArray->mvData.size(); ++nIdx )
{
mvData[nIdx].nEndRow = pDefaultColAttrArray->mvData[nIdx].nEndRow;
mvData[nIdx].setScPatternAttr(pDefaultColAttrArray->mvData[nIdx].getScPatternAttr());
bool bNumFormatChanged = false;
if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
mvData[nIdx].getScPatternAttr()->GetItemSet(),
rDocument.getCellAttributeHelper().getDefaultCellAttribute().GetItemSet() ) )
{
aAdrStart.SetRow( nIdx ? mvData[nIdx-1].nEndRow+1 : 0 );
aAdrEnd.SetRow( mvData[nIdx].nEndRow );
rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
}
}
}
ScAttrArray::~ScAttrArray()
{
#if DEBUG_SC_TESTATTRARRAY
TestData();
#endif
}
#if DEBUG_SC_TESTATTRARRAY
void ScAttrArray::TestData() const
{
sal_uInt16 nErr = 0;
SCSIZE nPos;
for (nPos=0; nPos<nCount; nPos++)
{
if (nPos > 0)
if (mvData[nPos].pPattern == mvData[nPos-1].pPattern || mvData[nPos].nRow <= mvData[nPos-1].nRow)
++nErr;
}
if ( nPos && mvData[nPos-1].nRow != rDocument.MaxRow() )
++nErr;
SAL_WARN_IF( nErr, "sc", nErr << " errors in attribute array, column " << nCol );
}
#endif
void ScAttrArray::SetDefaultIfNotInit( SCSIZE nNeeded )
{
if ( !mvData.empty() )
return;
SCSIZE nNewLimit = std::max<SCSIZE>( SC_ATTRARRAY_DELTA, nNeeded );
mvData.reserve( nNewLimit );
mvData.emplace_back();
mvData[0].nEndRow = rDocument.MaxRow();
mvData[0].setScPatternAttr(&rDocument.getCellAttributeHelper().getDefaultCellAttribute()); // no put
}
void ScAttrArray::Reset(const CellAttributeHolder& rPattern)
{
const ScPatternAttr* pPattern(rPattern.getScPatternAttr());
if (nullptr == pPattern)
return;
ScAddress aAdrStart( nCol, 0, nTab );
ScAddress aAdrEnd ( nCol, 0, nTab );
for (SCSIZE i=0; i<mvData.size(); i++)
{
// ensure that attributing changes text width of cell
const ScPatternAttr* pOldPattern(mvData[i].getScPatternAttr());
if ( nCol != -1 )
{
bool bNumFormatChanged;
if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
pPattern->GetItemSet(), pOldPattern->GetItemSet() ) )
{
aAdrStart.SetRow( i ? mvData[i-1].nEndRow+1 : 0 );
aAdrEnd .SetRow( mvData[i].nEndRow );
rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
}
}
}
mvData.resize(0);
rDocument.SetStreamValid(nTab, false);
mvData.resize(1);
mvData[0].nEndRow = rDocument.MaxRow();
mvData[0].setScPatternAttr(pPattern);
}
bool ScAttrArray::Concat(SCSIZE nPos)
{
bool bRet = false;
if (nPos < mvData.size())
{
if (nPos > 0)
{
if (ScPatternAttr::areSame(mvData[nPos - 1].getScPatternAttr(), mvData[nPos].getScPatternAttr()))
{
mvData[nPos - 1].nEndRow = mvData[nPos].nEndRow;
mvData.erase(mvData.begin() + nPos);
nPos--;
bRet = true;
}
}
if (nPos + 1 < mvData.size())
{
if (ScPatternAttr::areSame(mvData[nPos + 1].getScPatternAttr(), mvData[nPos].getScPatternAttr()))
{
mvData[nPos].nEndRow = mvData[nPos + 1].nEndRow;
mvData.erase(mvData.begin() + nPos + 1);
bRet = true;
}
}
}
return bRet;
}
/*
* nCount is the number of runs of different attribute combinations;
* no attribute in a column => nCount==1, one attribute somewhere => nCount == 3
* (ie. one run with no attribute + one attribute + another run with no attribute)
* so a range of identical attributes is only one entry in ScAttrArray.
*
* Iterative implementation of Binary Search
* The same implementation was used inside ScMarkArray::Search().
*
* @param oIndexHint, hint for the start of the search, useful when searching twice for successive values
*/
bool ScAttrArray::Search( SCROW nRow, SCSIZE& nIndex, std::optional<SCROW> oIndexHint ) const
{
/* auto it = std::lower_bound(mvData.begin(), mvData.end(), nRow,
[] (const ScAttrEntry &r1, SCROW nRow)
{ return r1.nEndRow < nRow; } );
if (it != mvData.end())
nIndex = it - mvData.begin();
return it != mvData.end(); */
if (mvData.size() == 1)
{
nIndex = 0;
return true;
}
tools::Long nHi = static_cast<tools::Long>(mvData.size()) - 1;
tools::Long i = 0;
assert((!oIndexHint || *oIndexHint <= nHi) && "bad index hint");
tools::Long nLo = oIndexHint ? *oIndexHint : 0;
while ( nLo <= nHi )
{
i = (nLo + nHi) / 2;
if (mvData[i].nEndRow < nRow)
{
// If [nRow] greater, ignore left half
nLo = i + 1;
}
else if ((i > 0) && (mvData[i - 1].nEndRow >= nRow))
{
// If [nRow] is smaller, ignore right half
nHi = i - 1;
}
else
{
// found
nIndex=static_cast<SCSIZE>(i);
return true;
}
}
nIndex=0;
return false;
}
const ScPatternAttr* ScAttrArray::GetPattern( SCROW nRow ) const
{
if ( mvData.empty() )
{
if ( !rDocument.ValidRow(nRow) )
return nullptr;
return &rDocument.getCellAttributeHelper().getDefaultCellAttribute();
}
SCSIZE i;
if (Search( nRow, i ))
return mvData[i].getScPatternAttr();
else
return nullptr;
}
const ScPatternAttr* ScAttrArray::GetPatternRange( SCROW& rStartRow,
SCROW& rEndRow, SCROW nRow ) const
{
if ( mvData.empty() )
{
if ( !rDocument.ValidRow( nRow ) )
return nullptr;
rStartRow = 0;
rEndRow = rDocument.MaxRow();
return &rDocument.getCellAttributeHelper().getDefaultCellAttribute();
}
SCSIZE nIndex;
if ( Search( nRow, nIndex ) )
{
if ( nIndex > 0 )
rStartRow = mvData[nIndex-1].nEndRow + 1;
else
rStartRow = 0;
rEndRow = mvData[nIndex].nEndRow;
return mvData[nIndex].getScPatternAttr();
}
return nullptr;
}
void ScAttrArray::AddCondFormat( SCROW nStartRow, SCROW nEndRow, sal_uInt32 nIndex )
{
if(!rDocument.ValidRow(nStartRow) || !rDocument.ValidRow(nEndRow))
return;
if(nEndRow < nStartRow)
return;
SCROW nTempStartRow = nStartRow;
SCROW nTempEndRow = nEndRow;
do
{
const ScPatternAttr* pPattern = GetPattern(nTempStartRow);
// changed to create pNewPattern only if needed, else use already
// existing pPattern. This shows by example how to avoid that special
// handling of ScPatternAttr in SC and massive
// incarnations/destructions of that Item (which contains an ItemSet)
std::unique_ptr<ScPatternAttr> pNewPattern;
if(pPattern)
{
SCROW nPatternStartRow;
SCROW nPatternEndRow;
GetPatternRange( nPatternStartRow, nPatternEndRow, nTempStartRow );
nTempEndRow = std::min<SCROW>( nPatternEndRow, nEndRow );
if (const ScCondFormatItem* pItem = pPattern->GetItemSet().GetItemIfSet( ATTR_CONDITIONAL ))
{
ScCondFormatIndexes const & rCondFormatData = pItem->GetCondFormatData();
if (rCondFormatData.find(nIndex) == rCondFormatData.end())
{
ScCondFormatIndexes aNewCondFormatData;
aNewCondFormatData.reserve(rCondFormatData.size()+1);
aNewCondFormatData = rCondFormatData;
aNewCondFormatData.insert(nIndex);
ScCondFormatItem aItem( std::move(aNewCondFormatData) );
pNewPattern.reset( new ScPatternAttr(*pPattern) );
pNewPattern->ItemSetPut(aItem);
}
}
else
{
ScCondFormatItem aItem(nIndex);
pNewPattern.reset( new ScPatternAttr(*pPattern) );
pNewPattern->ItemSetPut(aItem);
}
}
else
{
pNewPattern.reset( new ScPatternAttr( rDocument.getCellAttributeHelper() ) );
ScCondFormatItem aItem(nIndex);
pNewPattern->ItemSetPut(aItem);
nTempEndRow = nEndRow;
}
if (pNewPattern)
SetPatternArea( nTempStartRow, nTempEndRow, CellAttributeHolder(pNewPattern.release(), true) );
else
SetPatternArea( nTempStartRow, nTempEndRow, CellAttributeHolder(pPattern) );
nTempStartRow = nTempEndRow + 1;
}
while(nTempEndRow < nEndRow);
}
void ScAttrArray::RemoveCondFormat( SCROW nStartRow, SCROW nEndRow, sal_uInt32 nIndex )
{
if(!rDocument.ValidRow(nStartRow) || !rDocument.ValidRow(nEndRow))
return;
if(nEndRow < nStartRow)
return;
SCROW nTempStartRow = nStartRow;
SCROW nTempEndRow = nEndRow;
do
{
const ScPatternAttr* pPattern = GetPattern(nTempStartRow);
if(pPattern)
{
SCROW nPatternStartRow;
SCROW nPatternEndRow;
GetPatternRange( nPatternStartRow, nPatternEndRow, nTempStartRow );
nTempEndRow = std::min<SCROW>( nPatternEndRow, nEndRow );
if (const ScCondFormatItem* pItem = pPattern->GetItemSet().GetItemIfSet( ATTR_CONDITIONAL ))
{
if (nIndex == 0)
{
ScCondFormatItem aItem;
ScPatternAttr* pTemp(new ScPatternAttr(*pPattern));
pTemp->ItemSetPut(aItem);
SetPatternArea( nTempStartRow, nTempEndRow, CellAttributeHolder(pTemp, true) );
}
else
{
ScCondFormatIndexes const & rCondFormatData = pItem->GetCondFormatData();
auto itr = rCondFormatData.find(nIndex);
if(itr != rCondFormatData.end())
{
ScCondFormatIndexes aNewCondFormatData(rCondFormatData);
aNewCondFormatData.erase_at(std::distance(rCondFormatData.begin(), itr));
ScCondFormatItem aItem( std::move(aNewCondFormatData) );
ScPatternAttr* pTemp(new ScPatternAttr(*pPattern));
pTemp->ItemSetPut(aItem);
SetPatternArea( nTempStartRow, nTempEndRow, CellAttributeHolder(pTemp, true) );
}
}
}
}
else
{
return;
}
nTempStartRow = nTempEndRow + 1;
}
while(nTempEndRow < nEndRow);
}
void ScAttrArray::RemoveCellCharAttribs( SCROW nStartRow, SCROW nEndRow,
const ScPatternAttr* pPattern, ScEditDataArray* pDataArray )
{
assert( nCol != -1 );
// cache mdds position, this doesn't modify the mdds container, just EditTextObject's
sc::ColumnBlockPosition blockPos;
rDocument.InitColumnBlockPosition( blockPos, nTab, nCol );
nEndRow = rDocument.GetLastDataRow(nTab, nCol, nCol, nEndRow);
for (SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow)
{
ScAddress aPos(nCol, nRow, nTab);
ScRefCellValue aCell(rDocument, aPos, blockPos);
if (aCell.getType() != CELLTYPE_EDIT || !aCell.getEditText())
continue;
std::unique_ptr<EditTextObject> pOldData;
if (pDataArray)
pOldData = std::make_unique<EditTextObject>(*aCell.getEditText());
// Direct modification of cell content - something to watch out for if
// we decide to share edit text instances in the future.
ScEditUtil::RemoveCharAttribs(const_cast<EditTextObject&>(*aCell.getEditText()), *pPattern);
if (pDataArray)
{
auto pNewData = std::make_unique<EditTextObject>(*aCell.getEditText());
pDataArray->AddItem(nTab, nCol, nRow, std::move(pOldData), std::move(pNewData));
}
}
}
bool ScAttrArray::Reserve( SCSIZE nReserve )
{
if ( mvData.empty() && nReserve )
{
try {
mvData.reserve(nReserve);
mvData.emplace_back();
mvData[0].nEndRow = rDocument.MaxRow();
mvData[0].setScPatternAttr(&rDocument.getCellAttributeHelper().getDefaultCellAttribute()); // no put
return true;
} catch (std::bad_alloc const &) {
return false;
}
}
else if ( mvData.capacity() < nReserve )
{
try {
mvData.reserve(nReserve);
return true;
} catch (std::bad_alloc const &) {
return false;
}
}
else
return false;
}
const ScPatternAttr* ScAttrArray::SetPatternAreaImpl(
SCROW nStartRow, SCROW nEndRow, const CellAttributeHolder& rPattern, ScEditDataArray* pDataArray)
{
const ScPatternAttr* pPattern(rPattern.getScPatternAttr());
if (nullptr == pPattern)
return nullptr;
if (rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow))
{
if ((nStartRow == 0) && (nEndRow == rDocument.MaxRow()))
Reset(rPattern);
else
{
SCSIZE nNeeded = mvData.size() + 2;
SetDefaultIfNotInit( nNeeded );
ScAddress aAdrStart( nCol, 0, nTab );
ScAddress aAdrEnd ( nCol, 0, nTab );
SCSIZE ni = 0; // number of entries in beginning
SCSIZE nx = 0; // track position
SCROW ns = 0; // start row of track position
if ( nStartRow > 0 )
{
// skip beginning
SCSIZE nIndex;
Search( nStartRow, nIndex );
ni = nIndex;
if ( ni > 0 )
{
nx = ni;
ns = mvData[ni-1].nEndRow+1;
}
}
// ensure that attributing changes text width of cell
// otherwise, conditional formats need to be reset or deleted
bool bIsLoading = !rDocument.GetDocumentShell() || rDocument.GetDocumentShell()->IsLoading();
while ( ns <= nEndRow )
{
if ( nCol != -1 && !bIsLoading )
{
const SfxItemSet& rNewSet = pPattern->GetItemSet();
const SfxItemSet& rOldSet = mvData[nx].getScPatternAttr()->GetItemSet();
bool bNumFormatChanged;
if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
rNewSet, rOldSet ) )
{
aAdrStart.SetRow( std::max(nStartRow,ns) );
aAdrEnd .SetRow( std::min(nEndRow,mvData[nx].nEndRow) );
rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
}
}
ns = mvData[nx].nEndRow + 1;
nx++;
}
// continue modifying data array
SCSIZE nInsert; // insert position (MAXROWCOUNT := no insert)
bool bCombined = false;
bool bSplit = false;
if ( nStartRow > 0 )
{
nInsert = rDocument.MaxRow() + 1;
if ( !ScPatternAttr::areSame(mvData[ni].getScPatternAttr(), pPattern ) )
{
if ( ni == 0 || (mvData[ni-1].nEndRow < nStartRow - 1) )
{ // may be a split or a simple insert or just a shrink,
// row adjustment is done further down
if ( mvData[ni].nEndRow > nEndRow )
bSplit = true;
ni++;
nInsert = ni;
}
else if (mvData[ni - 1].nEndRow == nStartRow - 1)
nInsert = ni;
}
if ( ni > 0 && ScPatternAttr::areSame(mvData[ni-1].getScPatternAttr(), pPattern) )
{ // combine
mvData[ni-1].nEndRow = nEndRow;
nInsert = rDocument.MaxRow() + 1;
bCombined = true;
}
}
else
nInsert = 0;
SCSIZE nj = ni; // stop position of range to replace
while ( nj < mvData.size() && mvData[nj].nEndRow <= nEndRow )
nj++;
if ( !bSplit )
{
if ( nj < mvData.size() && ScPatternAttr::areSame(mvData[nj].getScPatternAttr(), pPattern ) )
{ // combine
if ( ni > 0 )
{
if ( ScPatternAttr::areSame(mvData[ni-1].getScPatternAttr(), pPattern ) )
{ // adjacent entries
mvData[ni-1].nEndRow = mvData[nj].nEndRow;
nj++;
}
else if ( ni == nInsert )
mvData[ni-1].nEndRow = nStartRow - 1; // shrink
}
nInsert = rDocument.MaxRow() + 1;
bCombined = true;
}
else if ( ni > 0 && ni == nInsert )
mvData[ni-1].nEndRow = nStartRow - 1; // shrink
}
if ( ni < nj )
{ // remove middle entries
if ( !bCombined )
{ // replace one entry
mvData[ni].nEndRow = nEndRow;
mvData[ni].setScPatternAttr(pPattern);
ni++;
nInsert = rDocument.MaxRow() + 1;
}
if ( ni < nj )
{ // remove entries
mvData.erase( mvData.begin() + ni, mvData.begin() + nj);
}
}
if ( nInsert < sal::static_int_cast<SCSIZE>(rDocument.MaxRow() + 1) )
{ // insert or append new entry
if ( nInsert <= mvData.size() )
{
if ( !bSplit )
mvData.emplace(mvData.begin() + nInsert);
else
{
mvData.insert(mvData.begin() + nInsert, 2, ScAttrEntry());
mvData[nInsert+1] = mvData[nInsert-1];
}
}
if ( nInsert )
mvData[nInsert-1].nEndRow = nStartRow - 1;
mvData[nInsert].nEndRow = nEndRow;
mvData[nInsert].setScPatternAttr(pPattern);
// Remove character attributes from these cells if the pattern
// is applied during normal session.
if (pDataArray && nCol != -1)
RemoveCellCharAttribs(nStartRow, nEndRow, pPattern, pDataArray);
}
rDocument.SetStreamValid(nTab, false);
}
}
#if DEBUG_SC_TESTATTRARRAY
TestData();
#endif
return pPattern;
}
void ScAttrArray::ApplyStyleArea( SCROW nStartRow, SCROW nEndRow, const ScStyleSheet& rStyle )
{
if (!(rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow)))
return;
SetDefaultIfNotInit();
SCSIZE nPos;
SCROW nStart=0;
if (!Search( nStartRow, nPos ))
{
OSL_FAIL("Search Failure");
return;
}
ScAddress aAdrStart( nCol, 0, nTab );
ScAddress aAdrEnd ( nCol, 0, nTab );
do
{
const ScPatternAttr* pOldPattern = mvData[nPos].getScPatternAttr();
std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr(*pOldPattern));
pNewPattern->SetStyleSheet(const_cast<ScStyleSheet*>(&rStyle));
SCROW nY1 = nStart;
SCROW nY2 = mvData[nPos].nEndRow;
nStart = mvData[nPos].nEndRow + 1;
if ( *pNewPattern == *pOldPattern )
{
// keep the original pattern (might be default)
// pNewPattern is deleted below
nPos++;
}
else if ( nY1 < nStartRow || nY2 > nEndRow )
{
if (nY1 < nStartRow) nY1=nStartRow;
if (nY2 > nEndRow) nY2=nEndRow;
SetPatternArea( nY1, nY2, CellAttributeHolder(pNewPattern.release(), true) );
Search( nStart, nPos );
}
else
{
if ( nCol != -1 )
{
// ensure attributing changes text width of cell; otherwise
// there aren't (yet) template format changes
const SfxItemSet& rNewSet = pNewPattern->GetItemSet();
const SfxItemSet& rOldSet = pOldPattern->GetItemSet();
bool bNumFormatChanged;
if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
rNewSet, rOldSet ) )
{
aAdrStart.SetRow( nPos ? mvData[nPos-1].nEndRow+1 : 0 );
aAdrEnd .SetRow( mvData[nPos].nEndRow );
rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
}
}
mvData[nPos].setScPatternAttr(pNewPattern.release(), true);
if (Concat(nPos))
Search(nStart, nPos);
else
nPos++;
}
}
while ((nStart <= nEndRow) && (nPos < mvData.size()));
rDocument.SetStreamValid(nTab, false);
#if DEBUG_SC_TESTATTRARRAY
TestData();
#endif
}
// const cast, otherwise it will be too inefficient/complicated
static void SetLineColor(SvxBorderLine const * dest, Color c)
{
if (dest)
{
const_cast<SvxBorderLine*>(dest)->SetColor(c);
}
}
static void SetLine(const SvxBorderLine* dest, const SvxBorderLine* src)
{
if (dest)
{
SvxBorderLine* pCast = const_cast<SvxBorderLine*>(dest);
pCast->SetBorderLineStyle( src->GetBorderLineStyle() );
pCast->SetWidth( src->GetWidth() );
}
}
void ScAttrArray::ApplyLineStyleArea( SCROW nStartRow, SCROW nEndRow,
const SvxBorderLine* pLine, bool bColorOnly )
{
if ( bColorOnly && !pLine )
return;
if (!(rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow)))
return;
SCSIZE nPos;
SCROW nStart=0;
SetDefaultIfNotInit();
if (!Search( nStartRow, nPos ))
{
OSL_FAIL("Search failure");
return;
}
do
{
const ScPatternAttr* pOldPattern = mvData[nPos].getScPatternAttr();
const SfxItemSet& rOldSet = pOldPattern->GetItemSet();
const SvxBoxItem* pBoxItem = rOldSet.GetItemIfSet( ATTR_BORDER );
const SvxLineItem* pTLBRItem = rOldSet.GetItemIfSet( ATTR_BORDER_TLBR );
const SvxLineItem* pBLTRItem = rOldSet.GetItemIfSet( ATTR_BORDER_BLTR );
if ( pBoxItem || pTLBRItem || pBLTRItem )
{
std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr(*pOldPattern));
SCROW nY1 = nStart;
SCROW nY2 = mvData[nPos].nEndRow;
std::unique_ptr<SvxBoxItem> pNewBoxItem( pBoxItem ? pBoxItem->Clone() : nullptr);
std::unique_ptr<SvxLineItem> pNewTLBRItem( pTLBRItem ? pTLBRItem->Clone() : nullptr);
std::unique_ptr<SvxLineItem> pNewBLTRItem(pBLTRItem ? pBLTRItem->Clone() : nullptr);
// fetch line and update attributes with parameters
if ( !pLine )
{
if( pNewBoxItem )
{
if ( pNewBoxItem->GetTop() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::TOP );
if ( pNewBoxItem->GetBottom() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::BOTTOM );
if ( pNewBoxItem->GetLeft() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::LEFT );
if ( pNewBoxItem->GetRight() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::RIGHT );
}
if( pNewTLBRItem && pNewTLBRItem->GetLine() )
pNewTLBRItem->SetLine( nullptr );
if( pNewBLTRItem && pNewBLTRItem->GetLine() )
pNewBLTRItem->SetLine( nullptr );
}
else
{
if ( bColorOnly )
{
Color aColor( pLine->GetColor() );
if( pNewBoxItem )
{
SetLineColor( pNewBoxItem->GetTop(), aColor );
SetLineColor( pNewBoxItem->GetBottom(), aColor );
SetLineColor( pNewBoxItem->GetLeft(), aColor );
SetLineColor( pNewBoxItem->GetRight(), aColor );
}
if( pNewTLBRItem )
SetLineColor( pNewTLBRItem->GetLine(), aColor );
if( pNewBLTRItem )
SetLineColor( pNewBLTRItem->GetLine(), aColor );
}
else
{
if( pNewBoxItem )
{
SetLine( pNewBoxItem->GetTop(), pLine );
SetLine( pNewBoxItem->GetBottom(), pLine );
SetLine( pNewBoxItem->GetLeft(), pLine );
SetLine( pNewBoxItem->GetRight(), pLine );
}
if( pNewTLBRItem )
SetLine( pNewTLBRItem->GetLine(), pLine );
if( pNewBLTRItem )
SetLine( pNewBLTRItem->GetLine(), pLine );
}
}
if (pNewBoxItem) pNewPattern->ItemSetPut(std::move(pNewBoxItem));
if (pNewTLBRItem) pNewPattern->ItemSetPut(std::move(pNewTLBRItem));
if (pNewBLTRItem) pNewPattern->ItemSetPut(std::move(pNewBLTRItem));
nStart = mvData[nPos].nEndRow + 1;
if ( nY1 < nStartRow || nY2 > nEndRow )
{
if (nY1 < nStartRow) nY1=nStartRow;
if (nY2 > nEndRow) nY2=nEndRow;
SetPatternArea( nY1, nY2, CellAttributeHolder(pNewPattern.release(), true) );
Search( nStart, nPos );
}
else
{
// remove from pool ?
mvData[nPos].setScPatternAttr(pNewPattern.release(), true);
if (Concat(nPos))
Search(nStart, nPos);
else
nPos++;
}
}
else
{
nStart = mvData[nPos].nEndRow + 1;
nPos++;
}
}
while ((nStart <= nEndRow) && (nPos < mvData.size()));
}
void ScAttrArray::ApplyCacheArea( SCROW nStartRow, SCROW nEndRow, ScItemPoolCache& rCache, ScEditDataArray* pDataArray, bool* const pIsChanged )
{
#if DEBUG_SC_TESTATTRARRAY
TestData();
#endif
if (!(rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow)))
return;
SCSIZE nPos;
SCROW nStart=0;
SetDefaultIfNotInit();
if (!Search( nStartRow, nPos ))
{
OSL_FAIL("Search Failure");
return;
}
ScAddress aAdrStart( nCol, 0, nTab );
ScAddress aAdrEnd ( nCol, 0, nTab );
do
{
const CellAttributeHolder& rOldPattern(mvData[nPos].getCellAttributeHolder());
const CellAttributeHolder& rNewPattern(rCache.ApplyTo( rOldPattern ));
if (!CellAttributeHolder::areSame(&rNewPattern, &rOldPattern))
{
SCROW nY1 = nStart;
SCROW nY2 = mvData[nPos].nEndRow;
nStart = mvData[nPos].nEndRow + 1;
if(pIsChanged)
*pIsChanged = true;
if ( nY1 < nStartRow || nY2 > nEndRow )
{
if (nY1 < nStartRow) nY1=nStartRow;
if (nY2 > nEndRow) nY2=nEndRow;
SetPatternArea( nY1, nY2, rNewPattern, pDataArray );
Search( nStart, nPos );
}
else
{
if ( nCol != -1 )
{
// ensure attributing changes text-width of cell
const SfxItemSet& rNewSet = rNewPattern.getScPatternAttr()->GetItemSet();
const SfxItemSet& rOldSet = rOldPattern.getScPatternAttr()->GetItemSet();
bool bNumFormatChanged;
if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
rNewSet, rOldSet ) )
{
aAdrStart.SetRow( nPos ? mvData[nPos-1].nEndRow+1 : 0 );
aAdrEnd .SetRow( mvData[nPos].nEndRow );
rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
}
}
mvData[nPos].setCellAttributeHolder(rNewPattern);
if (Concat(nPos))
Search(nStart, nPos);
else
++nPos;
}
}
else
{
nStart = mvData[nPos].nEndRow + 1;
++nPos;
}
}
while (nStart <= nEndRow);
rDocument.SetStreamValid(nTab, false);
#if DEBUG_SC_TESTATTRARRAY
TestData();
#endif
}
void ScAttrArray::SetAttrEntries(std::vector<ScAttrEntry> && vNewData)
{
mvData = std::move(vNewData);
#ifdef DBG_UTIL
SCROW lastEndRow = -1;
for(const auto& entry : mvData)
{ // Verify that the data is not corrupted.
assert(entry.nEndRow > lastEndRow);
lastEndRow = entry.nEndRow;
}
#endif
}
static void lcl_MergeDeep( SfxItemSet& rMergeSet, const SfxItemSet& rSource )
{
const SfxPoolItem* pNewItem;
const SfxPoolItem* pOldItem;
for (sal_uInt16 nId=ATTR_PATTERN_START; nId<=ATTR_PATTERN_END; nId++)
{
// pMergeSet has no parent
SfxItemState eOldState = rMergeSet.GetItemState( nId, false, &pOldItem );
if ( eOldState == SfxItemState::DEFAULT )
{
SfxItemState eNewState = rSource.GetItemState( nId, true, &pNewItem );
if ( eNewState == SfxItemState::SET )
{
if ( *pNewItem != rMergeSet.GetPool()->GetUserOrPoolDefaultItem(nId) )
rMergeSet.InvalidateItem( nId );
}
}
else if ( eOldState == SfxItemState::SET ) // Item set
{
SfxItemState eNewState = rSource.GetItemState( nId, true, &pNewItem );
if ( eNewState == SfxItemState::SET )
{
if ( !SfxPoolItem::areSame(pNewItem, pOldItem) ) // Both pulled
rMergeSet.InvalidateItem( nId );
}
else // Default
{
if ( *pOldItem != rSource.GetPool()->GetUserOrPoolDefaultItem(nId) )
rMergeSet.InvalidateItem( nId );
}
}
// Dontcare remains Dontcare
}
}
void ScAttrArray::MergePatternArea( SCROW nStartRow, SCROW nEndRow,
ScMergePatternState& rState, bool bDeep ) const
{
if (!(rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow)))
return;
SCSIZE nPos = 0;
SCROW nStart=0;
if ( !mvData.empty() && !Search( nStartRow, nPos ) )
{
OSL_FAIL("Search failure");
return;
}
do
{
// similar patterns must not be repeated
const ScPatternAttr* pPattern(&rDocument.getCellAttributeHelper().getDefaultCellAttribute());
if ( !mvData.empty() )
pPattern = mvData[nPos].getScPatternAttr();
if ( !ScPatternAttr::areSame(pPattern, rState.aOld1.getScPatternAttr())
&& !ScPatternAttr::areSame(pPattern, rState.aOld2.getScPatternAttr()) )
{
const SfxItemSet& rThisSet = pPattern->GetItemSet();
if (rState.pItemSet)
{
rState.mbValidPatternId = false;
if (bDeep)
lcl_MergeDeep( *rState.pItemSet, rThisSet );
else
rState.pItemSet->MergeValues( rThisSet );
}
else
{
// first pattern - copied from parent
rState.pItemSet.emplace( *rThisSet.GetPool(), rThisSet.GetRanges() );
rState.pItemSet->Set( rThisSet, bDeep );
rState.mnPatternId = pPattern->GetPAKey();
}
rState.aOld2 = rState.aOld1;
rState.aOld1 = pPattern;
}
if ( !mvData.empty() )
nStart = mvData[nPos].nEndRow + 1;
else
nStart = rDocument.MaxRow() + 1;
++nPos;
}
while (nStart <= nEndRow);
}
// assemble border
static bool lcl_TestAttr( const SvxBorderLine* pOldLine, const SvxBorderLine* pNewLine,
sal_uInt8& rModified, const SvxBorderLine*& rpNew )
{
if (rModified == SC_LINE_DONTCARE)
return false; // don't go again
if (rModified == SC_LINE_EMPTY)
{
rModified = SC_LINE_SET;
rpNew = pNewLine;
return true; // initial value
}
if (pOldLine == pNewLine)
{
rpNew = pOldLine;
return false;
}
if (pOldLine && pNewLine)
if (*pOldLine == *pNewLine)
{
rpNew = pOldLine;
return false;
}
rModified = SC_LINE_DONTCARE;
rpNew = nullptr;
return true; // another line -> don't care
}
static void lcl_MergeToFrame( SvxBoxItem* pLineOuter, SvxBoxInfoItem* pLineInner,
ScLineFlags& rFlags, const ScPatternAttr* pPattern,
bool bLeft, SCCOL nDistRight, bool bTop, SCROW nDistBottom )
{
// right/bottom border set when connected together
const ScMergeAttr& rMerge = pPattern->GetItem(ATTR_MERGE);
if ( rMerge.GetColMerge() == nDistRight + 1 )
nDistRight = 0;
if ( rMerge.GetRowMerge() == nDistBottom + 1 )
nDistBottom = 0;
const SvxBoxItem* pCellFrame = &pPattern->GetItem( ATTR_BORDER );
const SvxBorderLine* pLeftAttr = pCellFrame->GetLeft();
const SvxBorderLine* pRightAttr = pCellFrame->GetRight();
const SvxBorderLine* pTopAttr = pCellFrame->GetTop();
const SvxBorderLine* pBottomAttr = pCellFrame->GetBottom();
const SvxBorderLine* pNew;
if (bTop)
{
if (lcl_TestAttr( pLineOuter->GetTop(), pTopAttr, rFlags.nTop, pNew ))
pLineOuter->SetLine( pNew, SvxBoxItemLine::TOP );
}
else
{
if (lcl_TestAttr( pLineInner->GetHori(), pTopAttr, rFlags.nHori, pNew ))
pLineInner->SetLine( pNew, SvxBoxInfoItemLine::HORI );
}
if (nDistBottom == 0)
{
if (lcl_TestAttr( pLineOuter->GetBottom(), pBottomAttr, rFlags.nBottom, pNew ))
pLineOuter->SetLine( pNew, SvxBoxItemLine::BOTTOM );
}
else
{
if (lcl_TestAttr( pLineInner->GetHori(), pBottomAttr, rFlags.nHori, pNew ))
pLineInner->SetLine( pNew, SvxBoxInfoItemLine::HORI );
}
if (bLeft)
{
if (lcl_TestAttr( pLineOuter->GetLeft(), pLeftAttr, rFlags.nLeft, pNew ))
pLineOuter->SetLine( pNew, SvxBoxItemLine::LEFT );
}
else
{
if (lcl_TestAttr( pLineInner->GetVert(), pLeftAttr, rFlags.nVert, pNew ))
pLineInner->SetLine( pNew, SvxBoxInfoItemLine::VERT );
}
if (nDistRight == 0)
{
if (lcl_TestAttr( pLineOuter->GetRight(), pRightAttr, rFlags.nRight, pNew ))
pLineOuter->SetLine( pNew, SvxBoxItemLine::RIGHT );
}
else
{
if (lcl_TestAttr( pLineInner->GetVert(), pRightAttr, rFlags.nVert, pNew ))
pLineInner->SetLine( pNew, SvxBoxInfoItemLine::VERT );
}
}
void ScAttrArray::MergeBlockFrame( SvxBoxItem* pLineOuter, SvxBoxInfoItem* pLineInner,
ScLineFlags& rFlags,
SCROW nStartRow, SCROW nEndRow, bool bLeft, SCCOL nDistRight ) const
{
const ScPatternAttr* pPattern;
if (nStartRow == nEndRow)
{
pPattern = GetPattern( nStartRow );
lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, true, 0 );
}
else if ( !mvData.empty() ) // non-default pattern
{
pPattern = GetPattern( nStartRow );
lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, true,
nEndRow-nStartRow );
SCSIZE nStartIndex;
SCSIZE nEndIndex;
Search( nStartRow+1, nStartIndex );
Search( nEndRow-1, nEndIndex );
for (SCSIZE i=nStartIndex; i<=nEndIndex; i++)
{
pPattern = mvData[i].getScPatternAttr();
lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, false,
nEndRow - std::min( mvData[i].nEndRow, static_cast<SCROW>(nEndRow-1) ) );
// nDistBottom here always > 0
}
pPattern = GetPattern( nEndRow );
lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, false, 0 );
}
else
{
lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, &rDocument.getCellAttributeHelper().getDefaultCellAttribute(), bLeft, nDistRight, true, 0 );
}
}
// apply border
// ApplyFrame - on an entry into the array
bool ScAttrArray::ApplyFrame( const SvxBoxItem& rBoxItem,
const SvxBoxInfoItem* pBoxInfoItem,
SCROW nStartRow, SCROW nEndRow,
bool bLeft, SCCOL nDistRight, bool bTop, SCROW nDistBottom )
{
OSL_ENSURE( pBoxInfoItem, "Missing line attributes!" );
const ScPatternAttr* pPattern = GetPattern( nStartRow );
const SvxBoxItem* pOldFrame = &pPattern->GetItem( ATTR_BORDER );
// right/bottom border set when connected together
const ScMergeAttr& rMerge = pPattern->GetItem(ATTR_MERGE);
if ( rMerge.GetColMerge() == nDistRight + 1 )
nDistRight = 0;
if ( rMerge.GetRowMerge() == nDistBottom + 1 )
nDistBottom = 0;
SvxBoxItem aNewFrame( *pOldFrame );
bool bRTL=rDocument.IsLayoutRTL(nTab);
// fdo#37464 check if the sheet is RTL then replace right <=> left
if (bRTL)
{
if( bLeft && nDistRight==0)
{
if ( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) )
aNewFrame.SetLine( rBoxItem.GetLeft(), SvxBoxItemLine::RIGHT );
if ( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) )
aNewFrame.SetLine( rBoxItem.GetRight(), SvxBoxItemLine::LEFT );
}
else
{
if ( (nDistRight==0) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) )
aNewFrame.SetLine( (nDistRight==0) ? rBoxItem.GetLeft() : pBoxInfoItem->GetVert(),
SvxBoxItemLine::RIGHT );
if ( bLeft ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) )
aNewFrame.SetLine( bLeft ? rBoxItem.GetRight() : pBoxInfoItem->GetVert(),
SvxBoxItemLine::LEFT );
}
}
else
{
if ( bLeft ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) )
aNewFrame.SetLine( bLeft ? rBoxItem.GetLeft() : pBoxInfoItem->GetVert(),
SvxBoxItemLine::LEFT );
if ( (nDistRight==0) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) )
aNewFrame.SetLine( (nDistRight==0) ? rBoxItem.GetRight() : pBoxInfoItem->GetVert(),
SvxBoxItemLine::RIGHT );
}
if ( bTop ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::TOP) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::HORI) )
aNewFrame.SetLine( bTop ? rBoxItem.GetTop() : pBoxInfoItem->GetHori(),
SvxBoxItemLine::TOP );
if ( (nDistBottom==0) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::BOTTOM) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::HORI) )
aNewFrame.SetLine( (nDistBottom==0) ? rBoxItem.GetBottom() : pBoxInfoItem->GetHori(),
SvxBoxItemLine::BOTTOM );
if (aNewFrame == *pOldFrame)
{
// nothing to do
return false;
}
else
{
ScItemPoolCache aCache( rDocument.getCellAttributeHelper(), aNewFrame );
ApplyCacheArea( nStartRow, nEndRow, aCache );
return true;
}
}
void ScAttrArray::ApplyBlockFrame(const SvxBoxItem& rLineOuter, const SvxBoxInfoItem* pLineInner,
SCROW nStartRow, SCROW nEndRow, bool bLeft, SCCOL nDistRight)
{
SetDefaultIfNotInit();
if (nStartRow == nEndRow)
ApplyFrame(rLineOuter, pLineInner, nStartRow, nEndRow, bLeft, nDistRight, true, 0);
else
{
ApplyFrame(rLineOuter, pLineInner, nStartRow, nStartRow, bLeft, nDistRight,
true, nEndRow-nStartRow);
if ( nEndRow > nStartRow+1 ) // inner part available?
{
SCSIZE nStartIndex;
SCSIZE nEndIndex;
Search( nStartRow+1, nStartIndex );
Search( nEndRow-1, nEndIndex );
SCROW nTmpStart = nStartRow+1;
SCROW nTmpEnd;
for (SCSIZE i=nStartIndex; i<=nEndIndex;)
{
nTmpEnd = std::min( static_cast<SCROW>(nEndRow-1), mvData[i].nEndRow );
bool bChanged = ApplyFrame(rLineOuter, pLineInner, nTmpStart, nTmpEnd,
bLeft, nDistRight, false, nEndRow - nTmpEnd);
nTmpStart = nTmpEnd+1;
if (bChanged)
{
Search(nTmpStart, i);
Search(nEndRow-1, nEndIndex);
}
else
i++;
}
}
ApplyFrame(rLineOuter, pLineInner, nEndRow, nEndRow, bLeft, nDistRight, false, 0);
}
}
bool ScAttrArray::HasAttrib_Impl(const ScPatternAttr* pPattern, HasAttrFlags nMask, SCROW nRow1, SCROW nRow2, SCSIZE i) const
{
bool bFound = false;
if ( nMask & HasAttrFlags::Merged )
{
const ScMergeAttr* pMerge = &pPattern->GetItem( ATTR_MERGE );
if ( pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1 )
bFound = true;
}
if ( nMask & ( HasAttrFlags::Overlapped | HasAttrFlags::NotOverlapped | HasAttrFlags::AutoFilter ) )
{
const ScMergeFlagAttr* pMergeFlag = &pPattern->GetItem( ATTR_MERGE_FLAG );
if ( (nMask & HasAttrFlags::Overlapped) && pMergeFlag->IsOverlapped() )
bFound = true;
if ( (nMask & HasAttrFlags::NotOverlapped) && !pMergeFlag->IsOverlapped() )
bFound = true;
if ( (nMask & HasAttrFlags::AutoFilter) && pMergeFlag->HasAutoFilter() )
bFound = true;
}
if ( nMask & HasAttrFlags::Lines )
{
const SvxBoxItem* pBox = &pPattern->GetItem( ATTR_BORDER );
if ( pBox->GetLeft() || pBox->GetRight() || pBox->GetTop() || pBox->GetBottom() )
bFound = true;
}
if ( nMask & HasAttrFlags::Shadow )
{
const SvxShadowItem* pShadow = &pPattern->GetItem( ATTR_SHADOW );
if ( pShadow->GetLocation() != SvxShadowLocation::NONE )
bFound = true;
}
if ( nMask & HasAttrFlags::Conditional )
{
if ( !pPattern->GetItem( ATTR_CONDITIONAL ).GetCondFormatData().empty())
bFound = true;
}
if ( nMask & HasAttrFlags::Protected )
{
const ScProtectionAttr* pProtect = &pPattern->GetItem( ATTR_PROTECTION );
bool bFoundTemp = false;
if ( pProtect->GetProtection() || pProtect->GetHideCell() )
bFoundTemp = true;
bool bContainsCondFormat = !mvData.empty() &&
!pPattern->GetItem( ATTR_CONDITIONAL ).GetCondFormatData().empty();
if ( bContainsCondFormat && nCol != -1 ) // rDocument.GetCondResult() is valid only for real columns.
{
SCROW nRowStartCond = std::max<SCROW>( nRow1, i ? mvData[i-1].nEndRow + 1: 0 );
SCROW nRowEndCond = std::min<SCROW>( nRow2, mvData[i].nEndRow );
bool bFoundCond = false;
for(SCROW nRowCond = nRowStartCond; nRowCond <= nRowEndCond && !bFoundCond; ++nRowCond)
{
const SfxItemSet* pSet = rDocument.GetCondResult( nCol, nRowCond, nTab );
const ScProtectionAttr* pCondProtect;
if( pSet && (pCondProtect = pSet->GetItemIfSet( ATTR_PROTECTION )) )
{
if( pCondProtect->GetProtection() || pCondProtect->GetHideCell() )
bFoundCond = true;
else
break;
}
else
{
// well it is not true that we found one
// but existing one + cell where conditional
// formatting does not remove it
// => we should use the existing protection setting
bFoundCond = bFoundTemp;
}
}
bFoundTemp = bFoundCond;
}
if(bFoundTemp)
bFound = true;
}
if ( nMask & HasAttrFlags::Rotate )
{
const ScRotateValueItem* pRotate = &pPattern->GetItem( ATTR_ROTATE_VALUE );
// 90 or 270 degrees is former SvxOrientationItem - only look for other values
// (see ScPatternAttr::GetCellOrientation)
Degree100 nAngle = pRotate->GetValue();
if ( nAngle && nAngle != 9000_deg100 && nAngle != 27000_deg100 )
bFound = true;
}
if ( nMask & HasAttrFlags::NeedHeight )
{
if (pPattern->GetCellOrientation() != SvxCellOrientation::Standard)
bFound = true;
else if (pPattern->GetItem( ATTR_LINEBREAK ).GetValue())
bFound = true;
else if (pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() == SvxCellHorJustify::Block)
bFound = true;
else if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
bFound = true;
else if (pPattern->GetItem( ATTR_ROTATE_VALUE ).GetValue())
bFound = true;
}
if ( nMask & ( HasAttrFlags::ShadowRight | HasAttrFlags::ShadowDown ) )
{
const SvxShadowItem* pShadow = &pPattern->GetItem( ATTR_SHADOW );
SvxShadowLocation eLoc = pShadow->GetLocation();
if ( nMask & HasAttrFlags::ShadowRight )
if ( eLoc == SvxShadowLocation::TopRight || eLoc == SvxShadowLocation::BottomRight )
bFound = true;
if ( nMask & HasAttrFlags::ShadowDown )
if ( eLoc == SvxShadowLocation::BottomLeft || eLoc == SvxShadowLocation::BottomRight )
bFound = true;
}
if ( nMask & HasAttrFlags::RightOrCenter )
{
// called only if the sheet is LTR, so physical=logical alignment can be assumed
SvxCellHorJustify eHorJust = pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue();
if ( eHorJust == SvxCellHorJustify::Right || eHorJust == SvxCellHorJustify::Center )
bFound = true;
}
return bFound;
}
// Test if field contains specific attribute
bool ScAttrArray::HasAttrib( SCROW nRow1, SCROW nRow2, HasAttrFlags nMask ) const
{
if (mvData.empty())
{
return HasAttrib_Impl(&rDocument.getCellAttributeHelper().getDefaultCellAttribute(), nMask, 0, rDocument.MaxRow(), 0);
}
SCSIZE nStartIndex;
SCSIZE nEndIndex;
Search( nRow1, nStartIndex );
if (nRow1 != nRow2)
Search( nRow2, nEndIndex, /*hint*/nStartIndex );
else
nEndIndex = nStartIndex;
bool bFound = false;
for (SCSIZE i=nStartIndex; i<=nEndIndex && !bFound; i++)
{
const ScPatternAttr* pPattern = mvData[i].getScPatternAttr();
bFound = HasAttrib_Impl(pPattern, nMask, nRow1, nRow2, i);
}
return bFound;
}
bool ScAttrArray::HasAttrib( SCROW nRow, HasAttrFlags nMask, SCROW* nStartRow, SCROW* nEndRow ) const
{
if (mvData.empty())
{
if( nStartRow )
*nStartRow = 0;
if( nEndRow )
*nEndRow = rDocument.MaxRow();
return HasAttrib_Impl(&rDocument.getCellAttributeHelper().getDefaultCellAttribute(), nMask, 0, rDocument.MaxRow(), 0);
}
SCSIZE nIndex;
Search( nRow, nIndex );
if( nStartRow )
*nStartRow = nIndex > 0 ? mvData[nIndex-1].nEndRow+1 : 0;
if( nEndRow )
*nEndRow = mvData[nIndex].nEndRow;
const ScPatternAttr* pPattern = mvData[nIndex].getScPatternAttr();
return HasAttrib_Impl(pPattern, nMask, nRow, nRow, nIndex);
}
bool ScAttrArray::IsMerged( SCROW nRow ) const
{
if ( !mvData.empty() )
{
SCSIZE nIndex;
Search(nRow, nIndex);
const ScMergeAttr& rItem = mvData[nIndex].getScPatternAttr()->GetItem(ATTR_MERGE);
return rItem.IsMerged();
}
return rDocument.getCellAttributeHelper().getDefaultCellAttribute().GetItem(ATTR_MERGE).IsMerged();
}
/**
* Area around any given summaries expand and adapt any MergeFlag (bRefresh)
*/
bool ScAttrArray::ExtendMerge( SCCOL nThisCol, SCROW nStartRow, SCROW nEndRow,
SCCOL& rPaintCol, SCROW& rPaintRow,
bool bRefresh )
{
assert( nCol != -1 );
SetDefaultIfNotInit();
const ScPatternAttr* pPattern;
const ScMergeAttr* pItem;
SCSIZE nStartIndex;
SCSIZE nEndIndex;
Search( nStartRow, nStartIndex );
Search( nEndRow, nEndIndex );
bool bFound = false;
for (SCSIZE i=nStartIndex; i<=nEndIndex; i++)
{
pPattern = mvData[i].getScPatternAttr();
pItem = &pPattern->GetItem( ATTR_MERGE );
SCCOL nCountX = pItem->GetColMerge();
SCROW nCountY = pItem->GetRowMerge();
if (nCountX>1 || nCountY>1)
{
SCROW nThisRow = (i>0) ? mvData[i-1].nEndRow+1 : 0;
SCCOL nMergeEndCol = nThisCol + nCountX - 1;
SCROW nMergeEndRow = nThisRow + nCountY - 1;
if (nMergeEndCol > rPaintCol && nMergeEndCol <= rDocument.MaxCol())
rPaintCol = nMergeEndCol;
if (nMergeEndRow > rPaintRow && nMergeEndRow <= rDocument.MaxRow())
rPaintRow = nMergeEndRow;
bFound = true;
if (bRefresh)
{
if ( nMergeEndCol > nThisCol )
rDocument.ApplyFlagsTab( nThisCol+1, nThisRow, nMergeEndCol, mvData[i].nEndRow,
nTab, ScMF::Hor );
if ( nMergeEndRow > nThisRow )
rDocument.ApplyFlagsTab( nThisCol, nThisRow+1, nThisCol, nMergeEndRow,
nTab, ScMF::Ver );
if ( nMergeEndCol > nThisCol && nMergeEndRow > nThisRow )
rDocument.ApplyFlagsTab( nThisCol+1, nThisRow+1, nMergeEndCol, nMergeEndRow,
nTab, ScMF::Hor | ScMF::Ver );
Search( nThisRow, i ); // Data changed
Search( nStartRow, nStartIndex );
Search( nEndRow, nEndIndex );
}
}
}
return bFound;
}
void ScAttrArray::RemoveAreaMerge(SCROW nStartRow, SCROW nEndRow)
{
assert( nCol != -1 );
SetDefaultIfNotInit();
const ScPatternAttr* pPattern;
const ScMergeAttr* pItem;
SCSIZE nIndex;
Search( nStartRow, nIndex );
SCROW nThisStart = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
if (nThisStart < nStartRow)
nThisStart = nStartRow;
while ( nThisStart <= nEndRow )
{
SCROW nThisEnd = mvData[nIndex].nEndRow;
if (nThisEnd > nEndRow)
nThisEnd = nEndRow;
pPattern = mvData[nIndex].getScPatternAttr();
pItem = &pPattern->GetItem( ATTR_MERGE );
SCCOL nCountX = pItem->GetColMerge();
SCROW nCountY = pItem->GetRowMerge();
if (nCountX>1 || nCountY>1)
{
const ScMergeAttr* pAttr = &rDocument.GetPool()->GetUserOrPoolDefaultItem( ATTR_MERGE );
const ScMergeFlagAttr* pFlagAttr = &rDocument.GetPool()->GetUserOrPoolDefaultItem( ATTR_MERGE_FLAG );
OSL_ENSURE( nCountY==1 || nThisStart==nThisEnd, "What's up?" );
SCCOL nThisCol = nCol;
SCCOL nMergeEndCol = nThisCol + nCountX - 1;
SCROW nMergeEndRow = nThisEnd + nCountY - 1;
// ApplyAttr for areas
for (SCROW nThisRow = nThisStart; nThisRow <= nThisEnd; nThisRow++)
rDocument.ApplyAttr( nThisCol, nThisRow, nTab, *pAttr );
ScPatternAttr aNewPattern( rDocument.getCellAttributeHelper() );
aNewPattern.ItemSetPut(*pFlagAttr);
rDocument.ApplyPatternAreaTab( nThisCol, nThisStart, nMergeEndCol, nMergeEndRow,
nTab, aNewPattern );
Search( nThisEnd, nIndex ); // data changed
}
++nIndex;
if ( nIndex < mvData.size() )
nThisStart = mvData[nIndex-1].nEndRow+1;
else
nThisStart = rDocument.MaxRow()+1; // End
}
}
void ScAttrArray::SetPatternAreaSafe(SCROW nStartRow, SCROW nEndRow, const CellAttributeHolder& rWantedPattern)
{
SetDefaultIfNotInit();
const ScMergeFlagAttr* pItem;
SCSIZE nIndex;
SCROW nRow;
SCROW nThisRow;
Search( nStartRow, nIndex );
nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
while ( nThisRow <= nEndRow )
{
const CellAttributeHolder& rOldPattern(mvData[nIndex].getCellAttributeHolder());
if (!CellAttributeHolder::areSame(&rOldPattern, &rWantedPattern)) // FIXME: else-branch?
{
if (nThisRow < nStartRow) nThisRow = nStartRow;
nRow = mvData[nIndex].nEndRow;
SCROW nAttrRow = std::min( nRow, nEndRow );
pItem = &rOldPattern.getScPatternAttr()->GetItem( ATTR_MERGE_FLAG );
if (pItem->IsOverlapped() || pItem->HasAutoFilter())
{
// default-constructing a ScPatternAttr for DeleteArea doesn't work
// because it would have no cell style information.
// Instead, the document's getCellAttributeHelper().getDefaultCellAttribute() is copied. Since it is passed as
// pWantedPattern, no special treatment of default is needed here anymore.
ScPatternAttr* pNewPattern(new ScPatternAttr(*rWantedPattern.getScPatternAttr()));
pNewPattern->ItemSetPut(*pItem);
SetPatternArea( nThisRow, nAttrRow, CellAttributeHolder(pNewPattern, true) );
}
else
{
SetPatternArea(nThisRow, nAttrRow, rWantedPattern);
}
Search( nThisRow, nIndex ); // data changed
}
++nIndex;
nThisRow = mvData[nIndex-1].nEndRow+1;
}
}
bool ScAttrArray::ApplyFlags( SCROW nStartRow, SCROW nEndRow, ScMF nFlags )
{
SetDefaultIfNotInit();
const ScPatternAttr* pOldPattern;
ScMF nOldValue;
SCSIZE nIndex;
SCROW nRow;
SCROW nThisRow;
bool bChanged = false;
Search( nStartRow, nIndex );
nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
if (nThisRow < nStartRow) nThisRow = nStartRow;
while ( nThisRow <= nEndRow )
{
pOldPattern = mvData[nIndex].getScPatternAttr();
nOldValue = pOldPattern->GetItem( ATTR_MERGE_FLAG ).GetValue();
if ( (nOldValue | nFlags) != nOldValue )
{
nRow = mvData[nIndex].nEndRow;
SCROW nAttrRow = std::min( nRow, nEndRow );
ScPatternAttr* pNewPattern(new ScPatternAttr(*pOldPattern));
pNewPattern->ItemSetPut(ScMergeFlagAttr(nOldValue | nFlags));
SetPatternArea( nThisRow, nAttrRow, CellAttributeHolder(pNewPattern, true) );
Search( nThisRow, nIndex ); // data changed
bChanged = true;
}
++nIndex;
nThisRow = mvData[nIndex-1].nEndRow+1;
}
return bChanged;
}
bool ScAttrArray::RemoveFlags( SCROW nStartRow, SCROW nEndRow, ScMF nFlags )
{
SetDefaultIfNotInit();
const ScPatternAttr* pOldPattern;
ScMF nOldValue;
SCSIZE nIndex;
SCROW nRow;
SCROW nThisRow;
bool bChanged = false;
Search( nStartRow, nIndex );
nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
if (nThisRow < nStartRow) nThisRow = nStartRow;
while ( nThisRow <= nEndRow )
{
pOldPattern = mvData[nIndex].getScPatternAttr();
nOldValue = pOldPattern->GetItem( ATTR_MERGE_FLAG ).GetValue();
if ( (nOldValue & ~nFlags) != nOldValue )
{
nRow = mvData[nIndex].nEndRow;
SCROW nAttrRow = std::min( nRow, nEndRow );
ScPatternAttr* pNewPattern(new ScPatternAttr(*pOldPattern));
pNewPattern->ItemSetPut(ScMergeFlagAttr(nOldValue & ~nFlags));
SetPatternArea( nThisRow, nAttrRow, CellAttributeHolder(pNewPattern, true) );
Search( nThisRow, nIndex ); // data changed
bChanged = true;
}
++nIndex;
nThisRow = mvData[nIndex-1].nEndRow+1;
}
return bChanged;
}
void ScAttrArray::ClearItems( SCROW nStartRow, SCROW nEndRow, const sal_uInt16* pWhich )
{
SetDefaultIfNotInit();
SCSIZE nIndex;
SCROW nRow;
SCROW nThisRow;
Search( nStartRow, nIndex );
nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
if (nThisRow < nStartRow) nThisRow = nStartRow;
while ( nThisRow <= nEndRow )
{
const ScPatternAttr* pOldPattern = mvData[nIndex].getScPatternAttr();
if ( pOldPattern->HasItemsSet( pWhich ) )
{
ScPatternAttr* pNewPattern(new ScPatternAttr(*pOldPattern));
pNewPattern->ClearItems( pWhich );
nRow = mvData[nIndex].nEndRow;
SCROW nAttrRow = std::min( nRow, nEndRow );
SetPatternArea( nThisRow, nAttrRow, CellAttributeHolder(pNewPattern, true) );
Search( nThisRow, nIndex ); // data changed
}
++nIndex;
nThisRow = mvData[nIndex-1].nEndRow+1;
}
}
void ScAttrArray::ChangeIndent( SCROW nStartRow, SCROW nEndRow, bool bIncrement )
{
SetDefaultIfNotInit();
SCSIZE nIndex;
Search( nStartRow, nIndex );
SCROW nThisStart = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
if (nThisStart < nStartRow) nThisStart = nStartRow;
while ( nThisStart <= nEndRow )
{
const ScPatternAttr* pOldPattern = mvData[nIndex].getScPatternAttr();
const SfxItemSet& rOldSet = pOldPattern->GetItemSet();
const SvxHorJustifyItem* pItem;
bool bNeedJust = !( pItem = rOldSet.GetItemIfSet( ATTR_HOR_JUSTIFY, false ) )
|| (pItem->GetValue() != SvxCellHorJustify::Left &&
pItem->GetValue() != SvxCellHorJustify::Right );
sal_uInt16 nOldValue = rOldSet.Get( ATTR_INDENT ).GetValue();
sal_uInt16 nNewValue = nOldValue;
// To keep Increment indent from running outside the cell
tools::Long nColWidth = static_cast<tools::Long>(
rDocument.GetColWidth(nCol == -1 ? rDocument.MaxCol() : nCol,nTab));
if ( bIncrement )
{
if ( nNewValue < nColWidth-SC_INDENT_STEP )
{
nNewValue += SC_INDENT_STEP;
if ( nNewValue > nColWidth-SC_INDENT_STEP )
nNewValue = nColWidth-SC_INDENT_STEP;
}
}
else
{
if ( nNewValue > 0 )
{
if ( nNewValue > SC_INDENT_STEP )
nNewValue -= SC_INDENT_STEP;
else
nNewValue = 0;
}
}
if ( bNeedJust || nNewValue != nOldValue )
{
SCROW nThisEnd = mvData[nIndex].nEndRow;
SCROW nAttrRow = std::min( nThisEnd, nEndRow );
ScPatternAttr* pNewPattern(new ScPatternAttr(*pOldPattern));
pNewPattern->ItemSetPut(ScIndentItem(nNewValue));
if ( bNeedJust )
pNewPattern->ItemSetPut(
SvxHorJustifyItem( SvxCellHorJustify::Left, ATTR_HOR_JUSTIFY ) );
SetPatternArea( nThisStart, nAttrRow, CellAttributeHolder(pNewPattern, true) );
nThisStart = nThisEnd + 1;
Search( nThisStart, nIndex ); // data changed
}
else
{
nThisStart = mvData[nIndex].nEndRow + 1;
++nIndex;
}
}
}
SCROW ScAttrArray::GetNextUnprotected( SCROW nRow, bool bUp ) const
{
tools::Long nRet = nRow;
if (rDocument.ValidRow(nRow))
{
if ( mvData.empty() )
{
if ( bUp )
return -1;
else
return rDocument.MaxRow()+1;
}
SCSIZE nIndex;
Search(nRow, nIndex);
while (mvData[nIndex].getScPatternAttr()->GetItem(ATTR_PROTECTION).GetProtection())
{
if (bUp)
{
if (nIndex==0)
return -1; // not found
--nIndex;
nRet = mvData[nIndex].nEndRow;
}
else
{
nRet = mvData[nIndex].nEndRow+1;
++nIndex;
if (nIndex >= mvData.size())
return rDocument.MaxRow()+1; // not found
}
}
}
return nRet;
}
void ScAttrArray::FindStyleSheet( const SfxStyleSheetBase* pStyleSheet, ScFlatBoolRowSegments& rUsedRows, bool bReset )
{
SetDefaultIfNotInit();
SCROW nStart = 0;
SCSIZE nPos = 0;
while (nPos < mvData.size())
{
bool bIterateNext = true;
SCROW nEnd = mvData[nPos].nEndRow;
if (mvData[nPos].getScPatternAttr()->GetStyleSheet() == pStyleSheet)
{
rUsedRows.setTrue(nStart, nEnd);
if (bReset)
{
ScPatternAttr* pNewPattern(new ScPatternAttr(*mvData[nPos].getScPatternAttr()));
pNewPattern->SetStyleSheet( static_cast<ScStyleSheet*>(
rDocument.GetStyleSheetPool()->
Find( ScResId(STR_STYLENAME_STANDARD),
SfxStyleFamily::Para,
SfxStyleSearchBits::Auto | SfxStyleSearchBits::ScStandard ) ) );
mvData[nPos].setScPatternAttr(pNewPattern, true);
if (Concat(nPos))
{
Search(nStart, nPos);
bIterateNext = false; // because ++ at end otherwise
}
}
}
nStart = nEnd + 1;
if (bIterateNext)
++nPos;
}
}
bool ScAttrArray::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const
{
if ( mvData.empty() )
{
const ScStyleSheet* pStyle = rDocument.getCellAttributeHelper().getDefaultCellAttribute().GetStyleSheet();
if ( pStyle )
{
pStyle->SetUsage( ScStyleSheet::Usage::USED );
if ( pStyle == &rStyle )
return true;
}
return false;
}
bool bIsUsed = false;
SCSIZE nPos = 0;
while ( nPos < mvData.size() )
{
const ScStyleSheet* pStyle = mvData[nPos].getScPatternAttr()->GetStyleSheet();
if ( pStyle )
{
pStyle->SetUsage( ScStyleSheet::Usage::USED );
if ( pStyle == &rStyle )
{
bIsUsed = true;
}
}
nPos++;
}
return bIsUsed;
}
bool ScAttrArray::IsEmpty() const
{
if ( mvData.empty() )
return true;
if (mvData.size() == 1)
{
return mvData[0].getScPatternAttr()->isDefault();
}
else
return false;
}
bool ScAttrArray::GetFirstVisibleAttr( SCROW& rFirstRow ) const
{
if ( mvData.empty() )
return false;
bool bFound = false;
SCSIZE nStart = 0;
// Skip first entry if more than 1 row.
// Entries at the end are not skipped, GetFirstVisibleAttr may be larger than GetLastVisibleAttr.
SCSIZE nVisStart = 1;
while ( nVisStart < mvData.size() && mvData[nVisStart].getScPatternAttr()->IsVisibleEqual(*mvData[nVisStart-1].getScPatternAttr()) )
++nVisStart;
if ( nVisStart >= mvData.size() || mvData[nVisStart-1].nEndRow > 0 ) // more than 1 row?
nStart = nVisStart;
while ( nStart < mvData.size() && !bFound )
{
if ( mvData[nStart].getScPatternAttr()->IsVisible() )
{
rFirstRow = nStart ? ( mvData[nStart-1].nEndRow + 1 ) : 0;
bFound = true;
}
else
++nStart;
}
return bFound;
}
// size (rows) of a range of attributes after cell content where the search is stopped
// (more than a default page size, 2*42 because it's as good as any number)
const SCROW SC_VISATTR_STOP = 84;
// how far a bordered block reaches into the print area, in its whole length and in its
// distance below the last cell with content. The length counts the rows that already hold
// content, so it stays the same as the form is filled in. 2000 rows is some forty pages here.
const SCROW SC_VISATTR_BORDER_STOP = 2000;
bool ScAttrArray::GetLastVisibleAttr( SCROW& rLastRow, SCROW nLastData, bool bSkipEmpty ) const
{
if ( mvData.empty() )
{
rLastRow = nLastData;
return false;
}
// #i30830# changed behavior:
// ignore a long run of equally formatted rows below the last content cell, and everything
// below it. A bordered run of the size and position a form has is kept instead.
if ( nLastData == rDocument.MaxRow() )
{
rLastRow = rDocument.MaxRow(); // can't look for attributes below rDocument.MaxRow()
return true;
}
// Quick check: last data row in or immediately preceding a run that is the
// last attribution down to the end, e.g. default style or column style.
SCSIZE nPos = mvData.size() - 1;
SCROW nStartRow = (nPos ? mvData[nPos-1].nEndRow + 1 : 0);
if (nStartRow <= nLastData + 1)
{
// Ignore here a few rows if data happens to end within
// SC_VISATTR_STOP rows before rDocument.MaxRow().
rLastRow = nLastData;
return false;
}
// tdf#93315: If "Suppress output of empty pages" in Calc Options is not checked, show empty
// (containing only empty data cells) page in the document
bool bFound = false;
if (bSkipEmpty)
{
Search( nLastData, nPos );
while ( nPos < mvData.size() )
{
// find range of visually equal formats
SCSIZE nEndPos = nPos;
while ( nEndPos < mvData.size()-1 &&
mvData[nEndPos].getScPatternAttr()->IsVisibleEqual(*mvData[nEndPos+1].getScPatternAttr()))
++nEndPos;
SCROW nAttrStartRow = ( nPos > 0 ) ? ( mvData[nPos-1].nEndRow + 1) : 0;
if ( nAttrStartRow <= nLastData )
nAttrStartRow = nLastData + 1;
SCROW nAttrSize = mvData[nEndPos].nEndRow + 1 - nAttrStartRow;
bool bStop = false;
if ( nAttrSize >= SC_VISATTR_STOP )
{
// The length is measured over the whole block, so it holds steady as the
// sheet is filled in. The distance keeps the search within
// SC_VISATTR_BORDER_STOP rows of the last cell with content.
const SvxBoxItem& rBox
= mvData[nEndPos].getScPatternAttr()->GetItem( ATTR_BORDER );
const bool bBordered = rBox.GetLeft() || rBox.GetRight()
|| rBox.GetTop() || rBox.GetBottom();
// walk back to the first range of this block, so the length is the same
// whichever of its ranges holds the last cell with content
SCSIZE nStartPos = nPos;
while ( nStartPos > 0
&& mvData[nStartPos-1].getScPatternAttr()->IsVisibleEqual(
*mvData[nStartPos].getScPatternAttr() ) )
--nStartPos;
const SCROW nRangeStartRow
= ( nStartPos > 0 ) ? ( mvData[nStartPos-1].nEndRow + 1 ) : 0;
bStop = !bBordered
|| mvData[nEndPos].nEndRow + 1 - nRangeStartRow >= SC_VISATTR_BORDER_STOP
|| mvData[nEndPos].nEndRow - nLastData >= SC_VISATTR_BORDER_STOP;
}
if ( bStop )
break; // while, ignore this range and below
else if ( mvData[nEndPos].getScPatternAttr()->IsVisible() )
{
rLastRow = mvData[nEndPos].nEndRow;
bFound = true;
}
nPos = nEndPos + 1;
}
}
else
{
if ((nPos > 0 && mvData[nPos-1].getScPatternAttr()->IsVisible())
|| (nPos > 1 && !mvData[nPos-1].getScPatternAttr()->IsVisibleEqual(*mvData[nPos-2].getScPatternAttr())))
{
rLastRow = mvData[nPos-1].nEndRow;
return true;
}
rLastRow = nLastData;
}
return bFound;
}
bool ScAttrArray::HasVisibleAttrIn( SCROW nStartRow, SCROW nEndRow ) const
{
if ( mvData.empty() )
return rDocument.getCellAttributeHelper().getDefaultCellAttribute().IsVisible();
SCSIZE nIndex;
Search( nStartRow, nIndex );
SCROW nThisStart = nStartRow;
bool bFound = false;
while ( nIndex < mvData.size() && nThisStart <= nEndRow && !bFound )
{
if ( mvData[nIndex].getScPatternAttr()->IsVisible() )
bFound = true;
nThisStart = mvData[nIndex].nEndRow + 1;
++nIndex;
}
return bFound;
}
bool ScAttrArray::IsVisibleEqual( const ScAttrArray& rOther,
SCROW nStartRow, SCROW nEndRow ) const
{
if ( mvData.empty() && rOther.mvData.empty() )
{
const ScPatternAttr* pDefPattern1(&rDocument.getCellAttributeHelper().getDefaultCellAttribute());
const ScPatternAttr* pDefPattern2(&rOther.rDocument.getCellAttributeHelper().getDefaultCellAttribute());
return ( ScPatternAttr::areSame(pDefPattern1, pDefPattern2) || pDefPattern1->IsVisibleEqual( *pDefPattern2 ) );
}
{
const ScAttrArray* pNonDefault = nullptr;
const ScPatternAttr* pDefPattern = nullptr;
bool bDefNonDefCase = false;
if ( mvData.empty() && !rOther.mvData.empty() )
{
pNonDefault = &rOther;
pDefPattern = &rDocument.getCellAttributeHelper().getDefaultCellAttribute();
bDefNonDefCase = true;
}
else if ( !mvData.empty() && rOther.mvData.empty() )
{
pNonDefault = this;
pDefPattern = &rOther.rDocument.getCellAttributeHelper().getDefaultCellAttribute();
bDefNonDefCase = true;
}
if ( bDefNonDefCase )
{
bool bEqual = true;
SCSIZE nPos = 0;
if ( nStartRow > 0 )
pNonDefault->Search( nStartRow, nPos );
while ( nPos < pNonDefault->Count() && bEqual )
{
const ScPatternAttr* pNonDefPattern = pNonDefault->mvData[nPos].getScPatternAttr();
bEqual = ScPatternAttr::areSame(pNonDefPattern, pDefPattern) || pNonDefPattern->IsVisibleEqual( *pDefPattern );
if ( pNonDefault->mvData[nPos].nEndRow >= nEndRow ) break;
++nPos;
}
return bEqual;
}
}
bool bEqual = true;
SCSIZE nThisPos = 0;
SCSIZE nOtherPos = 0;
if ( nStartRow > 0 )
{
Search( nStartRow, nThisPos );
rOther.Search( nStartRow, nOtherPos );
}
while ( nThisPos<mvData.size() && nOtherPos<rOther.Count() && bEqual )
{
SCROW nThisRow = mvData[nThisPos].nEndRow;
SCROW nOtherRow = rOther.mvData[nOtherPos].nEndRow;
const ScPatternAttr* pThisPattern = mvData[nThisPos].getScPatternAttr();
const ScPatternAttr* pOtherPattern = rOther.mvData[nOtherPos].getScPatternAttr();
bEqual = ( ScPatternAttr::areSame(pThisPattern, pOtherPattern) || pThisPattern->IsVisibleEqual(*pOtherPattern) );
if ( nThisRow >= nOtherRow )
{
if ( nOtherRow >= nEndRow ) break;
++nOtherPos;
}
if ( nThisRow <= nOtherRow )
{
if ( nThisRow >= nEndRow ) break;
++nThisPos;
}
}
return bEqual;
}
bool ScAttrArray::IsAllEqual( const ScAttrArray& rOther, SCROW nStartRow, SCROW nEndRow ) const
{
// summarised with IsVisibleEqual
if ( mvData.empty() && rOther.mvData.empty() )
{
const ScPatternAttr* pDefPattern1(&rDocument.getCellAttributeHelper().getDefaultCellAttribute());
const ScPatternAttr* pDefPattern2(&rOther.rDocument.getCellAttributeHelper().getDefaultCellAttribute());
return ScPatternAttr::areSame(pDefPattern1, pDefPattern2);
}
{
const ScAttrArray* pNonDefault = nullptr;
const ScPatternAttr* pDefPattern = nullptr;
bool bDefNonDefCase = false;
if ( mvData.empty() && !rOther.mvData.empty() )
{
pNonDefault = &rOther;
pDefPattern = &rDocument.getCellAttributeHelper().getDefaultCellAttribute();
bDefNonDefCase = true;
}
else if ( !mvData.empty() && rOther.mvData.empty() )
{
pNonDefault = this;
pDefPattern = &rOther.rDocument.getCellAttributeHelper().getDefaultCellAttribute();
bDefNonDefCase = true;
}
if ( bDefNonDefCase )
{
bool bEqual = true;
SCSIZE nPos = 0;
if ( nStartRow > 0 )
pNonDefault->Search( nStartRow, nPos );
while ( nPos < pNonDefault->Count() && bEqual )
{
const ScPatternAttr* pNonDefPattern = pNonDefault->mvData[nPos].getScPatternAttr();
bEqual = ScPatternAttr::areSame( pNonDefPattern, pDefPattern );
if ( pNonDefault->mvData[nPos].nEndRow >= nEndRow ) break;
++nPos;
}
return bEqual;
}
}
bool bEqual = true;
SCSIZE nThisPos = 0;
SCSIZE nOtherPos = 0;
if ( nStartRow > 0 )
{
Search( nStartRow, nThisPos );
rOther.Search( nStartRow, nOtherPos );
}
while ( nThisPos<mvData.size() && nOtherPos<rOther.Count() && bEqual )
{
SCROW nThisRow = mvData[nThisPos].nEndRow;
SCROW nOtherRow = rOther.mvData[nOtherPos].nEndRow;
const ScPatternAttr* pThisPattern = mvData[nThisPos].getScPatternAttr();
const ScPatternAttr* pOtherPattern = rOther.mvData[nOtherPos].getScPatternAttr();
bEqual = ScPatternAttr::areSame( pThisPattern, pOtherPattern );
if ( nThisRow >= nOtherRow )
{
if ( nOtherRow >= nEndRow ) break;
++nOtherPos;
}
if ( nThisRow <= nOtherRow )
{
if ( nThisRow >= nEndRow ) break;
++nThisPos;
}
}
return bEqual;
}
bool ScAttrArray::TestInsertCol( SCROW nStartRow, SCROW nEndRow) const
{
// Horizontal aggregate are not allowed to be moved out; if whole summary,
// here is not recognized
bool bTest = true;
if (!IsEmpty())
{
SCSIZE nIndex = 0;
if ( nStartRow > 0 )
Search( nStartRow, nIndex );
for ( ; nIndex < mvData.size(); nIndex++ )
{
if ( mvData[nIndex].getScPatternAttr()->GetItem(ATTR_MERGE_FLAG).IsHorOverlapped() )
{
bTest = false; // may not be pushed out
break;
}
if ( mvData[nIndex].nEndRow >= nEndRow ) // end of range
break;
}
}
return bTest;
}
bool ScAttrArray::TestInsertRow( SCSIZE nSize ) const
{
// if 1st row pushed out is vertically overlapped, summary would be broken
// rDocument.MaxRow() + 1 - nSize = 1st row pushed out
if ( mvData.empty() )
return !rDocument.getCellAttributeHelper().getDefaultCellAttribute().GetItem(ATTR_MERGE_FLAG).IsVerOverlapped();
SCSIZE nFirstLost = mvData.size()-1;
while ( nFirstLost && mvData[nFirstLost-1].nEndRow >= sal::static_int_cast<SCROW>(rDocument.MaxRow() + 1 - nSize) )
--nFirstLost;
return !mvData[nFirstLost].getScPatternAttr()->GetItem(ATTR_MERGE_FLAG).IsVerOverlapped();
}
void ScAttrArray::InsertRow( SCROW nStartRow, SCSIZE nSize )
{
SetDefaultIfNotInit();
SCROW nSearch = nStartRow > 0 ? nStartRow - 1 : 0; // expand predecessor
SCSIZE nIndex;
Search( nSearch, nIndex );
// set ScMergeAttr may not be extended (so behind delete again)
bool bDoMerge = mvData[nIndex].getScPatternAttr()->GetItem(ATTR_MERGE).IsMerged();
assert( !bDoMerge || nCol != -1 );
SCSIZE nRemove = 0;
SCSIZE i;
for (i = nIndex; i < mvData.size()-1; i++)
{
SCROW nNew = mvData[i].nEndRow + nSize;
if ( nNew >= rDocument.MaxRow() ) // at end?
{
nNew = rDocument.MaxRow();
if (!nRemove)
nRemove = i+1; // remove the following?
}
mvData[i].nEndRow = nNew;
}
// Remove entries at end ?
if (nRemove && nRemove < mvData.size())
DeleteRange( nRemove, mvData.size()-1 );
if (bDoMerge) // extensively repair (again) ScMergeAttr
{
// ApplyAttr for areas
const SfxPoolItem& rDef = rDocument.GetPool()->GetUserOrPoolDefaultItem( ATTR_MERGE );
for (SCSIZE nAdd=0; nAdd<nSize; nAdd++)
rDocument.ApplyAttr( nCol, nStartRow+nAdd, nTab, rDef );
// reply inserts in this area not summarized
}
// Don't duplicate the merge flags in the inserted row.
// #i108488# ScMF::Scenario has to be allowed.
RemoveFlags(nStartRow, nStartRow + nSize - 1, (ScMF::All & ~ScMF::Scenario));
// tdf#140027: set new default attributes if we insert very first rows.
if (nStartRow == 0)
{
const CellAttributeHolder aDefHolder(&rDocument.getCellAttributeHelper().getDefaultCellAttribute());
SetPatternArea(nStartRow, nStartRow + nSize - 1, aDefHolder);
}
}
void ScAttrArray::DeleteRow( SCROW nStartRow, SCSIZE nSize )
{
SetDefaultIfNotInit();
bool bFirst=true;
SCSIZE nStartIndex = 0;
SCSIZE nEndIndex = 0;
SCSIZE i;
for ( i = 0; i < mvData.size()-1; i++)
if (mvData[i].nEndRow >= nStartRow && mvData[i].nEndRow <= sal::static_int_cast<SCROW>(nStartRow+nSize-1))
{
if (bFirst)
{
nStartIndex = i;
bFirst = false;
}
nEndIndex = i;
}
if (!bFirst)
{
SCROW nStart;
if (nStartIndex==0)
nStart = 0;
else
nStart = mvData[nStartIndex-1].nEndRow + 1;
if (nStart < nStartRow)
{
mvData[nStartIndex].nEndRow = nStartRow - 1;
++nStartIndex;
}
if (nEndIndex >= nStartIndex)
{
DeleteRange( nStartIndex, nEndIndex );
if (nStartIndex > 0)
if ( ScPatternAttr::areSame( mvData[nStartIndex-1].getScPatternAttr(), mvData[nStartIndex].getScPatternAttr() ) )
DeleteRange( nStartIndex-1, nStartIndex-1 );
}
}
for (i = 0; i < mvData.size()-1; i++)
if (mvData[i].nEndRow >= nStartRow)
mvData[i].nEndRow -= nSize;
// Below does not follow the pattern to detect pressure ranges;
// instead, only remove merge flags.
RemoveFlags( rDocument.MaxRow()-nSize+1, rDocument.MaxRow(), ScMF::Hor | ScMF::Ver | ScMF::Auto );
}
void ScAttrArray::DeleteRange( SCSIZE nStartIndex, SCSIZE nEndIndex )
{
SetDefaultIfNotInit();
mvData.erase(mvData.begin() + nStartIndex, mvData.begin() + nEndIndex + 1);
}
void ScAttrArray::DeleteArea(SCROW nStartRow, SCROW nEndRow)
{
SetDefaultIfNotInit();
if ( nCol != -1 )
RemoveAreaMerge( nStartRow, nEndRow ); // remove from combined flags
const CellAttributeHolder aDefHolder(&rDocument.getCellAttributeHelper().getDefaultCellAttribute());
if ( !HasAttrib( nStartRow, nEndRow, HasAttrFlags::Overlapped | HasAttrFlags::AutoFilter) )
SetPatternArea( nStartRow, nEndRow, aDefHolder );
else
SetPatternAreaSafe( nStartRow, nEndRow, aDefHolder ); // leave merge flags
}
void ScAttrArray::DeleteHardAttr(SCROW nStartRow, SCROW nEndRow)
{
SetDefaultIfNotInit();
const CellAttributeHolder aDefHolder(&rDocument.getCellAttributeHelper().getDefaultCellAttribute());
SCSIZE nIndex;
SCROW nRow;
SCROW nThisRow;
Search( nStartRow, nIndex );
nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
if (nThisRow < nStartRow) nThisRow = nStartRow;
while ( nThisRow <= nEndRow )
{
const ScPatternAttr* pOldPattern = mvData[nIndex].getScPatternAttr();
if ( pOldPattern->GetItemSet().Count() ) // hard attributes ?
{
nRow = mvData[nIndex].nEndRow;
SCROW nAttrRow = std::min( nRow, nEndRow );
ScPatternAttr* pNewPattern(new ScPatternAttr(*pOldPattern));
for (sal_uInt16 nId = ATTR_PATTERN_START; nId <= ATTR_PATTERN_END; nId++)
if (nId != ATTR_MERGE && nId != ATTR_MERGE_FLAG)
pNewPattern->ItemSetClearItem(nId);
if ( *pNewPattern == *aDefHolder.getScPatternAttr() )
{
delete pNewPattern;
SetPatternArea( nThisRow, nAttrRow, aDefHolder );
}
else
{
SetPatternArea( nThisRow, nAttrRow, CellAttributeHolder(pNewPattern, true) );
}
Search( nThisRow, nIndex ); // data changed
}
++nIndex;
nThisRow = mvData[nIndex-1].nEndRow+1;
}
}
/**
* Move within a document
*/
void ScAttrArray::MoveTo(SCROW nStartRow, SCROW nEndRow, ScAttrArray& rAttrArray)
{
SetDefaultIfNotInit();
SCROW nStart = nStartRow;
for (SCSIZE i = 0; i < mvData.size(); i++)
{
if ((mvData[i].nEndRow >= nStartRow) && (i == 0 || mvData[i-1].nEndRow < nEndRow))
{
// copy (bPutToPool=TRUE)
rAttrArray.SetPatternArea( nStart, std::min( mvData[i].nEndRow, nEndRow ), mvData[i].getCellAttributeHolder() );
}
nStart = std::max( nStart, mvData[i].nEndRow + 1 );
}
DeleteArea(nStartRow, nEndRow);
}
/**
* Copy between documents (Clipboard)
*/
void ScAttrArray::CopyArea(
SCROW nStartRow, SCROW nEndRow, tools::Long nDy, ScAttrArray& rAttrArray, ScMF nStripFlags) const
{
nStartRow -= nDy; // Source
nEndRow -= nDy;
SCROW nDestStart = std::max(static_cast<tools::Long>(static_cast<tools::Long>(nStartRow) + nDy), tools::Long(0));
SCROW nDestEnd = std::min(static_cast<tools::Long>(static_cast<tools::Long>(nEndRow) + nDy), tools::Long(rDocument.MaxRow()));
const bool bSameCellAttributeHelper(&rDocument.getCellAttributeHelper() == &rAttrArray.rDocument.getCellAttributeHelper());
const ScPatternAttr* pSourceDefaultPattern = &rDocument.getCellAttributeHelper().getDefaultCellAttribute();
const ScPatternAttr* pDestDefaultPattern = &rAttrArray.rDocument.getCellAttributeHelper().getDefaultCellAttribute();
if ( mvData.empty() )
{
rAttrArray.SetPatternArea(nDestStart, nDestEnd, pDestDefaultPattern);
return;
}
for (SCSIZE i = 0; (i < mvData.size()) && (nDestStart <= nDestEnd); i++)
{
if (mvData[i].nEndRow >= nStartRow)
{
const ScPatternAttr* pOldPattern = mvData[i].getScPatternAttr();
CellAttributeHolder aNewPattern;
if (ScPatternAttr::areSame(pSourceDefaultPattern, pOldPattern ))
{
// default: nothing changed
aNewPattern.setScPatternAttr(pDestDefaultPattern);
}
else if ( nStripFlags != ScMF::NONE )
{
ScPatternAttr* pTmpPattern(new ScPatternAttr(*pOldPattern ));
ScMF nNewFlags = ScMF::NONE;
if ( nStripFlags != ScMF::All )
nNewFlags = pTmpPattern->GetItem(ATTR_MERGE_FLAG).GetValue() & ~nStripFlags;
if ( nNewFlags != ScMF::NONE )
pTmpPattern->ItemSetPut(ScMergeFlagAttr(nNewFlags));
else
pTmpPattern->ItemSetClearItem(ATTR_MERGE_FLAG);
if (bSameCellAttributeHelper)
aNewPattern.setScPatternAttr(pTmpPattern, true);
else
{
aNewPattern = pTmpPattern->MigrateToDocument( &rAttrArray.rDocument, &rDocument );
delete pTmpPattern;
}
}
else
{
if (bSameCellAttributeHelper)
aNewPattern.setScPatternAttr(pOldPattern);
else
aNewPattern = pOldPattern->MigrateToDocument( &rAttrArray.rDocument, &rDocument );
}
rAttrArray.SetPatternArea(nDestStart,
std::min(static_cast<SCROW>(mvData[i].nEndRow + nDy), nDestEnd), aNewPattern.getScPatternAttr());
}
// when pasting from clipboard and skipping filtered rows, the adjusted
// end position can be negative
nDestStart = std::max(static_cast<tools::Long>(nDestStart), static_cast<tools::Long>(mvData[i].nEndRow + nDy + 1));
}
}
/**
* Leave flags
* summarized with CopyArea
*/
void ScAttrArray::CopyAreaSafe( SCROW nStartRow, SCROW nEndRow, tools::Long nDy, ScAttrArray& rAttrArray )
{
nStartRow -= nDy; // Source
nEndRow -= nDy;
SCROW nDestStart = std::max(static_cast<tools::Long>(static_cast<tools::Long>(nStartRow) + nDy), tools::Long(0));
SCROW nDestEnd = std::min(static_cast<tools::Long>(static_cast<tools::Long>(nEndRow) + nDy), tools::Long(rDocument.MaxRow()));
if ( !rAttrArray.HasAttrib( nDestStart, nDestEnd, HasAttrFlags::Overlapped ) )
{
CopyArea( nStartRow+nDy, nEndRow+nDy, nDy, rAttrArray );
return;
}
const bool bSameCellAttributeHelper(&rDocument.getCellAttributeHelper() == &rAttrArray.rDocument.getCellAttributeHelper());
if ( mvData.empty() )
{
CellAttributeHolder aNewPattern;
if (bSameCellAttributeHelper)
aNewPattern.setScPatternAttr(&rDocument.getCellAttributeHelper().getDefaultCellAttribute());
else
aNewPattern = rDocument.getCellAttributeHelper().getDefaultCellAttribute().MigrateToDocument( &rAttrArray.rDocument, &rDocument );
rAttrArray.SetPatternAreaSafe(nDestStart, nDestEnd, aNewPattern);
return;
}
for (SCSIZE i = 0; (i < mvData.size()) && (nDestStart <= nDestEnd); i++)
{
if (mvData[i].nEndRow >= nStartRow)
{
const ScPatternAttr* pOldPattern = mvData[i].getScPatternAttr();
CellAttributeHolder aNewPattern;
if (bSameCellAttributeHelper)
aNewPattern.setScPatternAttr(pOldPattern);
else
aNewPattern = pOldPattern->MigrateToDocument( &rAttrArray.rDocument, &rDocument );
rAttrArray.SetPatternAreaSafe(nDestStart,
std::min(static_cast<SCROW>(mvData[i].nEndRow + nDy), nDestEnd), aNewPattern);
}
// when pasting from clipboard and skipping filtered rows, the adjusted
// end position can be negative
nDestStart = std::max(static_cast<tools::Long>(nDestStart), static_cast<tools::Long>(mvData[i].nEndRow + nDy + 1));
}
}
SCROW ScAttrArray::SearchStyle(
SCROW nRow, const ScStyleSheet* pSearchStyle, bool bUp,
const ScMarkArray* pMarkArray) const
{
bool bFound = false;
if (pMarkArray)
{
nRow = pMarkArray->GetNextMarked( nRow, bUp );
if (!rDocument.ValidRow(nRow))
return nRow;
}
if ( mvData.empty() )
{
if (rDocument.getCellAttributeHelper().getDefaultCellAttribute().GetStyleSheet() == pSearchStyle)
return nRow;
nRow = bUp ? -1 : rDocument.MaxRow() + 1;
return nRow;
}
SCSIZE nIndex;
Search(nRow, nIndex);
const ScPatternAttr* pPattern = mvData[nIndex].getScPatternAttr();
while (nIndex < mvData.size() && !bFound)
{
if (pPattern->GetStyleSheet() == pSearchStyle)
{
if (pMarkArray)
{
nRow = pMarkArray->GetNextMarked( nRow, bUp );
SCROW nStart = nIndex ? mvData[nIndex-1].nEndRow+1 : 0;
if (nRow >= nStart && nRow <= mvData[nIndex].nEndRow)
bFound = true;
}
else
bFound = true;
}
if (!bFound)
{
if (bUp)
{
if (nIndex==0)
{
nIndex = mvData.size();
nRow = -1;
}
else
{
--nIndex;
nRow = mvData[nIndex].nEndRow;
pPattern = mvData[nIndex].getScPatternAttr();
}
}
else
{
nRow = mvData[nIndex].nEndRow+1;
++nIndex;
if (nIndex<mvData.size())
pPattern = mvData[nIndex].getScPatternAttr();
}
}
}
OSL_ENSURE( bFound || !rDocument.ValidRow(nRow), "Internal failure in ScAttrArray::SearchStyle" );
return nRow;
}
bool ScAttrArray::SearchStyleRange(
SCROW& rRow, SCROW& rEndRow, const ScStyleSheet* pSearchStyle, bool bUp,
const ScMarkArray* pMarkArray) const
{
SCROW nStartRow = SearchStyle( rRow, pSearchStyle, bUp, pMarkArray );
if (rDocument.ValidRow(nStartRow))
{
if ( mvData.empty() )
{
rRow = nStartRow;
if (bUp)
{
rEndRow = 0;
if (pMarkArray)
{
SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, true );
if (nMarkEnd>rEndRow)
rEndRow = nMarkEnd;
}
}
else
{
rEndRow = rDocument.MaxRow();
if (pMarkArray)
{
SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, false );
if (nMarkEnd<rEndRow)
rEndRow = nMarkEnd;
}
}
return true;
}
SCSIZE nIndex;
Search(nStartRow,nIndex);
rRow = nStartRow;
if (bUp)
{
if (nIndex>0)
rEndRow = mvData[nIndex-1].nEndRow + 1;
else
rEndRow = 0;
if (pMarkArray)
{
SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, true );
if (nMarkEnd>rEndRow)
rEndRow = nMarkEnd;
}
}
else
{
rEndRow = mvData[nIndex].nEndRow;
if (pMarkArray)
{
SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, false );
if (nMarkEnd<rEndRow)
rEndRow = nMarkEnd;
}
}
return true;
}
else
return false;
}
SCSIZE ScAttrArray::Count( SCROW nStartRow, SCROW nEndRow ) const
{
if ( mvData.empty() )
return 1;
SCSIZE nIndex1, nIndex2;
if( !Search( nStartRow, nIndex1 ) )
return 0;
if( !Search( nEndRow, nIndex2 ) )
nIndex2 = mvData.size() - 1;
return nIndex2 - nIndex1 + 1;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */