/* -*- 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.