/* -*- 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 "dpitemdata.hxx" #include "document.hxx" #include "dpresfilter.hxx" #include "dputil.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace com::sun::star; using ::std::vector; using ::std::pair; using ::com::sun::star::uno::Sequence; namespace { sal_uInt16 nFuncStrIds[12] = // passend zum enum ScSubTotalFunc { 0, // 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 }; bool lcl_SearchMember( const std::vector & list, SCROW nOrder, SCROW& rIndex) { rIndex = list.size(); 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& mrFilters; public: FilterStack(std::vector& rFilters) : mrFilters(rFilters) {} void pushDimName(const OUString& rName, bool bDataLayout) { mrFilters.push_back(ScDPResultFilter(rName, bDataLayout)); } void pushDimValue(const OUString& rValue) { ScDPResultFilter& rFilter = mrFilters.back(); 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; long nMeasure; bool bAscending; public: ScDPRowMembersOrder( ScDPResultDimension& rDim, long nM, bool bAsc ) : rDimension(rDim), nMeasure(nM), bAscending(bAsc) {} ~ScDPRowMembersOrder() {} bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const; }; class ScDPColMembersOrder { ScDPDataDimension& rDimension; long nMeasure; bool bAscending; public: ScDPColMembersOrder( ScDPDataDimension& rDim, long nM, bool bAsc ) : rDimension(rDim), nMeasure(nM), bAscending(bAsc) {} ~ScDPColMembersOrder() {} bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const; }; static bool lcl_IsLess( const ScDPDataMember* pDataMember1, const ScDPDataMember* pDataMember2, long nMeasure, bool bAscending ) { // members can be NULL if used for rows ScDPSubTotalState aEmptyState; const ScDPAggData* pAgg1 = pDataMember1 ? pDataMember1->GetConstAggData( nMeasure, aEmptyState ) : NULL; const ScDPAggData* pAgg2 = pDataMember2 ? pDataMember2->GetConstAggData( nMeasure, aEmptyState ) : NULL; 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, long nMeasure ) { // members can be NULL if used for rows ScDPSubTotalState aEmptyState; const ScDPAggData* pAgg1 = pDataMember1 ? pDataMember1->GetConstAggData( nMeasure, aEmptyState ) : NULL; const ScDPAggData* pAgg2 = pDataMember2 ? pDataMember2->GetConstAggData( nMeasure, aEmptyState ) : NULL; 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(long nSrcIndex, SCROW nNameIndex) : mnSrcIndex(nSrcIndex), mnNameIndex(nNameIndex) {} void ScDPInitState::AddMember( long nSourceIndex, SCROW nMember ) { maMembers.push_back(Member(nSourceIndex, nMember)); } void ScDPInitState::RemoveMember() { OSL_ENSURE(!maMembers.empty(), "ScDPInitState::RemoveMember: Attempt to remmove member while empty."); if (!maMembers.empty()) maMembers.pop_back(); } namespace { #if DEBUG_PIVOT_TABLE void lcl_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 lcl_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( long nVisible, long nSorted ) { maColVisible.back() = nVisible; maColVisible.push_back(-1); maColSorted.back() = nSorted; maColSorted.push_back(-1); } void ScDPRunningTotalState::AddRowIndex( long nVisible, 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( long nBase, 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: { // fAux is used to sum up squares if ( !SubTotal::SafePlus( fVal, rNext.mfValue ) ) nCount = -1; // -1 for error double fAdd = rNext.mfValue; if ( !SubTotal::SafeMult( fAdd, rNext.mfValue ) || !SubTotal::SafePlus( fAux, fAdd ) ) nCount = -1; // -1 for error } 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_MAX: case SUBTOTAL_FUNC_MIN: case SUBTOTAL_FUNC_STDP: case SUBTOTAL_FUNC_VARP: bError = ( nCount <= 0 ); // no data is an error break; case SUBTOTAL_FUNC_STD: case SUBTOTAL_FUNC_VAR: bError = ( nCount < 2 ); // need at least 2 values 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 / (double) nCount; break; //! use safe mul for fVal * fVal case SUBTOTAL_FUNC_STD: if ( nCount >= 2 ) fResult = sqrt((fAux - fVal*fVal/(double)(nCount)) / (double)(nCount-1)); break; case SUBTOTAL_FUNC_VAR: if ( nCount >= 2 ) fResult = (fAux - fVal*fVal/(double)(nCount)) / (double)(nCount-1); break; case SUBTOTAL_FUNC_STDP: if ( nCount > 0 ) fResult = sqrt((fAux - fVal*fVal/(double)(nCount)) / (double)nCount); break; case SUBTOTAL_FUNC_VARP: if ( nCount > 0 ) fResult = (fAux - fVal*fVal/(double)(nCount)) / (double)nCount; 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 { OSL_ENSURE( IsCalculated(), "ScDPAggData not calculated" ); return fVal; // use calculated value } bool ScDPAggData::HasError() const { OSL_ENSURE( IsCalculated(), "ScDPAggData not calculated" ); return ( nCount == SC_DPAGG_RESULT_ERROR ); } bool ScDPAggData::HasData() const { OSL_ENSURE( IsCalculated(), "ScDPAggData not calculated" ); return ( nCount != SC_DPAGG_RESULT_EMPTY ); // values or error } void ScDPAggData::SetResult( double fNew ) { OSL_ENSURE( IsCalculated(), "ScDPAggData not calculated" ); fVal = fNew; // don't reset error flag } void ScDPAggData::SetError() { OSL_ENSURE( IsCalculated(), "ScDPAggData not calculated" ); nCount = SC_DPAGG_RESULT_ERROR; } void ScDPAggData::SetEmpty( bool bSet ) { OSL_ENSURE( 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 OSL_ENSURE( 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 OSL_ENSURE( IsCalculated(), "ScDPAggData not calculated" ); fAux = fNew; } ScDPAggData* ScDPAggData::GetChild() { if (!pChild) pChild = new ScDPAggData; return pChild; } void ScDPAggData::Reset() { fVal = 0.0; fAux = 0.0; nCount = SC_DPAGG_EMPTY; delete pChild; pChild = NULL; } #if DEBUG_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, long nMeasure ) { OSL_ENSURE( nMeasure >= 0, "GetColTotal: no measure" ); ScDPAggData* pAgg = pFirst; long nSkip = nMeasure; // subtotal settings are ignored - column/row totals exist once per measure for ( long nPos=0; nPosGetChild(); // 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( long nMeasure ) { return lcl_GetChildTotal( &aRowTotal, nMeasure ); } ScDPAggData* ScDPRowTotals::GetGrandTotal( long nMeasure ) { return lcl_GetChildTotal( &aGrandTotal, nMeasure ); } static ScSubTotalFunc lcl_GetForceFunc( const ScDPLevel* pLevel, long nFuncNo ) { ScSubTotalFunc eRet = SUBTOTAL_FUNC_NONE; if ( pLevel ) { //! direct access via ScDPLevel uno::Sequence aSeq = pLevel->getSubTotals(); long nSequence = aSeq.getLength(); if ( nSequence && aSeq[0] != sheet::GeneralFunction_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 ) { sheet::GeneralFunction eUser = aSeq.getConstArray()[nFuncNo]; if (eUser != sheet::GeneralFunction_AUTO) eRet = ScDPUtil::toSubTotalFunc(eUser); } } return eRet; } ScDPResultData::ScDPResultData( ScDPSource& rSrc ) : mrSource(rSrc), bLateInit( false ), bDataAtCol( false ), bDataAtRow( false ) { } ScDPResultData::~ScDPResultData() { std::for_each(maDimMembers.begin(), maDimMembers.end(), boost::checked_deleter()); } void ScDPResultData::SetMeasureData( std::vector& rFunctions, std::vector& rRefs, std::vector& rRefOrient, std::vector& 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.push_back(sheet::DataPilotFieldReference()); // 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(ScGlobal::GetRscString(STR_EMPTYDATA)); } void ScDPResultData::SetDataLayoutOrientation( sal_uInt16 nOrient ) { bDataAtCol = ( nOrient == sheet::DataPilotFieldOrientation_COLUMN ); bDataAtRow = ( nOrient == sheet::DataPilotFieldOrientation_ROW ); } void ScDPResultData::SetLateInit( bool bSet ) { bLateInit = bSet; } long ScDPResultData::GetColStartMeasure() const { if (maMeasureFuncs.size() == 1) return 0; return bDataAtCol ? SC_DPMEASURE_ALL : SC_DPMEASURE_ANY; } long ScDPResultData::GetRowStartMeasure() const { if (maMeasureFuncs.size() == 1) return 0; return bDataAtRow ? SC_DPMEASURE_ALL : SC_DPMEASURE_ANY; } ScSubTotalFunc ScDPResultData::GetMeasureFunction(long nMeasure) const { OSL_ENSURE((size_t) nMeasure < maMeasureFuncs.size(), "bumm"); return maMeasureFuncs[nMeasure]; } const sheet::DataPilotFieldReference& ScDPResultData::GetMeasureRefVal(long nMeasure) const { OSL_ENSURE((size_t) nMeasure < maMeasureRefs.size(), "bumm"); return maMeasureRefs[nMeasure]; } sal_uInt16 ScDPResultData::GetMeasureRefOrient(long nMeasure) const { OSL_ENSURE((size_t) nMeasure < maMeasureRefOrients.size(), "bumm"); return maMeasureRefOrients[nMeasure]; } OUString ScDPResultData::GetMeasureString(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 if ( eForceFunc != SUBTOTAL_FUNC_NONE ) return ScGlobal::GetRscString(nFuncStrIds[eForceFunc]); rbTotalResult = true; return ScGlobal::GetRscString(STR_TABLE_ERGEBNIS); } else { OSL_ENSURE((size_t) nMeasure < maMeasureFuncs.size(), "bumm"); const ScDPDimension* pDataDim = mrSource.GetDataDimension(nMeasure); if (pDataDim) { const 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(long nMeasure) const { if ( nMeasure < 0 ) { OSL_FAIL("GetMeasureDimensionName: negative"); return OUString("***"); } return mrSource.GetDataDimName(nMeasure); } bool ScDPResultData::IsBaseForGroup( long nDim ) const { return mrSource.GetData()->IsBaseForGroup(nDim); } long ScDPResultData::GetGroupBase( long nGroupDim ) const { return mrSource.GetData()->GetGroupBase(nGroupDim); } bool ScDPResultData::IsNumOrDateGroup( long nDim ) const { return mrSource.GetData()->IsNumOrDateGroup(nDim); } bool ScDPResultData::IsInGroup( SCROW nGroupDataId, long nGroupIndex, const ScDPItemData& rBaseData, 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, long nFirstIndex, const ScDPItemData& rSecondData, 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(long nDim, ScDPDimension* pDim, ScDPLevel* pLevel) const { if (nDim < static_cast(maDimMembers.size()) && maDimMembers[nDim]) return maDimMembers[nDim]; maDimMembers.resize(nDim+1, NULL); 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(); long nMembCount = pMembers->getCount(); for (long i = 0; i < nMembCount; ++i) { long nSorted = rGlobalOrder.empty() ? i : rGlobalOrder[i]; ScDPMember* pMember = pMembers->getByIndex(nSorted); if (!pResultMembers->FindMember(pMember->GetItemDataId())) { ScDPParentDimData* pNew = new ScDPParentDimData(i, pDim, pLevel, pMember); pResultMembers->InsertMember(pNew); } } maDimMembers[nDim] = pResultMembers; return maDimMembers[nDim]; } ScDPResultMember::ScDPResultMember( const ScDPResultData* pData, const ScDPParentDimData& rParentDimData, bool bForceSub ) : pResultData( pData ), aParentDimData( rParentDimData ), pChildDimension( NULL ), pDataRoot( NULL ), bHasElements( false ), bForceSubTotal( bForceSub ), bHasHiddenDetails( false ), bInitialized( false ), bAutoHidden( false ), nMemberStep( 1 ) { // pParentLevel/pMemberDesc is 0 for root members } ScDPResultMember::ScDPResultMember( const ScDPResultData* pData, bool bForceSub ) : pResultData( pData ), pChildDimension( NULL ), pDataRoot( NULL ), bHasElements( false ), bForceSubTotal( bForceSub ), bHasHiddenDetails( false ), bInitialized( false ), bAutoHidden( false ), nMemberStep( 1 ) { } ScDPResultMember::~ScDPResultMember() { delete pChildDimension; delete pDataRoot; } OUString ScDPResultMember::GetName() const { const ScDPMember* pMemberDesc = GetDPMember(); if (pMemberDesc) return pMemberDesc->GetNameStr(); else return ScGlobal::GetRscString(STR_PIVOT_TOTAL); // root member } OUString ScDPResultMember::GetDisplayName() const { const ScDPMember* pDPMember = GetDPMember(); if (!pDPMember) return OUString(); ScDPItemData aItem; pDPMember->FillItemData(aItem); if (aParentDimData.mpParentDim) { long nDim = aParentDimData.mpParentDim->GetDimension(); return pResultData->GetSource().GetData()->GetFormattedString(nDim, aItem); } return aItem.GetString(); } void ScDPResultMember::FillItemData( ScDPItemData& rData ) const { const ScDPMember* pMemberDesc = GetDPMember(); if (pMemberDesc) pMemberDesc->FillItemData( rData ); else rData.SetString( ScGlobal::GetRscString(STR_PIVOT_TOTAL) ); // root member } bool ScDPResultMember::IsNamedItem( SCROW nIndex ) const { //! 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::const_iterator itr = aMembers.begin(); vector aChildMembers(++itr, aMembers.end()); return pChildDim->IsValidEntry(aChildMembers); } else return true; } void ScDPResultMember::InitFrom( const vector& ppDim, const vector& 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 = 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 = new ScDPResultDimension( pResultData ); pChildDimension->InitFrom(ppDim, ppLev, nPos, rInitState, true); } } void ScDPResultMember::LateInitFrom( LateInitParams& rParams, const vector& 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 = 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 = new ScDPResultDimension( pResultData ); pChildDimension->LateInitFrom( rParams, pItemData, nPos, rInitState ); } } bool ScDPResultMember::IsSubTotalInTitle(long nMeasure) const { bool bRet = false; if ( pChildDimension && /*pParentLevel*/GetParentLevel() && /*pParentLevel*/GetParentLevel()->IsOutlineLayout() && /*pParentLevel*/GetParentLevel()->IsSubtotalsAtTop() ) { long nUserSubStart; 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; } long ScDPResultMember::GetSize(long nMeasure) const { if ( !IsVisible() ) return 0; const ScDPLevel* pParentLevel = GetParentLevel(); 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; long nSize = pChildDimension->GetSize(nMeasure); long nUserSubStart; 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; } long ScDPResultMember::GetSubTotalCount( 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 ) { //! direct access via ScDPLevel uno::Sequence aSeq = pParentLevel->getSubTotals(); long nSequence = aSeq.getLength(); if ( nSequence && aSeq[0] != sheet::GeneralFunction_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& aValues ) { SetHasElements(); if (pChildDimension) pChildDimension->ProcessData( aChildMembers, pDataDim, aDataMembers, aValues ); if ( !pDataRoot ) { pDataRoot = new ScDPDataMember( pResultData, NULL ); if ( pDataDim ) pDataRoot->InitFrom( pDataDim ); // recursive } ScDPSubTotalState aSubState; // initial state 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 (long nUserPos=0; nUserPos