/* -*- 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 <scitems.hxx>
#include <svl/numformat.hxx>
#include <rtl/math.hxx>
#include <sal/log.hxx>
#include <unotools/collatorwrapper.hxx>
#include <com/sun/star/sheet/ConditionOperator2.hpp>
#include <attrib.hxx>
#include <conditio.hxx>
#include <formulacell.hxx>
#include <document.hxx>
#include <compiler.hxx>
#include <rangelst.hxx>
#include <rangenam.hxx>
#include <rangeutl.hxx>
#include <colorscale.hxx>
#include <cellvalue.hxx>
#include <editutil.hxx>
#include <tokenarray.hxx>
#include <fillinfo.hxx>
#include <refupdatecontext.hxx>
#include <formula/errorcodes.hxx>
#include <svl/sharedstring.hxx>
#include <svl/sharedstringpool.hxx>
#include <memory>
#include <numeric>
#include <utility>
using namespace formula;
ScFormatEntry::ScFormatEntry(ScDocument* pDoc):
mpDoc(pDoc)
{
}
bool ScFormatEntry::operator==( const ScFormatEntry& r ) const
{
return IsEqual(r, false);
}
// virtual
bool ScFormatEntry::IsEqual( const ScFormatEntry& /*r*/, bool /*bIgnoreSrcPos*/ ) const
{
// By default, return false; this makes sense for all cases except ScConditionEntry
// As soon as databar and color scale are tested we need to think about the range
return false;
}
void ScFormatEntry::startRendering()
{
}
void ScFormatEntry::endRendering()
{
}
void ScFormatEntry::updateValues()
{
}
static bool lcl_HasRelRef( ScDocument* pDoc, const ScTokenArray* pFormula, sal_uInt16 nRecursion = 0 )
{
if (pFormula)
{
FormulaTokenArrayPlainIterator aIter( *pFormula );
FormulaToken* t;
for( t = aIter.Next(); t; t = aIter.Next() )
{
switch( t->GetType() )
{
case svDoubleRef:
{
ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
if ( rRef2.IsColRel() || rRef2.IsRowRel() || rRef2.IsTabRel() )
return true;
[[fallthrough]];
}
case svSingleRef:
{
ScSingleRefData& rRef1 = *t->GetSingleRef();
if ( rRef1.IsColRel() || rRef1.IsRowRel() || rRef1.IsTabRel() )
return true;
}
break;
case svIndex:
{
if( t->GetOpCode() == ocName ) // DB areas always absolute
if( ScRangeData* pRangeData = pDoc->FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex()) )
if( (nRecursion < 42) && lcl_HasRelRef( pDoc, pRangeData->GetCode(), nRecursion + 1 ) )
return true;
}
break;
// #i34474# function result dependent on cell position
case svByte:
{
switch( t->GetOpCode() )
{
case ocRow: // ROW() returns own row index
case ocColumn: // COLUMN() returns own column index
case ocSheet: // SHEET() returns own sheet index
case ocCell: // CELL() may return own cell address
return true;
default:
{
// added to avoid warnings
}
}
}
break;
default:
{
// added to avoid warnings
}
}
}
}
return false;
}
namespace {
void start_listen_to(ScFormulaListener& rListener, const ScTokenArray* pTokens, const ScRangeList& rRangeList)
{
size_t n = rRangeList.size();
for (size_t i = 0; i < n; ++i)
{
const ScRange & rRange = rRangeList[i];
rListener.addTokenArray(pTokens, rRange);
}
}
}
void ScConditionEntry::StartListening()
{
if (!pCondFormat)
return;
mpRepaintTask = std::make_unique<RepaintInIdle>(pCondFormat);
const ScRangeList& rRanges = pCondFormat->GetRange();
mpListener->stopListening();
start_listen_to(*mpListener, pFormula1.get(), rRanges);
start_listen_to(*mpListener, pFormula2.get(), rRanges);
mpListener->setCallback([&]() { mpRepaintTask->Start();});
}
void ScConditionEntry::SetParent(ScConditionalFormat* pParent)
{
pCondFormat = pParent;
StartListening();
}
ScConditionEntry::ScConditionEntry( const ScConditionEntry& r ) :
ScFormatEntry(r.mpDoc),
eOp(r.eOp),
nOptions(r.nOptions),
nVal1(r.nVal1),
nVal2(r.nVal2),
aStrVal1(r.aStrVal1),
aStrVal2(r.aStrVal2),
aStrNmsp1(r.aStrNmsp1),
aStrNmsp2(r.aStrNmsp2),
eTempGrammar1(r.eTempGrammar1),
eTempGrammar2(r.eTempGrammar2),
bIsStr1(r.bIsStr1),
bIsStr2(r.bIsStr2),
aSrcPos(r.aSrcPos),
aSrcString(r.aSrcString),
bRelRef1(r.bRelRef1),
bRelRef2(r.bRelRef2),
bFirstRun(true),
mpListener(new ScFormulaListener(*r.mpDoc)),
eConditionType( r.eConditionType ),
pCondFormat(r.pCondFormat),
mpRepaintTask()
{
// ScTokenArray copy ctor creates a flat copy
if (r.pFormula1)
pFormula1.reset( new ScTokenArray( *r.pFormula1 ) );
if (r.pFormula2)
pFormula2.reset( new ScTokenArray( *r.pFormula2 ) );
StartListening();
// Formula cells are created at IsValid
}
ScConditionEntry::ScConditionEntry( ScDocument& rDocument, const ScConditionEntry& r ) :
ScFormatEntry(&rDocument),
eOp(r.eOp),
nOptions(r.nOptions),
nVal1(r.nVal1),
nVal2(r.nVal2),
aStrVal1(r.aStrVal1),
aStrVal2(r.aStrVal2),
aStrNmsp1(r.aStrNmsp1),
aStrNmsp2(r.aStrNmsp2),
eTempGrammar1(r.eTempGrammar1),
eTempGrammar2(r.eTempGrammar2),
bIsStr1(r.bIsStr1),
bIsStr2(r.bIsStr2),
aSrcPos(r.aSrcPos),
aSrcString(r.aSrcString),
bRelRef1(r.bRelRef1),
bRelRef2(r.bRelRef2),
bFirstRun(true),
mpListener(new ScFormulaListener(rDocument)),
eConditionType( r.eConditionType),
pCondFormat(r.pCondFormat),
mpRepaintTask()
{
// Real copy of the formulas (for Ref Undo)
if (r.pFormula1)
pFormula1 = r.pFormula1->Clone();
if (r.pFormula2)
pFormula2 = r.pFormula2->Clone();
// Formula cells are created at IsValid
// TODO: But not in the Clipboard! So interpret beforehand!
}
ScConditionEntry::ScConditionEntry( ScConditionMode eOper,
const OUString& rExpr1, const OUString& rExpr2, ScDocument& rDocument, const ScAddress& rPos,
const OUString& rExprNmsp1, const OUString& rExprNmsp2,
FormulaGrammar::Grammar eGrammar1, FormulaGrammar::Grammar eGrammar2,
Type eType ) :
ScFormatEntry(&rDocument),
eOp(eOper),
nOptions(0),
nVal1(0.0),
nVal2(0.0),
aStrNmsp1(rExprNmsp1),
aStrNmsp2(rExprNmsp2),
eTempGrammar1(eGrammar1),
eTempGrammar2(eGrammar2),
bIsStr1(false),
bIsStr2(false),
aSrcPos(rPos),
bRelRef1(false),
bRelRef2(false),
bFirstRun(true),
mpListener(new ScFormulaListener(rDocument)),
eConditionType(eType),
pCondFormat(nullptr),
mpRepaintTask()
{
Compile( rExpr1, rExpr2, rExprNmsp1, rExprNmsp2, eGrammar1, eGrammar2, false );
// Formula cells are created at IsValid
}
ScConditionEntry::ScConditionEntry( ScConditionMode eOper,
const ScTokenArray* pArr1, const ScTokenArray* pArr2,
ScDocument& rDocument, const ScAddress& rPos ) :
ScFormatEntry(&rDocument),
eOp(eOper),
nOptions(0),
nVal1(0.0),
nVal2(0.0),
eTempGrammar1(FormulaGrammar::GRAM_DEFAULT),
eTempGrammar2(FormulaGrammar::GRAM_DEFAULT),
bIsStr1(false),
bIsStr2(false),
aSrcPos(rPos),
bRelRef1(false),
bRelRef2(false),
bFirstRun(true),
mpListener(new ScFormulaListener(rDocument)),
eConditionType(ScFormatEntry::Type::Condition),
pCondFormat(nullptr),
mpRepaintTask()
{
if ( pArr1 )
{
pFormula1.reset( new ScTokenArray( *pArr1 ) );
SimplifyCompiledFormula( pFormula1, nVal1, bIsStr1, aStrVal1 );
bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() );
}
if ( pArr2 )
{
pFormula2.reset( new ScTokenArray( *pArr2 ) );
SimplifyCompiledFormula( pFormula2, nVal2, bIsStr2, aStrVal2 );
bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() );
}
StartListening();
// Formula cells are created at IsValid
}
ScConditionEntry::~ScConditionEntry()
{
}
void ScConditionEntry::SimplifyCompiledFormula( std::unique_ptr<ScTokenArray>& rFormula,
double& rVal,
bool& rIsStr,
OUString& rStrVal )
{
if ( rFormula->GetLen() != 1 )
return;
// Single (constant number)?
FormulaToken* pToken = rFormula->FirstToken();
if ( pToken->GetOpCode() != ocPush )
return;
if ( pToken->GetType() == svDouble )
{
rVal = pToken->GetDouble();
rFormula.reset(); // Do not remember as formula
}
else if ( pToken->GetType() == svString )
{
rIsStr = true;
rStrVal = pToken->GetString().getString();
rFormula.reset(); // Do not remember as formula
}
}
void ScConditionEntry::SetOperation(ScConditionMode eMode)
{
eOp = eMode;
}
void ScConditionEntry::Compile( const OUString& rExpr1, const OUString& rExpr2,
const OUString& rExprNmsp1, const OUString& rExprNmsp2,
FormulaGrammar::Grammar eGrammar1, FormulaGrammar::Grammar eGrammar2, bool bTextToReal )
{
if ( !rExpr1.isEmpty() || !rExpr2.isEmpty() )
{
ScCompiler aComp( *mpDoc, aSrcPos );
if ( !rExpr1.isEmpty() )
{
pFormula1.reset();
aComp.SetGrammar( eGrammar1 );
if ( mpDoc->IsImportingXML() && !bTextToReal )
{
// temporary formula string as string tokens
pFormula1.reset( new ScTokenArray(*mpDoc) );
pFormula1->AssignXMLString( rExpr1, rExprNmsp1 );
// bRelRef1 is set when the formula is compiled again (CompileXML)
}
else
{
pFormula1 = aComp.CompileString( rExpr1, rExprNmsp1 );
SimplifyCompiledFormula( pFormula1, nVal1, bIsStr1, aStrVal1 );
bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() );
}
}
if ( !rExpr2.isEmpty() )
{
pFormula2.reset();
aComp.SetGrammar( eGrammar2 );
if ( mpDoc->IsImportingXML() && !bTextToReal )
{
// temporary formula string as string tokens
pFormula2.reset( new ScTokenArray(*mpDoc) );
pFormula2->AssignXMLString( rExpr2, rExprNmsp2 );
// bRelRef2 is set when the formula is compiled again (CompileXML)
}
else
{
pFormula2 = aComp.CompileString( rExpr2, rExprNmsp2 );
SimplifyCompiledFormula( pFormula2, nVal2, bIsStr2, aStrVal2 );
bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() );
}
}
}
StartListening();
}
/**
* Create formula cells
*/
void ScConditionEntry::MakeCells( const ScAddress& rPos )
{
if ( mpDoc->IsClipOrUndo() ) // Never calculate in the Clipboard!
return;
if ( pFormula1 && !pFCell1 && !bRelRef1 )
{
// pFCell1 will hold a flat-copied ScTokenArray sharing ref-counted
// code tokens with pFormula1
pFCell1.reset( new ScFormulaCell(*mpDoc, rPos, *pFormula1) );
pFCell1->SetFreeFlying(true);
pFCell1->StartListeningTo( *mpDoc );
}
if ( pFormula2 && !pFCell2 && !bRelRef2 )
{
// pFCell2 will hold a flat-copied ScTokenArray sharing ref-counted
// code tokens with pFormula2
pFCell2.reset( new ScFormulaCell(*mpDoc, rPos, *pFormula2) );
pFCell2->SetFreeFlying(true);
pFCell2->StartListeningTo( *mpDoc );
}
}
void ScConditionEntry::SetIgnoreBlank(bool bSet)
{
// The bit SC_COND_NOBLANKS is set if blanks are not ignored
// (only of valid)
if (bSet)
nOptions &= ~SC_COND_NOBLANKS;
else
nOptions |= SC_COND_NOBLANKS;
}
void ScConditionEntry::SetCaseSensitive(bool bSet)
{
// The bit SC_COND_CASESENS is set if validation compare is case sensitive
// (only of valid)
if (bSet)
nOptions |= SC_COND_CASESENS;
else
nOptions &= ~SC_COND_CASESENS;
}
/**
* Delete formula cells, so we re-compile at the next IsValid
*/
void ScConditionEntry::CompileAll()
{
pFCell1.reset();
pFCell2.reset();
}
void ScConditionEntry::CompileXML()
{
// First parse the formula source position if it was stored as text
if ( !aSrcString.isEmpty() )
{
ScAddress aNew;
/* XML is always in OOo:A1 format, although R1C1 would be more amenable
* to compression */
if ( aNew.Parse( aSrcString, *mpDoc ) & ScRefFlags::VALID )
aSrcPos = aNew;
// if the position is invalid, there isn't much we can do at this time
aSrcString.clear();
}
// Convert the text tokens that were created during XML import into real tokens.
Compile( GetExpression(aSrcPos, 0, 0, eTempGrammar1),
GetExpression(aSrcPos, 1, 0, eTempGrammar2),
aStrNmsp1, aStrNmsp2, eTempGrammar1, eTempGrammar2, true );
// Importing ocDde/ocWebservice?
if (pFormula1)
mpDoc->CheckLinkFormulaNeedingCheck(*pFormula1);
if (pFormula2)
mpDoc->CheckLinkFormulaNeedingCheck(*pFormula2);
}
void ScConditionEntry::SetSrcString( const OUString& rNew )
{
// aSrcString is only evaluated in CompileXML
SAL_WARN_IF( !mpDoc->IsImportingXML(), "sc", "SetSrcString is only valid for XML import" );
aSrcString = rNew;
}
void ScConditionEntry::SetFormula1( const ScTokenArray& rArray )
{
pFormula1.reset();
if( rArray.GetLen() > 0 )
{
pFormula1.reset( new ScTokenArray( rArray ) );
SimplifyCompiledFormula(pFormula1, nVal1, bIsStr1, aStrVal1);
bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() );
}
StartListening();
}
void ScConditionEntry::SetFormula2( const ScTokenArray& rArray )
{
pFormula2.reset();
if( rArray.GetLen() > 0 )
{
pFormula2.reset( new ScTokenArray( rArray ) );
SimplifyCompiledFormula(pFormula2, nVal2, bIsStr2, aStrVal2);
bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() );
}
StartListening();
}
void ScConditionEntry::UpdateReference( sc::RefUpdateContext& rCxt )
{
if(pCondFormat)
aSrcPos = pCondFormat->GetRange().Combine().aStart;
ScAddress aOldSrcPos = aSrcPos;
bool bChangedPos = false;
if (rCxt.meMode == URM_INSDEL && rCxt.maRange.Contains(aSrcPos))
{
ScAddress aErrorPos( ScAddress::UNINITIALIZED );
if (!aSrcPos.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos, *mpDoc))
{
assert(!"can't move ScConditionEntry");
}
bChangedPos = aSrcPos != aOldSrcPos;
}
if (pFormula1)
{
sc::RefUpdateResult aRes;
switch (rCxt.meMode)
{
case URM_INSDEL:
aRes = pFormula1->AdjustReferenceOnShift(rCxt, aOldSrcPos);
break;
case URM_MOVE:
aRes = pFormula1->AdjustReferenceOnMove(rCxt, aOldSrcPos, aSrcPos);
break;
default:
;
}
if (aRes.mbReferenceModified || bChangedPos)
pFCell1.reset(); // is created again in IsValid
}
if (pFormula2)
{
sc::RefUpdateResult aRes;
switch (rCxt.meMode)
{
case URM_INSDEL:
aRes = pFormula2->AdjustReferenceOnShift(rCxt, aOldSrcPos);
break;
case URM_MOVE:
aRes = pFormula2->AdjustReferenceOnMove(rCxt, aOldSrcPos, aSrcPos);
break;
default:
;
}
if (aRes.mbReferenceModified || bChangedPos)
pFCell2.reset(); // is created again in IsValid
}
StartListening();
}
void ScConditionEntry::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
{
if (pFormula1)
{
pFormula1->AdjustReferenceOnInsertedTab(rCxt, aSrcPos);
pFCell1.reset();
}
if (pFormula2)
{
pFormula2->AdjustReferenceOnInsertedTab(rCxt, aSrcPos);
pFCell2.reset();
}
ScRangeUpdater::UpdateInsertTab(aSrcPos, rCxt);
}
void ScConditionEntry::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
{
if (pFormula1)
{
pFormula1->AdjustReferenceOnDeletedTab(rCxt, aSrcPos);
pFCell1.reset();
}
if (pFormula2)
{
pFormula2->AdjustReferenceOnDeletedTab(rCxt, aSrcPos);
pFCell2.reset();
}
ScRangeUpdater::UpdateDeleteTab(aSrcPos, rCxt);
StartListening();
}
void ScConditionEntry::UpdateMoveTab(sc::RefUpdateMoveTabContext& rCxt)
{
sc::RefUpdateResult aResFinal;
aResFinal.mnTab = aSrcPos.Tab();
if (pFormula1)
{
sc::RefUpdateResult aRes = pFormula1->AdjustReferenceOnMovedTab(rCxt, aSrcPos);
if (aRes.mbValueChanged)
aResFinal.mnTab = aRes.mnTab;
pFCell1.reset();
}
if (pFormula2)
{
sc::RefUpdateResult aRes = pFormula2->AdjustReferenceOnMovedTab(rCxt, aSrcPos);
if (aRes.mbValueChanged)
aResFinal.mnTab = aRes.mnTab;
pFCell2.reset();
}
if (aResFinal.mnTab != aSrcPos.Tab())
aSrcPos.SetTab(aResFinal.mnTab);
StartListening();
}
static bool lcl_IsEqual( const std::unique_ptr<ScTokenArray>& pArr1, const std::unique_ptr<ScTokenArray>& pArr2 )
{
// We only compare the non-RPN array
if ( pArr1 && pArr2 )
return pArr1->EqualTokens( pArr2.get() );
else
return !pArr1 && !pArr2; // Both 0? -> the same
}
// virtual
bool ScConditionEntry::IsEqual( const ScFormatEntry& rOther, bool bIgnoreSrcPos ) const
{
if (GetType() != rOther.GetType())
return false;
const ScConditionEntry& r = static_cast<const ScConditionEntry&>(rOther);
if (eOp != r.eOp || nOptions != r.nOptions
|| !lcl_IsEqual(pFormula1, r.pFormula1) || !lcl_IsEqual(pFormula2, r.pFormula2))
return false;
if (!bIgnoreSrcPos)
{
// for formulas, the reference positions must be compared, too
// (including aSrcString, for inserting the entries during XML import)
if ( ( pFormula1 || pFormula2 ) && ( aSrcPos != r.aSrcPos || aSrcString != r.aSrcString ) )
return false;
}
// If not formulas, compare values
if ( !pFormula1 && ( nVal1 != r.nVal1 || aStrVal1 != r.aStrVal1 || bIsStr1 != r.bIsStr1 ) )
return false;
if ( !pFormula2 && ( nVal2 != r.nVal2 || aStrVal2 != r.aStrVal2 || bIsStr2 != r.bIsStr2 ) )
return false;
return true;
}
void ScConditionEntry::Interpret( const ScAddress& rPos )
{
// Create formula cells
// Note: New Broadcaster (Note cells) may be inserted into the document!
if ( ( pFormula1 && !pFCell1 ) || ( pFormula2 && !pFCell2 ) )
MakeCells( rPos );
// Evaluate formulas
bool bDirty = false; // 1 and 2 separate?
std::optional<ScFormulaCell> oTemp;
ScFormulaCell* pEff1 = pFCell1.get();
if ( bRelRef1 )
{
if (pFormula1)
oTemp.emplace(*mpDoc, rPos, *pFormula1);
else
oTemp.emplace(*mpDoc, rPos);
pEff1 = &*oTemp;
pEff1->SetFreeFlying(true);
}
if ( pEff1 )
{
if (!pEff1->IsRunning()) // Don't create 522
{
//TODO: Query Changed instead of Dirty!
if (pEff1->GetDirty() && !bRelRef1 && mpDoc->GetAutoCalc())
bDirty = true;
if (pEff1->IsValue())
{
bIsStr1 = false;
nVal1 = pEff1->GetValue();
aStrVal1.clear();
}
else
{
bIsStr1 = true;
aStrVal1 = pEff1->GetString().getString();
nVal1 = 0.0;
}
}
}
oTemp.reset();
ScFormulaCell* pEff2 = pFCell2.get(); //@ 1!=2
if ( bRelRef2 )
{
if (pFormula2)
oTemp.emplace(*mpDoc, rPos, *pFormula2);
else
oTemp.emplace(*mpDoc, rPos);
pEff2 = &*oTemp;
pEff2->SetFreeFlying(true);
}
if ( pEff2 )
{
if (!pEff2->IsRunning()) // Don't create 522
{
if (pEff2->GetDirty() && !bRelRef2 && mpDoc->GetAutoCalc())
bDirty = true;
if (pEff2->IsValue())
{
bIsStr2 = false;
nVal2 = pEff2->GetValue();
aStrVal2.clear();
}
else
{
bIsStr2 = true;
aStrVal2 = pEff2->GetString().getString();
nVal2 = 0.0;
}
}
}
oTemp.reset();
// If IsRunning, the last values remain
if (bDirty && !bFirstRun)
{
// Repaint everything for dependent formats
DataChanged();
}
bFirstRun = false;
}
static bool lcl_GetCellContent( ScRefCellValue& rCell, bool bIsStr1, double& rArg, OUString& rArgStr,
const ScDocument* pDoc )
{
if (rCell.isEmpty())
return !bIsStr1;
bool bVal = true;
switch (rCell.getType())
{
case CELLTYPE_VALUE:
rArg = rCell.getDouble();
break;
case CELLTYPE_FORMULA:
{
bVal = rCell.getFormula()->IsValue();
if (bVal)
rArg = rCell.getFormula()->GetValue();
else
rArgStr = rCell.getFormula()->GetString().getString();
}
break;
case CELLTYPE_STRING:
case CELLTYPE_EDIT:
bVal = false;
if (rCell.getType() == CELLTYPE_STRING)
rArgStr = rCell.getSharedString()->getString();
else if (rCell.getEditText())
rArgStr = ScEditUtil::GetString(*rCell.getEditText(), pDoc);
break;
default:
;
}
return bVal;
}
void ScConditionEntry::FillCache() const
{
if(mpCache)
return;
const ScRangeList& rRanges = pCondFormat->GetRange();
mpCache.reset(new ScConditionEntryCache);
size_t nListCount = rRanges.size();
for( size_t i = 0; i < nListCount; i++ )
{
const ScRange & rRange = rRanges[i];
SCROW nRow = rRange.aEnd.Row();
SCCOL nCol = rRange.aEnd.Col();
SCCOL nColStart = rRange.aStart.Col();
SCROW nRowStart = rRange.aStart.Row();
SCTAB nTab = rRange.aStart.Tab();
// temporary fix to workaround slow duplicate entry
// conditions, prevent to use a whole row
if(nRow == mpDoc->MaxRow())
{
bool bShrunk = false;
mpDoc->ShrinkToUsedDataArea(bShrunk, nTab, nColStart, nRowStart,
nCol, nRow, false);
}
for( SCROW r = nRowStart; r <= nRow; r++ )
for( SCCOL c = nColStart; c <= nCol; c++ )
{
ScRefCellValue aCell(*mpDoc, ScAddress(c, r, nTab));
if (aCell.isEmpty())
continue;
double nVal = 0.0;
OUString aStr;
if (!lcl_GetCellContent(aCell, false, nVal, aStr, mpDoc))
{
std::pair<ScConditionEntryCache::StringCacheType::iterator, bool> aResult =
mpCache->maStrings.emplace(aStr, 1);
if(!aResult.second)
aResult.first->second++;
}
else
{
std::pair<ScConditionEntryCache::ValueCacheType::iterator, bool> aResult =
mpCache->maValues.emplace(nVal, 1);
if(!aResult.second)
aResult.first->second++;
++(mpCache->nValueItems);
}
}
}
}
bool ScConditionEntry::IsDuplicate( double nArg, const OUString& rStr ) const
{
FillCache();
if(rStr.isEmpty())
{
ScConditionEntryCache::ValueCacheType::iterator itr = mpCache->maValues.find(nArg);
if(itr == mpCache->maValues.end())
return false;
else
{
return itr->second > 1;
}
}
else
{
ScConditionEntryCache::StringCacheType::iterator itr = mpCache->maStrings.find(rStr);
if(itr == mpCache->maStrings.end())
return false;
else
{
return itr->second > 1;
}
}
}
bool ScConditionEntry::IsTopNElement( double nArg ) const
{
FillCache();
if(mpCache->nValueItems <= nVal1)
return true;
size_t nCells = 0;
for(ScConditionEntryCache::ValueCacheType::const_reverse_iterator itr = mpCache->maValues.rbegin(),
itrEnd = mpCache->maValues.rend(); itr != itrEnd; ++itr)
{
if(nCells >= nVal1)
return false;
if(itr->first <= nArg)
return true;
nCells += itr->second;
}
return true;
}
bool ScConditionEntry::IsBottomNElement( double nArg ) const
{
FillCache();
if(mpCache->nValueItems <= nVal1)
return true;
size_t nCells = 0;
for(const auto& [rVal, rCount] : mpCache->maValues)
{
if(nCells >= nVal1)
return false;
if(rVal >= nArg)
return true;
nCells += rCount;
}
return true;
}
bool ScConditionEntry::IsTopNPercent( double nArg ) const
{
FillCache();
size_t nCells = 0;
size_t nLimitCells = static_cast<size_t>(mpCache->nValueItems*nVal1/100);
for(ScConditionEntryCache::ValueCacheType::const_reverse_iterator itr = mpCache->maValues.rbegin(),
itrEnd = mpCache->maValues.rend(); itr != itrEnd; ++itr)
{
if(nCells >= nLimitCells)
return false;
if(itr->first <= nArg)
return true;
nCells += itr->second;
}
return true;
}
bool ScConditionEntry::IsBottomNPercent( double nArg ) const
{
FillCache();
size_t nCells = 0;
size_t nLimitCells = static_cast<size_t>(mpCache->nValueItems*nVal1/100);
for(const auto& [rVal, rCount] : mpCache->maValues)
{
if(nCells >= nLimitCells)
return false;
if(rVal >= nArg)
return true;
nCells += rCount;
}
return true;
}
bool ScConditionEntry::IsBelowAverage( double nArg, bool bEqual ) const
{
FillCache();
double nSum = std::accumulate(mpCache->maValues.begin(), mpCache->maValues.end(), double(0),
[](const double& rSum, const ScConditionEntryCache::ValueCacheType::value_type& rEntry) {
return rSum + rEntry.first * rEntry.second; });
if(bEqual)
return (nArg <= nSum/mpCache->nValueItems);
else
return (nArg < nSum/mpCache->nValueItems);
}
bool ScConditionEntry::IsAboveAverage( double nArg, bool bEqual ) const
{
FillCache();
double nSum = std::accumulate(mpCache->maValues.begin(), mpCache->maValues.end(), double(0),
[](const double& rSum, const ScConditionEntryCache::ValueCacheType::value_type& rEntry) {
return rSum + rEntry.first * rEntry.second; });
if(bEqual)
return (nArg >= nSum/mpCache->nValueItems);
else
return (nArg > nSum/mpCache->nValueItems);
}
bool ScConditionEntry::IsError( const ScAddress& rPos ) const
{
ScRefCellValue rCell(*mpDoc, rPos);
if (rCell.getType() == CELLTYPE_FORMULA)
{
if (rCell.getFormula()->GetErrCode() != FormulaError::NONE)
return true;
}
return false;
}
bool ScConditionEntry::IsValid( double nArg, const ScAddress& rPos ) const
{
// Interpret must already have been called
if ( bIsStr1 )
{
switch( eOp )
{
case ScConditionMode::BeginsWith:
case ScConditionMode::EndsWith:
case ScConditionMode::ContainsText:
case ScConditionMode::NotContainsText:
break;
case ScConditionMode::NotEqual:
return true;
default:
return false;
}
}
if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
if ( bIsStr2 )
return false;
double nComp1 = nVal1; // Copy, so that it can be changed
double nComp2 = nVal2;
if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
if ( nComp1 > nComp2 )
// Right order for value range
std::swap( nComp1, nComp2 );
// All corner cases need to be tested with ::rtl::math::approxEqual!
bool bValid = false;
switch (eOp)
{
case ScConditionMode::NONE:
break; // Always sal_False
case ScConditionMode::Equal:
bValid = ::rtl::math::approxEqual( nArg, nComp1 );
break;
case ScConditionMode::NotEqual:
bValid = !::rtl::math::approxEqual( nArg, nComp1 );
break;
case ScConditionMode::Greater:
bValid = ( nArg > nComp1 ) && !::rtl::math::approxEqual( nArg, nComp1 );
break;
case ScConditionMode::EqGreater:
bValid = ( nArg >= nComp1 ) || ::rtl::math::approxEqual( nArg, nComp1 );
break;
case ScConditionMode::Less:
bValid = ( nArg < nComp1 ) && !::rtl::math::approxEqual( nArg, nComp1 );
break;
case ScConditionMode::EqLess:
bValid = ( nArg <= nComp1 ) || ::rtl::math::approxEqual( nArg, nComp1 );
break;
case ScConditionMode::Between:
bValid = ( nArg >= nComp1 && nArg <= nComp2 ) ||
::rtl::math::approxEqual( nArg, nComp1 ) || ::rtl::math::approxEqual( nArg, nComp2 );
break;
case ScConditionMode::NotBetween:
bValid = ( nArg < nComp1 || nArg > nComp2 ) &&
!::rtl::math::approxEqual( nArg, nComp1 ) && !::rtl::math::approxEqual( nArg, nComp2 );
break;
case ScConditionMode::Duplicate:
case ScConditionMode::NotDuplicate:
if( pCondFormat )
{
bValid = IsDuplicate( nArg, OUString() );
if( eOp == ScConditionMode::NotDuplicate )
bValid = !bValid;
}
break;
case ScConditionMode::Direct:
bValid = nComp1 != 0.0;
break;
case ScConditionMode::Top10:
bValid = IsTopNElement( nArg );
break;
case ScConditionMode::Bottom10:
bValid = IsBottomNElement( nArg );
break;
case ScConditionMode::TopPercent:
bValid = IsTopNPercent( nArg );
break;
case ScConditionMode::BottomPercent:
bValid = IsBottomNPercent( nArg );
break;
case ScConditionMode::AboveAverage:
case ScConditionMode::AboveEqualAverage:
bValid = IsAboveAverage( nArg, eOp == ScConditionMode::AboveEqualAverage );
break;
case ScConditionMode::BelowAverage:
case ScConditionMode::BelowEqualAverage:
bValid = IsBelowAverage( nArg, eOp == ScConditionMode::BelowEqualAverage );
break;
case ScConditionMode::Error:
case ScConditionMode::NoError:
bValid = IsError( rPos );
if( eOp == ScConditionMode::NoError )
bValid = !bValid;
break;
case ScConditionMode::BeginsWith:
if(aStrVal1.isEmpty())
{
OUString aStr = OUString::number(nVal1);
OUString aStr2 = OUString::number(nArg);
bValid = aStr2.startsWith(aStr);
}
else
{
OUString aStr2 = OUString::number(nArg);
bValid = aStr2.startsWith(aStrVal1);
}
break;
case ScConditionMode::EndsWith:
if(aStrVal1.isEmpty())
{
OUString aStr = OUString::number(nVal1);
OUString aStr2 = OUString::number(nArg);
bValid = aStr2.endsWith(aStr);
}
else
{
OUString aStr2 = OUString::number(nArg);
bValid = aStr2.endsWith(aStrVal1);
}
break;
case ScConditionMode::ContainsText:
case ScConditionMode::NotContainsText:
if(aStrVal1.isEmpty())
{
OUString aStr = OUString::number(nVal1);
OUString aStr2 = OUString::number(nArg);
bValid = aStr2.indexOf(aStr) != -1;
}
else
{
OUString aStr2 = OUString::number(nArg);
bValid = aStr2.indexOf(aStrVal1) != -1;
}
if( eOp == ScConditionMode::NotContainsText )
bValid = !bValid;
break;
case ScConditionMode::Formula:
case ScConditionMode::Today:
case ScConditionMode::Yesterday:
case ScConditionMode::Tomorrow:
case ScConditionMode::Last7days:
case ScConditionMode::ThisWeek:
case ScConditionMode::LastWeek:
case ScConditionMode::NextWeek:
case ScConditionMode::ThisMonth:
case ScConditionMode::LastMonth:
case ScConditionMode::NextMonth:
case ScConditionMode::ThisYear:
case ScConditionMode::LastYear:
case ScConditionMode::NextYear:
bValid = true;
break;
default:
SAL_WARN("sc", "unknown operation at ScConditionEntry");
break;
}
return bValid;
}
bool ScConditionEntry::IsValidStr( const OUString& rArg, const ScAddress& rPos ) const
{
bool bValid = false;
// Interpret must already have been called
if ( eOp == ScConditionMode::Direct ) // Formula is independent from the content
return nVal1 != 0.0;
if ( eOp == ScConditionMode::Duplicate || eOp == ScConditionMode::NotDuplicate )
{
if( pCondFormat && !rArg.isEmpty() )
{
bValid = IsDuplicate( 0.0, rArg );
if( eOp == ScConditionMode::NotDuplicate )
bValid = !bValid;
return bValid;
}
}
if (eOp == ScConditionMode::Error)
return IsError(rPos);
if (eOp == ScConditionMode::NoError)
return !IsError(rPos);
// If number contains condition, always false, except for "not equal".
if (!bIsStr1)
return ( eOp == ScConditionMode::NotEqual );
if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
if ( !bIsStr2 )
return false;
OUString aUpVal1( aStrVal1 ); //TODO: As a member? (Also set in Interpret)
OUString aUpVal2( aStrVal2 );
switch ( eOp )
{
case ScConditionMode::Equal:
bValid = ScGlobal::GetTransliteration(IsCaseSensitive()).isEqual(aUpVal1, rArg);
break;
case ScConditionMode::NotEqual:
bValid = !ScGlobal::GetTransliteration(IsCaseSensitive()).isEqual(aUpVal1, rArg);
break;
case ScConditionMode::TopPercent:
case ScConditionMode::BottomPercent:
case ScConditionMode::Top10:
case ScConditionMode::Bottom10:
case ScConditionMode::AboveAverage:
case ScConditionMode::BelowAverage:
return false;
case ScConditionMode::BeginsWith:
bValid = ScGlobal::GetTransliteration(IsCaseSensitive()).isMatch(aUpVal1, rArg);
break;
case ScConditionMode::EndsWith:
{
sal_Int32 nStart = rArg.getLength();
const sal_Int32 nLen = aUpVal1.getLength();
if (nLen > nStart)
bValid = false;
else
{
nStart = nStart - nLen;
sal_Int32 nMatch1(0), nMatch2(0);
bValid = ScGlobal::GetTransliteration(IsCaseSensitive()).equals(rArg, nStart, nLen, nMatch1,
aUpVal1, 0, nLen, nMatch2);
}
}
break;
case ScConditionMode::ContainsText:
case ScConditionMode::NotContainsText:
{
const OUString aArgStr(!IsCaseSensitive() ? ScGlobal::getCharClass().lowercase(rArg) : rArg);
const OUString aValStr(!IsCaseSensitive() ? ScGlobal::getCharClass().lowercase(aUpVal1) : aUpVal1);
bValid = aArgStr.indexOf(aValStr) != -1;
if(eOp == ScConditionMode::NotContainsText)
bValid = !bValid;
}
break;
default:
{
sal_Int32 nCompare = ScGlobal::GetCollator(IsCaseSensitive()).compareString(
rArg, aUpVal1 );
switch ( eOp )
{
case ScConditionMode::Greater:
bValid = ( nCompare > 0 );
break;
case ScConditionMode::EqGreater:
bValid = ( nCompare >= 0 );
break;
case ScConditionMode::Less:
bValid = ( nCompare < 0 );
break;
case ScConditionMode::EqLess:
bValid = ( nCompare <= 0 );
break;
case ScConditionMode::Between:
case ScConditionMode::NotBetween:
{
const sal_Int32 nCompare2 = ScGlobal::GetCollator(IsCaseSensitive()).compareString(rArg, aUpVal2);
// Test for NOTBETWEEN:
bValid = (nCompare > 0 && nCompare2 > 0) || (nCompare < 0 && nCompare2 < 0);
if ( eOp == ScConditionMode::Between )
bValid = !bValid;
break;
}
default:
SAL_WARN("sc", "unknown operation in ScConditionEntry");
bValid = false;
break;
}
}
}
return bValid;
}
bool ScConditionEntry::IsCellValid( ScRefCellValue& rCell, const ScAddress& rPos ) const
{
const_cast<ScConditionEntry*>(this)->Interpret(rPos); // Evaluate formula
if ( eOp == ScConditionMode::Direct )
return nVal1 != 0.0;
double nArg = 0.0;
OUString aArgStr;
bool bVal = lcl_GetCellContent( rCell, bIsStr1, nArg, aArgStr, mpDoc );
if (bVal)
return IsValid( nArg, rPos );
else
return IsValidStr( aArgStr, rPos );
}
OUString ScConditionEntry::GetExpression( const ScAddress& rCursor, sal_uInt16 nIndex,
sal_uInt32 nNumFmt,
const FormulaGrammar::Grammar eGrammar ) const
{
assert( nIndex <= 1);
OUString aRet;
if ( FormulaGrammar::isEnglish( eGrammar) && nNumFmt == 0 )
nNumFmt = mpDoc->GetFormatTable()->GetStandardIndex( LANGUAGE_ENGLISH_US );
if ( nIndex==0 )
{
if ( pFormula1 )
{
ScCompiler aComp(*mpDoc, rCursor, *pFormula1, eGrammar);
OUStringBuffer aBuffer;
aComp.CreateStringFromTokenArray( aBuffer );
aRet = aBuffer.makeStringAndClear();
}
else if (bIsStr1)
{
aRet = "\"" + aStrVal1 + "\"";
}
else
aRet = mpDoc->GetFormatTable()->GetInputLineString(nVal1, nNumFmt);
}
else if ( nIndex==1 )
{
if ( pFormula2 )
{
ScCompiler aComp(*mpDoc, rCursor, *pFormula2, eGrammar);
OUStringBuffer aBuffer;
aComp.CreateStringFromTokenArray( aBuffer );
aRet = aBuffer.makeStringAndClear();
}
else if (bIsStr2)
{
aRet = "\"" + aStrVal2 + "\"";
}
else
aRet = mpDoc->GetFormatTable()->GetInputLineString(nVal2, nNumFmt);
}
return aRet;
}
std::unique_ptr<ScTokenArray> ScConditionEntry::CreateFlatCopiedTokenArray( sal_uInt16 nIndex ) const
{
assert(nIndex <= 1);
std::unique_ptr<ScTokenArray> pRet;
if ( nIndex==0 )
{
if ( pFormula1 )
pRet.reset(new ScTokenArray( *pFormula1 ));
else
{
pRet.reset(new ScTokenArray(*mpDoc));
if (bIsStr1)
{
svl::SharedStringPool& rSPool = mpDoc->GetSharedStringPool();
pRet->AddString(rSPool.intern(aStrVal1));
}
else
pRet->AddDouble( nVal1 );
}
}
else if ( nIndex==1 )
{
if ( pFormula2 )
pRet.reset(new ScTokenArray( *pFormula2 ));
else
{
pRet.reset(new ScTokenArray(*mpDoc));
if (bIsStr2)
{
svl::SharedStringPool& rSPool = mpDoc->GetSharedStringPool();
pRet->AddString(rSPool.intern(aStrVal2));
}
else
pRet->AddDouble( nVal2 );
}
}
return pRet;
}
/**
* Return a position that's adjusted to allow textual representation
* of expressions if possible
*/
ScAddress ScConditionEntry::GetValidSrcPos() const
{
SCTAB nMinTab = aSrcPos.Tab();
SCTAB nMaxTab = nMinTab;
for (sal_uInt16 nPass = 0; nPass < 2; nPass++)
{
ScTokenArray* pFormula = nPass ? pFormula2.get() : pFormula1.get();
if (pFormula)
{
for ( auto t: pFormula->References() )
{
ScSingleRefData& rRef1 = *t->GetSingleRef();
ScAddress aAbs = rRef1.toAbs(*mpDoc, aSrcPos);
if (!rRef1.IsTabDeleted())
{
if (aAbs.Tab() < nMinTab)
nMinTab = aAbs.Tab();
if (aAbs.Tab() > nMaxTab)
nMaxTab = aAbs.Tab();
}
if ( t->GetType() == svDoubleRef )
{
ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
aAbs = rRef2.toAbs(*mpDoc, aSrcPos);
if (!rRef2.IsTabDeleted())
{
if (aAbs.Tab() < nMinTab)
nMinTab = aAbs.Tab();
if (aAbs.Tab() > nMaxTab)
nMaxTab = aAbs.Tab();
}
}
}
}
}
ScAddress aValidPos = aSrcPos;
SCTAB nTabCount = mpDoc->GetTableCount();
if ( nMaxTab >= nTabCount && nMinTab > 0 )
aValidPos.SetTab( aSrcPos.Tab() - nMinTab ); // so the lowest tab ref will be on 0
if ( aValidPos.Tab() >= nTabCount )
aValidPos.SetTab( nTabCount - 1 ); // ensure a valid position even if some references will be invalid
return aValidPos;
}
void ScConditionEntry::DataChanged() const
{
//FIXME: Nothing so far
}
bool ScConditionEntry::MarkUsedExternalReferences() const
{
bool bAllMarked = false;
for (sal_uInt16 nPass = 0; !bAllMarked && nPass < 2; nPass++)
{
ScTokenArray* pFormula = nPass ? pFormula2.get() : pFormula1.get();
if (pFormula)
bAllMarked = mpDoc->MarkUsedExternalReferences(*pFormula, aSrcPos);
}
return bAllMarked;
}
ScFormatEntry* ScConditionEntry::Clone(ScDocument* pDoc) const
{
return new ScConditionEntry(*pDoc, *this);
}
ScConditionMode ScConditionEntry::GetModeFromApi(css::sheet::ConditionOperator nOperation)
{
ScConditionMode eMode = ScConditionMode::NONE;
switch (static_cast<sal_Int32>(nOperation))
{
case css::sheet::ConditionOperator2::EQUAL:
eMode = ScConditionMode::Equal;
break;
case css::sheet::ConditionOperator2::LESS:
eMode = ScConditionMode::Less;
break;
case css::sheet::ConditionOperator2::GREATER:
eMode = ScConditionMode::Greater;
break;
case css::sheet::ConditionOperator2::LESS_EQUAL:
eMode = ScConditionMode::EqLess;
break;
case css::sheet::ConditionOperator2::GREATER_EQUAL:
eMode = ScConditionMode::EqGreater;
break;
case css::sheet::ConditionOperator2::NOT_EQUAL:
eMode = ScConditionMode::NotEqual;
break;
case css::sheet::ConditionOperator2::BETWEEN:
eMode = ScConditionMode::Between;
break;
case css::sheet::ConditionOperator2::NOT_BETWEEN:
eMode = ScConditionMode::NotBetween;
break;
case css::sheet::ConditionOperator2::FORMULA:
eMode = ScConditionMode::Direct;
break;
case css::sheet::ConditionOperator2::DUPLICATE:
eMode = ScConditionMode::Duplicate;
break;
case css::sheet::ConditionOperator2::NOT_DUPLICATE:
eMode = ScConditionMode::NotDuplicate;
break;
default:
break;
}
return eMode;
}
void ScConditionEntry::startRendering()
{
mpCache.reset();
}
void ScConditionEntry::endRendering()
{
mpCache.reset();
}
bool ScConditionEntry::NeedsRepaint() const
{
return mpListener->NeedsRepaint();
}
ScCondFormatEntry::ScCondFormatEntry( ScConditionMode eOper,
const OUString& rExpr1, const OUString& rExpr2,
ScDocument& rDocument, const ScAddress& rPos,
OUString aStyle,
const OUString& rExprNmsp1, const OUString& rExprNmsp2,
FormulaGrammar::Grammar eGrammar1,
FormulaGrammar::Grammar eGrammar2,
ScFormatEntry::Type eType ) :
ScConditionEntry( eOper, rExpr1, rExpr2, rDocument, rPos, rExprNmsp1, rExprNmsp2, eGrammar1, eGrammar2, eType ),
aStyleName(std::move( aStyle )),
eCondFormatType( eType )
{
}
ScCondFormatEntry::ScCondFormatEntry( ScConditionMode eOper,
const ScTokenArray* pArr1, const ScTokenArray* pArr2,
ScDocument& rDocument, const ScAddress& rPos,
OUString aStyle ) :
ScConditionEntry( eOper, pArr1, pArr2, rDocument, rPos ),
aStyleName(std::move( aStyle ))
{
}
ScCondFormatEntry::ScCondFormatEntry( const ScCondFormatEntry& r ) :
ScConditionEntry( r ),
aStyleName( r.aStyleName ),
eCondFormatType( r.eCondFormatType)
{
}
ScCondFormatEntry::ScCondFormatEntry( ScDocument& rDocument, const ScCondFormatEntry& r ) :
ScConditionEntry( rDocument, r ),
aStyleName( r.aStyleName ),
eCondFormatType( r.eCondFormatType)
{
}
// virtual
bool ScCondFormatEntry::IsEqual( const ScFormatEntry& r, bool bIgnoreSrcPos ) const
{
return ScConditionEntry::IsEqual(r, bIgnoreSrcPos) &&
(aStyleName == static_cast<const ScCondFormatEntry&>(r).aStyleName);
}
ScCondFormatEntry::~ScCondFormatEntry()
{
}
void ScCondFormatEntry::DataChanged() const
{
if ( pCondFormat )
pCondFormat->DoRepaint();
}
ScFormatEntry* ScCondFormatEntry::Clone( ScDocument* pDoc ) const
{
return new ScCondFormatEntry( *pDoc, *this );
}
void ScConditionEntry::CalcAll()
{
if (pFCell1 || pFCell2)
{
if (pFCell1)
pFCell1->SetDirty();
if (pFCell2)
pFCell2->SetDirty();
pCondFormat->DoRepaint();
}
}
ScCondDateFormatEntry::ScCondDateFormatEntry( ScDocument* pDoc )
: ScFormatEntry( pDoc )
, meType(condformat::TODAY)
{
}
ScCondDateFormatEntry::ScCondDateFormatEntry( ScDocument* pDoc, const ScCondDateFormatEntry& rFormat ):
ScFormatEntry( pDoc ),
meType( rFormat.meType ),
maStyleName( rFormat.maStyleName )
{
}
bool ScCondDateFormatEntry::IsValid( const ScAddress& rPos ) const
{
ScRefCellValue rCell(*mpDoc, rPos);
if (!rCell.hasNumeric())
// non-numerical cell.
return false;
if( !mpCache )
mpCache.reset( new Date( Date::SYSTEM ) );
const Date& rActDate = *mpCache;
SvNumberFormatter* pFormatter = mpDoc->GetFormatTable();
sal_Int32 nCurrentDate = rActDate - pFormatter->GetNullDate();
double nVal = rCell.getValue();
sal_Int32 nCellDate = static_cast<sal_Int32>(::rtl::math::approxFloor(nVal));
Date aCellDate = pFormatter->GetNullDate();
aCellDate.AddDays(nCellDate);
switch(meType)
{
case condformat::TODAY:
if( nCurrentDate == nCellDate )
return true;
break;
case condformat::TOMORROW:
if( nCurrentDate == nCellDate -1 )
return true;
break;
case condformat::YESTERDAY:
if( nCurrentDate == nCellDate + 1)
return true;
break;
case condformat::LAST7DAYS:
if( nCurrentDate >= nCellDate && nCurrentDate - 7 < nCellDate )
return true;
break;
case condformat::LASTWEEK:
{
const DayOfWeek eDay = rActDate.GetDayOfWeek();
if( eDay != SUNDAY )
{
Date aBegin(rActDate - (8 + static_cast<sal_Int32>(eDay)));
Date aEnd(rActDate - (2 + static_cast<sal_Int32>(eDay)));
return aCellDate.IsBetween( aBegin, aEnd );
}
else
{
Date aBegin(rActDate - 8);
Date aEnd(rActDate - 1);
return aCellDate.IsBetween( aBegin, aEnd );
}
}
break;
case condformat::THISWEEK:
{
const DayOfWeek eDay = rActDate.GetDayOfWeek();
if( eDay != SUNDAY )
{
Date aBegin(rActDate - (1 + static_cast<sal_Int32>(eDay)));
Date aEnd(rActDate + (5 - static_cast<sal_Int32>(eDay)));
return aCellDate.IsBetween( aBegin, aEnd );
}
else
{
Date aEnd( rActDate + 6);
return aCellDate.IsBetween( rActDate, aEnd );
}
}
break;
case condformat::NEXTWEEK:
{
const DayOfWeek eDay = rActDate.GetDayOfWeek();
if( eDay != SUNDAY )
{
return aCellDate.IsBetween( rActDate + (6 - static_cast<sal_Int32>(eDay)),
rActDate + (12 - static_cast<sal_Int32>(eDay)) );
}
else
{
return aCellDate.IsBetween( rActDate + 7, rActDate + 13 );
}
}
break;
case condformat::LASTMONTH:
if( rActDate.GetMonth() == 1 )
{
if( aCellDate.GetMonth() == 12 && rActDate.GetYear() == aCellDate.GetNextYear() )
return true;
}
else if( rActDate.GetYear() == aCellDate.GetYear() )
{
if( rActDate.GetMonth() == aCellDate.GetMonth() + 1)
return true;
}
break;
case condformat::THISMONTH:
if( rActDate.GetYear() == aCellDate.GetYear() )
{
if( rActDate.GetMonth() == aCellDate.GetMonth() )
return true;
}
break;
case condformat::NEXTMONTH:
if( rActDate.GetMonth() == 12 )
{
if( aCellDate.GetMonth() == 1 && rActDate.GetYear() == aCellDate.GetYear() - 1 )
return true;
}
else if( rActDate.GetYear() == aCellDate.GetYear() )
{
if( rActDate.GetMonth() == aCellDate.GetMonth() - 1)
return true;
}
break;
case condformat::LASTYEAR:
if( rActDate.GetYear() == aCellDate.GetNextYear() )
return true;
break;
case condformat::THISYEAR:
if( rActDate.GetYear() == aCellDate.GetYear() )
return true;
break;
case condformat::NEXTYEAR:
if( rActDate.GetYear() == aCellDate.GetYear() - 1 )
return true;
break;
}
return false;
}
void ScCondDateFormatEntry::SetDateType( condformat::ScCondFormatDateType eType )
{
meType = eType;
}
void ScCondDateFormatEntry::SetStyleName( const OUString& rStyleName )
{
maStyleName = rStyleName;
}
ScFormatEntry* ScCondDateFormatEntry::Clone( ScDocument* pDoc ) const
{
return new ScCondDateFormatEntry( pDoc, *this );
}
void ScCondDateFormatEntry::startRendering()
{
mpCache.reset();
}
void ScCondDateFormatEntry::endRendering()
{
mpCache.reset();
}
ScColorFormatCache::ScColorFormatCache(ScDocument& rDoc, const ScRangeList& rRanges) :
mrDoc(rDoc)
{
if (mrDoc.IsClipOrUndo())
return;
for (const ScRange& rRange: rRanges)
mrDoc.StartListeningArea(rRange, false, this);
}
ScColorFormatCache::~ScColorFormatCache()
{
if (mrDoc.IsClipOrUndo())
return;
EndListeningAll();
}
void ScColorFormatCache::Notify(const SfxHint& rHint)
{
if (rHint.GetId() == SfxHintId::Dying)
{
EndListeningAll();
return;
}
maValues.clear();
}
ScConditionalFormat::ScConditionalFormat(sal_uInt32 nNewKey, ScDocument* pDocument) :
pDoc( pDocument ),
nKey( nNewKey )
{
}
std::unique_ptr<ScConditionalFormat> ScConditionalFormat::Clone(ScDocument* pNewDoc) const
{
// Real copy of the formula (for Ref Undo/between documents)
if (!pNewDoc)
pNewDoc = pDoc;
std::unique_ptr<ScConditionalFormat> pNew(new ScConditionalFormat(nKey, pNewDoc));
pNew->SetRange( maRanges ); // prerequisite for listeners
for (const auto& rxEntry : maEntries)
{
ScFormatEntry* pNewEntry = rxEntry->Clone(pNewDoc);
pNew->maEntries.push_back( std::unique_ptr<ScFormatEntry>(pNewEntry) );
pNewEntry->SetParent(pNew.get());
}
return pNew;
}
bool ScConditionalFormat::EqualEntries( const ScConditionalFormat& r, bool bIgnoreSrcPos ) const
{
if( size() != r.size())
return false;
//TODO: Test for same entries in reverse order?
if (! std::equal(maEntries.begin(), maEntries.end(), r.maEntries.begin(),
[&bIgnoreSrcPos](const std::unique_ptr<ScFormatEntry>& p1, const std::unique_ptr<ScFormatEntry>& p2) -> bool
{
return p1->IsEqual(*p2, bIgnoreSrcPos);
}))
return false;
// right now don't check for same range
// we only use this method to merge same conditional formats from
// old ODF data structure
return true;
}
void ScConditionalFormat::SetRange( const ScRangeList& rRanges )
{
maRanges = rRanges;
SAL_WARN_IF(maRanges.empty(), "sc", "the conditional format range is empty! will result in a crash later!");
ResetCache();
}
void ScConditionalFormat::AddEntry( ScFormatEntry* pNew )
{
maEntries.push_back( std::unique_ptr<ScFormatEntry>(pNew));
pNew->SetParent(this);
}
void ScConditionalFormat::RemoveEntry(size_t n)
{
if (n < maEntries.size())
{
maEntries.erase(maEntries.begin() + n);
DoRepaint();
}
}
bool ScConditionalFormat::IsEmpty() const
{
return maEntries.empty();
}
size_t ScConditionalFormat::size() const
{
return maEntries.size();
}
ScDocument* ScConditionalFormat::GetDocument()
{
return pDoc;
}
ScConditionalFormat::~ScConditionalFormat()
{
}
const ScFormatEntry* ScConditionalFormat::GetEntry( sal_uInt16 nPos ) const
{
if ( nPos < size() )
return maEntries[nPos].get();
else
return nullptr;
}
OUString ScConditionalFormat::GetCellStyle( ScRefCellValue& rCell, const ScAddress& rPos ) const
{
for (const auto& rxEntry : maEntries)
{
if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
{
const ScCondFormatEntry& rEntry = static_cast<const ScCondFormatEntry&>(*rxEntry);
if (rEntry.IsCellValid(rCell, rPos))
return rEntry.GetStyle();
}
else if(rxEntry->GetType() == ScFormatEntry::Type::Date)
{
const ScCondDateFormatEntry& rEntry = static_cast<const ScCondDateFormatEntry&>(*rxEntry);
if (rEntry.IsValid( rPos ))
return rEntry.GetStyleName();
}
}
return OUString();
}
ScCondFormatData ScConditionalFormat::GetData( ScRefCellValue& rCell, const ScAddress& rPos ) const
{
ScCondFormatData aData;
for(const auto& rxEntry : maEntries)
{
if( (rxEntry->GetType() == ScFormatEntry::Type::Condition ||
rxEntry->GetType() == ScFormatEntry::Type::ExtCondition) &&
aData.aStyleName.isEmpty())
{
const ScCondFormatEntry& rEntry = static_cast<const ScCondFormatEntry&>(*rxEntry);
if (rEntry.IsCellValid(rCell, rPos))
aData.aStyleName = rEntry.GetStyle();
}
else if(rxEntry->GetType() == ScFormatEntry::Type::Colorscale && !aData.mxColorScale)
{
const ScColorScaleFormat& rEntry = static_cast<const ScColorScaleFormat&>(*rxEntry);
aData.mxColorScale = rEntry.GetColor(rPos);
}
else if(rxEntry->GetType() == ScFormatEntry::Type::Databar && !aData.pDataBar)
{
const ScDataBarFormat& rEntry = static_cast<const ScDataBarFormat&>(*rxEntry);
aData.pDataBar = rEntry.GetDataBarInfo(rPos);
}
else if(rxEntry->GetType() == ScFormatEntry::Type::Iconset && !aData.pIconSet)
{
const ScIconSetFormat& rEntry = static_cast<const ScIconSetFormat&>(*rxEntry);
aData.pIconSet = rEntry.GetIconSetInfo(rPos);
}
else if(rxEntry->GetType() == ScFormatEntry::Type::Date && aData.aStyleName.isEmpty())
{
const ScCondDateFormatEntry& rEntry = static_cast<const ScCondDateFormatEntry&>(*rxEntry);
if ( rEntry.IsValid( rPos ) )
aData.aStyleName = rEntry.GetStyleName();
}
}
return aData;
}
void ScConditionalFormat::DoRepaint()
{
// all conditional format cells
pDoc->RepaintRange( maRanges );
}
void ScConditionalFormat::CompileAll()
{
for(auto& rxEntry : maEntries)
if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
static_cast<ScCondFormatEntry&>(*rxEntry).CompileAll();
}
void ScConditionalFormat::CompileXML()
{
for(auto& rxEntry : maEntries)
if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
static_cast<ScCondFormatEntry&>(*rxEntry).CompileXML();
}
void ScConditionalFormat::UpdateReference( sc::RefUpdateContext& rCxt, bool bCopyAsMove )
{
if (rCxt.meMode == URM_COPY && bCopyAsMove)
{
// ScConditionEntry::UpdateReference() obtains its aSrcPos from
// maRanges and does not update it on URM_COPY, but it's needed later
// for the moved position, so update maRanges beforehand.
maRanges.UpdateReference(URM_MOVE, pDoc, rCxt.maRange, rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta);
for (auto& rxEntry : maEntries)
rxEntry->UpdateReference(rCxt);
}
else
{
for (auto& rxEntry : maEntries)
rxEntry->UpdateReference(rCxt);
maRanges.UpdateReference(rCxt.meMode, pDoc, rCxt.maRange, rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta);
}
ResetCache();
}
void ScConditionalFormat::InsertRow(SCTAB nTab, SCCOL nColStart, SCCOL nColEnd, SCROW nRowPos, SCSIZE nSize)
{
maRanges.InsertRow(nTab, nColStart, nColEnd, nRowPos, nSize);
ResetCache();
}
void ScConditionalFormat::InsertCol(SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, SCCOL nColPos, SCSIZE nSize)
{
maRanges.InsertCol(nTab, nRowStart, nRowEnd, nColPos, nSize);
ResetCache();
}
void ScConditionalFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
{
for (size_t i = 0, n = maRanges.size(); i < n; ++i)
{
// We assume that the start and end sheet indices are equal.
ScRange & rRange = maRanges[i];
SCTAB nTab = rRange.aStart.Tab();
if (nTab < rCxt.mnInsertPos)
// Unaffected.
continue;
rRange.aStart.IncTab(rCxt.mnSheets);
rRange.aEnd.IncTab(rCxt.mnSheets);
}
ResetCache();
for (auto& rxEntry : maEntries)
rxEntry->UpdateInsertTab(rCxt);
}
void ScConditionalFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
{
for (size_t i = 0, n = maRanges.size(); i < n; ++i)
{
// We assume that the start and end sheet indices are equal.
ScRange & rRange = maRanges[i];
SCTAB nTab = rRange.aStart.Tab();
if (nTab < rCxt.mnDeletePos)
// Left of the deleted sheet(s). Unaffected.
continue;
if (nTab <= rCxt.mnDeletePos+rCxt.mnSheets-1)
{
// On the deleted sheet(s).
rRange.aStart.SetTab(-1);
rRange.aEnd.SetTab(-1);
continue;
}
// Right of the deleted sheet(s). Adjust the sheet indices.
rRange.aStart.IncTab(-1*rCxt.mnSheets);
rRange.aEnd.IncTab(-1*rCxt.mnSheets);
}
ResetCache();
for (auto& rxEntry : maEntries)
rxEntry->UpdateDeleteTab(rCxt);
}
void ScConditionalFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
{
size_t n = maRanges.size();
SCTAB nMinTab = std::min<SCTAB>(rCxt.mnOldPos, rCxt.mnNewPos);
SCTAB nMaxTab = std::max<SCTAB>(rCxt.mnOldPos, rCxt.mnNewPos);
for(size_t i = 0; i < n; ++i)
{
ScRange & rRange = maRanges[i];
SCTAB nTab = rRange.aStart.Tab();
if(nTab < nMinTab || nTab > nMaxTab)
{
continue;
}
if (nTab == rCxt.mnOldPos)
{
rRange.aStart.SetTab(rCxt.mnNewPos);
rRange.aEnd.SetTab(rCxt.mnNewPos);
continue;
}
if (rCxt.mnNewPos < rCxt.mnOldPos)
{
rRange.aStart.IncTab();
rRange.aEnd.IncTab();
}
else
{
rRange.aStart.IncTab(-1);
rRange.aEnd.IncTab(-1);
}
}
ResetCache();
for (auto& rxEntry : maEntries)
rxEntry->UpdateMoveTab(rCxt);
}
void ScConditionalFormat::DeleteArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
{
if (maRanges.empty())
return;
SCTAB nTab = maRanges[0].aStart.Tab();
maRanges.DeleteArea( nCol1, nRow1, nTab, nCol2, nRow2, nTab );
ResetCache();
}
void ScConditionalFormat::RenameCellStyle(std::u16string_view rOld, const OUString& rNew)
{
for(const auto& rxEntry : maEntries)
if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
{
ScCondFormatEntry& rFormat = static_cast<ScCondFormatEntry&>(*rxEntry);
if(rFormat.GetStyle() == rOld)
rFormat.UpdateStyleName( rNew );
}
}
bool ScConditionalFormat::MarkUsedExternalReferences() const
{
bool bAllMarked = false;
for(const auto& rxEntry : maEntries)
if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
{
const ScCondFormatEntry& rFormat = static_cast<const ScCondFormatEntry&>(*rxEntry);
bAllMarked = rFormat.MarkUsedExternalReferences();
if (bAllMarked)
break;
}
return bAllMarked;
}
void ScConditionalFormat::startRendering()
{
for(auto& rxEntry : maEntries)
{
rxEntry->startRendering();
}
}
void ScConditionalFormat::endRendering()
{
for(auto& rxEntry : maEntries)
{
rxEntry->endRendering();
}
}
void ScConditionalFormat::updateValues()
{
for(auto& rxEntry : maEntries)
{
rxEntry->updateValues();
}
}
void ScConditionalFormat::CalcAll()
{
for(const auto& rxEntry : maEntries)
{
if (rxEntry->GetType() == ScFormatEntry::Type::Condition ||
rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
{
ScCondFormatEntry& rFormat = static_cast<ScCondFormatEntry&>(*rxEntry);
rFormat.CalcAll();
}
}
}
void ScConditionalFormat::ResetCache() const
{
if (!maRanges.empty() && pDoc)
mpCache = std::make_unique<ScColorFormatCache>(*pDoc, maRanges);
else
mpCache.reset();
}
void ScConditionalFormat::SetCache(const std::vector<double>& aValues) const
{
if (!mpCache)
ResetCache();
if (mpCache)
mpCache->maValues = aValues;
}
std::vector<double>* ScConditionalFormat::GetCache() const
{
return mpCache ? &mpCache->maValues : nullptr;
}
ScConditionalFormatList::ScConditionalFormatList(const ScConditionalFormatList& rList)
{
for(const auto& rxFormat : rList)
InsertNew( rxFormat->Clone() );
}
ScConditionalFormatList::ScConditionalFormatList(ScDocument& rDoc, const ScConditionalFormatList& rList)
{
for(const auto& rxFormat : rList)
InsertNew( rxFormat->Clone(&rDoc) );
}
void ScConditionalFormatList::InsertNew( std::unique_ptr<ScConditionalFormat> pNew )
{
m_ConditionalFormats.insert(std::move(pNew));
}
ScConditionalFormat* ScConditionalFormatList::GetFormat( sal_uInt32 nKey )
{
auto itr = m_ConditionalFormats.find(nKey);
if (itr != m_ConditionalFormats.end())
return itr->get();
SAL_WARN("sc", "ScConditionalFormatList: Entry not found");
return nullptr;
}
const ScConditionalFormat* ScConditionalFormatList::GetFormat( sal_uInt32 nKey ) const
{
auto itr = m_ConditionalFormats.find(nKey);
if (itr != m_ConditionalFormats.end())
return itr->get();
SAL_WARN("sc", "ScConditionalFormatList: Entry not found");
return nullptr;
}
void ScConditionalFormatList::CompileAll()
{
for (auto const& it : m_ConditionalFormats)
{
it->CompileAll();
}
}
void ScConditionalFormatList::CompileXML()
{
for (auto const& it : m_ConditionalFormats)
{
it->CompileXML();
}
}
void ScConditionalFormatList::UpdateReference( sc::RefUpdateContext& rCxt )
{
for (auto const& it : m_ConditionalFormats)
{
it->UpdateReference(rCxt);
}
if (rCxt.meMode == URM_INSDEL)
{
// need to check which must be deleted
CheckAllEntries();
}
}
void ScConditionalFormatList::InsertRow(SCTAB nTab, SCCOL nColStart, SCCOL nColEnd, SCROW nRowPos, SCSIZE nSize)
{
for (auto const& it : m_ConditionalFormats)
{
it->InsertRow(nTab, nColStart, nColEnd, nRowPos, nSize);
}
}
void ScConditionalFormatList::InsertCol(SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, SCCOL nColPos, SCSIZE nSize)
{
for (auto const& it : m_ConditionalFormats)
{
it->InsertCol(nTab, nRowStart, nRowEnd, nColPos, nSize);
}
}
void ScConditionalFormatList::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
{
for (auto const& it : m_ConditionalFormats)
{
it->UpdateInsertTab(rCxt);
}
}
void ScConditionalFormatList::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
{
for (auto const& it : m_ConditionalFormats)
{
it->UpdateDeleteTab(rCxt);
}
}
void ScConditionalFormatList::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
{
for (auto const& it : m_ConditionalFormats)
{
it->UpdateMoveTab(rCxt);
}
}
void ScConditionalFormatList::RenameCellStyle( std::u16string_view rOld, const OUString& rNew )
{
for (auto const& it : m_ConditionalFormats)
{
it->RenameCellStyle(rOld, rNew);
}
}
bool ScConditionalFormatList::CheckAllEntries(const Link<ScConditionalFormat*,void>& rLink)
{
bool bValid = true;
// need to check which must be deleted
iterator itr = m_ConditionalFormats.begin();
while(itr != m_ConditionalFormats.end())
{
if ((*itr)->GetRange().empty())
{
bValid = false;
if (rLink.IsSet())
rLink.Call(itr->get());
itr = m_ConditionalFormats.erase(itr);
}
else
++itr;
}
return bValid;
}
void ScConditionalFormatList::DeleteArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
{
for (auto& rxFormat : m_ConditionalFormats)
rxFormat->DeleteArea( nCol1, nRow1, nCol2, nRow2 );
CheckAllEntries();
}
ScConditionalFormatList::iterator ScConditionalFormatList::begin()
{
return m_ConditionalFormats.begin();
}
ScConditionalFormatList::const_iterator ScConditionalFormatList::begin() const
{
return m_ConditionalFormats.begin();
}
ScConditionalFormatList::iterator ScConditionalFormatList::end()
{
return m_ConditionalFormats.end();
}
ScConditionalFormatList::const_iterator ScConditionalFormatList::end() const
{
return m_ConditionalFormats.end();
}
ScRangeList ScConditionalFormatList::GetCombinedRange() const
{
ScRangeList aRange;
for (auto& itr: m_ConditionalFormats)
{
const ScRangeList& rRange = itr->GetRange();
for (size_t i = 0, n = rRange.size(); i < n; ++i)
{
aRange.Join(rRange[i]);
}
}
return aRange;
}
void ScConditionalFormatList::RemoveFromDocument(ScDocument& rDoc) const
{
ScRangeList aRange = GetCombinedRange();
ScMarkData aMark(rDoc.GetSheetLimits());
aMark.MarkFromRangeList(aRange, true);
sal_uInt16 const pItems[2] = { sal_uInt16(ATTR_CONDITIONAL),0};
rDoc.ClearSelectionItems(pItems, aMark);
}
void ScConditionalFormatList::AddToDocument(ScDocument& rDoc) const
{
for (auto& itr: m_ConditionalFormats)
{
const ScRangeList& rRange = itr->GetRange();
if (rRange.empty())
continue;
SCTAB nTab = rRange.front().aStart.Tab();
rDoc.AddCondFormatData(rRange, nTab, itr->GetKey());
}
}
size_t ScConditionalFormatList::size() const
{
return m_ConditionalFormats.size();
}
bool ScConditionalFormatList::empty() const
{
return m_ConditionalFormats.empty();
}
void ScConditionalFormatList::erase( sal_uLong nIndex )
{
auto itr = m_ConditionalFormats.find(nIndex);
if (itr != end())
m_ConditionalFormats.erase(itr);
}
void ScConditionalFormatList::startRendering()
{
for (auto const& it : m_ConditionalFormats)
{
it->startRendering();
}
}
void ScConditionalFormatList::endRendering()
{
for (auto const& it : m_ConditionalFormats)
{
it->endRendering();
}
}
void ScConditionalFormatList::updateValues()
{
for (auto const& it : m_ConditionalFormats)
{
it->updateValues();
}
}
void ScConditionalFormatList::clear()
{
m_ConditionalFormats.clear();
}
sal_uInt32 ScConditionalFormatList::getMaxKey() const
{
if (m_ConditionalFormats.empty())
return 0;
return (*m_ConditionalFormats.rbegin())->GetKey();
}
void ScConditionalFormatList::CalcAll()
{
for (const auto& aEntry : m_ConditionalFormats)
{
aEntry->CalcAll();
}
}
ScCondFormatData::ScCondFormatData() {}
ScCondFormatData::ScCondFormatData(ScCondFormatData&&) = default;
ScCondFormatData::~ScCondFormatData() {}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V1098 The insert function call contains potentially dangerous move operation. Moved object can be destroyed even if there is no insertion. Check the 'std::move(pNew)' argument.
↑ V547 Expression '!"can't move ScConditionEntry"' is always false.