Gtk-WARNING gtktreestore.c:1047: Invalid column number 1 added to iter
[LibreOffice.git] / sc / source / core / data / documen4.cxx
blob1a2cdeeaf2f41f6e204a70c025eddc95a275300a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <svl/numformat.hxx>
21 #include <svl/zforlist.hxx>
22 #include <svl/zformat.hxx>
23 #include <formula/token.hxx>
24 #include <sal/log.hxx>
25 #include <comphelper/configuration.hxx>
26 #include <osl/diagnose.h>
27 #include <o3tl/string_view.hxx>
29 #include <document.hxx>
30 #include <docsh.hxx>
31 #include <table.hxx>
32 #include <globstr.hrc>
33 #include <scresid.hxx>
34 #include <subtotal.hxx>
35 #include <docoptio.hxx>
36 #include <markdata.hxx>
37 #include <validat.hxx>
38 #include <scitems.hxx>
39 #include <stlpool.hxx>
40 #include <poolhelp.hxx>
41 #include <detdata.hxx>
42 #include <patattr.hxx>
43 #include <chgtrack.hxx>
44 #include <progress.hxx>
45 #include <paramisc.hxx>
46 #include <compiler.hxx>
47 #include <externalrefmgr.hxx>
48 #include <attrib.hxx>
49 #include <formulacell.hxx>
50 #include <tokenarray.hxx>
51 #include <tokenstringcontext.hxx>
52 #include <memory>
54 using namespace formula;
56 /** (Goal Seek) Find a value of x that is a root of f(x)
58 This function is used internally for the goal seek operation. It uses the
59 Regula Falsi (aka false position) algorithm to find a root of f(x). The
60 start value and the target value are to be given by the user in the
61 goal seek dialog. The f(x) in this case is defined as the formula in the
62 formula cell minus target value. This function may also perform additional
63 search in the horizontal directions when the f(x) is discrete in order to
64 ensure a non-zero slope necessary for deriving a subsequent x that is
65 reasonably close to the root of interest.
67 @change 24.10.2004 by Kohei Yoshida (kohei@openoffice.org)
69 @see #i28955#
71 @change 6 Aug 2013, fdo37341
73 bool ScDocument::Solver(SCCOL nFCol, SCROW nFRow, SCTAB nFTab,
74 SCCOL nVCol, SCROW nVRow, SCTAB nVTab,
75 const OUString& sValStr, double& nX)
77 bool bRet = false;
78 nX = 0.0;
79 ScFormulaCell* pFormula = nullptr;
80 double fTargetVal = 0.0;
82 CellType eFType = GetCellType(nFCol, nFRow, nFTab);
83 // #i108005# convert target value to number using default format,
84 // as previously done in ScInterpreter::GetDouble
85 sal_uInt32 nFIndex = 0;
86 if ( eFType == CELLTYPE_FORMULA && FetchTable(nVTab) && ValidColRow(nVCol, nVRow) &&
87 GetFormatTable()->IsNumberFormat( sValStr, nFIndex, fTargetVal ) )
89 ScAddress aFormulaAdr( nFCol, nFRow, nFTab );
90 pFormula = GetFormulaCell( aFormulaAdr );
93 if (pFormula)
95 bool bDoneIteration = false;
96 const ScAddress aValueAdr(nVCol, nVRow, nVTab);
97 const ScRange aVRange(aValueAdr, aValueAdr); // for SetDirty
99 const sal_uInt16 nMaxIter = 100;
100 const double fEps = 1E-10;
101 const double fDelta = 1E-6;
103 double fXPrev = GetValue(aValueAdr);
104 double fBestX = fXPrev;
106 // Original value to be restored later if necessary
107 const ScCellValue aSaveVal(GetRefCellValue(aValueAdr));
108 const bool changeCellType = aSaveVal.getType() != CELLTYPE_VALUE;
109 if (changeCellType)
110 SetValue(aValueAdr, fXPrev);
111 double* pVCell = GetValueCell(aValueAdr);
113 pFormula->Interpret();
114 bool bError = ( pFormula->GetErrCode() != FormulaError::NONE );
115 // bError always corresponds with fF
117 double fFPrev = pFormula->GetValue() - fTargetVal;
119 double fBestF = fabs( fFPrev );
120 if ( fBestF < fDelta )
121 bDoneIteration = true;
123 double fX = fXPrev + fEps;
124 double fF = fFPrev;
125 double fSlope;
127 sal_uInt16 nIter = 0;
129 bool bHorMoveError = false;
130 // Conform Regula Falsi Method
131 while ( !bDoneIteration && ( nIter++ < nMaxIter ) )
133 *pVCell = fX;
134 SetDirty( aVRange, false );
135 pFormula->Interpret();
136 bError = ( pFormula->GetErrCode() != FormulaError::NONE );
137 fF = pFormula->GetValue() - fTargetVal;
139 if ( fF == fFPrev && !bError )
141 // HORIZONTAL SEARCH: Keep moving x in both directions until the f(x)
142 // becomes different from the previous f(x). This routine is needed
143 // when a given function is discrete, in which case the resulting slope
144 // may become zero which ultimately causes the goal seek operation
145 // to fail. #i28955#
147 sal_uInt16 nHorIter = 0;
148 const double fHorStepAngle = 5.0;
149 const double fHorMaxAngle = 80.0;
150 int const nHorMaxIter = static_cast<int>( fHorMaxAngle / fHorStepAngle );
151 bool bDoneHorMove = false;
153 while ( !bDoneHorMove && !bHorMoveError && nHorIter++ < nHorMaxIter )
155 double fHorAngle = fHorStepAngle * static_cast<double>( nHorIter );
156 double fHorTangent = std::tan(basegfx::deg2rad(fHorAngle));
158 sal_uInt16 nIdx = 0;
159 while( nIdx++ < 2 && !bDoneHorMove )
161 double fHorX;
162 if ( nIdx == 1 )
163 fHorX = fX + fabs( fF ) * fHorTangent;
164 else
165 fHorX = fX - fabs( fF ) * fHorTangent;
167 *pVCell = fHorX;
168 SetDirty( aVRange, false );
169 pFormula->Interpret();
170 bHorMoveError = ( pFormula->GetErrCode() != FormulaError::NONE );
171 if ( bHorMoveError )
172 break;
174 fF = pFormula->GetValue() - fTargetVal;
175 if ( fF != fFPrev )
177 fX = fHorX;
178 bDoneHorMove = true;
182 if ( !bDoneHorMove )
183 bHorMoveError = true;
186 if ( bError )
188 // move closer to last valid value (fXPrev), keep fXPrev & fFPrev
189 double fDiff = ( fXPrev - fX ) / 2;
190 if ( fabs( fDiff ) < fEps )
191 fDiff = ( fDiff < 0.0 ? - fEps : fEps );
192 fX += fDiff;
194 else if ( bHorMoveError )
195 break;
196 else if ( fabs(fF) < fDelta )
198 // converged to root
199 fBestX = fX;
200 bDoneIteration = true;
202 else
204 if ( fabs(fF) + fDelta < fBestF )
206 fBestX = fX;
207 fBestF = fabs( fF );
210 if ( ( fXPrev - fX ) != 0 )
212 fSlope = ( fFPrev - fF ) / ( fXPrev - fX );
213 if ( fabs( fSlope ) < fEps )
214 fSlope = fSlope < 0.0 ? -fEps : fEps;
216 else
217 fSlope = fEps;
219 fXPrev = fX;
220 fFPrev = fF;
221 fX = fX - ( fF / fSlope );
225 // Try a nice rounded input value if possible.
226 const double fNiceDelta = ( bDoneIteration && fabs( fBestX ) >= 1e-3 ? 1e-3 : fDelta );
227 nX = ::rtl::math::approxFloor( ( fBestX / fNiceDelta ) + 0.5 ) * fNiceDelta;
229 if ( bDoneIteration )
231 *pVCell = nX;
232 SetDirty( aVRange, false );
233 pFormula->Interpret();
234 if ( fabs( pFormula->GetValue() - fTargetVal ) > fabs( fF ) )
235 nX = fBestX;
236 bRet = true;
238 else if ( bError || bHorMoveError )
240 nX = fBestX;
242 if (changeCellType)
243 aSaveVal.commit(*this, aValueAdr);
244 else
245 *pVCell = aSaveVal.getDouble();
246 SetDirty( aVRange, false );
247 pFormula->Interpret();
249 return bRet;
252 void ScDocument::InsertMatrixFormula(SCCOL nCol1, SCROW nRow1,
253 SCCOL nCol2, SCROW nRow2,
254 const ScMarkData& rMark,
255 const OUString& rFormula,
256 const ScTokenArray* pArr,
257 const formula::FormulaGrammar::Grammar eGram )
259 PutInOrder(nCol1, nCol2);
260 PutInOrder(nRow1, nRow2);
261 nCol2 = std::min<SCCOL>(nCol2, MaxCol());
262 nRow2 = std::min<SCROW>(nRow2, MaxRow());
263 if (!rMark.GetSelectCount())
265 SAL_WARN("sc", "ScDocument::InsertMatrixFormula: No table marked");
266 return;
268 if (comphelper::IsFuzzing())
270 // just too slow
271 if (nCol2 - nCol1 > 64)
272 return;
273 if (nRow2 - nRow1 > 64)
274 return;
276 assert( ValidColRow( nCol1, nRow1) && ValidColRow( nCol2, nRow2));
278 SCTAB nTab1 = *rMark.begin();
280 ScFormulaCell* pCell;
281 ScAddress aPos( nCol1, nRow1, nTab1 );
282 if (pArr)
283 pCell = new ScFormulaCell(*this, aPos, *pArr, eGram, ScMatrixMode::Formula);
284 else
285 pCell = new ScFormulaCell(*this, aPos, rFormula, eGram, ScMatrixMode::Formula);
286 pCell->SetMatColsRows( nCol2 - nCol1 + 1, nRow2 - nRow1 + 1 );
287 SCTAB nMax = GetTableCount();
288 for (const auto& rTab : rMark)
290 if (rTab >= nMax)
291 break;
293 if (!maTabs[rTab])
294 continue;
296 if (rTab == nTab1)
298 pCell = maTabs[rTab]->SetFormulaCell(nCol1, nRow1, pCell);
299 if (!pCell) //NULL if nCol1/nRow1 is invalid, which it can't be here
300 break;
302 else
303 maTabs[rTab]->SetFormulaCell(
304 nCol1, nRow1,
305 new ScFormulaCell(
306 *pCell, *this, ScAddress(nCol1, nRow1, rTab), ScCloneFlags::StartListening));
309 ScSingleRefData aRefData;
310 aRefData.InitFlags();
311 aRefData.SetRelCol(0);
312 aRefData.SetRelRow(0);
313 aRefData.SetRelTab(0); // 2D matrix, always same sheet
315 ScTokenArray aArr(*this); // consists only of one single reference token.
316 formula::FormulaToken* t = aArr.AddMatrixSingleReference(aRefData);
318 for (const SCTAB& nTab : rMark)
320 if (nTab >= nMax)
321 break;
323 ScTable* pTab = FetchTable(nTab);
324 if (!pTab)
325 continue;
327 for (SCCOL nCol : GetWritableColumnsRange(nTab, nCol1, nCol2))
329 aRefData.SetRelCol(nCol1 - nCol);
330 for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
332 if (nCol == nCol1 && nRow == nRow1)
333 // Skip the base position.
334 continue;
336 // Reference in each cell must point to the origin cell relative to the current cell.
337 aRefData.SetRelRow(nRow1 - nRow);
338 *t->GetSingleRef() = aRefData;
339 // Token array must be cloned so that each formula cell receives its own copy.
340 ScTokenArray aTokArr(aArr.CloneValue());
341 aPos = ScAddress(nCol, nRow, nTab);
342 pCell = new ScFormulaCell(*this, aPos, aTokArr, eGram, ScMatrixMode::Reference);
343 pTab->SetFormulaCell(nCol, nRow, pCell);
349 void ScDocument::InsertTableOp(const ScTabOpParam& rParam, // multiple (repeated?) operation
350 SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
351 const ScMarkData& rMark)
353 PutInOrder(nCol1, nCol2);
354 PutInOrder(nRow1, nRow2);
355 assert( ValidColRow( nCol1, nRow1) && ValidColRow( nCol2, nRow2));
356 SCTAB i, nTab1;
357 SCCOL j;
358 SCROW k;
359 i = 0;
360 bool bStop = false;
361 SCTAB nMax = GetTableCount();
362 for (const auto& rTab : rMark)
364 if (rTab >= nMax)
365 break;
367 if (maTabs[rTab])
369 i = rTab;
370 bStop = true;
371 break;
374 nTab1 = i;
375 if (!bStop)
377 OSL_FAIL("ScDocument::InsertTableOp: No table marked");
378 return;
381 ScRefAddress aRef;
382 OUStringBuffer aForString("="
383 + ScCompiler::GetNativeSymbol(ocTableOp)
384 + ScCompiler::GetNativeSymbol( ocOpen));
386 const OUString& sSep = ScCompiler::GetNativeSymbol( ocSep);
387 if (rParam.meMode == ScTabOpParam::Column) // column only
389 aRef.Set( rParam.aRefFormulaCell.GetAddress(), true, false, false );
390 aForString.append(aRef.GetRefString(*this, nTab1)
391 + sSep
392 + rParam.aRefColCell.GetRefString(*this, nTab1)
393 + sSep);
394 aRef.Set( nCol1, nRow1, nTab1, false, true, true );
395 aForString.append(aRef.GetRefString(*this, nTab1));
396 nCol1++;
397 nCol2 = std::min( nCol2, static_cast<SCCOL>(rParam.aRefFormulaEnd.Col() -
398 rParam.aRefFormulaCell.Col() + nCol1 + 1));
400 else if (rParam.meMode == ScTabOpParam::Row) // row only
402 aRef.Set( rParam.aRefFormulaCell.GetAddress(), false, true, false );
403 aForString.append(aRef.GetRefString(*this, nTab1)
404 + sSep
405 + rParam.aRefRowCell.GetRefString(*this, nTab1)
406 + sSep);
407 aRef.Set( nCol1, nRow1, nTab1, true, false, true );
408 aForString.append(aRef.GetRefString(*this, nTab1));
409 nRow1++;
410 nRow2 = std::min( nRow2, static_cast<SCROW>(rParam.aRefFormulaEnd.Row() -
411 rParam.aRefFormulaCell.Row() + nRow1 + 1));
413 else // both
415 aForString.append(rParam.aRefFormulaCell.GetRefString(*this, nTab1)
416 + sSep
417 + rParam.aRefColCell.GetRefString(*this, nTab1)
418 + sSep);
419 aRef.Set( nCol1, nRow1 + 1, nTab1, false, true, true );
420 aForString.append(aRef.GetRefString(*this, nTab1)
421 + sSep
422 + rParam.aRefRowCell.GetRefString(*this, nTab1)
423 + sSep);
424 aRef.Set( nCol1 + 1, nRow1, nTab1, true, false, true );
425 aForString.append(aRef.GetRefString(*this, nTab1));
426 nCol1++; nRow1++;
428 aForString.append(ScCompiler::GetNativeSymbol( ocClose ));
430 ScFormulaCell aRefCell( *this, ScAddress( nCol1, nRow1, nTab1 ), aForString.makeStringAndClear(),
431 formula::FormulaGrammar::GRAM_NATIVE, ScMatrixMode::NONE );
432 for( j = nCol1; j <= nCol2; j++ )
434 for( k = nRow1; k <= nRow2; k++ )
436 for (const auto& rTab : rMark)
438 if (rTab >= nMax)
439 break;
440 if( maTabs[rTab] )
441 maTabs[rTab]->SetFormulaCell(
442 j, k, new ScFormulaCell(aRefCell, *this, ScAddress(j, k, rTab), ScCloneFlags::StartListening));
448 namespace {
450 bool setCacheTableReferenced(const ScDocument& rDoc, formula::FormulaToken& rToken, ScExternalRefManager& rRefMgr, const ScAddress& rPos)
452 switch (rToken.GetType())
454 case svExternalSingleRef:
455 return rRefMgr.setCacheTableReferenced(
456 rToken.GetIndex(), rToken.GetString().getString(), 1);
457 case svExternalDoubleRef:
459 const ScComplexRefData& rRef = *rToken.GetDoubleRef();
460 ScRange aAbs = rRef.toAbs(rDoc, rPos);
461 size_t nSheets = aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1;
462 return rRefMgr.setCacheTableReferenced(
463 rToken.GetIndex(), rToken.GetString().getString(), nSheets);
465 case svExternalName:
466 /* TODO: external names aren't supported yet, but would
467 * have to be marked as well, if so. Mechanism would be
468 * different. */
469 OSL_FAIL("ScDocument::MarkUsedExternalReferences: implement the svExternalName case!");
470 break;
471 default:
472 break;
474 return false;
479 bool ScDocument::MarkUsedExternalReferences( const ScTokenArray& rArr, const ScAddress& rPos )
481 if (!rArr.GetLen())
482 return false;
484 ScExternalRefManager* pRefMgr = nullptr;
485 formula::FormulaTokenArrayPlainIterator aIter( rArr );
486 bool bAllMarked = false;
487 while (!bAllMarked)
489 formula::FormulaToken* t = aIter.GetNextReferenceOrName();
490 if (!t)
491 break;
492 if (t->IsExternalRef())
494 if (!pRefMgr)
495 pRefMgr = GetExternalRefManager();
497 bAllMarked = setCacheTableReferenced(*this, *t, *pRefMgr, rPos);
499 else if (t->GetType() == svIndex)
501 // this is a named range. Check if the range contains an external
502 // reference.
503 ScRangeData* pRangeData = GetRangeName()->findByIndex(t->GetIndex());
504 if (!pRangeData)
505 continue;
507 ScTokenArray* pArray = pRangeData->GetCode();
508 formula::FormulaTokenArrayPlainIterator aArrayIter(*pArray);
509 for (t = aArrayIter.First(); t; t = aArrayIter.Next())
511 if (!t->IsExternalRef())
512 continue;
514 if (!pRefMgr)
515 pRefMgr = GetExternalRefManager();
517 bAllMarked = setCacheTableReferenced(*this, *t, *pRefMgr, rPos);
521 return bAllMarked;
524 bool ScDocument::GetNextSpellingCell(SCCOL& nCol, SCROW& nRow, SCTAB nTab,
525 bool bInSel, const ScMarkData& rMark) const
527 if (const ScTable* pTable = FetchTable(nTab))
528 return pTable->GetNextSpellingCell( nCol, nRow, bInSel, rMark );
529 return false;
532 bool ScDocument::GetNextMarkedCell( SCCOL& rCol, SCROW& rRow, SCTAB nTab,
533 const ScMarkData& rMark )
535 if (ScTable* pTable = FetchTable(nTab))
536 return pTable->GetNextMarkedCell( rCol, rRow, rMark );
537 return false;
540 void ScDocument::ReplaceStyle(const SvxSearchItem& rSearchItem,
541 SCCOL nCol, SCROW nRow, SCTAB nTab,
542 const ScMarkData& rMark)
544 if (ScTable* pTable = FetchTable(nTab))
545 pTable->ReplaceStyle(rSearchItem, nCol, nRow, rMark, true/*bIsUndoP*/);
548 void ScDocument::CompileDBFormula()
550 sc::CompileFormulaContext aCxt(*this);
551 for (auto& rxTab : maTabs)
553 if (rxTab)
554 rxTab->CompileDBFormula(aCxt);
558 void ScDocument::CompileColRowNameFormula()
560 sc::CompileFormulaContext aCxt(*this);
561 for (auto& rxTab : maTabs)
563 if (rxTab)
564 rxTab->CompileColRowNameFormula(aCxt);
568 void ScDocument::InvalidateTableArea()
570 for (auto& rxTab : maTabs)
572 if (!rxTab)
573 break;
574 rxTab->InvalidateTableArea();
575 if ( rxTab->IsScenario() )
576 rxTab->InvalidateScenarioRanges();
580 sal_Int32 ScDocument::GetMaxStringLen( SCTAB nTab, SCCOL nCol,
581 SCROW nRowStart, SCROW nRowEnd, rtl_TextEncoding eCharSet ) const
583 if (const ScTable* pTable = FetchTable(nTab))
584 return pTable->GetMaxStringLen(nCol, nRowStart, nRowEnd, eCharSet);
585 return 0;
588 sal_Int32 ScDocument::GetMaxNumberStringLen( sal_uInt16& nPrecision, SCTAB nTab,
589 SCCOL nCol, SCROW nRowStart, SCROW nRowEnd ) const
591 if (const ScTable* pTable = FetchTable(nTab))
592 return pTable->GetMaxNumberStringLen(nPrecision, nCol, nRowStart, nRowEnd);
593 return 0;
596 bool ScDocument::GetSelectionFunction( ScSubTotalFunc eFunc,
597 const ScAddress& rCursor, const ScMarkData& rMark,
598 double& rResult )
600 ScFunctionData aData(eFunc);
602 ScMarkData aMark(rMark);
603 aMark.MarkToMulti();
604 if (!aMark.IsMultiMarked() && !aMark.IsCellMarked(rCursor.Col(), rCursor.Row()))
605 aMark.SetMarkArea(ScRange(rCursor));
607 SCTAB nMax = GetTableCount();
608 ScMarkData::const_iterator itr = aMark.begin(), itrEnd = aMark.end();
610 for (; itr != itrEnd && *itr < nMax && !aData.getError(); ++itr)
611 if (maTabs[*itr])
612 maTabs[*itr]->UpdateSelectionFunction(aData, aMark);
614 rResult = aData.getResult();
615 if (aData.getError())
616 rResult = 0.0;
618 return !aData.getError();
621 double ScDocument::RoundValueAsShown( double fVal, sal_uInt32 nFormat, const ScInterpreterContext* pContext ) const
623 const SvNumberFormatter* pFormatter = pContext ? pContext->GetFormatTable() : GetFormatTable();
624 const SvNumberformat* pFormat = pFormatter->GetEntry( nFormat );
625 if (!pFormat)
626 return fVal;
627 SvNumFormatType nType = pFormat->GetMaskedType();
628 if (nType != SvNumFormatType::DATE && nType != SvNumFormatType::TIME && nType != SvNumFormatType::DATETIME )
630 // MSVC doesn't recognize all paths init nPrecision and wails about
631 // "potentially uninitialized local variable 'nPrecision' used"
632 // so init to some random sensible value preserving all decimals.
633 short nPrecision = 20;
634 bool bStdPrecision = ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0);
635 if (!bStdPrecision)
637 sal_uInt16 nIdx = pFormat->GetSubformatIndex( fVal );
638 nPrecision = static_cast<short>(pFormat->GetFormatPrecision( nIdx ));
639 switch ( nType )
641 case SvNumFormatType::PERCENT: // 0.41% == 0.0041
642 nPrecision += 2;
643 break;
644 case SvNumFormatType::SCIENTIFIC: // 1.23e-3 == 0.00123
646 short nExp = 0;
647 if ( fVal > 0.0 )
648 nExp = static_cast<short>(floor( log10( fVal ) ));
649 else if ( fVal < 0.0 )
650 nExp = static_cast<short>(floor( log10( -fVal ) ));
651 nPrecision -= nExp;
652 short nInteger = static_cast<short>(pFormat->GetFormatIntegerDigits( nIdx ));
653 if ( nInteger > 1 ) // Engineering notation
655 short nIncrement = nExp % nInteger;
656 if ( nIncrement != 0 )
658 nPrecision += nIncrement;
659 if (nExp < 0 )
660 nPrecision += nInteger;
663 break;
665 case SvNumFormatType::FRACTION: // get value of fraction representation
667 return pFormat->GetRoundFractionValue( fVal );
669 case SvNumFormatType::NUMBER:
670 case SvNumFormatType::CURRENCY:
671 { // tdf#106253 Thousands divisors for format "0,"
672 const sal_uInt16 nTD = pFormat->GetThousandDivisorPrecision( nIdx );
673 if (nTD == SvNumberFormatter::UNLIMITED_PRECISION)
674 // Format contains General keyword, handled below.
675 bStdPrecision = true;
676 else
677 nPrecision -= nTD;
678 break;
680 default: break;
683 if (bStdPrecision)
685 nPrecision = static_cast<short>(GetDocOptions().GetStdPrecision());
686 // #i115512# no rounding for automatic decimals
687 if (nPrecision == static_cast<short>(SvNumberFormatter::UNLIMITED_PRECISION))
688 return fVal;
690 double fRound = ::rtl::math::round( fVal, nPrecision );
691 if ( ::rtl::math::approxEqual( fVal, fRound ) )
692 return fVal; // rounding might introduce some error
693 else
694 return fRound;
696 else
697 return fVal;
700 // conditional formats and validation ranges
702 sal_uInt32 ScDocument::AddCondFormat( std::unique_ptr<ScConditionalFormat> pNew, SCTAB nTab )
704 if(!pNew)
705 return 0;
707 if (ScTable* pTable = FetchTable(nTab))
708 return pTable->AddCondFormat(std::move(pNew));
710 return 0;
713 sal_uInt32 ScDocument::AddValidationEntry( const ScValidationData& rNew )
715 if (rNew.IsEmpty())
716 return 0; // empty is always 0
718 if (!pValidationList)
720 ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
721 pValidationList.reset(new ScValidationDataList);
724 sal_uInt32 nMax = 0;
725 for( const auto& rxData : *pValidationList )
727 const ScValidationData* pData = rxData.get();
728 sal_uInt32 nKey = pData->GetKey();
729 if ( pData->EqualEntries( rNew ) )
730 return nKey;
731 if ( nKey > nMax )
732 nMax = nKey;
735 // might be called from ScPatternAttr::MigrateToDocument; thus clone (real copy)
736 sal_uInt32 nNewKey = nMax + 1;
737 std::unique_ptr<ScValidationData> pInsert(rNew.Clone(this));
738 pInsert->SetKey( nNewKey );
739 ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
740 pValidationList->InsertNew( std::move(pInsert) );
741 return nNewKey;
744 const SfxPoolItem* ScDocument::GetEffItem(
745 SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich ) const
747 const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
748 if ( pPattern )
750 const SfxItemSet& rSet = pPattern->GetItemSet();
751 const ScCondFormatItem* pConditionalItem = nullptr;
752 if ( rSet.GetItemState( ATTR_CONDITIONAL, true, &pConditionalItem ) == SfxItemState::SET )
754 const ScCondFormatIndexes& rIndex = pConditionalItem->GetCondFormatData();
755 ScConditionalFormatList* pCondFormList = GetCondFormList( nTab );
756 if (!rIndex.empty() && pCondFormList)
758 for(const auto& rItem : rIndex)
760 const ScConditionalFormat* pForm = pCondFormList->GetFormat( rItem );
761 if ( pForm )
763 ScAddress aPos(nCol, nRow, nTab);
764 ScRefCellValue aCell(const_cast<ScDocument&>(*this), aPos);
765 const OUString aStyle = pForm->GetCellStyle(aCell, aPos);
766 if (!aStyle.isEmpty())
768 SfxStyleSheetBase* pStyleSheet = mxPoolHelper->GetStylePool()->Find(
769 aStyle, SfxStyleFamily::Para );
770 const SfxPoolItem* pItem = nullptr;
771 if ( pStyleSheet && pStyleSheet->GetItemSet().GetItemState(
772 nWhich, true, &pItem ) == SfxItemState::SET )
773 return pItem;
779 return &rSet.Get( nWhich );
781 OSL_FAIL("no pattern");
782 return nullptr;
785 const SfxItemSet* ScDocument::GetCondResult( SCCOL nCol, SCROW nRow, SCTAB nTab, ScRefCellValue* pCell ) const
787 ScConditionalFormatList* pFormatList = GetCondFormList(nTab);
788 if (!pFormatList)
789 return nullptr;
791 ScAddress aPos(nCol, nRow, nTab);
792 ScRefCellValue aCell;
793 if( pCell == nullptr )
795 aCell.assign(const_cast<ScDocument&>(*this), aPos);
796 pCell = &aCell;
798 const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
799 const ScCondFormatIndexes& rIndex =
800 pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData();
802 return GetCondResult(*pCell, aPos, *pFormatList, rIndex);
805 const SfxItemSet* ScDocument::GetCondResult(
806 ScRefCellValue& rCell, const ScAddress& rPos, const ScConditionalFormatList& rList,
807 const ScCondFormatIndexes& rIndex ) const
809 for (const auto& rItem : rIndex)
811 const ScConditionalFormat* pForm = rList.GetFormat(rItem);
812 if (!pForm)
813 continue;
815 const OUString aStyle = pForm->GetCellStyle(rCell, rPos);
816 if (!aStyle.isEmpty())
818 SfxStyleSheetBase* pStyleSheet =
819 mxPoolHelper->GetStylePool()->Find(aStyle, SfxStyleFamily::Para);
821 if (pStyleSheet)
822 return &pStyleSheet->GetItemSet();
824 // if style is not there, treat like no condition
828 return nullptr;
831 ScConditionalFormat* ScDocument::GetCondFormat(
832 SCCOL nCol, SCROW nRow, SCTAB nTab ) const
834 sal_uInt32 nIndex = 0;
835 const ScCondFormatIndexes& rCondFormats = GetAttr(nCol, nRow, nTab, ATTR_CONDITIONAL)->GetCondFormatData();
837 if(!rCondFormats.empty())
838 nIndex = rCondFormats[0];
840 if (nIndex)
842 ScConditionalFormatList* pCondFormList = GetCondFormList(nTab);
843 if (pCondFormList)
844 return pCondFormList->GetFormat( nIndex );
845 else
847 OSL_FAIL("pCondFormList is 0");
851 return nullptr;
854 ScConditionalFormatList* ScDocument::GetCondFormList(SCTAB nTab) const
856 if (HasTable(nTab))
857 return maTabs[nTab]->GetCondFormList();
858 return nullptr;
861 void ScDocument::SetCondFormList( ScConditionalFormatList* pList, SCTAB nTab )
863 if (ScTable* pTable = FetchTable(nTab))
864 pTable->SetCondFormList(pList);
867 const ScValidationData* ScDocument::GetValidationEntry( sal_uInt32 nIndex ) const
869 if ( pValidationList )
870 return pValidationList->GetData( nIndex );
871 else
872 return nullptr;
875 void ScDocument::DeleteConditionalFormat(sal_uLong nOldIndex, SCTAB nTab)
877 if (ScTable* pTable = FetchTable(nTab))
878 pTable->DeleteConditionalFormat(nOldIndex);
881 bool ScDocument::HasDetectiveOperations() const
883 return pDetOpList && pDetOpList->Count();
886 void ScDocument::AddDetectiveOperation( const ScDetOpData& rData )
888 if (!pDetOpList)
889 pDetOpList.reset(new ScDetOpList);
891 pDetOpList->Append( rData );
894 void ScDocument::ClearDetectiveOperations()
896 pDetOpList.reset(); // deletes also the entries
899 void ScDocument::SetDetOpList(std::unique_ptr<ScDetOpList> pNew)
901 pDetOpList = std::move(pNew);
904 // Comparison of Documents
906 // Pfriemel-Factors
907 #define SC_DOCCOMP_MAXDIFF 256
908 #define SC_DOCCOMP_MINGOOD 128
909 #define SC_DOCCOMP_COLUMNS 10
910 #define SC_DOCCOMP_ROWS 100
912 sal_uInt16 ScDocument::RowDifferences( SCROW nThisRow, SCTAB nThisTab,
913 ScDocument& rOtherDoc, SCROW nOtherRow, SCTAB nOtherTab,
914 SCCOL nMaxCol, const SCCOLROW* pOtherCols )
916 sal_uLong nDif = 0;
917 sal_uLong nUsed = 0;
918 for (SCCOL nThisCol=0; nThisCol<=nMaxCol; nThisCol++)
920 SCCOL nOtherCol;
921 if ( pOtherCols )
922 nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
923 else
924 nOtherCol = nThisCol;
926 if (ValidCol(nOtherCol)) // only compare columns that are common to both docs
928 ScRefCellValue aThisCell(*this, ScAddress(nThisCol, nThisRow, nThisTab));
929 ScRefCellValue aOtherCell(rOtherDoc, ScAddress(nOtherCol, nOtherRow, nOtherTab));
930 if (!aThisCell.equalsWithoutFormat(aOtherCell))
932 if (!aThisCell.isEmpty() && !aOtherCell.isEmpty())
933 nDif += 3;
934 else
935 nDif += 4; // content <-> empty counts more
938 if (!aThisCell.isEmpty() || !aOtherCell.isEmpty())
939 ++nUsed;
943 if (nUsed > 0)
944 return static_cast<sal_uInt16>((nDif*64)/nUsed); // max.256 (SC_DOCCOMP_MAXDIFF)
946 OSL_ENSURE(!nDif,"Diff without Used");
947 return 0;
950 sal_uInt16 ScDocument::ColDifferences( SCCOL nThisCol, SCTAB nThisTab,
951 ScDocument& rOtherDoc, SCCOL nOtherCol, SCTAB nOtherTab,
952 SCROW nMaxRow, const SCCOLROW* pOtherRows )
955 //TODO: optimize e.g. with iterator?
957 sal_uInt64 nDif = 0;
958 sal_uInt64 nUsed = 0;
959 for (SCROW nThisRow=0; nThisRow<=nMaxRow; nThisRow++)
961 SCROW nOtherRow;
962 if ( pOtherRows )
963 nOtherRow = pOtherRows[nThisRow];
964 else
965 nOtherRow = nThisRow;
967 if (ValidRow(nOtherRow)) // only compare rows that are common to both docs
969 ScRefCellValue aThisCell(*this, ScAddress(nThisCol, nThisRow, nThisTab));
970 ScRefCellValue aOtherCell(rOtherDoc, ScAddress(nOtherCol, nOtherRow, nOtherTab));
971 if (!aThisCell.equalsWithoutFormat(aOtherCell))
973 if (!aThisCell.isEmpty() && !aOtherCell.isEmpty())
974 nDif += 3;
975 else
976 nDif += 4; // content <-> empty counts more
979 if (!aThisCell.isEmpty() || !aOtherCell.isEmpty())
980 ++nUsed;
984 if (nUsed > 0)
985 return static_cast<sal_uInt16>((nDif*64)/nUsed); // max.256
987 OSL_ENSURE(!nDif,"Diff without Used");
988 return 0;
991 void ScDocument::FindOrder( SCCOLROW* pOtherRows, SCCOLROW nThisEndRow, SCCOLROW nOtherEndRow,
992 bool bColumns, ScDocument& rOtherDoc, SCTAB nThisTab, SCTAB nOtherTab,
993 SCCOLROW nEndCol, const SCCOLROW* pTranslate, ScProgress* pProgress, sal_uInt64 nProAdd )
995 // bColumns=true: rows are columns and vice versa
997 SCCOLROW nMaxCont; // continue by how much
998 SCCOLROW nMinGood; // what is a hit (incl.)
999 if ( bColumns )
1001 nMaxCont = SC_DOCCOMP_COLUMNS; // 10 columns
1002 nMinGood = SC_DOCCOMP_MINGOOD;
1004 //TODO: additional pass with nMinGood = 0 ????
1007 else
1009 nMaxCont = SC_DOCCOMP_ROWS; // 100 rows
1010 nMinGood = SC_DOCCOMP_MINGOOD;
1012 bool bUseTotal = bColumns && !pTranslate; // only for the 1st pass
1014 SCCOLROW nOtherRow = 0;
1015 sal_uInt16 nComp;
1016 SCCOLROW nThisRow;
1017 bool bTotal = false; // hold for several nThisRow
1018 SCCOLROW nUnknown = 0;
1019 for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++)
1021 SCCOLROW nTempOther = nOtherRow;
1022 bool bFound = false;
1023 sal_uInt16 nBest = SC_DOCCOMP_MAXDIFF;
1024 SCCOLROW nMax = std::min( nOtherEndRow, static_cast<SCCOLROW>(( nTempOther + nMaxCont + nUnknown )) );
1025 for (SCCOLROW i=nTempOther; i<=nMax && nBest>0; i++) // stop at 0
1027 if (bColumns)
1028 nComp = ColDifferences( static_cast<SCCOL>(nThisRow), nThisTab, rOtherDoc, static_cast<SCCOL>(i), nOtherTab, nEndCol, pTranslate );
1029 else
1030 nComp = RowDifferences( nThisRow, nThisTab, rOtherDoc, i, nOtherTab, static_cast<SCCOL>(nEndCol), pTranslate );
1031 if ( nComp < nBest && ( nComp <= nMinGood || bTotal ) )
1033 nTempOther = i;
1034 nBest = nComp;
1035 bFound = true;
1037 if ( nComp < SC_DOCCOMP_MAXDIFF || bFound )
1038 bTotal = false;
1039 else if ( i == nTempOther && bUseTotal )
1040 bTotal = true; // only at the very top
1042 if ( bFound )
1044 pOtherRows[nThisRow] = nTempOther;
1045 nOtherRow = nTempOther + 1;
1046 nUnknown = 0;
1048 else
1050 pOtherRows[nThisRow] = SCROW_MAX;
1051 ++nUnknown;
1054 if (pProgress)
1055 pProgress->SetStateOnPercent(nProAdd+static_cast<sal_uLong>(nThisRow));
1058 // fill in blocks that don't match
1060 SCROW nFillStart = 0;
1061 SCROW nFillPos = 0;
1062 bool bInFill = false;
1063 for (nThisRow = 0; nThisRow <= nThisEndRow+1; nThisRow++)
1065 SCROW nThisOther = ( nThisRow <= nThisEndRow ) ? pOtherRows[nThisRow] : (nOtherEndRow+1);
1066 if ( ValidRow(nThisOther) )
1068 if ( bInFill )
1070 if ( nThisOther > nFillStart ) // is there something to distribute?
1072 SCROW nDiff1 = nThisOther - nFillStart;
1073 SCROW nDiff2 = nThisRow - nFillPos;
1074 SCROW nMinDiff = std::min(nDiff1, nDiff2);
1075 for (SCROW i=0; i<nMinDiff; i++)
1076 pOtherRows[nFillPos+i] = nFillStart+i;
1079 bInFill = false;
1081 nFillStart = nThisOther + 1;
1082 nFillPos = nThisRow + 1;
1084 else
1085 bInFill = true;
1089 void ScDocument::CompareDocument( ScDocument& rOtherDoc )
1091 if (!pChangeTrack)
1092 return;
1094 SCTAB nThisCount = GetTableCount();
1095 SCTAB nOtherCount = rOtherDoc.GetTableCount();
1096 std::unique_ptr<SCTAB[]> pOtherTabs(new SCTAB[nThisCount]);
1097 SCTAB nThisTab;
1099 // compare tables with identical names
1100 OUString aThisName;
1101 OUString aOtherName;
1102 for (nThisTab=0; nThisTab<nThisCount; nThisTab++)
1104 SCTAB nOtherTab = SCTAB_MAX;
1105 if (!IsScenario(nThisTab)) // skip scenarios
1107 GetName( nThisTab, aThisName );
1108 for (SCTAB nTemp=0; nTemp<nOtherCount && nOtherTab>MAXTAB; nTemp++)
1109 if (!rOtherDoc.IsScenario(nTemp))
1111 rOtherDoc.GetName( nTemp, aOtherName );
1112 if ( aThisName == aOtherName )
1113 nOtherTab = nTemp;
1116 pOtherTabs[nThisTab] = nOtherTab;
1118 // fill in, so that un-named tables don't get lost
1119 SCTAB nFillStart = 0;
1120 SCTAB nFillPos = 0;
1121 bool bInFill = false;
1122 for (nThisTab = 0; nThisTab <= nThisCount; nThisTab++)
1124 SCTAB nThisOther = ( nThisTab < nThisCount ) ? pOtherTabs[nThisTab] : nOtherCount;
1125 if ( ValidTab(nThisOther) )
1127 if ( bInFill )
1129 if ( nThisOther > nFillStart ) // is there something to distribute?
1131 SCTAB nDiff1 = nThisOther - nFillStart;
1132 SCTAB nDiff2 = nThisTab - nFillPos;
1133 SCTAB nMinDiff = std::min(nDiff1, nDiff2);
1134 for (SCTAB i=0; i<nMinDiff; i++)
1135 if ( !IsScenario(nFillPos+i) && !rOtherDoc.IsScenario(nFillStart+i) )
1136 pOtherTabs[nFillPos+i] = nFillStart+i;
1139 bInFill = false;
1141 nFillStart = nThisOther + 1;
1142 nFillPos = nThisTab + 1;
1144 else
1145 bInFill = true;
1148 // compare tables in the original order
1150 for (nThisTab=0; nThisTab<nThisCount; nThisTab++)
1152 SCTAB nOtherTab = pOtherTabs[nThisTab];
1153 if ( ValidTab(nOtherTab) )
1155 SCCOL nThisEndCol = 0;
1156 SCROW nThisEndRow = 0;
1157 SCCOL nOtherEndCol = 0;
1158 SCROW nOtherEndRow = 0;
1159 GetCellArea( nThisTab, nThisEndCol, nThisEndRow );
1160 rOtherDoc.GetCellArea( nOtherTab, nOtherEndCol, nOtherEndRow );
1161 SCCOL nEndCol = std::max(nThisEndCol, nOtherEndCol);
1162 SCROW nEndRow = std::max(nThisEndRow, nOtherEndRow);
1163 SCCOL nThisCol;
1164 SCROW nThisRow;
1165 sal_uLong n1,n2; // for AppendDeleteRange
1167 //TODO: one Progress over all tables ???
1169 OUString aTabName;
1170 GetName( nThisTab, aTabName );
1171 OUString aTemplate = ScResId(STR_PROGRESS_COMPARING);
1172 sal_Int32 nIndex = 0;
1173 OUString aProText = o3tl::getToken(aTemplate, 0, '#', nIndex ) +
1174 aTabName +
1175 o3tl::getToken(aTemplate, 0, '#', nIndex );
1176 ScProgress aProgress( GetDocumentShell(), aProText, 3*nThisEndRow, true ); // 2x FindOrder, 1x here
1177 tools::Long nProgressStart = 2*nThisEndRow; // start for here
1179 std::unique_ptr<SCCOLROW[]> pTempRows(new SCCOLROW[nThisEndRow+1]);
1180 std::unique_ptr<SCCOLROW[]> pOtherRows(new SCCOLROW[nThisEndRow+1]);
1181 std::unique_ptr<SCCOLROW[]> pOtherCols(new SCCOLROW[nThisEndCol+1]);
1183 // find inserted/deleted columns/rows:
1184 // Two attempts:
1185 // 1) compare original rows (pTempRows)
1186 // 2) compare original columns (pOtherCols)
1187 // with this column order compare rows (pOtherRows)
1189 //TODO: compare columns twice with different nMinGood ???
1191 // 1
1192 FindOrder( pTempRows.get(), nThisEndRow, nOtherEndRow, false,
1193 rOtherDoc, nThisTab, nOtherTab, nEndCol, nullptr, &aProgress, 0 );
1194 // 2
1195 FindOrder( pOtherCols.get(), nThisEndCol, nOtherEndCol, true,
1196 rOtherDoc, nThisTab, nOtherTab, nEndRow, nullptr, nullptr, 0 );
1197 FindOrder( pOtherRows.get(), nThisEndRow, nOtherEndRow, false,
1198 rOtherDoc, nThisTab, nOtherTab, nThisEndCol,
1199 pOtherCols.get(), &aProgress, nThisEndRow );
1201 sal_uLong nMatch1 = 0; // pTempRows, no columns
1202 for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++)
1203 if (ValidRow(pTempRows[nThisRow]))
1204 nMatch1 += SC_DOCCOMP_MAXDIFF -
1205 RowDifferences( nThisRow, nThisTab, rOtherDoc, pTempRows[nThisRow],
1206 nOtherTab, nEndCol, nullptr );
1208 sal_uLong nMatch2 = 0; // pOtherRows, pOtherCols
1209 for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++)
1210 if (ValidRow(pOtherRows[nThisRow]))
1211 nMatch2 += SC_DOCCOMP_MAXDIFF -
1212 RowDifferences( nThisRow, nThisTab, rOtherDoc, pOtherRows[nThisRow],
1213 nOtherTab, nThisEndCol, pOtherCols.get() );
1215 if ( nMatch1 >= nMatch2 ) // without columns ?
1217 // reset columns
1218 for (nThisCol = 0; nThisCol<=nThisEndCol; nThisCol++)
1219 pOtherCols[nThisCol] = nThisCol;
1221 // swap row-arrays (they get both deleted anyway)
1222 pTempRows.swap(pOtherRows);
1224 else
1226 // remains for pOtherCols, pOtherRows
1229 // Generate Change-Actions
1230 // 1) columns from the right
1231 // 2) rows from below
1232 // 3) single cells in normal order
1234 // Actions for inserted/deleted columns
1236 SCCOL nLastOtherCol = static_cast<SCCOL>(nOtherEndCol + 1);
1237 // nThisEndCol ... 0
1238 for ( nThisCol = nThisEndCol+1; nThisCol > 0; )
1240 --nThisCol;
1241 SCCOL nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
1242 if ( ValidCol(nOtherCol) && nOtherCol+1 < nLastOtherCol )
1244 // gap -> deleted
1245 ScRange aDelRange( nOtherCol+1, 0, nOtherTab,
1246 nLastOtherCol-1, MaxRow(), nOtherTab );
1247 pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
1249 if ( nOtherCol > MaxCol() ) // inserted
1251 // combine
1252 if ( nThisCol == nThisEndCol || ValidCol(static_cast<SCCOL>(pOtherCols[nThisCol+1])) )
1254 SCCOL nFirstNew = nThisCol;
1255 while ( nFirstNew > 0 && pOtherCols[nFirstNew-1] > MaxCol() )
1256 --nFirstNew;
1257 SCCOL nDiff = nThisCol - nFirstNew;
1258 ScRange aRange( nLastOtherCol, 0, nOtherTab,
1259 nLastOtherCol+nDiff, MaxRow(), nOtherTab );
1260 pChangeTrack->AppendInsert( aRange );
1263 else
1264 nLastOtherCol = nOtherCol;
1266 if ( nLastOtherCol > 0 ) // deleted at the very top
1268 ScRange aDelRange( 0, 0, nOtherTab,
1269 nLastOtherCol-1, MaxRow(), nOtherTab );
1270 pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
1273 // Actions for inserted/deleted rows
1275 SCROW nLastOtherRow = nOtherEndRow + 1;
1276 // nThisEndRow ... 0
1277 for ( nThisRow = nThisEndRow+1; nThisRow > 0; )
1279 --nThisRow;
1280 SCROW nOtherRow = pOtherRows[nThisRow];
1281 if ( ValidRow(nOtherRow) && nOtherRow+1 < nLastOtherRow )
1283 // gap -> deleted
1284 ScRange aDelRange( 0, nOtherRow+1, nOtherTab,
1285 MaxCol(), nLastOtherRow-1, nOtherTab );
1286 pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
1288 if ( nOtherRow > MaxRow() ) // inserted
1290 // combine
1291 if ( nThisRow == nThisEndRow || ValidRow(pOtherRows[nThisRow+1]) )
1293 SCROW nFirstNew = nThisRow;
1294 while ( nFirstNew > 0 && pOtherRows[nFirstNew-1] > MaxRow() )
1295 --nFirstNew;
1296 SCROW nDiff = nThisRow - nFirstNew;
1297 ScRange aRange( 0, nLastOtherRow, nOtherTab,
1298 MaxCol(), nLastOtherRow+nDiff, nOtherTab );
1299 pChangeTrack->AppendInsert( aRange );
1302 else
1303 nLastOtherRow = nOtherRow;
1305 if ( nLastOtherRow > 0 ) // deleted at the very top
1307 ScRange aDelRange( 0, 0, nOtherTab,
1308 MaxCol(), nLastOtherRow-1, nOtherTab );
1309 pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
1312 // walk rows to find single cells
1314 for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++)
1316 SCROW nOtherRow = pOtherRows[nThisRow];
1317 for (nThisCol = 0; nThisCol <= nThisEndCol; nThisCol++)
1319 SCCOL nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
1320 ScAddress aThisPos( nThisCol, nThisRow, nThisTab );
1321 ScCellValue aThisCell;
1322 aThisCell.assign(*this, aThisPos);
1323 ScCellValue aOtherCell; // start empty
1324 if ( ValidCol(nOtherCol) && ValidRow(nOtherRow) )
1326 ScAddress aOtherPos( nOtherCol, nOtherRow, nOtherTab );
1327 aOtherCell.assign(rOtherDoc, aOtherPos);
1330 if (!aThisCell.equalsWithoutFormat(aOtherCell))
1332 ScRange aRange( aThisPos );
1333 ScChangeActionContent* pAction = new ScChangeActionContent( aRange );
1334 pAction->SetOldValue(aOtherCell, &rOtherDoc, this);
1335 pAction->SetNewValue(aThisCell, this);
1336 pChangeTrack->Append( pAction );
1339 aProgress.SetStateOnPercent(nProgressStart+nThisRow);
1345 sal_Unicode ScDocument::GetSheetSeparator() const
1347 const ScCompiler::Convention* pConv = ScCompiler::GetRefConvention(
1348 FormulaGrammar::extractRefConvention( GetGrammar()));
1349 assert(pConv);
1350 return pConv ? pConv->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR) : '.';
1353 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */