4121 lines
146 KiB
C++
4121 lines
146 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <dptabres.hxx>
|
|
|
|
#include <dptabdat.hxx>
|
|
#include <dptabsrc.hxx>
|
|
#include <global.hxx>
|
|
#include <subtotal.hxx>
|
|
#include <globstr.hrc>
|
|
#include <scresid.hxx>
|
|
#include <dpitemdata.hxx>
|
|
#include <generalfunction.hxx>
|
|
|
|
#include <document.hxx>
|
|
#include <dpresfilter.hxx>
|
|
#include <dputil.hxx>
|
|
|
|
#include <o3tl/safeint.hxx>
|
|
#include <osl/diagnose.h>
|
|
#include <rtl/math.hxx>
|
|
#include <sal/log.hxx>
|
|
|
|
#include <math.h>
|
|
#include <float.h>
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
|
|
#include <com/sun/star/sheet/DataResultFlags.hpp>
|
|
#include <com/sun/star/sheet/MemberResultFlags.hpp>
|
|
#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp>
|
|
#include <com/sun/star/sheet/DataPilotFieldReferenceItemType.hpp>
|
|
#include <com/sun/star/sheet/DataPilotFieldShowItemsMode.hpp>
|
|
#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>
|
|
#include <com/sun/star/sheet/GeneralFunction2.hpp>
|
|
|
|
using namespace com::sun::star;
|
|
using ::std::vector;
|
|
using ::std::pair;
|
|
using ::com::sun::star::uno::Sequence;
|
|
|
|
namespace {
|
|
|
|
const TranslateId aFuncStrIds[] = // matching enum ScSubTotalFunc
|
|
{
|
|
{}, // SUBTOTAL_FUNC_NONE
|
|
STR_FUN_TEXT_AVG, // SUBTOTAL_FUNC_AVE
|
|
STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT
|
|
STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT2
|
|
STR_FUN_TEXT_MAX, // SUBTOTAL_FUNC_MAX
|
|
STR_FUN_TEXT_MIN, // SUBTOTAL_FUNC_MIN
|
|
STR_FUN_TEXT_PRODUCT, // SUBTOTAL_FUNC_PROD
|
|
STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STD
|
|
STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STDP
|
|
STR_FUN_TEXT_SUM, // SUBTOTAL_FUNC_SUM
|
|
STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VAR
|
|
STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VARP
|
|
STR_FUN_TEXT_MEDIAN, // SUBTOTAL_FUNC_MED
|
|
{} // SUBTOTAL_FUNC_SELECTION_COUNT - not used for pivot table
|
|
};
|
|
|
|
bool lcl_SearchMember( const std::vector<std::unique_ptr<ScDPResultMember>>& list, SCROW nOrder, SCROW& rIndex)
|
|
{
|
|
bool bFound = false;
|
|
SCROW nLo = 0;
|
|
SCROW nHi = list.size() - 1;
|
|
SCROW nIndex;
|
|
while (nLo <= nHi)
|
|
{
|
|
nIndex = (nLo + nHi) / 2;
|
|
if ( list[nIndex]->GetOrder() < nOrder )
|
|
nLo = nIndex + 1;
|
|
else
|
|
{
|
|
nHi = nIndex - 1;
|
|
if ( list[nIndex]->GetOrder() == nOrder )
|
|
{
|
|
bFound = true;
|
|
nLo = nIndex;
|
|
}
|
|
}
|
|
}
|
|
rIndex = nLo;
|
|
return bFound;
|
|
}
|
|
|
|
class FilterStack
|
|
{
|
|
std::vector<ScDPResultFilter>& mrFilters;
|
|
public:
|
|
explicit FilterStack(std::vector<ScDPResultFilter>& rFilters) : mrFilters(rFilters) {}
|
|
|
|
void pushDimName(const OUString& rName, bool bDataLayout)
|
|
{
|
|
mrFilters.emplace_back(rName, bDataLayout);
|
|
}
|
|
|
|
void pushDimValue(const OUString& rValueName, const OUString& rValue)
|
|
{
|
|
ScDPResultFilter& rFilter = mrFilters.back();
|
|
rFilter.maValueName = rValueName;
|
|
rFilter.maValue = rValue;
|
|
rFilter.mbHasValue = true;
|
|
}
|
|
|
|
~FilterStack()
|
|
{
|
|
ScDPResultFilter& rFilter = mrFilters.back();
|
|
if (rFilter.mbHasValue)
|
|
rFilter.mbHasValue = false;
|
|
else
|
|
mrFilters.pop_back();
|
|
}
|
|
};
|
|
|
|
// function objects for sorting of the column and row members:
|
|
|
|
class ScDPRowMembersOrder
|
|
{
|
|
ScDPResultDimension& rDimension;
|
|
tools::Long nMeasure;
|
|
bool bAscending;
|
|
|
|
public:
|
|
ScDPRowMembersOrder( ScDPResultDimension& rDim, tools::Long nM, bool bAsc ) :
|
|
rDimension(rDim),
|
|
nMeasure(nM),
|
|
bAscending(bAsc)
|
|
{}
|
|
|
|
bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const;
|
|
};
|
|
|
|
class ScDPColMembersOrder
|
|
{
|
|
ScDPDataDimension& rDimension;
|
|
tools::Long nMeasure;
|
|
bool bAscending;
|
|
|
|
public:
|
|
ScDPColMembersOrder( ScDPDataDimension& rDim, tools::Long nM, bool bAsc ) :
|
|
rDimension(rDim),
|
|
nMeasure(nM),
|
|
bAscending(bAsc)
|
|
{}
|
|
|
|
bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const;
|
|
};
|
|
|
|
}
|
|
|
|
static bool lcl_IsLess( const ScDPDataMember* pDataMember1, const ScDPDataMember* pDataMember2, tools::Long nMeasure, bool bAscending )
|
|
{
|
|
// members can be NULL if used for rows
|
|
|
|
ScDPSubTotalState aEmptyState;
|
|
const ScDPAggData* pAgg1 = pDataMember1 ? pDataMember1->GetConstAggData( nMeasure, aEmptyState ) : nullptr;
|
|
const ScDPAggData* pAgg2 = pDataMember2 ? pDataMember2->GetConstAggData( nMeasure, aEmptyState ) : nullptr;
|
|
|
|
bool bError1 = pAgg1 && pAgg1->HasError();
|
|
bool bError2 = pAgg2 && pAgg2->HasError();
|
|
if ( bError1 )
|
|
return false; // errors are always sorted at the end
|
|
else if ( bError2 )
|
|
return true; // errors are always sorted at the end
|
|
else
|
|
{
|
|
double fVal1 = ( pAgg1 && pAgg1->HasData() ) ? pAgg1->GetResult() : 0.0; // no data is sorted as 0
|
|
double fVal2 = ( pAgg2 && pAgg2->HasData() ) ? pAgg2->GetResult() : 0.0;
|
|
|
|
// compare values
|
|
// don't have to check approxEqual, as this is the only sort criterion
|
|
|
|
return bAscending ? ( fVal1 < fVal2 ) : ( fVal1 > fVal2 );
|
|
}
|
|
}
|
|
|
|
static bool lcl_IsEqual( const ScDPDataMember* pDataMember1, const ScDPDataMember* pDataMember2, tools::Long nMeasure )
|
|
{
|
|
// members can be NULL if used for rows
|
|
|
|
ScDPSubTotalState aEmptyState;
|
|
const ScDPAggData* pAgg1 = pDataMember1 ? pDataMember1->GetConstAggData( nMeasure, aEmptyState ) : nullptr;
|
|
const ScDPAggData* pAgg2 = pDataMember2 ? pDataMember2->GetConstAggData( nMeasure, aEmptyState ) : nullptr;
|
|
|
|
bool bError1 = pAgg1 && pAgg1->HasError();
|
|
bool bError2 = pAgg2 && pAgg2->HasError();
|
|
if ( bError1 )
|
|
{
|
|
if ( bError2 )
|
|
return true; // equal
|
|
else
|
|
return false;
|
|
}
|
|
else if ( bError2 )
|
|
return false;
|
|
else
|
|
{
|
|
double fVal1 = ( pAgg1 && pAgg1->HasData() ) ? pAgg1->GetResult() : 0.0; // no data is sorted as 0
|
|
double fVal2 = ( pAgg2 && pAgg2->HasData() ) ? pAgg2->GetResult() : 0.0;
|
|
|
|
// compare values
|
|
// this is used to find equal data at the end of the AutoShow range, so approxEqual must be used
|
|
|
|
return rtl::math::approxEqual( fVal1, fVal2 );
|
|
}
|
|
}
|
|
|
|
bool ScDPRowMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const
|
|
{
|
|
const ScDPResultMember* pMember1 = rDimension.GetMember(nIndex1);
|
|
const ScDPResultMember* pMember2 = rDimension.GetMember(nIndex2);
|
|
|
|
// make the hide item to the largest order.
|
|
if ( !pMember1->IsVisible() || !pMember2->IsVisible() )
|
|
return pMember1->IsVisible();
|
|
const ScDPDataMember* pDataMember1 = pMember1->GetDataRoot() ;
|
|
const ScDPDataMember* pDataMember2 = pMember2->GetDataRoot();
|
|
// GetDataRoot can be NULL if there was no data.
|
|
// IsVisible == false can happen after AutoShow.
|
|
return lcl_IsLess( pDataMember1, pDataMember2, nMeasure, bAscending );
|
|
}
|
|
|
|
bool ScDPColMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const
|
|
{
|
|
const ScDPDataMember* pDataMember1 = rDimension.GetMember(nIndex1);
|
|
const ScDPDataMember* pDataMember2 = rDimension.GetMember(nIndex2);
|
|
bool bHide1 = pDataMember1 && !pDataMember1->IsVisible();
|
|
bool bHide2 = pDataMember2 && !pDataMember2->IsVisible();
|
|
if ( bHide1 || bHide2 )
|
|
return !bHide1;
|
|
return lcl_IsLess( pDataMember1, pDataMember2, nMeasure, bAscending );
|
|
}
|
|
|
|
ScDPInitState::Member::Member(tools::Long nSrcIndex, SCROW nNameIndex) :
|
|
mnSrcIndex(nSrcIndex), mnNameIndex(nNameIndex) {}
|
|
|
|
void ScDPInitState::AddMember( tools::Long nSourceIndex, SCROW nMember )
|
|
{
|
|
maMembers.emplace_back(nSourceIndex, nMember);
|
|
}
|
|
|
|
void ScDPInitState::RemoveMember()
|
|
{
|
|
OSL_ENSURE(!maMembers.empty(), "ScDPInitState::RemoveMember: Attempt to remove member while empty.");
|
|
if (!maMembers.empty())
|
|
maMembers.pop_back();
|
|
}
|
|
|
|
namespace {
|
|
|
|
#if DUMP_PIVOT_TABLE
|
|
void dumpRow(
|
|
const OUString& rType, const OUString& rName, const ScDPAggData* pAggData,
|
|
ScDocument* pDoc, ScAddress& rPos )
|
|
{
|
|
SCCOL nCol = rPos.Col();
|
|
SCROW nRow = rPos.Row();
|
|
SCTAB nTab = rPos.Tab();
|
|
pDoc->SetString( nCol++, nRow, nTab, rType );
|
|
pDoc->SetString( nCol++, nRow, nTab, rName );
|
|
while ( pAggData )
|
|
{
|
|
pDoc->SetValue( nCol++, nRow, nTab, pAggData->GetResult() );
|
|
pAggData = pAggData->GetExistingChild();
|
|
}
|
|
rPos.SetRow( nRow + 1 );
|
|
}
|
|
|
|
void indent( ScDocument* pDoc, SCROW nStartRow, const ScAddress& rPos )
|
|
{
|
|
SCCOL nCol = rPos.Col();
|
|
SCTAB nTab = rPos.Tab();
|
|
|
|
OUString aString;
|
|
for (SCROW nRow = nStartRow; nRow < rPos.Row(); nRow++)
|
|
{
|
|
aString = pDoc->GetString(nCol, nRow, nTab);
|
|
if (!aString.isEmpty())
|
|
{
|
|
aString = " " + aString;
|
|
pDoc->SetString( nCol, nRow, nTab, aString );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
ScDPRunningTotalState::ScDPRunningTotalState( ScDPResultMember* pColRoot, ScDPResultMember* pRowRoot ) :
|
|
pColResRoot(pColRoot), pRowResRoot(pRowRoot)
|
|
{
|
|
// These arrays should never be empty as the terminating value must be present at all times.
|
|
maColVisible.push_back(-1);
|
|
maColSorted.push_back(-1);
|
|
maRowVisible.push_back(-1);
|
|
maRowSorted.push_back(-1);
|
|
}
|
|
|
|
void ScDPRunningTotalState::AddColIndex( sal_Int32 nVisible, tools::Long nSorted )
|
|
{
|
|
maColVisible.back() = nVisible;
|
|
maColVisible.push_back(-1);
|
|
|
|
maColSorted.back() = nSorted;
|
|
maColSorted.push_back(-1);
|
|
}
|
|
|
|
void ScDPRunningTotalState::AddRowIndex( sal_Int32 nVisible, tools::Long nSorted )
|
|
{
|
|
maRowVisible.back() = nVisible;
|
|
maRowVisible.push_back(-1);
|
|
|
|
maRowSorted.back() = nSorted;
|
|
maRowSorted.push_back(-1);
|
|
}
|
|
|
|
void ScDPRunningTotalState::RemoveColIndex()
|
|
{
|
|
OSL_ENSURE(!maColVisible.empty() && !maColSorted.empty(), "ScDPRunningTotalState::RemoveColIndex: array is already empty!");
|
|
if (maColVisible.size() >= 2)
|
|
{
|
|
maColVisible.pop_back();
|
|
maColVisible.back() = -1;
|
|
}
|
|
|
|
if (maColSorted.size() >= 2)
|
|
{
|
|
maColSorted.pop_back();
|
|
maColSorted.back() = -1;
|
|
}
|
|
}
|
|
|
|
void ScDPRunningTotalState::RemoveRowIndex()
|
|
{
|
|
OSL_ENSURE(!maRowVisible.empty() && !maRowSorted.empty(), "ScDPRunningTotalState::RemoveRowIndex: array is already empty!");
|
|
if (maRowVisible.size() >= 2)
|
|
{
|
|
maRowVisible.pop_back();
|
|
maRowVisible.back() = -1;
|
|
}
|
|
|
|
if (maRowSorted.size() >= 2)
|
|
{
|
|
maRowSorted.pop_back();
|
|
maRowSorted.back() = -1;
|
|
}
|
|
}
|
|
|
|
ScDPRelativePos::ScDPRelativePos( tools::Long nBase, tools::Long nDir ) :
|
|
nBasePos( nBase ),
|
|
nDirection( nDir )
|
|
{
|
|
}
|
|
|
|
void ScDPAggData::Update( const ScDPValue& rNext, ScSubTotalFunc eFunc, const ScDPSubTotalState& rSubState )
|
|
{
|
|
if (nCount<0) // error?
|
|
return; // nothing more...
|
|
|
|
if (rNext.meType == ScDPValue::Empty)
|
|
return;
|
|
|
|
if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE && rSubState.eRowForce != SUBTOTAL_FUNC_NONE &&
|
|
rSubState.eColForce != rSubState.eRowForce )
|
|
return;
|
|
if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eColForce;
|
|
if ( rSubState.eRowForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eRowForce;
|
|
|
|
if ( eFunc == SUBTOTAL_FUNC_NONE )
|
|
return;
|
|
|
|
if ( eFunc != SUBTOTAL_FUNC_CNT2 ) // CNT2 counts everything, incl. strings and errors
|
|
{
|
|
if (rNext.meType == ScDPValue::Error)
|
|
{
|
|
nCount = -1; // -1 for error (not for CNT2)
|
|
return;
|
|
}
|
|
if (rNext.meType == ScDPValue::String)
|
|
return; // ignore
|
|
}
|
|
|
|
++nCount; // for all functions
|
|
|
|
switch (eFunc)
|
|
{
|
|
case SUBTOTAL_FUNC_SUM:
|
|
case SUBTOTAL_FUNC_AVE:
|
|
if ( !SubTotal::SafePlus( fVal, rNext.mfValue ) )
|
|
nCount = -1; // -1 for error
|
|
break;
|
|
case SUBTOTAL_FUNC_PROD:
|
|
if ( nCount == 1 ) // copy first value (fVal is initialized to 0)
|
|
fVal = rNext.mfValue;
|
|
else if ( !SubTotal::SafeMult( fVal, rNext.mfValue ) )
|
|
nCount = -1; // -1 for error
|
|
break;
|
|
case SUBTOTAL_FUNC_CNT:
|
|
case SUBTOTAL_FUNC_CNT2:
|
|
// nothing more than incrementing nCount
|
|
break;
|
|
case SUBTOTAL_FUNC_MAX:
|
|
if ( nCount == 1 || rNext.mfValue > fVal )
|
|
fVal = rNext.mfValue;
|
|
break;
|
|
case SUBTOTAL_FUNC_MIN:
|
|
if ( nCount == 1 || rNext.mfValue < fVal )
|
|
fVal = rNext.mfValue;
|
|
break;
|
|
case SUBTOTAL_FUNC_STD:
|
|
case SUBTOTAL_FUNC_STDP:
|
|
case SUBTOTAL_FUNC_VAR:
|
|
case SUBTOTAL_FUNC_VARP:
|
|
maWelford.update( rNext.mfValue);
|
|
break;
|
|
case SUBTOTAL_FUNC_MED:
|
|
{
|
|
auto aIter = std::upper_bound(mSortedValues.begin(), mSortedValues.end(), rNext.mfValue);
|
|
if (aIter == mSortedValues.end())
|
|
mSortedValues.push_back(rNext.mfValue);
|
|
else
|
|
mSortedValues.insert(aIter, rNext.mfValue);
|
|
}
|
|
break;
|
|
default:
|
|
OSL_FAIL("invalid function");
|
|
}
|
|
}
|
|
|
|
void ScDPAggData::Calculate( ScSubTotalFunc eFunc, const ScDPSubTotalState& rSubState )
|
|
{
|
|
// calculate the original result
|
|
// (without reference value, used as the basis for reference value calculation)
|
|
|
|
// called several times at the cross-section of several subtotals - don't calculate twice then
|
|
if ( IsCalculated() )
|
|
return;
|
|
|
|
if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eColForce;
|
|
if ( rSubState.eRowForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eRowForce;
|
|
|
|
if ( eFunc == SUBTOTAL_FUNC_NONE ) // this happens when there is no data dimension
|
|
{
|
|
nCount = SC_DPAGG_RESULT_EMPTY; // make sure there's a valid state for HasData etc.
|
|
return;
|
|
}
|
|
|
|
// check the error conditions for the selected function
|
|
|
|
bool bError = false;
|
|
switch (eFunc)
|
|
{
|
|
case SUBTOTAL_FUNC_SUM:
|
|
case SUBTOTAL_FUNC_PROD:
|
|
case SUBTOTAL_FUNC_CNT:
|
|
case SUBTOTAL_FUNC_CNT2:
|
|
bError = ( nCount < 0 ); // only real errors
|
|
break;
|
|
|
|
case SUBTOTAL_FUNC_AVE:
|
|
case SUBTOTAL_FUNC_MED:
|
|
case SUBTOTAL_FUNC_MAX:
|
|
case SUBTOTAL_FUNC_MIN:
|
|
bError = ( nCount <= 0 ); // no data is an error
|
|
break;
|
|
|
|
case SUBTOTAL_FUNC_STDP:
|
|
case SUBTOTAL_FUNC_VARP:
|
|
bError = ( nCount <= 0 ); // no data is an error
|
|
assert(bError || nCount == static_cast<sal_Int64>(maWelford.getCount()));
|
|
break;
|
|
|
|
case SUBTOTAL_FUNC_STD:
|
|
case SUBTOTAL_FUNC_VAR:
|
|
bError = ( nCount < 2 ); // need at least 2 values
|
|
assert(bError || nCount == static_cast<sal_Int64>(maWelford.getCount()));
|
|
break;
|
|
|
|
default:
|
|
OSL_FAIL("invalid function");
|
|
}
|
|
|
|
// calculate the selected function
|
|
|
|
double fResult = 0.0;
|
|
if ( !bError )
|
|
{
|
|
switch (eFunc)
|
|
{
|
|
case SUBTOTAL_FUNC_MAX:
|
|
case SUBTOTAL_FUNC_MIN:
|
|
case SUBTOTAL_FUNC_SUM:
|
|
case SUBTOTAL_FUNC_PROD:
|
|
// different error conditions are handled above
|
|
fResult = fVal;
|
|
break;
|
|
|
|
case SUBTOTAL_FUNC_CNT:
|
|
case SUBTOTAL_FUNC_CNT2:
|
|
fResult = nCount;
|
|
break;
|
|
|
|
case SUBTOTAL_FUNC_AVE:
|
|
if ( nCount > 0 )
|
|
fResult = fVal / static_cast<double>(nCount);
|
|
break;
|
|
|
|
case SUBTOTAL_FUNC_STD:
|
|
if ( nCount >= 2 )
|
|
{
|
|
fResult = maWelford.getVarianceSample();
|
|
if (fResult < 0.0)
|
|
bError = true;
|
|
else
|
|
fResult = sqrt( fResult);
|
|
}
|
|
break;
|
|
case SUBTOTAL_FUNC_VAR:
|
|
if ( nCount >= 2 )
|
|
fResult = maWelford.getVarianceSample();
|
|
break;
|
|
case SUBTOTAL_FUNC_STDP:
|
|
if ( nCount > 0 )
|
|
{
|
|
fResult = maWelford.getVariancePopulation();
|
|
if (fResult < 0.0)
|
|
bError = true;
|
|
else
|
|
fResult = sqrt( fResult);
|
|
}
|
|
break;
|
|
case SUBTOTAL_FUNC_VARP:
|
|
if ( nCount > 0 )
|
|
fResult = maWelford.getVariancePopulation();
|
|
break;
|
|
case SUBTOTAL_FUNC_MED:
|
|
{
|
|
size_t nSize = mSortedValues.size();
|
|
if (nSize > 0)
|
|
{
|
|
assert(nSize == static_cast<size_t>(nCount));
|
|
if ((nSize % 2) == 1)
|
|
fResult = mSortedValues[nSize / 2];
|
|
else
|
|
fResult = (mSortedValues[nSize / 2 - 1] + mSortedValues[nSize / 2]) / 2.0;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
OSL_FAIL("invalid function");
|
|
}
|
|
}
|
|
|
|
bool bEmpty = ( nCount == 0 ); // no data
|
|
|
|
// store the result
|
|
// Empty is checked first, so empty results are shown empty even for "average" etc.
|
|
// If these results should be treated as errors in reference value calculations,
|
|
// a separate state value (EMPTY_ERROR) is needed.
|
|
// Now, for compatibility, empty "average" results are counted as 0.
|
|
|
|
if ( bEmpty )
|
|
nCount = SC_DPAGG_RESULT_EMPTY;
|
|
else if ( bError )
|
|
nCount = SC_DPAGG_RESULT_ERROR;
|
|
else
|
|
nCount = SC_DPAGG_RESULT_VALID;
|
|
|
|
if ( bEmpty || bError )
|
|
fResult = 0.0; // default, in case the state is later modified
|
|
|
|
fVal = fResult; // used directly from now on
|
|
fAux = 0.0; // used for running total or original result of reference value
|
|
}
|
|
|
|
bool ScDPAggData::IsCalculated() const
|
|
{
|
|
return ( nCount <= SC_DPAGG_RESULT_EMPTY );
|
|
}
|
|
|
|
double ScDPAggData::GetResult() const
|
|
{
|
|
assert( IsCalculated() && "ScDPAggData not calculated" );
|
|
|
|
return fVal; // use calculated value
|
|
}
|
|
|
|
bool ScDPAggData::HasError() const
|
|
{
|
|
assert( IsCalculated() && "ScDPAggData not calculated" );
|
|
|
|
return ( nCount == SC_DPAGG_RESULT_ERROR );
|
|
}
|
|
|
|
bool ScDPAggData::HasData() const
|
|
{
|
|
assert( IsCalculated() && "ScDPAggData not calculated" );
|
|
|
|
return ( nCount != SC_DPAGG_RESULT_EMPTY ); // values or error
|
|
}
|
|
|
|
void ScDPAggData::SetResult( double fNew )
|
|
{
|
|
assert( IsCalculated() && "ScDPAggData not calculated" );
|
|
|
|
fVal = fNew; // don't reset error flag
|
|
}
|
|
|
|
void ScDPAggData::SetError()
|
|
{
|
|
assert( IsCalculated() && "ScDPAggData not calculated" );
|
|
|
|
nCount = SC_DPAGG_RESULT_ERROR;
|
|
}
|
|
|
|
void ScDPAggData::SetEmpty( bool bSet )
|
|
{
|
|
assert( IsCalculated() && "ScDPAggData not calculated" );
|
|
|
|
if ( bSet )
|
|
nCount = SC_DPAGG_RESULT_EMPTY;
|
|
else
|
|
nCount = SC_DPAGG_RESULT_VALID;
|
|
}
|
|
|
|
double ScDPAggData::GetAuxiliary() const
|
|
{
|
|
// after Calculate, fAux is used as auxiliary value for running totals and reference values
|
|
assert( IsCalculated() && "ScDPAggData not calculated" );
|
|
|
|
return fAux;
|
|
}
|
|
|
|
void ScDPAggData::SetAuxiliary( double fNew )
|
|
{
|
|
// after Calculate, fAux is used as auxiliary value for running totals and reference values
|
|
assert( IsCalculated() && "ScDPAggData not calculated" );
|
|
|
|
fAux = fNew;
|
|
}
|
|
|
|
ScDPAggData* ScDPAggData::GetChild()
|
|
{
|
|
if (!pChild)
|
|
pChild.reset( new ScDPAggData );
|
|
return pChild.get();
|
|
}
|
|
|
|
void ScDPAggData::Reset()
|
|
{
|
|
maWelford = WelfordRunner();
|
|
fVal = 0.0;
|
|
fAux = 0.0;
|
|
nCount = SC_DPAGG_EMPTY;
|
|
pChild.reset();
|
|
}
|
|
|
|
#if DUMP_PIVOT_TABLE
|
|
void ScDPAggData::Dump(int nIndent) const
|
|
{
|
|
std::string aIndent(nIndent*2, ' ');
|
|
std::cout << aIndent << "* ";
|
|
if (IsCalculated())
|
|
std::cout << GetResult();
|
|
else
|
|
std::cout << "not calculated";
|
|
|
|
std::cout << " [val=" << fVal << "; aux=" << fAux << "; count=" << nCount << "]" << std::endl;
|
|
}
|
|
#endif
|
|
|
|
ScDPRowTotals::ScDPRowTotals() :
|
|
bIsInColRoot( false )
|
|
{
|
|
}
|
|
|
|
ScDPRowTotals::~ScDPRowTotals()
|
|
{
|
|
}
|
|
|
|
static ScDPAggData* lcl_GetChildTotal( ScDPAggData* pFirst, tools::Long nMeasure )
|
|
{
|
|
OSL_ENSURE( nMeasure >= 0, "GetColTotal: no measure" );
|
|
|
|
ScDPAggData* pAgg = pFirst;
|
|
tools::Long nSkip = nMeasure;
|
|
|
|
// subtotal settings are ignored - column/row totals exist once per measure
|
|
|
|
for ( tools::Long nPos=0; nPos<nSkip; nPos++ )
|
|
pAgg = pAgg->GetChild(); // column total is constructed empty - children need to be created
|
|
|
|
if ( !pAgg->IsCalculated() )
|
|
{
|
|
// for first use, simulate an empty calculation
|
|
ScDPSubTotalState aEmptyState;
|
|
pAgg->Calculate( SUBTOTAL_FUNC_SUM, aEmptyState );
|
|
}
|
|
|
|
return pAgg;
|
|
}
|
|
|
|
ScDPAggData* ScDPRowTotals::GetRowTotal( tools::Long nMeasure )
|
|
{
|
|
return lcl_GetChildTotal( &aRowTotal, nMeasure );
|
|
}
|
|
|
|
ScDPAggData* ScDPRowTotals::GetGrandTotal( tools::Long nMeasure )
|
|
{
|
|
return lcl_GetChildTotal( &aGrandTotal, nMeasure );
|
|
}
|
|
|
|
static ScSubTotalFunc lcl_GetForceFunc( const ScDPLevel* pLevel, tools::Long nFuncNo )
|
|
{
|
|
ScSubTotalFunc eRet = SUBTOTAL_FUNC_NONE;
|
|
if ( pLevel )
|
|
{
|
|
//TODO: direct access via ScDPLevel
|
|
|
|
uno::Sequence<sal_Int16> aSeq = pLevel->getSubTotals();
|
|
tools::Long nSequence = aSeq.getLength();
|
|
if ( nSequence && aSeq[0] != sheet::GeneralFunction2::AUTO )
|
|
{
|
|
// For manual subtotals, "automatic" is added as first function.
|
|
// ScDPResultMember::GetSubTotalCount adds to the count, here NONE has to be
|
|
// returned as the first function then.
|
|
|
|
--nFuncNo; // keep NONE for first (check below), move the other entries
|
|
}
|
|
|
|
if ( nFuncNo >= 0 && nFuncNo < nSequence )
|
|
{
|
|
ScGeneralFunction eUser = static_cast<ScGeneralFunction>(aSeq.getConstArray()[nFuncNo]);
|
|
if (eUser != ScGeneralFunction::AUTO)
|
|
eRet = ScDPUtil::toSubTotalFunc(eUser);
|
|
}
|
|
}
|
|
return eRet;
|
|
}
|
|
|
|
ScDPResultData::ScDPResultData( ScDPSource& rSrc ) :
|
|
mrSource(rSrc),
|
|
bLateInit( false ),
|
|
bDataAtCol( false ),
|
|
bDataAtRow( false )
|
|
{
|
|
}
|
|
|
|
ScDPResultData::~ScDPResultData()
|
|
{
|
|
}
|
|
|
|
void ScDPResultData::SetMeasureData(
|
|
std::vector<ScSubTotalFunc>& rFunctions, std::vector<sheet::DataPilotFieldReference>& rRefs,
|
|
std::vector<sheet::DataPilotFieldOrientation>& rRefOrient, std::vector<OUString>& rNames )
|
|
{
|
|
// We need to have at least one measure data at all times.
|
|
|
|
maMeasureFuncs.swap(rFunctions);
|
|
if (maMeasureFuncs.empty())
|
|
maMeasureFuncs.push_back(SUBTOTAL_FUNC_NONE);
|
|
|
|
maMeasureRefs.swap(rRefs);
|
|
if (maMeasureRefs.empty())
|
|
maMeasureRefs.emplace_back(); // default ctor is ok.
|
|
|
|
maMeasureRefOrients.swap(rRefOrient);
|
|
if (maMeasureRefOrients.empty())
|
|
maMeasureRefOrients.push_back(sheet::DataPilotFieldOrientation_HIDDEN);
|
|
|
|
maMeasureNames.swap(rNames);
|
|
if (maMeasureNames.empty())
|
|
maMeasureNames.push_back(ScResId(STR_EMPTYDATA));
|
|
}
|
|
|
|
void ScDPResultData::SetDataLayoutOrientation( sheet::DataPilotFieldOrientation nOrient )
|
|
{
|
|
bDataAtCol = ( nOrient == sheet::DataPilotFieldOrientation_COLUMN );
|
|
bDataAtRow = ( nOrient == sheet::DataPilotFieldOrientation_ROW );
|
|
}
|
|
|
|
void ScDPResultData::SetLateInit( bool bSet )
|
|
{
|
|
bLateInit = bSet;
|
|
}
|
|
|
|
tools::Long ScDPResultData::GetColStartMeasure() const
|
|
{
|
|
if (maMeasureFuncs.size() == 1)
|
|
return 0;
|
|
|
|
return bDataAtCol ? SC_DPMEASURE_ALL : SC_DPMEASURE_ANY;
|
|
}
|
|
|
|
tools::Long ScDPResultData::GetRowStartMeasure() const
|
|
{
|
|
if (maMeasureFuncs.size() == 1)
|
|
return 0;
|
|
|
|
return bDataAtRow ? SC_DPMEASURE_ALL : SC_DPMEASURE_ANY;
|
|
}
|
|
|
|
ScSubTotalFunc ScDPResultData::GetMeasureFunction(tools::Long nMeasure) const
|
|
{
|
|
OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureFuncs.size(), "bumm");
|
|
return maMeasureFuncs[nMeasure];
|
|
}
|
|
|
|
const sheet::DataPilotFieldReference& ScDPResultData::GetMeasureRefVal(tools::Long nMeasure) const
|
|
{
|
|
OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureRefs.size(), "bumm");
|
|
return maMeasureRefs[nMeasure];
|
|
}
|
|
|
|
sheet::DataPilotFieldOrientation ScDPResultData::GetMeasureRefOrient(tools::Long nMeasure) const
|
|
{
|
|
OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureRefOrients.size(), "bumm");
|
|
return maMeasureRefOrients[nMeasure];
|
|
}
|
|
|
|
OUString ScDPResultData::GetMeasureString(tools::Long nMeasure, bool bForce, ScSubTotalFunc eForceFunc, bool& rbTotalResult) const
|
|
{
|
|
// with bForce==true, return function instead of "result" for single measure
|
|
// with eForceFunc != SUBTOTAL_FUNC_NONE, always use eForceFunc
|
|
rbTotalResult = false;
|
|
if ( nMeasure < 0 || (maMeasureFuncs.size() == 1 && !bForce && eForceFunc == SUBTOTAL_FUNC_NONE) )
|
|
{
|
|
// for user-specified subtotal function with all measures,
|
|
// display only function name
|
|
assert(unsigned(eForceFunc) < SAL_N_ELEMENTS(aFuncStrIds));
|
|
if ( eForceFunc != SUBTOTAL_FUNC_NONE )
|
|
return ScResId(aFuncStrIds[eForceFunc]);
|
|
|
|
rbTotalResult = true;
|
|
return ScResId(STR_TABLE_ERGEBNIS);
|
|
}
|
|
else
|
|
{
|
|
OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureFuncs.size(), "bumm");
|
|
const ScDPDimension* pDataDim = mrSource.GetDataDimension(nMeasure);
|
|
if (pDataDim)
|
|
{
|
|
const std::optional<OUString> & pLayoutName = pDataDim->GetLayoutName();
|
|
if (pLayoutName)
|
|
return *pLayoutName;
|
|
}
|
|
|
|
ScSubTotalFunc eFunc = ( eForceFunc == SUBTOTAL_FUNC_NONE ) ?
|
|
GetMeasureFunction(nMeasure) : eForceFunc;
|
|
|
|
return ScDPUtil::getDisplayedMeasureName(maMeasureNames[nMeasure], eFunc);
|
|
}
|
|
}
|
|
|
|
OUString ScDPResultData::GetMeasureDimensionName(tools::Long nMeasure) const
|
|
{
|
|
if ( nMeasure < 0 )
|
|
{
|
|
OSL_FAIL("GetMeasureDimensionName: negative");
|
|
return u"***"_ustr;
|
|
}
|
|
|
|
return mrSource.GetDataDimName(nMeasure);
|
|
}
|
|
|
|
bool ScDPResultData::IsBaseForGroup( tools::Long nDim ) const
|
|
{
|
|
return mrSource.GetData()->IsBaseForGroup(nDim);
|
|
}
|
|
|
|
tools::Long ScDPResultData::GetGroupBase( tools::Long nGroupDim ) const
|
|
{
|
|
return mrSource.GetData()->GetGroupBase(nGroupDim);
|
|
}
|
|
|
|
bool ScDPResultData::IsNumOrDateGroup( tools::Long nDim ) const
|
|
{
|
|
return mrSource.GetData()->IsNumOrDateGroup(nDim);
|
|
}
|
|
|
|
bool ScDPResultData::IsInGroup( SCROW nGroupDataId, tools::Long nGroupIndex,
|
|
const ScDPItemData& rBaseData, tools::Long nBaseIndex ) const
|
|
{
|
|
const ScDPItemData* pGroupData = mrSource.GetItemDataById(nGroupIndex , nGroupDataId);
|
|
if ( pGroupData )
|
|
return mrSource.GetData()->IsInGroup(*pGroupData, nGroupIndex, rBaseData, nBaseIndex);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool ScDPResultData::HasCommonElement( SCROW nFirstDataId, tools::Long nFirstIndex,
|
|
const ScDPItemData& rSecondData, tools::Long nSecondIndex ) const
|
|
{
|
|
const ScDPItemData* pFirstData = mrSource.GetItemDataById(nFirstIndex , nFirstDataId);
|
|
if ( pFirstData )
|
|
return mrSource.GetData()->HasCommonElement(*pFirstData, nFirstIndex, rSecondData, nSecondIndex);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
ResultMembers& ScDPResultData::GetDimResultMembers(tools::Long nDim, const ScDPDimension* pDim, ScDPLevel* pLevel) const
|
|
{
|
|
if (nDim < static_cast<tools::Long>(maDimMembers.size()) && maDimMembers[nDim])
|
|
return *maDimMembers[nDim];
|
|
|
|
if (nDim >= static_cast<tools::Long>(maDimMembers.size()))
|
|
maDimMembers.resize(nDim+1);
|
|
|
|
std::unique_ptr<ResultMembers> pResultMembers(new ResultMembers());
|
|
// global order is used to initialize aMembers, so it doesn't have to be looked at later
|
|
const ScMemberSortOrder& rGlobalOrder = pLevel->GetGlobalOrder();
|
|
|
|
ScDPMembers* pMembers = pLevel->GetMembersObject();
|
|
tools::Long nMembCount = pMembers->getCount();
|
|
for (tools::Long i = 0; i < nMembCount; ++i)
|
|
{
|
|
tools::Long nSorted = rGlobalOrder.empty() ? i : rGlobalOrder[i];
|
|
ScDPMember* pMember = pMembers->getByIndex(nSorted);
|
|
if (!pResultMembers->FindMember(pMember->GetItemDataId()))
|
|
{
|
|
ScDPParentDimData aNew(i, pDim, pLevel, pMember);
|
|
pResultMembers->InsertMember(aNew);
|
|
}
|
|
}
|
|
|
|
maDimMembers[nDim] = std::move(pResultMembers);
|
|
return *maDimMembers[nDim];
|
|
}
|
|
|
|
ScDPResultMember::ScDPResultMember(
|
|
const ScDPResultData* pData, const ScDPParentDimData& rParentDimData ) :
|
|
pResultData( pData ),
|
|
aParentDimData( rParentDimData ),
|
|
bHasElements( false ),
|
|
bForceSubTotal( false ),
|
|
bHasHiddenDetails( false ),
|
|
bInitialized( false ),
|
|
bAutoHidden( false ),
|
|
nMemberStep( 1 )
|
|
{
|
|
// pParentLevel/pMemberDesc is 0 for root members
|
|
}
|
|
|
|
ScDPResultMember::ScDPResultMember(
|
|
const ScDPResultData* pData, bool bForceSub ) :
|
|
pResultData( pData ),
|
|
bHasElements( false ),
|
|
bForceSubTotal( bForceSub ),
|
|
bHasHiddenDetails( false ),
|
|
bInitialized( false ),
|
|
bAutoHidden( false ),
|
|
nMemberStep( 1 )
|
|
{
|
|
}
|
|
ScDPResultMember::~ScDPResultMember()
|
|
{
|
|
}
|
|
|
|
OUString ScDPResultMember::GetName() const
|
|
{
|
|
const ScDPMember* pMemberDesc = GetDPMember();
|
|
if (pMemberDesc)
|
|
return pMemberDesc->GetNameStr( false );
|
|
else
|
|
return ScResId(STR_PIVOT_TOTAL); // root member
|
|
}
|
|
|
|
OUString ScDPResultMember::GetDisplayName( bool bLocaleIndependent ) const
|
|
{
|
|
const ScDPMember* pDPMember = GetDPMember();
|
|
if (!pDPMember)
|
|
return OUString();
|
|
|
|
ScDPItemData aItem(pDPMember->FillItemData());
|
|
if (aParentDimData.mpParentDim)
|
|
{
|
|
tools::Long nDim = aParentDimData.mpParentDim->GetDimension();
|
|
return pResultData->GetSource().GetData()->GetFormattedString(nDim, aItem, bLocaleIndependent);
|
|
}
|
|
|
|
return aItem.GetString();
|
|
}
|
|
|
|
ScDPItemData ScDPResultMember::FillItemData() const
|
|
{
|
|
const ScDPMember* pMemberDesc = GetDPMember();
|
|
if (pMemberDesc)
|
|
return pMemberDesc->FillItemData();
|
|
return ScDPItemData(ScResId(STR_PIVOT_TOTAL)); // root member
|
|
}
|
|
|
|
bool ScDPResultMember::IsNamedItem( SCROW nIndex ) const
|
|
{
|
|
//TODO: store ScDPMember pointer instead of ScDPMember ???
|
|
const ScDPMember* pMemberDesc = GetDPMember();
|
|
if (pMemberDesc)
|
|
return pMemberDesc->IsNamedItem(nIndex);
|
|
return false;
|
|
}
|
|
|
|
bool ScDPResultMember::IsValidEntry( const vector< SCROW >& aMembers ) const
|
|
{
|
|
if ( !IsValid() )
|
|
return false;
|
|
|
|
const ScDPResultDimension* pChildDim = GetChildDimension();
|
|
if (pChildDim)
|
|
{
|
|
if (aMembers.size() < 2)
|
|
return false;
|
|
|
|
vector<SCROW>::const_iterator itr = aMembers.begin();
|
|
vector<SCROW> aChildMembers(++itr, aMembers.end());
|
|
return pChildDim->IsValidEntry(aChildMembers);
|
|
}
|
|
else
|
|
return true;
|
|
}
|
|
|
|
void ScDPResultMember::InitFrom( const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLev,
|
|
size_t nPos, ScDPInitState& rInitState ,
|
|
bool bInitChild )
|
|
{
|
|
// with LateInit, initialize only those members that have data
|
|
if ( pResultData->IsLateInit() )
|
|
return;
|
|
|
|
bInitialized = true;
|
|
|
|
if (nPos >= ppDim.size())
|
|
return;
|
|
|
|
// skip child dimension if details are not shown
|
|
if ( GetDPMember() && !GetDPMember()->getShowDetails() )
|
|
{
|
|
// Show DataLayout dimension
|
|
nMemberStep = 1;
|
|
while ( nPos < ppDim.size() )
|
|
{
|
|
if ( ppDim[nPos]->getIsDataLayoutDimension() )
|
|
{
|
|
if ( !pChildDimension )
|
|
pChildDimension.reset( new ScDPResultDimension( pResultData ) );
|
|
pChildDimension->InitFrom( ppDim, ppLev, nPos, rInitState , false );
|
|
return;
|
|
}
|
|
else
|
|
{ //find next dim
|
|
nPos ++;
|
|
nMemberStep ++;
|
|
}
|
|
}
|
|
bHasHiddenDetails = true; // only if there is a next dimension
|
|
return;
|
|
}
|
|
|
|
if ( bInitChild )
|
|
{
|
|
pChildDimension.reset( new ScDPResultDimension( pResultData ) );
|
|
pChildDimension->InitFrom(ppDim, ppLev, nPos, rInitState);
|
|
}
|
|
}
|
|
|
|
void ScDPResultMember::LateInitFrom(
|
|
LateInitParams& rParams, const vector<SCROW>& pItemData, size_t nPos, ScDPInitState& rInitState)
|
|
{
|
|
// without LateInit, everything has already been initialized
|
|
if ( !pResultData->IsLateInit() )
|
|
return;
|
|
|
|
bInitialized = true;
|
|
|
|
if ( rParams.IsEnd( nPos ) /*nPos >= ppDim.size()*/)
|
|
// No next dimension. Bail out.
|
|
return;
|
|
|
|
// skip child dimension if details are not shown
|
|
if ( GetDPMember() && !GetDPMember()->getShowDetails() )
|
|
{
|
|
// Show DataLayout dimension
|
|
nMemberStep = 1;
|
|
while ( !rParams.IsEnd( nPos ) )
|
|
{
|
|
if ( rParams.GetDim( nPos )->getIsDataLayoutDimension() )
|
|
{
|
|
if ( !pChildDimension )
|
|
pChildDimension.reset( new ScDPResultDimension( pResultData ) );
|
|
|
|
// #i111462# reset InitChild flag only for this child dimension's LateInitFrom call,
|
|
// not for following members of parent dimensions
|
|
bool bWasInitChild = rParams.GetInitChild();
|
|
rParams.SetInitChild( false );
|
|
pChildDimension->LateInitFrom( rParams, pItemData, nPos, rInitState );
|
|
rParams.SetInitChild( bWasInitChild );
|
|
return;
|
|
}
|
|
else
|
|
{ //find next dim
|
|
nPos ++;
|
|
nMemberStep ++;
|
|
}
|
|
}
|
|
bHasHiddenDetails = true; // only if there is a next dimension
|
|
return;
|
|
}
|
|
|
|
// LateInitFrom is called several times...
|
|
if ( rParams.GetInitChild() )
|
|
{
|
|
if ( !pChildDimension )
|
|
pChildDimension.reset( new ScDPResultDimension( pResultData ) );
|
|
pChildDimension->LateInitFrom( rParams, pItemData, nPos, rInitState );
|
|
}
|
|
}
|
|
|
|
bool ScDPResultMember::IsSubTotalInTitle(tools::Long nMeasure) const
|
|
{
|
|
bool bRet = false;
|
|
if ( pChildDimension && /*pParentLevel*/GetParentLevel() &&
|
|
/*pParentLevel*/GetParentLevel()->IsOutlineLayout() && /*pParentLevel*/GetParentLevel()->IsSubtotalsAtTop() )
|
|
{
|
|
tools::Long nUserSubStart;
|
|
tools::Long nSubTotals = GetSubTotalCount( &nUserSubStart );
|
|
nSubTotals -= nUserSubStart; // visible count
|
|
if ( nSubTotals )
|
|
{
|
|
if ( nMeasure == SC_DPMEASURE_ALL )
|
|
nSubTotals *= pResultData->GetMeasureCount(); // number of subtotals that will be inserted
|
|
|
|
// only a single subtotal row will be shown in the outline title row
|
|
if ( nSubTotals == 1 )
|
|
bRet = true;
|
|
}
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
tools::Long ScDPResultMember::GetSize(tools::Long nMeasure) const
|
|
{
|
|
if ( !IsVisible() )
|
|
return 0;
|
|
const ScDPLevel* pParentLevel = GetParentLevel();
|
|
tools::Long nExtraSpace = 0;
|
|
if ( pParentLevel && pParentLevel->IsAddEmpty() )
|
|
++nExtraSpace;
|
|
|
|
if ( pChildDimension )
|
|
{
|
|
// outline layout takes up an extra row for the title only if subtotals aren't shown in that row
|
|
if ( pParentLevel && pParentLevel->IsOutlineLayout() && !IsSubTotalInTitle( nMeasure ) )
|
|
++nExtraSpace;
|
|
|
|
tools::Long nSize = pChildDimension->GetSize(nMeasure);
|
|
tools::Long nUserSubStart;
|
|
tools::Long nUserSubCount = GetSubTotalCount( &nUserSubStart );
|
|
nUserSubCount -= nUserSubStart; // for output size, use visible count
|
|
if ( nUserSubCount )
|
|
{
|
|
if ( nMeasure == SC_DPMEASURE_ALL )
|
|
nSize += pResultData->GetMeasureCount() * nUserSubCount;
|
|
else
|
|
nSize += nUserSubCount;
|
|
}
|
|
return nSize + nExtraSpace;
|
|
}
|
|
else
|
|
{
|
|
if ( nMeasure == SC_DPMEASURE_ALL )
|
|
return pResultData->GetMeasureCount() + nExtraSpace;
|
|
else
|
|
return 1 + nExtraSpace;
|
|
}
|
|
}
|
|
|
|
bool ScDPResultMember::IsVisible() const
|
|
{
|
|
if (!bInitialized)
|
|
return false;
|
|
|
|
if (!IsValid())
|
|
return false;
|
|
|
|
if (bHasElements)
|
|
return true;
|
|
|
|
// not initialized -> shouldn't be there at all
|
|
// (allocated only to preserve ordering)
|
|
const ScDPLevel* pParentLevel = GetParentLevel();
|
|
|
|
return (pParentLevel && pParentLevel->getShowEmpty());
|
|
}
|
|
|
|
bool ScDPResultMember::IsValid() const
|
|
{
|
|
// non-Valid members are left out of calculation
|
|
|
|
// was member set no invisible at the DataPilotSource?
|
|
const ScDPMember* pMemberDesc = GetDPMember();
|
|
if ( pMemberDesc && !pMemberDesc->isVisible() )
|
|
return false;
|
|
|
|
if ( bAutoHidden )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
tools::Long ScDPResultMember::GetSubTotalCount( tools::Long* pUserSubStart ) const
|
|
{
|
|
if ( pUserSubStart )
|
|
*pUserSubStart = 0; // default
|
|
|
|
const ScDPLevel* pParentLevel = GetParentLevel();
|
|
|
|
if ( bForceSubTotal ) // set if needed for root members
|
|
return 1; // grand total is always "automatic"
|
|
else if ( pParentLevel )
|
|
{
|
|
//TODO: direct access via ScDPLevel
|
|
|
|
uno::Sequence<sal_Int16> aSeq = pParentLevel->getSubTotals();
|
|
tools::Long nSequence = aSeq.getLength();
|
|
if ( nSequence && aSeq[0] != sheet::GeneralFunction2::AUTO )
|
|
{
|
|
// For manual subtotals, always add "automatic" as first function
|
|
// (used for calculation, but not for display, needed for sorting, see lcl_GetForceFunc)
|
|
|
|
++nSequence;
|
|
if ( pUserSubStart )
|
|
*pUserSubStart = 1; // visible subtotals start at 1
|
|
}
|
|
return nSequence;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void ScDPResultMember::ProcessData( const vector< SCROW >& aChildMembers, const ScDPResultDimension* pDataDim,
|
|
const vector< SCROW >& aDataMembers, const vector<ScDPValue>& aValues )
|
|
{
|
|
SetHasElements();
|
|
|
|
if (pChildDimension)
|
|
pChildDimension->ProcessData( aChildMembers, pDataDim, aDataMembers, aValues );
|
|
|
|
if ( !pDataRoot )
|
|
{
|
|
pDataRoot.reset( new ScDPDataMember( pResultData, nullptr ) );
|
|
if ( pDataDim )
|
|
pDataRoot->InitFrom( pDataDim ); // recursive
|
|
}
|
|
|
|
ScDPSubTotalState aSubState; // initial state
|
|
|
|
tools::Long nUserSubCount = GetSubTotalCount();
|
|
|
|
// Calculate at least automatic if no subtotals are selected,
|
|
// show only own values if there's no child dimension (innermost).
|
|
if ( !nUserSubCount || !pChildDimension )
|
|
nUserSubCount = 1;
|
|
|
|
const ScDPLevel* pParentLevel = GetParentLevel();
|
|
|
|
for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
|
|
{
|
|
// #i68338# if nUserSubCount is 1 (automatic only), don't set nRowSubTotalFunc
|
|
if ( pChildDimension && nUserSubCount > 1 )
|
|
{
|
|
aSubState.nRowSubTotalFunc = nUserPos;
|
|
aSubState.eRowForce = lcl_GetForceFunc( pParentLevel, nUserPos );
|
|
}
|
|
|
|
pDataRoot->ProcessData( aDataMembers, aValues, aSubState );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse subtotal string and replace all occurrences of '?' with the caption
|
|
* string. Do ensure that escaped characters are not translated.
|
|
*/
|
|
static OUString lcl_parseSubtotalName(std::u16string_view rSubStr, std::u16string_view rCaption)
|
|
{
|
|
OUStringBuffer aNewStr;
|
|
sal_Int32 n = rSubStr.size();
|
|
bool bEscaped = false;
|
|
for (sal_Int32 i = 0; i < n; ++i)
|
|
{
|
|
sal_Unicode c = rSubStr[i];
|
|
if (!bEscaped && c == '\\')
|
|
{
|
|
bEscaped = true;
|
|
continue;
|
|
}
|
|
|
|
if (!bEscaped && c == '?')
|
|
aNewStr.append(rCaption);
|
|
else
|
|
aNewStr.append(c);
|
|
bEscaped = false;
|
|
}
|
|
return aNewStr.makeStringAndClear();
|
|
}
|
|
|
|
void ScDPResultMember::FillMemberResults(
|
|
uno::Sequence<sheet::MemberResult>* pSequences, tools::Long& rPos, tools::Long nMeasure, bool bRoot,
|
|
const OUString* pMemberName, const OUString* pMemberCaption )
|
|
{
|
|
// IsVisible() test is in ScDPResultDimension::FillMemberResults
|
|
// (not on data layout dimension)
|
|
|
|
if (!pSequences->hasElements())
|
|
// empty sequence. Bail out.
|
|
return;
|
|
|
|
tools::Long nSize = GetSize(nMeasure);
|
|
sheet::MemberResult* pArray = pSequences->getArray();
|
|
OSL_ENSURE( rPos+nSize <= pSequences->getLength(), "bumm" );
|
|
|
|
bool bIsNumeric = false;
|
|
double fValue = std::numeric_limits<double>::quiet_NaN();
|
|
OUString aName;
|
|
if ( pMemberName ) // if pMemberName != NULL, use instead of real member name
|
|
{
|
|
aName = *pMemberName;
|
|
}
|
|
else
|
|
{
|
|
ScDPItemData aItemData(FillItemData());
|
|
if (aParentDimData.mpParentDim)
|
|
{
|
|
tools::Long nDim = aParentDimData.mpParentDim->GetDimension();
|
|
aName = pResultData->GetSource().GetData()->GetFormattedString(nDim, aItemData, false);
|
|
}
|
|
else
|
|
{
|
|
tools::Long nDim = -1;
|
|
const ScDPMember* pMem = GetDPMember();
|
|
if (pMem)
|
|
nDim = pMem->GetDim();
|
|
aName = pResultData->GetSource().GetData()->GetFormattedString(nDim, aItemData, false);
|
|
}
|
|
|
|
ScDPItemData::Type eType = aItemData.GetType();
|
|
bIsNumeric = eType == ScDPItemData::Value || eType == ScDPItemData::GroupValue;
|
|
// IsValue() is not identical to bIsNumeric, i.e.
|
|
// ScDPItemData::GroupValue is excluded and not stored in the double,
|
|
// so even if the item is numeric the Value may be NaN.
|
|
if (aItemData.IsValue())
|
|
fValue = aItemData.GetValue();
|
|
}
|
|
|
|
const ScDPDimension* pParentDim = GetParentDim();
|
|
if ( bIsNumeric && pParentDim && pResultData->IsNumOrDateGroup( pParentDim->GetDimension() ) )
|
|
{
|
|
// Numeric group dimensions use numeric entries for proper sorting,
|
|
// but the group titles must be output as text.
|
|
bIsNumeric = false;
|
|
}
|
|
|
|
OUString aCaption = aName;
|
|
const ScDPMember* pMemberDesc = GetDPMember();
|
|
if (pMemberDesc)
|
|
{
|
|
const std::optional<OUString> & pLayoutName = pMemberDesc->GetLayoutName();
|
|
if (pLayoutName)
|
|
{
|
|
aCaption = *pLayoutName;
|
|
bIsNumeric = false; // layout name is always non-numeric.
|
|
}
|
|
}
|
|
|
|
if ( pMemberCaption ) // use pMemberCaption if != NULL
|
|
aCaption = *pMemberCaption;
|
|
if (aCaption.isEmpty())
|
|
aCaption = ScResId(STR_EMPTYDATA);
|
|
|
|
if (bIsNumeric)
|
|
pArray[rPos].Flags |= sheet::MemberResultFlags::NUMERIC;
|
|
else
|
|
pArray[rPos].Flags &= ~sheet::MemberResultFlags::NUMERIC;
|
|
|
|
const ScDPLevel* pParentLevel = GetParentLevel();
|
|
if ( nSize && !bRoot ) // root is overwritten by first dimension
|
|
{
|
|
pArray[rPos].Name = aName;
|
|
pArray[rPos].Caption = aCaption;
|
|
pArray[rPos].Flags |= sheet::MemberResultFlags::HASMEMBER;
|
|
pArray[rPos].Value = fValue;
|
|
|
|
// set "continue" flag (removed for subtotals later)
|
|
for (tools::Long i=1; i<nSize; i++)
|
|
{
|
|
pArray[rPos+i].Flags |= sheet::MemberResultFlags::CONTINUE;
|
|
// tdf#113002 - add numeric flag to recurring data fields
|
|
if (bIsNumeric)
|
|
pArray[rPos + i].Flags |= sheet::MemberResultFlags::NUMERIC;
|
|
}
|
|
|
|
if ( pParentLevel && pParentLevel->getRepeatItemLabels() )
|
|
{
|
|
tools::Long nSizeNonEmpty = nSize;
|
|
if ( pParentLevel->IsAddEmpty() )
|
|
--nSizeNonEmpty;
|
|
for (tools::Long i=1; i<nSizeNonEmpty; i++)
|
|
{
|
|
pArray[rPos+i].Name = aName;
|
|
pArray[rPos+i].Caption = aCaption;
|
|
pArray[rPos+i].Flags |= sheet::MemberResultFlags::HASMEMBER;
|
|
pArray[rPos+i].Value = fValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
tools::Long nExtraSpace = 0;
|
|
if ( pParentLevel && pParentLevel->IsAddEmpty() )
|
|
++nExtraSpace;
|
|
|
|
bool bTitleLine = false;
|
|
if ( pParentLevel && pParentLevel->IsOutlineLayout() )
|
|
bTitleLine = true;
|
|
|
|
// if the subtotals are shown at the top (title row) in outline layout,
|
|
// no extra row for the subtotals is needed
|
|
bool bSubTotalInTitle = IsSubTotalInTitle( nMeasure );
|
|
|
|
bool bHasChild = ( pChildDimension != nullptr );
|
|
if (bHasChild)
|
|
{
|
|
if ( bTitleLine ) // in tabular layout the title is on a separate row
|
|
++rPos; // -> fill child dimension one row below
|
|
|
|
if (bRoot) // same sequence for root member
|
|
pChildDimension->FillMemberResults( pSequences, rPos, nMeasure );
|
|
else
|
|
pChildDimension->FillMemberResults( pSequences + nMemberStep/*1*/, rPos, nMeasure );
|
|
|
|
if ( bTitleLine ) // title row is included in GetSize, so the following
|
|
--rPos; // positions are calculated with the normal values
|
|
}
|
|
|
|
rPos += nSize;
|
|
|
|
tools::Long nUserSubStart;
|
|
tools::Long nUserSubCount = GetSubTotalCount(&nUserSubStart);
|
|
if ( !nUserSubCount || !pChildDimension || bSubTotalInTitle )
|
|
return;
|
|
|
|
tools::Long nMemberMeasure = nMeasure;
|
|
tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
|
|
|
|
rPos -= nSubSize * (nUserSubCount - nUserSubStart); // GetSize includes space for SubTotal
|
|
rPos -= nExtraSpace; // GetSize includes the empty line
|
|
|
|
for (tools::Long nUserPos=nUserSubStart; nUserPos<nUserSubCount; nUserPos++)
|
|
{
|
|
for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
|
|
{
|
|
if ( nMeasure == SC_DPMEASURE_ALL )
|
|
nMemberMeasure = nSubCount;
|
|
|
|
ScSubTotalFunc eForce = SUBTOTAL_FUNC_NONE;
|
|
if (bHasChild)
|
|
eForce = lcl_GetForceFunc( pParentLevel, nUserPos );
|
|
|
|
bool bTotalResult = false;
|
|
OUString aSubStr = aCaption + " " + pResultData->GetMeasureString(nMemberMeasure, false, eForce, bTotalResult);
|
|
|
|
if (bTotalResult)
|
|
{
|
|
if (pMemberDesc)
|
|
{
|
|
// single data field layout.
|
|
const std::optional<OUString> & pSubtotalName = pParentDim->GetSubtotalName();
|
|
if (pSubtotalName)
|
|
aSubStr = lcl_parseSubtotalName(*pSubtotalName, aCaption);
|
|
pArray[rPos].Flags &= ~sheet::MemberResultFlags::GRANDTOTAL;
|
|
}
|
|
else
|
|
{
|
|
// root member - subtotal (grand total?) for multi-data field layout.
|
|
const std::optional<OUString> & pGrandTotalName = pResultData->GetSource().GetGrandTotalName();
|
|
if (pGrandTotalName)
|
|
aSubStr = *pGrandTotalName;
|
|
pArray[rPos].Flags |= sheet::MemberResultFlags::GRANDTOTAL;
|
|
}
|
|
}
|
|
|
|
fValue = std::numeric_limits<double>::quiet_NaN(); /* TODO: any numeric value to obtain? */
|
|
pArray[rPos].Name = aName;
|
|
pArray[rPos].Caption = aSubStr;
|
|
pArray[rPos].Flags = ( pArray[rPos].Flags |
|
|
( sheet::MemberResultFlags::HASMEMBER | sheet::MemberResultFlags::SUBTOTAL) ) &
|
|
~sheet::MemberResultFlags::CONTINUE;
|
|
pArray[rPos].Value = fValue;
|
|
|
|
if ( nMeasure == SC_DPMEASURE_ALL )
|
|
{
|
|
// data layout dimension is (direct/indirect) child of this.
|
|
// data layout dimension must have name for all entries.
|
|
|
|
uno::Sequence<sheet::MemberResult>* pLayoutSeq = pSequences;
|
|
if (!bRoot)
|
|
++pLayoutSeq;
|
|
ScDPResultDimension* pLayoutDim = pChildDimension.get();
|
|
while ( pLayoutDim && !pLayoutDim->IsDataLayout() )
|
|
{
|
|
pLayoutDim = pLayoutDim->GetFirstChildDimension();
|
|
++pLayoutSeq;
|
|
}
|
|
if ( pLayoutDim )
|
|
{
|
|
sheet::MemberResult* pLayoutArray = pLayoutSeq->getArray();
|
|
pLayoutArray[rPos].Name = pResultData->GetMeasureDimensionName(nMemberMeasure);
|
|
}
|
|
}
|
|
|
|
rPos += 1;
|
|
}
|
|
}
|
|
|
|
rPos += nExtraSpace; // add again (subtracted above)
|
|
}
|
|
|
|
void ScDPResultMember::FillDataResults(
|
|
const ScDPResultMember* pRefMember,
|
|
ScDPResultFilterContext& rFilterCxt, uno::Sequence<uno::Sequence<sheet::DataResult> >& rSequence,
|
|
tools::Long nMeasure) const
|
|
{
|
|
std::unique_ptr<FilterStack> pFilterStack;
|
|
const ScDPMember* pDPMember = GetDPMember();
|
|
if (pDPMember)
|
|
{
|
|
// Root result has no corresponding DP member. Only take the non-root results.
|
|
pFilterStack.reset(new FilterStack(rFilterCxt.maFilters));
|
|
pFilterStack->pushDimValue( GetDisplayName( false), GetDisplayName( true));
|
|
}
|
|
|
|
// IsVisible() test is in ScDPResultDimension::FillDataResults
|
|
// (not on data layout dimension)
|
|
const ScDPLevel* pParentLevel = GetParentLevel();
|
|
sal_Int32 nStartRow = rFilterCxt.mnRow;
|
|
|
|
tools::Long nExtraSpace = 0;
|
|
if ( pParentLevel && pParentLevel->IsAddEmpty() )
|
|
++nExtraSpace;
|
|
|
|
bool bTitleLine = false;
|
|
if ( pParentLevel && pParentLevel->IsOutlineLayout() )
|
|
bTitleLine = true;
|
|
|
|
bool bSubTotalInTitle = IsSubTotalInTitle( nMeasure );
|
|
|
|
bool bHasChild = ( pChildDimension != nullptr );
|
|
if (bHasChild)
|
|
{
|
|
if ( bTitleLine ) // in tabular layout the title is on a separate row
|
|
++rFilterCxt.mnRow; // -> fill child dimension one row below
|
|
|
|
sal_Int32 nOldRow = rFilterCxt.mnRow;
|
|
pChildDimension->FillDataResults(pRefMember, rFilterCxt, rSequence, nMeasure);
|
|
rFilterCxt.mnRow = nOldRow; // Revert to the original row before the call.
|
|
|
|
rFilterCxt.mnRow += GetSize( nMeasure );
|
|
|
|
if ( bTitleLine ) // title row is included in GetSize, so the following
|
|
--rFilterCxt.mnRow; // positions are calculated with the normal values
|
|
}
|
|
|
|
tools::Long nUserSubStart;
|
|
tools::Long nUserSubCount = GetSubTotalCount(&nUserSubStart);
|
|
if ( !nUserSubCount && bHasChild )
|
|
return;
|
|
|
|
// Calculate at least automatic if no subtotals are selected,
|
|
// show only own values if there's no child dimension (innermost).
|
|
if ( !nUserSubCount || !bHasChild )
|
|
{
|
|
nUserSubCount = 1;
|
|
nUserSubStart = 0;
|
|
}
|
|
|
|
tools::Long nMemberMeasure = nMeasure;
|
|
tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
|
|
if (bHasChild)
|
|
{
|
|
rFilterCxt.mnRow -= nSubSize * ( nUserSubCount - nUserSubStart ); // GetSize includes space for SubTotal
|
|
rFilterCxt.mnRow -= nExtraSpace; // GetSize includes the empty line
|
|
}
|
|
|
|
tools::Long nMoveSubTotal = 0;
|
|
if ( bSubTotalInTitle )
|
|
{
|
|
nMoveSubTotal = rFilterCxt.mnRow - nStartRow; // force to first (title) row
|
|
rFilterCxt.mnRow = nStartRow;
|
|
}
|
|
|
|
if ( pDataRoot )
|
|
{
|
|
ScDPSubTotalState aSubState; // initial state
|
|
|
|
for (tools::Long nUserPos=nUserSubStart; nUserPos<nUserSubCount; nUserPos++)
|
|
{
|
|
if ( bHasChild && nUserSubCount > 1 )
|
|
{
|
|
aSubState.nRowSubTotalFunc = nUserPos;
|
|
aSubState.eRowForce = lcl_GetForceFunc( /*pParentLevel*/GetParentLevel() , nUserPos );
|
|
}
|
|
|
|
for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
|
|
{
|
|
if ( nMeasure == SC_DPMEASURE_ALL )
|
|
nMemberMeasure = nSubCount;
|
|
else if ( pResultData->GetColStartMeasure() == SC_DPMEASURE_ALL )
|
|
nMemberMeasure = SC_DPMEASURE_ALL;
|
|
|
|
OSL_ENSURE( rFilterCxt.mnRow < rSequence.getLength(), "bumm" );
|
|
rFilterCxt.mnCol = 0;
|
|
if (pRefMember->IsVisible())
|
|
{
|
|
uno::Sequence<sheet::DataResult>& rSubSeq = rSequence.getArray()[rFilterCxt.mnRow];
|
|
pDataRoot->FillDataRow(pRefMember, rFilterCxt, rSubSeq, nMemberMeasure, bHasChild, aSubState);
|
|
}
|
|
rFilterCxt.mnRow += 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
rFilterCxt.mnRow += nSubSize * ( nUserSubCount - nUserSubStart ); // empty rows occur when ShowEmpty is true
|
|
|
|
// add extra space again if subtracted from GetSize above,
|
|
// add to own size if no children
|
|
rFilterCxt.mnRow += nExtraSpace;
|
|
rFilterCxt.mnRow += nMoveSubTotal;
|
|
}
|
|
|
|
void ScDPResultMember::UpdateDataResults( const ScDPResultMember* pRefMember, tools::Long nMeasure ) const
|
|
{
|
|
// IsVisible() test is in ScDPResultDimension::FillDataResults
|
|
// (not on data layout dimension)
|
|
|
|
bool bHasChild = ( pChildDimension != nullptr );
|
|
|
|
tools::Long nUserSubCount = GetSubTotalCount();
|
|
|
|
// process subtotals even if not shown
|
|
|
|
// Calculate at least automatic if no subtotals are selected,
|
|
// show only own values if there's no child dimension (innermost).
|
|
if (!nUserSubCount || !bHasChild)
|
|
nUserSubCount = 1;
|
|
|
|
tools::Long nMemberMeasure = nMeasure;
|
|
tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
|
|
|
|
if (pDataRoot)
|
|
{
|
|
ScDPSubTotalState aSubState; // initial state
|
|
|
|
for (tools::Long nUserPos = 0; nUserPos < nUserSubCount; ++nUserPos) // including hidden "automatic"
|
|
{
|
|
if (bHasChild && nUserSubCount > 1)
|
|
{
|
|
aSubState.nRowSubTotalFunc = nUserPos;
|
|
aSubState.eRowForce = lcl_GetForceFunc(GetParentLevel(), nUserPos);
|
|
}
|
|
|
|
for (tools::Long nSubCount = 0; nSubCount < nSubSize; ++nSubCount)
|
|
{
|
|
if (nMeasure == SC_DPMEASURE_ALL)
|
|
nMemberMeasure = nSubCount;
|
|
else if (pResultData->GetColStartMeasure() == SC_DPMEASURE_ALL)
|
|
nMemberMeasure = SC_DPMEASURE_ALL;
|
|
|
|
pDataRoot->UpdateDataRow(pRefMember, nMemberMeasure, bHasChild, aSubState);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bHasChild) // child dimension must be processed last, so the column total is known
|
|
{
|
|
pChildDimension->UpdateDataResults( pRefMember, nMeasure );
|
|
}
|
|
}
|
|
|
|
void ScDPResultMember::SortMembers( ScDPResultMember* pRefMember )
|
|
{
|
|
bool bHasChild = ( pChildDimension != nullptr );
|
|
if (bHasChild)
|
|
pChildDimension->SortMembers( pRefMember ); // sorting is done at the dimension
|
|
|
|
if ( IsRoot() && pDataRoot )
|
|
{
|
|
// use the row root member to sort columns
|
|
// sub total count is always 1
|
|
|
|
pDataRoot->SortMembers( pRefMember );
|
|
}
|
|
}
|
|
|
|
void ScDPResultMember::DoAutoShow( ScDPResultMember* pRefMember )
|
|
{
|
|
bool bHasChild = ( pChildDimension != nullptr );
|
|
if (bHasChild)
|
|
pChildDimension->DoAutoShow( pRefMember ); // sorting is done at the dimension
|
|
|
|
if ( IsRoot()&& pDataRoot )
|
|
{
|
|
// use the row root member to sort columns
|
|
// sub total count is always 1
|
|
|
|
pDataRoot->DoAutoShow( pRefMember );
|
|
}
|
|
}
|
|
|
|
void ScDPResultMember::ResetResults()
|
|
{
|
|
if (pDataRoot)
|
|
pDataRoot->ResetResults();
|
|
|
|
if (pChildDimension)
|
|
pChildDimension->ResetResults();
|
|
}
|
|
|
|
void ScDPResultMember::UpdateRunningTotals( const ScDPResultMember* pRefMember, tools::Long nMeasure,
|
|
ScDPRunningTotalState& rRunning, ScDPRowTotals& rTotals ) const
|
|
{
|
|
// IsVisible() test is in ScDPResultDimension::FillDataResults
|
|
// (not on data layout dimension)
|
|
|
|
rTotals.SetInColRoot( IsRoot() );
|
|
|
|
bool bHasChild = ( pChildDimension != nullptr );
|
|
|
|
tools::Long nUserSubCount = GetSubTotalCount();
|
|
//if ( nUserSubCount || !bHasChild )
|
|
{
|
|
// Calculate at least automatic if no subtotals are selected,
|
|
// show only own values if there's no child dimension (innermost).
|
|
if ( !nUserSubCount || !bHasChild )
|
|
nUserSubCount = 1;
|
|
|
|
tools::Long nMemberMeasure = nMeasure;
|
|
tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
|
|
|
|
if ( pDataRoot )
|
|
{
|
|
ScDPSubTotalState aSubState; // initial state
|
|
|
|
for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
|
|
{
|
|
if ( bHasChild && nUserSubCount > 1 )
|
|
{
|
|
aSubState.nRowSubTotalFunc = nUserPos;
|
|
aSubState.eRowForce = lcl_GetForceFunc(GetParentLevel(), nUserPos);
|
|
}
|
|
|
|
for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
|
|
{
|
|
if ( nMeasure == SC_DPMEASURE_ALL )
|
|
nMemberMeasure = nSubCount;
|
|
else if ( pResultData->GetColStartMeasure() == SC_DPMEASURE_ALL )
|
|
nMemberMeasure = SC_DPMEASURE_ALL;
|
|
|
|
if (pRefMember->IsVisible())
|
|
pDataRoot->UpdateRunningTotals(
|
|
pRefMember, nMemberMeasure, bHasChild, aSubState, rRunning, rTotals, *this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bHasChild) // child dimension must be processed last, so the column total is known
|
|
{
|
|
pChildDimension->UpdateRunningTotals( pRefMember, nMeasure, rRunning, rTotals );
|
|
}
|
|
}
|
|
|
|
#if DUMP_PIVOT_TABLE
|
|
void ScDPResultMember::DumpState( const ScDPResultMember* pRefMember, ScDocument* pDoc, ScAddress& rPos ) const
|
|
{
|
|
dumpRow(u"ScDPResultMember"_ustr, GetName(), nullptr, pDoc, rPos);
|
|
SCROW nStartRow = rPos.Row();
|
|
|
|
if (pDataRoot)
|
|
pDataRoot->DumpState( pRefMember, pDoc, rPos );
|
|
|
|
if (pChildDimension)
|
|
pChildDimension->DumpState( pRefMember, pDoc, rPos );
|
|
|
|
indent(pDoc, nStartRow, rPos);
|
|
}
|
|
|
|
void ScDPResultMember::Dump(int nIndent) const
|
|
{
|
|
std::string aIndent(nIndent*2, ' ');
|
|
std::cout << aIndent << "-- result member '" << GetName() << "'" << std::endl;
|
|
|
|
std::cout << aIndent << " column totals" << std::endl;
|
|
for (const ScDPAggData* p = &aColTotal; p; p = p->GetExistingChild())
|
|
p->Dump(nIndent+1);
|
|
|
|
if (pChildDimension)
|
|
pChildDimension->Dump(nIndent+1);
|
|
|
|
if (pDataRoot)
|
|
{
|
|
std::cout << aIndent << " data root" << std::endl;
|
|
pDataRoot->Dump(nIndent+1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
ScDPAggData* ScDPResultMember::GetColTotal( tools::Long nMeasure ) const
|
|
{
|
|
return lcl_GetChildTotal( const_cast<ScDPAggData*>(&aColTotal), nMeasure );
|
|
}
|
|
|
|
void ScDPResultMember::FillVisibilityData(ScDPResultVisibilityData& rData) const
|
|
{
|
|
if (pChildDimension)
|
|
pChildDimension->FillVisibilityData(rData);
|
|
}
|
|
|
|
ScDPDataMember::ScDPDataMember( const ScDPResultData* pData, const ScDPResultMember* pRes ) :
|
|
pResultData( pData ),
|
|
pResultMember( pRes )
|
|
{
|
|
// pResultMember is 0 for root members
|
|
}
|
|
|
|
ScDPDataMember::~ScDPDataMember()
|
|
{
|
|
}
|
|
|
|
OUString ScDPDataMember::GetName() const
|
|
{
|
|
if (pResultMember)
|
|
return pResultMember->GetName();
|
|
else
|
|
return OUString();
|
|
}
|
|
|
|
bool ScDPDataMember::IsVisible() const
|
|
{
|
|
if (pResultMember)
|
|
return pResultMember->IsVisible();
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool ScDPDataMember::IsNamedItem( SCROW nRow ) const
|
|
{
|
|
if (pResultMember)
|
|
return pResultMember->IsNamedItem(nRow);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool ScDPDataMember::HasHiddenDetails() const
|
|
{
|
|
if (pResultMember)
|
|
return pResultMember->HasHiddenDetails();
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void ScDPDataMember::InitFrom( const ScDPResultDimension* pDim )
|
|
{
|
|
if ( !pChildDimension )
|
|
pChildDimension.reset( new ScDPDataDimension(pResultData) );
|
|
pChildDimension->InitFrom(pDim);
|
|
}
|
|
|
|
const tools::Long SC_SUBTOTALPOS_AUTO = -1; // default
|
|
const tools::Long SC_SUBTOTALPOS_SKIP = -2; // don't use
|
|
|
|
static tools::Long lcl_GetSubTotalPos( const ScDPSubTotalState& rSubState )
|
|
{
|
|
if ( rSubState.nColSubTotalFunc >= 0 && rSubState.nRowSubTotalFunc >= 0 &&
|
|
rSubState.nColSubTotalFunc != rSubState.nRowSubTotalFunc )
|
|
{
|
|
// #i68338# don't return the same index for different combinations (leading to repeated updates),
|
|
// return a "don't use" value instead
|
|
|
|
return SC_SUBTOTALPOS_SKIP;
|
|
}
|
|
|
|
tools::Long nRet = SC_SUBTOTALPOS_AUTO;
|
|
if ( rSubState.nColSubTotalFunc >= 0 ) nRet = rSubState.nColSubTotalFunc;
|
|
if ( rSubState.nRowSubTotalFunc >= 0 ) nRet = rSubState.nRowSubTotalFunc;
|
|
return nRet;
|
|
}
|
|
|
|
void ScDPDataMember::UpdateValues( const vector<ScDPValue>& aValues, const ScDPSubTotalState& rSubState )
|
|
{
|
|
//TODO: find out how many and which subtotals are used
|
|
|
|
ScDPAggData* pAgg = &aAggregate;
|
|
|
|
tools::Long nSubPos = lcl_GetSubTotalPos(rSubState);
|
|
if (nSubPos == SC_SUBTOTALPOS_SKIP)
|
|
return;
|
|
if (nSubPos > 0)
|
|
{
|
|
tools::Long nSkip = nSubPos * pResultData->GetMeasureCount();
|
|
for (tools::Long i=0; i<nSkip; i++)
|
|
pAgg = pAgg->GetChild(); // created if not there
|
|
}
|
|
|
|
size_t nCount = aValues.size();
|
|
for (size_t nPos = 0; nPos < nCount; ++nPos)
|
|
{
|
|
pAgg->Update(aValues[nPos], pResultData->GetMeasureFunction(nPos), rSubState);
|
|
pAgg = pAgg->GetChild();
|
|
}
|
|
}
|
|
|
|
void ScDPDataMember::ProcessData( const vector< SCROW >& aChildMembers, const vector<ScDPValue>& aValues,
|
|
const ScDPSubTotalState& rSubState )
|
|
{
|
|
if ( pResultData->IsLateInit() && !pChildDimension && pResultMember && pResultMember->GetChildDimension() )
|
|
{
|
|
// if this DataMember doesn't have a child dimension because the ResultMember's
|
|
// child dimension wasn't there yet during this DataMembers's creation,
|
|
// create the child dimension now
|
|
InitFrom( pResultMember->GetChildDimension() );
|
|
}
|
|
|
|
tools::Long nUserSubCount = pResultMember ? pResultMember->GetSubTotalCount() : 0;
|
|
|
|
// Calculate at least automatic if no subtotals are selected,
|
|
// show only own values if there's no child dimension (innermost).
|
|
if ( !nUserSubCount || !pChildDimension )
|
|
nUserSubCount = 1;
|
|
|
|
ScDPSubTotalState aLocalSubState = rSubState; // keep row state, modify column
|
|
for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
|
|
{
|
|
if ( pChildDimension && nUserSubCount > 1 )
|
|
{
|
|
const ScDPLevel* pForceLevel = pResultMember ? pResultMember->GetParentLevel() : nullptr;
|
|
aLocalSubState.nColSubTotalFunc = nUserPos;
|
|
aLocalSubState.eColForce = lcl_GetForceFunc( pForceLevel, nUserPos );
|
|
}
|
|
|
|
UpdateValues( aValues, aLocalSubState );
|
|
}
|
|
|
|
if (pChildDimension)
|
|
pChildDimension->ProcessData( aChildMembers, aValues, rSubState ); // with unmodified subtotal state
|
|
}
|
|
|
|
bool ScDPDataMember::HasData( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
|
|
{
|
|
if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE && rSubState.eRowForce != SUBTOTAL_FUNC_NONE &&
|
|
rSubState.eColForce != rSubState.eRowForce )
|
|
return false;
|
|
|
|
// HasData can be different between measures!
|
|
|
|
const ScDPAggData* pAgg = GetConstAggData( nMeasure, rSubState );
|
|
if (!pAgg)
|
|
return false; //TODO: error?
|
|
|
|
return pAgg->HasData();
|
|
}
|
|
|
|
bool ScDPDataMember::HasError( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
|
|
{
|
|
const ScDPAggData* pAgg = GetConstAggData( nMeasure, rSubState );
|
|
if (!pAgg)
|
|
return true;
|
|
|
|
return pAgg->HasError();
|
|
}
|
|
|
|
double ScDPDataMember::GetAggregate( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
|
|
{
|
|
const ScDPAggData* pAgg = GetConstAggData( nMeasure, rSubState );
|
|
if (!pAgg)
|
|
return DBL_MAX; //TODO: error?
|
|
|
|
return pAgg->GetResult();
|
|
}
|
|
|
|
ScDPAggData* ScDPDataMember::GetAggData( tools::Long nMeasure, const ScDPSubTotalState& rSubState )
|
|
{
|
|
OSL_ENSURE( nMeasure >= 0, "GetAggData: no measure" );
|
|
|
|
ScDPAggData* pAgg = &aAggregate;
|
|
tools::Long nSkip = nMeasure;
|
|
tools::Long nSubPos = lcl_GetSubTotalPos(rSubState);
|
|
if (nSubPos == SC_SUBTOTALPOS_SKIP)
|
|
return nullptr;
|
|
if (nSubPos > 0)
|
|
nSkip += nSubPos * pResultData->GetMeasureCount();
|
|
|
|
for ( tools::Long nPos=0; nPos<nSkip; nPos++ )
|
|
pAgg = pAgg->GetChild(); //TODO: need to create children here?
|
|
|
|
return pAgg;
|
|
}
|
|
|
|
const ScDPAggData* ScDPDataMember::GetConstAggData( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
|
|
{
|
|
OSL_ENSURE( nMeasure >= 0, "GetConstAggData: no measure" );
|
|
|
|
const ScDPAggData* pAgg = &aAggregate;
|
|
tools::Long nSkip = nMeasure;
|
|
tools::Long nSubPos = lcl_GetSubTotalPos(rSubState);
|
|
if (nSubPos == SC_SUBTOTALPOS_SKIP)
|
|
return nullptr;
|
|
if (nSubPos > 0)
|
|
nSkip += nSubPos * pResultData->GetMeasureCount();
|
|
|
|
for ( tools::Long nPos=0; nPos<nSkip; nPos++ )
|
|
{
|
|
pAgg = pAgg->GetExistingChild();
|
|
if (!pAgg)
|
|
return nullptr;
|
|
}
|
|
|
|
return pAgg;
|
|
}
|
|
|
|
void ScDPDataMember::FillDataRow(
|
|
const ScDPResultMember* pRefMember, ScDPResultFilterContext& rFilterCxt,
|
|
uno::Sequence<sheet::DataResult>& rSequence, tools::Long nMeasure, bool bIsSubTotalRow,
|
|
const ScDPSubTotalState& rSubState) const
|
|
{
|
|
std::unique_ptr<FilterStack> pFilterStack;
|
|
if (pResultMember)
|
|
{
|
|
// Topmost data member (pResultMember=NULL) doesn't need to be handled
|
|
// since its immediate parent result member is linked to the same
|
|
// dimension member.
|
|
pFilterStack.reset(new FilterStack(rFilterCxt.maFilters));
|
|
pFilterStack->pushDimValue( pResultMember->GetDisplayName( false), pResultMember->GetDisplayName( true));
|
|
}
|
|
|
|
OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
|
|
|
|
tools::Long nStartCol = rFilterCxt.mnCol;
|
|
|
|
const ScDPDataDimension* pDataChild = GetChildDimension();
|
|
const ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
|
|
|
|
const ScDPLevel* pRefParentLevel = pRefMember->GetParentLevel();
|
|
|
|
tools::Long nExtraSpace = 0;
|
|
if ( pRefParentLevel && pRefParentLevel->IsAddEmpty() )
|
|
++nExtraSpace;
|
|
|
|
bool bTitleLine = false;
|
|
if ( pRefParentLevel && pRefParentLevel->IsOutlineLayout() )
|
|
bTitleLine = true;
|
|
|
|
bool bSubTotalInTitle = pRefMember->IsSubTotalInTitle( nMeasure );
|
|
|
|
// leave space for children even if the DataMember hasn't been initialized
|
|
// (pDataChild is null then, this happens when no values for it are in this row)
|
|
bool bHasChild = ( pRefChild != nullptr );
|
|
|
|
if ( bHasChild )
|
|
{
|
|
if ( bTitleLine ) // in tabular layout the title is on a separate column
|
|
++rFilterCxt.mnCol; // -> fill child dimension one column below
|
|
|
|
if ( pDataChild )
|
|
{
|
|
tools::Long nOldCol = rFilterCxt.mnCol;
|
|
pDataChild->FillDataRow(pRefChild, rFilterCxt, rSequence, nMeasure, bIsSubTotalRow, rSubState);
|
|
rFilterCxt.mnCol = nOldCol; // Revert to the old column value before the call.
|
|
}
|
|
rFilterCxt.mnCol += static_cast<sal_uInt16>(pRefMember->GetSize( nMeasure ));
|
|
|
|
if ( bTitleLine ) // title column is included in GetSize, so the following
|
|
--rFilterCxt.mnCol; // positions are calculated with the normal values
|
|
}
|
|
|
|
tools::Long nUserSubStart;
|
|
tools::Long nUserSubCount = pRefMember->GetSubTotalCount(&nUserSubStart);
|
|
if ( !nUserSubCount && bHasChild )
|
|
return;
|
|
|
|
// Calculate at least automatic if no subtotals are selected,
|
|
// show only own values if there's no child dimension (innermost).
|
|
if ( !nUserSubCount || !bHasChild )
|
|
{
|
|
nUserSubCount = 1;
|
|
nUserSubStart = 0;
|
|
}
|
|
|
|
ScDPSubTotalState aLocalSubState(rSubState); // keep row state, modify column
|
|
|
|
tools::Long nMemberMeasure = nMeasure;
|
|
tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
|
|
if (bHasChild)
|
|
{
|
|
rFilterCxt.mnCol -= nSubSize * ( nUserSubCount - nUserSubStart ); // GetSize includes space for SubTotal
|
|
rFilterCxt.mnCol -= nExtraSpace; // GetSize includes the empty line
|
|
}
|
|
|
|
tools::Long nMoveSubTotal = 0;
|
|
if ( bSubTotalInTitle )
|
|
{
|
|
nMoveSubTotal = rFilterCxt.mnCol - nStartCol; // force to first (title) column
|
|
rFilterCxt.mnCol = nStartCol;
|
|
}
|
|
|
|
for (tools::Long nUserPos=nUserSubStart; nUserPos<nUserSubCount; nUserPos++)
|
|
{
|
|
if ( pChildDimension && nUserSubCount > 1 )
|
|
{
|
|
const ScDPLevel* pForceLevel = pResultMember ? pResultMember->GetParentLevel() : nullptr;
|
|
aLocalSubState.nColSubTotalFunc = nUserPos;
|
|
aLocalSubState.eColForce = lcl_GetForceFunc( pForceLevel, nUserPos );
|
|
}
|
|
|
|
for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
|
|
{
|
|
if ( nMeasure == SC_DPMEASURE_ALL )
|
|
nMemberMeasure = nSubCount;
|
|
|
|
OSL_ENSURE( rFilterCxt.mnCol < rSequence.getLength(), "bumm" );
|
|
sheet::DataResult& rRes = rSequence.getArray()[rFilterCxt.mnCol];
|
|
|
|
if ( HasData( nMemberMeasure, aLocalSubState ) )
|
|
{
|
|
if ( HasError( nMemberMeasure, aLocalSubState ) )
|
|
{
|
|
rRes.Value = 0;
|
|
rRes.Flags |= sheet::DataResultFlags::ERROR;
|
|
}
|
|
else
|
|
{
|
|
rRes.Value = GetAggregate( nMemberMeasure, aLocalSubState );
|
|
rRes.Flags |= sheet::DataResultFlags::HASDATA;
|
|
}
|
|
}
|
|
|
|
if ( bHasChild || bIsSubTotalRow )
|
|
rRes.Flags |= sheet::DataResultFlags::SUBTOTAL;
|
|
|
|
rFilterCxt.maFilterSet.add(rFilterCxt.maFilters, rRes.Value);
|
|
rFilterCxt.mnCol += 1;
|
|
}
|
|
}
|
|
|
|
// add extra space again if subtracted from GetSize above,
|
|
// add to own size if no children
|
|
rFilterCxt.mnCol += nExtraSpace;
|
|
rFilterCxt.mnCol += nMoveSubTotal;
|
|
}
|
|
|
|
void ScDPDataMember::UpdateDataRow(
|
|
const ScDPResultMember* pRefMember, tools::Long nMeasure, bool bIsSubTotalRow,
|
|
const ScDPSubTotalState& rSubState )
|
|
{
|
|
OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
|
|
|
|
// Calculate must be called even if not visible (for use as reference value)
|
|
const ScDPDataDimension* pDataChild = GetChildDimension();
|
|
const ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
|
|
|
|
// leave space for children even if the DataMember hasn't been initialized
|
|
// (pDataChild is null then, this happens when no values for it are in this row)
|
|
bool bHasChild = ( pRefChild != nullptr );
|
|
|
|
// process subtotals even if not shown
|
|
tools::Long nUserSubCount = pRefMember->GetSubTotalCount();
|
|
|
|
// Calculate at least automatic if no subtotals are selected,
|
|
// show only own values if there's no child dimension (innermost).
|
|
if ( !nUserSubCount || !bHasChild )
|
|
nUserSubCount = 1;
|
|
|
|
ScDPSubTotalState aLocalSubState(rSubState); // keep row state, modify column
|
|
|
|
tools::Long nMemberMeasure = nMeasure;
|
|
tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
|
|
|
|
for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
|
|
{
|
|
if ( pChildDimension && nUserSubCount > 1 )
|
|
{
|
|
const ScDPLevel* pForceLevel = pResultMember ? pResultMember->GetParentLevel() : nullptr;
|
|
aLocalSubState.nColSubTotalFunc = nUserPos;
|
|
aLocalSubState.eColForce = lcl_GetForceFunc( pForceLevel, nUserPos );
|
|
}
|
|
|
|
for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
|
|
{
|
|
if ( nMeasure == SC_DPMEASURE_ALL )
|
|
nMemberMeasure = nSubCount;
|
|
|
|
// update data...
|
|
ScDPAggData* pAggData = GetAggData( nMemberMeasure, aLocalSubState );
|
|
if (pAggData)
|
|
{
|
|
//TODO: aLocalSubState?
|
|
ScSubTotalFunc eFunc = pResultData->GetMeasureFunction( nMemberMeasure );
|
|
sheet::DataPilotFieldReference aReferenceValue = pResultData->GetMeasureRefVal( nMemberMeasure );
|
|
sal_Int32 eRefType = aReferenceValue.ReferenceType;
|
|
|
|
// calculate the result first - for all members, regardless of reference value
|
|
pAggData->Calculate( eFunc, aLocalSubState );
|
|
|
|
if ( eRefType == sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE ||
|
|
eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE ||
|
|
eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE )
|
|
{
|
|
// copy the result into auxiliary value, so differences can be
|
|
// calculated in any order
|
|
pAggData->SetAuxiliary( pAggData->GetResult() );
|
|
}
|
|
// column/row percentage/index is now in UpdateRunningTotals, so it doesn't disturb sorting
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bHasChild ) // child dimension must be processed last, so the row total is known
|
|
{
|
|
if ( pDataChild )
|
|
pDataChild->UpdateDataRow( pRefChild, nMeasure, bIsSubTotalRow, rSubState );
|
|
}
|
|
}
|
|
|
|
void ScDPDataMember::SortMembers( ScDPResultMember* pRefMember )
|
|
{
|
|
OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
|
|
|
|
if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataDimension ???
|
|
{
|
|
ScDPDataDimension* pDataChild = GetChildDimension();
|
|
ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
|
|
if ( pRefChild && pDataChild )
|
|
pDataChild->SortMembers( pRefChild ); // sorting is done at the dimension
|
|
}
|
|
}
|
|
|
|
void ScDPDataMember::DoAutoShow( ScDPResultMember* pRefMember )
|
|
{
|
|
OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
|
|
|
|
if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataDimension ???
|
|
{
|
|
ScDPDataDimension* pDataChild = GetChildDimension();
|
|
ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
|
|
if ( pRefChild && pDataChild )
|
|
pDataChild->DoAutoShow( pRefChild ); // sorting is done at the dimension
|
|
}
|
|
}
|
|
|
|
void ScDPDataMember::ResetResults()
|
|
{
|
|
aAggregate.Reset();
|
|
|
|
ScDPDataDimension* pDataChild = GetChildDimension();
|
|
if ( pDataChild )
|
|
pDataChild->ResetResults();
|
|
}
|
|
|
|
void ScDPDataMember::UpdateRunningTotals(
|
|
const ScDPResultMember* pRefMember, tools::Long nMeasure, bool bIsSubTotalRow,
|
|
const ScDPSubTotalState& rSubState, ScDPRunningTotalState& rRunning,
|
|
ScDPRowTotals& rTotals, const ScDPResultMember& rRowParent )
|
|
{
|
|
OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
|
|
|
|
const ScDPDataDimension* pDataChild = GetChildDimension();
|
|
const ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
|
|
|
|
bool bIsRoot = ( pResultMember == nullptr || pResultMember->GetParentLevel() == nullptr );
|
|
|
|
// leave space for children even if the DataMember hasn't been initialized
|
|
// (pDataChild is null then, this happens when no values for it are in this row)
|
|
bool bHasChild = ( pRefChild != nullptr );
|
|
|
|
tools::Long nUserSubCount = pRefMember->GetSubTotalCount();
|
|
{
|
|
// Calculate at least automatic if no subtotals are selected,
|
|
// show only own values if there's no child dimension (innermost).
|
|
if ( !nUserSubCount || !bHasChild )
|
|
nUserSubCount = 1;
|
|
|
|
ScDPSubTotalState aLocalSubState(rSubState); // keep row state, modify column
|
|
|
|
tools::Long nMemberMeasure = nMeasure;
|
|
tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
|
|
|
|
for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
|
|
{
|
|
if ( pChildDimension && nUserSubCount > 1 )
|
|
{
|
|
const ScDPLevel* pForceLevel = pResultMember ? pResultMember->GetParentLevel() : nullptr;
|
|
aLocalSubState.nColSubTotalFunc = nUserPos;
|
|
aLocalSubState.eColForce = lcl_GetForceFunc( pForceLevel, nUserPos );
|
|
}
|
|
|
|
for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
|
|
{
|
|
if ( nMeasure == SC_DPMEASURE_ALL )
|
|
nMemberMeasure = nSubCount;
|
|
|
|
// update data...
|
|
ScDPAggData* pAggData = GetAggData( nMemberMeasure, aLocalSubState );
|
|
if (pAggData)
|
|
{
|
|
//TODO: aLocalSubState?
|
|
sheet::DataPilotFieldReference aReferenceValue = pResultData->GetMeasureRefVal( nMemberMeasure );
|
|
sal_Int32 eRefType = aReferenceValue.ReferenceType;
|
|
|
|
if ( eRefType == sheet::DataPilotFieldReferenceType::RUNNING_TOTAL ||
|
|
eRefType == sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE ||
|
|
eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE ||
|
|
eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE )
|
|
{
|
|
bool bRunningTotal = ( eRefType == sheet::DataPilotFieldReferenceType::RUNNING_TOTAL );
|
|
bool bRelative =
|
|
( aReferenceValue.ReferenceItemType != sheet::DataPilotFieldReferenceItemType::NAMED && !bRunningTotal );
|
|
tools::Long nRelativeDir = bRelative ?
|
|
( ( aReferenceValue.ReferenceItemType == sheet::DataPilotFieldReferenceItemType::PREVIOUS ) ? -1 : 1 ) : 0;
|
|
|
|
const ScDPRunningTotalState::IndexArray& rColVisible = rRunning.GetColVisible();
|
|
const ScDPRunningTotalState::IndexArray& rColSorted = rRunning.GetColSorted();
|
|
const ScDPRunningTotalState::IndexArray& rRowVisible = rRunning.GetRowVisible();
|
|
const ScDPRunningTotalState::IndexArray& rRowSorted = rRunning.GetRowSorted();
|
|
|
|
OUString aRefFieldName = aReferenceValue.ReferenceField;
|
|
|
|
//TODO: aLocalSubState?
|
|
sheet::DataPilotFieldOrientation nRefOrient = pResultData->GetMeasureRefOrient( nMemberMeasure );
|
|
bool bRefDimInCol = ( nRefOrient == sheet::DataPilotFieldOrientation_COLUMN );
|
|
bool bRefDimInRow = ( nRefOrient == sheet::DataPilotFieldOrientation_ROW );
|
|
|
|
ScDPResultDimension* pSelectDim = nullptr;
|
|
sal_Int32 nRowPos = 0;
|
|
sal_Int32 nColPos = 0;
|
|
|
|
// find the reference field in column or row dimensions
|
|
|
|
if ( bRefDimInRow ) // look in row dimensions
|
|
{
|
|
pSelectDim = rRunning.GetRowResRoot()->GetChildDimension();
|
|
while ( pSelectDim && pSelectDim->GetName() != aRefFieldName )
|
|
{
|
|
tools::Long nIndex = rRowSorted[nRowPos];
|
|
if ( nIndex >= 0 && nIndex < pSelectDim->GetMemberCount() )
|
|
pSelectDim = pSelectDim->GetMember(nIndex)->GetChildDimension();
|
|
else
|
|
pSelectDim = nullptr;
|
|
++nRowPos;
|
|
}
|
|
// child dimension of innermost member?
|
|
if ( pSelectDim && rRowSorted[nRowPos] < 0 )
|
|
pSelectDim = nullptr;
|
|
}
|
|
|
|
if ( bRefDimInCol ) // look in column dimensions
|
|
{
|
|
pSelectDim = rRunning.GetColResRoot()->GetChildDimension();
|
|
while ( pSelectDim && pSelectDim->GetName() != aRefFieldName )
|
|
{
|
|
tools::Long nIndex = rColSorted[nColPos];
|
|
if ( nIndex >= 0 && nIndex < pSelectDim->GetMemberCount() )
|
|
pSelectDim = pSelectDim->GetMember(nIndex)->GetChildDimension();
|
|
else
|
|
pSelectDim = nullptr;
|
|
++nColPos;
|
|
}
|
|
// child dimension of innermost member?
|
|
if ( pSelectDim && rColSorted[nColPos] < 0 )
|
|
pSelectDim = nullptr;
|
|
}
|
|
|
|
bool bNoDetailsInRef = false;
|
|
if ( pSelectDim && bRunningTotal )
|
|
{
|
|
// Running totals:
|
|
// If details are hidden for this member in the reference dimension,
|
|
// don't show or sum up the value. Otherwise, for following members,
|
|
// the running totals of details and subtotals wouldn't match.
|
|
|
|
tools::Long nMyIndex = bRefDimInCol ? rColSorted[nColPos] : rRowSorted[nRowPos];
|
|
if ( nMyIndex >= 0 && nMyIndex < pSelectDim->GetMemberCount() )
|
|
{
|
|
const ScDPResultMember* pMyRefMember = pSelectDim->GetMember(nMyIndex);
|
|
if ( pMyRefMember && pMyRefMember->HasHiddenDetails() )
|
|
{
|
|
pSelectDim = nullptr; // don't calculate
|
|
bNoDetailsInRef = true; // show error, not empty
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bRelative )
|
|
{
|
|
// Difference/Percentage from previous/next:
|
|
// If details are hidden for this member in the innermost column/row
|
|
// dimension (the orientation of the reference dimension), show an
|
|
// error value.
|
|
// - If the no-details dimension is the reference dimension, its
|
|
// members will be skipped when finding the previous/next member,
|
|
// so there must be no results for its members.
|
|
// - If the no-details dimension is outside of the reference dimension,
|
|
// no calculation in the reference dimension is possible.
|
|
// - Otherwise, the error isn't strictly necessary, but shown for
|
|
// consistency.
|
|
|
|
bool bInnerNoDetails = bRefDimInCol ? HasHiddenDetails() :
|
|
( !bRefDimInRow || rRowParent.HasHiddenDetails() );
|
|
if ( bInnerNoDetails )
|
|
{
|
|
pSelectDim = nullptr;
|
|
bNoDetailsInRef = true; // show error, not empty
|
|
}
|
|
}
|
|
|
|
if ( !bRefDimInCol && !bRefDimInRow ) // invalid dimension specified
|
|
bNoDetailsInRef = true; // pSelectDim is then already NULL
|
|
|
|
// get the member for the reference item and do the calculation
|
|
|
|
if ( bRunningTotal )
|
|
{
|
|
// running total in (dimension) -> find first existing member
|
|
|
|
if ( pSelectDim )
|
|
{
|
|
ScDPDataMember* pSelectMember;
|
|
if ( bRefDimInCol )
|
|
pSelectMember = ScDPResultDimension::GetColReferenceMember( nullptr, nullptr,
|
|
nColPos, rRunning );
|
|
else
|
|
{
|
|
const sal_Int32* pRowSorted = rRowSorted.data();
|
|
const sal_Int32* pColSorted = rColSorted.data();
|
|
pRowSorted += nRowPos + 1; // including the reference dimension
|
|
pSelectMember = pSelectDim->GetRowReferenceMember(
|
|
nullptr, nullptr, pRowSorted, pColSorted);
|
|
}
|
|
|
|
if ( pSelectMember )
|
|
{
|
|
// The running total is kept as the auxiliary value in
|
|
// the first available member for the reference dimension.
|
|
// Members are visited in final order, so each one's result
|
|
// can be used and then modified.
|
|
|
|
ScDPAggData* pSelectData = pSelectMember->
|
|
GetAggData( nMemberMeasure, aLocalSubState );
|
|
if ( pSelectData )
|
|
{
|
|
double fTotal = pSelectData->GetAuxiliary();
|
|
fTotal += pAggData->GetResult();
|
|
pSelectData->SetAuxiliary( fTotal );
|
|
pAggData->SetResult( fTotal );
|
|
pAggData->SetEmpty(false); // always display
|
|
}
|
|
}
|
|
else
|
|
pAggData->SetError();
|
|
}
|
|
else if (bNoDetailsInRef)
|
|
pAggData->SetError();
|
|
else
|
|
pAggData->SetEmpty(true); // empty (dim set to 0 above)
|
|
}
|
|
else
|
|
{
|
|
// difference/percentage -> find specified member
|
|
|
|
if ( pSelectDim )
|
|
{
|
|
OUString aRefItemName = aReferenceValue.ReferenceItemName;
|
|
ScDPRelativePos aRefItemPos( 0, nRelativeDir ); // nBasePos is modified later
|
|
|
|
const OUString* pRefName = nullptr;
|
|
const ScDPRelativePos* pRefPos = nullptr;
|
|
if ( bRelative )
|
|
pRefPos = &aRefItemPos;
|
|
else
|
|
pRefName = &aRefItemName;
|
|
|
|
ScDPDataMember* pSelectMember;
|
|
if ( bRefDimInCol )
|
|
{
|
|
aRefItemPos.nBasePos = rColVisible[nColPos]; // without sort order applied
|
|
pSelectMember = ScDPResultDimension::GetColReferenceMember( pRefPos, pRefName,
|
|
nColPos, rRunning );
|
|
}
|
|
else
|
|
{
|
|
aRefItemPos.nBasePos = rRowVisible[nRowPos]; // without sort order applied
|
|
const sal_Int32* pRowSorted = rRowSorted.data();
|
|
const sal_Int32* pColSorted = rColSorted.data();
|
|
pRowSorted += nRowPos + 1; // including the reference dimension
|
|
pSelectMember = pSelectDim->GetRowReferenceMember(
|
|
pRefPos, pRefName, pRowSorted, pColSorted);
|
|
}
|
|
|
|
// difference or perc.difference is empty for the reference item itself
|
|
if ( pSelectMember == this &&
|
|
eRefType != sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE )
|
|
{
|
|
pAggData->SetEmpty(true);
|
|
}
|
|
else if ( pSelectMember )
|
|
{
|
|
const ScDPAggData* pOtherAggData = pSelectMember->
|
|
GetConstAggData( nMemberMeasure, aLocalSubState );
|
|
OSL_ENSURE( pOtherAggData, "no agg data" );
|
|
if ( pOtherAggData )
|
|
{
|
|
// Reference member may be visited before or after this one,
|
|
// so the auxiliary value is used for the original result.
|
|
|
|
double fOtherResult = pOtherAggData->GetAuxiliary();
|
|
double fThisResult = pAggData->GetResult();
|
|
bool bError = false;
|
|
switch ( eRefType )
|
|
{
|
|
case sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE:
|
|
fThisResult = fThisResult - fOtherResult;
|
|
break;
|
|
case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE:
|
|
if ( fOtherResult == 0.0 )
|
|
bError = true;
|
|
else
|
|
fThisResult = fThisResult / fOtherResult;
|
|
break;
|
|
case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE:
|
|
if ( fOtherResult == 0.0 )
|
|
bError = true;
|
|
else
|
|
fThisResult = ( fThisResult - fOtherResult ) / fOtherResult;
|
|
break;
|
|
default:
|
|
OSL_FAIL("invalid calculation type");
|
|
}
|
|
if ( bError )
|
|
{
|
|
pAggData->SetError();
|
|
}
|
|
else
|
|
{
|
|
pAggData->SetResult(fThisResult);
|
|
pAggData->SetEmpty(false); // always display
|
|
}
|
|
//TODO: errors in data?
|
|
}
|
|
}
|
|
else if (bRelative && !bNoDetailsInRef)
|
|
pAggData->SetEmpty(true); // empty
|
|
else
|
|
pAggData->SetError(); // error
|
|
}
|
|
else if (bNoDetailsInRef)
|
|
pAggData->SetError(); // error
|
|
else
|
|
pAggData->SetEmpty(true); // empty
|
|
}
|
|
}
|
|
else if ( eRefType == sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE ||
|
|
eRefType == sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE ||
|
|
eRefType == sheet::DataPilotFieldReferenceType::TOTAL_PERCENTAGE ||
|
|
eRefType == sheet::DataPilotFieldReferenceType::INDEX )
|
|
{
|
|
|
|
// set total values when they are encountered (always before their use)
|
|
|
|
ScDPAggData* pColTotalData = pRefMember->GetColTotal( nMemberMeasure );
|
|
ScDPAggData* pRowTotalData = rTotals.GetRowTotal( nMemberMeasure );
|
|
ScDPAggData* pGrandTotalData = rTotals.GetGrandTotal( nMemberMeasure );
|
|
|
|
double fTotalValue = pAggData->HasError() ? 0 : pAggData->GetResult();
|
|
|
|
if ( bIsRoot && rTotals.IsInColRoot() && pGrandTotalData )
|
|
pGrandTotalData->SetAuxiliary( fTotalValue );
|
|
|
|
if ( bIsRoot && pRowTotalData )
|
|
pRowTotalData->SetAuxiliary( fTotalValue );
|
|
|
|
if ( rTotals.IsInColRoot() && pColTotalData )
|
|
pColTotalData->SetAuxiliary( fTotalValue );
|
|
|
|
// find relation to total values
|
|
|
|
switch ( eRefType )
|
|
{
|
|
case sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE:
|
|
case sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE:
|
|
case sheet::DataPilotFieldReferenceType::TOTAL_PERCENTAGE:
|
|
{
|
|
double nTotal;
|
|
if ( eRefType == sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE )
|
|
nTotal = pRowTotalData ? pRowTotalData->GetAuxiliary() : 0.0;
|
|
else if ( eRefType == sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE )
|
|
nTotal = pColTotalData ? pColTotalData->GetAuxiliary() : 0.0;
|
|
else
|
|
nTotal = pGrandTotalData ? pGrandTotalData->GetAuxiliary() : 0.0;
|
|
|
|
if ( nTotal == 0.0 )
|
|
pAggData->SetError();
|
|
else
|
|
pAggData->SetResult( pAggData->GetResult() / nTotal );
|
|
}
|
|
break;
|
|
case sheet::DataPilotFieldReferenceType::INDEX:
|
|
{
|
|
double nColTotal = pColTotalData ? pColTotalData->GetAuxiliary() : 0.0;
|
|
double nRowTotal = pRowTotalData ? pRowTotalData->GetAuxiliary() : 0.0;
|
|
double nGrandTotal = pGrandTotalData ? pGrandTotalData->GetAuxiliary() : 0.0;
|
|
if ( nRowTotal == 0.0 || nColTotal == 0.0 )
|
|
pAggData->SetError();
|
|
else
|
|
pAggData->SetResult(
|
|
( pAggData->GetResult() * nGrandTotal ) /
|
|
( nRowTotal * nColTotal ) );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bHasChild ) // child dimension must be processed last, so the row total is known
|
|
{
|
|
if ( pDataChild )
|
|
pDataChild->UpdateRunningTotals( pRefChild, nMeasure,
|
|
bIsSubTotalRow, rSubState, rRunning, rTotals, rRowParent );
|
|
}
|
|
}
|
|
|
|
#if DUMP_PIVOT_TABLE
|
|
void ScDPDataMember::DumpState( const ScDPResultMember* pRefMember, ScDocument* pDoc, ScAddress& rPos ) const
|
|
{
|
|
dumpRow(u"ScDPDataMember"_ustr, GetName(), &aAggregate, pDoc, rPos);
|
|
SCROW nStartRow = rPos.Row();
|
|
|
|
const ScDPDataDimension* pDataChild = GetChildDimension();
|
|
const ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
|
|
if ( pDataChild && pRefChild )
|
|
pDataChild->DumpState( pRefChild, pDoc, rPos );
|
|
|
|
indent(pDoc, nStartRow, rPos);
|
|
}
|
|
|
|
void ScDPDataMember::Dump(int nIndent) const
|
|
{
|
|
std::string aIndent(nIndent*2, ' ');
|
|
std::cout << aIndent << "-- data member '"
|
|
<< (pResultMember ? pResultMember->GetName() : OUString()) << "'" << std::endl;
|
|
for (const ScDPAggData* pAgg = &aAggregate; pAgg; pAgg = pAgg->GetExistingChild())
|
|
pAgg->Dump(nIndent+1);
|
|
|
|
if (pChildDimension)
|
|
pChildDimension->Dump(nIndent+1);
|
|
}
|
|
#endif
|
|
|
|
// Helper class to select the members to include in
|
|
// ScDPResultDimension::InitFrom or LateInitFrom if groups are used
|
|
|
|
namespace {
|
|
|
|
class ScDPGroupCompare
|
|
{
|
|
private:
|
|
const ScDPResultData* pResultData;
|
|
const ScDPInitState& rInitState;
|
|
tools::Long nDimSource;
|
|
bool bIncludeAll;
|
|
bool bIsBase;
|
|
tools::Long nGroupBase;
|
|
public:
|
|
ScDPGroupCompare( const ScDPResultData* pData, const ScDPInitState& rState, tools::Long nDimension );
|
|
|
|
bool IsIncluded( const ScDPMember& rMember ) { return bIncludeAll || TestIncluded( rMember ); }
|
|
bool TestIncluded( const ScDPMember& rMember );
|
|
};
|
|
|
|
}
|
|
|
|
ScDPGroupCompare::ScDPGroupCompare( const ScDPResultData* pData, const ScDPInitState& rState, tools::Long nDimension ) :
|
|
pResultData( pData ),
|
|
rInitState( rState ),
|
|
nDimSource( nDimension )
|
|
{
|
|
bIsBase = pResultData->IsBaseForGroup( nDimSource );
|
|
nGroupBase = pResultData->GetGroupBase( nDimSource ); //TODO: get together in one call?
|
|
|
|
// if bIncludeAll is set, TestIncluded doesn't need to be called
|
|
bIncludeAll = !( bIsBase || nGroupBase >= 0 );
|
|
}
|
|
|
|
bool ScDPGroupCompare::TestIncluded( const ScDPMember& rMember )
|
|
{
|
|
bool bInclude = true;
|
|
if ( bIsBase )
|
|
{
|
|
// need to check all previous groups
|
|
//TODO: get array of groups (or indexes) before loop?
|
|
ScDPItemData aMemberData(rMember.FillItemData());
|
|
|
|
const std::vector<ScDPInitState::Member>& rMemStates = rInitState.GetMembers();
|
|
bInclude = std::all_of(rMemStates.begin(), rMemStates.end(),
|
|
[this, &aMemberData](const ScDPInitState::Member& rMem) {
|
|
return (pResultData->GetGroupBase(rMem.mnSrcIndex) != nDimSource)
|
|
|| pResultData->IsInGroup(rMem.mnNameIndex, rMem.mnSrcIndex, aMemberData, nDimSource);
|
|
});
|
|
}
|
|
else if ( nGroupBase >= 0 )
|
|
{
|
|
// base isn't used in preceding fields
|
|
// -> look for other groups using the same base
|
|
|
|
//TODO: get array of groups (or indexes) before loop?
|
|
ScDPItemData aMemberData(rMember.FillItemData());
|
|
const std::vector<ScDPInitState::Member>& rMemStates = rInitState.GetMembers();
|
|
bInclude = std::all_of(rMemStates.begin(), rMemStates.end(),
|
|
[this, &aMemberData](const ScDPInitState::Member& rMem) {
|
|
// coverity[copy_paste_error : FALSE] - same base (hierarchy between
|
|
// the two groups is irrelevant)
|
|
return (pResultData->GetGroupBase(rMem.mnSrcIndex) != nGroupBase)
|
|
|| pResultData->HasCommonElement(rMem.mnNameIndex, rMem.mnSrcIndex, aMemberData, nDimSource);
|
|
});
|
|
}
|
|
|
|
return bInclude;
|
|
}
|
|
|
|
ScDPResultDimension::ScDPResultDimension( const ScDPResultData* pData ) :
|
|
pResultData( pData ),
|
|
nSortMeasure( 0 ),
|
|
bIsDataLayout( false ),
|
|
bSortByData( false ),
|
|
bSortAscending( false ),
|
|
bAutoShow( false ),
|
|
bAutoTopItems( false ),
|
|
bInitialized( false ),
|
|
nAutoMeasure( 0 ),
|
|
nAutoCount( 0 )
|
|
{
|
|
}
|
|
|
|
ScDPResultDimension::~ScDPResultDimension()
|
|
{
|
|
}
|
|
|
|
ScDPResultMember *ScDPResultDimension::FindMember( SCROW iData ) const
|
|
{
|
|
if( bIsDataLayout )
|
|
{
|
|
SAL_WARN_IF(maMemberArray.empty(), "sc.core", "MemberArray is empty");
|
|
return !maMemberArray.empty() ? maMemberArray[0].get() : nullptr;
|
|
}
|
|
|
|
MemberHash::const_iterator aRes = maMemberHash.find( iData );
|
|
if( aRes != maMemberHash.end()) {
|
|
if ( aRes->second->IsNamedItem( iData ) )
|
|
return aRes->second;
|
|
OSL_FAIL("problem! hash result is not the same as IsNamedItem");
|
|
}
|
|
|
|
unsigned int i;
|
|
unsigned int nCount = maMemberArray.size();
|
|
for( i = 0; i < nCount ; i++ )
|
|
{
|
|
ScDPResultMember* pResultMember = maMemberArray[i].get();
|
|
if ( pResultMember->IsNamedItem( iData ) )
|
|
return pResultMember;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ScDPResultDimension::InitFrom(
|
|
const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLev,
|
|
size_t nPos, ScDPInitState& rInitState, bool bInitChild )
|
|
{
|
|
if (nPos >= ppDim.size() || nPos >= ppLev.size())
|
|
{
|
|
bInitialized = true;
|
|
return;
|
|
}
|
|
|
|
ScDPDimension* pThisDim = ppDim[nPos];
|
|
ScDPLevel* pThisLevel = ppLev[nPos];
|
|
|
|
if (!pThisDim || !pThisLevel)
|
|
{
|
|
bInitialized = true;
|
|
return;
|
|
}
|
|
|
|
bIsDataLayout = pThisDim->getIsDataLayoutDimension(); // member
|
|
aDimensionName = pThisDim->getName(); // member
|
|
|
|
// Check the autoshow setting. If it's enabled, store the settings.
|
|
const sheet::DataPilotFieldAutoShowInfo& rAutoInfo = pThisLevel->GetAutoShow();
|
|
if ( rAutoInfo.IsEnabled )
|
|
{
|
|
bAutoShow = true;
|
|
bAutoTopItems = ( rAutoInfo.ShowItemsMode == sheet::DataPilotFieldShowItemsMode::FROM_TOP );
|
|
nAutoMeasure = pThisLevel->GetAutoMeasure();
|
|
nAutoCount = rAutoInfo.ItemCount;
|
|
}
|
|
|
|
// Check the sort info, and store the settings if appropriate.
|
|
const sheet::DataPilotFieldSortInfo& rSortInfo = pThisLevel->GetSortInfo();
|
|
if ( rSortInfo.Mode == sheet::DataPilotFieldSortMode::DATA )
|
|
{
|
|
bSortByData = true;
|
|
bSortAscending = rSortInfo.IsAscending;
|
|
nSortMeasure = pThisLevel->GetSortMeasure();
|
|
}
|
|
|
|
// global order is used to initialize aMembers, so it doesn't have to be looked at later
|
|
const ScMemberSortOrder& rGlobalOrder = pThisLevel->GetGlobalOrder();
|
|
|
|
tools::Long nDimSource = pThisDim->GetDimension(); //TODO: check GetSourceDim?
|
|
ScDPGroupCompare aCompare( pResultData, rInitState, nDimSource );
|
|
|
|
// Now, go through all members and initialize them.
|
|
ScDPMembers* pMembers = pThisLevel->GetMembersObject();
|
|
tools::Long nMembCount = pMembers->getCount();
|
|
for ( tools::Long i=0; i<nMembCount; i++ )
|
|
{
|
|
tools::Long nSorted = rGlobalOrder.empty() ? i : rGlobalOrder[i];
|
|
|
|
ScDPMember* pMember = pMembers->getByIndex(nSorted);
|
|
if ( aCompare.IsIncluded( *pMember ) )
|
|
{
|
|
ScDPParentDimData aData( i, pThisDim, pThisLevel, pMember);
|
|
ScDPResultMember* pNew = AddMember( aData );
|
|
|
|
rInitState.AddMember(nDimSource, pNew->GetDataId());
|
|
pNew->InitFrom( ppDim, ppLev, nPos+1, rInitState, bInitChild );
|
|
rInitState.RemoveMember();
|
|
}
|
|
}
|
|
bInitialized = true;
|
|
}
|
|
|
|
void ScDPResultDimension::LateInitFrom(
|
|
LateInitParams& rParams, const vector<SCROW>& pItemData, size_t nPos, ScDPInitState& rInitState)
|
|
{
|
|
if ( rParams.IsEnd( nPos ) )
|
|
return;
|
|
if (nPos >= pItemData.size())
|
|
{
|
|
SAL_WARN("sc.core", "pos " << nPos << ", but vector size is " << pItemData.size());
|
|
return;
|
|
}
|
|
SCROW rThisData = pItemData[nPos];
|
|
ScDPDimension* pThisDim = rParams.GetDim( nPos );
|
|
ScDPLevel* pThisLevel = rParams.GetLevel( nPos );
|
|
|
|
if (!pThisDim || !pThisLevel)
|
|
return;
|
|
|
|
tools::Long nDimSource = pThisDim->GetDimension(); //TODO: check GetSourceDim?
|
|
|
|
bool bShowEmpty = pThisLevel->getShowEmpty();
|
|
|
|
if ( !bInitialized )
|
|
{ // init some values
|
|
// create all members at the first call (preserve order)
|
|
bIsDataLayout = pThisDim->getIsDataLayoutDimension();
|
|
aDimensionName = pThisDim->getName();
|
|
|
|
const sheet::DataPilotFieldAutoShowInfo& rAutoInfo = pThisLevel->GetAutoShow();
|
|
if ( rAutoInfo.IsEnabled )
|
|
{
|
|
bAutoShow = true;
|
|
bAutoTopItems = ( rAutoInfo.ShowItemsMode == sheet::DataPilotFieldShowItemsMode::FROM_TOP );
|
|
nAutoMeasure = pThisLevel->GetAutoMeasure();
|
|
nAutoCount = rAutoInfo.ItemCount;
|
|
}
|
|
|
|
const sheet::DataPilotFieldSortInfo& rSortInfo = pThisLevel->GetSortInfo();
|
|
if ( rSortInfo.Mode == sheet::DataPilotFieldSortMode::DATA )
|
|
{
|
|
bSortByData = true;
|
|
bSortAscending = rSortInfo.IsAscending;
|
|
nSortMeasure = pThisLevel->GetSortMeasure();
|
|
}
|
|
}
|
|
|
|
bool bLateInitAllMembers= bIsDataLayout || rParams.GetInitAllChild() || bShowEmpty;
|
|
|
|
if ( !bLateInitAllMembers )
|
|
{
|
|
ResultMembers& rMembers = pResultData->GetDimResultMembers(nDimSource, pThisDim, pThisLevel);
|
|
bLateInitAllMembers = rMembers.IsHasHideDetailsMembers();
|
|
|
|
SAL_INFO("sc.core", aDimensionName << (rMembers.IsHasHideDetailsMembers() ? " HasHideDetailsMembers" : ""));
|
|
|
|
rMembers.SetHasHideDetailsMembers( false );
|
|
}
|
|
|
|
bool bNewAllMembers = (!rParams.IsRow()) || nPos == 0 || bLateInitAllMembers;
|
|
|
|
if (bNewAllMembers )
|
|
{
|
|
// global order is used to initialize aMembers, so it doesn't have to be looked at later
|
|
if ( !bInitialized )
|
|
{ //init all members
|
|
const ScMemberSortOrder& rGlobalOrder = pThisLevel->GetGlobalOrder();
|
|
|
|
ScDPGroupCompare aCompare( pResultData, rInitState, nDimSource );
|
|
ScDPMembers* pMembers = pThisLevel->GetMembersObject();
|
|
tools::Long nMembCount = pMembers->getCount();
|
|
for ( tools::Long i=0; i<nMembCount; i++ )
|
|
{
|
|
tools::Long nSorted = rGlobalOrder.empty() ? i : rGlobalOrder[i];
|
|
|
|
ScDPMember* pMember = pMembers->getByIndex(nSorted);
|
|
if ( aCompare.IsIncluded( *pMember ) )
|
|
{ // add all members
|
|
ScDPParentDimData aData( i, pThisDim, pThisLevel, pMember );
|
|
AddMember( aData );
|
|
}
|
|
}
|
|
bInitialized = true; // don't call again, even if no members were included
|
|
}
|
|
// initialize only specific member (or all if "show empty" flag is set)
|
|
if ( bLateInitAllMembers )
|
|
{
|
|
tools::Long nCount = maMemberArray.size();
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
ScDPResultMember* pResultMember = maMemberArray[i].get();
|
|
|
|
// check show empty
|
|
bool bAllChildren = false;
|
|
if( bShowEmpty )
|
|
{
|
|
bAllChildren = !pResultMember->IsNamedItem( rThisData );
|
|
}
|
|
rParams.SetInitAllChildren( bAllChildren );
|
|
rInitState.AddMember( nDimSource, pResultMember->GetDataId() );
|
|
pResultMember->LateInitFrom( rParams, pItemData, nPos+1, rInitState );
|
|
rInitState.RemoveMember();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ScDPResultMember* pResultMember = FindMember( rThisData );
|
|
if( nullptr != pResultMember )
|
|
{
|
|
rInitState.AddMember( nDimSource, pResultMember->GetDataId() );
|
|
pResultMember->LateInitFrom( rParams, pItemData, nPos+1, rInitState );
|
|
rInitState.RemoveMember();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
InitWithMembers( rParams, pItemData, nPos, rInitState );
|
|
}
|
|
|
|
tools::Long ScDPResultDimension::GetSize(tools::Long nMeasure) const
|
|
{
|
|
tools::Long nMemberCount = maMemberArray.size();
|
|
if (!nMemberCount)
|
|
return 0;
|
|
|
|
tools::Long nTotal = 0;
|
|
if (bIsDataLayout)
|
|
{
|
|
OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
|
|
"DataLayout dimension twice?");
|
|
// repeat first member...
|
|
nTotal = nMemberCount * maMemberArray[0]->GetSize(0); // all measures have equal size
|
|
}
|
|
else
|
|
{
|
|
// add all members
|
|
for (tools::Long nMem=0; nMem<nMemberCount; nMem++)
|
|
nTotal += maMemberArray[nMem]->GetSize(nMeasure);
|
|
}
|
|
return nTotal;
|
|
}
|
|
|
|
bool ScDPResultDimension::IsValidEntry( const vector< SCROW >& aMembers ) const
|
|
{
|
|
if (aMembers.empty())
|
|
return false;
|
|
|
|
const ScDPResultMember* pMember = FindMember( aMembers[0] );
|
|
if ( nullptr != pMember )
|
|
return pMember->IsValidEntry( aMembers );
|
|
#if OSL_DEBUG_LEVEL > 0
|
|
SAL_INFO("sc.core", "IsValidEntry: Member not found, DimNam = " << GetName());
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void ScDPResultDimension::ProcessData( const vector< SCROW >& aMembers,
|
|
const ScDPResultDimension* pDataDim,
|
|
const vector< SCROW >& aDataMembers,
|
|
const vector<ScDPValue>& aValues ) const
|
|
{
|
|
if (aMembers.empty())
|
|
return;
|
|
|
|
ScDPResultMember* pMember = FindMember( aMembers[0] );
|
|
if ( nullptr != pMember )
|
|
{
|
|
vector<SCROW> aChildMembers;
|
|
if (aMembers.size() > 1)
|
|
{
|
|
vector<SCROW>::const_iterator itr = aMembers.begin();
|
|
aChildMembers.insert(aChildMembers.begin(), ++itr, aMembers.end());
|
|
}
|
|
pMember->ProcessData( aChildMembers, pDataDim, aDataMembers, aValues );
|
|
return;
|
|
}
|
|
|
|
OSL_FAIL("ProcessData: Member not found");
|
|
}
|
|
|
|
void ScDPResultDimension::FillMemberResults( uno::Sequence<sheet::MemberResult>* pSequences,
|
|
tools::Long nStart, tools::Long nMeasure )
|
|
{
|
|
tools::Long nPos = nStart;
|
|
tools::Long nCount = maMemberArray.size();
|
|
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
tools::Long nSorted = aMemberOrder.empty() ? i : aMemberOrder[i];
|
|
|
|
ScDPResultMember* pMember = maMemberArray[nSorted].get();
|
|
// in data layout dimension, use first member with different measures/names
|
|
if ( bIsDataLayout )
|
|
{
|
|
bool bTotalResult = false;
|
|
OUString aMbrName = pResultData->GetMeasureDimensionName( nSorted );
|
|
OUString aMbrCapt = pResultData->GetMeasureString( nSorted, false, SUBTOTAL_FUNC_NONE, bTotalResult );
|
|
maMemberArray[0]->FillMemberResults( pSequences, nPos, nSorted, false, &aMbrName, &aMbrCapt );
|
|
}
|
|
else if ( pMember->IsVisible() )
|
|
{
|
|
pMember->FillMemberResults( pSequences, nPos, nMeasure, false, nullptr, nullptr );
|
|
}
|
|
// nPos is modified
|
|
}
|
|
}
|
|
|
|
void ScDPResultDimension::FillDataResults(
|
|
const ScDPResultMember* pRefMember, ScDPResultFilterContext& rFilterCxt,
|
|
uno::Sequence< uno::Sequence<sheet::DataResult> >& rSequence, tools::Long nMeasure) const
|
|
{
|
|
FilterStack aFilterStack(rFilterCxt.maFilters);
|
|
aFilterStack.pushDimName(GetName(), bIsDataLayout);
|
|
|
|
tools::Long nMemberMeasure = nMeasure;
|
|
tools::Long nCount = maMemberArray.size();
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
tools::Long nSorted = aMemberOrder.empty() ? i : aMemberOrder[i];
|
|
|
|
const ScDPResultMember* pMember;
|
|
if (bIsDataLayout)
|
|
{
|
|
OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
|
|
"DataLayout dimension twice?");
|
|
pMember = maMemberArray[0].get();
|
|
nMemberMeasure = nSorted;
|
|
}
|
|
else
|
|
pMember = maMemberArray[nSorted].get();
|
|
|
|
if ( pMember->IsVisible() )
|
|
pMember->FillDataResults(pRefMember, rFilterCxt, rSequence, nMemberMeasure);
|
|
}
|
|
}
|
|
|
|
void ScDPResultDimension::UpdateDataResults( const ScDPResultMember* pRefMember, tools::Long nMeasure ) const
|
|
{
|
|
tools::Long nMemberMeasure = nMeasure;
|
|
tools::Long nCount = maMemberArray.size();
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
const ScDPResultMember* pMember;
|
|
if (bIsDataLayout)
|
|
{
|
|
OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
|
|
"DataLayout dimension twice?");
|
|
pMember = maMemberArray[0].get();
|
|
nMemberMeasure = i;
|
|
}
|
|
else
|
|
pMember = maMemberArray[i].get();
|
|
|
|
if ( pMember->IsVisible() )
|
|
pMember->UpdateDataResults( pRefMember, nMemberMeasure );
|
|
}
|
|
}
|
|
|
|
void ScDPResultDimension::SortMembers( ScDPResultMember* pRefMember )
|
|
{
|
|
tools::Long nCount = maMemberArray.size();
|
|
|
|
if ( bSortByData )
|
|
{
|
|
// sort members
|
|
|
|
OSL_ENSURE( aMemberOrder.empty(), "sort twice?" );
|
|
aMemberOrder.resize( nCount );
|
|
for (tools::Long nPos=0; nPos<nCount; nPos++)
|
|
aMemberOrder[nPos] = nPos;
|
|
|
|
ScDPRowMembersOrder aComp( *this, nSortMeasure, bSortAscending );
|
|
::std::sort( aMemberOrder.begin(), aMemberOrder.end(), aComp );
|
|
}
|
|
|
|
// handle children
|
|
|
|
// for data layout, call only once - sorting measure is always taken from settings
|
|
tools::Long nLoopCount = bIsDataLayout ? std::min<tools::Long>(1, nCount) : nCount;
|
|
for (tools::Long i=0; i<nLoopCount; i++)
|
|
{
|
|
ScDPResultMember* pMember = maMemberArray[i].get();
|
|
if ( pMember->IsVisible() )
|
|
pMember->SortMembers( pRefMember );
|
|
}
|
|
}
|
|
|
|
void ScDPResultDimension::DoAutoShow( ScDPResultMember* pRefMember )
|
|
{
|
|
tools::Long nCount = maMemberArray.size();
|
|
|
|
// handle children first, before changing the visible state
|
|
|
|
// for data layout, call only once - sorting measure is always taken from settings
|
|
tools::Long nLoopCount = bIsDataLayout ? 1 : nCount;
|
|
for (tools::Long i=0; i<nLoopCount; i++)
|
|
{
|
|
ScDPResultMember* pMember = maMemberArray[i].get();
|
|
if ( pMember->IsVisible() )
|
|
pMember->DoAutoShow( pRefMember );
|
|
}
|
|
|
|
if ( !(bAutoShow && nAutoCount > 0 && nAutoCount < nCount) )
|
|
return;
|
|
|
|
// establish temporary order, hide remaining members
|
|
|
|
ScMemberSortOrder aAutoOrder;
|
|
aAutoOrder.resize( nCount );
|
|
tools::Long nPos;
|
|
for (nPos=0; nPos<nCount; nPos++)
|
|
aAutoOrder[nPos] = nPos;
|
|
|
|
ScDPRowMembersOrder aComp( *this, nAutoMeasure, !bAutoTopItems );
|
|
::std::sort( aAutoOrder.begin(), aAutoOrder.end(), aComp );
|
|
|
|
// look for equal values to the last included one
|
|
|
|
tools::Long nIncluded = nAutoCount;
|
|
const ScDPResultMember* pMember1 = maMemberArray[aAutoOrder[nIncluded - 1]].get();
|
|
const ScDPDataMember* pDataMember1 = pMember1->IsVisible() ? pMember1->GetDataRoot() : nullptr;
|
|
bool bContinue = true;
|
|
while ( bContinue )
|
|
{
|
|
bContinue = false;
|
|
if ( nIncluded < nCount )
|
|
{
|
|
const ScDPResultMember* pMember2 = maMemberArray[aAutoOrder[nIncluded]].get();
|
|
const ScDPDataMember* pDataMember2 = pMember2->IsVisible() ? pMember2->GetDataRoot() : nullptr;
|
|
|
|
if ( lcl_IsEqual( pDataMember1, pDataMember2, nAutoMeasure ) )
|
|
{
|
|
++nIncluded; // include more members if values are equal
|
|
bContinue = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// hide the remaining members
|
|
|
|
for (nPos = nIncluded; nPos < nCount; nPos++)
|
|
{
|
|
ScDPResultMember* pMember = maMemberArray[aAutoOrder[nPos]].get();
|
|
pMember->SetAutoHidden();
|
|
}
|
|
}
|
|
|
|
void ScDPResultDimension::ResetResults()
|
|
{
|
|
tools::Long nCount = maMemberArray.size();
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
// sort order doesn't matter
|
|
ScDPResultMember* pMember = maMemberArray[bIsDataLayout ? 0 : i].get();
|
|
pMember->ResetResults();
|
|
}
|
|
}
|
|
|
|
tools::Long ScDPResultDimension::GetSortedIndex( tools::Long nUnsorted ) const
|
|
{
|
|
return aMemberOrder.empty() ? nUnsorted : aMemberOrder[nUnsorted];
|
|
}
|
|
|
|
void ScDPResultDimension::UpdateRunningTotals( const ScDPResultMember* pRefMember, tools::Long nMeasure,
|
|
ScDPRunningTotalState& rRunning, ScDPRowTotals& rTotals ) const
|
|
{
|
|
const ScDPResultMember* pMember;
|
|
tools::Long nMemberMeasure = nMeasure;
|
|
tools::Long nCount = maMemberArray.size();
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
tools::Long nSorted = aMemberOrder.empty() ? i : aMemberOrder[i];
|
|
|
|
if (bIsDataLayout)
|
|
{
|
|
OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
|
|
"DataLayout dimension twice?");
|
|
pMember = maMemberArray[0].get();
|
|
nMemberMeasure = nSorted;
|
|
}
|
|
else
|
|
pMember = maMemberArray[nSorted].get();
|
|
|
|
if ( pMember->IsVisible() )
|
|
{
|
|
if ( bIsDataLayout )
|
|
rRunning.AddRowIndex( 0, 0 );
|
|
else
|
|
rRunning.AddRowIndex( i, nSorted );
|
|
pMember->UpdateRunningTotals( pRefMember, nMemberMeasure, rRunning, rTotals );
|
|
rRunning.RemoveRowIndex();
|
|
}
|
|
}
|
|
}
|
|
|
|
ScDPDataMember* ScDPResultDimension::GetRowReferenceMember(
|
|
const ScDPRelativePos* pRelativePos, const OUString* pName,
|
|
const sal_Int32* pRowIndexes, const sal_Int32* pColIndexes ) const
|
|
{
|
|
// get named, previous/next, or first member of this dimension (first existing if pRelativePos and pName are NULL)
|
|
|
|
OSL_ENSURE( pRelativePos == nullptr || pName == nullptr, "can't use position and name" );
|
|
|
|
ScDPDataMember* pColMember = nullptr;
|
|
|
|
bool bFirstExisting = ( pRelativePos == nullptr && pName == nullptr );
|
|
tools::Long nMemberCount = maMemberArray.size();
|
|
tools::Long nMemberIndex = 0; // unsorted
|
|
tools::Long nDirection = 1; // forward if no relative position is used
|
|
if ( pRelativePos )
|
|
{
|
|
nDirection = pRelativePos->nDirection;
|
|
nMemberIndex = pRelativePos->nBasePos + nDirection; // bounds are handled below
|
|
|
|
OSL_ENSURE( nDirection == 1 || nDirection == -1, "Direction must be 1 or -1" );
|
|
}
|
|
else if ( pName )
|
|
{
|
|
// search for named member
|
|
|
|
const ScDPResultMember* pRowMember = maMemberArray[GetSortedIndex(nMemberIndex)].get();
|
|
|
|
//TODO: use ScDPItemData, as in ScDPDimension::IsValidPage?
|
|
while ( pRowMember && pRowMember->GetName() != *pName )
|
|
{
|
|
++nMemberIndex;
|
|
if ( nMemberIndex < nMemberCount )
|
|
pRowMember = maMemberArray[GetSortedIndex(nMemberIndex)].get();
|
|
else
|
|
pRowMember = nullptr;
|
|
}
|
|
}
|
|
|
|
bool bContinue = true;
|
|
while ( bContinue && nMemberIndex >= 0 && nMemberIndex < nMemberCount )
|
|
{
|
|
const ScDPResultMember* pRowMember = maMemberArray[GetSortedIndex(nMemberIndex)].get();
|
|
|
|
// get child members by given indexes
|
|
|
|
const sal_Int32* pNextRowIndex = pRowIndexes;
|
|
while ( *pNextRowIndex >= 0 && pRowMember )
|
|
{
|
|
const ScDPResultDimension* pRowChild = pRowMember->GetChildDimension();
|
|
if ( pRowChild && *pNextRowIndex < pRowChild->GetMemberCount() )
|
|
pRowMember = pRowChild->GetMember( *pNextRowIndex );
|
|
else
|
|
pRowMember = nullptr;
|
|
++pNextRowIndex;
|
|
}
|
|
|
|
if ( pRowMember && pRelativePos )
|
|
{
|
|
// Skip the member if it has hidden details
|
|
// (because when looking for the details, it is skipped, too).
|
|
// Also skip if the member is invisible because it has no data,
|
|
// for consistent ordering.
|
|
if ( pRowMember->HasHiddenDetails() || !pRowMember->IsVisible() )
|
|
pRowMember = nullptr;
|
|
}
|
|
|
|
if ( pRowMember )
|
|
{
|
|
pColMember = pRowMember->GetDataRoot();
|
|
|
|
const sal_Int32* pNextColIndex = pColIndexes;
|
|
while ( *pNextColIndex >= 0 && pColMember )
|
|
{
|
|
ScDPDataDimension* pColChild = pColMember->GetChildDimension();
|
|
if ( pColChild && *pNextColIndex < pColChild->GetMemberCount() )
|
|
pColMember = pColChild->GetMember( *pNextColIndex );
|
|
else
|
|
pColMember = nullptr;
|
|
++pNextColIndex;
|
|
}
|
|
}
|
|
|
|
// continue searching only if looking for first existing or relative position
|
|
bContinue = ( pColMember == nullptr && ( bFirstExisting || pRelativePos ) );
|
|
nMemberIndex += nDirection;
|
|
}
|
|
|
|
return pColMember;
|
|
}
|
|
|
|
ScDPDataMember* ScDPResultDimension::GetColReferenceMember(
|
|
const ScDPRelativePos* pRelativePos, const OUString* pName,
|
|
sal_Int32 nRefDimPos, const ScDPRunningTotalState& rRunning )
|
|
{
|
|
OSL_ENSURE( pRelativePos == nullptr || pName == nullptr, "can't use position and name" );
|
|
|
|
const sal_Int32* pColIndexes = rRunning.GetColSorted().data();
|
|
const sal_Int32* pRowIndexes = rRunning.GetRowSorted().data();
|
|
|
|
// get own row member using all indexes
|
|
|
|
const ScDPResultMember* pRowMember = rRunning.GetRowResRoot();
|
|
ScDPDataMember* pColMember = nullptr;
|
|
|
|
const sal_Int32* pNextRowIndex = pRowIndexes;
|
|
while ( *pNextRowIndex >= 0 && pRowMember )
|
|
{
|
|
const ScDPResultDimension* pRowChild = pRowMember->GetChildDimension();
|
|
if ( pRowChild && *pNextRowIndex < pRowChild->GetMemberCount() )
|
|
pRowMember = pRowChild->GetMember( *pNextRowIndex );
|
|
else
|
|
pRowMember = nullptr;
|
|
++pNextRowIndex;
|
|
}
|
|
|
|
// get column (data) members before the reference field
|
|
//TODO: pass rRowParent from ScDPDataMember::UpdateRunningTotals instead
|
|
|
|
if ( pRowMember )
|
|
{
|
|
pColMember = pRowMember->GetDataRoot();
|
|
|
|
const sal_Int32* pNextColIndex = pColIndexes;
|
|
sal_Int32 nColSkipped = 0;
|
|
while ( *pNextColIndex >= 0 && pColMember && nColSkipped < nRefDimPos )
|
|
{
|
|
ScDPDataDimension* pColChild = pColMember->GetChildDimension();
|
|
if ( pColChild && *pNextColIndex < pColChild->GetMemberCount() )
|
|
pColMember = pColChild->GetMember( *pNextColIndex );
|
|
else
|
|
pColMember = nullptr;
|
|
++pNextColIndex;
|
|
++nColSkipped;
|
|
}
|
|
}
|
|
|
|
// get column member for the reference field
|
|
|
|
if ( pColMember )
|
|
{
|
|
ScDPDataDimension* pReferenceDim = pColMember->GetChildDimension();
|
|
if ( pReferenceDim )
|
|
{
|
|
tools::Long nReferenceCount = pReferenceDim->GetMemberCount();
|
|
|
|
bool bFirstExisting = ( pRelativePos == nullptr && pName == nullptr );
|
|
tools::Long nMemberIndex = 0; // unsorted
|
|
tools::Long nDirection = 1; // forward if no relative position is used
|
|
pColMember = nullptr; // don't use parent dimension's member if none found
|
|
if ( pRelativePos )
|
|
{
|
|
nDirection = pRelativePos->nDirection;
|
|
nMemberIndex = pRelativePos->nBasePos + nDirection; // bounds are handled below
|
|
}
|
|
else if ( pName )
|
|
{
|
|
// search for named member
|
|
|
|
pColMember = pReferenceDim->GetMember( pReferenceDim->GetSortedIndex( nMemberIndex ) );
|
|
|
|
//TODO: use ScDPItemData, as in ScDPDimension::IsValidPage?
|
|
while ( pColMember && pColMember->GetName() != *pName )
|
|
{
|
|
++nMemberIndex;
|
|
if ( nMemberIndex < nReferenceCount )
|
|
pColMember = pReferenceDim->GetMember( pReferenceDim->GetSortedIndex( nMemberIndex ) );
|
|
else
|
|
pColMember = nullptr;
|
|
}
|
|
}
|
|
|
|
bool bContinue = true;
|
|
while ( bContinue && nMemberIndex >= 0 && nMemberIndex < nReferenceCount )
|
|
{
|
|
pColMember = pReferenceDim->GetMember( pReferenceDim->GetSortedIndex( nMemberIndex ) );
|
|
|
|
// get column members below the reference field
|
|
|
|
const sal_Int32* pNextColIndex = pColIndexes + nRefDimPos + 1;
|
|
while ( *pNextColIndex >= 0 && pColMember )
|
|
{
|
|
ScDPDataDimension* pColChild = pColMember->GetChildDimension();
|
|
if ( pColChild && *pNextColIndex < pColChild->GetMemberCount() )
|
|
pColMember = pColChild->GetMember( *pNextColIndex );
|
|
else
|
|
pColMember = nullptr;
|
|
++pNextColIndex;
|
|
}
|
|
|
|
if ( pColMember && pRelativePos )
|
|
{
|
|
// Skip the member if it has hidden details
|
|
// (because when looking for the details, it is skipped, too).
|
|
// Also skip if the member is invisible because it has no data,
|
|
// for consistent ordering.
|
|
if ( pColMember->HasHiddenDetails() || !pColMember->IsVisible() )
|
|
pColMember = nullptr;
|
|
}
|
|
|
|
// continue searching only if looking for first existing or relative position
|
|
bContinue = ( pColMember == nullptr && ( bFirstExisting || pRelativePos ) );
|
|
nMemberIndex += nDirection;
|
|
}
|
|
}
|
|
else
|
|
pColMember = nullptr;
|
|
}
|
|
|
|
return pColMember;
|
|
}
|
|
|
|
#if DUMP_PIVOT_TABLE
|
|
void ScDPResultDimension::DumpState( const ScDPResultMember* pRefMember, ScDocument* pDoc, ScAddress& rPos ) const
|
|
{
|
|
OUString aDimName = bIsDataLayout ? u"(data layout)"_ustr : GetName();
|
|
dumpRow(u"ScDPResultDimension"_ustr, aDimName, nullptr, pDoc, rPos);
|
|
|
|
SCROW nStartRow = rPos.Row();
|
|
|
|
tools::Long nCount = bIsDataLayout ? 1 : maMemberArray.size();
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
const ScDPResultMember* pMember = maMemberArray[i].get();
|
|
pMember->DumpState( pRefMember, pDoc, rPos );
|
|
}
|
|
|
|
indent(pDoc, nStartRow, rPos);
|
|
}
|
|
|
|
void ScDPResultDimension::Dump(int nIndent) const
|
|
{
|
|
std::string aIndent(nIndent*2, ' ');
|
|
std::cout << aIndent << "-- dimension '" << GetName() << "'" << std::endl;
|
|
for (const auto& rxMember : maMemberArray)
|
|
{
|
|
const ScDPResultMember* p = rxMember.get();
|
|
p->Dump(nIndent+1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
tools::Long ScDPResultDimension::GetMemberCount() const
|
|
{
|
|
return maMemberArray.size();
|
|
}
|
|
|
|
const ScDPResultMember* ScDPResultDimension::GetMember(tools::Long n) const
|
|
{
|
|
return maMemberArray[n].get();
|
|
}
|
|
ScDPResultMember* ScDPResultDimension::GetMember(tools::Long n)
|
|
{
|
|
return maMemberArray[n].get();
|
|
}
|
|
|
|
ScDPResultDimension* ScDPResultDimension::GetFirstChildDimension() const
|
|
{
|
|
if ( !maMemberArray.empty() )
|
|
return maMemberArray[0]->GetChildDimension();
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
void ScDPResultDimension::FillVisibilityData(ScDPResultVisibilityData& rData) const
|
|
{
|
|
if (IsDataLayout())
|
|
return;
|
|
|
|
for (const auto& rxMember : maMemberArray)
|
|
{
|
|
ScDPResultMember* pMember = rxMember.get();
|
|
if (pMember->IsValid())
|
|
{
|
|
ScDPItemData aItem(pMember->FillItemData());
|
|
rData.addVisibleMember(GetName(), aItem);
|
|
pMember->FillVisibilityData(rData);
|
|
}
|
|
}
|
|
}
|
|
|
|
ScDPDataDimension::ScDPDataDimension( const ScDPResultData* pData ) :
|
|
pResultData( pData ),
|
|
pResultDimension( nullptr ),
|
|
bIsDataLayout( false )
|
|
{
|
|
}
|
|
|
|
ScDPDataDimension::~ScDPDataDimension()
|
|
{
|
|
}
|
|
|
|
void ScDPDataDimension::InitFrom( const ScDPResultDimension* pDim )
|
|
{
|
|
if (!pDim)
|
|
return;
|
|
|
|
pResultDimension = pDim;
|
|
bIsDataLayout = pDim->IsDataLayout();
|
|
|
|
// Go through all result members under the given result dimension, and
|
|
// create a new data member instance for each result member.
|
|
tools::Long nCount = pDim->GetMemberCount();
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
const ScDPResultMember* pResMem = pDim->GetMember(i);
|
|
|
|
ScDPDataMember* pNew = new ScDPDataMember( pResultData, pResMem );
|
|
maMembers.emplace_back( pNew);
|
|
|
|
if ( !pResultData->IsLateInit() )
|
|
{
|
|
// with LateInit, pResMem hasn't necessarily been initialized yet,
|
|
// so InitFrom for the new result member is called from its ProcessData method
|
|
|
|
const ScDPResultDimension* pChildDim = pResMem->GetChildDimension();
|
|
if ( pChildDim )
|
|
pNew->InitFrom( pChildDim );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScDPDataDimension::ProcessData( const vector< SCROW >& aDataMembers, const vector<ScDPValue>& aValues,
|
|
const ScDPSubTotalState& rSubState )
|
|
{
|
|
// the ScDPItemData array must contain enough entries for all dimensions - this isn't checked
|
|
|
|
tools::Long nCount = maMembers.size();
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
ScDPDataMember* pMember = maMembers[static_cast<sal_uInt16>(i)].get();
|
|
|
|
// always first member for data layout dim
|
|
if ( bIsDataLayout || ( !aDataMembers.empty() && pMember->IsNamedItem(aDataMembers[0]) ) )
|
|
{
|
|
vector<SCROW> aChildDataMembers;
|
|
if (aDataMembers.size() > 1)
|
|
{
|
|
vector<SCROW>::const_iterator itr = aDataMembers.begin();
|
|
aChildDataMembers.insert(aChildDataMembers.begin(), ++itr, aDataMembers.end());
|
|
}
|
|
pMember->ProcessData( aChildDataMembers, aValues, rSubState );
|
|
return;
|
|
}
|
|
}
|
|
|
|
OSL_FAIL("ProcessData: Member not found");
|
|
}
|
|
|
|
void ScDPDataDimension::FillDataRow(
|
|
const ScDPResultDimension* pRefDim, ScDPResultFilterContext& rFilterCxt,
|
|
uno::Sequence<sheet::DataResult>& rSequence, tools::Long nMeasure, bool bIsSubTotalRow,
|
|
const ScDPSubTotalState& rSubState) const
|
|
{
|
|
OUString aDimName;
|
|
bool bDataLayout = false;
|
|
if (pResultDimension)
|
|
{
|
|
aDimName = pResultDimension->GetName();
|
|
bDataLayout = pResultDimension->IsDataLayout();
|
|
}
|
|
|
|
FilterStack aFilterStack(rFilterCxt.maFilters);
|
|
aFilterStack.pushDimName(aDimName, bDataLayout);
|
|
|
|
assert(pRefDim);
|
|
OSL_ENSURE( static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
|
|
OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
|
|
|
|
const ScMemberSortOrder& rMemberOrder = pRefDim->GetMemberOrder();
|
|
|
|
tools::Long nMemberMeasure = nMeasure;
|
|
tools::Long nCount = maMembers.size();
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
tools::Long nSorted = rMemberOrder.empty() ? i : rMemberOrder[i];
|
|
|
|
tools::Long nMemberPos = nSorted;
|
|
if (bIsDataLayout)
|
|
{
|
|
OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
|
|
"DataLayout dimension twice?");
|
|
nMemberPos = 0;
|
|
nMemberMeasure = nSorted;
|
|
}
|
|
|
|
const ScDPResultMember* pRefMember = pRefDim->GetMember(nMemberPos);
|
|
if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataMember::FillDataRow ???
|
|
{
|
|
const ScDPDataMember* pDataMember = maMembers[static_cast<sal_uInt16>(nMemberPos)].get();
|
|
pDataMember->FillDataRow(pRefMember, rFilterCxt, rSequence, nMemberMeasure, bIsSubTotalRow, rSubState);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScDPDataDimension::UpdateDataRow( const ScDPResultDimension* pRefDim,
|
|
tools::Long nMeasure, bool bIsSubTotalRow,
|
|
const ScDPSubTotalState& rSubState ) const
|
|
{
|
|
assert(pRefDim);
|
|
OSL_ENSURE( static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
|
|
OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
|
|
|
|
tools::Long nMemberMeasure = nMeasure;
|
|
tools::Long nCount = maMembers.size();
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
tools::Long nMemberPos = i;
|
|
if (bIsDataLayout)
|
|
{
|
|
OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
|
|
"DataLayout dimension twice?");
|
|
nMemberPos = 0;
|
|
nMemberMeasure = i;
|
|
}
|
|
|
|
// Calculate must be called even if the member is not visible (for use as reference value)
|
|
const ScDPResultMember* pRefMember = pRefDim->GetMember(nMemberPos);
|
|
ScDPDataMember* pDataMember = maMembers[static_cast<sal_uInt16>(nMemberPos)].get();
|
|
pDataMember->UpdateDataRow( pRefMember, nMemberMeasure, bIsSubTotalRow, rSubState );
|
|
}
|
|
}
|
|
|
|
void ScDPDataDimension::SortMembers( ScDPResultDimension* pRefDim )
|
|
{
|
|
tools::Long nCount = maMembers.size();
|
|
|
|
if ( pRefDim->IsSortByData() )
|
|
{
|
|
// sort members
|
|
|
|
ScMemberSortOrder& rMemberOrder = pRefDim->GetMemberOrder();
|
|
OSL_ENSURE( rMemberOrder.empty(), "sort twice?" );
|
|
rMemberOrder.resize( nCount );
|
|
for (tools::Long nPos=0; nPos<nCount; nPos++)
|
|
rMemberOrder[nPos] = nPos;
|
|
|
|
ScDPColMembersOrder aComp( *this, pRefDim->GetSortMeasure(), pRefDim->IsSortAscending() );
|
|
::std::sort( rMemberOrder.begin(), rMemberOrder.end(), aComp );
|
|
}
|
|
|
|
// handle children
|
|
|
|
OSL_ENSURE( pRefDim && static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
|
|
OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
|
|
|
|
// for data layout, call only once - sorting measure is always taken from settings
|
|
tools::Long nLoopCount = bIsDataLayout ? 1 : nCount;
|
|
for (tools::Long i=0; i<nLoopCount; i++)
|
|
{
|
|
ScDPResultMember* pRefMember = pRefDim->GetMember(i);
|
|
if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataMember ???
|
|
{
|
|
ScDPDataMember* pDataMember = maMembers[static_cast<sal_uInt16>(i)].get();
|
|
pDataMember->SortMembers( pRefMember );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScDPDataDimension::DoAutoShow( ScDPResultDimension* pRefDim )
|
|
{
|
|
tools::Long nCount = maMembers.size();
|
|
|
|
// handle children first, before changing the visible state
|
|
|
|
assert(pRefDim);
|
|
OSL_ENSURE( static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
|
|
OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
|
|
|
|
// for data layout, call only once - sorting measure is always taken from settings
|
|
tools::Long nLoopCount = bIsDataLayout ? 1 : nCount;
|
|
for (tools::Long i=0; i<nLoopCount; i++)
|
|
{
|
|
ScDPResultMember* pRefMember = pRefDim->GetMember(i);
|
|
if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataMember ???
|
|
{
|
|
ScDPDataMember* pDataMember = maMembers[i].get();
|
|
pDataMember->DoAutoShow( pRefMember );
|
|
}
|
|
}
|
|
|
|
if ( !(pRefDim->IsAutoShow() && pRefDim->GetAutoCount() > 0 && pRefDim->GetAutoCount() < nCount) )
|
|
return;
|
|
|
|
// establish temporary order, hide remaining members
|
|
|
|
ScMemberSortOrder aAutoOrder;
|
|
aAutoOrder.resize( nCount );
|
|
tools::Long nPos;
|
|
for (nPos=0; nPos<nCount; nPos++)
|
|
aAutoOrder[nPos] = nPos;
|
|
|
|
ScDPColMembersOrder aComp( *this, pRefDim->GetAutoMeasure(), !pRefDim->IsAutoTopItems() );
|
|
::std::sort( aAutoOrder.begin(), aAutoOrder.end(), aComp );
|
|
|
|
// look for equal values to the last included one
|
|
|
|
tools::Long nIncluded = pRefDim->GetAutoCount();
|
|
ScDPDataMember* pDataMember1 = maMembers[aAutoOrder[nIncluded - 1]].get();
|
|
if ( !pDataMember1->IsVisible() )
|
|
pDataMember1 = nullptr;
|
|
bool bContinue = true;
|
|
while ( bContinue )
|
|
{
|
|
bContinue = false;
|
|
if ( nIncluded < nCount )
|
|
{
|
|
ScDPDataMember* pDataMember2 = maMembers[aAutoOrder[nIncluded]].get();
|
|
if ( !pDataMember2->IsVisible() )
|
|
pDataMember2 = nullptr;
|
|
|
|
if ( lcl_IsEqual( pDataMember1, pDataMember2, pRefDim->GetAutoMeasure() ) )
|
|
{
|
|
++nIncluded; // include more members if values are equal
|
|
bContinue = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// hide the remaining members
|
|
|
|
for (nPos = nIncluded; nPos < nCount; nPos++)
|
|
{
|
|
ScDPResultMember* pMember = pRefDim->GetMember(aAutoOrder[nPos]);
|
|
pMember->SetAutoHidden();
|
|
}
|
|
}
|
|
|
|
void ScDPDataDimension::ResetResults()
|
|
{
|
|
tools::Long nCount = maMembers.size();
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
// sort order doesn't matter
|
|
|
|
tools::Long nMemberPos = bIsDataLayout ? 0 : i;
|
|
ScDPDataMember* pDataMember = maMembers[nMemberPos].get();
|
|
pDataMember->ResetResults();
|
|
}
|
|
}
|
|
|
|
tools::Long ScDPDataDimension::GetSortedIndex( tools::Long nUnsorted ) const
|
|
{
|
|
if (!pResultDimension)
|
|
return nUnsorted;
|
|
|
|
const ScMemberSortOrder& rMemberOrder = pResultDimension->GetMemberOrder();
|
|
return rMemberOrder.empty() ? nUnsorted : rMemberOrder[nUnsorted];
|
|
}
|
|
|
|
void ScDPDataDimension::UpdateRunningTotals( const ScDPResultDimension* pRefDim,
|
|
tools::Long nMeasure, bool bIsSubTotalRow,
|
|
const ScDPSubTotalState& rSubState, ScDPRunningTotalState& rRunning,
|
|
ScDPRowTotals& rTotals, const ScDPResultMember& rRowParent ) const
|
|
{
|
|
assert(pRefDim);
|
|
OSL_ENSURE( static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
|
|
OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
|
|
|
|
tools::Long nMemberMeasure = nMeasure;
|
|
tools::Long nCount = maMembers.size();
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
const ScMemberSortOrder& rMemberOrder = pRefDim->GetMemberOrder();
|
|
tools::Long nSorted = rMemberOrder.empty() ? i : rMemberOrder[i];
|
|
|
|
tools::Long nMemberPos = nSorted;
|
|
if (bIsDataLayout)
|
|
{
|
|
OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
|
|
"DataLayout dimension twice?");
|
|
nMemberPos = 0;
|
|
nMemberMeasure = nSorted;
|
|
}
|
|
|
|
const ScDPResultMember* pRefMember = pRefDim->GetMember(nMemberPos);
|
|
if ( pRefMember->IsVisible() )
|
|
{
|
|
if ( bIsDataLayout )
|
|
rRunning.AddColIndex( 0, 0 );
|
|
else
|
|
rRunning.AddColIndex( i, nSorted );
|
|
|
|
ScDPDataMember* pDataMember = maMembers[nMemberPos].get();
|
|
pDataMember->UpdateRunningTotals(
|
|
pRefMember, nMemberMeasure, bIsSubTotalRow, rSubState, rRunning, rTotals, rRowParent);
|
|
|
|
rRunning.RemoveColIndex();
|
|
}
|
|
}
|
|
}
|
|
|
|
#if DUMP_PIVOT_TABLE
|
|
void ScDPDataDimension::DumpState( const ScDPResultDimension* pRefDim, ScDocument* pDoc, ScAddress& rPos ) const
|
|
{
|
|
OUString aDimName = bIsDataLayout ? u"(data layout)"_ustr : u"(unknown)"_ustr;
|
|
dumpRow(u"ScDPDataDimension"_ustr, aDimName, nullptr, pDoc, rPos);
|
|
|
|
SCROW nStartRow = rPos.Row();
|
|
|
|
tools::Long nCount = bIsDataLayout ? 1 : maMembers.size();
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
const ScDPResultMember* pRefMember = pRefDim->GetMember(i);
|
|
const ScDPDataMember* pDataMember = maMembers[i].get();
|
|
pDataMember->DumpState( pRefMember, pDoc, rPos );
|
|
}
|
|
|
|
indent(pDoc, nStartRow, rPos);
|
|
}
|
|
|
|
void ScDPDataDimension::Dump(int nIndent) const
|
|
{
|
|
std::string aIndent(nIndent*2, ' ');
|
|
std::cout << aIndent << "-- data dimension '"
|
|
<< (pResultDimension ? pResultDimension->GetName() : OUString()) << "'" << std::endl;
|
|
for (auto& rxMember : maMembers)
|
|
rxMember->Dump(nIndent+1);
|
|
}
|
|
#endif
|
|
|
|
tools::Long ScDPDataDimension::GetMemberCount() const
|
|
{
|
|
return maMembers.size();
|
|
}
|
|
|
|
const ScDPDataMember* ScDPDataDimension::GetMember(tools::Long n) const
|
|
{
|
|
return maMembers[n].get();
|
|
}
|
|
|
|
ScDPDataMember* ScDPDataDimension::GetMember(tools::Long n)
|
|
{
|
|
return maMembers[n].get();
|
|
}
|
|
|
|
ScDPResultVisibilityData::ScDPResultVisibilityData(
|
|
ScDPSource* pSource) :
|
|
mpSource(pSource)
|
|
{
|
|
}
|
|
|
|
ScDPResultVisibilityData::~ScDPResultVisibilityData()
|
|
{
|
|
}
|
|
|
|
void ScDPResultVisibilityData::addVisibleMember(const OUString& rDimName, const ScDPItemData& rMemberItem)
|
|
{
|
|
DimMemberType::iterator itr = maDimensions.find(rDimName);
|
|
if (itr == maDimensions.end())
|
|
{
|
|
pair<DimMemberType::iterator, bool> r = maDimensions.emplace(
|
|
rDimName, VisibleMemberType());
|
|
|
|
if (!r.second)
|
|
// insertion failed.
|
|
return;
|
|
|
|
itr = r.first;
|
|
}
|
|
VisibleMemberType& rMem = itr->second;
|
|
rMem.insert(rMemberItem);
|
|
}
|
|
|
|
void ScDPResultVisibilityData::fillFieldFilters(vector<ScDPFilteredCache::Criterion>& rFilters) const
|
|
{
|
|
typedef std::unordered_map<OUString, tools::Long> FieldNameMapType;
|
|
FieldNameMapType aFieldNames;
|
|
ScDPTableData* pData = mpSource->GetData();
|
|
sal_Int32 nColumnCount = pData->GetColumnCount();
|
|
for (sal_Int32 i = 0; i < nColumnCount; ++i)
|
|
{
|
|
aFieldNames.emplace(pData->getDimensionName(i), i);
|
|
}
|
|
|
|
const ScDPDimensions* pDims = mpSource->GetDimensionsObject();
|
|
for (const auto& [rDimName, rMem] : maDimensions)
|
|
{
|
|
ScDPFilteredCache::Criterion aCri;
|
|
FieldNameMapType::const_iterator itrField = aFieldNames.find(rDimName);
|
|
if (itrField == aFieldNames.end())
|
|
// This should never happen!
|
|
continue;
|
|
|
|
tools::Long nDimIndex = itrField->second;
|
|
aCri.mnFieldIndex = static_cast<sal_Int32>(nDimIndex);
|
|
aCri.mpFilter = std::make_shared<ScDPFilteredCache::GroupFilter>();
|
|
|
|
ScDPFilteredCache::GroupFilter* pGrpFilter =
|
|
static_cast<ScDPFilteredCache::GroupFilter*>(aCri.mpFilter.get());
|
|
|
|
for (const ScDPItemData& rMemItem : rMem)
|
|
{
|
|
pGrpFilter->addMatchItem(rMemItem);
|
|
}
|
|
|
|
ScDPDimension* pDim = pDims->getByIndex(nDimIndex);
|
|
ScDPMembers* pMembers = pDim->GetHierarchiesObject()->getByIndex(0)->
|
|
GetLevelsObject()->getByIndex(0)->GetMembersObject();
|
|
if (pGrpFilter->getMatchItemCount() < o3tl::make_unsigned(pMembers->getCount()))
|
|
rFilters.push_back(aCri);
|
|
}
|
|
}
|
|
|
|
size_t ScDPResultVisibilityData::MemberHash::operator() (const ScDPItemData& r) const
|
|
{
|
|
if (r.IsValue())
|
|
return static_cast<size_t>(::rtl::math::approxFloor(r.GetValue()));
|
|
else
|
|
return r.GetString().hashCode();
|
|
}
|
|
SCROW ScDPResultMember::GetDataId( ) const
|
|
{
|
|
const ScDPMember* pMemberDesc = GetDPMember();
|
|
if (pMemberDesc)
|
|
return pMemberDesc->GetItemDataId();
|
|
return -1;
|
|
}
|
|
|
|
ScDPResultMember* ScDPResultDimension::AddMember(const ScDPParentDimData &aData )
|
|
{
|
|
ScDPResultMember* pMember = new ScDPResultMember( pResultData, aData );
|
|
SCROW nDataIndex = pMember->GetDataId();
|
|
maMemberArray.emplace_back( pMember );
|
|
|
|
maMemberHash.emplace( nDataIndex, pMember );
|
|
return pMember;
|
|
}
|
|
|
|
ScDPResultMember* ScDPResultDimension::InsertMember(const ScDPParentDimData *pMemberData)
|
|
{
|
|
SCROW nInsert = 0;
|
|
if ( !lcl_SearchMember( maMemberArray, pMemberData->mnOrder , nInsert ) )
|
|
{
|
|
ScDPResultMember* pNew = new ScDPResultMember( pResultData, *pMemberData );
|
|
maMemberArray.emplace( maMemberArray.begin()+nInsert, pNew );
|
|
|
|
SCROW nDataIndex = pMemberData->mpMemberDesc->GetItemDataId();
|
|
maMemberHash.emplace( nDataIndex, pNew );
|
|
return pNew;
|
|
}
|
|
return maMemberArray[ nInsert ].get();
|
|
}
|
|
|
|
void ScDPResultDimension::InitWithMembers(
|
|
LateInitParams& rParams, const std::vector<SCROW>& pItemData, size_t nPos,
|
|
ScDPInitState& rInitState)
|
|
{
|
|
if ( rParams.IsEnd( nPos ) )
|
|
return;
|
|
ScDPDimension* pThisDim = rParams.GetDim( nPos );
|
|
ScDPLevel* pThisLevel = rParams.GetLevel( nPos );
|
|
SCROW nDataID = pItemData[nPos];
|
|
|
|
if (!(pThisDim && pThisLevel))
|
|
return;
|
|
|
|
tools::Long nDimSource = pThisDim->GetDimension(); //TODO: check GetSourceDim?
|
|
|
|
// create all members at the first call (preserve order)
|
|
ResultMembers& rMembers = pResultData->GetDimResultMembers(nDimSource, pThisDim, pThisLevel);
|
|
ScDPGroupCompare aCompare( pResultData, rInitState, nDimSource );
|
|
// initialize only specific member (or all if "show empty" flag is set)
|
|
ScDPResultMember* pResultMember = nullptr;
|
|
if ( bInitialized )
|
|
pResultMember = FindMember( nDataID );
|
|
else
|
|
bInitialized = true;
|
|
|
|
if ( pResultMember == nullptr )
|
|
{ //only insert found item
|
|
const ScDPParentDimData* pMemberData = rMembers.FindMember( nDataID );
|
|
if ( pMemberData && aCompare.IsIncluded( *( pMemberData->mpMemberDesc ) ) )
|
|
pResultMember = InsertMember( pMemberData );
|
|
}
|
|
if ( pResultMember )
|
|
{
|
|
rInitState.AddMember( nDimSource, pResultMember->GetDataId() );
|
|
pResultMember->LateInitFrom(rParams, pItemData, nPos+1, rInitState);
|
|
rInitState.RemoveMember();
|
|
}
|
|
}
|
|
|
|
ScDPParentDimData::ScDPParentDimData() :
|
|
mnOrder(-1), mpParentDim(nullptr), mpParentLevel(nullptr), mpMemberDesc(nullptr) {}
|
|
|
|
ScDPParentDimData::ScDPParentDimData(
|
|
SCROW nIndex, const ScDPDimension* pDim, const ScDPLevel* pLev, const ScDPMember* pMember) :
|
|
mnOrder(nIndex), mpParentDim(pDim), mpParentLevel(pLev), mpMemberDesc(pMember) {}
|
|
|
|
const ScDPParentDimData* ResultMembers::FindMember( SCROW nIndex ) const
|
|
{
|
|
auto aRes = maMemberHash.find( nIndex );
|
|
if( aRes != maMemberHash.end()) {
|
|
if ( aRes->second.mpMemberDesc && aRes->second.mpMemberDesc->GetItemDataId()==nIndex )
|
|
return &aRes->second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
void ResultMembers::InsertMember( const ScDPParentDimData& rNew )
|
|
{
|
|
if ( !rNew.mpMemberDesc->getShowDetails() )
|
|
mbHasHideDetailsMember = true;
|
|
maMemberHash.emplace( rNew.mpMemberDesc->GetItemDataId(), rNew );
|
|
}
|
|
|
|
ResultMembers::ResultMembers():
|
|
mbHasHideDetailsMember( false )
|
|
{
|
|
}
|
|
ResultMembers::~ResultMembers()
|
|
{
|
|
}
|
|
|
|
LateInitParams::LateInitParams(
|
|
const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLev, bool bRow ) :
|
|
mppDim( ppDim ),
|
|
mppLev( ppLev ),
|
|
mbRow( bRow ),
|
|
mbInitChild( true ),
|
|
mbAllChildren( false )
|
|
{
|
|
}
|
|
|
|
bool LateInitParams::IsEnd( size_t nPos ) const
|
|
{
|
|
return nPos >= mppDim.size();
|
|
}
|
|
|
|
void ScDPResultDimension::CheckShowEmpty( bool bShow )
|
|
{
|
|
tools::Long nCount = maMemberArray.size();
|
|
|
|
for (tools::Long i=0; i<nCount; i++)
|
|
{
|
|
ScDPResultMember* pMember = maMemberArray.at(i).get();
|
|
pMember->CheckShowEmpty(bShow);
|
|
}
|
|
|
|
}
|
|
|
|
void ScDPResultMember::CheckShowEmpty( bool bShow )
|
|
{
|
|
if (bHasElements)
|
|
{
|
|
ScDPResultDimension* pChildDim = GetChildDimension();
|
|
if (pChildDim)
|
|
pChildDim->CheckShowEmpty();
|
|
}
|
|
else if (IsValid() && bInitialized)
|
|
{
|
|
bShow = bShow || (GetParentLevel() && GetParentLevel()->getShowEmpty());
|
|
if (bShow)
|
|
{
|
|
SetHasElements();
|
|
ScDPResultDimension* pChildDim = GetChildDimension();
|
|
if (pChildDim)
|
|
pChildDim->CheckShowEmpty(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|