diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sc/source/core/data/dptabsrc.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sc/source/core/data/dptabsrc.cxx')
-rw-r--r-- | sc/source/core/data/dptabsrc.cxx | 2613 |
1 files changed, 2613 insertions, 0 deletions
diff --git a/sc/source/core/data/dptabsrc.cxx b/sc/source/core/data/dptabsrc.cxx new file mode 100644 index 000000000..cab605f69 --- /dev/null +++ b/sc/source/core/data/dptabsrc.cxx @@ -0,0 +1,2613 @@ +/* -*- 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 <dptabsrc.hxx> + +#include <algorithm> +#include <vector> + +#include <comphelper/sequence.hxx> +#include <o3tl/any.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <rtl/math.hxx> +#include <sal/log.hxx> +#include <svl/itemprop.hxx> +#include <vcl/svapp.hxx> + +#include <dpcache.hxx> +#include <dptabres.hxx> +#include <dptabdat.hxx> +#include <global.hxx> +#include <miscuno.hxx> +#include <unonames.hxx> +#include <dpitemdata.hxx> +#include <dputil.hxx> +#include <dpresfilter.hxx> +#include <calcmacros.hxx> +#include <generalfunction.hxx> + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/sheet/DataPilotFieldFilter.hpp> +#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp> +#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp> +#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp> +#include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp> +#include <com/sun/star/sheet/GeneralFunction2.hpp> +#include <com/sun/star/sheet/TableFilterField.hpp> + +#include <unotools/calendarwrapper.hxx> +#include <com/sun/star/i18n/CalendarDisplayIndex.hpp> + +using namespace com::sun::star; +using ::std::vector; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Any; +using ::com::sun::star::sheet::DataPilotFieldAutoShowInfo; + +#define SC_MINCOUNT_LIMIT 1000000 + +SC_SIMPLE_SERVICE_INFO( ScDPSource, "ScDPSource", "com.sun.star.sheet.DataPilotSource" ) +SC_SIMPLE_SERVICE_INFO( ScDPDimensions, "ScDPDimensions", "com.sun.star.sheet.DataPilotSourceDimensions" ) +SC_SIMPLE_SERVICE_INFO( ScDPDimension, "ScDPDimension", "com.sun.star.sheet.DataPilotSourceDimension" ) + +// Typos are on purpose here, quote from Eike Rathke (see https://gerrit.libreoffice.org/c/core/+/101116): +// "The typo is exactly why the SC_SIMPLE_SERVICE_INFO_COMPAT() lists both service names, +// the old with the typo and the new corrected one. Correcting the typo in the old name +// will make all extensions fail that use it. This is not to be changed." +SC_SIMPLE_SERVICE_INFO_COMPAT( ScDPHierarchies, "ScDPHierarchies", + "com.sun.star.sheet.DataPilotSourceHierarchies", "com.sun.star.sheet.DataPilotSourceHierarcies" ) +SC_SIMPLE_SERVICE_INFO_COMPAT( ScDPHierarchy, "ScDPHierarchy", + "com.sun.star.sheet.DataPilotSourceHierarchy", "com.sun.star.sheet.DataPilotSourceHierarcy" ) + +SC_SIMPLE_SERVICE_INFO( ScDPLevels, "ScDPLevels", "com.sun.star.sheet.DataPilotSourceLevels" ) +SC_SIMPLE_SERVICE_INFO( ScDPLevel, "ScDPLevel", "com.sun.star.sheet.DataPilotSourceLevel" ) +SC_SIMPLE_SERVICE_INFO( ScDPMembers, "ScDPMembers", "com.sun.star.sheet.DataPilotSourceMembers" ) +SC_SIMPLE_SERVICE_INFO( ScDPMember, "ScDPMember", "com.sun.star.sheet.DataPilotSourceMember" ) + +// property maps for PropertySetInfo +// DataDescription / NumberFormat are internal + +//TODO: move to a header? +static bool lcl_GetBoolFromAny( const uno::Any& aAny ) +{ + auto b = o3tl::tryAccess<bool>(aAny); + return b && *b; +} + +ScDPSource::ScDPSource( ScDPTableData* pD ) : + pData( pD ), + bColumnGrand( true ), // default is true + bRowGrand( true ), + bIgnoreEmptyRows( false ), + bRepeatIfEmpty( false ), + nDupCount( 0 ), + bResultOverflow( false ), + bPageFiltered( false ) +{ + pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty ); +} + +ScDPSource::~ScDPSource() +{ + // free lists + + pColResults.reset(); + pRowResults.reset(); + + pColResRoot.reset(); + pRowResRoot.reset(); + pResData.reset(); +} + +const std::optional<OUString> & ScDPSource::GetGrandTotalName() const +{ + return mpGrandTotalName; +} + +sheet::DataPilotFieldOrientation ScDPSource::GetOrientation(sal_Int32 nColumn) +{ + if (std::find(maColDims.begin(), maColDims.end(), nColumn) != maColDims.end()) + return sheet::DataPilotFieldOrientation_COLUMN; + + if (std::find(maRowDims.begin(), maRowDims.end(), nColumn) != maRowDims.end()) + return sheet::DataPilotFieldOrientation_ROW; + + if (std::find(maDataDims.begin(), maDataDims.end(), nColumn) != maDataDims.end()) + return sheet::DataPilotFieldOrientation_DATA; + + if (std::find(maPageDims.begin(), maPageDims.end(), nColumn) != maPageDims.end()) + return sheet::DataPilotFieldOrientation_PAGE; + + return sheet::DataPilotFieldOrientation_HIDDEN; +} + +sal_Int32 ScDPSource::GetDataDimensionCount() const +{ + return maDataDims.size(); +} + +ScDPDimension* ScDPSource::GetDataDimension(sal_Int32 nIndex) +{ + if (nIndex < 0 || o3tl::make_unsigned(nIndex) >= maDataDims.size()) + return nullptr; + + sal_Int32 nDimIndex = maDataDims[nIndex]; + return GetDimensionsObject()->getByIndex(nDimIndex); +} + +OUString ScDPSource::GetDataDimName(sal_Int32 nIndex) +{ + OUString aRet; + ScDPDimension* pDim = GetDataDimension(nIndex); + if (pDim) + aRet = pDim->getName(); + return aRet; +} + +sal_Int32 ScDPSource::GetPosition(sal_Int32 nColumn) +{ + std::vector<sal_Int32>::const_iterator it, itBeg = maColDims.begin(), itEnd = maColDims.end(); + it = std::find(itBeg, itEnd, nColumn); + if (it != itEnd) + return std::distance(itBeg, it); + + itBeg = maRowDims.begin(); + itEnd = maRowDims.end(); + it = std::find(itBeg, itEnd, nColumn); + if (it != itEnd) + return std::distance(itBeg, it); + + itBeg = maDataDims.begin(); + itEnd = maDataDims.end(); + it = std::find(itBeg, itEnd, nColumn); + if (it != itEnd) + return std::distance(itBeg, it); + + itBeg = maPageDims.begin(); + itEnd = maPageDims.end(); + it = std::find(itBeg, itEnd, nColumn); + if (it != itEnd) + return std::distance(itBeg, it); + + return 0; +} + +namespace { + +bool testSubTotal( bool& rAllowed, sal_Int32 nColumn, const std::vector<sal_Int32>& rDims, ScDPSource* pSource ) +{ + rAllowed = true; + std::vector<sal_Int32>::const_iterator it = rDims.begin(), itEnd = rDims.end(); + for (; it != itEnd; ++it) + { + if (*it != nColumn) + continue; + + if ( pSource->IsDataLayoutDimension(nColumn) ) + { + // no subtotals for data layout dim, no matter where + rAllowed = false; + return true; + } + + // no subtotals if no other dim but data layout follows + ++it; + if (it != itEnd && pSource->IsDataLayoutDimension(*it)) + ++it; + if (it == itEnd) + rAllowed = false; + + return true; // found + } + + return false; +} + +void removeDim( sal_Int32 nRemove, std::vector<sal_Int32>& rDims ) +{ + std::vector<sal_Int32>::iterator it = std::find(rDims.begin(), rDims.end(), nRemove); + if (it != rDims.end()) + rDims.erase(it); +} + +} + +bool ScDPSource::SubTotalAllowed(sal_Int32 nColumn) +{ + //TODO: cache this at ScDPResultData + bool bAllowed = true; + if ( testSubTotal(bAllowed, nColumn, maColDims, this) ) + return bAllowed; + if ( testSubTotal(bAllowed, nColumn, maRowDims, this) ) + return bAllowed; + return bAllowed; +} + +void ScDPSource::SetOrientation(sal_Int32 nColumn, sheet::DataPilotFieldOrientation nNew) +{ + //TODO: change to no-op if new orientation is equal to old? + + // remove from old list + removeDim(nColumn, maColDims); + removeDim(nColumn, maRowDims); + removeDim(nColumn, maDataDims); + removeDim(nColumn, maPageDims); + + // add to new list + switch (nNew) + { + case sheet::DataPilotFieldOrientation_COLUMN: + maColDims.push_back(nColumn); + break; + case sheet::DataPilotFieldOrientation_ROW: + maRowDims.push_back(nColumn); + break; + case sheet::DataPilotFieldOrientation_DATA: + maDataDims.push_back(nColumn); + break; + case sheet::DataPilotFieldOrientation_PAGE: + maPageDims.push_back(nColumn); + break; + // DataPilot Migration - Cache&&Performance + case sheet::DataPilotFieldOrientation_HIDDEN: + break; + default: + OSL_FAIL( "ScDPSource::SetOrientation: unexpected orientation" ); + break; + } +} + +bool ScDPSource::IsDataLayoutDimension(sal_Int32 nDim) +{ + return nDim == pData->GetColumnCount(); +} + +sheet::DataPilotFieldOrientation ScDPSource::GetDataLayoutOrientation() +{ + return GetOrientation(pData->GetColumnCount()); +} + +bool ScDPSource::IsDateDimension(sal_Int32 nDim) +{ + return pData->IsDateDimension(nDim); +} + +ScDPDimensions* ScDPSource::GetDimensionsObject() +{ + if (!pDimensions.is()) + { + pDimensions = new ScDPDimensions(this); + } + return pDimensions.get(); +} + +uno::Reference<container::XNameAccess> SAL_CALL ScDPSource::getDimensions() +{ + return GetDimensionsObject(); +} + +void ScDPSource::SetDupCount( tools::Long nNew ) +{ + nDupCount = nNew; +} + +ScDPDimension* ScDPSource::AddDuplicated(std::u16string_view rNewName) +{ + OSL_ENSURE( pDimensions.is(), "AddDuplicated without dimensions?" ); + + // re-use + + tools::Long nOldDimCount = pDimensions->getCount(); + for (tools::Long i=0; i<nOldDimCount; i++) + { + ScDPDimension* pDim = pDimensions->getByIndex(i); + if (pDim && pDim->getName() == rNewName) + { + //TODO: test if pDim is a duplicate of source + return pDim; + } + } + + SetDupCount( nDupCount + 1 ); + pDimensions->CountChanged(); // uses nDupCount + + return pDimensions->getByIndex( pDimensions->getCount() - 1 ); +} + +sal_Int32 ScDPSource::GetSourceDim(sal_Int32 nDim) +{ + // original source dimension or data layout dimension? + if ( nDim <= pData->GetColumnCount() ) + return nDim; + + if ( nDim < pDimensions->getCount() ) + { + ScDPDimension* pDimObj = pDimensions->getByIndex( nDim ); + if ( pDimObj ) + { + tools::Long nSource = pDimObj->GetSourceDim(); + if ( nSource >= 0 ) + return nSource; + } + } + + OSL_FAIL("GetSourceDim: wrong dim"); + return nDim; +} + +uno::Sequence< uno::Sequence<sheet::DataResult> > SAL_CALL ScDPSource::getResults() +{ + CreateRes_Impl(); // create pColResRoot and pRowResRoot + + if ( bResultOverflow ) // set in CreateRes_Impl + { + // no results available + throw uno::RuntimeException(); + } + + sal_Int32 nColCount = pColResRoot->GetSize(pResData->GetColStartMeasure()); + sal_Int32 nRowCount = pRowResRoot->GetSize(pResData->GetRowStartMeasure()); + + // allocate full sequence + //TODO: leave out empty rows??? + + uno::Sequence< uno::Sequence<sheet::DataResult> > aSeq( nRowCount ); + uno::Sequence<sheet::DataResult>* pRowAry = aSeq.getArray(); + for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence<sheet::DataResult> aColSeq( nColCount ); + // use default values of DataResult + pRowAry[nRow] = aColSeq; + } + + ScDPResultFilterContext aFilterCxt; + pRowResRoot->FillDataResults( + pColResRoot.get(), aFilterCxt, aSeq, pResData->GetRowStartMeasure()); + + maResFilterSet.swap(aFilterCxt.maFilterSet); // Keep this data for GETPIVOTDATA. + + return aSeq; +} + +uno::Sequence<double> ScDPSource::getFilteredResults( + const uno::Sequence<sheet::DataPilotFieldFilter>& aFilters ) +{ + if (maResFilterSet.empty()) + getResults(); // Build result tree first. + + // Get result values from the tree. + const ScDPResultTree::ValuesType* pVals = maResFilterSet.getResults(aFilters); + if (pVals && !pVals->empty()) + { + return comphelper::containerToSequence(*pVals); + } + + if (aFilters.getLength() == 1) + { + // Try to get result from the leaf nodes. + double fVal = maResFilterSet.getLeafResult(aFilters[0]); + if (!std::isnan(fVal)) + { + return { fVal }; + } + } + + return uno::Sequence<double>(); +} + +void SAL_CALL ScDPSource::refresh() +{ + disposeData(); +} + +void SAL_CALL ScDPSource::addRefreshListener( const uno::Reference<util::XRefreshListener >& ) +{ + OSL_FAIL("not implemented"); //TODO: exception? +} + +void SAL_CALL ScDPSource::removeRefreshListener( const uno::Reference<util::XRefreshListener >& ) +{ + OSL_FAIL("not implemented"); //TODO: exception? +} + +Sequence< Sequence<Any> > SAL_CALL ScDPSource::getDrillDownData(const Sequence<sheet::DataPilotFieldFilter>& aFilters) +{ + sal_Int32 nColumnCount = GetData()->GetColumnCount(); + + vector<ScDPFilteredCache::Criterion> aFilterCriteria; + for (const sheet::DataPilotFieldFilter& rFilter : aFilters) + { + const OUString& aFieldName = rFilter.FieldName; + for (sal_Int32 nCol = 0; nCol < nColumnCount; ++nCol) + { + if (aFieldName == pData->getDimensionName(nCol)) + { + ScDPDimension* pDim = GetDimensionsObject()->getByIndex( nCol ); + ScDPMembers* pMembers = pDim->GetHierarchiesObject()->getByIndex(0)-> + GetLevelsObject()->getByIndex(0)->GetMembersObject(); + sal_Int32 nIndex = pMembers->GetIndexFromName( rFilter.MatchValueName ); + if ( nIndex >= 0 ) + { + ScDPItemData aItem(pMembers->getByIndex(nIndex)->FillItemData()); + aFilterCriteria.emplace_back( ); + aFilterCriteria.back().mnFieldIndex = nCol; + aFilterCriteria.back().mpFilter = + std::make_shared<ScDPFilteredCache::SingleFilter>(aItem); + } + } + } + } + + // Take into account the visibilities of field members. + ScDPResultVisibilityData aResVisData(this); + pRowResRoot->FillVisibilityData(aResVisData); + pColResRoot->FillVisibilityData(aResVisData); + aResVisData.fillFieldFilters(aFilterCriteria); + + Sequence< Sequence<Any> > aTabData; + std::unordered_set<sal_Int32> aCatDims; + GetCategoryDimensionIndices(aCatDims); + pData->GetDrillDownData(std::move(aFilterCriteria), std::move(aCatDims), aTabData); + return aTabData; +} + +OUString ScDPSource::getDataDescription() +{ + CreateRes_Impl(); // create pResData + + OUString aRet; + if ( pResData->GetMeasureCount() == 1 ) + { + bool bTotalResult = false; + aRet = pResData->GetMeasureString(0, true, SUBTOTAL_FUNC_NONE, bTotalResult); + } + + // empty for more than one measure + + return aRet; +} + +void ScDPSource::setIgnoreEmptyRows(bool bSet) +{ + bIgnoreEmptyRows = bSet; + pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty ); +} + +void ScDPSource::setRepeatIfEmpty(bool bSet) +{ + bRepeatIfEmpty = bSet; + pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty ); +} + +void ScDPSource::disposeData() +{ + maResFilterSet.clear(); + + if ( pResData ) + { + // reset all data... + + pColResRoot.reset(); + pRowResRoot.reset(); + pResData.reset(); + pColResults.reset(); + pRowResults.reset(); + aColLevelList.clear(); + aRowLevelList.clear(); + } + + pDimensions.clear(); // settings have to be applied (from SaveData) again! + SetDupCount( 0 ); + + maColDims.clear(); + maRowDims.clear(); + maDataDims.clear(); + maPageDims.clear(); + + pData->DisposeData(); // cached entries etc. + bPageFiltered = false; + bResultOverflow = false; +} + +static tools::Long lcl_CountMinMembers(const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLevel, tools::Long nLevels ) +{ + // Calculate the product of the member count for those consecutive levels that + // have the "show all" flag, one following level, and the data layout dimension. + + tools::Long nTotal = 1; + tools::Long nDataCount = 1; + bool bWasShowAll = true; + tools::Long nPos = nLevels; + while ( nPos > 0 ) + { + --nPos; + + if ( nPos+1 < nLevels && ppDim[nPos] == ppDim[nPos+1] ) + { + OSL_FAIL("lcl_CountMinMembers: multiple levels from one dimension not implemented"); + return 0; + } + + bool bDo = false; + if ( ppDim[nPos]->getIsDataLayoutDimension() ) + { + // data layout dim doesn't interfere with "show all" flags + nDataCount = ppLevel[nPos]->GetMembersObject()->getCount(); + if ( nDataCount == 0 ) + nDataCount = 1; + } + else if ( bWasShowAll ) // "show all" set for all following levels? + { + bDo = true; + if ( !ppLevel[nPos]->getShowEmpty() ) + { + // this level is counted, following ones are not + bWasShowAll = false; + } + } + if ( bDo ) + { + tools::Long nThisCount = ppLevel[nPos]->GetMembersObject()->getMinMembers(); + if ( nThisCount == 0 ) + { + nTotal = 1; // empty level -> start counting from here + //TODO: start with visible elements in this level? + } + else + { + if ( nTotal >= LONG_MAX / nThisCount ) + return LONG_MAX; // overflow + nTotal *= nThisCount; + } + } + } + + // always include data layout dim, even after restarting + if ( nTotal >= LONG_MAX / nDataCount ) + return LONG_MAX; // overflow + nTotal *= nDataCount; + + return nTotal; +} + +void ScDPSource::FillCalcInfo(bool bIsRow, ScDPTableData::CalcInfo& rInfo, bool &rHasAutoShow) +{ + const std::vector<sal_Int32>& rDims = bIsRow ? maRowDims : maColDims; + for (const auto& rDimIndex : rDims) + { + ScDPDimension* pDim = GetDimensionsObject()->getByIndex(rDimIndex); + tools::Long nHierarchy = ScDPDimension::getUsedHierarchy(); + if ( nHierarchy >= ScDPHierarchies::getCount() ) + nHierarchy = 0; + ScDPLevels* pLevels = pDim->GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject(); + sal_Int32 nCount = pLevels->getCount(); + + //TODO: Test + if (pDim->getIsDataLayoutDimension() && maDataDims.size() < 2) + nCount = 0; + //TODO: Test + + for (sal_Int32 j = 0; j < nCount; ++j) + { + ScDPLevel* pLevel = pLevels->getByIndex(j); + pLevel->EvaluateSortOrder(); + + // no layout flags for column fields, only for row fields + pLevel->SetEnableLayout( bIsRow ); + + if ( pLevel->GetAutoShow().IsEnabled ) + rHasAutoShow = true; + + if (bIsRow) + { + rInfo.aRowLevelDims.push_back(rDimIndex); + rInfo.aRowDims.push_back(pDim); + rInfo.aRowLevels.push_back(pLevel); + } + else + { + rInfo.aColLevelDims.push_back(rDimIndex); + rInfo.aColDims.push_back(pDim); + rInfo.aColLevels.push_back(pLevel); + } + + pLevel->GetMembersObject(); // initialize for groups + } + } +} + +namespace { + +class CategoryDimInserter +{ + ScDPSource& mrSource; + std::unordered_set<sal_Int32>& mrCatDims; +public: + CategoryDimInserter(ScDPSource& rSource, std::unordered_set<sal_Int32>& rCatDims) : + mrSource(rSource), + mrCatDims(rCatDims) {} + + void operator() (tools::Long nDim) + { + if (!mrSource.IsDataLayoutDimension(nDim)) + mrCatDims.insert(nDim); + } +}; + +} + +void ScDPSource::GetCategoryDimensionIndices(std::unordered_set<sal_Int32>& rCatDims) +{ + std::unordered_set<sal_Int32> aCatDims; + + CategoryDimInserter aInserter(*this, aCatDims); + std::for_each(maColDims.begin(), maColDims.end(), aInserter); + std::for_each(maRowDims.begin(), maRowDims.end(), aInserter); + std::for_each(maPageDims.begin(), maPageDims.end(), aInserter); + + rCatDims.swap(aCatDims); +} + +void ScDPSource::FilterCacheByPageDimensions() +{ + // #i117661# Repeated calls to ScDPFilteredCache::filterByPageDimension + // are invalid because rows are only hidden, never shown again. If + // FilterCacheByPageDimensions is called again, the cache table must + // be re-initialized. Currently, CreateRes_Impl always uses a fresh cache + // because ScDBDocFunc::DataPilotUpdate calls InvalidateData. + + if (bPageFiltered) + { + SAL_WARN( "sc.core","tried to apply page field filters several times"); + + pData->DisposeData(); + pData->CreateCacheTable(); // re-initialize the cache table + bPageFiltered = false; + } + + // filter table by page dimensions. + vector<ScDPFilteredCache::Criterion> aCriteria; + for (const auto& rDimIndex : maPageDims) + { + ScDPDimension* pDim = GetDimensionsObject()->getByIndex(rDimIndex); + tools::Long nField = pDim->GetDimension(); + + ScDPMembers* pMems = pDim->GetHierarchiesObject()->getByIndex(0)-> + GetLevelsObject()->getByIndex(0)->GetMembersObject(); + + tools::Long nMemCount = pMems->getCount(); + ScDPFilteredCache::Criterion aFilter; + aFilter.mnFieldIndex = static_cast<sal_Int32>(nField); + aFilter.mpFilter = std::make_shared<ScDPFilteredCache::GroupFilter>(); + ScDPFilteredCache::GroupFilter* pGrpFilter = + static_cast<ScDPFilteredCache::GroupFilter*>(aFilter.mpFilter.get()); + for (tools::Long j = 0; j < nMemCount; ++j) + { + ScDPMember* pMem = pMems->getByIndex(j); + if (pMem->isVisible()) + { + ScDPItemData aData(pMem->FillItemData()); + pGrpFilter->addMatchItem(aData); + } + } + if (pGrpFilter->getMatchItemCount() < o3tl::make_unsigned(nMemCount)) + // there is at least one invisible item. Add this filter criterion to the mix. + aCriteria.push_back(aFilter); + + if (!pDim->HasSelectedPage()) + continue; + + const ScDPItemData& rData = pDim->GetSelectedData(); + aCriteria.emplace_back(); + ScDPFilteredCache::Criterion& r = aCriteria.back(); + r.mnFieldIndex = static_cast<sal_Int32>(nField); + r.mpFilter = std::make_shared<ScDPFilteredCache::SingleFilter>(rData); + } + if (!aCriteria.empty()) + { + std::unordered_set<sal_Int32> aCatDims; + GetCategoryDimensionIndices(aCatDims); + pData->FilterCacheTable(std::move(aCriteria), std::move(aCatDims)); + bPageFiltered = true; + } +} + +void ScDPSource::CreateRes_Impl() +{ + if (pResData) + return; + + sheet::DataPilotFieldOrientation nDataOrient = GetDataLayoutOrientation(); + if (maDataDims.size() > 1 && ( nDataOrient != sheet::DataPilotFieldOrientation_COLUMN && + nDataOrient != sheet::DataPilotFieldOrientation_ROW ) ) + { + // if more than one data dimension, data layout orientation must be set + SetOrientation( pData->GetColumnCount(), sheet::DataPilotFieldOrientation_ROW ); + nDataOrient = sheet::DataPilotFieldOrientation_ROW; + } + + // TODO: Aggregate pDataNames, pDataRefValues, nDataRefOrient, and + // eDataFunctions into a structure and use vector instead of static + // or pointer arrays. + vector<OUString> aDataNames; + vector<sheet::DataPilotFieldReference> aDataRefValues; + vector<ScSubTotalFunc> aDataFunctions; + vector<sheet::DataPilotFieldOrientation> aDataRefOrient; + + ScDPTableData::CalcInfo aInfo; + + // LateInit (initialize only those rows/children that are used) can be used unless + // any data dimension needs reference values from column/row dimensions + bool bLateInit = true; + + // Go through all data dimensions (i.e. fields) and build their meta data + // so that they can be passed on to ScDPResultData instance later. + // TODO: aggregate all of data dimension info into a structure. + for (const tools::Long nDimIndex : maDataDims) + { + // Get function for each data field. + ScDPDimension* pDim = GetDimensionsObject()->getByIndex(nDimIndex); + ScGeneralFunction eUser = pDim->getFunction(); + if (eUser == ScGeneralFunction::AUTO) + { + //TODO: test for numeric data + eUser = ScGeneralFunction::SUM; + } + + // Map UNO's enum to internal enum ScSubTotalFunc. + aDataFunctions.push_back(ScDPUtil::toSubTotalFunc(eUser)); + + // Get reference field/item information. + aDataRefValues.push_back(pDim->GetReferenceValue()); + sheet::DataPilotFieldOrientation nDataRefOrient = sheet::DataPilotFieldOrientation_HIDDEN; // default if not used + sal_Int32 eRefType = aDataRefValues.back().ReferenceType; + if ( eRefType == sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE || + eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE || + eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE || + eRefType == sheet::DataPilotFieldReferenceType::RUNNING_TOTAL ) + { + sal_Int32 nColumn = comphelper::findValue( + GetDimensionsObject()->getElementNames(), aDataRefValues.back().ReferenceField); + if ( nColumn >= 0 ) + { + nDataRefOrient = GetOrientation(nColumn); + // need fully initialized results to find reference values + // (both in column or row dimensions), so updated values or + // differences to 0 can be displayed even for empty results. + bLateInit = false; + } + } + + aDataRefOrient.push_back(nDataRefOrient); + + aDataNames.push_back(pDim->getName()); + + //TODO: modify user visible strings as in ScDPResultData::GetMeasureString instead! + + aDataNames.back() = ScDPUtil::getSourceDimensionName(aDataNames.back()); + + //TODO: if the name is overridden by user, a flag must be set + //TODO: so the user defined name replaces the function string and field name. + + //TODO: the complete name (function and field) must be stored at the dimension + + tools::Long nSource = pDim->GetSourceDim(); + if (nSource >= 0) + aInfo.aDataSrcCols.push_back(nSource); + else + aInfo.aDataSrcCols.push_back(nDimIndex); + } + + pResData.reset( new ScDPResultData(*this) ); + pResData->SetMeasureData(aDataFunctions, aDataRefValues, aDataRefOrient, aDataNames); + pResData->SetDataLayoutOrientation(nDataOrient); + pResData->SetLateInit( bLateInit ); + + bool bHasAutoShow = false; + + ScDPInitState aInitState; + + // Page field selections restrict the members shown in related fields + // (both in column and row fields). aInitState is filled with the page + // field selections, they are kept across the data iterator loop. + + for (const auto& rDimIndex : maPageDims) + { + ScDPDimension* pDim = GetDimensionsObject()->getByIndex(rDimIndex); + if ( pDim->HasSelectedPage() ) + aInitState.AddMember(rDimIndex, GetCache()->GetIdByItemData(rDimIndex, pDim->GetSelectedData())); + } + + // Show grand total columns only when the option is set *and* there is at + // least one column field. Same for the grand total rows. + sheet::DataPilotFieldOrientation nDataLayoutOrient = GetDataLayoutOrientation(); + tools::Long nColDimCount2 = maColDims.size() - (nDataLayoutOrient == sheet::DataPilotFieldOrientation_COLUMN ? 1 : 0); + tools::Long nRowDimCount2 = maRowDims.size() - (nDataLayoutOrient == sheet::DataPilotFieldOrientation_ROW ? 1 : 0); + bool bShowColGrand = bColumnGrand && nColDimCount2 > 0; + bool bShowRowGrand = bRowGrand && nRowDimCount2 > 0; + pColResRoot.reset( new ScDPResultMember(pResData.get(), bShowColGrand) ); + pRowResRoot.reset( new ScDPResultMember(pResData.get(), bShowRowGrand) ); + + FillCalcInfo(false, aInfo, bHasAutoShow); + tools::Long nColLevelCount = aInfo.aColLevels.size(); + + pColResRoot->InitFrom( aInfo.aColDims, aInfo.aColLevels, 0, aInitState ); + pColResRoot->SetHasElements(); + + FillCalcInfo(true, aInfo, bHasAutoShow); + tools::Long nRowLevelCount = aInfo.aRowLevels.size(); + + if ( nRowLevelCount > 0 ) + { + // disable layout flags for the innermost row field (level) + aInfo.aRowLevels[nRowLevelCount-1]->SetEnableLayout( false ); + } + + pRowResRoot->InitFrom( aInfo.aRowDims, aInfo.aRowLevels, 0, aInitState ); + pRowResRoot->SetHasElements(); + + // initialize members object also for all page dimensions (needed for numeric groups) + for (const auto& rDimIndex : maPageDims) + { + ScDPDimension* pDim = GetDimensionsObject()->getByIndex(rDimIndex); + tools::Long nHierarchy = ScDPDimension::getUsedHierarchy(); + if ( nHierarchy >= ScDPHierarchies::getCount() ) + nHierarchy = 0; + + ScDPLevels* pLevels = pDim->GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject(); + tools::Long nCount = pLevels->getCount(); + for (tools::Long j=0; j<nCount; j++) + pLevels->getByIndex(j)->GetMembersObject(); // initialize for groups + } + + // pre-check: calculate minimum number of result columns / rows from + // levels that have the "show all" flag set + + tools::Long nMinColMembers = lcl_CountMinMembers( aInfo.aColDims, aInfo.aColLevels, nColLevelCount ); + tools::Long nMinRowMembers = lcl_CountMinMembers( aInfo.aRowDims, aInfo.aRowLevels, nRowLevelCount ); + + if ( nMinColMembers > MAXCOLCOUNT/*SC_MINCOUNT_LIMIT*/ || nMinRowMembers > SC_MINCOUNT_LIMIT ) + { + // resulting table is too big -> abort before calculating + // (this relies on late init, so no members are allocated in InitFrom above) + + bResultOverflow = true; + return; + } + + FilterCacheByPageDimensions(); + + aInfo.aPageDims = maPageDims; + aInfo.pInitState = &aInitState; + aInfo.pColRoot = pColResRoot.get(); + aInfo.pRowRoot = pRowResRoot.get(); + pData->CalcResults(aInfo, false); + + pColResRoot->CheckShowEmpty(); + pRowResRoot->CheckShowEmpty(); + + // With all data processed, calculate the final results: + + // UpdateDataResults calculates all original results from the collected values, + // and stores them as reference values if needed. + pRowResRoot->UpdateDataResults( pColResRoot.get(), pResData->GetRowStartMeasure() ); + + if ( bHasAutoShow ) // do the double calculation only if AutoShow is used + { + // Find the desired members and set bAutoHidden flag for the others + pRowResRoot->DoAutoShow( pColResRoot.get() ); + + // Reset all results to empty, so they can be built again with data for the + // desired members only. + pColResRoot->ResetResults(); + pRowResRoot->ResetResults(); + pData->CalcResults(aInfo, true); + + // Call UpdateDataResults again, with the new (limited) values. + pRowResRoot->UpdateDataResults( pColResRoot.get(), pResData->GetRowStartMeasure() ); + } + + // SortMembers does the sorting by a result dimension, using the original results, + // but not running totals etc. + pRowResRoot->SortMembers( pColResRoot.get() ); + + // UpdateRunningTotals calculates running totals along column/row dimensions, + // differences from other members (named or relative), and column/row percentages + // or index values. + // Running totals and relative differences need to be done using the sorted values. + // Column/row percentages and index values must be done after sorting, because the + // results may no longer be in the right order (row total for percentage of row is + // always 1). + ScDPRunningTotalState aRunning( pColResRoot.get(), pRowResRoot.get() ); + ScDPRowTotals aTotals; + pRowResRoot->UpdateRunningTotals( pColResRoot.get(), pResData->GetRowStartMeasure(), aRunning, aTotals ); + +#if DUMP_PIVOT_TABLE + DumpResults(); +#endif +} + +void ScDPSource::FillLevelList( sheet::DataPilotFieldOrientation nOrientation, std::vector<ScDPLevel*> &rList ) +{ + rList.clear(); + + std::vector<sal_Int32>* pDimIndex = nullptr; + switch (nOrientation) + { + case sheet::DataPilotFieldOrientation_COLUMN: + pDimIndex = &maColDims; + break; + case sheet::DataPilotFieldOrientation_ROW: + pDimIndex = &maRowDims; + break; + case sheet::DataPilotFieldOrientation_DATA: + pDimIndex = &maDataDims; + break; + case sheet::DataPilotFieldOrientation_PAGE: + pDimIndex = &maPageDims; + break; + default: + OSL_FAIL( "ScDPSource::FillLevelList: unexpected orientation" ); + break; + } + if (!pDimIndex) + { + OSL_FAIL("invalid orientation"); + return; + } + + ScDPDimensions* pDims = GetDimensionsObject(); + for (const auto& rIndex : *pDimIndex) + { + ScDPDimension* pDim = pDims->getByIndex(rIndex); + OSL_ENSURE( pDim->getOrientation() == nOrientation, "orientations are wrong" ); + + ScDPHierarchies* pHiers = pDim->GetHierarchiesObject(); + sal_Int32 nHierarchy = ScDPDimension::getUsedHierarchy(); + if ( nHierarchy >= ScDPHierarchies::getCount() ) + nHierarchy = 0; + ScDPHierarchy* pHier = pHiers->getByIndex(nHierarchy); + ScDPLevels* pLevels = pHier->GetLevelsObject(); + sal_Int32 nLevCount = pLevels->getCount(); + for (sal_Int32 nLev=0; nLev<nLevCount; nLev++) + { + ScDPLevel* pLevel = pLevels->getByIndex(nLev); + rList.push_back(pLevel); + } + } +} + +void ScDPSource::FillMemberResults() +{ + if ( pColResults || pRowResults ) + return; + + CreateRes_Impl(); + + if ( bResultOverflow ) // set in CreateRes_Impl + { + // no results available -> abort (leave empty) + // exception is thrown in ScDPSource::getResults + return; + } + + FillLevelList( sheet::DataPilotFieldOrientation_COLUMN, aColLevelList ); + sal_Int32 nColLevelCount = aColLevelList.size(); + if (nColLevelCount) + { + tools::Long nColDimSize = pColResRoot->GetSize(pResData->GetColStartMeasure()); + pColResults.reset(new uno::Sequence<sheet::MemberResult>[nColLevelCount]); + for (tools::Long i=0; i<nColLevelCount; i++) + pColResults[i].realloc(nColDimSize); + + tools::Long nPos = 0; + pColResRoot->FillMemberResults( pColResults.get(), nPos, pResData->GetColStartMeasure(), + true, nullptr, nullptr ); + } + + FillLevelList( sheet::DataPilotFieldOrientation_ROW, aRowLevelList ); + tools::Long nRowLevelCount = aRowLevelList.size(); + if (nRowLevelCount) + { + tools::Long nRowDimSize = pRowResRoot->GetSize(pResData->GetRowStartMeasure()); + pRowResults.reset( new uno::Sequence<sheet::MemberResult>[nRowLevelCount] ); + for (tools::Long i=0; i<nRowLevelCount; i++) + pRowResults[i].realloc(nRowDimSize); + + tools::Long nPos = 0; + pRowResRoot->FillMemberResults( pRowResults.get(), nPos, pResData->GetRowStartMeasure(), + true, nullptr, nullptr ); + } +} + +const uno::Sequence<sheet::MemberResult>* ScDPSource::GetMemberResults( const ScDPLevel* pLevel ) +{ + FillMemberResults(); + + sal_Int32 i = 0; + sal_Int32 nColCount = aColLevelList.size(); + for (i=0; i<nColCount; i++) + { + ScDPLevel* pColLevel = aColLevelList[i]; + if ( pColLevel == pLevel ) + return &pColResults[i]; + } + sal_Int32 nRowCount = aRowLevelList.size(); + for (i=0; i<nRowCount; i++) + { + ScDPLevel* pRowLevel = aRowLevelList[i]; + if ( pRowLevel == pLevel ) + return &pRowResults[i]; + } + return nullptr; +} + +// XPropertySet + +uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPSource::getPropertySetInfo() +{ + using beans::PropertyAttribute::READONLY; + + static const SfxItemPropertyMapEntry aDPSourceMap_Impl[] = + { + { SC_UNO_DP_COLGRAND, 0, cppu::UnoType<bool>::get(), 0, 0 }, + { SC_UNO_DP_DATADESC, 0, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::READONLY, 0 }, + { SC_UNO_DP_IGNOREEMPTY, 0, cppu::UnoType<bool>::get(), 0, 0 }, // for sheet data only + { SC_UNO_DP_REPEATEMPTY, 0, cppu::UnoType<bool>::get(), 0, 0 }, // for sheet data only + { SC_UNO_DP_ROWGRAND, 0, cppu::UnoType<bool>::get(), 0, 0 }, + { SC_UNO_DP_ROWFIELDCOUNT, 0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 }, + { SC_UNO_DP_COLUMNFIELDCOUNT, 0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 }, + { SC_UNO_DP_DATAFIELDCOUNT, 0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 }, + { SC_UNO_DP_GRANDTOTAL_NAME, 0, cppu::UnoType<OUString>::get(), 0, 0 }, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static uno::Reference<beans::XPropertySetInfo> aRef = + new SfxItemPropertySetInfo( aDPSourceMap_Impl ); + return aRef; +} + +void SAL_CALL ScDPSource::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue ) +{ + if (aPropertyName == SC_UNO_DP_COLGRAND) + bColumnGrand = lcl_GetBoolFromAny(aValue); + else if (aPropertyName == SC_UNO_DP_ROWGRAND) + bRowGrand = lcl_GetBoolFromAny(aValue); + else if (aPropertyName == SC_UNO_DP_IGNOREEMPTY) + setIgnoreEmptyRows( lcl_GetBoolFromAny( aValue ) ); + else if (aPropertyName == SC_UNO_DP_REPEATEMPTY) + setRepeatIfEmpty( lcl_GetBoolFromAny( aValue ) ); + else if (aPropertyName == SC_UNO_DP_GRANDTOTAL_NAME) + { + OUString aName; + if (aValue >>= aName) + mpGrandTotalName = aName; + } + else + { + OSL_FAIL("unknown property"); + //TODO: THROW( UnknownPropertyException() ); + } +} + +uno::Any SAL_CALL ScDPSource::getPropertyValue( const OUString& aPropertyName ) +{ + uno::Any aRet; + if ( aPropertyName == SC_UNO_DP_COLGRAND ) + aRet <<= bColumnGrand; + else if ( aPropertyName == SC_UNO_DP_ROWGRAND ) + aRet <<= bRowGrand; + else if ( aPropertyName == SC_UNO_DP_IGNOREEMPTY ) + aRet <<= bIgnoreEmptyRows; + else if ( aPropertyName == SC_UNO_DP_REPEATEMPTY ) + aRet <<= bRepeatIfEmpty; + else if ( aPropertyName == SC_UNO_DP_DATADESC ) // read-only + aRet <<= getDataDescription(); + else if ( aPropertyName == SC_UNO_DP_ROWFIELDCOUNT ) // read-only + aRet <<= static_cast<sal_Int32>(maRowDims.size()); + else if ( aPropertyName == SC_UNO_DP_COLUMNFIELDCOUNT ) // read-only + aRet <<= static_cast<sal_Int32>(maColDims.size()); + else if ( aPropertyName == SC_UNO_DP_DATAFIELDCOUNT ) // read-only + aRet <<= static_cast<sal_Int32>(maDataDims.size()); + else if (aPropertyName == SC_UNO_DP_GRANDTOTAL_NAME) + { + if (mpGrandTotalName) + aRet <<= *mpGrandTotalName; + } + else + { + OSL_FAIL("unknown property"); + //TODO: THROW( UnknownPropertyException() ); + } + return aRet; +} + +#if DUMP_PIVOT_TABLE +void ScDPSource::DumpResults() const +{ + std::cout << "+++++ column root" << std::endl; + pColResRoot->Dump(1); + std::cout << "+++++ row root" << std::endl; + pRowResRoot->Dump(1); +} +#endif + +SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPSource ) + +ScDPDimensions::ScDPDimensions( ScDPSource* pSrc ) : + pSource( pSrc ) +{ + //TODO: hold pSource + + // include data layout dimension and duplicated dimensions + nDimCount = pSource->GetData()->GetColumnCount() + 1 + pSource->GetDupCount(); +} + +ScDPDimensions::~ScDPDimensions() +{ + //TODO: release pSource +} + +void ScDPDimensions::CountChanged() +{ + // include data layout dimension and duplicated dimensions + sal_Int32 nNewCount = pSource->GetData()->GetColumnCount() + 1 + pSource->GetDupCount(); + if ( ppDims ) + { + sal_Int32 i; + sal_Int32 nCopy = std::min( nNewCount, nDimCount ); + rtl::Reference<ScDPDimension>* ppNew = new rtl::Reference<ScDPDimension>[nNewCount]; + + for (i=0; i<nCopy; i++) // copy existing dims + ppNew[i] = ppDims[i]; + for (i=nCopy; i<nNewCount; i++) // clear additional pointers + ppNew[i] = nullptr; + + ppDims.reset( ppNew ); + } + nDimCount = nNewCount; +} + +// very simple XNameAccess implementation using getCount/getByIndex + +uno::Any SAL_CALL ScDPDimensions::getByName( const OUString& aName ) +{ + sal_Int32 nCount = getCount(); + for (sal_Int32 i=0; i<nCount; i++) + if ( getByIndex(i)->getName() == aName ) + { + uno::Reference<container::XNamed> xNamed = getByIndex(i); + uno::Any aRet; + aRet <<= xNamed; + return aRet; + } + + throw container::NoSuchElementException(); +// return uno::Any(); +} + +uno::Sequence<OUString> SAL_CALL ScDPDimensions::getElementNames() +{ + tools::Long nCount = getCount(); + uno::Sequence<OUString> aSeq(nCount); + OUString* pArr = aSeq.getArray(); + for (tools::Long i=0; i<nCount; i++) + pArr[i] = getByIndex(i)->getName(); + return aSeq; +} + +sal_Bool SAL_CALL ScDPDimensions::hasByName( const OUString& aName ) +{ + tools::Long nCount = getCount(); + for (tools::Long i=0; i<nCount; i++) + if ( getByIndex(i)->getName() == aName ) + return true; + return false; +} + +uno::Type SAL_CALL ScDPDimensions::getElementType() +{ + return cppu::UnoType<container::XNamed>::get(); +} + +sal_Bool SAL_CALL ScDPDimensions::hasElements() +{ + return ( getCount() > 0 ); +} + +// end of XNameAccess implementation + +tools::Long ScDPDimensions::getCount() const +{ + // in tabular data, every column of source data is a dimension + + return nDimCount; +} + +ScDPDimension* ScDPDimensions::getByIndex(tools::Long nIndex) const +{ + if ( nIndex >= 0 && nIndex < nDimCount ) + { + if ( !ppDims ) + { + const_cast<ScDPDimensions*>(this)->ppDims.reset(new rtl::Reference<ScDPDimension>[nDimCount] ); + for (tools::Long i=0; i<nDimCount; i++) + ppDims[i] = nullptr; + } + if ( !ppDims[nIndex].is() ) + { + ppDims[nIndex] = new ScDPDimension( pSource, nIndex ); + } + + return ppDims[nIndex].get(); + } + + return nullptr; //TODO: exception? +} + +ScDPDimension::ScDPDimension( ScDPSource* pSrc, tools::Long nD ) : + pSource( pSrc ), + nDim( nD ), + nFunction( ScGeneralFunction::SUM ), // sum is default + nSourceDim( -1 ), + bHasSelectedPage( false ), + mbHasHiddenMember(false) +{ + //TODO: hold pSource +} + +ScDPDimension::~ScDPDimension() +{ + //TODO: release pSource +} + +ScDPHierarchies* ScDPDimension::GetHierarchiesObject() +{ + if (!mxHierarchies.is()) + { + mxHierarchies = new ScDPHierarchies( pSource, nDim ); + } + return mxHierarchies.get(); +} + +const std::optional<OUString> & ScDPDimension::GetLayoutName() const +{ + return mpLayoutName; +} + +const std::optional<OUString> & ScDPDimension::GetSubtotalName() const +{ + return mpSubtotalName; +} + +uno::Reference<container::XNameAccess> SAL_CALL ScDPDimension::getHierarchies() +{ + return GetHierarchiesObject(); +} + +OUString SAL_CALL ScDPDimension::getName() +{ + if (!aName.isEmpty()) + return aName; + else + return pSource->GetData()->getDimensionName( nDim ); +} + +void SAL_CALL ScDPDimension::setName( const OUString& rNewName ) +{ + // used after cloning + aName = rNewName; +} + +sheet::DataPilotFieldOrientation ScDPDimension::getOrientation() const +{ + return pSource->GetOrientation( nDim ); +} + +bool ScDPDimension::getIsDataLayoutDimension() const +{ + return pSource->GetData()->getIsDataLayoutDimension( nDim ); +} + +void ScDPDimension::setFunction(ScGeneralFunction nNew) +{ + nFunction = nNew; +} + +ScDPDimension* ScDPDimension::CreateCloneObject() +{ + OSL_ENSURE( nSourceDim < 0, "recursive duplicate - not implemented" ); + + //TODO: set new name here, or temporary name ??? + OUString aNewName = aName; + + ScDPDimension* pNew = pSource->AddDuplicated( aNewName ); + + pNew->aName = aNewName; //TODO: here or in source? + pNew->nSourceDim = nDim; //TODO: recursive? + + return pNew; +} + +uno::Reference<util::XCloneable> SAL_CALL ScDPDimension::createClone() +{ + return CreateCloneObject(); +} + +const ScDPItemData& ScDPDimension::GetSelectedData() +{ + if ( !pSelectedData ) + { + // find the named member to initialize pSelectedData from it, with name and value + + tools::Long nLevel = 0; + + tools::Long nHierarchy = getUsedHierarchy(); + if ( nHierarchy >= ScDPHierarchies::getCount() ) + nHierarchy = 0; + ScDPLevels* pLevels = GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject(); + tools::Long nLevCount = pLevels->getCount(); + if ( nLevel < nLevCount ) + { + ScDPMembers* pMembers = pLevels->getByIndex(nLevel)->GetMembersObject(); + + //TODO: merge with ScDPMembers::getByName + tools::Long nCount = pMembers->getCount(); + for (tools::Long i=0; i<nCount && !pSelectedData; i++) + { + ScDPMember* pMember = pMembers->getByIndex(i); + if (aSelectedPage == pMember->GetNameStr(false)) + { + pSelectedData.reset( new ScDPItemData(pMember->FillItemData()) ); + } + } + } + + if ( !pSelectedData ) + pSelectedData.reset( new ScDPItemData(aSelectedPage) ); // default - name only + } + + return *pSelectedData; +} + +// XPropertySet + +uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPDimension::getPropertySetInfo() +{ + static const SfxItemPropertyMapEntry aDPDimensionMap_Impl[] = + { + { SC_UNO_DP_FILTER, 0, cppu::UnoType<uno::Sequence<sheet::TableFilterField>>::get(), 0, 0 }, + { SC_UNO_DP_FLAGS, 0, cppu::UnoType<sal_Int32>::get(), beans::PropertyAttribute::READONLY, 0 }, + { SC_UNO_DP_FUNCTION, 0, cppu::UnoType<sheet::GeneralFunction>::get(), 0, 0 }, + { SC_UNO_DP_FUNCTION2, 0, cppu::UnoType<sal_Int16>::get(), 0, 0 }, + { SC_UNO_DP_ISDATALAYOUT, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::READONLY, 0 }, + { SC_UNO_DP_NUMBERFO, 0, cppu::UnoType<sal_Int32>::get(), beans::PropertyAttribute::READONLY, 0 }, + { SC_UNO_DP_ORIENTATION, 0, cppu::UnoType<sheet::DataPilotFieldOrientation>::get(), 0, 0 }, + { SC_UNO_DP_ORIGINAL, 0, cppu::UnoType<container::XNamed>::get(), beans::PropertyAttribute::READONLY, 0 }, + { SC_UNO_DP_ORIGINAL_POS, 0, cppu::UnoType<sal_Int32>::get(), 0, 0 }, + { SC_UNO_DP_POSITION, 0, cppu::UnoType<sal_Int32>::get(), 0, 0 }, + { SC_UNO_DP_REFVALUE, 0, cppu::UnoType<sheet::DataPilotFieldReference>::get(), 0, 0 }, + { SC_UNO_DP_USEDHIERARCHY, 0, cppu::UnoType<sal_Int32>::get(), 0, 0 }, + { SC_UNO_DP_LAYOUTNAME, 0, cppu::UnoType<OUString>::get(), 0, 0 }, + { SC_UNO_DP_FIELD_SUBTOTALNAME, 0, cppu::UnoType<OUString>::get(), 0, 0 }, + { SC_UNO_DP_HAS_HIDDEN_MEMBER, 0, cppu::UnoType<bool>::get(), 0, 0 }, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static uno::Reference<beans::XPropertySetInfo> aRef = + new SfxItemPropertySetInfo( aDPDimensionMap_Impl ); + return aRef; +} + +void SAL_CALL ScDPDimension::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue ) +{ + if ( aPropertyName == SC_UNO_DP_USEDHIERARCHY ) + { + // #i52547# don't use the incomplete date hierarchy implementation - ignore the call + } + else if ( aPropertyName == SC_UNO_DP_ORIENTATION ) + { + sheet::DataPilotFieldOrientation eEnum; + if (aValue >>= eEnum) + pSource->SetOrientation( nDim, eEnum ); + } + else if ( aPropertyName == SC_UNO_DP_FUNCTION ) + { + sheet::GeneralFunction eEnum; + if (aValue >>= eEnum) + setFunction( static_cast<ScGeneralFunction>(eEnum) ); + } + else if ( aPropertyName == SC_UNO_DP_FUNCTION2 ) + { + sal_Int16 eEnum; + if (aValue >>= eEnum) + setFunction( static_cast<ScGeneralFunction>(eEnum) ); + } + else if ( aPropertyName == SC_UNO_DP_REFVALUE ) + aValue >>= aReferenceValue; + else if ( aPropertyName == SC_UNO_DP_FILTER ) + { + bool bDone = false; + uno::Sequence<sheet::TableFilterField> aSeq; + if (aValue >>= aSeq) + { + sal_Int32 nLength = aSeq.getLength(); + if ( nLength == 0 ) + { + aSelectedPage.clear(); + bHasSelectedPage = false; + bDone = true; + } + else if ( nLength == 1 ) + { + const sheet::TableFilterField& rField = aSeq[0]; + if ( rField.Field == 0 && rField.Operator == sheet::FilterOperator_EQUAL && !rField.IsNumeric ) + { + aSelectedPage = rField.StringValue; + bHasSelectedPage = true; + bDone = true; + } + } + } + if ( !bDone ) + { + OSL_FAIL("Filter property is not a single string"); + throw lang::IllegalArgumentException(); + } + pSelectedData.reset(); // invalid after changing aSelectedPage + } + else if (aPropertyName == SC_UNO_DP_LAYOUTNAME) + { + OUString aTmpName; + if (aValue >>= aTmpName) + mpLayoutName = aTmpName; + } + else if (aPropertyName == SC_UNO_DP_FIELD_SUBTOTALNAME) + { + OUString aTmpName; + if (aValue >>= aTmpName) + mpSubtotalName = aTmpName; + } + else if (aPropertyName == SC_UNO_DP_HAS_HIDDEN_MEMBER) + { + bool b = false; + aValue >>= b; + mbHasHiddenMember = b; + } + else + { + OSL_FAIL("unknown property"); + //TODO: THROW( UnknownPropertyException() ); + } +} + +uno::Any SAL_CALL ScDPDimension::getPropertyValue( const OUString& aPropertyName ) +{ + uno::Any aRet; + if ( aPropertyName == SC_UNO_DP_POSITION ) + aRet <<= pSource->GetPosition( nDim ); + else if ( aPropertyName == SC_UNO_DP_USEDHIERARCHY ) + aRet <<= static_cast<sal_Int32>(getUsedHierarchy()); + else if ( aPropertyName == SC_UNO_DP_ORIENTATION ) + { + sheet::DataPilotFieldOrientation eVal = getOrientation(); + aRet <<= eVal; + } + else if ( aPropertyName == SC_UNO_DP_FUNCTION ) + { + ScGeneralFunction nVal = getFunction(); + if (nVal == ScGeneralFunction::MEDIAN) + nVal = ScGeneralFunction::NONE; + const int nValAsInt = static_cast<int>(nVal); + assert(nValAsInt >= int(css::sheet::GeneralFunction_NONE) && + nValAsInt <= int(css::sheet::GeneralFunction_VARP)); + aRet <<= static_cast<sheet::GeneralFunction>(nValAsInt); + } + else if ( aPropertyName == SC_UNO_DP_FUNCTION2 ) + { + ScGeneralFunction eVal = getFunction(); + aRet <<= static_cast<sal_Int16>(eVal); + } + else if ( aPropertyName == SC_UNO_DP_REFVALUE ) + aRet <<= aReferenceValue; + else if ( aPropertyName == SC_UNO_DP_ISDATALAYOUT ) // read-only properties + aRet <<= getIsDataLayoutDimension(); + else if ( aPropertyName == SC_UNO_DP_NUMBERFO ) + { + sal_Int32 nFormat = 0; + ScGeneralFunction eFunc = getFunction(); + // #i63745# don't use source format for "count" + if ( eFunc != ScGeneralFunction::COUNT && eFunc != ScGeneralFunction::COUNTNUMS ) + nFormat = pSource->GetData()->GetNumberFormat( ( nSourceDim >= 0 ) ? nSourceDim : nDim ); + + switch ( aReferenceValue.ReferenceType ) + { + case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE: + case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE: + case sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE: + case sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE: + case sheet::DataPilotFieldReferenceType::TOTAL_PERCENTAGE: + nFormat = pSource->GetData()->GetNumberFormatByIdx( NF_PERCENT_DEC2 ); + break; + case sheet::DataPilotFieldReferenceType::INDEX: + nFormat = pSource->GetData()->GetNumberFormatByIdx( NF_NUMBER_SYSTEM ); + break; + default: + break; + } + + aRet <<= nFormat; + } + else if ( aPropertyName == SC_UNO_DP_ORIGINAL ) + { + uno::Reference<container::XNamed> xOriginal; + if (nSourceDim >= 0) + xOriginal = pSource->GetDimensionsObject()->getByIndex(nSourceDim); + aRet <<= xOriginal; + } + else if (aPropertyName == SC_UNO_DP_ORIGINAL_POS) + { + aRet <<= nSourceDim; + } + else if ( aPropertyName == SC_UNO_DP_FILTER ) + { + if ( bHasSelectedPage ) + { + // single filter field: first field equal to selected string + sheet::TableFilterField aField( sheet::FilterConnection_AND, 0, + sheet::FilterOperator_EQUAL, false, 0.0, aSelectedPage ); + aRet <<= uno::Sequence<sheet::TableFilterField>( &aField, 1 ); + } + else + aRet <<= uno::Sequence<sheet::TableFilterField>(0); + } + else if (aPropertyName == SC_UNO_DP_LAYOUTNAME) + aRet <<= mpLayoutName ? *mpLayoutName : OUString(); + else if (aPropertyName == SC_UNO_DP_FIELD_SUBTOTALNAME) + aRet <<= mpSubtotalName ? *mpSubtotalName : OUString(); + else if (aPropertyName == SC_UNO_DP_HAS_HIDDEN_MEMBER) + aRet <<= mbHasHiddenMember; + else if (aPropertyName == SC_UNO_DP_FLAGS) + { + aRet <<= sal_Int32(0); // tabular data: all orientations are possible + } + else + { + OSL_FAIL("unknown property"); + //TODO: THROW( UnknownPropertyException() ); + } + return aRet; +} + +SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPDimension ) + +ScDPHierarchies::ScDPHierarchies( ScDPSource* pSrc, tools::Long nD ) : + pSource( pSrc ), + nDim( nD ) +{ + //TODO: hold pSource +} + +ScDPHierarchies::~ScDPHierarchies() +{ + //TODO: release pSource +} + +// very simple XNameAccess implementation using getCount/getByIndex + +uno::Any SAL_CALL ScDPHierarchies::getByName( const OUString& aName ) +{ + tools::Long nCount = getCount(); + for (tools::Long i=0; i<nCount; i++) + if ( getByIndex(i)->getName() == aName ) + { + uno::Reference<container::XNamed> xNamed = getByIndex(i); + uno::Any aRet; + aRet <<= xNamed; + return aRet; + } + + throw container::NoSuchElementException(); +} + +uno::Sequence<OUString> SAL_CALL ScDPHierarchies::getElementNames() +{ + tools::Long nCount = getCount(); + uno::Sequence<OUString> aSeq(nCount); + OUString* pArr = aSeq.getArray(); + for (tools::Long i=0; i<nCount; i++) + pArr[i] = getByIndex(i)->getName(); + return aSeq; +} + +sal_Bool SAL_CALL ScDPHierarchies::hasByName( const OUString& aName ) +{ + tools::Long nCount = getCount(); + for (tools::Long i=0; i<nCount; i++) + if ( getByIndex(i)->getName() == aName ) + return true; + return false; +} + +uno::Type SAL_CALL ScDPHierarchies::getElementType() +{ + return cppu::UnoType<container::XNamed>::get(); +} + +sal_Bool SAL_CALL ScDPHierarchies::hasElements() +{ + return ( getCount() > 0 ); +} + +// end of XNameAccess implementation + +sal_Int32 ScDPHierarchies::getCount() +{ + return nHierCount; +} + +ScDPHierarchy* ScDPHierarchies::getByIndex(tools::Long nIndex) const +{ + // pass hierarchy index to new object in case the implementation + // will be extended to more than one hierarchy + + if ( nIndex >= 0 && nIndex < nHierCount ) + { + if ( !ppHiers ) + { + const_cast<ScDPHierarchies*>(this)->ppHiers.reset( new rtl::Reference<ScDPHierarchy>[nHierCount] ); + for (sal_Int32 i=0; i<nHierCount; i++) + ppHiers[i] = nullptr; + } + if ( !ppHiers[nIndex].is() ) + { + ppHiers[nIndex] = new ScDPHierarchy( pSource, nDim, nIndex ); + } + + return ppHiers[nIndex].get(); + } + + return nullptr; //TODO: exception? +} + +ScDPHierarchy::ScDPHierarchy( ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH ) : + pSource( pSrc ), + nDim( nD ), + nHier( nH ) +{ + //TODO: hold pSource +} + +ScDPHierarchy::~ScDPHierarchy() +{ + //TODO: release pSource +} + +ScDPLevels* ScDPHierarchy::GetLevelsObject() +{ + if (!mxLevels.is()) + { + mxLevels = new ScDPLevels( pSource, nDim, nHier ); + } + return mxLevels.get(); +} + +uno::Reference<container::XNameAccess> SAL_CALL ScDPHierarchy::getLevels() +{ + return GetLevelsObject(); +} + +OUString SAL_CALL ScDPHierarchy::getName() +{ + OUString aRet; //TODO: globstr-ID !!!! + switch (nHier) + { + case SC_DAPI_HIERARCHY_FLAT: + aRet = "flat"; + break; //TODO: name ??????? + case SC_DAPI_HIERARCHY_QUARTER: + aRet = "Quarter"; + break; //TODO: name ??????? + case SC_DAPI_HIERARCHY_WEEK: + aRet = "Week"; + break; //TODO: name ??????? + default: + OSL_FAIL( "ScDPHierarchy::getName: unexpected hierarchy" ); + break; + } + return aRet; +} + +void SAL_CALL ScDPHierarchy::setName( const OUString& /* rNewName */ ) +{ + OSL_FAIL("not implemented"); //TODO: exception? +} + +ScDPLevels::ScDPLevels( ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH ) : + pSource( pSrc ), + nDim( nD ), + nHier( nH ) +{ + //TODO: hold pSource + + // text columns have only one level + + tools::Long nSrcDim = pSource->GetSourceDim( nDim ); + if ( pSource->IsDateDimension( nSrcDim ) ) + { + switch ( nHier ) + { + case SC_DAPI_HIERARCHY_FLAT: nLevCount = SC_DAPI_FLAT_LEVELS; break; + case SC_DAPI_HIERARCHY_QUARTER: nLevCount = SC_DAPI_QUARTER_LEVELS; break; + case SC_DAPI_HIERARCHY_WEEK: nLevCount = SC_DAPI_WEEK_LEVELS; break; + default: + OSL_FAIL("wrong hierarchy"); + nLevCount = 0; + } + } + else + nLevCount = 1; +} + +ScDPLevels::~ScDPLevels() +{ + //TODO: release pSource +} + +// very simple XNameAccess implementation using getCount/getByIndex + +uno::Any SAL_CALL ScDPLevels::getByName( const OUString& aName ) +{ + tools::Long nCount = getCount(); + for (tools::Long i=0; i<nCount; i++) + if ( getByIndex(i)->getName() == aName ) + { + uno::Reference<container::XNamed> xNamed = getByIndex(i); + uno::Any aRet; + aRet <<= xNamed; + return aRet; + } + + throw container::NoSuchElementException(); +} + +uno::Sequence<OUString> SAL_CALL ScDPLevels::getElementNames() +{ + tools::Long nCount = getCount(); + uno::Sequence<OUString> aSeq(nCount); + OUString* pArr = aSeq.getArray(); + for (tools::Long i=0; i<nCount; i++) + pArr[i] = getByIndex(i)->getName(); + return aSeq; +} + +sal_Bool SAL_CALL ScDPLevels::hasByName( const OUString& aName ) +{ + tools::Long nCount = getCount(); + for (tools::Long i=0; i<nCount; i++) + if ( getByIndex(i)->getName() == aName ) + return true; + return false; +} + +uno::Type SAL_CALL ScDPLevels::getElementType() +{ + return cppu::UnoType<container::XNamed>::get(); +} + +sal_Bool SAL_CALL ScDPLevels::hasElements() +{ + return ( getCount() > 0 ); +} + +// end of XNameAccess implementation + +sal_Int32 ScDPLevels::getCount() const +{ + return nLevCount; +} + +ScDPLevel* ScDPLevels::getByIndex(sal_Int32 nIndex) const +{ + if ( nIndex >= 0 && nIndex < nLevCount ) + { + if ( !ppLevs ) + { + const_cast<ScDPLevels*>(this)->ppLevs.reset(new rtl::Reference<ScDPLevel>[nLevCount] ); + for (tools::Long i=0; i<nLevCount; i++) + ppLevs[i] = nullptr; + } + if ( !ppLevs[nIndex].is() ) + { + ppLevs[nIndex] = new ScDPLevel( pSource, nDim, nHier, nIndex ); + } + + return ppLevs[nIndex].get(); + } + + return nullptr; //TODO: exception? +} + +namespace { + +class ScDPGlobalMembersOrder +{ + ScDPLevel& rLevel; + bool bAscending; + +public: + ScDPGlobalMembersOrder( ScDPLevel& rLev, bool bAsc ) : + rLevel(rLev), + bAscending(bAsc) + {} + + bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const; +}; + +} + +bool ScDPGlobalMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const +{ + sal_Int32 nCompare = 0; + // seems that some ::std::sort() implementations pass the same index twice + if( nIndex1 != nIndex2 ) + { + ScDPMembers* pMembers = rLevel.GetMembersObject(); + ScDPMember* pMember1 = pMembers->getByIndex(nIndex1); + ScDPMember* pMember2 = pMembers->getByIndex(nIndex2); + nCompare = pMember1->Compare( *pMember2 ); + } + return bAscending ? (nCompare < 0) : (nCompare > 0); +} + +ScDPLevel::ScDPLevel( ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH, sal_Int32 nL ) : + pSource( pSrc ), + nDim( nD ), + nHier( nH ), + nLev( nL ), + aSortInfo( OUString(), true, sheet::DataPilotFieldSortMode::NAME ), // default: sort by name + nSortMeasure( 0 ), + nAutoMeasure( 0 ), + bShowEmpty( false ), + bEnableLayout( false ), + bRepeatItemLabels( false ) +{ + //TODO: hold pSource + // aSubTotals is empty +} + +ScDPLevel::~ScDPLevel() +{ + //TODO: release pSource +} + +void ScDPLevel::EvaluateSortOrder() +{ + switch (aSortInfo.Mode) + { + case sheet::DataPilotFieldSortMode::DATA: + { + // find index of measure (index among data dimensions) + + tools::Long nMeasureCount = pSource->GetDataDimensionCount(); + for (tools::Long nMeasure=0; nMeasure<nMeasureCount; nMeasure++) + { + if (pSource->GetDataDimName(nMeasure) == aSortInfo.Field) + { + nSortMeasure = nMeasure; + break; + } + } + + //TODO: error if not found? + } + break; + case sheet::DataPilotFieldSortMode::MANUAL: + case sheet::DataPilotFieldSortMode::NAME: + { + ScDPMembers* pLocalMembers = GetMembersObject(); + tools::Long nCount = pLocalMembers->getCount(); + + aGlobalOrder.resize( nCount ); + for (tools::Long nPos=0; nPos<nCount; nPos++) + aGlobalOrder[nPos] = nPos; + + // allow manual or name (manual is always ascending) + bool bAscending = ( aSortInfo.Mode == sheet::DataPilotFieldSortMode::MANUAL || aSortInfo.IsAscending ); + ScDPGlobalMembersOrder aComp( *this, bAscending ); + ::std::sort( aGlobalOrder.begin(), aGlobalOrder.end(), aComp ); + } + break; + } + + if ( !aAutoShowInfo.IsEnabled ) + return; + + // find index of measure (index among data dimensions) + + tools::Long nMeasureCount = pSource->GetDataDimensionCount(); + for (tools::Long nMeasure=0; nMeasure<nMeasureCount; nMeasure++) + { + if (pSource->GetDataDimName(nMeasure) == aAutoShowInfo.DataField) + { + nAutoMeasure = nMeasure; + break; + } + } + + //TODO: error if not found? +} + +void ScDPLevel::SetEnableLayout(bool bSet) +{ + bEnableLayout = bSet; +} + +ScDPMembers* ScDPLevel::GetMembersObject() +{ + if (!mxMembers.is()) + { + mxMembers = new ScDPMembers( pSource, nDim, nHier, nLev ); + } + return mxMembers.get(); +} + +uno::Reference<sheet::XMembersAccess> SAL_CALL ScDPLevel::getMembers() +{ + return GetMembersObject(); +} + +uno::Sequence<sheet::MemberResult> SAL_CALL ScDPLevel::getResults() +{ + const uno::Sequence<sheet::MemberResult>* pRes = pSource->GetMemberResults( this ); + if (pRes) + return *pRes; + + return {}; //TODO: Error? +} + +OUString SAL_CALL ScDPLevel::getName() +{ + tools::Long nSrcDim = pSource->GetSourceDim( nDim ); + if ( pSource->IsDateDimension( nSrcDim ) ) + { + OUString aRet; //TODO: globstr-ID !!!! + + if ( nHier == SC_DAPI_HIERARCHY_QUARTER ) + { + switch ( nLev ) + { + case SC_DAPI_LEVEL_YEAR: + aRet = "Year"; + break; + case SC_DAPI_LEVEL_QUARTER: + aRet = "Quarter"; + break; + case SC_DAPI_LEVEL_MONTH: + aRet = "Month"; + break; + case SC_DAPI_LEVEL_DAY: + aRet = "Day"; + break; + default: + OSL_FAIL( "ScDPLevel::getName: unexpected level" ); + break; + } + } + else if ( nHier == SC_DAPI_HIERARCHY_WEEK ) + { + switch ( nLev ) + { + case SC_DAPI_LEVEL_YEAR: + aRet = "Year"; + break; + case SC_DAPI_LEVEL_WEEK: + aRet = "Week"; + break; + case SC_DAPI_LEVEL_WEEKDAY: + aRet = "Weekday"; + break; + default: + OSL_FAIL( "ScDPLevel::getName: unexpected level" ); + break; + } + } + if (!aRet.isEmpty()) + return aRet; + } + + ScDPDimension* pDim = pSource->GetDimensionsObject()->getByIndex(nSrcDim); + if (!pDim) + return OUString(); + + return pDim->getName(); +} + +void SAL_CALL ScDPLevel::setName( const OUString& /* rNewName */ ) +{ + OSL_FAIL("not implemented"); //TODO: exception? +} + +uno::Sequence<sal_Int16> ScDPLevel::getSubTotals() const +{ + //TODO: separate functions for settings and evaluation? + + tools::Long nSrcDim = pSource->GetSourceDim( nDim ); + if ( !pSource->SubTotalAllowed( nSrcDim ) ) + return {}; + + return aSubTotals; +} + +// XPropertySet + +uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPLevel::getPropertySetInfo() +{ + static const SfxItemPropertyMapEntry aDPLevelMap_Impl[] = + { + //TODO: change type of AutoShow/Layout/Sorting to API struct when available + { SC_UNO_DP_AUTOSHOW, 0, cppu::UnoType<sheet::DataPilotFieldAutoShowInfo>::get(), 0, 0 }, + { SC_UNO_DP_LAYOUT, 0, cppu::UnoType<sheet::DataPilotFieldLayoutInfo>::get(), 0, 0 }, + { SC_UNO_DP_SHOWEMPTY, 0, cppu::UnoType<bool>::get(), 0, 0 }, + { SC_UNO_DP_REPEATITEMLABELS, 0, cppu::UnoType<bool>::get(), 0, 0 }, + { SC_UNO_DP_SORTING, 0, cppu::UnoType<sheet::DataPilotFieldSortInfo>::get(), 0, 0 }, + { SC_UNO_DP_SUBTOTAL, 0, cppu::UnoType<uno::Sequence<sheet::GeneralFunction>>::get(), 0, 0 }, + { SC_UNO_DP_SUBTOTAL2, 0, cppu::UnoType<uno::Sequence<sal_Int16>>::get(), 0, 0 }, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static uno::Reference<beans::XPropertySetInfo> aRef = + new SfxItemPropertySetInfo( aDPLevelMap_Impl ); + return aRef; +} + +void SAL_CALL ScDPLevel::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue ) +{ + if ( aPropertyName == SC_UNO_DP_SHOWEMPTY ) + bShowEmpty = lcl_GetBoolFromAny(aValue); + else if ( aPropertyName == SC_UNO_DP_REPEATITEMLABELS ) + bRepeatItemLabels = lcl_GetBoolFromAny(aValue); + else if ( aPropertyName == SC_UNO_DP_SUBTOTAL ) + { + uno::Sequence<sheet::GeneralFunction> aSeq; + aValue >>= aSeq; + aSubTotals.realloc(aSeq.getLength()); + std::transform(std::cbegin(aSeq), std::cend(aSeq), aSubTotals.getArray(), + [](const sheet::GeneralFunction& rFunc) -> sal_Int16 { + return static_cast<sal_Int16>(rFunc); }); + } + else if ( aPropertyName == SC_UNO_DP_SUBTOTAL2 ) + aValue >>= aSubTotals; + else if ( aPropertyName == SC_UNO_DP_SORTING ) + aValue >>= aSortInfo; + else if ( aPropertyName == SC_UNO_DP_AUTOSHOW ) + aValue >>= aAutoShowInfo; + else if ( aPropertyName == SC_UNO_DP_LAYOUT ) + aValue >>= aLayoutInfo; + else + { + OSL_FAIL("unknown property"); + } +} + +uno::Any SAL_CALL ScDPLevel::getPropertyValue( const OUString& aPropertyName ) +{ + uno::Any aRet; + if ( aPropertyName == SC_UNO_DP_SHOWEMPTY ) + aRet <<= bShowEmpty; + else if ( aPropertyName == SC_UNO_DP_REPEATITEMLABELS ) + aRet <<= bRepeatItemLabels; + else if ( aPropertyName == SC_UNO_DP_SUBTOTAL ) + { + const uno::Sequence<sal_Int16> aSeq = getSubTotals(); + uno::Sequence<sheet::GeneralFunction> aNewSeq(aSeq.getLength()); + std::transform(aSeq.begin(), aSeq.end(), aNewSeq.getArray(), + [](const sal_Int16 nFunc) -> sheet::GeneralFunction { + if (nFunc == sheet::GeneralFunction2::MEDIAN) + return sheet::GeneralFunction_NONE; + return static_cast<sheet::GeneralFunction>(nFunc); + }); + + aRet <<= aNewSeq; + } + else if ( aPropertyName == SC_UNO_DP_SUBTOTAL2 ) + { + uno::Sequence<sal_Int16> aSeq = getSubTotals(); //TODO: avoid extra copy? + aRet <<= aSeq; + } + else if ( aPropertyName == SC_UNO_DP_SORTING ) + aRet <<= aSortInfo; + else if ( aPropertyName == SC_UNO_DP_AUTOSHOW ) + aRet <<= aAutoShowInfo; + else if ( aPropertyName == SC_UNO_DP_LAYOUT ) + aRet <<= aLayoutInfo; + else if (aPropertyName == SC_UNO_DP_LAYOUTNAME) + { + // read only property + tools::Long nSrcDim = pSource->GetSourceDim(nDim); + ScDPDimension* pDim = pSource->GetDimensionsObject()->getByIndex(nSrcDim); + if (!pDim) + return aRet; + + const std::optional<OUString> & pLayoutName = pDim->GetLayoutName(); + if (!pLayoutName) + return aRet; + + aRet <<= *pLayoutName; + } + else + { + OSL_FAIL("unknown property"); + } + return aRet; +} + +SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPLevel ) + +ScDPMembers::ScDPMembers( ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH, sal_Int32 nL ) : + pSource( pSrc ), + nDim( nD ), + nHier( nH ), + nLev( nL ) +{ + //TODO: hold pSource + + tools::Long nSrcDim = pSource->GetSourceDim( nDim ); + if ( pSource->IsDataLayoutDimension(nSrcDim) ) + nMbrCount = pSource->GetDataDimensionCount(); + else if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) ) + { + nMbrCount = 0; + if ( nHier == SC_DAPI_HIERARCHY_QUARTER ) + { + switch (nLev) + { + case SC_DAPI_LEVEL_YEAR: + { + const ScDPItemData* pLastNumData = nullptr; + for ( SCROW n = 0; n < static_cast<SCROW>(pSource->GetData()->GetColumnEntries(nDim).size()); n-- ) + { + const ScDPItemData* pData = GetSrcItemDataByIndex( n ); + if ( pData && pData->HasStringData() ) + break; + else + pLastNumData = pData; + } + + if ( pLastNumData ) + { + const ScDPItemData* pFirstData = GetSrcItemDataByIndex( 0 ); + double fFirstVal = pFirstData->GetValue(); + double fLastVal = pLastNumData->GetValue(); + + tools::Long nFirstYear = pSource->GetData()->GetDatePart( + static_cast<tools::Long>(::rtl::math::approxFloor( fFirstVal )), + nHier, nLev ); + tools::Long nLastYear = pSource->GetData()->GetDatePart( + static_cast<tools::Long>(::rtl::math::approxFloor( fLastVal )), + nHier, nLev ); + + nMbrCount = nLastYear + 1 - nFirstYear; + } + else + nMbrCount = 0; // no values + } + break; + case SC_DAPI_LEVEL_QUARTER: nMbrCount = 4; break; + case SC_DAPI_LEVEL_MONTH: nMbrCount = 12; break; + case SC_DAPI_LEVEL_DAY: nMbrCount = 31; break; + default: + OSL_FAIL( "ScDPMembers::ScDPMembers: unexpected level" ); + break; + } + } + else if ( nHier == SC_DAPI_HIERARCHY_WEEK ) + { + switch (nLev) + { + case SC_DAPI_LEVEL_YEAR: nMbrCount = 1; break; //TODO: get years from source + case SC_DAPI_LEVEL_WEEK: nMbrCount = 53; break; + case SC_DAPI_LEVEL_WEEKDAY: nMbrCount = 7; break; + default: + OSL_FAIL( "ScDPMembers::ScDPMembers: unexpected level" ); + break; + } + } + } + else + nMbrCount = pSource->GetData()->GetMembersCount( nSrcDim ); +} + +ScDPMembers::~ScDPMembers() +{ +} + +// XNameAccess implementation using getCount/getByIndex + +sal_Int32 ScDPMembers::GetIndexFromName( const OUString& rName ) const +{ + if ( aHashMap.empty() ) + { + // store the index for each name + + sal_Int32 nCount = getCount(); + for (sal_Int32 i=0; i<nCount; i++) + aHashMap[ getByIndex(i)->getName() ] = i; + } + + ScDPMembersHashMap::const_iterator aIter = aHashMap.find( rName ); + if ( aIter != aHashMap.end() ) + return aIter->second; // found index + else + return -1; // not found +} + +uno::Any SAL_CALL ScDPMembers::getByName( const OUString& aName ) +{ + sal_Int32 nIndex = GetIndexFromName( aName ); + if ( nIndex >= 0 ) + { + uno::Reference<container::XNamed> xNamed = getByIndex(nIndex); + uno::Any aRet; + aRet <<= xNamed; + return aRet; + } + + throw container::NoSuchElementException(); +} + +uno::Sequence<OUString> SAL_CALL ScDPMembers::getElementNames() +{ + return getElementNames( false ); +} + +sal_Bool SAL_CALL ScDPMembers::hasByName( const OUString& aName ) +{ + return ( GetIndexFromName( aName ) >= 0 ); +} + +uno::Type SAL_CALL ScDPMembers::getElementType() +{ + return cppu::UnoType<container::XNamed>::get(); +} + +sal_Bool SAL_CALL ScDPMembers::hasElements() +{ + return ( getCount() > 0 ); +} + +// end of XNameAccess implementation + +// XMembersAccess implementation + +uno::Sequence<OUString> SAL_CALL ScDPMembers::getLocaleIndependentElementNames() +{ + return getElementNames( true ); +} + +// end of XMembersAccess implementation + +uno::Sequence<OUString> ScDPMembers::getElementNames( bool bLocaleIndependent ) const +{ + // Return list of names in sorted order, + // so it's displayed in that order in the field options dialog. + // Sorting is done at the level object (parent of this). + + ScDPLevel* pLevel = pSource->GetDimensionsObject()->getByIndex(nDim)-> + GetHierarchiesObject()->getByIndex(nHier)->GetLevelsObject()->getByIndex(nLev); + pLevel->EvaluateSortOrder(); + const std::vector<sal_Int32>& rGlobalOrder = pLevel->GetGlobalOrder(); + bool bSort = !rGlobalOrder.empty(); + + tools::Long nCount = getCount(); + uno::Sequence<OUString> aSeq(nCount); + OUString* pArr = aSeq.getArray(); + for (tools::Long i=0; i<nCount; i++) + pArr[i] = getByIndex(bSort ? rGlobalOrder[i] : i)->GetNameStr( bLocaleIndependent); + return aSeq; +} + +sal_Int32 ScDPMembers::getMinMembers() const +{ + // used in lcl_CountMinMembers + + sal_Int32 nVisCount = 0; + if (!maMembers.empty()) + { + nVisCount = std::count_if(maMembers.begin(), maMembers.end(), [](const rtl::Reference<ScDPMember>& pMbr) { + // count only visible with details (default is true for both) + return !pMbr || (pMbr->isVisible() && pMbr->getShowDetails()); }); + } + else + nVisCount = nMbrCount; // default for all + + return nVisCount; +} + +ScDPMember* ScDPMembers::getByIndex(sal_Int32 nIndex) const +{ + // result of GetColumnEntries must not change between ScDPMembers ctor + // and all calls to getByIndex + + if ( nIndex >= 0 && nIndex < nMbrCount ) + { + if (maMembers.empty()) + maMembers.resize(nMbrCount); + + if (!maMembers[nIndex]) + { + rtl::Reference<ScDPMember> pNew; + sal_Int32 nSrcDim = pSource->GetSourceDim( nDim ); + if ( pSource->IsDataLayoutDimension(nSrcDim) ) + { + // empty name (never shown, not used for lookup) + pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, 0)); + } + else if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) ) + { + sal_Int32 nGroupBy = 0; + sal_Int32 nVal = 0; + OUString aName; + + if ( nLev == SC_DAPI_LEVEL_YEAR ) // YEAR is in both hierarchies + { + //TODO: cache year range here! + + double fFirstVal = pSource->GetData()->GetMemberByIndex( nSrcDim, 0 )->GetValue(); + tools::Long nFirstYear = pSource->GetData()->GetDatePart( + static_cast<tools::Long>(::rtl::math::approxFloor( fFirstVal )), + nHier, nLev ); + + nVal = nFirstYear + nIndex; + } + else if ( nHier == SC_DAPI_HIERARCHY_WEEK && nLev == SC_DAPI_LEVEL_WEEKDAY ) + { + nVal = nIndex; // DayOfWeek is 0-based + aName = ScGlobal::GetCalendar().getDisplayName( + css::i18n::CalendarDisplayIndex::DAY, + sal::static_int_cast<sal_Int16>(nVal), 0 ); + } + else if ( nHier == SC_DAPI_HIERARCHY_QUARTER && nLev == SC_DAPI_LEVEL_MONTH ) + { + nVal = nIndex; // Month is 0-based + aName = ScGlobal::GetCalendar().getDisplayName( + css::i18n::CalendarDisplayIndex::MONTH, + sal::static_int_cast<sal_Int16>(nVal), 0 ); + } + else + nVal = nIndex + 1; // Quarter, Day, Week are 1-based + + switch (nLev) + { + case SC_DAPI_LEVEL_YEAR: + nGroupBy = sheet::DataPilotFieldGroupBy::YEARS; + break; + case SC_DAPI_LEVEL_QUARTER: + case SC_DAPI_LEVEL_WEEK: + nGroupBy = sheet::DataPilotFieldGroupBy::QUARTERS; + break; + case SC_DAPI_LEVEL_MONTH: + case SC_DAPI_LEVEL_WEEKDAY: + nGroupBy = sheet::DataPilotFieldGroupBy::MONTHS; + break; + case SC_DAPI_LEVEL_DAY: + nGroupBy = sheet::DataPilotFieldGroupBy::DAYS; + break; + default: + ; + } + if (aName.isEmpty()) + aName = OUString::number(nVal); + + ScDPItemData aData(nGroupBy, nVal); + SCROW nId = pSource->GetCache()->GetIdByItemData(nDim, aData); + pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, nId)); + } + else + { + const std::vector<SCROW>& memberIndexs = pSource->GetData()->GetColumnEntries(nSrcDim); + pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, memberIndexs[nIndex])); + } + maMembers[nIndex] = pNew; + } + + return maMembers[nIndex].get(); + } + + return nullptr; //TODO: exception? +} + +ScDPMember::ScDPMember( + ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH, sal_Int32 nL, SCROW nIndex) : + pSource( pSrc ), + nDim( nD ), + nHier( nH ), + nLev( nL ), + mnDataId( nIndex ), + nPosition( -1 ), + bVisible( true ), + bShowDet( true ) +{ + //TODO: hold pSource +} + +ScDPMember::~ScDPMember() +{ + //TODO: release pSource +} + +bool ScDPMember::IsNamedItem(SCROW nIndex) const +{ + sal_Int32 nSrcDim = pSource->GetSourceDim( nDim ); + if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) ) + { + const ScDPItemData* pData = pSource->GetCache()->GetItemDataById(nDim, nIndex); + if (pData->IsValue()) + { + tools::Long nComp = pSource->GetData()->GetDatePart( + static_cast<tools::Long>(::rtl::math::approxFloor( pData->GetValue() )), + nHier, nLev ); + // fValue is converted from integer, so simple comparison works + const ScDPItemData* pData2 = GetItemData(); + return pData2 && nComp == pData2->GetValue(); + } + } + + return nIndex == mnDataId; +} + +sal_Int32 ScDPMember::Compare( const ScDPMember& rOther ) const +{ + if ( nPosition >= 0 ) + { + if ( rOther.nPosition >= 0 ) + { + OSL_ENSURE( nPosition != rOther.nPosition, "same position for two members" ); + return ( nPosition < rOther.nPosition ) ? -1 : 1; + } + else + { + // only this has a position - members with specified positions come before those without + return -1; + } + } + else if ( rOther.nPosition >= 0 ) + { + // only rOther has a position + return 1; + } + + // no positions set - compare names + return pSource->GetData()->Compare( pSource->GetSourceDim(nDim),mnDataId,rOther.GetItemDataId()); +} + +ScDPItemData ScDPMember::FillItemData() const +{ + //TODO: handle date hierarchy... + + const ScDPItemData* pData = GetItemData(); + return (pData ? *pData : ScDPItemData()); +} + +const std::optional<OUString> & ScDPMember::GetLayoutName() const +{ + return mpLayoutName; +} + +OUString ScDPMember::GetNameStr( bool bLocaleIndependent ) const +{ + const ScDPItemData* pData = GetItemData(); + if (pData) + return pSource->GetData()->GetFormattedString(nDim, *pData, bLocaleIndependent); + return OUString(); +} + +OUString SAL_CALL ScDPMember::getName() +{ + return GetNameStr( false ); +} + +void SAL_CALL ScDPMember::setName( const OUString& /* rNewName */ ) +{ + OSL_FAIL("not implemented"); //TODO: exception? +} + +// XPropertySet + +uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPMember::getPropertySetInfo() +{ + static const SfxItemPropertyMapEntry aDPMemberMap_Impl[] = + { + { SC_UNO_DP_ISVISIBLE, 0, cppu::UnoType<bool>::get(), 0, 0 }, + { SC_UNO_DP_POSITION, 0, cppu::UnoType<sal_Int32>::get(), 0, 0 }, + { SC_UNO_DP_SHOWDETAILS, 0, cppu::UnoType<bool>::get(), 0, 0 }, + { SC_UNO_DP_LAYOUTNAME, 0, cppu::UnoType<OUString>::get(), 0, 0 }, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static uno::Reference<beans::XPropertySetInfo> aRef = + new SfxItemPropertySetInfo( aDPMemberMap_Impl ); + return aRef; +} + +void SAL_CALL ScDPMember::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue ) +{ + if ( aPropertyName == SC_UNO_DP_ISVISIBLE ) + bVisible = lcl_GetBoolFromAny(aValue); + else if ( aPropertyName == SC_UNO_DP_SHOWDETAILS ) + bShowDet = lcl_GetBoolFromAny(aValue); + else if ( aPropertyName == SC_UNO_DP_POSITION ) + aValue >>= nPosition; + else if (aPropertyName == SC_UNO_DP_LAYOUTNAME) + { + OUString aName; + if (aValue >>= aName) + mpLayoutName = aName; + } + else + { + OSL_FAIL("unknown property"); + } +} + +uno::Any SAL_CALL ScDPMember::getPropertyValue( const OUString& aPropertyName ) +{ + uno::Any aRet; + if ( aPropertyName == SC_UNO_DP_ISVISIBLE ) + aRet <<= bVisible; + else if ( aPropertyName == SC_UNO_DP_SHOWDETAILS ) + aRet <<= bShowDet; + else if ( aPropertyName == SC_UNO_DP_POSITION ) + aRet <<= nPosition; + else if (aPropertyName == SC_UNO_DP_LAYOUTNAME) + aRet <<= mpLayoutName ? *mpLayoutName : OUString(); + else + { + OSL_FAIL("unknown property"); + } + return aRet; +} + +SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPMember ) + +const ScDPCache* ScDPSource::GetCache() +{ + OSL_ENSURE( GetData() , "empty ScDPTableData pointer"); + return ( GetData()!=nullptr ) ? &GetData()->GetCacheTable().getCache() : nullptr ; +} + +const ScDPItemData* ScDPMember::GetItemData() const +{ + const ScDPItemData* pData = pSource->GetItemDataById(nDim, mnDataId); + SAL_WARN_IF( !pData, "sc.core", "ScDPMember::GetItemData: what data? nDim " << nDim << ", mnDataId " << mnDataId); + return pData; +} + +const ScDPItemData* ScDPSource::GetItemDataById(sal_Int32 nDim, sal_Int32 nId) +{ + return GetData()->GetMemberById(nDim, nId); +} + +const ScDPItemData* ScDPMembers::GetSrcItemDataByIndex(SCROW nIndex) +{ + const std::vector< SCROW >& memberIds = pSource->GetData()->GetColumnEntries( nDim ); + if ( nIndex < 0 || o3tl::make_unsigned(nIndex) >= memberIds.size() ) + return nullptr; + SCROW nId = memberIds[ nIndex ]; + return pSource->GetItemDataById( nDim, nId ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |