blob: 4109355edb3f3124c19a5981b4821f8ca125a6df [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 <rtl/math.hxx>
#include <unotools/textsearch.hxx>
#include <svl/zforlist.hxx>
#include <svl/zformat.hxx>
#include <unotools/charclass.hxx>
#include <unotools/collatorwrapper.hxx>
#include <com/sun/star/i18n/CollatorOptions.hpp>
#include <stdlib.h>
#include <unotools/transliterationwrapper.hxx>
#include "table.hxx"
#include "scitems.hxx"
#include "collect.hxx"
#include "attrib.hxx"
#include "cell.hxx"
#include "document.hxx"
#include "globstr.hrc"
#include "global.hxx"
#include "stlpool.hxx"
#include "compiler.hxx"
#include "patattr.hxx"
#include "subtotal.hxx"
#include "docoptio.hxx"
#include "markdata.hxx"
#include "rangelst.hxx"
#include "attarray.hxx"
#include "userlist.hxx"
#include "progress.hxx"
#include "cellform.hxx"
#include "postit.hxx"
#include "queryparam.hxx"
#include "segmenttree.hxx"
#include "drwlayer.hxx"
#include <vector>
// STATIC DATA -----------------------------------------------------------
const sal_uInt16 nMaxSorts = 3; // maximale Anzahl Sortierkriterien in aSortParam
struct ScSortInfo
{
ScBaseCell* pCell;
SCCOLROW nOrg;
DECL_FIXEDMEMPOOL_NEWDEL( ScSortInfo );
};
const sal_uInt16 nMemPoolSortInfo = (0x8000 - 64) / sizeof(ScSortInfo);
IMPL_FIXEDMEMPOOL_NEWDEL( ScSortInfo, nMemPoolSortInfo, nMemPoolSortInfo )
// END OF STATIC DATA -----------------------------------------------------
class ScSortInfoArray
{
private:
ScSortInfo** pppInfo[nMaxSorts];
SCSIZE nCount;
SCCOLROW nStart;
sal_uInt16 nUsedSorts;
public:
ScSortInfoArray( sal_uInt16 nSorts, SCCOLROW nInd1, SCCOLROW nInd2 ) :
nCount( nInd2 - nInd1 + 1 ), nStart( nInd1 ),
nUsedSorts( Min( nSorts, nMaxSorts ) )
{
for ( sal_uInt16 nSort = 0; nSort < nUsedSorts; nSort++ )
{
ScSortInfo** ppInfo = new ScSortInfo* [nCount];
for ( SCSIZE j = 0; j < nCount; j++ )
ppInfo[j] = new ScSortInfo;
pppInfo[nSort] = ppInfo;
}
}
~ScSortInfoArray()
{
for ( sal_uInt16 nSort = 0; nSort < nUsedSorts; nSort++ )
{
ScSortInfo** ppInfo = pppInfo[nSort];
for ( SCSIZE j = 0; j < nCount; j++ )
delete ppInfo[j];
delete [] ppInfo;
}
}
ScSortInfo* Get( sal_uInt16 nSort, SCCOLROW nInd )
{ return (pppInfo[nSort])[ nInd - nStart ]; }
void Swap( SCCOLROW nInd1, SCCOLROW nInd2 )
{
SCSIZE n1 = static_cast<SCSIZE>(nInd1 - nStart);
SCSIZE n2 = static_cast<SCSIZE>(nInd2 - nStart);
for ( sal_uInt16 nSort = 0; nSort < nUsedSorts; nSort++ )
{
ScSortInfo** ppInfo = pppInfo[nSort];
ScSortInfo* pTmp = ppInfo[n1];
ppInfo[n1] = ppInfo[n2];
ppInfo[n2] = pTmp;
}
}
sal_uInt16 GetUsedSorts() { return nUsedSorts; }
ScSortInfo** GetFirstArray() { return pppInfo[0]; }
SCCOLROW GetStart() { return nStart; }
SCSIZE GetCount() { return nCount; }
};
ScSortInfoArray* ScTable::CreateSortInfoArray( SCCOLROW nInd1, SCCOLROW nInd2 )
{
sal_uInt16 nUsedSorts = 1;
while ( nUsedSorts < nMaxSorts && aSortParam.bDoSort[nUsedSorts] )
nUsedSorts++;
ScSortInfoArray* pArray = new ScSortInfoArray( nUsedSorts, nInd1, nInd2 );
if ( aSortParam.bByRow )
{
for ( sal_uInt16 nSort = 0; nSort < nUsedSorts; nSort++ )
{
SCCOL nCol = static_cast<SCCOL>(aSortParam.nField[nSort]);
ScColumn* pCol = &aCol[nCol];
for ( SCROW nRow = nInd1; nRow <= nInd2; nRow++ )
{
//2do: FillSortInfo an ScColumn und Array abklappern statt Search in GetCell
ScSortInfo* pInfo = pArray->Get( nSort, nRow );
pInfo->pCell = pCol->GetCell( nRow );
pInfo->nOrg = nRow;
}
}
}
else
{
for ( sal_uInt16 nSort = 0; nSort < nUsedSorts; nSort++ )
{
SCROW nRow = aSortParam.nField[nSort];
for ( SCCOL nCol = static_cast<SCCOL>(nInd1);
nCol <= static_cast<SCCOL>(nInd2); nCol++ )
{
ScSortInfo* pInfo = pArray->Get( nSort, nCol );
pInfo->pCell = GetCell( nCol, nRow );
pInfo->nOrg = nCol;
}
}
}
return pArray;
}
sal_Bool ScTable::IsSortCollatorGlobal() const
{
return pSortCollator == ScGlobal::GetCollator() ||
pSortCollator == ScGlobal::GetCaseCollator();
}
void ScTable::InitSortCollator( const ScSortParam& rPar )
{
if ( rPar.aCollatorLocale.Language.getLength() )
{
if ( !pSortCollator || IsSortCollatorGlobal() )
pSortCollator = new CollatorWrapper( pDocument->GetServiceManager() );
pSortCollator->loadCollatorAlgorithm( rPar.aCollatorAlgorithm,
rPar.aCollatorLocale, (rPar.bCaseSens ? 0 : SC_COLLATOR_IGNORES) );
}
else
{ // SYSTEM
DestroySortCollator();
pSortCollator = (rPar.bCaseSens ? ScGlobal::GetCaseCollator() :
ScGlobal::GetCollator());
}
}
void ScTable::DestroySortCollator()
{
if ( pSortCollator )
{
if ( !IsSortCollatorGlobal() )
delete pSortCollator;
pSortCollator = NULL;
}
}
void ScTable::SortReorder( ScSortInfoArray* pArray, ScProgress& rProgress )
{
sal_Bool bByRow = aSortParam.bByRow;
SCSIZE nCount = pArray->GetCount();
SCCOLROW nStart = pArray->GetStart();
ScSortInfo** ppInfo = pArray->GetFirstArray();
::std::vector<ScSortInfo*> aTable(nCount);
SCSIZE nPos;
for ( nPos = 0; nPos < nCount; nPos++ )
aTable[ppInfo[nPos]->nOrg - nStart] = ppInfo[nPos];
SCCOLROW nDest = nStart;
for ( nPos = 0; nPos < nCount; nPos++, nDest++ )
{
SCCOLROW nOrg = ppInfo[nPos]->nOrg;
if ( nDest != nOrg )
{
if ( bByRow )
SwapRow( nDest, nOrg );
else
SwapCol( static_cast<SCCOL>(nDest), static_cast<SCCOL>(nOrg) );
// neue Position des weggeswapten eintragen
ScSortInfo* p = ppInfo[nPos];
p->nOrg = nDest;
::std::swap(p, aTable[nDest-nStart]);
p->nOrg = nOrg;
::std::swap(p, aTable[nOrg-nStart]);
DBG_ASSERT( p == ppInfo[nPos], "SortReorder: nOrg MisMatch" );
}
rProgress.SetStateOnPercent( nPos );
}
}
short ScTable::CompareCell( sal_uInt16 nSort,
ScBaseCell* pCell1, SCCOL nCell1Col, SCROW nCell1Row,
ScBaseCell* pCell2, SCCOL nCell2Col, SCROW nCell2Row )
{
short nRes = 0;
CellType eType1 = CELLTYPE_NONE, eType2 = CELLTYPE_NONE;
if (pCell1)
{
eType1 = pCell1->GetCellType();
if (eType1 == CELLTYPE_NOTE)
pCell1 = NULL;
}
if (pCell2)
{
eType2 = pCell2->GetCellType();
if (eType2 == CELLTYPE_NOTE)
pCell2 = NULL;
}
if (pCell1)
{
if (pCell2)
{
sal_Bool bStr1 = ( eType1 != CELLTYPE_VALUE );
if ( eType1 == CELLTYPE_FORMULA && ((ScFormulaCell*)pCell1)->IsValue() )
bStr1 = sal_False;
sal_Bool bStr2 = ( eType2 != CELLTYPE_VALUE );
if ( eType2 == CELLTYPE_FORMULA && ((ScFormulaCell*)pCell2)->IsValue() )
bStr2 = sal_False;
if ( bStr1 && bStr2 ) // nur Strings untereinander als String vergleichen!
{
String aStr1;
String aStr2;
if (eType1 == CELLTYPE_STRING)
((ScStringCell*)pCell1)->GetString(aStr1);
else
GetString(nCell1Col, nCell1Row, aStr1);
if (eType2 == CELLTYPE_STRING)
((ScStringCell*)pCell2)->GetString(aStr2);
else
GetString(nCell2Col, nCell2Row, aStr2);
sal_Bool bUserDef = aSortParam.bUserDef;
if (bUserDef)
{
ScUserListData* pData =
(ScUserListData*)(ScGlobal::GetUserList()->At(
aSortParam.nUserIndex));
if (pData)
{
if ( aSortParam.bCaseSens )
nRes = sal::static_int_cast<short>( pData->Compare(aStr1, aStr2) );
else
nRes = sal::static_int_cast<short>( pData->ICompare(aStr1, aStr2) );
}
else
bUserDef = sal_False;
}
if (!bUserDef)
nRes = (short) pSortCollator->compareString( aStr1, aStr2 );
}
else if ( bStr1 ) // String <-> Zahl
nRes = 1; // Zahl vorne
else if ( bStr2 ) // Zahl <-> String
nRes = -1; // Zahl vorne
else // Zahlen untereinander
{
double nVal1;
double nVal2;
if (eType1 == CELLTYPE_VALUE)
nVal1 = ((ScValueCell*)pCell1)->GetValue();
else if (eType1 == CELLTYPE_FORMULA)
nVal1 = ((ScFormulaCell*)pCell1)->GetValue();
else
nVal1 = 0;
if (eType2 == CELLTYPE_VALUE)
nVal2 = ((ScValueCell*)pCell2)->GetValue();
else if (eType2 == CELLTYPE_FORMULA)
nVal2 = ((ScFormulaCell*)pCell2)->GetValue();
else
nVal2 = 0;
if (nVal1 < nVal2)
nRes = -1;
else if (nVal1 > nVal2)
nRes = 1;
}
if ( !aSortParam.bAscending[nSort] )
nRes = -nRes;
}
else
nRes = -1;
}
else
{
if ( pCell2 )
nRes = 1;
else
nRes = 0; // beide leer
}
return nRes;
}
short ScTable::Compare( ScSortInfoArray* pArray, SCCOLROW nIndex1, SCCOLROW nIndex2 )
{
short nRes;
sal_uInt16 nSort = 0;
do
{
ScSortInfo* pInfo1 = pArray->Get( nSort, nIndex1 );
ScSortInfo* pInfo2 = pArray->Get( nSort, nIndex2 );
if ( aSortParam.bByRow )
nRes = CompareCell( nSort,
pInfo1->pCell, static_cast<SCCOL>(aSortParam.nField[nSort]), pInfo1->nOrg,
pInfo2->pCell, static_cast<SCCOL>(aSortParam.nField[nSort]), pInfo2->nOrg );
else
nRes = CompareCell( nSort,
pInfo1->pCell, static_cast<SCCOL>(pInfo1->nOrg), aSortParam.nField[nSort],
pInfo2->pCell, static_cast<SCCOL>(pInfo2->nOrg), aSortParam.nField[nSort] );
} while ( nRes == 0 && ++nSort < pArray->GetUsedSorts() );
if( nRes == 0 )
{
ScSortInfo* pInfo1 = pArray->Get( 0, nIndex1 );
ScSortInfo* pInfo2 = pArray->Get( 0, nIndex2 );
if( pInfo1->nOrg < pInfo2->nOrg )
nRes = -1;
else if( pInfo1->nOrg > pInfo2->nOrg )
nRes = 1;
}
return nRes;
}
void ScTable::QuickSort( ScSortInfoArray* pArray, SCsCOLROW nLo, SCsCOLROW nHi )
{
if ((nHi - nLo) == 1)
{
if (Compare(pArray, nLo, nHi) > 0)
pArray->Swap( nLo, nHi );
}
else
{
SCsCOLROW ni = nLo;
SCsCOLROW nj = nHi;
do
{
while ((ni <= nHi) && (Compare(pArray, ni, nLo)) < 0)
ni++;
while ((nj >= nLo) && (Compare(pArray, nLo, nj)) < 0)
nj--;
if (ni <= nj)
{
if (ni != nj)
pArray->Swap( ni, nj );
ni++;
nj--;
}
} while (ni < nj);
if ((nj - nLo) < (nHi - ni))
{
if (nLo < nj)
QuickSort(pArray, nLo, nj);
if (ni < nHi)
QuickSort(pArray, ni, nHi);
}
else
{
if (ni < nHi)
QuickSort(pArray, ni, nHi);
if (nLo < nj)
QuickSort(pArray, nLo, nj);
}
}
}
void ScTable::SwapCol(SCCOL nCol1, SCCOL nCol2)
{
for (SCROW nRow = aSortParam.nRow1; nRow <= aSortParam.nRow2; nRow++)
{
aCol[nCol1].SwapCell(nRow, aCol[nCol2]);
if (aSortParam.bIncludePattern)
{
const ScPatternAttr* pPat1 = GetPattern(nCol1, nRow);
const ScPatternAttr* pPat2 = GetPattern(nCol2, nRow);
if (pPat1 != pPat2)
{
//Add Reference to avoid pPat1 to be deleted by merge same cell attributes for adjacent cells
if( IsPooledItem( pPat1 ) ) pPat1->AddRef();
SetPattern(nCol1, nRow, *pPat2, sal_True);
SetPattern(nCol2, nRow, *pPat1, sal_True);
if( IsPooledItem( pPat1 ) ) pPat1->ReleaseRef();
}
}
}
}
void ScTable::SwapRow(SCROW nRow1, SCROW nRow2)
{
for (SCCOL nCol = aSortParam.nCol1; nCol <= aSortParam.nCol2; nCol++)
{
aCol[nCol].SwapRow(nRow1, nRow2);
if (aSortParam.bIncludePattern)
{
const ScPatternAttr* pPat1 = GetPattern(nCol, nRow1);
const ScPatternAttr* pPat2 = GetPattern(nCol, nRow2);
if (pPat1 != pPat2)
{
//Add Reference to avoid pPat1 to be deleted by merge same cell attributes for adjacent cells
if( IsPooledItem( pPat1 ) ) pPat1->AddRef();
SetPattern(nCol, nRow1, *pPat2, sal_True);
SetPattern(nCol, nRow2, *pPat1, sal_True);
if( IsPooledItem( pPat1 ) ) pPat1->ReleaseRef();
}
}
}
if (bGlobalKeepQuery)
{
bool bRow1Hidden = RowHidden(nRow1);
bool bRow2Hidden = RowHidden(nRow2);
SetRowHidden(nRow1, nRow1, bRow2Hidden);
SetRowHidden(nRow2, nRow2, bRow1Hidden);
bool bRow1Filtered = RowFiltered(nRow1);
bool bRow2Filtered = RowFiltered(nRow2);
SetRowFiltered(nRow1, nRow1, bRow2Filtered);
SetRowFiltered(nRow2, nRow2, bRow1Filtered);
}
}
short ScTable::Compare(SCCOLROW nIndex1, SCCOLROW nIndex2)
{
short nRes;
sal_uInt16 nSort = 0;
if (aSortParam.bByRow)
{
do
{
SCCOL nCol = static_cast<SCCOL>(aSortParam.nField[nSort]);
ScBaseCell* pCell1 = aCol[nCol].GetCell( nIndex1 );
ScBaseCell* pCell2 = aCol[nCol].GetCell( nIndex2 );
nRes = CompareCell( nSort, pCell1, nCol, nIndex1, pCell2, nCol, nIndex2 );
} while ( nRes == 0 && ++nSort < nMaxSorts && aSortParam.bDoSort[nSort] );
}
else
{
do
{
SCROW nRow = aSortParam.nField[nSort];
ScBaseCell* pCell1 = aCol[nIndex1].GetCell( nRow );
ScBaseCell* pCell2 = aCol[nIndex2].GetCell( nRow );
nRes = CompareCell( nSort, pCell1, static_cast<SCCOL>(nIndex1),
nRow, pCell2, static_cast<SCCOL>(nIndex2), nRow );
} while ( nRes == 0 && ++nSort < nMaxSorts && aSortParam.bDoSort[nSort] );
}
return nRes;
}
sal_Bool ScTable::IsSorted( SCCOLROW nStart, SCCOLROW nEnd ) // ueber aSortParam
{
for (SCCOLROW i=nStart; i<nEnd; i++)
{
if (Compare( i, i+1 ) > 0)
return sal_False;
}
return sal_True;
}
void ScTable::DecoladeRow( ScSortInfoArray* pArray, SCROW nRow1, SCROW nRow2 )
{
SCROW nRow;
SCROW nMax = nRow2 - nRow1;
for (SCROW i = nRow1; (i + 4) <= nRow2; i += 4)
{
nRow = rand() % nMax;
pArray->Swap(i, nRow1 + nRow);
}
}
void ScTable::Sort(const ScSortParam& rSortParam, sal_Bool bKeepQuery)
{
aSortParam = rSortParam;
InitSortCollator( rSortParam );
bGlobalKeepQuery = bKeepQuery;
if (rSortParam.bByRow)
{
SCROW nLastRow = 0;
for (SCCOL nCol = aSortParam.nCol1; nCol <= aSortParam.nCol2; nCol++)
nLastRow = Max(nLastRow, aCol[nCol].GetLastDataPos());
nLastRow = Min(nLastRow, aSortParam.nRow2);
SCROW nRow1 = (rSortParam.bHasHeader ?
aSortParam.nRow1 + 1 : aSortParam.nRow1);
if (!IsSorted(nRow1, nLastRow))
{
ScProgress aProgress( pDocument->GetDocumentShell(),
ScGlobal::GetRscString(STR_PROGRESS_SORTING), nLastRow - nRow1 );
ScSortInfoArray* pArray = CreateSortInfoArray( nRow1, nLastRow );
if ( nLastRow - nRow1 > 255 )
DecoladeRow( pArray, nRow1, nLastRow );
QuickSort( pArray, nRow1, nLastRow );
SortReorder( pArray, aProgress );
delete pArray;
// #158377# #i59745# update position of caption objects of cell notes
ScNoteUtil::UpdateCaptionPositions( *pDocument, ScRange( aSortParam.nCol1, nRow1, nTab, aSortParam.nCol2, nLastRow, nTab ) );
}
}
else
{
SCCOL nLastCol;
for (nLastCol = aSortParam.nCol2;
(nLastCol > aSortParam.nCol1) && aCol[nLastCol].IsEmptyBlock(aSortParam.nRow1, aSortParam.nRow2); nLastCol--)
{
}
SCCOL nCol1 = (rSortParam.bHasHeader ?
aSortParam.nCol1 + 1 : aSortParam.nCol1);
if (!IsSorted(nCol1, nLastCol))
{
ScProgress aProgress( pDocument->GetDocumentShell(),
ScGlobal::GetRscString(STR_PROGRESS_SORTING), nLastCol - nCol1 );
ScSortInfoArray* pArray = CreateSortInfoArray( nCol1, nLastCol );
QuickSort( pArray, nCol1, nLastCol );
SortReorder( pArray, aProgress );
delete pArray;
// #158377# #i59745# update position of caption objects of cell notes
ScNoteUtil::UpdateCaptionPositions( *pDocument, ScRange( nCol1, aSortParam.nRow1, nTab, nLastCol, aSortParam.nRow2, nTab ) );
}
}
DestroySortCollator();
}
// Testen, ob beim Loeschen von Zwischenergebnissen andere Daten mit geloescht werden
// (fuer Hinweis-Box)
sal_Bool ScTable::TestRemoveSubTotals( const ScSubTotalParam& rParam )
{
SCCOL nStartCol = rParam.nCol1;
SCROW nStartRow = rParam.nRow1 + 1; // Header
SCCOL nEndCol = rParam.nCol2;
SCROW nEndRow = rParam.nRow2;
SCCOL nCol;
SCROW nRow;
ScBaseCell* pCell;
sal_Bool bWillDelete = sal_False;
for ( nCol=nStartCol; nCol<=nEndCol && !bWillDelete; nCol++ )
{
ScColumnIterator aIter( &aCol[nCol],nStartRow,nEndRow );
while ( aIter.Next( nRow, pCell ) && !bWillDelete )
{
if ( pCell->GetCellType() == CELLTYPE_FORMULA )
if (((ScFormulaCell*)pCell)->IsSubTotal())
{
for (SCCOL nTestCol=0; nTestCol<=MAXCOL; nTestCol++)
if (nTestCol<nStartCol || nTestCol>nEndCol)
if (aCol[nTestCol].HasDataAt(nRow))
bWillDelete = sal_True;
}
}
}
return bWillDelete;
}
// alte Ergebnisse loeschen
// rParam.nRow2 wird veraendert !
void ScTable::RemoveSubTotals( ScSubTotalParam& rParam )
{
SCCOL nStartCol = rParam.nCol1;
SCROW nStartRow = rParam.nRow1 + 1; // Header
SCCOL nEndCol = rParam.nCol2;
SCROW nEndRow = rParam.nRow2; // wird veraendert
SCCOL nCol;
SCROW nRow;
ScBaseCell* pCell;
for ( nCol=nStartCol; nCol<=nEndCol; nCol++ )
{
ScColumnIterator aIter( &aCol[nCol],nStartRow,nEndRow );
while ( aIter.Next( nRow, pCell ) )
{
if ( pCell->GetCellType() == CELLTYPE_FORMULA )
if (((ScFormulaCell*)pCell)->IsSubTotal())
{
RemoveRowBreak(nRow+1, false, true);
pDocument->DeleteRow( 0,nTab, MAXCOL,nTab, nRow, 1 );
--nEndRow;
aIter = ScColumnIterator( &aCol[nCol],nRow,nEndRow );
}
}
}
rParam.nRow2 = nEndRow; // neues Ende
}
// harte Zahlenformate loeschen (fuer Ergebnisformeln)
void lcl_RemoveNumberFormat( ScTable* pTab, SCCOL nCol, SCROW nRow )
{
const ScPatternAttr* pPattern = pTab->GetPattern( nCol, nRow );
if ( pPattern->GetItemSet().GetItemState( ATTR_VALUE_FORMAT, sal_False )
== SFX_ITEM_SET )
{
ScPatternAttr aNewPattern( *pPattern );
SfxItemSet& rSet = aNewPattern.GetItemSet();
rSet.ClearItem( ATTR_VALUE_FORMAT );
rSet.ClearItem( ATTR_LANGUAGE_FORMAT );
pTab->SetPattern( nCol, nRow, aNewPattern, sal_True );
}
}
// at least MSC needs this at linkage level to be able to use it in a template
typedef struct lcl_ScTable_DoSubTotals_RowEntry
{
sal_uInt16 nGroupNo;
SCROW nSubStartRow;
SCROW nDestRow;
SCROW nFuncStart;
SCROW nFuncEnd;
} RowEntry;
// neue Zwischenergebnisse
// rParam.nRow2 wird veraendert !
sal_Bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
{
SCCOL nStartCol = rParam.nCol1;
SCROW nStartRow = rParam.nRow1 + 1; // Header
SCCOL nEndCol = rParam.nCol2;
SCROW nEndRow = rParam.nRow2; // wird veraendert
sal_uInt16 i;
// Leerzeilen am Ende weglassen,
// damit alle Ueberlaeufe (MAXROW) bei InsertRow gefunden werden (#35180#)
// Wenn sortiert wurde, sind alle Leerzeilen am Ende.
SCSIZE nEmpty = GetEmptyLinesInBlock( nStartCol, nStartRow, nEndCol, nEndRow, DIR_BOTTOM );
nEndRow -= nEmpty;
sal_uInt16 nLevelCount = 0; // Anzahl Gruppierungen
sal_Bool bDoThis = sal_True;
for (i=0; i<MAXSUBTOTAL && bDoThis; i++)
if (rParam.bGroupActive[i])
nLevelCount = i+1;
else
bDoThis = sal_False;
if (nLevelCount==0) // nichts tun
return sal_True;
SCCOL* nGroupCol = rParam.nField; // Spalten nach denen
// gruppiert wird
// #44444# Durch (leer) als eigene Kategorie muss immer auf
// Teilergebniszeilen aus den anderen Spalten getestet werden
// (frueher nur, wenn eine Spalte mehrfach vorkam)
sal_Bool bTestPrevSub = ( nLevelCount > 1 );
String aSubString;
String aOutString;
sal_Bool bIgnoreCase = !rParam.bCaseSens;
String *pCompString[MAXSUBTOTAL]; // Pointer wegen Compiler-Problemen
for (i=0; i<MAXSUBTOTAL; i++)
pCompString[i] = new String;
//! sortieren?
ScStyleSheet* pStyle = (ScStyleSheet*) pDocument->GetStyleSheetPool()->Find(
ScGlobal::GetRscString(STR_STYLENAME_RESULT), SFX_STYLE_FAMILY_PARA );
sal_Bool bSpaceLeft = sal_True; // Erfolg beim Einfuegen?
// #90279# For performance reasons collect formula entries so their
// references don't have to be tested for updates each time a new row is
// inserted
RowEntry aRowEntry;
::std::vector< RowEntry > aRowVector;
for (sal_uInt16 nLevel=0; nLevel<=nLevelCount && bSpaceLeft; nLevel++) // incl. Gesamtergebnis
{
sal_Bool bTotal = ( nLevel == nLevelCount );
aRowEntry.nGroupNo = bTotal ? 0 : (nLevelCount-nLevel-1);
// how many results per level
SCCOL nResCount = rParam.nSubTotals[aRowEntry.nGroupNo];
// result functions
ScSubTotalFunc* eResFunc = rParam.pFunctions[aRowEntry.nGroupNo];
if (nResCount > 0) // sonst nur sortieren
{
for (i=0; i<=aRowEntry.nGroupNo; i++)
{
GetString( nGroupCol[i], nStartRow, aSubString );
if ( bIgnoreCase )
*pCompString[i] = ScGlobal::pCharClass->upper( aSubString );
else
*pCompString[i] = aSubString;
} // aSubString bleibt auf dem letzten stehen
sal_Bool bBlockVis = sal_False; // Gruppe eingeblendet?
aRowEntry.nSubStartRow = nStartRow;
for (SCROW nRow=nStartRow; nRow<=nEndRow+1 && bSpaceLeft; nRow++)
{
sal_Bool bChanged;
if (nRow>nEndRow)
bChanged = sal_True;
else
{
bChanged = sal_False;
if (!bTotal)
{
String aString;
for (i=0; i<=aRowEntry.nGroupNo && !bChanged; i++)
{
GetString( nGroupCol[i], nRow, aString );
if (bIgnoreCase)
ScGlobal::pCharClass->toUpper( aString );
// #41427# wenn sortiert, ist "leer" eine eigene Gruppe
// sonst sind leere Zellen unten erlaubt
bChanged = ( ( aString.Len() || rParam.bDoSort ) &&
aString != *pCompString[i] );
}
if ( bChanged && bTestPrevSub )
{
// No group change on rows that will contain subtotal formulas
for ( ::std::vector< RowEntry >::const_iterator
iEntry( aRowVector.begin());
iEntry != aRowVector.end(); ++iEntry)
{
if ( iEntry->nDestRow == nRow )
{
bChanged = sal_False;
break;
}
}
}
}
}
if ( bChanged )
{
aRowEntry.nDestRow = nRow;
aRowEntry.nFuncStart = aRowEntry.nSubStartRow;
aRowEntry.nFuncEnd = nRow-1;
bSpaceLeft = pDocument->InsertRow( 0, nTab, MAXCOL, nTab,
aRowEntry.nDestRow, 1 );
DBShowRow( aRowEntry.nDestRow, bBlockVis );
bBlockVis = sal_False;
if ( rParam.bPagebreak && nRow < MAXROW &&
aRowEntry.nSubStartRow != nStartRow && nLevel == 0)
SetRowBreak(aRowEntry.nSubStartRow, false, true);
if (bSpaceLeft)
{
for ( ::std::vector< RowEntry >::iterator iMove(
aRowVector.begin() );
iMove != aRowVector.end(); ++iMove)
{
if ( aRowEntry.nDestRow <= iMove->nSubStartRow )
++iMove->nSubStartRow;
if ( aRowEntry.nDestRow <= iMove->nDestRow )
++iMove->nDestRow;
if ( aRowEntry.nDestRow <= iMove->nFuncStart )
++iMove->nFuncStart;
if ( aRowEntry.nDestRow <= iMove->nFuncEnd )
++iMove->nFuncEnd;
}
// collect formula positions
aRowVector.push_back( aRowEntry );
if (bTotal) // "Gesamtergebnis"
aOutString = ScGlobal::GetRscString( STR_TABLE_GESAMTERGEBNIS );
else
{ // " Ergebnis"
aOutString = aSubString;
if (!aOutString.Len())
aOutString = ScGlobal::GetRscString( STR_EMPTYDATA );
aOutString += ' ';
sal_uInt16 nStrId = STR_TABLE_ERGEBNIS;
if ( nResCount == 1 )
switch ( eResFunc[0] )
{
case SUBTOTAL_FUNC_AVE: nStrId = STR_FUN_TEXT_AVG; break;
case SUBTOTAL_FUNC_CNT:
case SUBTOTAL_FUNC_CNT2: nStrId = STR_FUN_TEXT_COUNT; break;
case SUBTOTAL_FUNC_MAX: nStrId = STR_FUN_TEXT_MAX; break;
case SUBTOTAL_FUNC_MIN: nStrId = STR_FUN_TEXT_MIN; break;
case SUBTOTAL_FUNC_PROD: nStrId = STR_FUN_TEXT_PRODUCT; break;
case SUBTOTAL_FUNC_STD:
case SUBTOTAL_FUNC_STDP: nStrId = STR_FUN_TEXT_STDDEV; break;
case SUBTOTAL_FUNC_SUM: nStrId = STR_FUN_TEXT_SUM; break;
case SUBTOTAL_FUNC_VAR:
case SUBTOTAL_FUNC_VARP: nStrId = STR_FUN_TEXT_VAR; break;
default:
{
// added to avoid warnings
}
}
aOutString += ScGlobal::GetRscString( nStrId );
}
SetString( nGroupCol[aRowEntry.nGroupNo], aRowEntry.nDestRow, nTab, aOutString );
ApplyStyle( nGroupCol[aRowEntry.nGroupNo], aRowEntry.nDestRow, *pStyle );
++nRow;
++nEndRow;
aRowEntry.nSubStartRow = nRow;
for (i=0; i<=aRowEntry.nGroupNo; i++)
{
GetString( nGroupCol[i], nRow, aSubString );
if ( bIgnoreCase )
*pCompString[i] = ScGlobal::pCharClass->upper( aSubString );
else
*pCompString[i] = aSubString;
}
}
}
bBlockVis = !RowFiltered(nRow);
}
}
else
{
// DBG_ERROR( "nSubTotals==0 bei DoSubTotals" );
}
}
// now insert the formulas
ScComplexRefData aRef;
aRef.InitFlags();
aRef.Ref1.nTab = nTab;
aRef.Ref2.nTab = nTab;
for ( ::std::vector< RowEntry >::const_iterator iEntry( aRowVector.begin());
iEntry != aRowVector.end(); ++iEntry)
{
SCCOL nResCount = rParam.nSubTotals[iEntry->nGroupNo];
SCCOL* nResCols = rParam.pSubTotals[iEntry->nGroupNo];
ScSubTotalFunc* eResFunc = rParam.pFunctions[iEntry->nGroupNo];
for ( SCCOL nResult=0; nResult < nResCount; ++nResult )
{
aRef.Ref1.nCol = nResCols[nResult];
aRef.Ref1.nRow = iEntry->nFuncStart;
aRef.Ref2.nCol = nResCols[nResult];
aRef.Ref2.nRow = iEntry->nFuncEnd;
ScTokenArray aArr;
aArr.AddOpCode( ocSubTotal );
aArr.AddOpCode( ocOpen );
aArr.AddDouble( (double) eResFunc[nResult] );
aArr.AddOpCode( ocSep );
aArr.AddDoubleReference( aRef );
aArr.AddOpCode( ocClose );
aArr.AddOpCode( ocStop );
ScBaseCell* pCell = new ScFormulaCell( pDocument, ScAddress(
nResCols[nResult], iEntry->nDestRow, nTab), &aArr );
PutCell( nResCols[nResult], iEntry->nDestRow, pCell );
if ( nResCols[nResult] != nGroupCol[iEntry->nGroupNo] )
{
ApplyStyle( nResCols[nResult], iEntry->nDestRow, *pStyle );
// Zahlformat loeschen
lcl_RemoveNumberFormat( this, nResCols[nResult], iEntry->nDestRow );
}
}
}
//! je nach Einstellung Zwischensummen-Zeilen nach oben verschieben ?
//! Outlines direkt erzeugen?
if (bSpaceLeft)
DoAutoOutline( nStartCol, nStartRow, nEndCol, nEndRow );
for (i=0; i<MAXSUBTOTAL; i++)
delete pCompString[i];
rParam.nRow2 = nEndRow; // neues Ende
return bSpaceLeft;
}
sal_Bool ScTable::ValidQuery(SCROW nRow, const ScQueryParam& rParam,
sal_Bool* pSpecial /* =NULL */ , ScBaseCell* pCell /* =NULL */ ,
sal_Bool* pbTestEqualCondition /* = NULL */ )
{
if (!rParam.GetEntry(0).bDoQuery)
return sal_True;
//---------------------------------------------------------------
const SCSIZE nFixedBools = 32;
sal_Bool aBool[nFixedBools];
sal_Bool aTest[nFixedBools];
SCSIZE nEntryCount = rParam.GetEntryCount();
sal_Bool* pPasst = ( nEntryCount <= nFixedBools ? &aBool[0] : new sal_Bool[nEntryCount] );
sal_Bool* pTest = ( nEntryCount <= nFixedBools ? &aTest[0] : new sal_Bool[nEntryCount] );
long nPos = -1;
SCSIZE i = 0;
sal_Bool bMatchWholeCell = pDocument->GetDocOptions().IsMatchWholeCell();
CollatorWrapper* pCollator = (rParam.bCaseSens ? ScGlobal::GetCaseCollator() :
ScGlobal::GetCollator());
::utl::TransliterationWrapper* pTransliteration = (rParam.bCaseSens ?
ScGlobal::GetCaseTransliteration() : ScGlobal::GetpTransliteration());
while ( (i < nEntryCount) && rParam.GetEntry(i).bDoQuery )
{
ScQueryEntry& rEntry = rParam.GetEntry(i);
// we can only handle one single direct query
if ( !pCell || i > 0 )
pCell = GetCell( static_cast<SCCOL>(rEntry.nField), nRow );
sal_Bool bOk = sal_False;
sal_Bool bTestEqual = sal_False;
if ( pSpecial && pSpecial[i] )
{
if (rEntry.nVal == SC_EMPTYFIELDS)
bOk = !( aCol[rEntry.nField].HasDataAt( nRow ) );
else // if (rEntry.nVal == SC_NONEMPTYFIELDS)
bOk = aCol[rEntry.nField].HasDataAt( nRow );
}
else if ( !rEntry.bQueryByString && (pCell ? pCell->HasValueData() :
HasValueData( static_cast<SCCOL>(rEntry.nField), nRow)))
{ // by Value
double nCellVal;
if ( pCell )
{
switch ( pCell->GetCellType() )
{
case CELLTYPE_VALUE :
nCellVal = ((ScValueCell*)pCell)->GetValue();
break;
case CELLTYPE_FORMULA :
nCellVal = ((ScFormulaCell*)pCell)->GetValue();
break;
default:
nCellVal = 0.0;
}
}
else
nCellVal = GetValue( static_cast<SCCOL>(rEntry.nField), nRow );
/* NOTE: lcl_PrepareQuery() prepares a filter query such that if a
* date+time format was queried rEntry.bQueryByDate is not set. In
* case other queries wanted to use this mechanism they should do
* the same, in other words only if rEntry.nVal is an integer value
* rEntry.bQueryByDate should be true and the time fraction be
* stripped here. */
if (rEntry.bQueryByDate)
{
sal_uInt32 nNumFmt = GetNumberFormat(static_cast<SCCOL>(rEntry.nField), nRow);
const SvNumberformat* pEntry = pDocument->GetFormatTable()->GetEntry(nNumFmt);
if (pEntry)
{
short nNumFmtType = pEntry->GetType();
/* NOTE: Omitting the check for absence of
* NUMBERFORMAT_TIME would include also date+time formatted
* values of the same day. That may be desired in some
* cases, querying all time values of a day, but confusing
* in other cases. A user can always setup a standard
* filter query for x >= date AND x < date+1 */
if ((nNumFmtType & NUMBERFORMAT_DATE) && !(nNumFmtType & NUMBERFORMAT_TIME))
{
// The format is of date type. Strip off the time
// element.
nCellVal = ::rtl::math::approxFloor(nCellVal);
}
}
}
switch (rEntry.eOp)
{
case SC_EQUAL :
bOk = ::rtl::math::approxEqual( nCellVal, rEntry.nVal );
break;
case SC_LESS :
bOk = (nCellVal < rEntry.nVal) && !::rtl::math::approxEqual( nCellVal, rEntry.nVal );
break;
case SC_GREATER :
bOk = (nCellVal > rEntry.nVal) && !::rtl::math::approxEqual( nCellVal, rEntry.nVal );
break;
case SC_LESS_EQUAL :
bOk = (nCellVal < rEntry.nVal) || ::rtl::math::approxEqual( nCellVal, rEntry.nVal );
if ( bOk && pbTestEqualCondition )
bTestEqual = ::rtl::math::approxEqual( nCellVal, rEntry.nVal );
break;
case SC_GREATER_EQUAL :
bOk = (nCellVal > rEntry.nVal) || ::rtl::math::approxEqual( nCellVal, rEntry.nVal );
if ( bOk && pbTestEqualCondition )
bTestEqual = ::rtl::math::approxEqual( nCellVal, rEntry.nVal );
break;
case SC_NOT_EQUAL :
bOk = !::rtl::math::approxEqual( nCellVal, rEntry.nVal );
break;
default:
{
// added to avoid warnings
}
}
}
else if ( (rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL) ||
(rEntry.eOp == SC_CONTAINS || rEntry.eOp == SC_DOES_NOT_CONTAIN ||
rEntry.eOp == SC_BEGINS_WITH || rEntry.eOp == SC_ENDS_WITH ||
rEntry.eOp == SC_DOES_NOT_BEGIN_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH) ||
(rEntry.bQueryByString && (pCell ? pCell->HasStringData() :
HasStringData(
static_cast<SCCOL>(rEntry.nField),
nRow))))
{ // by String
String aCellStr;
if( rEntry.eOp == SC_CONTAINS || rEntry.eOp == SC_DOES_NOT_CONTAIN
|| rEntry.eOp == SC_BEGINS_WITH || rEntry.eOp == SC_ENDS_WITH
|| rEntry.eOp == SC_DOES_NOT_BEGIN_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH )
bMatchWholeCell = sal_False;
if ( pCell )
{
if (pCell->GetCellType() != CELLTYPE_NOTE)
{
sal_uLong nFormat = GetNumberFormat( static_cast<SCCOL>(rEntry.nField), nRow );
ScCellFormat::GetInputString( pCell, nFormat, aCellStr, *(pDocument->GetFormatTable()) );
}
}
else
GetInputString( static_cast<SCCOL>(rEntry.nField), nRow, aCellStr );
sal_Bool bRealRegExp = (rParam.bRegExp && ((rEntry.eOp == SC_EQUAL)
|| (rEntry.eOp == SC_NOT_EQUAL) || (rEntry.eOp == SC_CONTAINS)
|| (rEntry.eOp == SC_DOES_NOT_CONTAIN) || (rEntry.eOp == SC_BEGINS_WITH)
|| (rEntry.eOp == SC_ENDS_WITH) || (rEntry.eOp == SC_DOES_NOT_BEGIN_WITH)
|| (rEntry.eOp == SC_DOES_NOT_END_WITH)));
sal_Bool bTestRegExp = (pbTestEqualCondition && rParam.bRegExp
&& ((rEntry.eOp == SC_LESS_EQUAL)
|| (rEntry.eOp == SC_GREATER_EQUAL)));
if ( bRealRegExp || bTestRegExp )
{
xub_StrLen nStart = 0;
xub_StrLen nEnd = aCellStr.Len();
// from 614 on, nEnd is behind the found text
sal_Bool bMatch = sal_False;
if ( rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH )
{
nEnd = 0;
nStart = aCellStr.Len();
bMatch = (sal_Bool) rEntry.GetSearchTextPtr( rParam.bCaseSens )
->SearchBkwrd( aCellStr, &nStart, &nEnd );
}
else
{
bMatch = (sal_Bool) rEntry.GetSearchTextPtr( rParam.bCaseSens )
->SearchFrwrd( aCellStr, &nStart, &nEnd );
}
if ( bMatch && bMatchWholeCell
&& (nStart != 0 || nEnd != aCellStr.Len()) )
bMatch = sal_False; // RegExp must match entire cell string
if ( bRealRegExp )
switch (rEntry.eOp)
{
case SC_EQUAL:
case SC_CONTAINS:
bOk = bMatch;
break;
case SC_NOT_EQUAL:
case SC_DOES_NOT_CONTAIN:
bOk = !bMatch;
break;
case SC_BEGINS_WITH:
bOk = ( bMatch && (nStart == 0) );
break;
case SC_DOES_NOT_BEGIN_WITH:
bOk = !( bMatch && (nStart == 0) );
break;
case SC_ENDS_WITH:
bOk = ( bMatch && (nEnd == aCellStr.Len()) );
break;
case SC_DOES_NOT_END_WITH:
bOk = !( bMatch && (nEnd == aCellStr.Len()) );
break;
default:
{
// added to avoid warnings
}
}
else
bTestEqual = bMatch;
}
if ( !bRealRegExp )
{
if ( rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL
|| rEntry.eOp == SC_CONTAINS || rEntry.eOp == SC_DOES_NOT_CONTAIN
|| rEntry.eOp == SC_BEGINS_WITH || rEntry.eOp == SC_ENDS_WITH
|| rEntry.eOp == SC_DOES_NOT_BEGIN_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH )
{
if ( !rEntry.bQueryByString && rEntry.pStr->Len() == 0 )
{
// #i18374# When used from functions (match, countif, sumif, vlookup, hlookup, lookup),
// the query value is assigned directly, and the string is empty. In that case,
// don't find any string (isEqual would find empty string results in formula cells).
bOk = sal_False;
if ( rEntry.eOp == SC_NOT_EQUAL )
bOk = !bOk;
}
else if ( bMatchWholeCell )
{
bOk = pTransliteration->isEqual( aCellStr, *rEntry.pStr );
if ( rEntry.eOp == SC_NOT_EQUAL )
bOk = !bOk;
}
else
{
String aCell( pTransliteration->transliterate(
aCellStr, ScGlobal::eLnge, 0, aCellStr.Len(),
NULL ) );
String aQuer( pTransliteration->transliterate(
*rEntry.pStr, ScGlobal::eLnge, 0, rEntry.pStr->Len(),
NULL ) );
xub_StrLen nIndex = (rEntry.eOp == SC_ENDS_WITH
|| rEntry.eOp == SC_DOES_NOT_END_WITH)? (aCell.Len()-aQuer.Len()):0;
xub_StrLen nStrPos = aCell.Search( aQuer, nIndex );
switch (rEntry.eOp)
{
case SC_EQUAL:
case SC_CONTAINS:
bOk = ( nStrPos != STRING_NOTFOUND );
break;
case SC_NOT_EQUAL:
case SC_DOES_NOT_CONTAIN:
bOk = ( nStrPos == STRING_NOTFOUND );
break;
case SC_BEGINS_WITH:
bOk = ( nStrPos == 0 );
break;
case SC_DOES_NOT_BEGIN_WITH:
bOk = ( nStrPos != 0 );
break;
case SC_ENDS_WITH:
bOk = ( nStrPos + aQuer.Len() == aCell.Len() );
break;
case SC_DOES_NOT_END_WITH:
bOk = ( nStrPos + aQuer.Len() != aCell.Len() );
break;
default:
{
// added to avoid warnings
}
}
}
}
else
{ // use collator here because data was probably sorted
sal_Int32 nCompare = pCollator->compareString(
aCellStr, *rEntry.pStr );
switch (rEntry.eOp)
{
case SC_LESS :
bOk = (nCompare < 0);
break;
case SC_GREATER :
bOk = (nCompare > 0);
break;
case SC_LESS_EQUAL :
bOk = (nCompare <= 0);
if ( bOk && pbTestEqualCondition && !bTestEqual )
bTestEqual = (nCompare == 0);
break;
case SC_GREATER_EQUAL :
bOk = (nCompare >= 0);
if ( bOk && pbTestEqualCondition && !bTestEqual )
bTestEqual = (nCompare == 0);
break;
default:
{
// added to avoid warnings
}
}
}
}
}
else if (rParam.bMixedComparison)
{
if (rEntry.bQueryByString &&
(rEntry.eOp == SC_LESS || rEntry.eOp == SC_LESS_EQUAL) &&
(pCell ? pCell->HasValueData() :
HasValueData( static_cast<SCCOL>(rEntry.nField), nRow)))
{
bOk = sal_True;
}
else if (!rEntry.bQueryByString &&
(rEntry.eOp == SC_GREATER || rEntry.eOp == SC_GREATER_EQUAL) &&
(pCell ? pCell->HasStringData() :
HasStringData( static_cast<SCCOL>(rEntry.nField), nRow)))
{
bOk = sal_True;
}
}
if (nPos == -1)
{
nPos++;
pPasst[nPos] = bOk;
pTest[nPos] = bTestEqual;
}
else
{
if (rEntry.eConnect == SC_AND)
{
pPasst[nPos] = pPasst[nPos] && bOk;
pTest[nPos] = pTest[nPos] && bTestEqual;
}
else
{
nPos++;
pPasst[nPos] = bOk;
pTest[nPos] = bTestEqual;
}
}
i++;
}
for ( long j=1; j <= nPos; j++ )
{
pPasst[0] = pPasst[0] || pPasst[j];
pTest[0] = pTest[0] || pTest[j];
}
sal_Bool bRet = pPasst[0];
if ( pPasst != &aBool[0] )
delete [] pPasst;
if ( pbTestEqualCondition )
*pbTestEqualCondition = pTest[0];
if ( pTest != &aTest[0] )
delete [] pTest;
return bRet;
}
void ScTable::TopTenQuery( ScQueryParam& rParam )
{
sal_Bool bSortCollatorInitialized = sal_False;
SCSIZE nEntryCount = rParam.GetEntryCount();
SCROW nRow1 = (rParam.bHasHeader ? rParam.nRow1 + 1 : rParam.nRow1);
SCSIZE nCount = static_cast<SCSIZE>(rParam.nRow2 - nRow1 + 1);
for ( SCSIZE i=0; (i<nEntryCount) && (rParam.GetEntry(i).bDoQuery); i++ )
{
ScQueryEntry& rEntry = rParam.GetEntry(i);
switch ( rEntry.eOp )
{
case SC_TOPVAL:
case SC_BOTVAL:
case SC_TOPPERC:
case SC_BOTPERC:
{
ScSortParam aLocalSortParam( rParam, static_cast<SCCOL>(rEntry.nField) );
aSortParam = aLocalSortParam; // used in CreateSortInfoArray, Compare
if ( !bSortCollatorInitialized )
{
bSortCollatorInitialized = sal_True;
InitSortCollator( aLocalSortParam );
}
ScSortInfoArray* pArray = CreateSortInfoArray( nRow1, rParam.nRow2 );
DecoladeRow( pArray, nRow1, rParam.nRow2 );
QuickSort( pArray, nRow1, rParam.nRow2 );
ScSortInfo** ppInfo = pArray->GetFirstArray();
SCSIZE nValidCount = nCount;
// keine Note-/Leerzellen zaehlen, sind ans Ende sortiert
while ( nValidCount > 0 && ( ppInfo[nValidCount-1]->pCell == NULL ||
ppInfo[nValidCount-1]->pCell->GetCellType() == CELLTYPE_NOTE ) )
nValidCount--;
// keine Strings zaehlen, sind zwischen Value und Leer
while ( nValidCount > 0
&& ppInfo[nValidCount-1]->pCell->HasStringData() )
nValidCount--;
if ( nValidCount > 0 )
{
if ( rEntry.bQueryByString )
{ // dat wird nix
rEntry.bQueryByString = sal_False;
rEntry.nVal = 10; // 10 bzw. 10%
}
SCSIZE nVal = (rEntry.nVal >= 1 ? static_cast<SCSIZE>(rEntry.nVal) : 1);
SCSIZE nOffset = 0;
switch ( rEntry.eOp )
{
case SC_TOPVAL:
{
rEntry.eOp = SC_GREATER_EQUAL;
if ( nVal > nValidCount )
nVal = nValidCount;
nOffset = nValidCount - nVal; // 1 <= nVal <= nValidCount
}
break;
case SC_BOTVAL:
{
rEntry.eOp = SC_LESS_EQUAL;
if ( nVal > nValidCount )
nVal = nValidCount;
nOffset = nVal - 1; // 1 <= nVal <= nValidCount
}
break;
case SC_TOPPERC:
{
rEntry.eOp = SC_GREATER_EQUAL;
if ( nVal > 100 )
nVal = 100;
nOffset = nValidCount - (nValidCount * nVal / 100);
if ( nOffset >= nValidCount )
nOffset = nValidCount - 1;
}
break;
case SC_BOTPERC:
{
rEntry.eOp = SC_LESS_EQUAL;
if ( nVal > 100 )
nVal = 100;
nOffset = (nValidCount * nVal / 100);
if ( nOffset >= nValidCount )
nOffset = nValidCount - 1;
}
break;
default:
{
// added to avoid warnings
}
}
ScBaseCell* pCell = ppInfo[nOffset]->pCell;
if ( pCell->HasValueData() )
{
if ( pCell->GetCellType() == CELLTYPE_VALUE )
rEntry.nVal = ((ScValueCell*)pCell)->GetValue();
else
rEntry.nVal = ((ScFormulaCell*)pCell)->GetValue();
}
else
{
DBG_ERRORFILE( "TopTenQuery: pCell kein ValueData" );
rEntry.eOp = SC_GREATER_EQUAL;
rEntry.nVal = 0;
}
}
else
{
rEntry.eOp = SC_GREATER_EQUAL;
rEntry.bQueryByString = sal_False;
rEntry.nVal = 0;
}
delete pArray;
}
break;
default:
{
// added to avoid warnings
}
}
}
if ( bSortCollatorInitialized )
DestroySortCollator();
}
static void lcl_PrepareQuery( ScDocument* pDoc, ScTable* pTab, ScQueryParam& rParam, sal_Bool* pSpecial )
{
bool bTopTen = false;
SCSIZE nEntryCount = rParam.GetEntryCount();
for ( SCSIZE i = 0; i < nEntryCount; ++i )
{
pSpecial[i] = sal_False;
ScQueryEntry& rEntry = rParam.GetEntry(i);
if ( rEntry.bDoQuery )
{
if ( rEntry.bQueryByString )
{
sal_uInt32 nIndex = 0;
rEntry.bQueryByString = !( pDoc->GetFormatTable()->
IsNumberFormat( *rEntry.pStr, nIndex, rEntry.nVal ) );
if (rEntry.bQueryByDate)
{
if (!rEntry.bQueryByString && ((nIndex % SV_COUNTRY_LANGUAGE_OFFSET) != 0))
{
const SvNumberformat* pEntry = pDoc->GetFormatTable()->GetEntry(nIndex);
if (pEntry)
{
short nNumFmtType = pEntry->GetType();
if (!((nNumFmtType & NUMBERFORMAT_DATE) && !(nNumFmtType & NUMBERFORMAT_TIME)))
rEntry.bQueryByDate = false; // not a date only
}
else
rEntry.bQueryByDate = false; // what the ... not a date
}
else
rEntry.bQueryByDate = false; // not a date
}
}
else
{
// #58736# call from UNO or second call from autofilter
if ( rEntry.nVal == SC_EMPTYFIELDS || rEntry.nVal == SC_NONEMPTYFIELDS )
{
pSpecial[i] = sal_True;
}
}
if ( !bTopTen )
{
switch ( rEntry.eOp )
{
case SC_TOPVAL:
case SC_BOTVAL:
case SC_TOPPERC:
case SC_BOTPERC:
{
bTopTen = true;
}
break;
default:
{
}
}
}
}
}
if ( bTopTen )
{
pTab->TopTenQuery( rParam );
}
}
SCSIZE ScTable::Query(ScQueryParam& rParamOrg, sal_Bool bKeepSub)
{
ScQueryParam aParam( rParamOrg );
ScStrCollection aScStrCollection;
StrData* pStrData = NULL;
sal_Bool bStarted = sal_False;
sal_Bool bOldResult = sal_True;
SCROW nOldStart = 0;
SCROW nOldEnd = 0;
SCSIZE nCount = 0;
SCROW nOutRow = 0;
SCROW nHeader = aParam.bHasHeader ? 1 : 0;
SCSIZE nEntryCount = aParam.GetEntryCount();
sal_Bool* pSpecial = new sal_Bool[nEntryCount];
lcl_PrepareQuery( pDocument, this, aParam, pSpecial );
if (!aParam.bInplace)
{
nOutRow = aParam.nDestRow + nHeader;
if (nHeader > 0)
CopyData( aParam.nCol1, aParam.nRow1, aParam.nCol2, aParam.nRow1,
aParam.nDestCol, aParam.nDestRow, aParam.nDestTab );
}
if (aParam.bInplace)
IncRecalcLevel(); // #i116164# once for all entries
// #i116164# If there are no drawing objects within the area, call SetRowHidden/SetRowFiltered for all rows at the end
std::vector<ScShowRowsEntry> aEntries;
ScDrawLayer* pDrawLayer = pDocument->GetDrawLayer();
bool bHasObjects = pDrawLayer && pDrawLayer->HasObjectsInRows( nTab, aParam.nRow1 + nHeader, aParam.nRow2, false );
for (SCROW j=aParam.nRow1 + nHeader; j<=aParam.nRow2; j++)
{
sal_Bool bResult; // Filterergebnis
sal_Bool bValid = ValidQuery(j, aParam, pSpecial);
if (!bValid && bKeepSub) // Subtotals stehenlassen
{
for (SCCOL nCol=aParam.nCol1; nCol<=aParam.nCol2 && !bValid; nCol++)
{
ScBaseCell* pCell;
pCell = GetCell( nCol, j );
if ( pCell )
if ( pCell->GetCellType() == CELLTYPE_FORMULA )
if (((ScFormulaCell*)pCell)->IsSubTotal())
if (RefVisible((ScFormulaCell*)pCell))
bValid = sal_True;
}
}
if (bValid)
{
if (aParam.bDuplicate)
bResult = sal_True;
else
{
String aStr;
for (SCCOL k=aParam.nCol1; k <= aParam.nCol2; k++)
{
String aCellStr;
GetString(k, j, aCellStr);
aStr += aCellStr;
aStr += (sal_Unicode)1;
}
pStrData = new StrData(aStr);
sal_Bool bIsUnique = sal_True;
if (pStrData)
bIsUnique = aScStrCollection.Insert(pStrData);
if (bIsUnique)
bResult = sal_True;
else
{
delete pStrData;
bResult = sal_False;
}
}
}
else
bResult = sal_False;
if (aParam.bInplace)
{
if (bResult == bOldResult && bStarted)
nOldEnd = j;
else
{
if (bStarted)
{
DBShowRows(nOldStart,nOldEnd, bOldResult, bHasObjects);
if (!bHasObjects)
aEntries.push_back(ScShowRowsEntry(nOldStart, nOldEnd, bOldResult));
}
nOldStart = nOldEnd = j;
bOldResult = bResult;
}
bStarted = sal_True;
}
else
{
if (bResult)
{
CopyData( aParam.nCol1,j, aParam.nCol2,j, aParam.nDestCol,nOutRow,aParam.nDestTab );
++nOutRow;
}
}
if (bResult)
++nCount;
}
if (aParam.bInplace && bStarted)
{
DBShowRows(nOldStart,nOldEnd, bOldResult, bHasObjects);
if (!bHasObjects)
aEntries.push_back(ScShowRowsEntry(nOldStart, nOldEnd, bOldResult));
}
// #i116164# execute the collected SetRowHidden/SetRowFiltered calls
if (!bHasObjects)
{
std::vector<ScShowRowsEntry>::const_iterator aEnd = aEntries.end();
std::vector<ScShowRowsEntry>::const_iterator aIter = aEntries.begin();
if ( aIter != aEnd )
{
// do only one HeightChanged call with the final difference in heights
long nOldHeight = 0;
if ( pDrawLayer )
nOldHeight = static_cast<long>(GetRowHeight(aParam.nRow1 + nHeader, aParam.nRow2));
// clear the range first instead of many changes in the middle of the filled array
SetRowHidden(aParam.nRow1 + nHeader, aParam.nRow2, false);
SetRowFiltered(aParam.nRow1 + nHeader, aParam.nRow2, false);
// insert from back, in case the filter range is large
mpHiddenRows->setInsertFromBack(true);
mpFilteredRows->setInsertFromBack(true);
while (aIter != aEnd)
{
if (!aIter->mbShow)
{
SCROW nStartRow = aIter->mnRow1;
SCROW nEndRow = aIter->mnRow2;
SetRowHidden(nStartRow, nEndRow, true);
SetRowFiltered(nStartRow, nEndRow, true);
}
++aIter;
}
mpHiddenRows->setInsertFromBack(false);
mpFilteredRows->setInsertFromBack(false);
if ( pDrawLayer )
{
// if there are no objects in the filtered range, a single HeightChanged call is enough
long nNewHeight = static_cast<long>(GetRowHeight(aParam.nRow1 + nHeader, aParam.nRow2));
pDrawLayer->HeightChanged( nTab, aParam.nRow1 + nHeader, nNewHeight - nOldHeight );
}
}
}
if (aParam.bInplace)
DecRecalcLevel();
delete[] pSpecial;
return nCount;
}
sal_Bool ScTable::CreateExcelQuery(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScQueryParam& rQueryParam)
{
sal_Bool bValid = sal_True;
SCCOL* pFields = new SCCOL[nCol2-nCol1+1];
String aCellStr;
SCCOL nCol = nCol1;
DBG_ASSERT( rQueryParam.nTab != SCTAB_MAX, "rQueryParam.nTab no value, not bad but no good" );
SCTAB nDBTab = (rQueryParam.nTab == SCTAB_MAX ? nTab : rQueryParam.nTab);
SCROW nDBRow1 = rQueryParam.nRow1;
SCCOL nDBCol2 = rQueryParam.nCol2;
// Erste Zeile muessen Spaltenkoepfe sein
while (bValid && (nCol <= nCol2))
{
String aQueryStr;
GetUpperCellString(nCol, nRow1, aQueryStr);
sal_Bool bFound = sal_False;
SCCOL i = rQueryParam.nCol1;
while (!bFound && (i <= nDBCol2))
{
if ( nTab == nDBTab )
GetUpperCellString(i, nDBRow1, aCellStr);
else
pDocument->GetUpperCellString(i, nDBRow1, nDBTab, aCellStr);
bFound = (aCellStr == aQueryStr);
if (!bFound) i++;
}
if (bFound)
pFields[nCol - nCol1] = i;
else
bValid = sal_False;
nCol++;
}
if (bValid)
{
sal_uLong nVisible = 0;
for ( nCol=nCol1; nCol<=nCol2; nCol++ )
nVisible += aCol[nCol].VisibleCount( nRow1+1, nRow2 );
if ( nVisible > SCSIZE_MAX / sizeof(void*) )
{
DBG_ERROR("zu viele Filterkritierien");
nVisible = 0;
}
SCSIZE nNewEntries = nVisible;
rQueryParam.Resize( nNewEntries );
SCSIZE nIndex = 0;
SCROW nRow = nRow1 + 1;
while (nRow <= nRow2)
{
nCol = nCol1;
while (nCol <= nCol2)
{
GetInputString( nCol, nRow, aCellStr );
// ScGlobal::pCharClass->toUpper( aCellStr ); // #i119637
if (aCellStr.Len() > 0)
{
if (nIndex < nNewEntries)
{
rQueryParam.GetEntry(nIndex).nField = pFields[nCol - nCol1];
rQueryParam.FillInExcelSyntax(aCellStr, nIndex);
nIndex++;
if (nIndex < nNewEntries)
rQueryParam.GetEntry(nIndex).eConnect = SC_AND;
}
else
bValid = sal_False;
}
nCol++;
}
nRow++;
if (nIndex < nNewEntries)
rQueryParam.GetEntry(nIndex).eConnect = SC_OR;
}
}
delete [] pFields;
return bValid;
}
sal_Bool ScTable::CreateStarQuery(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScQueryParam& rQueryParam)
{
// A valid StarQuery must be at least 4 columns wide. To be precise it
// should be exactly 4 columns ...
// Additionally, if this wasn't checked, a formula pointing to a valid 1-3
// column Excel style query range immediately left to itself would result
// in a circular reference when the field name or operator or value (first
// to third query range column) is obtained (#i58354#). Furthermore, if the
// range wasn't sufficiently specified data changes wouldn't flag formula
// cells for recalculation.
if (nCol2 - nCol1 < 3)
return sal_False;
sal_Bool bValid;
sal_Bool bFound;
String aCellStr;
SCSIZE nIndex = 0;
SCROW nRow = nRow1;
DBG_ASSERT( rQueryParam.nTab != SCTAB_MAX, "rQueryParam.nTab no value, not bad but no good" );
SCTAB nDBTab = (rQueryParam.nTab == SCTAB_MAX ? nTab : rQueryParam.nTab);
SCROW nDBRow1 = rQueryParam.nRow1;
SCCOL nDBCol2 = rQueryParam.nCol2;
SCSIZE nNewEntries = static_cast<SCSIZE>(nRow2-nRow1+1);
rQueryParam.Resize( nNewEntries );
do
{
ScQueryEntry& rEntry = rQueryParam.GetEntry(nIndex);
bValid = sal_False;
// Erste Spalte UND/ODER
if (nIndex > 0)
{
GetUpperCellString(nCol1, nRow, aCellStr);
if ( aCellStr == ScGlobal::GetRscString(STR_TABLE_UND) )
{
rEntry.eConnect = SC_AND;
bValid = sal_True;
}
else if ( aCellStr == ScGlobal::GetRscString(STR_TABLE_ODER) )
{
rEntry.eConnect = SC_OR;
bValid = sal_True;
}
}
// Zweite Spalte FeldName
if ((nIndex < 1) || bValid)
{
bFound = sal_False;
GetUpperCellString(nCol1 + 1, nRow, aCellStr);
for (SCCOL i=rQueryParam.nCol1; (i <= nDBCol2) && (!bFound); i++)
{
String aFieldStr;
if ( nTab == nDBTab )
GetUpperCellString(i, nDBRow1, aFieldStr);
else
pDocument->GetUpperCellString(i, nDBRow1, nDBTab, aFieldStr);
bFound = (aCellStr == aFieldStr);
if (bFound)
{
rEntry.nField = i;
bValid = sal_True;
}
else
bValid = sal_False;
}
}
// Dritte Spalte Operator =<>...
if (bValid)
{
bFound = sal_False;
GetUpperCellString(nCol1 + 2, nRow, aCellStr);
if (aCellStr.GetChar(0) == '<')
{
if (aCellStr.GetChar(1) == '>')
rEntry.eOp = SC_NOT_EQUAL;
else if (aCellStr.GetChar(1) == '=')
rEntry.eOp = SC_LESS_EQUAL;
else
rEntry.eOp = SC_LESS;
}
else if (aCellStr.GetChar(0) == '>')
{
if (aCellStr.GetChar(1) == '=')
rEntry.eOp = SC_GREATER_EQUAL;
else
rEntry.eOp = SC_GREATER;
}
else if (aCellStr.GetChar(0) == '=')
rEntry.eOp = SC_EQUAL;
}
// Vierte Spalte Wert
if (bValid)
{
GetString(nCol1 + 3, nRow, *rEntry.pStr);
rEntry.bDoQuery = sal_True;
}
nIndex++;
nRow++;
}
while (bValid && (nRow <= nRow2) /* && (nIndex < MAXQUERY) */ );
return bValid;
}
sal_Bool ScTable::CreateQueryParam(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScQueryParam& rQueryParam)
{
SCSIZE i, nCount;
PutInOrder(nCol1, nCol2);
PutInOrder(nRow1, nRow2);
nCount = rQueryParam.GetEntryCount();
for (i=0; i < nCount; i++)
rQueryParam.GetEntry(i).Clear();
// Standard QueryTabelle
sal_Bool bValid = CreateStarQuery(nCol1, nRow1, nCol2, nRow2, rQueryParam);
// Excel QueryTabelle
if (!bValid)
bValid = CreateExcelQuery(nCol1, nRow1, nCol2, nRow2, rQueryParam);
nCount = rQueryParam.GetEntryCount();
if (bValid)
{
// bQueryByString muss gesetzt sein
for (i=0; i < nCount; i++)
rQueryParam.GetEntry(i).bQueryByString = sal_True;
}
else
{
// nix
for (i=0; i < nCount; i++)
rQueryParam.GetEntry(i).Clear();
}
return bValid;
}
sal_Bool ScTable::HasColHeader( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW /* nEndRow */ )
{
for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++)
{
CellType eType = GetCellType( nCol, nStartRow );
if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT)
return sal_False;
}
return sal_True;
}
sal_Bool ScTable::HasRowHeader( SCCOL nStartCol, SCROW nStartRow, SCCOL /* nEndCol */, SCROW nEndRow )
{
for (SCROW nRow=nStartRow; nRow<=nEndRow; nRow++)
{
CellType eType = GetCellType( nStartCol, nRow );
if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT)
return sal_False;
}
return sal_True;
}
void ScTable::GetFilterEntries(SCCOL nCol, SCROW nRow1, SCROW nRow2, TypedScStrCollection& rStrings, bool& rHasDates)
{
aCol[nCol].GetFilterEntries( nRow1, nRow2, rStrings, rHasDates );
}
void ScTable::GetFilteredFilterEntries(
SCCOL nCol, SCROW nRow1, SCROW nRow2, const ScQueryParam& rParam, TypedScStrCollection& rStrings, bool& rHasDates )
{
// remove the entry for this column from the query parameter
ScQueryParam aParam( rParam );
SCSIZE nEntryCount = aParam.GetEntryCount();
for ( SCSIZE i = 0; i < nEntryCount && aParam.GetEntry(i).bDoQuery; ++i )
{
ScQueryEntry& rEntry = aParam.GetEntry(i);
if ( rEntry.nField == nCol )
{
aParam.DeleteQuery(i);
break;
}
}
nEntryCount = aParam.GetEntryCount();
sal_Bool* pSpecial = new sal_Bool[nEntryCount];
lcl_PrepareQuery( pDocument, this, aParam, pSpecial );
bool bHasDates = false;
for ( SCROW j = nRow1; j <= nRow2; ++j )
{
if ( ValidQuery( j, aParam, pSpecial ) )
{
bool bThisHasDates = false;
aCol[nCol].GetFilterEntries( j, j, rStrings, bThisHasDates );
bHasDates |= bThisHasDates;
}
}
rHasDates = bHasDates;
delete[] pSpecial;
}
sal_Bool ScTable::GetDataEntries(SCCOL nCol, SCROW nRow, TypedScStrCollection& rStrings, sal_Bool bLimit)
{
return aCol[nCol].GetDataEntries( nRow, rStrings, bLimit );
}
SCSIZE ScTable::GetCellCount(SCCOL nCol) const
{
return aCol[nCol].GetCellCount();
}
sal_uLong ScTable::GetCellCount() const
{
sal_uLong nCellCount = 0;
for ( SCCOL nCol=0; nCol<=MAXCOL; nCol++ )
nCellCount += aCol[nCol].GetCellCount();
return nCellCount;
}
sal_uLong ScTable::GetWeightedCount() const
{
sal_uLong nCellCount = 0;
for ( SCCOL nCol=0; nCol<=MAXCOL; nCol++ )
if ( aCol[nCol].GetCellCount() ) // GetCellCount ist inline
nCellCount += aCol[nCol].GetWeightedCount();
return nCellCount;
}
sal_uLong ScTable::GetCodeCount() const
{
sal_uLong nCodeCount = 0;
for ( SCCOL nCol=0; nCol<=MAXCOL; nCol++ )
if ( aCol[nCol].GetCellCount() ) // GetCellCount ist inline
nCodeCount += aCol[nCol].GetCodeCount();
return nCodeCount;
}
sal_Int32 ScTable::GetMaxStringLen( SCCOL nCol, SCROW nRowStart,
SCROW nRowEnd, CharSet eCharSet ) const
{
if ( ValidCol(nCol) )
return aCol[nCol].GetMaxStringLen( nRowStart, nRowEnd, eCharSet );
else
return 0;
}
xub_StrLen ScTable::GetMaxNumberStringLen(
sal_uInt16& nPrecision, SCCOL nCol, SCROW nRowStart, SCROW nRowEnd ) const
{
if ( ValidCol(nCol) )
return aCol[nCol].GetMaxNumberStringLen( nPrecision, nRowStart, nRowEnd );
else
return 0;
}
void ScTable::UpdateSelectionFunction( ScFunctionData& rData,
SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
const ScMarkData& rMark )
{
// Cursor neben einer Markierung nicht beruecksichtigen:
//! nur noch MarkData uebergeben, Cursorposition ggf. hineinselektieren!!!
sal_Bool bSingle = ( rMark.IsMarked() || !rMark.IsMultiMarked() );
// Mehrfachselektion:
SCCOL nCol;
if ( rMark.IsMultiMarked() )
for (nCol=0; nCol<=MAXCOL && !rData.bError; nCol++)
if ( !pColFlags || !ColHidden(nCol) )
aCol[nCol].UpdateSelectionFunction( rMark, rData, *mpHiddenRows,
bSingle && ( nCol >= nStartCol && nCol <= nEndCol ),
nStartRow, nEndRow );
// Einfachselektion (oder Cursor) nur wenn nicht negativ (und s.o.):
if ( bSingle && !rMark.IsMarkNegative() )
for (nCol=nStartCol; nCol<=nEndCol && !rData.bError; nCol++)
if ( !pColFlags || !ColHidden(nCol) )
aCol[nCol].UpdateAreaFunction( rData, *mpHiddenRows, nStartRow, nEndRow );
}
void ScTable::FindConditionalFormat( sal_uLong nKey, ScRangeList& rList )
{
SCROW nStartRow = 0, nEndRow = 0;
for (SCCOL nCol=0; nCol<=MAXCOL; nCol++)
{
ScAttrIterator* pIter = aCol[nCol].CreateAttrIterator( 0, MAXROW );
const ScPatternAttr* pPattern = pIter->Next( nStartRow, nEndRow );
while (pPattern)
{
if (((SfxUInt32Item&)pPattern->GetItem(ATTR_CONDITIONAL)).GetValue() == nKey)
rList.Join( ScRange(nCol,nStartRow,nTab, nCol,nEndRow,nTab) );
pPattern = pIter->Next( nStartRow, nEndRow );
}
delete pIter;
}
}