2598 lines
84 KiB
C++
2598 lines
84 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <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 <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, u"ScDPSource"_ustr, u"com.sun.star.sheet.DataPilotSource"_ustr )
|
|
SC_SIMPLE_SERVICE_INFO( ScDPDimensions, u"ScDPDimensions"_ustr, u"com.sun.star.sheet.DataPilotSourceDimensions"_ustr )
|
|
SC_SIMPLE_SERVICE_INFO( ScDPDimension, u"ScDPDimension"_ustr, u"com.sun.star.sheet.DataPilotSourceDimension"_ustr )
|
|
|
|
// 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, u"ScDPHierarchies"_ustr,
|
|
u"com.sun.star.sheet.DataPilotSourceHierarchies"_ustr, u"com.sun.star.sheet.DataPilotSourceHierarcies"_ustr )
|
|
SC_SIMPLE_SERVICE_INFO_COMPAT( ScDPHierarchy, u"ScDPHierarchy"_ustr,
|
|
u"com.sun.star.sheet.DataPilotSourceHierarchy"_ustr, u"com.sun.star.sheet.DataPilotSourceHierarcy"_ustr )
|
|
|
|
SC_SIMPLE_SERVICE_INFO( ScDPLevels, u"ScDPLevels"_ustr, u"com.sun.star.sheet.DataPilotSourceLevels"_ustr )
|
|
SC_SIMPLE_SERVICE_INFO( ScDPLevel, u"ScDPLevel"_ustr, u"com.sun.star.sheet.DataPilotSourceLevel"_ustr )
|
|
SC_SIMPLE_SERVICE_INFO( ScDPMembers, u"ScDPMembers"_ustr, u"com.sun.star.sheet.DataPilotSourceMembers"_ustr )
|
|
SC_SIMPLE_SERVICE_INFO( ScDPMember, u"ScDPMember"_ustr, u"com.sun.star.sheet.DataPilotSourceMember"_ustr )
|
|
|
|
// 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.has_value() && *b;
|
|
}
|
|
|
|
ScDPSource::ScDPSource(ScDPTableData* pData)
|
|
: mpData(pData)
|
|
{
|
|
mpData->SetEmptyFlags(mbIgnoreEmptyRows, mbRepeatIfEmpty);
|
|
}
|
|
|
|
ScDPSource::~ScDPSource()
|
|
{
|
|
// free lists
|
|
|
|
mpColumnResults.reset();
|
|
mpRowResults.reset();
|
|
|
|
mpColumnResultRoot.reset();
|
|
mpRowResultRoot.reset();
|
|
mpResultData.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 == mpData->GetColumnCount();
|
|
}
|
|
|
|
sheet::DataPilotFieldOrientation ScDPSource::GetDataLayoutOrientation()
|
|
{
|
|
return GetOrientation(mpData->GetColumnCount());
|
|
}
|
|
|
|
bool ScDPSource::IsDateDimension(sal_Int32 nDim)
|
|
{
|
|
return mpData->IsDateDimension(nDim);
|
|
}
|
|
|
|
ScDPDimensions* ScDPSource::GetDimensionsObject()
|
|
{
|
|
if (!mpDimensions.is())
|
|
mpDimensions = new ScDPDimensions(this);
|
|
return mpDimensions.get();
|
|
}
|
|
|
|
uno::Reference<container::XNameAccess> SAL_CALL ScDPSource::getDimensions()
|
|
{
|
|
return GetDimensionsObject();
|
|
}
|
|
|
|
void ScDPSource::SetDupCount( tools::Long nNew )
|
|
{
|
|
mnDupCount = nNew;
|
|
}
|
|
|
|
ScDPDimension* ScDPSource::AddDuplicated(std::u16string_view rNewName)
|
|
{
|
|
OSL_ENSURE(mpDimensions.is(), "AddDuplicated without dimensions?");
|
|
|
|
// re-use
|
|
|
|
tools::Long nOldDimCount = mpDimensions->getCount();
|
|
for (tools::Long i=0; i<nOldDimCount; i++)
|
|
{
|
|
ScDPDimension* pDim = mpDimensions->getByIndex(i);
|
|
if (pDim && pDim->getName() == rNewName)
|
|
{
|
|
//TODO: test if pDim is a duplicate of source
|
|
return pDim;
|
|
}
|
|
}
|
|
|
|
SetDupCount(mnDupCount + 1);
|
|
mpDimensions->CountChanged(); // uses mnDupCount
|
|
|
|
return mpDimensions->getByIndex(mpDimensions->getCount() - 1);
|
|
}
|
|
|
|
sal_Int32 ScDPSource::GetSourceDim(sal_Int32 nDim)
|
|
{
|
|
// original source dimension or data layout dimension?
|
|
if (nDim <= mpData->GetColumnCount())
|
|
return nDim;
|
|
|
|
if (nDim < mpDimensions->getCount())
|
|
{
|
|
ScDPDimension* pDimObj = mpDimensions->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 mpColumnResultRoot and mpRowResultRoot
|
|
|
|
if (mbResultOverflow) // set in CreateRes_Impl
|
|
{
|
|
// no results available
|
|
throw uno::RuntimeException();
|
|
}
|
|
|
|
sal_Int32 nColCount = mpColumnResultRoot->GetSize(mpResultData->GetColStartMeasure());
|
|
sal_Int32 nRowCount = mpRowResultRoot->GetSize(mpResultData->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++)
|
|
{
|
|
// use default values of DataResult
|
|
pRowAry[nRow] = uno::Sequence<sheet::DataResult>(nColCount);
|
|
}
|
|
|
|
ScDPResultFilterContext aFilterCxt;
|
|
mpRowResultRoot->FillDataResults(
|
|
mpColumnResultRoot.get(), aFilterCxt, aSeq, mpResultData->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 == mpData->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);
|
|
mpRowResultRoot->FillVisibilityData(aResVisData);
|
|
mpColumnResultRoot->FillVisibilityData(aResVisData);
|
|
aResVisData.fillFieldFilters(aFilterCriteria);
|
|
|
|
Sequence< Sequence<Any> > aTabData;
|
|
std::unordered_set<sal_Int32> aCatDims;
|
|
GetCategoryDimensionIndices(aCatDims);
|
|
mpData->GetDrillDownData(std::move(aFilterCriteria), std::move(aCatDims), aTabData);
|
|
return aTabData;
|
|
}
|
|
|
|
OUString ScDPSource::getDataDescription()
|
|
{
|
|
CreateRes_Impl(); // create mpResultData
|
|
|
|
OUString aRet;
|
|
if (mpResultData->GetMeasureCount() == 1)
|
|
{
|
|
bool bTotalResult = false;
|
|
aRet = mpResultData->GetMeasureString(0, true, SUBTOTAL_FUNC_NONE, bTotalResult);
|
|
}
|
|
|
|
// empty for more than one measure
|
|
|
|
return aRet;
|
|
}
|
|
|
|
void ScDPSource::setIgnoreEmptyRows(bool bSet)
|
|
{
|
|
mbIgnoreEmptyRows = bSet;
|
|
mpData->SetEmptyFlags(mbIgnoreEmptyRows, mbRepeatIfEmpty);
|
|
}
|
|
|
|
void ScDPSource::setRepeatIfEmpty(bool bSet)
|
|
{
|
|
mbRepeatIfEmpty = bSet;
|
|
mpData->SetEmptyFlags(mbIgnoreEmptyRows, mbRepeatIfEmpty);
|
|
}
|
|
|
|
void ScDPSource::disposeData()
|
|
{
|
|
maResFilterSet.clear();
|
|
|
|
if (mpResultData)
|
|
{
|
|
// reset all data...
|
|
|
|
mpColumnResultRoot.reset();
|
|
mpRowResultRoot.reset();
|
|
mpResultData.reset();
|
|
mpColumnResults.reset();
|
|
mpRowResults.reset();
|
|
maColumnLevelList.clear();
|
|
maRowLevelList.clear();
|
|
}
|
|
|
|
mpDimensions.clear(); // settings have to be applied (from SaveData) again!
|
|
SetDupCount( 0 );
|
|
|
|
maColDims.clear();
|
|
maRowDims.clear();
|
|
maDataDims.clear();
|
|
maPageDims.clear();
|
|
|
|
mpData->DisposeData(); // cached entries etc.
|
|
mbPageFiltered = false;
|
|
mbResultOverflow = 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 (mbPageFiltered)
|
|
{
|
|
SAL_WARN( "sc.core","tried to apply page field filters several times");
|
|
|
|
mpData->DisposeData();
|
|
mpData->CreateCacheTable(); // re-initialize the cache table
|
|
mbPageFiltered = 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);
|
|
mpData->FilterCacheTable(std::move(aCriteria), std::move(aCatDims));
|
|
mbPageFiltered = true;
|
|
}
|
|
}
|
|
|
|
void ScDPSource::CreateRes_Impl()
|
|
{
|
|
if (mpResultData)
|
|
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(mpData->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);
|
|
}
|
|
|
|
mpResultData.reset( new ScDPResultData(*this) );
|
|
mpResultData->SetMeasureData(aDataFunctions, aDataRefValues, aDataRefOrient, aDataNames);
|
|
mpResultData->SetDataLayoutOrientation(nDataOrient);
|
|
mpResultData->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 = mbColumnGrand && nColDimCount2 > 0;
|
|
bool bShowRowGrand = mbRowGrand && nRowDimCount2 > 0;
|
|
mpColumnResultRoot.reset( new ScDPResultMember(mpResultData.get(), bShowColGrand) );
|
|
mpRowResultRoot.reset( new ScDPResultMember(mpResultData.get(), bShowRowGrand) );
|
|
|
|
FillCalcInfo(false, aInfo, bHasAutoShow);
|
|
tools::Long nColLevelCount = aInfo.aColLevels.size();
|
|
|
|
mpColumnResultRoot->InitFrom( aInfo.aColDims, aInfo.aColLevels, 0, aInitState );
|
|
mpColumnResultRoot->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 );
|
|
}
|
|
|
|
mpRowResultRoot->InitFrom( aInfo.aRowDims, aInfo.aRowLevels, 0, aInitState );
|
|
mpRowResultRoot->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)
|
|
|
|
mbResultOverflow = true;
|
|
return;
|
|
}
|
|
|
|
FilterCacheByPageDimensions();
|
|
|
|
aInfo.aPageDims = maPageDims;
|
|
aInfo.pInitState = &aInitState;
|
|
aInfo.pColRoot = mpColumnResultRoot.get();
|
|
aInfo.pRowRoot = mpRowResultRoot.get();
|
|
mpData->CalcResults(aInfo, false);
|
|
|
|
mpColumnResultRoot->CheckShowEmpty();
|
|
mpRowResultRoot->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.
|
|
mpRowResultRoot->UpdateDataResults(mpColumnResultRoot.get(), mpResultData->GetRowStartMeasure());
|
|
|
|
if ( bHasAutoShow ) // do the double calculation only if AutoShow is used
|
|
{
|
|
// Find the desired members and set bAutoHidden flag for the others
|
|
mpRowResultRoot->DoAutoShow(mpColumnResultRoot.get());
|
|
|
|
// Reset all results to empty, so they can be built again with data for the
|
|
// desired members only.
|
|
mpColumnResultRoot->ResetResults();
|
|
mpRowResultRoot->ResetResults();
|
|
mpData->CalcResults(aInfo, true);
|
|
|
|
// Call UpdateDataResults again, with the new (limited) values.
|
|
mpRowResultRoot->UpdateDataResults(mpColumnResultRoot.get(), mpResultData->GetRowStartMeasure());
|
|
}
|
|
|
|
// SortMembers does the sorting by a result dimension, using the original results,
|
|
// but not running totals etc.
|
|
mpRowResultRoot->SortMembers(mpColumnResultRoot.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(mpColumnResultRoot.get(), mpRowResultRoot.get());
|
|
ScDPRowTotals aTotals;
|
|
mpRowResultRoot->UpdateRunningTotals(mpColumnResultRoot.get(), mpResultData->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 (mpColumnResults || mpRowResults)
|
|
return;
|
|
|
|
CreateRes_Impl();
|
|
|
|
if (mbResultOverflow) // set in CreateRes_Impl
|
|
{
|
|
// no results available -> abort (leave empty)
|
|
// exception is thrown in ScDPSource::getResults
|
|
return;
|
|
}
|
|
|
|
FillLevelList(sheet::DataPilotFieldOrientation_COLUMN, maColumnLevelList);
|
|
sal_Int32 nColLevelCount = maColumnLevelList.size();
|
|
if (nColLevelCount)
|
|
{
|
|
tools::Long nColDimSize = mpColumnResultRoot->GetSize(mpResultData->GetColStartMeasure());
|
|
mpColumnResults.reset(new uno::Sequence<sheet::MemberResult>[nColLevelCount]);
|
|
for (tools::Long i=0; i<nColLevelCount; i++)
|
|
mpColumnResults[i].realloc(nColDimSize);
|
|
|
|
tools::Long nPos = 0;
|
|
mpColumnResultRoot->FillMemberResults(mpColumnResults.get(), nPos, mpResultData->GetColStartMeasure(),
|
|
true, nullptr, nullptr);
|
|
}
|
|
|
|
FillLevelList(sheet::DataPilotFieldOrientation_ROW, maRowLevelList);
|
|
tools::Long nRowLevelCount = maRowLevelList.size();
|
|
if (nRowLevelCount)
|
|
{
|
|
tools::Long nRowDimSize = mpRowResultRoot->GetSize(mpResultData->GetRowStartMeasure());
|
|
mpRowResults.reset( new uno::Sequence<sheet::MemberResult>[nRowLevelCount] );
|
|
for (tools::Long i=0; i<nRowLevelCount; i++)
|
|
mpRowResults[i].realloc(nRowDimSize);
|
|
|
|
tools::Long nPos = 0;
|
|
mpRowResultRoot->FillMemberResults(mpRowResults.get(), nPos, mpResultData->GetRowStartMeasure(),
|
|
true, nullptr, nullptr);
|
|
}
|
|
}
|
|
|
|
const uno::Sequence<sheet::MemberResult>* ScDPSource::GetMemberResults( const ScDPLevel* pLevel )
|
|
{
|
|
FillMemberResults();
|
|
|
|
sal_Int32 i = 0;
|
|
sal_Int32 nColCount = maColumnLevelList.size();
|
|
for (i=0; i<nColCount; i++)
|
|
{
|
|
ScDPLevel* pColLevel = maColumnLevelList[i];
|
|
if ( pColLevel == pLevel )
|
|
return &mpColumnResults[i];
|
|
}
|
|
sal_Int32 nRowCount = maRowLevelList.size();
|
|
for (i=0; i<nRowCount; i++)
|
|
{
|
|
ScDPLevel* pRowLevel = maRowLevelList[i];
|
|
if ( pRowLevel == pLevel )
|
|
return &mpRowResults[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 },
|
|
};
|
|
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)
|
|
mbColumnGrand = lcl_GetBoolFromAny(aValue);
|
|
else if (aPropertyName == SC_UNO_DP_ROWGRAND)
|
|
mbRowGrand = 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 <<= mbColumnGrand;
|
|
else if ( aPropertyName == SC_UNO_DP_ROWGRAND )
|
|
aRet <<= mbRowGrand;
|
|
else if ( aPropertyName == SC_UNO_DP_IGNOREEMPTY )
|
|
aRet <<= mbIgnoreEmptyRows;
|
|
else if ( aPropertyName == SC_UNO_DP_REPEATEMPTY )
|
|
aRet <<= mbRepeatIfEmpty;
|
|
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;
|
|
mpColumnResultRoot->Dump(1);
|
|
std::cout << "+++++ row root" << std::endl;
|
|
mpRowResultRoot->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 },
|
|
};
|
|
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 },
|
|
};
|
|
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] = std::move(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 },
|
|
};
|
|
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: */
|