xref: /core/sc/source/core/data/conditio.cxx (revision b3a90323)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <scitems.hxx>
21 #include <svl/numformat.hxx>
22 #include <rtl/math.hxx>
23 #include <sal/log.hxx>
24 #include <unotools/collatorwrapper.hxx>
25 
26 #include <com/sun/star/sheet/ConditionOperator2.hpp>
27 
28 #include <attrib.hxx>
29 #include <conditio.hxx>
30 #include <formulacell.hxx>
31 #include <document.hxx>
32 #include <compiler.hxx>
33 #include <rangelst.hxx>
34 #include <rangenam.hxx>
35 #include <rangeutl.hxx>
36 #include <colorscale.hxx>
37 #include <cellvalue.hxx>
38 #include <editutil.hxx>
39 #include <tokenarray.hxx>
40 #include <fillinfo.hxx>
41 #include <refupdatecontext.hxx>
42 #include <formula/errorcodes.hxx>
43 #include <svl/sharedstring.hxx>
44 #include <svl/sharedstringpool.hxx>
45 #include <memory>
46 #include <numeric>
47 #include <utility>
48 
49 using namespace formula;
50 
ScFormatEntry(ScDocument * pDoc)51 ScFormatEntry::ScFormatEntry(ScDocument* pDoc):
52     mpDoc(pDoc)
53 {
54 }
55 
operator ==(const ScFormatEntry & r) const56 bool ScFormatEntry::operator==( const ScFormatEntry& r ) const
57 {
58     return IsEqual(r, false);
59 }
60 
61 // virtual
IsEqual(const ScFormatEntry &,bool) const62 bool ScFormatEntry::IsEqual( const ScFormatEntry& /*r*/, bool /*bIgnoreSrcPos*/ ) const
63 {
64     // By default, return false; this makes sense for all cases except ScConditionEntry
65     // As soon as databar and color scale are tested we need to think about the range
66     return false;
67 }
68 
startRendering()69 void ScFormatEntry::startRendering()
70 {
71 }
72 
endRendering()73 void ScFormatEntry::endRendering()
74 {
75 }
76 
updateValues()77 void ScFormatEntry::updateValues()
78 {
79 }
80 
lcl_HasRelRef(ScDocument * pDoc,const ScTokenArray * pFormula,sal_uInt16 nRecursion=0)81 static bool lcl_HasRelRef( ScDocument* pDoc, const ScTokenArray* pFormula, sal_uInt16 nRecursion = 0 )
82 {
83     if (pFormula)
84     {
85         FormulaTokenArrayPlainIterator aIter( *pFormula );
86         FormulaToken* t;
87         for( t = aIter.Next(); t; t = aIter.Next() )
88         {
89             switch( t->GetType() )
90             {
91                 case svDoubleRef:
92                 {
93                     ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
94                     if ( rRef2.IsColRel() || rRef2.IsRowRel() || rRef2.IsTabRel() )
95                         return true;
96                     [[fallthrough]];
97                 }
98 
99                 case svSingleRef:
100                 {
101                     ScSingleRefData& rRef1 = *t->GetSingleRef();
102                     if ( rRef1.IsColRel() || rRef1.IsRowRel() || rRef1.IsTabRel() )
103                         return true;
104                 }
105                 break;
106 
107                 case svIndex:
108                 {
109                     if( t->GetOpCode() == ocName )      // DB areas always absolute
110                         if( ScRangeData* pRangeData = pDoc->FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex()) )
111                             if( (nRecursion < 42) && lcl_HasRelRef( pDoc, pRangeData->GetCode(), nRecursion + 1 ) )
112                                 return true;
113                 }
114                 break;
115 
116                 // #i34474# function result dependent on cell position
117                 case svByte:
118                 {
119                     switch( t->GetOpCode() )
120                     {
121                         case ocRow:     // ROW() returns own row index
122                         case ocColumn:  // COLUMN() returns own column index
123                         case ocSheet:   // SHEET() returns own sheet index
124                         case ocCell:    // CELL() may return own cell address
125                             return true;
126                         default:
127                         {
128                             // added to avoid warnings
129                         }
130                     }
131                 }
132                 break;
133 
134                 default:
135                 {
136                     // added to avoid warnings
137                 }
138             }
139         }
140     }
141     return false;
142 }
143 
144 namespace {
145 
start_listen_to(ScFormulaListener & rListener,const ScTokenArray * pTokens,const ScRangeList & rRangeList)146 void start_listen_to(ScFormulaListener& rListener, const ScTokenArray* pTokens, const ScRangeList& rRangeList)
147 {
148     size_t n = rRangeList.size();
149     for (size_t i = 0; i < n; ++i)
150     {
151         const ScRange & rRange = rRangeList[i];
152         rListener.addTokenArray(pTokens, rRange);
153     }
154 }
155 
156 }
157 
StartListening()158 void ScConditionEntry::StartListening()
159 {
160     if (!pCondFormat)
161         return;
162 
163     mpRepaintTask = std::make_unique<RepaintInIdle>(pCondFormat);
164     const ScRangeList& rRanges = pCondFormat->GetRange();
165     mpListener->stopListening();
166     start_listen_to(*mpListener, pFormula1.get(), rRanges);
167     start_listen_to(*mpListener, pFormula2.get(), rRanges);
168 
169     mpListener->setCallback([&]() { mpRepaintTask->Start();});
170 }
171 
SetParent(ScConditionalFormat * pParent)172 void ScConditionEntry::SetParent(ScConditionalFormat* pParent)
173 {
174     pCondFormat = pParent;
175     StartListening();
176 }
177 
ScConditionEntry(const ScConditionEntry & r)178 ScConditionEntry::ScConditionEntry( const ScConditionEntry& r ) :
179     ScFormatEntry(r.mpDoc),
180     eOp(r.eOp),
181     nOptions(r.nOptions),
182     nVal1(r.nVal1),
183     nVal2(r.nVal2),
184     aStrVal1(r.aStrVal1),
185     aStrVal2(r.aStrVal2),
186     aStrNmsp1(r.aStrNmsp1),
187     aStrNmsp2(r.aStrNmsp2),
188     eTempGrammar1(r.eTempGrammar1),
189     eTempGrammar2(r.eTempGrammar2),
190     bIsStr1(r.bIsStr1),
191     bIsStr2(r.bIsStr2),
192     aSrcPos(r.aSrcPos),
193     aSrcString(r.aSrcString),
194     bRelRef1(r.bRelRef1),
195     bRelRef2(r.bRelRef2),
196     bFirstRun(true),
197     mpListener(new ScFormulaListener(*r.mpDoc)),
198     eConditionType( r.eConditionType ),
199     pCondFormat(r.pCondFormat),
200     mpRepaintTask()
201 {
202     // ScTokenArray copy ctor creates a flat copy
203     if (r.pFormula1)
204         pFormula1.reset( new ScTokenArray( *r.pFormula1 ) );
205     if (r.pFormula2)
206         pFormula2.reset( new ScTokenArray( *r.pFormula2 ) );
207 
208     StartListening();
209     // Formula cells are created at IsValid
210 }
211 
ScConditionEntry(ScDocument & rDocument,const ScConditionEntry & r)212 ScConditionEntry::ScConditionEntry( ScDocument& rDocument, const ScConditionEntry& r ) :
213     ScFormatEntry(&rDocument),
214     eOp(r.eOp),
215     nOptions(r.nOptions),
216     nVal1(r.nVal1),
217     nVal2(r.nVal2),
218     aStrVal1(r.aStrVal1),
219     aStrVal2(r.aStrVal2),
220     aStrNmsp1(r.aStrNmsp1),
221     aStrNmsp2(r.aStrNmsp2),
222     eTempGrammar1(r.eTempGrammar1),
223     eTempGrammar2(r.eTempGrammar2),
224     bIsStr1(r.bIsStr1),
225     bIsStr2(r.bIsStr2),
226     aSrcPos(r.aSrcPos),
227     aSrcString(r.aSrcString),
228     bRelRef1(r.bRelRef1),
229     bRelRef2(r.bRelRef2),
230     bFirstRun(true),
231     mpListener(new ScFormulaListener(rDocument)),
232     eConditionType( r.eConditionType),
233     pCondFormat(r.pCondFormat),
234     mpRepaintTask()
235 {
236     // Real copy of the formulas (for Ref Undo)
237     if (r.pFormula1)
238         pFormula1 = r.pFormula1->Clone();
239     if (r.pFormula2)
240         pFormula2 = r.pFormula2->Clone();
241 
242     // Formula cells are created at IsValid
243     // TODO: But not in the Clipboard! So interpret beforehand!
244 }
245 
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)246 ScConditionEntry::ScConditionEntry( ScConditionMode eOper,
247         const OUString& rExpr1, const OUString& rExpr2, ScDocument& rDocument, const ScAddress& rPos,
248         const OUString& rExprNmsp1, const OUString& rExprNmsp2,
249         FormulaGrammar::Grammar eGrammar1, FormulaGrammar::Grammar eGrammar2,
250         Type eType ) :
251     ScFormatEntry(&rDocument),
252     eOp(eOper),
253     nOptions(0),
254     nVal1(0.0),
255     nVal2(0.0),
256     aStrNmsp1(rExprNmsp1),
257     aStrNmsp2(rExprNmsp2),
258     eTempGrammar1(eGrammar1),
259     eTempGrammar2(eGrammar2),
260     bIsStr1(false),
261     bIsStr2(false),
262     aSrcPos(rPos),
263     bRelRef1(false),
264     bRelRef2(false),
265     bFirstRun(true),
266     mpListener(new ScFormulaListener(rDocument)),
267     eConditionType(eType),
268     pCondFormat(nullptr),
269     mpRepaintTask()
270 {
271     Compile( rExpr1, rExpr2, rExprNmsp1, rExprNmsp2, eGrammar1, eGrammar2, false );
272 
273     // Formula cells are created at IsValid
274 }
275 
ScConditionEntry(ScConditionMode eOper,const ScTokenArray * pArr1,const ScTokenArray * pArr2,ScDocument & rDocument,const ScAddress & rPos)276 ScConditionEntry::ScConditionEntry( ScConditionMode eOper,
277                                 const ScTokenArray* pArr1, const ScTokenArray* pArr2,
278                                 ScDocument& rDocument, const ScAddress& rPos ) :
279     ScFormatEntry(&rDocument),
280     eOp(eOper),
281     nOptions(0),
282     nVal1(0.0),
283     nVal2(0.0),
284     eTempGrammar1(FormulaGrammar::GRAM_DEFAULT),
285     eTempGrammar2(FormulaGrammar::GRAM_DEFAULT),
286     bIsStr1(false),
287     bIsStr2(false),
288     aSrcPos(rPos),
289     bRelRef1(false),
290     bRelRef2(false),
291     bFirstRun(true),
292     mpListener(new ScFormulaListener(rDocument)),
293     eConditionType(ScFormatEntry::Type::Condition),
294     pCondFormat(nullptr),
295     mpRepaintTask()
296 {
297     if ( pArr1 )
298     {
299         pFormula1.reset( new ScTokenArray( *pArr1 ) );
300         SimplifyCompiledFormula( pFormula1, nVal1, bIsStr1, aStrVal1 );
301         bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() );
302     }
303     if ( pArr2 )
304     {
305         pFormula2.reset( new ScTokenArray( *pArr2 ) );
306         SimplifyCompiledFormula( pFormula2, nVal2, bIsStr2, aStrVal2 );
307         bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() );
308     }
309 
310     StartListening();
311 
312     // Formula cells are created at IsValid
313 }
314 
~ScConditionEntry()315 ScConditionEntry::~ScConditionEntry()
316 {
317 }
318 
SimplifyCompiledFormula(std::unique_ptr<ScTokenArray> & rFormula,double & rVal,bool & rIsStr,OUString & rStrVal)319 void ScConditionEntry::SimplifyCompiledFormula( std::unique_ptr<ScTokenArray>& rFormula,
320                                                 double& rVal,
321                                                 bool& rIsStr,
322                                                 OUString& rStrVal )
323 {
324     if ( rFormula->GetLen() != 1 )
325         return;
326 
327     // Single (constant number)?
328     FormulaToken* pToken = rFormula->FirstToken();
329     if ( pToken->GetOpCode() != ocPush )
330         return;
331 
332     if ( pToken->GetType() == svDouble )
333     {
334         rVal = pToken->GetDouble();
335         rFormula.reset();             // Do not remember as formula
336     }
337     else if ( pToken->GetType() == svString )
338     {
339         rIsStr = true;
340         rStrVal = pToken->GetString().getString();
341         rFormula.reset();             // Do not remember as formula
342     }
343 }
344 
SetOperation(ScConditionMode eMode)345 void ScConditionEntry::SetOperation(ScConditionMode eMode)
346 {
347     eOp = eMode;
348 }
349 
Compile(const OUString & rExpr1,const OUString & rExpr2,const OUString & rExprNmsp1,const OUString & rExprNmsp2,FormulaGrammar::Grammar eGrammar1,FormulaGrammar::Grammar eGrammar2,bool bTextToReal)350 void ScConditionEntry::Compile( const OUString& rExpr1, const OUString& rExpr2,
351         const OUString& rExprNmsp1, const OUString& rExprNmsp2,
352         FormulaGrammar::Grammar eGrammar1, FormulaGrammar::Grammar eGrammar2, bool bTextToReal )
353 {
354     if ( !rExpr1.isEmpty() || !rExpr2.isEmpty() )
355     {
356         ScCompiler aComp( *mpDoc, aSrcPos );
357 
358         if ( !rExpr1.isEmpty() )
359         {
360             pFormula1.reset();
361             aComp.SetGrammar( eGrammar1 );
362             if ( mpDoc->IsImportingXML() && !bTextToReal )
363             {
364                 //  temporary formula string as string tokens
365                 pFormula1.reset( new ScTokenArray(*mpDoc) );
366                 pFormula1->AssignXMLString( rExpr1, rExprNmsp1 );
367                 // bRelRef1 is set when the formula is compiled again (CompileXML)
368             }
369             else
370             {
371                 pFormula1 = aComp.CompileString( rExpr1, rExprNmsp1 );
372                 SimplifyCompiledFormula( pFormula1, nVal1, bIsStr1, aStrVal1 );
373                 bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() );
374             }
375         }
376 
377         if ( !rExpr2.isEmpty() )
378         {
379             pFormula2.reset();
380             aComp.SetGrammar( eGrammar2 );
381             if ( mpDoc->IsImportingXML() && !bTextToReal )
382             {
383                 //  temporary formula string as string tokens
384                 pFormula2.reset( new ScTokenArray(*mpDoc) );
385                 pFormula2->AssignXMLString( rExpr2, rExprNmsp2 );
386                 // bRelRef2 is set when the formula is compiled again (CompileXML)
387             }
388             else
389             {
390                 pFormula2 = aComp.CompileString( rExpr2, rExprNmsp2 );
391                 SimplifyCompiledFormula( pFormula2, nVal2, bIsStr2, aStrVal2 );
392                 bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() );
393             }
394         }
395     }
396 
397     StartListening();
398 }
399 
400 /**
401  * Create formula cells
402  */
MakeCells(const ScAddress & rPos)403 void ScConditionEntry::MakeCells( const ScAddress& rPos )
404 {
405     if ( mpDoc->IsClipOrUndo() ) // Never calculate in the Clipboard!
406         return;
407 
408     if ( pFormula1 && !pFCell1 && !bRelRef1 )
409     {
410         // pFCell1 will hold a flat-copied ScTokenArray sharing ref-counted
411         // code tokens with pFormula1
412         pFCell1.reset( new ScFormulaCell(*mpDoc, rPos, *pFormula1) );
413         pFCell1->SetFreeFlying(true);
414         pFCell1->StartListeningTo( *mpDoc );
415     }
416 
417     if ( pFormula2 && !pFCell2 && !bRelRef2 )
418     {
419         // pFCell2 will hold a flat-copied ScTokenArray sharing ref-counted
420         // code tokens with pFormula2
421         pFCell2.reset( new ScFormulaCell(*mpDoc, rPos, *pFormula2) );
422         pFCell2->SetFreeFlying(true);
423         pFCell2->StartListeningTo( *mpDoc );
424     }
425 }
426 
SetIgnoreBlank(bool bSet)427 void ScConditionEntry::SetIgnoreBlank(bool bSet)
428 {
429     // The bit SC_COND_NOBLANKS is set if blanks are not ignored
430     // (only of valid)
431     if (bSet)
432         nOptions &= ~SC_COND_NOBLANKS;
433     else
434         nOptions |= SC_COND_NOBLANKS;
435 }
436 
SetCaseSensitive(bool bSet)437 void ScConditionEntry::SetCaseSensitive(bool bSet)
438 {
439     // The bit SC_COND_CASESENS is set if validation compare is case sensitive
440     // (only of valid)
441     if (bSet)
442         nOptions |= SC_COND_CASESENS;
443     else
444         nOptions &= ~SC_COND_CASESENS;
445 }
446 
447 /**
448  * Delete formula cells, so we re-compile at the next IsValid
449  */
CompileAll()450 void ScConditionEntry::CompileAll()
451 {
452     pFCell1.reset();
453     pFCell2.reset();
454 }
455 
CompileXML()456 void ScConditionEntry::CompileXML()
457 {
458     //  First parse the formula source position if it was stored as text
459     if ( !aSrcString.isEmpty() )
460     {
461         ScAddress aNew;
462         /* XML is always in OOo:A1 format, although R1C1 would be more amenable
463          * to compression */
464         if ( aNew.Parse( aSrcString, *mpDoc ) & ScRefFlags::VALID )
465             aSrcPos = aNew;
466         // if the position is invalid, there isn't much we can do at this time
467         aSrcString.clear();
468     }
469 
470     //  Convert the text tokens that were created during XML import into real tokens.
471     Compile( GetExpression(aSrcPos, 0, 0, eTempGrammar1),
472              GetExpression(aSrcPos, 1, 0, eTempGrammar2),
473              aStrNmsp1, aStrNmsp2, eTempGrammar1, eTempGrammar2, true );
474 
475     // Importing ocDde/ocWebservice?
476     if (pFormula1)
477         mpDoc->CheckLinkFormulaNeedingCheck(*pFormula1);
478     if (pFormula2)
479         mpDoc->CheckLinkFormulaNeedingCheck(*pFormula2);
480 }
481 
SetSrcString(const OUString & rNew)482 void ScConditionEntry::SetSrcString( const OUString& rNew )
483 {
484     // aSrcString is only evaluated in CompileXML
485     SAL_WARN_IF( !mpDoc->IsImportingXML(), "sc", "SetSrcString is only valid for XML import" );
486 
487     aSrcString = rNew;
488 }
489 
SetFormula1(const ScTokenArray & rArray)490 void ScConditionEntry::SetFormula1( const ScTokenArray& rArray )
491 {
492     pFormula1.reset();
493     if( rArray.GetLen() > 0 )
494     {
495         pFormula1.reset( new ScTokenArray( rArray ) );
496         SimplifyCompiledFormula(pFormula1, nVal1, bIsStr1, aStrVal1);
497         bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() );
498     }
499 
500     StartListening();
501 }
502 
SetFormula2(const ScTokenArray & rArray)503 void ScConditionEntry::SetFormula2( const ScTokenArray& rArray )
504 {
505     pFormula2.reset();
506     if( rArray.GetLen() > 0 )
507     {
508         pFormula2.reset( new ScTokenArray( rArray ) );
509         SimplifyCompiledFormula(pFormula2, nVal2, bIsStr2, aStrVal2);
510         bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() );
511     }
512 
513     StartListening();
514 }
515 
UpdateReference(sc::RefUpdateContext & rCxt)516 void ScConditionEntry::UpdateReference( sc::RefUpdateContext& rCxt )
517 {
518     if(pCondFormat)
519         aSrcPos = pCondFormat->GetRange().Combine().aStart;
520     ScAddress aOldSrcPos = aSrcPos;
521     bool bChangedPos = false;
522     if (rCxt.meMode == URM_INSDEL && rCxt.maRange.Contains(aSrcPos))
523     {
524         ScAddress aErrorPos( ScAddress::UNINITIALIZED );
525         if (!aSrcPos.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos, *mpDoc))
526         {
527             assert(!"can't move ScConditionEntry");
528         }
529         bChangedPos = aSrcPos != aOldSrcPos;
530     }
531 
532     if (pFormula1)
533     {
534         sc::RefUpdateResult aRes;
535         switch (rCxt.meMode)
536         {
537             case URM_INSDEL:
538                 aRes = pFormula1->AdjustReferenceOnShift(rCxt, aOldSrcPos);
539             break;
540             case URM_MOVE:
541                 aRes = pFormula1->AdjustReferenceOnMove(rCxt, aOldSrcPos, aSrcPos);
542             break;
543             default:
544                 ;
545         }
546 
547         if (aRes.mbReferenceModified || bChangedPos)
548             pFCell1.reset();       // is created again in IsValid
549     }
550 
551     if (pFormula2)
552     {
553         sc::RefUpdateResult aRes;
554         switch (rCxt.meMode)
555         {
556             case URM_INSDEL:
557                 aRes = pFormula2->AdjustReferenceOnShift(rCxt, aOldSrcPos);
558             break;
559             case URM_MOVE:
560                 aRes = pFormula2->AdjustReferenceOnMove(rCxt, aOldSrcPos, aSrcPos);
561             break;
562             default:
563                 ;
564         }
565 
566         if (aRes.mbReferenceModified || bChangedPos)
567             pFCell2.reset();       // is created again in IsValid
568     }
569 
570     StartListening();
571 }
572 
UpdateInsertTab(sc::RefUpdateInsertTabContext & rCxt)573 void ScConditionEntry::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
574 {
575     if (pFormula1)
576     {
577         pFormula1->AdjustReferenceOnInsertedTab(rCxt, aSrcPos);
578         pFCell1.reset();
579     }
580 
581     if (pFormula2)
582     {
583         pFormula2->AdjustReferenceOnInsertedTab(rCxt, aSrcPos);
584         pFCell2.reset();
585     }
586 
587     ScRangeUpdater::UpdateInsertTab(aSrcPos, rCxt);
588 }
589 
UpdateDeleteTab(sc::RefUpdateDeleteTabContext & rCxt)590 void ScConditionEntry::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
591 {
592     if (pFormula1)
593     {
594         pFormula1->AdjustReferenceOnDeletedTab(rCxt, aSrcPos);
595         pFCell1.reset();
596     }
597 
598     if (pFormula2)
599     {
600         pFormula2->AdjustReferenceOnDeletedTab(rCxt, aSrcPos);
601         pFCell2.reset();
602     }
603 
604     ScRangeUpdater::UpdateDeleteTab(aSrcPos, rCxt);
605     StartListening();
606 }
607 
UpdateMoveTab(sc::RefUpdateMoveTabContext & rCxt)608 void ScConditionEntry::UpdateMoveTab(sc::RefUpdateMoveTabContext& rCxt)
609 {
610     sc::RefUpdateResult aResFinal;
611     aResFinal.mnTab = aSrcPos.Tab();
612     if (pFormula1)
613     {
614         sc::RefUpdateResult aRes = pFormula1->AdjustReferenceOnMovedTab(rCxt, aSrcPos);
615         if (aRes.mbValueChanged)
616             aResFinal.mnTab = aRes.mnTab;
617         pFCell1.reset();
618     }
619 
620     if (pFormula2)
621     {
622         sc::RefUpdateResult aRes = pFormula2->AdjustReferenceOnMovedTab(rCxt, aSrcPos);
623         if (aRes.mbValueChanged)
624             aResFinal.mnTab = aRes.mnTab;
625         pFCell2.reset();
626     }
627 
628     if (aResFinal.mnTab != aSrcPos.Tab())
629         aSrcPos.SetTab(aResFinal.mnTab);
630 
631     StartListening();
632 }
633 
lcl_IsEqual(const std::unique_ptr<ScTokenArray> & pArr1,const std::unique_ptr<ScTokenArray> & pArr2)634 static bool lcl_IsEqual( const std::unique_ptr<ScTokenArray>& pArr1, const std::unique_ptr<ScTokenArray>& pArr2 )
635 {
636     // We only compare the non-RPN array
637     if ( pArr1 && pArr2 )
638         return pArr1->EqualTokens( pArr2.get() );
639     else
640         return !pArr1 && !pArr2; // Both 0? -> the same
641 }
642 
643 // virtual
IsEqual(const ScFormatEntry & rOther,bool bIgnoreSrcPos) const644 bool ScConditionEntry::IsEqual( const ScFormatEntry& rOther, bool bIgnoreSrcPos ) const
645 {
646     if (GetType() != rOther.GetType())
647         return false;
648 
649     const ScConditionEntry& r = static_cast<const ScConditionEntry&>(rOther);
650 
651     if (eOp != r.eOp || nOptions != r.nOptions
652         || !lcl_IsEqual(pFormula1, r.pFormula1) || !lcl_IsEqual(pFormula2, r.pFormula2))
653         return false;
654 
655     if (!bIgnoreSrcPos)
656     {
657         // for formulas, the reference positions must be compared, too
658         // (including aSrcString, for inserting the entries during XML import)
659         if ( ( pFormula1 || pFormula2 ) && ( aSrcPos != r.aSrcPos || aSrcString != r.aSrcString ) )
660             return false;
661     }
662 
663     // If not formulas, compare values
664     if ( !pFormula1 && ( nVal1 != r.nVal1 || aStrVal1 != r.aStrVal1 || bIsStr1 != r.bIsStr1 ) )
665         return false;
666     if ( !pFormula2 && ( nVal2 != r.nVal2 || aStrVal2 != r.aStrVal2 || bIsStr2 != r.bIsStr2 ) )
667         return false;
668 
669     return true;
670 }
671 
Interpret(const ScAddress & rPos)672 void ScConditionEntry::Interpret( const ScAddress& rPos )
673 {
674     // Create formula cells
675     // Note: New Broadcaster (Note cells) may be inserted into the document!
676     if ( ( pFormula1 && !pFCell1 ) || ( pFormula2 && !pFCell2 ) )
677         MakeCells( rPos );
678 
679     // Evaluate formulas
680     bool bDirty = false; // 1 and 2 separate?
681 
682     std::optional<ScFormulaCell> oTemp;
683     ScFormulaCell* pEff1 = pFCell1.get();
684     if ( bRelRef1 )
685     {
686         if (pFormula1)
687             oTemp.emplace(*mpDoc, rPos, *pFormula1);
688         else
689             oTemp.emplace(*mpDoc, rPos);
690         pEff1 = &*oTemp;
691         pEff1->SetFreeFlying(true);
692     }
693     if ( pEff1 )
694     {
695         if (!pEff1->IsRunning()) // Don't create 522
696         {
697             //TODO: Query Changed instead of Dirty!
698             if (pEff1->GetDirty() && !bRelRef1 && mpDoc->GetAutoCalc())
699                 bDirty = true;
700             if (pEff1->IsValue())
701             {
702                 bIsStr1 = false;
703                 nVal1 = pEff1->GetValue();
704                 aStrVal1.clear();
705             }
706             else
707             {
708                 bIsStr1 = true;
709                 aStrVal1 = pEff1->GetString().getString();
710                 nVal1 = 0.0;
711             }
712         }
713     }
714     oTemp.reset();
715 
716     ScFormulaCell* pEff2 = pFCell2.get(); //@ 1!=2
717     if ( bRelRef2 )
718     {
719         if (pFormula2)
720             oTemp.emplace(*mpDoc, rPos, *pFormula2);
721         else
722             oTemp.emplace(*mpDoc, rPos);
723         pEff2 = &*oTemp;
724         pEff2->SetFreeFlying(true);
725     }
726     if ( pEff2 )
727     {
728         if (!pEff2->IsRunning()) // Don't create 522
729         {
730             if (pEff2->GetDirty() && !bRelRef2 && mpDoc->GetAutoCalc())
731                 bDirty = true;
732             if (pEff2->IsValue())
733             {
734                 bIsStr2 = false;
735                 nVal2 = pEff2->GetValue();
736                 aStrVal2.clear();
737             }
738             else
739             {
740                 bIsStr2 = true;
741                 aStrVal2 = pEff2->GetString().getString();
742                 nVal2 = 0.0;
743             }
744         }
745     }
746     oTemp.reset();
747 
748     // If IsRunning, the last values remain
749     if (bDirty && !bFirstRun)
750     {
751         // Repaint everything for dependent formats
752         DataChanged();
753     }
754 
755     bFirstRun = false;
756 }
757 
lcl_GetCellContent(ScRefCellValue & rCell,bool bIsStr1,double & rArg,OUString & rArgStr,const ScDocument * pDoc)758 static bool lcl_GetCellContent( ScRefCellValue& rCell, bool bIsStr1, double& rArg, OUString& rArgStr,
759         const ScDocument* pDoc )
760 {
761 
762     if (rCell.isEmpty())
763         return !bIsStr1;
764 
765     bool bVal = true;
766 
767     switch (rCell.getType())
768     {
769         case CELLTYPE_VALUE:
770             rArg = rCell.getDouble();
771         break;
772         case CELLTYPE_FORMULA:
773         {
774             bVal = rCell.getFormula()->IsValue();
775             if (bVal)
776                 rArg = rCell.getFormula()->GetValue();
777             else
778                 rArgStr = rCell.getFormula()->GetString().getString();
779         }
780         break;
781         case CELLTYPE_STRING:
782         case CELLTYPE_EDIT:
783             bVal = false;
784             if (rCell.getType() == CELLTYPE_STRING)
785                 rArgStr = rCell.getSharedString()->getString();
786             else if (rCell.getEditText())
787                 rArgStr = ScEditUtil::GetString(*rCell.getEditText(), pDoc);
788         break;
789         default:
790             ;
791     }
792 
793     return bVal;
794 }
795 
FillCache() const796 void ScConditionEntry::FillCache() const
797 {
798     if(mpCache)
799         return;
800 
801     const ScRangeList& rRanges = pCondFormat->GetRange();
802     mpCache.reset(new ScConditionEntryCache);
803     size_t nListCount = rRanges.size();
804     for( size_t i = 0; i < nListCount; i++ )
805     {
806         const ScRange & rRange = rRanges[i];
807         SCROW nRow = rRange.aEnd.Row();
808         SCCOL nCol = rRange.aEnd.Col();
809         SCCOL nColStart = rRange.aStart.Col();
810         SCROW nRowStart = rRange.aStart.Row();
811         SCTAB nTab = rRange.aStart.Tab();
812 
813         // temporary fix to workaround slow duplicate entry
814         // conditions, prevent to use a whole row
815         if(nRow == mpDoc->MaxRow())
816         {
817             bool bShrunk = false;
818             mpDoc->ShrinkToUsedDataArea(bShrunk, nTab, nColStart, nRowStart,
819                     nCol, nRow, false);
820         }
821 
822         for( SCROW r = nRowStart; r <= nRow; r++ )
823             for( SCCOL c = nColStart; c <= nCol; c++ )
824             {
825                 ScRefCellValue aCell(*mpDoc, ScAddress(c, r, nTab));
826                 if (aCell.isEmpty())
827                     continue;
828 
829                 double nVal = 0.0;
830                 OUString aStr;
831                 if (!lcl_GetCellContent(aCell, false, nVal, aStr, mpDoc))
832                 {
833                     std::pair<ScConditionEntryCache::StringCacheType::iterator, bool> aResult =
834                         mpCache->maStrings.emplace(aStr, 1);
835 
836                     if(!aResult.second)
837                         aResult.first->second++;
838                 }
839                 else
840                 {
841                     std::pair<ScConditionEntryCache::ValueCacheType::iterator, bool> aResult =
842                         mpCache->maValues.emplace(nVal, 1);
843 
844                     if(!aResult.second)
845                         aResult.first->second++;
846 
847                     ++(mpCache->nValueItems);
848                 }
849             }
850     }
851 }
852 
IsDuplicate(double nArg,const OUString & rStr) const853 bool ScConditionEntry::IsDuplicate( double nArg, const OUString& rStr ) const
854 {
855     FillCache();
856 
857     if(rStr.isEmpty())
858     {
859         ScConditionEntryCache::ValueCacheType::iterator itr = mpCache->maValues.find(nArg);
860         if(itr == mpCache->maValues.end())
861             return false;
862         else
863         {
864             return itr->second > 1;
865         }
866     }
867     else
868     {
869         ScConditionEntryCache::StringCacheType::iterator itr = mpCache->maStrings.find(rStr);
870         if(itr == mpCache->maStrings.end())
871             return false;
872         else
873         {
874             return itr->second > 1;
875         }
876     }
877 }
878 
IsTopNElement(double nArg) const879 bool ScConditionEntry::IsTopNElement( double nArg ) const
880 {
881     FillCache();
882 
883     if(mpCache->nValueItems <= nVal1)
884         return true;
885 
886     size_t nCells = 0;
887     for(ScConditionEntryCache::ValueCacheType::const_reverse_iterator itr = mpCache->maValues.rbegin(),
888             itrEnd = mpCache->maValues.rend(); itr != itrEnd; ++itr)
889     {
890         if(nCells >= nVal1)
891             return false;
892         if(itr->first <= nArg)
893             return true;
894         nCells += itr->second;
895     }
896 
897     return true;
898 }
899 
IsBottomNElement(double nArg) const900 bool ScConditionEntry::IsBottomNElement( double nArg ) const
901 {
902     FillCache();
903 
904     if(mpCache->nValueItems <= nVal1)
905         return true;
906 
907     size_t nCells = 0;
908     for(const auto& [rVal, rCount] : mpCache->maValues)
909     {
910         if(nCells >= nVal1)
911             return false;
912         if(rVal >= nArg)
913             return true;
914         nCells += rCount;
915     }
916 
917     return true;
918 }
919 
IsTopNPercent(double nArg) const920 bool ScConditionEntry::IsTopNPercent( double nArg ) const
921 {
922     FillCache();
923 
924     size_t nCells = 0;
925     size_t nLimitCells = static_cast<size_t>(mpCache->nValueItems*nVal1/100);
926     for(ScConditionEntryCache::ValueCacheType::const_reverse_iterator itr = mpCache->maValues.rbegin(),
927             itrEnd = mpCache->maValues.rend(); itr != itrEnd; ++itr)
928     {
929         if(nCells >= nLimitCells)
930             return false;
931         if(itr->first <= nArg)
932             return true;
933         nCells += itr->second;
934     }
935 
936     return true;
937 }
938 
IsBottomNPercent(double nArg) const939 bool ScConditionEntry::IsBottomNPercent( double nArg ) const
940 {
941     FillCache();
942 
943     size_t nCells = 0;
944     size_t nLimitCells = static_cast<size_t>(mpCache->nValueItems*nVal1/100);
945     for(const auto& [rVal, rCount] : mpCache->maValues)
946     {
947         if(nCells >= nLimitCells)
948             return false;
949         if(rVal >= nArg)
950             return true;
951         nCells += rCount;
952     }
953 
954     return true;
955 }
956 
IsBelowAverage(double nArg,bool bEqual) const957 bool ScConditionEntry::IsBelowAverage( double nArg, bool bEqual ) const
958 {
959     FillCache();
960 
961     double nSum = std::accumulate(mpCache->maValues.begin(), mpCache->maValues.end(), double(0),
962         [](const double& rSum, const ScConditionEntryCache::ValueCacheType::value_type& rEntry) {
963             return rSum + rEntry.first * rEntry.second; });
964 
965     if(bEqual)
966         return (nArg <= nSum/mpCache->nValueItems);
967     else
968         return (nArg < nSum/mpCache->nValueItems);
969 }
970 
IsAboveAverage(double nArg,bool bEqual) const971 bool ScConditionEntry::IsAboveAverage( double nArg, bool bEqual ) const
972 {
973     FillCache();
974 
975     double nSum = std::accumulate(mpCache->maValues.begin(), mpCache->maValues.end(), double(0),
976         [](const double& rSum, const ScConditionEntryCache::ValueCacheType::value_type& rEntry) {
977             return rSum + rEntry.first * rEntry.second; });
978 
979     if(bEqual)
980         return (nArg >= nSum/mpCache->nValueItems);
981     else
982         return (nArg > nSum/mpCache->nValueItems);
983 }
984 
IsError(const ScAddress & rPos) const985 bool ScConditionEntry::IsError( const ScAddress& rPos ) const
986 {
987     ScRefCellValue rCell(*mpDoc, rPos);
988 
989     if (rCell.getType() == CELLTYPE_FORMULA)
990     {
991         if (rCell.getFormula()->GetErrCode() != FormulaError::NONE)
992             return true;
993     }
994 
995     return false;
996 }
997 
IsValid(double nArg,const ScAddress & rPos) const998 bool ScConditionEntry::IsValid( double nArg, const ScAddress& rPos ) const
999 {
1000     // Interpret must already have been called
1001     if ( bIsStr1 )
1002     {
1003         switch( eOp )
1004         {
1005             case ScConditionMode::BeginsWith:
1006             case ScConditionMode::EndsWith:
1007             case ScConditionMode::ContainsText:
1008             case ScConditionMode::NotContainsText:
1009                 break;
1010             case ScConditionMode::NotEqual:
1011                 return true;
1012             default:
1013                 return false;
1014         }
1015     }
1016 
1017     if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
1018         if ( bIsStr2 )
1019             return false;
1020 
1021     double nComp1 = nVal1; // Copy, so that it can be changed
1022     double nComp2 = nVal2;
1023 
1024     if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
1025         if ( nComp1 > nComp2 )
1026             // Right order for value range
1027             std::swap( nComp1, nComp2 );
1028 
1029     // All corner cases need to be tested with ::rtl::math::approxEqual!
1030     bool bValid = false;
1031     switch (eOp)
1032     {
1033         case ScConditionMode::NONE:
1034             break;                  // Always sal_False
1035         case ScConditionMode::Equal:
1036             bValid = ::rtl::math::approxEqual( nArg, nComp1 );
1037             break;
1038         case ScConditionMode::NotEqual:
1039             bValid = !::rtl::math::approxEqual( nArg, nComp1 );
1040             break;
1041         case ScConditionMode::Greater:
1042             bValid = ( nArg > nComp1 ) && !::rtl::math::approxEqual( nArg, nComp1 );
1043             break;
1044         case ScConditionMode::EqGreater:
1045             bValid = ( nArg >= nComp1 ) || ::rtl::math::approxEqual( nArg, nComp1 );
1046             break;
1047         case ScConditionMode::Less:
1048             bValid = ( nArg < nComp1 ) && !::rtl::math::approxEqual( nArg, nComp1 );
1049             break;
1050         case ScConditionMode::EqLess:
1051             bValid = ( nArg <= nComp1 ) || ::rtl::math::approxEqual( nArg, nComp1 );
1052             break;
1053         case ScConditionMode::Between:
1054             bValid = ( nArg >= nComp1 && nArg <= nComp2 ) ||
1055                      ::rtl::math::approxEqual( nArg, nComp1 ) || ::rtl::math::approxEqual( nArg, nComp2 );
1056             break;
1057         case ScConditionMode::NotBetween:
1058             bValid = ( nArg < nComp1 || nArg > nComp2 ) &&
1059                      !::rtl::math::approxEqual( nArg, nComp1 ) && !::rtl::math::approxEqual( nArg, nComp2 );
1060             break;
1061         case ScConditionMode::Duplicate:
1062         case ScConditionMode::NotDuplicate:
1063             if( pCondFormat )
1064             {
1065                 bValid = IsDuplicate( nArg, OUString() );
1066                 if( eOp == ScConditionMode::NotDuplicate )
1067                     bValid = !bValid;
1068             }
1069             break;
1070         case ScConditionMode::Direct:
1071             bValid = nComp1 != 0.0;
1072             break;
1073         case ScConditionMode::Top10:
1074             bValid = IsTopNElement( nArg );
1075             break;
1076         case ScConditionMode::Bottom10:
1077             bValid = IsBottomNElement( nArg );
1078             break;
1079         case ScConditionMode::TopPercent:
1080             bValid = IsTopNPercent( nArg );
1081             break;
1082         case ScConditionMode::BottomPercent:
1083             bValid = IsBottomNPercent( nArg );
1084             break;
1085         case ScConditionMode::AboveAverage:
1086         case ScConditionMode::AboveEqualAverage:
1087             bValid = IsAboveAverage( nArg, eOp == ScConditionMode::AboveEqualAverage );
1088             break;
1089         case ScConditionMode::BelowAverage:
1090         case ScConditionMode::BelowEqualAverage:
1091             bValid = IsBelowAverage( nArg, eOp == ScConditionMode::BelowEqualAverage );
1092             break;
1093         case ScConditionMode::Error:
1094         case ScConditionMode::NoError:
1095             bValid = IsError( rPos );
1096             if( eOp == ScConditionMode::NoError )
1097                 bValid = !bValid;
1098             break;
1099         case ScConditionMode::BeginsWith:
1100             if(aStrVal1.isEmpty())
1101             {
1102                 OUString aStr = OUString::number(nVal1);
1103                 OUString aStr2 = OUString::number(nArg);
1104                 bValid = aStr2.startsWith(aStr);
1105             }
1106             else
1107             {
1108                 OUString aStr2 = OUString::number(nArg);
1109                 bValid = aStr2.startsWith(aStrVal1);
1110             }
1111             break;
1112         case ScConditionMode::EndsWith:
1113             if(aStrVal1.isEmpty())
1114             {
1115                 OUString aStr = OUString::number(nVal1);
1116                 OUString aStr2 = OUString::number(nArg);
1117                 bValid = aStr2.endsWith(aStr);
1118             }
1119             else
1120             {
1121                 OUString aStr2 = OUString::number(nArg);
1122                 bValid = aStr2.endsWith(aStrVal1);
1123             }
1124             break;
1125         case ScConditionMode::ContainsText:
1126         case ScConditionMode::NotContainsText:
1127             if(aStrVal1.isEmpty())
1128             {
1129                 OUString aStr = OUString::number(nVal1);
1130                 OUString aStr2 = OUString::number(nArg);
1131                 bValid = aStr2.indexOf(aStr) != -1;
1132             }
1133             else
1134             {
1135                 OUString aStr2 = OUString::number(nArg);
1136                 bValid = aStr2.indexOf(aStrVal1) != -1;
1137             }
1138 
1139             if( eOp == ScConditionMode::NotContainsText )
1140                 bValid = !bValid;
1141             break;
1142         default:
1143             SAL_WARN("sc", "unknown operation at ScConditionEntry");
1144             break;
1145     }
1146     return bValid;
1147 }
1148 
IsValidStr(const OUString & rArg,const ScAddress & rPos) const1149 bool ScConditionEntry::IsValidStr( const OUString& rArg, const ScAddress& rPos ) const
1150 {
1151     bool bValid = false;
1152     // Interpret must already have been called
1153     if ( eOp == ScConditionMode::Direct ) // Formula is independent from the content
1154         return nVal1 != 0.0;
1155 
1156     if ( eOp == ScConditionMode::Duplicate || eOp == ScConditionMode::NotDuplicate )
1157     {
1158         if( pCondFormat && !rArg.isEmpty() )
1159         {
1160             bValid = IsDuplicate( 0.0, rArg );
1161             if( eOp == ScConditionMode::NotDuplicate )
1162                 bValid = !bValid;
1163             return bValid;
1164         }
1165     }
1166 
1167     if (eOp == ScConditionMode::Error)
1168         return IsError(rPos);
1169     if (eOp == ScConditionMode::NoError)
1170         return !IsError(rPos);
1171 
1172     // If number contains condition, always false, except for "not equal".
1173     if (!bIsStr1)
1174         return ( eOp == ScConditionMode::NotEqual );
1175     if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
1176         if ( !bIsStr2 )
1177             return false;
1178 
1179     OUString aUpVal1( aStrVal1 ); //TODO: As a member? (Also set in Interpret)
1180     OUString aUpVal2( aStrVal2 );
1181 
1182     switch ( eOp )
1183     {
1184         case ScConditionMode::Equal:
1185             bValid = ScGlobal::GetTransliteration(IsCaseSensitive()).isEqual(aUpVal1, rArg);
1186         break;
1187         case ScConditionMode::NotEqual:
1188             bValid = !ScGlobal::GetTransliteration(IsCaseSensitive()).isEqual(aUpVal1, rArg);
1189         break;
1190         case ScConditionMode::TopPercent:
1191         case ScConditionMode::BottomPercent:
1192         case ScConditionMode::Top10:
1193         case ScConditionMode::Bottom10:
1194         case ScConditionMode::AboveAverage:
1195         case ScConditionMode::BelowAverage:
1196             return false;
1197         case ScConditionMode::BeginsWith:
1198             bValid = ScGlobal::GetTransliteration(IsCaseSensitive()).isMatch(aUpVal1, rArg);
1199         break;
1200         case ScConditionMode::EndsWith:
1201         {
1202             sal_Int32 nStart = rArg.getLength();
1203             const sal_Int32 nLen = aUpVal1.getLength();
1204             if (nLen > nStart)
1205                 bValid = false;
1206             else
1207             {
1208                 nStart = nStart - nLen;
1209                 sal_Int32 nMatch1(0), nMatch2(0);
1210                 bValid = ScGlobal::GetTransliteration(IsCaseSensitive()).equals(rArg, nStart, nLen, nMatch1,
1211                                                                aUpVal1, 0, nLen, nMatch2);
1212             }
1213         }
1214         break;
1215         case ScConditionMode::ContainsText:
1216         case ScConditionMode::NotContainsText:
1217         {
1218             const OUString aArgStr(!IsCaseSensitive() ? ScGlobal::getCharClass().lowercase(rArg) : rArg);
1219             const OUString aValStr(!IsCaseSensitive() ? ScGlobal::getCharClass().lowercase(aUpVal1) : aUpVal1);
1220             bValid = aArgStr.indexOf(aValStr) != -1;
1221 
1222             if(eOp == ScConditionMode::NotContainsText)
1223                 bValid = !bValid;
1224         }
1225         break;
1226         default:
1227         {
1228             sal_Int32 nCompare = ScGlobal::GetCollator(IsCaseSensitive()).compareString(
1229                 rArg, aUpVal1 );
1230             switch ( eOp )
1231             {
1232                 case ScConditionMode::Greater:
1233                     bValid = ( nCompare > 0 );
1234                     break;
1235                 case ScConditionMode::EqGreater:
1236                     bValid = ( nCompare >= 0 );
1237                     break;
1238                 case ScConditionMode::Less:
1239                     bValid = ( nCompare < 0 );
1240                     break;
1241                 case ScConditionMode::EqLess:
1242                     bValid = ( nCompare <= 0 );
1243                     break;
1244                 case ScConditionMode::Between:
1245                 case ScConditionMode::NotBetween:
1246                 {
1247                     const sal_Int32 nCompare2 = ScGlobal::GetCollator(IsCaseSensitive()).compareString(rArg, aUpVal2);
1248                     //  Test for NOTBETWEEN:
1249                     bValid = (nCompare > 0 && nCompare2 > 0) || (nCompare < 0 && nCompare2 < 0);
1250                     if ( eOp == ScConditionMode::Between )
1251                         bValid = !bValid;
1252                     break;
1253                 }
1254                 default:
1255                     SAL_WARN("sc", "unknown operation in ScConditionEntry");
1256                     bValid = false;
1257                     break;
1258             }
1259         }
1260     }
1261     return bValid;
1262 }
1263 
IsCellValid(ScRefCellValue & rCell,const ScAddress & rPos) const1264 bool ScConditionEntry::IsCellValid( ScRefCellValue& rCell, const ScAddress& rPos ) const
1265 {
1266     const_cast<ScConditionEntry*>(this)->Interpret(rPos); // Evaluate formula
1267 
1268     if ( eOp == ScConditionMode::Direct )
1269         return nVal1 != 0.0;
1270 
1271     double nArg = 0.0;
1272     OUString aArgStr;
1273     bool bVal = lcl_GetCellContent( rCell, bIsStr1, nArg, aArgStr, mpDoc );
1274     if (bVal)
1275         return IsValid( nArg, rPos );
1276     else
1277         return IsValidStr( aArgStr, rPos );
1278 }
1279 
GetExpression(const ScAddress & rCursor,sal_uInt16 nIndex,sal_uInt32 nNumFmt,const FormulaGrammar::Grammar eGrammar) const1280 OUString ScConditionEntry::GetExpression( const ScAddress& rCursor, sal_uInt16 nIndex,
1281                                         sal_uInt32 nNumFmt,
1282                                         const FormulaGrammar::Grammar eGrammar ) const
1283 {
1284     assert( nIndex <= 1);
1285     OUString aRet;
1286 
1287     if ( FormulaGrammar::isEnglish( eGrammar) && nNumFmt == 0 )
1288         nNumFmt = mpDoc->GetFormatTable()->GetStandardIndex( LANGUAGE_ENGLISH_US );
1289 
1290     if ( nIndex==0 )
1291     {
1292         if ( pFormula1 )
1293         {
1294             ScCompiler aComp(*mpDoc, rCursor, *pFormula1, eGrammar);
1295             OUStringBuffer aBuffer;
1296             aComp.CreateStringFromTokenArray( aBuffer );
1297             aRet = aBuffer.makeStringAndClear();
1298         }
1299         else if (bIsStr1)
1300         {
1301             aRet = "\"" + aStrVal1 + "\"";
1302         }
1303         else
1304             mpDoc->GetFormatTable()->GetInputLineString(nVal1, nNumFmt, aRet);
1305     }
1306     else if ( nIndex==1 )
1307     {
1308         if ( pFormula2 )
1309         {
1310             ScCompiler aComp(*mpDoc, rCursor, *pFormula2, eGrammar);
1311             OUStringBuffer aBuffer;
1312             aComp.CreateStringFromTokenArray( aBuffer );
1313             aRet = aBuffer.makeStringAndClear();
1314         }
1315         else if (bIsStr2)
1316         {
1317             aRet = "\"" + aStrVal2 + "\"";
1318         }
1319         else
1320             mpDoc->GetFormatTable()->GetInputLineString(nVal2, nNumFmt, aRet);
1321     }
1322 
1323     return aRet;
1324 }
1325 
CreateFlatCopiedTokenArray(sal_uInt16 nIndex) const1326 std::unique_ptr<ScTokenArray> ScConditionEntry::CreateFlatCopiedTokenArray( sal_uInt16 nIndex ) const
1327 {
1328     assert(nIndex <= 1);
1329     std::unique_ptr<ScTokenArray> pRet;
1330 
1331     if ( nIndex==0 )
1332     {
1333         if ( pFormula1 )
1334             pRet.reset(new ScTokenArray( *pFormula1 ));
1335         else
1336         {
1337             pRet.reset(new ScTokenArray(*mpDoc));
1338             if (bIsStr1)
1339             {
1340                 svl::SharedStringPool& rSPool = mpDoc->GetSharedStringPool();
1341                 pRet->AddString(rSPool.intern(aStrVal1));
1342             }
1343             else
1344                 pRet->AddDouble( nVal1 );
1345         }
1346     }
1347     else if ( nIndex==1 )
1348     {
1349         if ( pFormula2 )
1350             pRet.reset(new ScTokenArray( *pFormula2 ));
1351         else
1352         {
1353             pRet.reset(new ScTokenArray(*mpDoc));
1354             if (bIsStr2)
1355             {
1356                 svl::SharedStringPool& rSPool = mpDoc->GetSharedStringPool();
1357                 pRet->AddString(rSPool.intern(aStrVal2));
1358             }
1359             else
1360                 pRet->AddDouble( nVal2 );
1361         }
1362     }
1363 
1364     return pRet;
1365 }
1366 
1367 /**
1368  * Return a position that's adjusted to allow textual representation
1369  * of expressions if possible
1370  */
GetValidSrcPos() const1371 ScAddress ScConditionEntry::GetValidSrcPos() const
1372 {
1373     SCTAB nMinTab = aSrcPos.Tab();
1374     SCTAB nMaxTab = nMinTab;
1375 
1376     for (sal_uInt16 nPass = 0; nPass < 2; nPass++)
1377     {
1378         ScTokenArray* pFormula = nPass ? pFormula2.get() : pFormula1.get();
1379         if (pFormula)
1380         {
1381             for ( auto t: pFormula->References() )
1382             {
1383                 ScSingleRefData& rRef1 = *t->GetSingleRef();
1384                 ScAddress aAbs = rRef1.toAbs(*mpDoc, aSrcPos);
1385                 if (!rRef1.IsTabDeleted())
1386                 {
1387                     if (aAbs.Tab() < nMinTab)
1388                         nMinTab = aAbs.Tab();
1389                     if (aAbs.Tab() > nMaxTab)
1390                         nMaxTab = aAbs.Tab();
1391                 }
1392                 if ( t->GetType() == svDoubleRef )
1393                 {
1394                     ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
1395                     aAbs = rRef2.toAbs(*mpDoc, aSrcPos);
1396                     if (!rRef2.IsTabDeleted())
1397                     {
1398                         if (aAbs.Tab() < nMinTab)
1399                             nMinTab = aAbs.Tab();
1400                         if (aAbs.Tab() > nMaxTab)
1401                             nMaxTab = aAbs.Tab();
1402                     }
1403                 }
1404             }
1405         }
1406     }
1407 
1408     ScAddress aValidPos = aSrcPos;
1409     SCTAB nTabCount = mpDoc->GetTableCount();
1410     if ( nMaxTab >= nTabCount && nMinTab > 0 )
1411         aValidPos.SetTab( aSrcPos.Tab() - nMinTab ); // so the lowest tab ref will be on 0
1412 
1413     if ( aValidPos.Tab() >= nTabCount )
1414         aValidPos.SetTab( nTabCount - 1 );  // ensure a valid position even if some references will be invalid
1415 
1416     return aValidPos;
1417 }
1418 
DataChanged() const1419 void ScConditionEntry::DataChanged() const
1420 {
1421     //FIXME: Nothing so far
1422 }
1423 
MarkUsedExternalReferences() const1424 bool ScConditionEntry::MarkUsedExternalReferences() const
1425 {
1426     bool bAllMarked = false;
1427     for (sal_uInt16 nPass = 0; !bAllMarked && nPass < 2; nPass++)
1428     {
1429         ScTokenArray* pFormula = nPass ? pFormula2.get() : pFormula1.get();
1430         if (pFormula)
1431             bAllMarked = mpDoc->MarkUsedExternalReferences(*pFormula, aSrcPos);
1432     }
1433     return bAllMarked;
1434 }
1435 
Clone(ScDocument * pDoc) const1436 ScFormatEntry* ScConditionEntry::Clone(ScDocument* pDoc) const
1437 {
1438     return new ScConditionEntry(*pDoc, *this);
1439 }
1440 
GetModeFromApi(css::sheet::ConditionOperator nOperation)1441 ScConditionMode ScConditionEntry::GetModeFromApi(css::sheet::ConditionOperator nOperation)
1442 {
1443     ScConditionMode eMode = ScConditionMode::NONE;
1444     switch (static_cast<sal_Int32>(nOperation))
1445     {
1446         case css::sheet::ConditionOperator2::EQUAL:
1447             eMode = ScConditionMode::Equal;
1448             break;
1449         case css::sheet::ConditionOperator2::LESS:
1450             eMode = ScConditionMode::Less;
1451             break;
1452         case css::sheet::ConditionOperator2::GREATER:
1453             eMode = ScConditionMode::Greater;
1454             break;
1455         case css::sheet::ConditionOperator2::LESS_EQUAL:
1456             eMode = ScConditionMode::EqLess;
1457             break;
1458         case css::sheet::ConditionOperator2::GREATER_EQUAL:
1459             eMode = ScConditionMode::EqGreater;
1460             break;
1461         case css::sheet::ConditionOperator2::NOT_EQUAL:
1462             eMode = ScConditionMode::NotEqual;
1463             break;
1464         case css::sheet::ConditionOperator2::BETWEEN:
1465             eMode = ScConditionMode::Between;
1466             break;
1467         case css::sheet::ConditionOperator2::NOT_BETWEEN:
1468             eMode = ScConditionMode::NotBetween;
1469             break;
1470         case css::sheet::ConditionOperator2::FORMULA:
1471             eMode = ScConditionMode::Direct;
1472             break;
1473         case css::sheet::ConditionOperator2::DUPLICATE:
1474             eMode = ScConditionMode::Duplicate;
1475             break;
1476         case css::sheet::ConditionOperator2::NOT_DUPLICATE:
1477             eMode = ScConditionMode::NotDuplicate;
1478             break;
1479         default:
1480             break;
1481     }
1482     return eMode;
1483 }
1484 
startRendering()1485 void ScConditionEntry::startRendering()
1486 {
1487     mpCache.reset();
1488 }
1489 
endRendering()1490 void ScConditionEntry::endRendering()
1491 {
1492     mpCache.reset();
1493 }
1494 
NeedsRepaint() const1495 bool ScConditionEntry::NeedsRepaint() const
1496 {
1497     return mpListener->NeedsRepaint();
1498 }
1499 
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)1500 ScCondFormatEntry::ScCondFormatEntry( ScConditionMode eOper,
1501                                         const OUString& rExpr1, const OUString& rExpr2,
1502                                         ScDocument& rDocument, const ScAddress& rPos,
1503                                         OUString aStyle,
1504                                         const OUString& rExprNmsp1, const OUString& rExprNmsp2,
1505                                         FormulaGrammar::Grammar eGrammar1,
1506                                         FormulaGrammar::Grammar eGrammar2,
1507                                         ScFormatEntry::Type eType ) :
1508     ScConditionEntry( eOper, rExpr1, rExpr2, rDocument, rPos, rExprNmsp1, rExprNmsp2, eGrammar1, eGrammar2, eType ),
1509     aStyleName(std::move( aStyle )),
1510     eCondFormatType( eType )
1511 {
1512 }
1513 
ScCondFormatEntry(ScConditionMode eOper,const ScTokenArray * pArr1,const ScTokenArray * pArr2,ScDocument & rDocument,const ScAddress & rPos,OUString aStyle)1514 ScCondFormatEntry::ScCondFormatEntry( ScConditionMode eOper,
1515                                         const ScTokenArray* pArr1, const ScTokenArray* pArr2,
1516                                         ScDocument& rDocument, const ScAddress& rPos,
1517                                         OUString aStyle ) :
1518     ScConditionEntry( eOper, pArr1, pArr2, rDocument, rPos ),
1519     aStyleName(std::move( aStyle ))
1520 {
1521 }
1522 
ScCondFormatEntry(const ScCondFormatEntry & r)1523 ScCondFormatEntry::ScCondFormatEntry( const ScCondFormatEntry& r ) :
1524     ScConditionEntry( r ),
1525     aStyleName( r.aStyleName ),
1526     eCondFormatType( r.eCondFormatType)
1527 {
1528 }
1529 
ScCondFormatEntry(ScDocument & rDocument,const ScCondFormatEntry & r)1530 ScCondFormatEntry::ScCondFormatEntry( ScDocument& rDocument, const ScCondFormatEntry& r ) :
1531     ScConditionEntry( rDocument, r ),
1532     aStyleName( r.aStyleName ),
1533     eCondFormatType( r.eCondFormatType)
1534 {
1535 }
1536 
1537 // virtual
IsEqual(const ScFormatEntry & r,bool bIgnoreSrcPos) const1538 bool ScCondFormatEntry::IsEqual( const ScFormatEntry& r, bool bIgnoreSrcPos ) const
1539 {
1540     return ScConditionEntry::IsEqual(r, bIgnoreSrcPos) &&
1541         (aStyleName == static_cast<const ScCondFormatEntry&>(r).aStyleName);
1542 }
1543 
~ScCondFormatEntry()1544 ScCondFormatEntry::~ScCondFormatEntry()
1545 {
1546 }
1547 
DataChanged() const1548 void ScCondFormatEntry::DataChanged() const
1549 {
1550     if ( pCondFormat )
1551         pCondFormat->DoRepaint();
1552 }
1553 
Clone(ScDocument * pDoc) const1554 ScFormatEntry* ScCondFormatEntry::Clone( ScDocument* pDoc ) const
1555 {
1556     return new ScCondFormatEntry( *pDoc, *this );
1557 }
1558 
CalcAll()1559 void ScConditionEntry::CalcAll()
1560 {
1561     if (pFCell1 || pFCell2)
1562     {
1563         if (pFCell1)
1564             pFCell1->SetDirty();
1565         if (pFCell2)
1566             pFCell2->SetDirty();
1567         pCondFormat->DoRepaint();
1568     }
1569 }
1570 
ScCondDateFormatEntry(ScDocument * pDoc)1571 ScCondDateFormatEntry::ScCondDateFormatEntry( ScDocument* pDoc )
1572     : ScFormatEntry( pDoc )
1573     , meType(condformat::TODAY)
1574 {
1575 }
1576 
ScCondDateFormatEntry(ScDocument * pDoc,const ScCondDateFormatEntry & rFormat)1577 ScCondDateFormatEntry::ScCondDateFormatEntry( ScDocument* pDoc, const ScCondDateFormatEntry& rFormat ):
1578     ScFormatEntry( pDoc ),
1579     meType( rFormat.meType ),
1580     maStyleName( rFormat.maStyleName )
1581 {
1582 }
1583 
IsValid(const ScAddress & rPos) const1584 bool ScCondDateFormatEntry::IsValid( const ScAddress& rPos ) const
1585 {
1586     ScRefCellValue rCell(*mpDoc, rPos);
1587 
1588     if (!rCell.hasNumeric())
1589         // non-numerical cell.
1590         return false;
1591 
1592     if( !mpCache )
1593         mpCache.reset( new Date( Date::SYSTEM ) );
1594 
1595     const Date& rActDate = *mpCache;
1596     SvNumberFormatter* pFormatter = mpDoc->GetFormatTable();
1597     sal_Int32 nCurrentDate = rActDate - pFormatter->GetNullDate();
1598 
1599     double nVal = rCell.getValue();
1600     sal_Int32 nCellDate = static_cast<sal_Int32>(::rtl::math::approxFloor(nVal));
1601     Date aCellDate = pFormatter->GetNullDate();
1602     aCellDate.AddDays(nCellDate);
1603 
1604     switch(meType)
1605     {
1606         case condformat::TODAY:
1607             if( nCurrentDate == nCellDate )
1608                 return true;
1609             break;
1610         case condformat::TOMORROW:
1611             if( nCurrentDate == nCellDate -1 )
1612                 return true;
1613             break;
1614         case condformat::YESTERDAY:
1615             if( nCurrentDate == nCellDate + 1)
1616                 return true;
1617             break;
1618         case condformat::LAST7DAYS:
1619             if( nCurrentDate >= nCellDate && nCurrentDate - 7 < nCellDate )
1620                 return true;
1621             break;
1622         case condformat::LASTWEEK:
1623             {
1624                 const DayOfWeek eDay = rActDate.GetDayOfWeek();
1625                 if( eDay != SUNDAY )
1626                 {
1627                     Date aBegin(rActDate - (8 + static_cast<sal_Int32>(eDay)));
1628                     Date aEnd(rActDate - (2 + static_cast<sal_Int32>(eDay)));
1629                     return aCellDate.IsBetween( aBegin, aEnd );
1630                 }
1631                 else
1632                 {
1633                     Date aBegin(rActDate - 8);
1634                     Date aEnd(rActDate - 1);
1635                     return aCellDate.IsBetween( aBegin, aEnd );
1636                 }
1637             }
1638             break;
1639         case condformat::THISWEEK:
1640             {
1641                 const DayOfWeek eDay = rActDate.GetDayOfWeek();
1642                 if( eDay != SUNDAY )
1643                 {
1644                     Date aBegin(rActDate - (1 + static_cast<sal_Int32>(eDay)));
1645                     Date aEnd(rActDate + (5 - static_cast<sal_Int32>(eDay)));
1646                     return aCellDate.IsBetween( aBegin, aEnd );
1647                 }
1648                 else
1649                 {
1650                     Date aEnd( rActDate + 6);
1651                     return aCellDate.IsBetween( rActDate, aEnd );
1652                 }
1653             }
1654             break;
1655         case condformat::NEXTWEEK:
1656             {
1657                 const DayOfWeek eDay = rActDate.GetDayOfWeek();
1658                 if( eDay != SUNDAY )
1659                 {
1660                     return aCellDate.IsBetween( rActDate + (6 - static_cast<sal_Int32>(eDay)),
1661                             rActDate + (12 - static_cast<sal_Int32>(eDay)) );
1662                 }
1663                 else
1664                 {
1665                     return aCellDate.IsBetween( rActDate + 7, rActDate + 13 );
1666                 }
1667             }
1668             break;
1669         case condformat::LASTMONTH:
1670             if( rActDate.GetMonth() == 1 )
1671             {
1672                 if( aCellDate.GetMonth() == 12 && rActDate.GetYear() == aCellDate.GetNextYear() )
1673                     return true;
1674             }
1675             else if( rActDate.GetYear() == aCellDate.GetYear() )
1676             {
1677                 if( rActDate.GetMonth() == aCellDate.GetMonth() + 1)
1678                     return true;
1679             }
1680             break;
1681         case condformat::THISMONTH:
1682             if( rActDate.GetYear() == aCellDate.GetYear() )
1683             {
1684                 if( rActDate.GetMonth() == aCellDate.GetMonth() )
1685                     return true;
1686             }
1687             break;
1688         case condformat::NEXTMONTH:
1689             if( rActDate.GetMonth() == 12 )
1690             {
1691                 if( aCellDate.GetMonth() == 1 && rActDate.GetYear() == aCellDate.GetYear() - 1 )
1692                     return true;
1693             }
1694             else if( rActDate.GetYear() == aCellDate.GetYear() )
1695             {
1696                 if( rActDate.GetMonth() == aCellDate.GetMonth() - 1)
1697                     return true;
1698             }
1699             break;
1700         case condformat::LASTYEAR:
1701             if( rActDate.GetYear() == aCellDate.GetNextYear() )
1702                 return true;
1703             break;
1704         case condformat::THISYEAR:
1705             if( rActDate.GetYear() == aCellDate.GetYear() )
1706                 return true;
1707             break;
1708         case condformat::NEXTYEAR:
1709             if( rActDate.GetYear() == aCellDate.GetYear() - 1 )
1710                 return true;
1711             break;
1712     }
1713 
1714     return false;
1715 }
1716 
SetDateType(condformat::ScCondFormatDateType eType)1717 void ScCondDateFormatEntry::SetDateType( condformat::ScCondFormatDateType eType )
1718 {
1719     meType = eType;
1720 }
1721 
SetStyleName(const OUString & rStyleName)1722 void ScCondDateFormatEntry::SetStyleName( const OUString& rStyleName )
1723 {
1724     maStyleName = rStyleName;
1725 }
1726 
Clone(ScDocument * pDoc) const1727 ScFormatEntry* ScCondDateFormatEntry::Clone( ScDocument* pDoc ) const
1728 {
1729     return new ScCondDateFormatEntry( pDoc, *this );
1730 }
1731 
startRendering()1732 void ScCondDateFormatEntry::startRendering()
1733 {
1734     mpCache.reset();
1735 }
1736 
endRendering()1737 void ScCondDateFormatEntry::endRendering()
1738 {
1739     mpCache.reset();
1740 }
1741 
ScColorFormatCache(ScDocument & rDoc,const ScRangeList & rRanges)1742 ScColorFormatCache::ScColorFormatCache(ScDocument& rDoc, const ScRangeList& rRanges) :
1743     mrDoc(rDoc)
1744 {
1745     if (mrDoc.IsClipOrUndo())
1746         return;
1747 
1748     for (const ScRange& rRange: rRanges)
1749         mrDoc.StartListeningArea(rRange, false, this);
1750 }
1751 
~ScColorFormatCache()1752 ScColorFormatCache::~ScColorFormatCache()
1753 {
1754     if (mrDoc.IsClipOrUndo())
1755         return;
1756 
1757     EndListeningAll();
1758 }
1759 
Notify(const SfxHint & rHint)1760 void ScColorFormatCache::Notify(const SfxHint& rHint)
1761 {
1762     if (rHint.GetId() == SfxHintId::Dying)
1763     {
1764         EndListeningAll();
1765         return;
1766     }
1767 
1768     maValues.clear();
1769 }
1770 
1771 
ScConditionalFormat(sal_uInt32 nNewKey,ScDocument * pDocument)1772 ScConditionalFormat::ScConditionalFormat(sal_uInt32 nNewKey, ScDocument* pDocument) :
1773     pDoc( pDocument ),
1774     nKey( nNewKey )
1775 {
1776 }
1777 
Clone(ScDocument * pNewDoc) const1778 std::unique_ptr<ScConditionalFormat> ScConditionalFormat::Clone(ScDocument* pNewDoc) const
1779 {
1780     // Real copy of the formula (for Ref Undo/between documents)
1781     if (!pNewDoc)
1782         pNewDoc = pDoc;
1783 
1784     std::unique_ptr<ScConditionalFormat> pNew(new ScConditionalFormat(nKey, pNewDoc));
1785     pNew->SetRange( maRanges );     // prerequisite for listeners
1786 
1787     for (const auto& rxEntry : maEntries)
1788     {
1789         ScFormatEntry* pNewEntry = rxEntry->Clone(pNewDoc);
1790         pNew->maEntries.push_back( std::unique_ptr<ScFormatEntry>(pNewEntry) );
1791         pNewEntry->SetParent(pNew.get());
1792     }
1793 
1794     return pNew;
1795 }
1796 
EqualEntries(const ScConditionalFormat & r,bool bIgnoreSrcPos) const1797 bool ScConditionalFormat::EqualEntries( const ScConditionalFormat& r, bool bIgnoreSrcPos ) const
1798 {
1799     if( size() != r.size())
1800         return false;
1801 
1802     //TODO: Test for same entries in reverse order?
1803     if (! std::equal(maEntries.begin(), maEntries.end(), r.maEntries.begin(),
1804         [&bIgnoreSrcPos](const std::unique_ptr<ScFormatEntry>& p1, const std::unique_ptr<ScFormatEntry>& p2) -> bool
1805             {
1806                 return p1->IsEqual(*p2, bIgnoreSrcPos);
1807             }))
1808         return false;
1809 
1810     // right now don't check for same range
1811     // we only use this method to merge same conditional formats from
1812     // old ODF data structure
1813     return true;
1814 }
1815 
SetRange(const ScRangeList & rRanges)1816 void ScConditionalFormat::SetRange( const ScRangeList& rRanges )
1817 {
1818     maRanges = rRanges;
1819     SAL_WARN_IF(maRanges.empty(), "sc", "the conditional format range is empty! will result in a crash later!");
1820     ResetCache();
1821 }
1822 
AddEntry(ScFormatEntry * pNew)1823 void ScConditionalFormat::AddEntry( ScFormatEntry* pNew )
1824 {
1825     maEntries.push_back( std::unique_ptr<ScFormatEntry>(pNew));
1826     pNew->SetParent(this);
1827 }
1828 
RemoveEntry(size_t n)1829 void ScConditionalFormat::RemoveEntry(size_t n)
1830 {
1831     if (n < maEntries.size())
1832     {
1833         maEntries.erase(maEntries.begin() + n);
1834         DoRepaint();
1835     }
1836 }
1837 
IsEmpty() const1838 bool ScConditionalFormat::IsEmpty() const
1839 {
1840     return maEntries.empty();
1841 }
1842 
size() const1843 size_t ScConditionalFormat::size() const
1844 {
1845     return maEntries.size();
1846 }
1847 
GetDocument()1848 ScDocument* ScConditionalFormat::GetDocument()
1849 {
1850     return pDoc;
1851 }
1852 
~ScConditionalFormat()1853 ScConditionalFormat::~ScConditionalFormat()
1854 {
1855 }
1856 
GetEntry(sal_uInt16 nPos) const1857 const ScFormatEntry* ScConditionalFormat::GetEntry( sal_uInt16 nPos ) const
1858 {
1859     if ( nPos < size() )
1860         return maEntries[nPos].get();
1861     else
1862         return nullptr;
1863 }
1864 
GetCellStyle(ScRefCellValue & rCell,const ScAddress & rPos) const1865 OUString ScConditionalFormat::GetCellStyle( ScRefCellValue& rCell, const ScAddress& rPos ) const
1866 {
1867     for (const auto& rxEntry : maEntries)
1868     {
1869         if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
1870            rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
1871         {
1872             const ScCondFormatEntry& rEntry = static_cast<const ScCondFormatEntry&>(*rxEntry);
1873             if (rEntry.IsCellValid(rCell, rPos))
1874                 return rEntry.GetStyle();
1875         }
1876         else if(rxEntry->GetType() == ScFormatEntry::Type::Date)
1877         {
1878             const ScCondDateFormatEntry& rEntry = static_cast<const ScCondDateFormatEntry&>(*rxEntry);
1879             if (rEntry.IsValid( rPos ))
1880                 return rEntry.GetStyleName();
1881         }
1882     }
1883 
1884     return OUString();
1885 }
1886 
GetData(ScRefCellValue & rCell,const ScAddress & rPos) const1887 ScCondFormatData ScConditionalFormat::GetData( ScRefCellValue& rCell, const ScAddress& rPos ) const
1888 {
1889     ScCondFormatData aData;
1890     for(const auto& rxEntry : maEntries)
1891     {
1892         if( (rxEntry->GetType() == ScFormatEntry::Type::Condition ||
1893              rxEntry->GetType() == ScFormatEntry::Type::ExtCondition) &&
1894              aData.aStyleName.isEmpty())
1895         {
1896             const ScCondFormatEntry& rEntry = static_cast<const ScCondFormatEntry&>(*rxEntry);
1897             if (rEntry.IsCellValid(rCell, rPos))
1898                 aData.aStyleName = rEntry.GetStyle();
1899         }
1900         else if(rxEntry->GetType() == ScFormatEntry::Type::Colorscale && !aData.mxColorScale)
1901         {
1902             const ScColorScaleFormat& rEntry = static_cast<const ScColorScaleFormat&>(*rxEntry);
1903             aData.mxColorScale = rEntry.GetColor(rPos);
1904         }
1905         else if(rxEntry->GetType() == ScFormatEntry::Type::Databar && !aData.pDataBar)
1906         {
1907             const ScDataBarFormat& rEntry = static_cast<const ScDataBarFormat&>(*rxEntry);
1908             aData.pDataBar = rEntry.GetDataBarInfo(rPos);
1909         }
1910         else if(rxEntry->GetType() == ScFormatEntry::Type::Iconset && !aData.pIconSet)
1911         {
1912             const ScIconSetFormat& rEntry = static_cast<const ScIconSetFormat&>(*rxEntry);
1913             aData.pIconSet = rEntry.GetIconSetInfo(rPos);
1914         }
1915         else if(rxEntry->GetType() == ScFormatEntry::Type::Date && aData.aStyleName.isEmpty())
1916         {
1917             const ScCondDateFormatEntry& rEntry = static_cast<const ScCondDateFormatEntry&>(*rxEntry);
1918             if ( rEntry.IsValid( rPos ) )
1919                 aData.aStyleName = rEntry.GetStyleName();
1920         }
1921     }
1922     return aData;
1923 }
1924 
DoRepaint()1925 void ScConditionalFormat::DoRepaint()
1926 {
1927     // all conditional format cells
1928     pDoc->RepaintRange( maRanges );
1929 }
1930 
CompileAll()1931 void ScConditionalFormat::CompileAll()
1932 {
1933     for(auto& rxEntry : maEntries)
1934         if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
1935            rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
1936             static_cast<ScCondFormatEntry&>(*rxEntry).CompileAll();
1937 }
1938 
CompileXML()1939 void ScConditionalFormat::CompileXML()
1940 {
1941     for(auto& rxEntry : maEntries)
1942         if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
1943            rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
1944             static_cast<ScCondFormatEntry&>(*rxEntry).CompileXML();
1945 }
1946 
UpdateReference(sc::RefUpdateContext & rCxt,bool bCopyAsMove)1947 void ScConditionalFormat::UpdateReference( sc::RefUpdateContext& rCxt, bool bCopyAsMove )
1948 {
1949     if (rCxt.meMode == URM_COPY && bCopyAsMove)
1950     {
1951         // ScConditionEntry::UpdateReference() obtains its aSrcPos from
1952         // maRanges and does not update it on URM_COPY, but it's needed later
1953         // for the moved position, so update maRanges beforehand.
1954         maRanges.UpdateReference(URM_MOVE, pDoc, rCxt.maRange, rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta);
1955         for (auto& rxEntry : maEntries)
1956             rxEntry->UpdateReference(rCxt);
1957     }
1958     else
1959     {
1960         for (auto& rxEntry : maEntries)
1961             rxEntry->UpdateReference(rCxt);
1962         maRanges.UpdateReference(rCxt.meMode, pDoc, rCxt.maRange, rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta);
1963     }
1964 
1965     ResetCache();
1966 }
1967 
InsertRow(SCTAB nTab,SCCOL nColStart,SCCOL nColEnd,SCROW nRowPos,SCSIZE nSize)1968 void ScConditionalFormat::InsertRow(SCTAB nTab, SCCOL nColStart, SCCOL nColEnd, SCROW nRowPos, SCSIZE nSize)
1969 {
1970     maRanges.InsertRow(nTab, nColStart, nColEnd, nRowPos, nSize);
1971     ResetCache();
1972 }
1973 
InsertCol(SCTAB nTab,SCROW nRowStart,SCROW nRowEnd,SCCOL nColPos,SCSIZE nSize)1974 void ScConditionalFormat::InsertCol(SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, SCCOL nColPos, SCSIZE nSize)
1975 {
1976     maRanges.InsertCol(nTab, nRowStart, nRowEnd, nColPos, nSize);
1977     ResetCache();
1978 }
1979 
UpdateInsertTab(sc::RefUpdateInsertTabContext & rCxt)1980 void ScConditionalFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
1981 {
1982     for (size_t i = 0, n = maRanges.size(); i < n; ++i)
1983     {
1984         // We assume that the start and end sheet indices are equal.
1985         ScRange & rRange = maRanges[i];
1986         SCTAB nTab = rRange.aStart.Tab();
1987 
1988         if (nTab < rCxt.mnInsertPos)
1989             // Unaffected.
1990             continue;
1991 
1992         rRange.aStart.IncTab(rCxt.mnSheets);
1993         rRange.aEnd.IncTab(rCxt.mnSheets);
1994     }
1995 
1996     ResetCache();
1997 
1998     for (auto& rxEntry : maEntries)
1999         rxEntry->UpdateInsertTab(rCxt);
2000 }
2001 
UpdateDeleteTab(sc::RefUpdateDeleteTabContext & rCxt)2002 void ScConditionalFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
2003 {
2004     for (size_t i = 0, n = maRanges.size(); i < n; ++i)
2005     {
2006         // We assume that the start and end sheet indices are equal.
2007         ScRange & rRange = maRanges[i];
2008         SCTAB nTab = rRange.aStart.Tab();
2009 
2010         if (nTab < rCxt.mnDeletePos)
2011             // Left of the deleted sheet(s).  Unaffected.
2012             continue;
2013 
2014         if (nTab <= rCxt.mnDeletePos+rCxt.mnSheets-1)
2015         {
2016             // On the deleted sheet(s).
2017             rRange.aStart.SetTab(-1);
2018             rRange.aEnd.SetTab(-1);
2019             continue;
2020         }
2021 
2022         // Right of the deleted sheet(s).  Adjust the sheet indices.
2023         rRange.aStart.IncTab(-1*rCxt.mnSheets);
2024         rRange.aEnd.IncTab(-1*rCxt.mnSheets);
2025     }
2026 
2027     ResetCache();
2028 
2029     for (auto& rxEntry : maEntries)
2030         rxEntry->UpdateDeleteTab(rCxt);
2031 }
2032 
UpdateMoveTab(sc::RefUpdateMoveTabContext & rCxt)2033 void ScConditionalFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
2034 {
2035     size_t n = maRanges.size();
2036     SCTAB nMinTab = std::min<SCTAB>(rCxt.mnOldPos, rCxt.mnNewPos);
2037     SCTAB nMaxTab = std::max<SCTAB>(rCxt.mnOldPos, rCxt.mnNewPos);
2038     for(size_t i = 0; i < n; ++i)
2039     {
2040         ScRange & rRange = maRanges[i];
2041         SCTAB nTab = rRange.aStart.Tab();
2042         if(nTab < nMinTab || nTab > nMaxTab)
2043         {
2044             continue;
2045         }
2046 
2047         if (nTab == rCxt.mnOldPos)
2048         {
2049             rRange.aStart.SetTab(rCxt.mnNewPos);
2050             rRange.aEnd.SetTab(rCxt.mnNewPos);
2051             continue;
2052         }
2053 
2054         if (rCxt.mnNewPos < rCxt.mnOldPos)
2055         {
2056             rRange.aStart.IncTab();
2057             rRange.aEnd.IncTab();
2058         }
2059         else
2060         {
2061             rRange.aStart.IncTab(-1);
2062             rRange.aEnd.IncTab(-1);
2063         }
2064     }
2065 
2066     ResetCache();
2067 
2068     for (auto& rxEntry : maEntries)
2069         rxEntry->UpdateMoveTab(rCxt);
2070 }
2071 
DeleteArea(SCCOL nCol1,SCROW nRow1,SCCOL nCol2,SCROW nRow2)2072 void ScConditionalFormat::DeleteArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
2073 {
2074     if (maRanges.empty())
2075         return;
2076 
2077     SCTAB nTab = maRanges[0].aStart.Tab();
2078     maRanges.DeleteArea( nCol1, nRow1, nTab, nCol2, nRow2, nTab );
2079     ResetCache();
2080 }
2081 
RenameCellStyle(std::u16string_view rOld,const OUString & rNew)2082 void ScConditionalFormat::RenameCellStyle(std::u16string_view rOld, const OUString& rNew)
2083 {
2084     for(const auto& rxEntry : maEntries)
2085         if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
2086            rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
2087         {
2088             ScCondFormatEntry& rFormat = static_cast<ScCondFormatEntry&>(*rxEntry);
2089             if(rFormat.GetStyle() == rOld)
2090                 rFormat.UpdateStyleName( rNew );
2091         }
2092 }
2093 
MarkUsedExternalReferences() const2094 bool ScConditionalFormat::MarkUsedExternalReferences() const
2095 {
2096     bool bAllMarked = false;
2097     for(const auto& rxEntry : maEntries)
2098         if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
2099            rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
2100         {
2101             const ScCondFormatEntry& rFormat = static_cast<const ScCondFormatEntry&>(*rxEntry);
2102             bAllMarked = rFormat.MarkUsedExternalReferences();
2103             if (bAllMarked)
2104                 break;
2105         }
2106 
2107     return bAllMarked;
2108 }
2109 
startRendering()2110 void ScConditionalFormat::startRendering()
2111 {
2112     for(auto& rxEntry : maEntries)
2113     {
2114         rxEntry->startRendering();
2115     }
2116 }
2117 
endRendering()2118 void ScConditionalFormat::endRendering()
2119 {
2120     for(auto& rxEntry : maEntries)
2121     {
2122         rxEntry->endRendering();
2123     }
2124 }
2125 
updateValues()2126 void ScConditionalFormat::updateValues()
2127 {
2128     for(auto& rxEntry : maEntries)
2129     {
2130         rxEntry->updateValues();
2131     }
2132 }
2133 
CalcAll()2134 void ScConditionalFormat::CalcAll()
2135 {
2136     for(const auto& rxEntry : maEntries)
2137     {
2138         if (rxEntry->GetType() == ScFormatEntry::Type::Condition ||
2139             rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
2140         {
2141             ScCondFormatEntry& rFormat = static_cast<ScCondFormatEntry&>(*rxEntry);
2142             rFormat.CalcAll();
2143         }
2144     }
2145 }
2146 
ResetCache() const2147 void ScConditionalFormat::ResetCache() const
2148 {
2149     if (!maRanges.empty() && pDoc)
2150         mpCache = std::make_unique<ScColorFormatCache>(*pDoc, maRanges);
2151     else
2152         mpCache.reset();
2153 }
2154 
SetCache(const std::vector<double> & aValues) const2155 void ScConditionalFormat::SetCache(const std::vector<double>& aValues) const
2156 {
2157     if (!mpCache)
2158         ResetCache();
2159     if (mpCache)
2160         mpCache->maValues = aValues;
2161 }
2162 
GetCache() const2163 std::vector<double>* ScConditionalFormat::GetCache() const
2164 {
2165     return mpCache ? &mpCache->maValues : nullptr;
2166 }
2167 
ScConditionalFormatList(const ScConditionalFormatList & rList)2168 ScConditionalFormatList::ScConditionalFormatList(const ScConditionalFormatList& rList)
2169 {
2170     for(const auto& rxFormat : rList)
2171         InsertNew( rxFormat->Clone() );
2172 }
2173 
ScConditionalFormatList(ScDocument & rDoc,const ScConditionalFormatList & rList)2174 ScConditionalFormatList::ScConditionalFormatList(ScDocument& rDoc, const ScConditionalFormatList& rList)
2175 {
2176     for(const auto& rxFormat : rList)
2177         InsertNew( rxFormat->Clone(&rDoc) );
2178 }
2179 
InsertNew(std::unique_ptr<ScConditionalFormat> pNew)2180 void ScConditionalFormatList::InsertNew( std::unique_ptr<ScConditionalFormat> pNew )
2181 {
2182     m_ConditionalFormats.insert(std::move(pNew));
2183 }
2184 
GetFormat(sal_uInt32 nKey)2185 ScConditionalFormat* ScConditionalFormatList::GetFormat( sal_uInt32 nKey )
2186 {
2187     auto itr = m_ConditionalFormats.find(nKey);
2188     if (itr != m_ConditionalFormats.end())
2189         return itr->get();
2190 
2191     SAL_WARN("sc", "ScConditionalFormatList: Entry not found");
2192     return nullptr;
2193 }
2194 
GetFormat(sal_uInt32 nKey) const2195 const ScConditionalFormat* ScConditionalFormatList::GetFormat( sal_uInt32 nKey ) const
2196 {
2197     auto itr = m_ConditionalFormats.find(nKey);
2198     if (itr != m_ConditionalFormats.end())
2199         return itr->get();
2200 
2201     SAL_WARN("sc", "ScConditionalFormatList: Entry not found");
2202     return nullptr;
2203 }
2204 
CompileAll()2205 void ScConditionalFormatList::CompileAll()
2206 {
2207     for (auto const& it : m_ConditionalFormats)
2208     {
2209         it->CompileAll();
2210     }
2211 }
2212 
CompileXML()2213 void ScConditionalFormatList::CompileXML()
2214 {
2215     for (auto const& it : m_ConditionalFormats)
2216     {
2217         it->CompileXML();
2218     }
2219 }
2220 
UpdateReference(sc::RefUpdateContext & rCxt)2221 void ScConditionalFormatList::UpdateReference( sc::RefUpdateContext& rCxt )
2222 {
2223     for (auto const& it : m_ConditionalFormats)
2224     {
2225         it->UpdateReference(rCxt);
2226     }
2227 
2228     if (rCxt.meMode == URM_INSDEL)
2229     {
2230         // need to check which must be deleted
2231         CheckAllEntries();
2232     }
2233 }
2234 
InsertRow(SCTAB nTab,SCCOL nColStart,SCCOL nColEnd,SCROW nRowPos,SCSIZE nSize)2235 void ScConditionalFormatList::InsertRow(SCTAB nTab, SCCOL nColStart, SCCOL nColEnd, SCROW nRowPos, SCSIZE nSize)
2236 {
2237     for (auto const& it : m_ConditionalFormats)
2238     {
2239         it->InsertRow(nTab, nColStart, nColEnd, nRowPos, nSize);
2240     }
2241 }
2242 
InsertCol(SCTAB nTab,SCROW nRowStart,SCROW nRowEnd,SCCOL nColPos,SCSIZE nSize)2243 void ScConditionalFormatList::InsertCol(SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, SCCOL nColPos, SCSIZE nSize)
2244 {
2245     for (auto const& it : m_ConditionalFormats)
2246     {
2247         it->InsertCol(nTab, nRowStart, nRowEnd, nColPos, nSize);
2248     }
2249 }
2250 
UpdateInsertTab(sc::RefUpdateInsertTabContext & rCxt)2251 void ScConditionalFormatList::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
2252 {
2253     for (auto const& it : m_ConditionalFormats)
2254     {
2255         it->UpdateInsertTab(rCxt);
2256     }
2257 }
2258 
UpdateDeleteTab(sc::RefUpdateDeleteTabContext & rCxt)2259 void ScConditionalFormatList::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
2260 {
2261     for (auto const& it : m_ConditionalFormats)
2262     {
2263         it->UpdateDeleteTab(rCxt);
2264     }
2265 }
2266 
UpdateMoveTab(sc::RefUpdateMoveTabContext & rCxt)2267 void ScConditionalFormatList::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
2268 {
2269     for (auto const& it : m_ConditionalFormats)
2270     {
2271         it->UpdateMoveTab(rCxt);
2272     }
2273 }
2274 
RenameCellStyle(std::u16string_view rOld,const OUString & rNew)2275 void ScConditionalFormatList::RenameCellStyle( std::u16string_view rOld, const OUString& rNew )
2276 {
2277     for (auto const& it : m_ConditionalFormats)
2278     {
2279         it->RenameCellStyle(rOld, rNew);
2280     }
2281 }
2282 
CheckAllEntries(const Link<ScConditionalFormat *,void> & rLink)2283 bool ScConditionalFormatList::CheckAllEntries(const Link<ScConditionalFormat*,void>& rLink)
2284 {
2285     bool bValid = true;
2286 
2287     // need to check which must be deleted
2288     iterator itr = m_ConditionalFormats.begin();
2289     while(itr != m_ConditionalFormats.end())
2290     {
2291         if ((*itr)->GetRange().empty())
2292         {
2293             bValid = false;
2294             if (rLink.IsSet())
2295                 rLink.Call(itr->get());
2296             itr = m_ConditionalFormats.erase(itr);
2297         }
2298         else
2299             ++itr;
2300     }
2301 
2302     return bValid;
2303 }
2304 
DeleteArea(SCCOL nCol1,SCROW nRow1,SCCOL nCol2,SCROW nRow2)2305 void ScConditionalFormatList::DeleteArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
2306 {
2307     for (auto& rxFormat : m_ConditionalFormats)
2308         rxFormat->DeleteArea( nCol1, nRow1, nCol2, nRow2 );
2309 
2310     CheckAllEntries();
2311 }
2312 
begin()2313 ScConditionalFormatList::iterator ScConditionalFormatList::begin()
2314 {
2315     return m_ConditionalFormats.begin();
2316 }
2317 
begin() const2318 ScConditionalFormatList::const_iterator ScConditionalFormatList::begin() const
2319 {
2320     return m_ConditionalFormats.begin();
2321 }
2322 
end()2323 ScConditionalFormatList::iterator ScConditionalFormatList::end()
2324 {
2325     return m_ConditionalFormats.end();
2326 }
2327 
end() const2328 ScConditionalFormatList::const_iterator ScConditionalFormatList::end() const
2329 {
2330     return m_ConditionalFormats.end();
2331 }
2332 
GetCombinedRange() const2333 ScRangeList ScConditionalFormatList::GetCombinedRange() const
2334 {
2335     ScRangeList aRange;
2336     for (auto& itr: m_ConditionalFormats)
2337     {
2338         const ScRangeList& rRange = itr->GetRange();
2339         for (size_t i = 0, n = rRange.size(); i < n; ++i)
2340         {
2341             aRange.Join(rRange[i]);
2342         }
2343     }
2344     return aRange;
2345 }
2346 
RemoveFromDocument(ScDocument & rDoc) const2347 void ScConditionalFormatList::RemoveFromDocument(ScDocument& rDoc) const
2348 {
2349     ScRangeList aRange = GetCombinedRange();
2350     ScMarkData aMark(rDoc.GetSheetLimits());
2351     aMark.MarkFromRangeList(aRange, true);
2352     sal_uInt16 const pItems[2] = { sal_uInt16(ATTR_CONDITIONAL),0};
2353     rDoc.ClearSelectionItems(pItems, aMark);
2354 }
2355 
AddToDocument(ScDocument & rDoc) const2356 void ScConditionalFormatList::AddToDocument(ScDocument& rDoc) const
2357 {
2358     for (auto& itr: m_ConditionalFormats)
2359     {
2360         const ScRangeList& rRange = itr->GetRange();
2361         if (rRange.empty())
2362             continue;
2363 
2364         SCTAB nTab = rRange.front().aStart.Tab();
2365         rDoc.AddCondFormatData(rRange, nTab, itr->GetKey());
2366     }
2367 }
2368 
size() const2369 size_t ScConditionalFormatList::size() const
2370 {
2371     return m_ConditionalFormats.size();
2372 }
2373 
empty() const2374 bool ScConditionalFormatList::empty() const
2375 {
2376     return m_ConditionalFormats.empty();
2377 }
2378 
erase(sal_uLong nIndex)2379 void ScConditionalFormatList::erase( sal_uLong nIndex )
2380 {
2381     auto itr = m_ConditionalFormats.find(nIndex);
2382     if (itr != end())
2383         m_ConditionalFormats.erase(itr);
2384 }
2385 
startRendering()2386 void ScConditionalFormatList::startRendering()
2387 {
2388     for (auto const& it : m_ConditionalFormats)
2389     {
2390         it->startRendering();
2391     }
2392 }
2393 
endRendering()2394 void ScConditionalFormatList::endRendering()
2395 {
2396     for (auto const& it : m_ConditionalFormats)
2397     {
2398         it->endRendering();
2399     }
2400 }
2401 
updateValues()2402 void ScConditionalFormatList::updateValues()
2403 {
2404     for (auto const& it : m_ConditionalFormats)
2405     {
2406         it->updateValues();
2407     }
2408 }
2409 
clear()2410 void ScConditionalFormatList::clear()
2411 {
2412     m_ConditionalFormats.clear();
2413 }
2414 
getMaxKey() const2415 sal_uInt32 ScConditionalFormatList::getMaxKey() const
2416 {
2417     if (m_ConditionalFormats.empty())
2418         return 0;
2419     return (*m_ConditionalFormats.rbegin())->GetKey();
2420 }
2421 
CalcAll()2422 void ScConditionalFormatList::CalcAll()
2423 {
2424     for (const auto& aEntry : m_ConditionalFormats)
2425     {
2426         aEntry->CalcAll();
2427     }
2428 
2429 }
2430 
ScCondFormatData()2431 ScCondFormatData::ScCondFormatData() {}
2432 
2433 ScCondFormatData::ScCondFormatData(ScCondFormatData&&) = default;
2434 
~ScCondFormatData()2435 ScCondFormatData::~ScCondFormatData() {}
2436 
2437 
2438 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2439