blob: c90f9b2cea9444946dd042b8a132fd75a78feeb0 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sc.hxx"
// INCLUDE ---------------------------------------------------------------
#include <svl/intitem.hxx>
#include <svl/zforlist.hxx>
#include <vcl/sound.hxx>
#include <formula/token.hxx>
#include "document.hxx"
#include "table.hxx"
#include "globstr.hrc"
#include "subtotal.hxx"
#include "docoptio.hxx"
#include "interpre.hxx"
#include "markdata.hxx"
#include "validat.hxx"
#include "scitems.hxx"
#include "stlpool.hxx"
#include "poolhelp.hxx"
#include "detdata.hxx"
#include "patattr.hxx"
#include "chgtrack.hxx"
#include "progress.hxx"
#include "paramisc.hxx"
#include "compiler.hxx"
#include "externalrefmgr.hxx"
using namespace formula;
// -----------------------------------------------------------------------
// Nach der Regula Falsi Methode
sal_Bool ScDocument::Solver(SCCOL nFCol, SCROW nFRow, SCTAB nFTab,
SCCOL nVCol, SCROW nVRow, SCTAB nVTab,
const String& sValStr, double& nX)
{
sal_Bool bRet = sal_False;
nX = 0.0;
if (ValidColRow(nFCol, nFRow) && ValidColRow(nVCol, nVRow) &&
VALIDTAB(nFTab) && VALIDTAB(nVTab) && pTab[nFTab] && pTab[nVTab])
{
CellType eFType, eVType;
GetCellType(nFCol, nFRow, nFTab, eFType);
GetCellType(nVCol, nVRow, nVTab, eVType);
// CELLTYPE_NOTE: no value, but referenced by formula
// #i108005# convert target value to number using default format,
// as previously done in ScInterpreter::GetDouble
double nTargetVal = 0.0;
sal_uInt32 nFIndex = 0;
if (eFType == CELLTYPE_FORMULA && (eVType == CELLTYPE_VALUE || eVType == CELLTYPE_NOTE) &&
GetFormatTable()->IsNumberFormat(sValStr, nFIndex, nTargetVal))
{
ScSingleRefData aRefData;
aRefData.InitFlags();
aRefData.nCol = nVCol;
aRefData.nRow = nVRow;
aRefData.nTab = nVTab;
ScTokenArray aArr;
aArr.AddOpCode( ocBackSolver );
aArr.AddOpCode( ocOpen );
aArr.AddSingleReference( aRefData );
aArr.AddOpCode( ocSep );
aRefData.nCol = nFCol;
aRefData.nRow = nFRow;
aRefData.nTab = nFTab;
aArr.AddSingleReference( aRefData );
aArr.AddOpCode( ocSep );
aArr.AddDouble( nTargetVal );
aArr.AddOpCode( ocClose );
aArr.AddOpCode( ocStop );
ScFormulaCell* pCell = new ScFormulaCell( this, ScAddress(), &aArr );
if (pCell)
{
// FIXME FIXME FIXME this might need to be reworked now that we have formula::FormulaErrorToken and ScFormulaResult, double check !!!
DBG_ERRORFILE("ScDocument::Solver: -> ScFormulaCell::GetValueAlways might need reimplementation");
pCell->Interpret();
sal_uInt16 nErrCode = pCell->GetErrCode();
nX = pCell->GetValueAlways();
if (nErrCode == 0) // kein fehler beim Rechnen
bRet = sal_True;
delete pCell;
}
}
}
return bRet;
}
void ScDocument::InsertMatrixFormula(SCCOL nCol1, SCROW nRow1,
SCCOL nCol2, SCROW nRow2,
const ScMarkData& rMark,
const String& rFormula,
const ScTokenArray* pArr,
const formula::FormulaGrammar::Grammar eGram )
{
PutInOrder(nCol1, nCol2);
PutInOrder(nRow1, nRow2);
SCTAB i, nTab1;
SCCOL j;
SCROW k;
i = 0;
sal_Bool bStop = sal_False;
while (i <= MAXTAB && !bStop) // erste markierte Tabelle finden
{
if (pTab[i] && rMark.GetTableSelect(i))
bStop = sal_True;
else
i++;
}
nTab1 = i;
if (i == MAXTAB + 1)
{
Sound::Beep();
DBG_ERROR("ScDocument::InsertMatrixFormula Keine Tabelle markiert");
return;
}
ScFormulaCell* pCell;
ScAddress aPos( nCol1, nRow1, nTab1 );
if (pArr)
pCell = new ScFormulaCell( this, aPos, pArr, eGram, MM_FORMULA );
else
pCell = new ScFormulaCell( this, aPos, rFormula, eGram, MM_FORMULA );
pCell->SetMatColsRows( nCol2 - nCol1 + 1, nRow2 - nRow1 + 1 );
for (i = 0; i <= MAXTAB; i++)
{
if (pTab[i] && rMark.GetTableSelect(i))
{
if (i == nTab1)
pTab[i]->PutCell(nCol1, nRow1, pCell);
else
pTab[i]->PutCell(nCol1, nRow1, pCell->CloneWithoutNote(*this, ScAddress( nCol1, nRow1, i), SC_CLONECELL_STARTLISTENING));
}
}
ScSingleRefData aRefData;
aRefData.InitFlags();
aRefData.nCol = nCol1;
aRefData.nRow = nRow1;
aRefData.nTab = nTab1;
aRefData.SetColRel( sal_True );
aRefData.SetRowRel( sal_True );
aRefData.SetTabRel( sal_True );
aRefData.CalcRelFromAbs( ScAddress( nCol1, nRow1, nTab1 ) );
ScTokenArray aArr;
ScToken* t = static_cast<ScToken*>(aArr.AddMatrixSingleReference( aRefData));
for (i = 0; i <= MAXTAB; i++)
{
if (pTab[i] && rMark.GetTableSelect(i))
{
pTab[i]->DoColResize( nCol1, nCol2, static_cast<SCSIZE>(nRow2 - nRow1 + 1) );
if (i != nTab1)
{
aRefData.nTab = i;
aRefData.nRelTab = i - nTab1;
t->GetSingleRef() = aRefData;
}
for (j = nCol1; j <= nCol2; j++)
{
for (k = nRow1; k <= nRow2; k++)
{
if (j != nCol1 || k != nRow1) // nicht in der ersten Zelle
{
// Array muss geklont werden, damit jede
// Zelle ein eigenes Array erhaelt!
aPos = ScAddress( j, k, i );
t->CalcRelFromAbs( aPos );
pCell = new ScFormulaCell( this, aPos, aArr.Clone(), eGram, MM_REFERENCE );
pTab[i]->PutCell(j, k, (ScBaseCell*) pCell);
}
}
}
}
}
}
void ScDocument::InsertTableOp(const ScTabOpParam& rParam, // Mehrfachoperation
SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
const ScMarkData& rMark)
{
PutInOrder(nCol1, nCol2);
PutInOrder(nRow1, nRow2);
SCTAB i, nTab1;
SCCOL j;
SCROW k;
i = 0;
sal_Bool bStop = sal_False;
while (i <= MAXTAB && !bStop) // erste markierte Tabelle finden
{
if (pTab[i] && rMark.GetTableSelect(i))
bStop = sal_True;
else
i++;
}
nTab1 = i;
if (i == MAXTAB + 1)
{
Sound::Beep();
DBG_ERROR("ScDocument::InsertTableOp: Keine Tabelle markiert");
return;
}
ScRefAddress aRef;
String aForString = '=';
aForString += ScCompiler::GetNativeSymbol(ocTableOp);
aForString += ScCompiler::GetNativeSymbol( ocOpen);
const String& sSep = ScCompiler::GetNativeSymbol( ocSep);
if (rParam.nMode == 0) // nur Spalte
{
aRef.Set( rParam.aRefFormulaCell.GetAddress(), sal_True, sal_False, sal_False );
aForString += aRef.GetRefString(this, nTab1);
aForString += sSep;
aForString += rParam.aRefColCell.GetRefString(this, nTab1);
aForString += sSep;
aRef.Set( nCol1, nRow1, nTab1, sal_False, sal_True, sal_True );
aForString += aRef.GetRefString(this, nTab1);
nCol1++;
nCol2 = Min( nCol2, (SCCOL)(rParam.aRefFormulaEnd.Col() -
rParam.aRefFormulaCell.Col() + nCol1 + 1));
}
else if (rParam.nMode == 1) // nur zeilenweise
{
aRef.Set( rParam.aRefFormulaCell.GetAddress(), sal_False, sal_True, sal_False );
aForString += aRef.GetRefString(this, nTab1);
aForString += sSep;
aForString += rParam.aRefRowCell.GetRefString(this, nTab1);
aForString += sSep;
aRef.Set( nCol1, nRow1, nTab1, sal_True, sal_False, sal_True );
aForString += aRef.GetRefString(this, nTab1);
nRow1++;
nRow2 = Min( nRow2, (SCROW)(rParam.aRefFormulaEnd.Row() -
rParam.aRefFormulaCell.Row() + nRow1 + 1));
}
else // beides
{
aForString += rParam.aRefFormulaCell.GetRefString(this, nTab1);
aForString += sSep;
aForString += rParam.aRefColCell.GetRefString(this, nTab1);
aForString += sSep;
aRef.Set( nCol1, nRow1 + 1, nTab1, sal_False, sal_True, sal_True );
aForString += aRef.GetRefString(this, nTab1);
aForString += sSep;
aForString += rParam.aRefRowCell.GetRefString(this, nTab1);
aForString += sSep;
aRef.Set( nCol1 + 1, nRow1, nTab1, sal_True, sal_False, sal_True );
aForString += aRef.GetRefString(this, nTab1);
nCol1++; nRow1++;
}
aForString += ScCompiler::GetNativeSymbol( ocClose);
ScFormulaCell aRefCell( this, ScAddress( nCol1, nRow1, nTab1 ), aForString,
formula::FormulaGrammar::GRAM_NATIVE, MM_NONE );
for( j = nCol1; j <= nCol2; j++ )
for( k = nRow1; k <= nRow2; k++ )
for (i = 0; i <= MAXTAB; i++)
if( pTab[i] && rMark.GetTableSelect(i) )
pTab[i]->PutCell( j, k, aRefCell.CloneWithoutNote( *this, ScAddress( j, k, i ), SC_CLONECELL_STARTLISTENING ) );
}
bool ScDocument::MarkUsedExternalReferences( ScTokenArray & rArr )
{
bool bAllMarked = false;
if (rArr.GetLen())
{
ScExternalRefManager* pRefMgr = NULL;
rArr.Reset();
ScToken* t;
while (!bAllMarked && (t = static_cast<ScToken*>(rArr.GetNextReferenceOrName())) != NULL)
{
if (t->GetOpCode() == ocExternalRef)
{
if (!pRefMgr)
pRefMgr = GetExternalRefManager();
switch (t->GetType())
{
case svExternalSingleRef:
bAllMarked = pRefMgr->setCacheTableReferenced(
t->GetIndex(), t->GetString(), 1);
break;
case svExternalDoubleRef:
{
const ScComplexRefData& rRef = t->GetDoubleRef();
size_t nSheets = rRef.Ref2.nTab - rRef.Ref1.nTab + 1;
bAllMarked = pRefMgr->setCacheTableReferenced(
t->GetIndex(), t->GetString(), nSheets);
}
break;
case svExternalName:
/* TODO: external names aren't supported yet, but would
* have to be marked as well, if so. Mechanism would be
* different. */
DBG_ERRORFILE("ScDocument::MarkUsedExternalReferences: implement the svExternalName case!");
break;
default: break;
}
}
}
}
return bAllMarked;
}
sal_Bool ScDocument::GetNextSpellingCell(SCCOL& nCol, SCROW& nRow, SCTAB nTab,
sal_Bool bInSel, const ScMarkData& rMark) const
{
if (ValidTab(nTab) && pTab[nTab])
return pTab[nTab]->GetNextSpellingCell( nCol, nRow, bInSel, rMark );
else
return sal_False;
}
sal_Bool ScDocument::GetNextMarkedCell( SCCOL& rCol, SCROW& rRow, SCTAB nTab,
const ScMarkData& rMark )
{
if (ValidTab(nTab) && pTab[nTab])
return pTab[nTab]->GetNextMarkedCell( rCol, rRow, rMark );
else
return sal_False;
}
sal_Bool ScDocument::ReplaceStyle(const SvxSearchItem& rSearchItem,
SCCOL nCol, SCROW nRow, SCTAB nTab,
ScMarkData& rMark,
sal_Bool bIsUndoP)
{
if (pTab[nTab])
return pTab[nTab]->ReplaceStyle(rSearchItem, nCol, nRow, rMark, bIsUndoP);
else
return sal_False;
}
void ScDocument::CompileDBFormula()
{
for (SCTAB i=0; i<=MAXTAB; i++)
{
if (pTab[i]) pTab[i]->CompileDBFormula();
}
}
void ScDocument::CompileDBFormula( sal_Bool bCreateFormulaString )
{
for (SCTAB i=0; i<=MAXTAB; i++)
{
if (pTab[i]) pTab[i]->CompileDBFormula( bCreateFormulaString );
}
}
void ScDocument::CompileNameFormula( sal_Bool bCreateFormulaString )
{
if ( pCondFormList )
pCondFormList->CompileAll(); // nach ScNameDlg noetig
for (SCTAB i=0; i<=MAXTAB; i++)
{
if (pTab[i]) pTab[i]->CompileNameFormula( bCreateFormulaString );
}
}
void ScDocument::CompileColRowNameFormula()
{
for (SCTAB i=0; i<=MAXTAB; i++)
{
if (pTab[i]) pTab[i]->CompileColRowNameFormula();
}
}
void ScDocument::DoColResize( SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCSIZE nAdd )
{
if (ValidTab(nTab) && pTab[nTab])
pTab[nTab]->DoColResize( nCol1, nCol2, nAdd );
else
{
DBG_ERROR("DoColResize: falsche Tabelle");
}
}
void ScDocument::InvalidateTableArea()
{
for (SCTAB nTab=0; nTab<=MAXTAB && pTab[nTab]; nTab++)
{
pTab[nTab]->InvalidateTableArea();
if ( pTab[nTab]->IsScenario() )
pTab[nTab]->InvalidateScenarioRanges();
}
}
void ScDocument::GetLastAttrCell( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow ) const
{
if ( ValidTab( nTab ) && pTab[nTab] )
{
pTab[nTab]->GetLastAttrCell( rEndCol, rEndRow );
}
}
sal_Int32 ScDocument::GetMaxStringLen( SCTAB nTab, SCCOL nCol,
SCROW nRowStart, SCROW nRowEnd, CharSet eCharSet ) const
{
if (ValidTab(nTab) && pTab[nTab])
return pTab[nTab]->GetMaxStringLen( nCol, nRowStart, nRowEnd, eCharSet );
else
return 0;
}
xub_StrLen ScDocument::GetMaxNumberStringLen( sal_uInt16& nPrecision, SCTAB nTab,
SCCOL nCol,
SCROW nRowStart, SCROW nRowEnd ) const
{
if (ValidTab(nTab) && pTab[nTab])
return pTab[nTab]->GetMaxNumberStringLen( nPrecision, nCol,
nRowStart, nRowEnd );
else
return 0;
}
sal_Bool ScDocument::GetSelectionFunction( ScSubTotalFunc eFunc,
const ScAddress& rCursor, const ScMarkData& rMark,
double& rResult )
{
ScFunctionData aData(eFunc);
ScRange aSingle( rCursor );
if ( rMark.IsMarked() )
rMark.GetMarkArea(aSingle);
SCCOL nStartCol = aSingle.aStart.Col();
SCROW nStartRow = aSingle.aStart.Row();
SCCOL nEndCol = aSingle.aEnd.Col();
SCROW nEndRow = aSingle.aEnd.Row();
for (SCTAB nTab=0; nTab<=MAXTAB && !aData.bError; nTab++)
if (pTab[nTab] && rMark.GetTableSelect(nTab))
pTab[nTab]->UpdateSelectionFunction( aData,
nStartCol, nStartRow, nEndCol, nEndRow, rMark );
//! rMark an UpdateSelectionFunction uebergeben !!!!!
if (!aData.bError)
switch (eFunc)
{
case SUBTOTAL_FUNC_SUM:
rResult = aData.nVal;
break;
case SUBTOTAL_FUNC_CNT:
case SUBTOTAL_FUNC_CNT2:
rResult = aData.nCount;
break;
case SUBTOTAL_FUNC_AVE:
if (aData.nCount)
rResult = aData.nVal / (double) aData.nCount;
else
aData.bError = sal_True;
break;
case SUBTOTAL_FUNC_MAX:
case SUBTOTAL_FUNC_MIN:
if (aData.nCount)
rResult = aData.nVal;
else
aData.bError = sal_True;
break;
default:
{
// added to avoid warnings
}
}
if (aData.bError)
rResult = 0.0;
return !aData.bError;
}
double ScDocument::RoundValueAsShown( double fVal, sal_uLong nFormat )
{
short nType;
if ( (nType = GetFormatTable()->GetType( nFormat )) != NUMBERFORMAT_DATE
&& nType != NUMBERFORMAT_TIME && nType != NUMBERFORMAT_DATETIME )
{
short nPrecision;
if ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0)
{
nPrecision = (short)GetFormatTable()->GetFormatPrecision( nFormat );
switch ( nType )
{
case NUMBERFORMAT_PERCENT: // 0,41% == 0,0041
nPrecision += 2;
break;
case NUMBERFORMAT_SCIENTIFIC: // 1,23e-3 == 0,00123
{
if ( fVal > 0.0 )
nPrecision = sal::static_int_cast<short>( nPrecision - (short)floor( log10( fVal ) ) );
else if ( fVal < 0.0 )
nPrecision = sal::static_int_cast<short>( nPrecision - (short)floor( log10( -fVal ) ) );
break;
}
}
}
else
{
nPrecision = (short)GetDocOptions().GetStdPrecision();
// #i115512# no rounding for automatic decimals
if (nPrecision == static_cast<short>(SvNumberFormatter::UNLIMITED_PRECISION))
return fVal;
}
double fRound = ::rtl::math::round( fVal, nPrecision );
if ( ::rtl::math::approxEqual( fVal, fRound ) )
return fVal; // durch Rundung hoechstens Fehler
else
return fRound;
}
else
return fVal;
}
//
// bedingte Formate und Gueltigkeitsbereiche
//
sal_uLong ScDocument::AddCondFormat( const ScConditionalFormat& rNew )
{
if (rNew.IsEmpty())
return 0; // leer ist immer 0
if (!pCondFormList)
pCondFormList = new ScConditionalFormatList;
sal_uLong nMax = 0;
sal_uInt16 nCount = pCondFormList->Count();
for (sal_uInt16 i=0; i<nCount; i++)
{
const ScConditionalFormat* pForm = (*pCondFormList)[i];
sal_uLong nKey = pForm->GetKey();
if ( pForm->EqualEntries( rNew ) )
return nKey;
if ( nKey > nMax )
nMax = nKey;
}
// Der Aufruf kann aus ScPatternAttr::PutInPool kommen, darum Clone (echte Kopie)
sal_uLong nNewKey = nMax + 1;
ScConditionalFormat* pInsert = rNew.Clone(this);
pInsert->SetKey( nNewKey );
pCondFormList->InsertNew( pInsert );
return nNewKey;
}
sal_uLong ScDocument::AddValidationEntry( const ScValidationData& rNew )
{
if (rNew.IsEmpty())
return 0; // leer ist immer 0
if (!pValidationList)
pValidationList = new ScValidationDataList;
sal_uLong nMax = 0;
sal_uInt16 nCount = pValidationList->Count();
for (sal_uInt16 i=0; i<nCount; i++)
{
const ScValidationData* pData = (*pValidationList)[i];
sal_uLong nKey = pData->GetKey();
if ( pData->EqualEntries( rNew ) )
return nKey;
if ( nKey > nMax )
nMax = nKey;
}
// Der Aufruf kann aus ScPatternAttr::PutInPool kommen, darum Clone (echte Kopie)
sal_uLong nNewKey = nMax + 1;
ScValidationData* pInsert = rNew.Clone(this);
pInsert->SetKey( nNewKey );
pValidationList->InsertNew( pInsert );
return nNewKey;
}
const SfxPoolItem* ScDocument::GetEffItem(
SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich ) const
{
const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
if ( pPattern )
{
const SfxItemSet& rSet = pPattern->GetItemSet();
const SfxPoolItem* pItem;
if ( rSet.GetItemState( ATTR_CONDITIONAL, sal_True, &pItem ) == SFX_ITEM_SET )
{
sal_uLong nIndex = ((const SfxUInt32Item*)pItem)->GetValue();
if (nIndex && pCondFormList)
{
const ScConditionalFormat* pForm = pCondFormList->GetFormat( nIndex );
if ( pForm )
{
ScBaseCell* pCell = ((ScDocument*)this)->GetCell(ScAddress(nCol,nRow,nTab));
String aStyle = pForm->GetCellStyle( pCell, ScAddress(nCol, nRow, nTab) );
if (aStyle.Len())
{
SfxStyleSheetBase* pStyleSheet = xPoolHelper->GetStylePool()->Find(
aStyle, SFX_STYLE_FAMILY_PARA );
if ( pStyleSheet && pStyleSheet->GetItemSet().GetItemState(
nWhich, sal_True, &pItem ) == SFX_ITEM_SET )
return pItem;
}
}
}
}
return &rSet.Get( nWhich );
}
DBG_ERROR("kein Pattern");
return NULL;
}
const SfxItemSet* ScDocument::GetCondResult( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
const ScConditionalFormat* pForm = GetCondFormat( nCol, nRow, nTab );
if ( pForm )
{
ScBaseCell* pCell = ((ScDocument*)this)->GetCell(ScAddress(nCol,nRow,nTab));
String aStyle = pForm->GetCellStyle( pCell, ScAddress(nCol, nRow, nTab) );
if (aStyle.Len())
{
SfxStyleSheetBase* pStyleSheet = xPoolHelper->GetStylePool()->Find( aStyle, SFX_STYLE_FAMILY_PARA );
if ( pStyleSheet )
return &pStyleSheet->GetItemSet();
// if style is not there, treat like no condition
}
}
return NULL;
}
const ScConditionalFormat* ScDocument::GetCondFormat(
SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
sal_uLong nIndex = ((const SfxUInt32Item*)GetAttr(nCol,nRow,nTab,ATTR_CONDITIONAL))->GetValue();
if (nIndex)
{
if (pCondFormList)
return pCondFormList->GetFormat( nIndex );
else
{
DBG_ERROR("pCondFormList ist 0");
}
}
return NULL;
}
const ScValidationData* ScDocument::GetValidationEntry( sal_uLong nIndex ) const
{
if ( pValidationList )
return pValidationList->GetData( nIndex );
else
return NULL;
}
void ScDocument::FindConditionalFormat( sal_uLong nKey, ScRangeList& rRanges )
{
for (SCTAB i=0; i<=MAXTAB && pTab[i]; i++)
pTab[i]->FindConditionalFormat( nKey, rRanges );
}
void ScDocument::FindConditionalFormat( sal_uLong nKey, ScRangeList& rRanges, SCTAB nTab )
{
if(VALIDTAB(nTab) && pTab[nTab])
pTab[nTab]->FindConditionalFormat( nKey, rRanges );
}
void ScDocument::ConditionalChanged( sal_uLong nKey )
{
if ( nKey && pCondFormList && !bIsClip && !bIsUndo ) // nKey==0 -> noop
{
ScConditionalFormat* pForm = pCondFormList->GetFormat( nKey );
if (pForm)
pForm->InvalidateArea();
}
}
void ScDocument::SetCondFormList(ScConditionalFormatList* pNew)
{
if (pCondFormList)
{
pCondFormList->DeleteAndDestroy( 0, pCondFormList->Count() );
delete pCondFormList;
}
pCondFormList = pNew;
}
//------------------------------------------------------------------------
sal_Bool ScDocument::HasDetectiveOperations() const
{
return pDetOpList && pDetOpList->Count();
}
void ScDocument::AddDetectiveOperation( const ScDetOpData& rData )
{
if (!pDetOpList)
pDetOpList = new ScDetOpList;
pDetOpList->Append( new ScDetOpData( rData ) );
}
void ScDocument::ClearDetectiveOperations()
{
delete pDetOpList; // loescht auch die Eintraege
pDetOpList = NULL;
}
void ScDocument::SetDetOpList(ScDetOpList* pNew)
{
delete pDetOpList; // loescht auch die Eintraege
pDetOpList = pNew;
}
//------------------------------------------------------------------------
//
// Vergleich von Dokumenten
//
//------------------------------------------------------------------------
// Pfriemel-Faktoren
#define SC_DOCCOMP_MAXDIFF 256
#define SC_DOCCOMP_MINGOOD 128
#define SC_DOCCOMP_COLUMNS 10
#define SC_DOCCOMP_ROWS 100
sal_uInt16 ScDocument::RowDifferences( SCROW nThisRow, SCTAB nThisTab,
ScDocument& rOtherDoc, SCROW nOtherRow, SCTAB nOtherTab,
SCCOL nMaxCol, SCCOLROW* pOtherCols )
{
sal_uLong nDif = 0;
sal_uLong nUsed = 0;
for (SCCOL nThisCol=0; nThisCol<=nMaxCol; nThisCol++)
{
SCCOL nOtherCol;
if ( pOtherCols )
nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
else
nOtherCol = nThisCol;
if (ValidCol(nOtherCol)) // nur Spalten vergleichen, die in beiden Dateien sind
{
const ScBaseCell* pThisCell = GetCell( ScAddress( nThisCol, nThisRow, nThisTab ) );
const ScBaseCell* pOtherCell = rOtherDoc.GetCell( ScAddress( nOtherCol, nOtherRow, nOtherTab ) );
if (!ScBaseCell::CellEqual( pThisCell, pOtherCell ))
{
if ( pThisCell && pOtherCell )
nDif += 3;
else
nDif += 4; // Inhalt <-> leer zaehlt mehr
}
if ( ( pThisCell && pThisCell->GetCellType()!=CELLTYPE_NOTE ) ||
( pOtherCell && pOtherCell->GetCellType()!=CELLTYPE_NOTE ) )
++nUsed;
}
}
if (nUsed > 0)
return static_cast<sal_uInt16>((nDif*64)/nUsed); // max.256 (SC_DOCCOMP_MAXDIFF)
DBG_ASSERT(!nDif,"Diff ohne Used");
return 0;
}
sal_uInt16 ScDocument::ColDifferences( SCCOL nThisCol, SCTAB nThisTab,
ScDocument& rOtherDoc, SCCOL nOtherCol, SCTAB nOtherTab,
SCROW nMaxRow, SCCOLROW* pOtherRows )
{
//! optimieren mit Iterator oder so
sal_uLong nDif = 0;
sal_uLong nUsed = 0;
for (SCROW nThisRow=0; nThisRow<=nMaxRow; nThisRow++)
{
SCROW nOtherRow;
if ( pOtherRows )
nOtherRow = pOtherRows[nThisRow];
else
nOtherRow = nThisRow;
if (ValidRow(nOtherRow)) // nur Zeilen vergleichen, die in beiden Dateien sind
{
const ScBaseCell* pThisCell = GetCell( ScAddress( nThisCol, nThisRow, nThisTab ) );
const ScBaseCell* pOtherCell = rOtherDoc.GetCell( ScAddress( nOtherCol, nOtherRow, nOtherTab ) );
if (!ScBaseCell::CellEqual( pThisCell, pOtherCell ))
{
if ( pThisCell && pOtherCell )
nDif += 3;
else
nDif += 4; // Inhalt <-> leer zaehlt mehr
}
if ( ( pThisCell && pThisCell->GetCellType()!=CELLTYPE_NOTE ) ||
( pOtherCell && pOtherCell->GetCellType()!=CELLTYPE_NOTE ) )
++nUsed;
}
}
if (nUsed > 0)
return static_cast<sal_uInt16>((nDif*64)/nUsed); // max.256
DBG_ASSERT(!nDif,"Diff ohne Used");
return 0;
}
void ScDocument::FindOrder( SCCOLROW* pOtherRows, SCCOLROW nThisEndRow, SCCOLROW nOtherEndRow,
sal_Bool bColumns, ScDocument& rOtherDoc, SCTAB nThisTab, SCTAB nOtherTab,
SCCOLROW nEndCol, SCCOLROW* pTranslate, ScProgress* pProgress, sal_uLong nProAdd )
{
// bColumns=sal_True: Zeilen sind Spalten und umgekehrt
SCCOLROW nMaxCont; // wieviel weiter
SCCOLROW nMinGood; // was ist ein Treffer (incl.)
if ( bColumns )
{
nMaxCont = SC_DOCCOMP_COLUMNS; // 10 Spalten
nMinGood = SC_DOCCOMP_MINGOOD;
//! Extra Durchgang mit nMinGood = 0 ????
}
else
{
nMaxCont = SC_DOCCOMP_ROWS; // 100 Zeilen
nMinGood = SC_DOCCOMP_MINGOOD;
}
sal_Bool bUseTotal = bColumns && !pTranslate; // nur beim ersten Durchgang
SCCOLROW nOtherRow = 0;
sal_uInt16 nComp;
SCCOLROW nThisRow;
sal_Bool bTotal = sal_False; // ueber verschiedene nThisRow beibehalten
SCCOLROW nUnknown = 0;
for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++)
{
SCCOLROW nTempOther = nOtherRow;
sal_Bool bFound = sal_False;
sal_uInt16 nBest = SC_DOCCOMP_MAXDIFF;
SCCOLROW nMax = Min( nOtherEndRow, static_cast<SCCOLROW>(( nTempOther + nMaxCont + nUnknown )) );
for (SCCOLROW i=nTempOther; i<=nMax && nBest>0; i++) // bei 0 abbrechen
{
if (bColumns)
nComp = ColDifferences( static_cast<SCCOL>(nThisRow), nThisTab, rOtherDoc, static_cast<SCCOL>(i), nOtherTab, nEndCol, pTranslate );
else
nComp = RowDifferences( nThisRow, nThisTab, rOtherDoc, i, nOtherTab, static_cast<SCCOL>(nEndCol), pTranslate );
if ( nComp < nBest && ( nComp <= nMinGood || bTotal ) )
{
nTempOther = i;
nBest = nComp;
bFound = sal_True;
}
if ( nComp < SC_DOCCOMP_MAXDIFF || bFound )
bTotal = sal_False;
else if ( i == nTempOther && bUseTotal )
bTotal = sal_True; // nur ganz oben
}
if ( bFound )
{
pOtherRows[nThisRow] = nTempOther;
nOtherRow = nTempOther + 1;
nUnknown = 0;
}
else
{
pOtherRows[nThisRow] = SCROW_MAX;
++nUnknown;
}
if (pProgress)
pProgress->SetStateOnPercent(nProAdd+static_cast<sal_uLong>(nThisRow));
}
// Bloecke ohne Uebereinstimmung ausfuellen
SCROW nFillStart = 0;
SCROW nFillPos = 0;
sal_Bool bInFill = sal_False;
for (nThisRow = 0; nThisRow <= nThisEndRow+1; nThisRow++)
{
SCROW nThisOther = ( nThisRow <= nThisEndRow ) ? pOtherRows[nThisRow] : (nOtherEndRow+1);
if ( ValidRow(nThisOther) )
{
if ( bInFill )
{
if ( nThisOther > nFillStart ) // ist was zu verteilen da?
{
SCROW nDiff1 = nThisOther - nFillStart;
SCROW nDiff2 = nThisRow - nFillPos;
SCROW nMinDiff = Min(nDiff1, nDiff2);
for (SCROW i=0; i<nMinDiff; i++)
pOtherRows[nFillPos+i] = nFillStart+i;
}
bInFill = sal_False;
}
nFillStart = nThisOther + 1;
nFillPos = nThisRow + 1;
}
else
bInFill = sal_True;
}
}
void ScDocument::CompareDocument( ScDocument& rOtherDoc )
{
if (!pChangeTrack)
return;
SCTAB nThisCount = GetTableCount();
SCTAB nOtherCount = rOtherDoc.GetTableCount();
SCTAB* pOtherTabs = new SCTAB[nThisCount];
SCTAB nThisTab;
// Tabellen mit gleichen Namen vergleichen
String aThisName;
String aOtherName;
for (nThisTab=0; nThisTab<nThisCount; nThisTab++)
{
SCTAB nOtherTab = SCTAB_MAX;
if (!IsScenario(nThisTab)) // Szenarien weglassen
{
GetName( nThisTab, aThisName );
for (SCTAB nTemp=0; nTemp<nOtherCount && nOtherTab>MAXTAB; nTemp++)
if (!rOtherDoc.IsScenario(nTemp))
{
rOtherDoc.GetName( nTemp, aOtherName );
if ( aThisName == aOtherName )
nOtherTab = nTemp;
}
}
pOtherTabs[nThisTab] = nOtherTab;
}
// auffuellen, damit einzeln umbenannte Tabellen nicht wegfallen
SCTAB nFillStart = 0;
SCTAB nFillPos = 0;
sal_Bool bInFill = sal_False;
for (nThisTab = 0; nThisTab <= nThisCount; nThisTab++)
{
SCTAB nThisOther = ( nThisTab < nThisCount ) ? pOtherTabs[nThisTab] : nOtherCount;
if ( ValidTab(nThisOther) )
{
if ( bInFill )
{
if ( nThisOther > nFillStart ) // ist was zu verteilen da?
{
SCTAB nDiff1 = nThisOther - nFillStart;
SCTAB nDiff2 = nThisTab - nFillPos;
SCTAB nMinDiff = Min(nDiff1, nDiff2);
for (SCTAB i=0; i<nMinDiff; i++)
if ( !IsScenario(nFillPos+i) && !rOtherDoc.IsScenario(nFillStart+i) )
pOtherTabs[nFillPos+i] = nFillStart+i;
}
bInFill = sal_False;
}
nFillStart = nThisOther + 1;
nFillPos = nThisTab + 1;
}
else
bInFill = sal_True;
}
//
// Tabellen in der gefundenen Reihenfolge vergleichen
//
for (nThisTab=0; nThisTab<nThisCount; nThisTab++)
{
SCTAB nOtherTab = pOtherTabs[nThisTab];
if ( ValidTab(nOtherTab) )
{
SCCOL nThisEndCol = 0;
SCROW nThisEndRow = 0;
SCCOL nOtherEndCol = 0;
SCROW nOtherEndRow = 0;
GetCellArea( nThisTab, nThisEndCol, nThisEndRow );
rOtherDoc.GetCellArea( nOtherTab, nOtherEndCol, nOtherEndRow );
SCCOL nEndCol = Max(nThisEndCol, nOtherEndCol);
SCROW nEndRow = Max(nThisEndRow, nOtherEndRow);
SCCOL nThisCol;
SCROW nThisRow;
sal_uLong n1,n2; // fuer AppendDeleteRange
//! ein Progress ueber alle Tabellen ???
String aTabName;
GetName( nThisTab, aTabName );
String aTemplate = ScGlobal::GetRscString(STR_PROGRESS_COMPARING);
String aProText = aTemplate.GetToken( 0, '#' );
aProText += aTabName;
aProText += aTemplate.GetToken( 1, '#' );
ScProgress aProgress( GetDocumentShell(),
aProText, 3*nThisEndRow ); // 2x FindOrder, 1x hier
long nProgressStart = 2*nThisEndRow; // start fuer hier
SCCOLROW* pTempRows = new SCCOLROW[nThisEndRow+1];
SCCOLROW* pOtherRows = new SCCOLROW[nThisEndRow+1];
SCCOLROW* pOtherCols = new SCCOLROW[nThisEndCol+1];
// eingefuegte/geloeschte Spalten/Zeilen finden:
// Zwei Versuche:
// 1) Original Zeilen vergleichen (pTempRows)
// 2) Original Spalten vergleichen (pOtherCols)
// mit dieser Spaltenreihenfolge Zeilen vergleichen (pOtherRows)
//! Spalten vergleichen zweimal mit unterschiedlichem nMinGood ???
// 1
FindOrder( pTempRows, nThisEndRow, nOtherEndRow, sal_False,
rOtherDoc, nThisTab, nOtherTab, nEndCol, NULL, &aProgress, 0 );
// 2
FindOrder( pOtherCols, nThisEndCol, nOtherEndCol, sal_True,
rOtherDoc, nThisTab, nOtherTab, nEndRow, NULL, NULL, 0 );
FindOrder( pOtherRows, nThisEndRow, nOtherEndRow, sal_False,
rOtherDoc, nThisTab, nOtherTab, nThisEndCol,
pOtherCols, &aProgress, nThisEndRow );
sal_uLong nMatch1 = 0; // pTempRows, keine Spalten
for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++)
if (ValidRow(pTempRows[nThisRow]))
nMatch1 += SC_DOCCOMP_MAXDIFF -
RowDifferences( nThisRow, nThisTab, rOtherDoc, pTempRows[nThisRow],
nOtherTab, nEndCol, NULL );
sal_uLong nMatch2 = 0; // pOtherRows, pOtherCols
for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++)
if (ValidRow(pOtherRows[nThisRow]))
nMatch2 += SC_DOCCOMP_MAXDIFF -
RowDifferences( nThisRow, nThisTab, rOtherDoc, pOtherRows[nThisRow],
nOtherTab, nThisEndCol, pOtherCols );
if ( nMatch1 >= nMatch2 ) // ohne Spalten ?
{
// Spalten zuruecksetzen
for (nThisCol = 0; nThisCol<=nThisEndCol; nThisCol++)
pOtherCols[nThisCol] = nThisCol;
// Zeilenarrays vertauschen (geloescht werden sowieso beide)
SCCOLROW* pSwap = pTempRows;
pTempRows = pOtherRows;
pOtherRows = pSwap;
}
else
{
// bleibt bei pOtherCols, pOtherRows
}
// Change-Actions erzeugen
// 1) Spalten von rechts
// 2) Zeilen von unten
// 3) einzelne Zellen in normaler Reihenfolge
// Actions fuer eingefuegte/geloeschte Spalten
SCCOL nLastOtherCol = static_cast<SCCOL>(nOtherEndCol + 1);
// nThisEndCol ... 0
for ( nThisCol = nThisEndCol+1; nThisCol > 0; )
{
--nThisCol;
SCCOL nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
if ( ValidCol(nOtherCol) && nOtherCol+1 < nLastOtherCol )
{
// Luecke -> geloescht
ScRange aDelRange( nOtherCol+1, 0, nOtherTab,
nLastOtherCol-1, MAXROW, nOtherTab );
pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
}
if ( nOtherCol > MAXCOL ) // eingefuegt
{
// zusammenfassen
if ( nThisCol == nThisEndCol || ValidCol(static_cast<SCCOL>(pOtherCols[nThisCol+1])) )
{
SCCOL nFirstNew = static_cast<SCCOL>(nThisCol);
while ( nFirstNew > 0 && pOtherCols[nFirstNew-1] > MAXCOL )
--nFirstNew;
SCCOL nDiff = nThisCol - nFirstNew;
ScRange aRange( nLastOtherCol, 0, nOtherTab,
nLastOtherCol+nDiff, MAXROW, nOtherTab );
pChangeTrack->AppendInsert( aRange );
}
}
else
nLastOtherCol = nOtherCol;
}
if ( nLastOtherCol > 0 ) // ganz oben geloescht
{
ScRange aDelRange( 0, 0, nOtherTab,
nLastOtherCol-1, MAXROW, nOtherTab );
pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
}
// Actions fuer eingefuegte/geloeschte Zeilen
SCROW nLastOtherRow = nOtherEndRow + 1;
// nThisEndRow ... 0
for ( nThisRow = nThisEndRow+1; nThisRow > 0; )
{
--nThisRow;
SCROW nOtherRow = pOtherRows[nThisRow];
if ( ValidRow(nOtherRow) && nOtherRow+1 < nLastOtherRow )
{
// Luecke -> geloescht
ScRange aDelRange( 0, nOtherRow+1, nOtherTab,
MAXCOL, nLastOtherRow-1, nOtherTab );
pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
}
if ( nOtherRow > MAXROW ) // eingefuegt
{
// zusammenfassen
if ( nThisRow == nThisEndRow || ValidRow(pOtherRows[nThisRow+1]) )
{
SCROW nFirstNew = nThisRow;
while ( nFirstNew > 0 && pOtherRows[nFirstNew-1] > MAXROW )
--nFirstNew;
SCROW nDiff = nThisRow - nFirstNew;
ScRange aRange( 0, nLastOtherRow, nOtherTab,
MAXCOL, nLastOtherRow+nDiff, nOtherTab );
pChangeTrack->AppendInsert( aRange );
}
}
else
nLastOtherRow = nOtherRow;
}
if ( nLastOtherRow > 0 ) // ganz oben geloescht
{
ScRange aDelRange( 0, 0, nOtherTab,
MAXCOL, nLastOtherRow-1, nOtherTab );
pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
}
// Zeilen durchgehen um einzelne Zellen zu finden
for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++)
{
SCROW nOtherRow = pOtherRows[nThisRow];
for (nThisCol = 0; nThisCol <= nThisEndCol; nThisCol++)
{
SCCOL nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
ScAddress aThisPos( nThisCol, nThisRow, nThisTab );
const ScBaseCell* pThisCell = GetCell( aThisPos );
const ScBaseCell* pOtherCell = NULL;
if ( ValidCol(nOtherCol) && ValidRow(nOtherRow) )
{
ScAddress aOtherPos( nOtherCol, nOtherRow, nOtherTab );
pOtherCell = rOtherDoc.GetCell( aOtherPos );
}
if ( !ScBaseCell::CellEqual( pThisCell, pOtherCell ) )
{
ScRange aRange( aThisPos );
ScChangeActionContent* pAction = new ScChangeActionContent( aRange );
pAction->SetOldValue( pOtherCell, &rOtherDoc, this );
pAction->SetNewValue( pThisCell, this );
pChangeTrack->Append( pAction );
}
}
aProgress.SetStateOnPercent(nProgressStart+nThisRow);
}
delete[] pOtherCols;
delete[] pOtherRows;
delete[] pTempRows;
}
}
//! Inhalt von eingefuegten / geloeschten Tabellen ???
//! Aktionen fuer eingefuegte / geloeschte Tabellen ???
delete[] pOtherTabs;
}