2438 lines
71 KiB
C++
2438 lines
71 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <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;
|
|
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) :
|
|
mpDoc( pDocument ),
|
|
mnKey( nNewKey )
|
|
{
|
|
}
|
|
|
|
std::unique_ptr<ScConditionalFormat> ScConditionalFormat::Clone(ScDocument* pNewDoc) const
|
|
{
|
|
// Real copy of the formula (for Ref Undo/between documents)
|
|
if (!pNewDoc)
|
|
pNewDoc = mpDoc;
|
|
|
|
std::unique_ptr<ScConditionalFormat> pNew(new ScConditionalFormat(mnKey, 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 mpDoc;
|
|
}
|
|
|
|
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
|
|
mpDoc->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, mpDoc, 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, mpDoc, 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() && mpDoc)
|
|
mpCache = std::make_unique<ScColorFormatCache>(*mpDoc, 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: */
|