/* -*- 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 <algorithm>
#include <cmath>
 
#include <o3tl/safeint.hxx>
#include <svl/hint.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include <sal/log.hxx>
 
#include <dapiuno.hxx>
#include <datauno.hxx>
#include <miscuno.hxx>
#include <convuno.hxx>
#include <docsh.hxx>
#include <tabvwsh.hxx>
#include <rangeutl.hxx>
#include <dpobject.hxx>
#include <dpshttab.hxx>
#include <dpsdbtab.hxx>
#include <dpsave.hxx>
#include <dbdocfun.hxx>
#include <unonames.hxx>
#include <dpdimsave.hxx>
#include <hints.hxx>
#include <dputil.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <generalfunction.hxx>
 
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <com/sun/star/lang/NullPointerException.hpp>
#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
#include <com/sun/star/sheet/XDimensionsSupplier.hpp>
#include <com/sun/star/sheet/XLevelsSupplier.hpp>
#include <com/sun/star/sheet/XMembersAccess.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/sheet/DataImportMode.hpp>
#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
#include <com/sun/star/sheet/DataPilotFieldFilter.hpp>
#include <com/sun/star/sheet/DataPilotOutputRangeType.hpp>
#include <com/sun/star/sheet/DataPilotTablePositionData.hpp>
#include <com/sun/star/sheet/GeneralFunction2.hpp>
 
#include <comphelper/extract.hxx>
#include <comphelper/propertysequence.hxx>
#include <cppuhelper/queryinterface.hxx>
#include <comphelper/sequence.hxx>
#include <cppuhelper/exc_hlp.hxx>
 
using namespace com::sun::star;
using namespace com::sun::star::sheet;
 
using ::com::sun::star::uno::Any;
using ::com::sun::star::uno::Exception;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::RuntimeException;
using ::com::sun::star::uno::Sequence;
using ::com::sun::star::uno::UNO_QUERY;
using ::com::sun::star::uno::UNO_QUERY_THROW;
 
using ::com::sun::star::container::ElementExistException;
using ::com::sun::star::container::NoSuchElementException;
using ::com::sun::star::container::XEnumeration;
using ::com::sun::star::container::XIndexAccess;
using ::com::sun::star::container::XNameAccess;
using ::com::sun::star::container::XNamed;
 
using ::com::sun::star::beans::UnknownPropertyException;
using ::com::sun::star::beans::XPropertyChangeListener;
using ::com::sun::star::beans::XPropertySet;
using ::com::sun::star::beans::XPropertySetInfo;
using ::com::sun::star::beans::XVetoableChangeListener;
 
using ::com::sun::star::lang::IllegalArgumentException;
using ::com::sun::star::lang::IndexOutOfBoundsException;
using ::com::sun::star::lang::NullPointerException;
 
using ::com::sun::star::table::CellAddress;
using ::com::sun::star::table::CellRangeAddress;
 
namespace {
 
std::span<const SfxItemPropertyMapEntry> lcl_GetDataPilotDescriptorBaseMap()
{
    static const SfxItemPropertyMapEntry aDataPilotDescriptorBaseMap_Impl[] =
    {
        { SC_UNO_DP_COLGRAND,     0,  cppu::UnoType<bool>::get(),  0, 0 },
        { SC_UNO_DP_DRILLDOWN,    0,  cppu::UnoType<bool>::get(),  0, 0 },
        { SC_UNO_DP_GRANDTOTAL_NAME,0,cppu::UnoType<OUString>::get(), beans::PropertyAttribute::MAYBEVOID, 0 },
        { SC_UNO_DP_IGNORE_EMPTYROWS,   0,  cppu::UnoType<bool>::get(),  0, 0 },
        { SC_UNO_DP_IMPORTDESC,   0,  cppu::UnoType<uno::Sequence<beans::PropertyValue>>::get(), 0, 0 },
        { SC_UNO_DP_REPEATEMPTY,     0,  cppu::UnoType<bool>::get(),  0, 0 },
        { SC_UNO_DP_ROWGRAND,     0,  cppu::UnoType<bool>::get(),  0, 0 },
        { SC_UNO_DP_SERVICEARG,   0,  cppu::UnoType<uno::Sequence<beans::PropertyValue>>::get(), 0, 0 },
        { SC_UNO_DP_SHOWFILTER,     0,  cppu::UnoType<bool>::get(),  0, 0 },
        { SC_UNO_DP_SOURCESERVICE,   0,  cppu::UnoType<OUString>::get(), 0, 0 },
    };
    return aDataPilotDescriptorBaseMap_Impl;
}
 
std::span<const SfxItemPropertyMapEntry> lcl_GetDataPilotFieldMap()
{
    using namespace ::com::sun::star::beans::PropertyAttribute;
    static const SfxItemPropertyMapEntry aDataPilotFieldMap_Impl[] =
    {
        { SC_UNONAME_AUTOSHOW,     0,  cppu::UnoType<DataPilotFieldAutoShowInfo>::get(),   MAYBEVOID, 0 },
        { SC_UNONAME_FUNCTION,     0,  cppu::UnoType<GeneralFunction>::get(),              0, 0 },
        { SC_UNONAME_FUNCTION2,    0,  cppu::UnoType<sal_Int16>::get(),             0, 0 },
        { SC_UNONAME_GROUPINFO,    0,  cppu::UnoType<DataPilotFieldGroupInfo>::get(),      MAYBEVOID, 0 },
        { SC_UNONAME_HASAUTOSHOW,  0,  cppu::UnoType<bool>::get(),                          0, 0 },
        { SC_UNONAME_HASLAYOUTINFO,0,  cppu::UnoType<bool>::get(),                          0, 0 },
        { SC_UNONAME_HASREFERENCE, 0,  cppu::UnoType<bool>::get(),                          0, 0 },
        { SC_UNONAME_HASSORTINFO,  0,  cppu::UnoType<bool>::get(),                          0, 0 },
        { SC_UNONAME_ISGROUP,      0,  cppu::UnoType<bool>::get(),                          0, 0 },
        { SC_UNONAME_LAYOUTINFO,   0,  cppu::UnoType<DataPilotFieldLayoutInfo>::get(),     MAYBEVOID, 0 },
        { SC_UNONAME_ORIENT,       0,  cppu::UnoType<DataPilotFieldOrientation>::get(),    MAYBEVOID, 0 },
        { SC_UNONAME_REFERENCE,    0,  cppu::UnoType<DataPilotFieldReference>::get(),      MAYBEVOID, 0 },
        { SC_UNONAME_SELPAGE,      0,  cppu::UnoType<OUString>::get(),                     0, 0 },
        { SC_UNONAME_SHOWEMPTY,    0,  cppu::UnoType<bool>::get(),                          0, 0 },
        { SC_UNONAME_REPEATITEMLABELS,    0,  cppu::UnoType<bool>::get(),                          0, 0 },
        { SC_UNONAME_SORTINFO,     0,  cppu::UnoType<DataPilotFieldSortInfo>::get(),       MAYBEVOID, 0 },
        { SC_UNONAME_SUBTOTALS,    0,  cppu::UnoType<Sequence<GeneralFunction>>::get(),    0, 0 },
        { SC_UNONAME_SUBTOTALS2,   0,  cppu::UnoType<Sequence<sal_Int16>>::get(),   0, 0 },
        { SC_UNONAME_USESELPAGE,   0,  cppu::UnoType<bool>::get(),                          0, 0 },
    };
    return aDataPilotFieldMap_Impl;
}
 
std::span<const SfxItemPropertyMapEntry> lcl_GetDataPilotItemMap()
{
    static const SfxItemPropertyMapEntry aDataPilotItemMap_Impl[] =
    {
        { SC_UNONAME_ISHIDDEN,     0,  cppu::UnoType<bool>::get(),          0, 0 },
        { SC_UNONAME_POS,          0,  cppu::UnoType<sal_Int32>::get(),    0, 0 },
        { SC_UNONAME_SHOWDETAIL,   0,  cppu::UnoType<bool>::get(),          0, 0 },
    };
    return aDataPilotItemMap_Impl;
}
 
bool lclCheckValidDouble( double fValue, bool bAuto )
{
    return bAuto || std::isfinite( fValue );
}
 
bool lclCheckMinMaxStep( const DataPilotFieldGroupInfo& rInfo )
{
    return
        lclCheckValidDouble( rInfo.Start, rInfo.HasAutoStart ) &&
        lclCheckValidDouble( rInfo.End, rInfo.HasAutoEnd ) &&
        (rInfo.HasAutoStart || rInfo.HasAutoEnd || (rInfo.Start <= rInfo.End)) &&
        lclCheckValidDouble( rInfo.Step, false ) &&
        (0.0 <= rInfo.Step);
}
 
} // namespace
 
SC_SIMPLE_SERVICE_INFO( ScDataPilotDescriptor, u"ScDataPilotDescriptor"_ustr, u"stardiv::one::sheet::DataPilotDescriptor"_ustr )
SC_SIMPLE_SERVICE_INFO( ScDataPilotFieldObj, u"ScDataPilotFieldObj"_ustr, u"com.sun.star.sheet.DataPilotField"_ustr )
SC_SIMPLE_SERVICE_INFO( ScDataPilotFieldsObj, u"ScDataPilotFieldsObj"_ustr, u"com.sun.star.sheet.DataPilotFields"_ustr )
SC_SIMPLE_SERVICE_INFO( ScDataPilotTableObj, u"ScDataPilotTableObj"_ustr, u"com.sun.star.sheet.DataPilotTable"_ustr )
SC_SIMPLE_SERVICE_INFO( ScDataPilotTablesObj, u"ScDataPilotTablesObj"_ustr, u"com.sun.star.sheet.DataPilotTables"_ustr )
SC_SIMPLE_SERVICE_INFO( ScDataPilotItemsObj, u"ScDataPilotItemsObj"_ustr, u"com.sun.star.sheet.DataPilotItems"_ustr )
SC_SIMPLE_SERVICE_INFO( ScDataPilotItemObj, u"ScDataPilotItemObj"_ustr, u"com.sun.star.sheet.DataPilotItem"_ustr )
 
SC_SIMPLE_SERVICE_INFO( ScDataPilotFieldGroupsObj, u"ScDataPilotFieldGroupsObj"_ustr, u"com.sun.star.sheet.DataPilotFieldGroups"_ustr )
SC_SIMPLE_SERVICE_INFO( ScDataPilotFieldGroupObj, u"ScDataPilotFieldGroupObj"_ustr, u"com.sun.star.sheet.DataPilotFieldGroup"_ustr )
SC_SIMPLE_SERVICE_INFO( ScDataPilotFieldGroupItemObj, u"ScDataPilotFieldGroupItemObj"_ustr, u"com.sun.star.sheet.DataPilotFieldGroupItem"_ustr )
 
// name that is used in the API for the data layout field
constexpr OUString SC_DATALAYOUT_NAME = u"Data"_ustr;
 
ScGeneralFunction ScDataPilotConversion::FirstFunc( PivotFunc nBits )
{
    if ( nBits & PivotFunc::Sum )       return ScGeneralFunction::SUM;
    if ( nBits & PivotFunc::Count )     return ScGeneralFunction::COUNT;
    if ( nBits & PivotFunc::Average )   return ScGeneralFunction::AVERAGE;
    if ( nBits & PivotFunc::Median )    return ScGeneralFunction::MEDIAN;
    if ( nBits & PivotFunc::Max )       return ScGeneralFunction::MAX;
    if ( nBits & PivotFunc::Min )       return ScGeneralFunction::MIN;
    if ( nBits & PivotFunc::Product )   return ScGeneralFunction::PRODUCT;
    if ( nBits & PivotFunc::CountNum )  return ScGeneralFunction::COUNTNUMS;
    if ( nBits & PivotFunc::StdDev )    return ScGeneralFunction::STDEV;
    if ( nBits & PivotFunc::StdDevP )   return ScGeneralFunction::STDEVP;
    if ( nBits & PivotFunc::StdVar )    return ScGeneralFunction::VAR;
    if ( nBits & PivotFunc::StdVarP )   return ScGeneralFunction::VARP;
    if ( nBits & PivotFunc::Auto )      return ScGeneralFunction::AUTO;
    return ScGeneralFunction::NONE;
}
 
PivotFunc ScDataPilotConversion::FunctionBit( sal_Int16 eFunc )
{
    PivotFunc nRet = PivotFunc::NONE;  // 0
    switch (eFunc)
    {
        case GeneralFunction2::SUM:       nRet = PivotFunc::Sum;       break;
        case GeneralFunction2::COUNT:     nRet = PivotFunc::Count;     break;
        case GeneralFunction2::AVERAGE:   nRet = PivotFunc::Average;   break;
        case GeneralFunction2::MEDIAN:    nRet = PivotFunc::Median;    break;
        case GeneralFunction2::MAX:       nRet = PivotFunc::Max;       break;
        case GeneralFunction2::MIN:       nRet = PivotFunc::Min;       break;
        case GeneralFunction2::PRODUCT:   nRet = PivotFunc::Product;   break;
        case GeneralFunction2::COUNTNUMS: nRet = PivotFunc::CountNum; break;
        case GeneralFunction2::STDEV:     nRet = PivotFunc::StdDev;   break;
        case GeneralFunction2::STDEVP:    nRet = PivotFunc::StdDevP;  break;
        case GeneralFunction2::VAR:       nRet = PivotFunc::StdVar;   break;
        case GeneralFunction2::VARP:      nRet = PivotFunc::StdVarP;  break;
        case GeneralFunction2::AUTO:      nRet = PivotFunc::Auto;      break;
        default:
        {
            assert(false);
        }
    }
    return nRet;
}
 
void ScDataPilotConversion::FillGroupInfo( DataPilotFieldGroupInfo& rInfo, const ScDPNumGroupInfo& rGroupInfo )
{
    rInfo.HasDateValues = rGroupInfo.mbDateValues;
    rInfo.HasAutoStart  = rGroupInfo.mbAutoStart;
    rInfo.Start         = rGroupInfo.mfStart;
    rInfo.HasAutoEnd    = rGroupInfo.mbAutoEnd;
    rInfo.End           = rGroupInfo.mfEnd;
    rInfo.Step          = rGroupInfo.mfStep;
}
 
static ScDPObject* lcl_GetDPObject( ScDocShell* pDocShell, SCTAB nTab, std::u16string_view rName )
{
    if (pDocShell)
    {
        ScDocument& rDoc = pDocShell->GetDocument();
        ScDPCollection* pColl = rDoc.GetDPCollection();
        if ( pColl )
        {
            size_t nCount = pColl->GetCount();
            for (size_t i=0; i<nCount; ++i)
            {
                ScDPObject& rDPObj = (*pColl)[i];
                if ( rDPObj.GetOutRange().aStart.Tab() == nTab &&
                     rDPObj.GetName() == rName )
                    return &rDPObj;
            }
        }
    }
    return nullptr;    // not found
}
 
static OUString lcl_CreatePivotName( ScDocShell* pDocShell )
{
    if (pDocShell)
    {
        ScDocument& rDoc = pDocShell->GetDocument();
        ScDPCollection* pColl = rDoc.GetDPCollection();
        if ( pColl )
            return pColl->CreateNewName();
    }
    return OUString();                    // shouldn't happen
}
 
static sal_Int32 lcl_GetObjectIndex( ScDPObject* pDPObj, const ScFieldIdentifier& rFieldId )
{
    // used for items - nRepeat in identifier can be ignored
    if ( pDPObj )
    {
        sal_Int32 nCount = pDPObj->GetDimCount();
        for ( sal_Int32 nDim = 0; nDim < nCount; ++nDim )
        {
            bool bIsDataLayout = false;
            OUString aDimName( pDPObj->GetDimName( nDim, bIsDataLayout ) );
            if ( rFieldId.mbDataLayout ? bIsDataLayout : (aDimName == rFieldId.maFieldName) )
                return nDim;
        }
    }
    return -1;  // none
}
 
ScDataPilotTablesObj::ScDataPilotTablesObj(ScDocShell& rDocSh, SCTAB nT) :
    pDocShell( &rDocSh ),
    nTab( nT )
{
    pDocShell->GetDocument().AddUnoObject(*this);
}
 
ScDataPilotTablesObj::~ScDataPilotTablesObj()
{
    SolarMutexGuard g;
 
    if (pDocShell)
        pDocShell->GetDocument().RemoveUnoObject(*this);
}
 
void ScDataPilotTablesObj::Notify( SfxBroadcaster&, const SfxHint& rHint )
{
    //! update of references
 
    if ( rHint.GetId() == SfxHintId::Dying )
    {
        pDocShell = nullptr;       // became invalid
    }
}
 
// XDataPilotTables
 
rtl::Reference<ScDataPilotTableObj> ScDataPilotTablesObj::GetObjectByIndex_Impl( sal_Int32 nIndex )
{
    if (pDocShell)
    {
        ScDocument& rDoc = pDocShell->GetDocument();
        ScDPCollection* pColl = rDoc.GetDPCollection();
        if ( pColl )
        {
            //  count tables on this sheet
            sal_Int32 nFound = 0;
            size_t nCount = pColl->GetCount();
            for (size_t i=0; i<nCount; ++i)
            {
                ScDPObject& rDPObj = (*pColl)[i];
                if ( rDPObj.GetOutRange().aStart.Tab() == nTab )
                {
                    if ( nFound == nIndex )
                    {
                        return new ScDataPilotTableObj(*pDocShell, nTab, rDPObj.GetName());
                    }
                    ++nFound;
                }
            }
        }
    }
    return nullptr;
}
 
rtl::Reference<ScDataPilotTableObj> ScDataPilotTablesObj::GetObjectByName_Impl(const OUString& rName)
{
    if (hasByName(rName))
        return new ScDataPilotTableObj(*pDocShell, nTab, rName);
    return nullptr;
}
 
Reference<XDataPilotDescriptor> SAL_CALL ScDataPilotTablesObj::createDataPilotDescriptor()
{
    SolarMutexGuard aGuard;
    if (pDocShell)
        return new ScDataPilotDescriptor(*pDocShell);
    return nullptr;
}
 
static bool lcl_IsDuplicated(const Reference<XPropertySet>& rDimProps)
{
    try
    {
        Any aAny = rDimProps->getPropertyValue( SC_UNO_DP_ORIGINAL );
        Reference< XNamed > xOriginal( aAny, UNO_QUERY );
        return xOriginal.is();
    }
    catch( Exception& )
    {
    }
    return false;
}
 
static OUString lcl_GetOriginalName(const Reference< XNamed >& rDim)
{
    Reference< XNamed > xOriginal;
 
    Reference< XPropertySet > xDimProps(rDim, UNO_QUERY);
    if ( xDimProps.is() )
    {
        try
        {
            Any aAny = xDimProps->getPropertyValue(SC_UNO_DP_ORIGINAL);
            aAny >>= xOriginal;
        }
        catch( Exception& )
        {
        }
    }
 
    if ( !xOriginal.is() )
        xOriginal = rDim;
 
    return xOriginal->getName();
}
 
void SAL_CALL ScDataPilotTablesObj::insertNewByName( const OUString& aNewName,
    const CellAddress& aOutputAddress,
    const Reference<XDataPilotDescriptor>& xDescriptor )
{
    SolarMutexGuard aGuard;
    if (!xDescriptor.is()) return;
 
    if ( !aNewName.isEmpty() && hasByName( aNewName ) )
        throw IllegalArgumentException("Name \"" + aNewName + "\" already exists", getXWeak(), 0);
 
    if (!pDocShell)
        throw RuntimeException(u"DocShell is null"_ustr, getXWeak());
 
    auto pImp = dynamic_cast<ScDataPilotDescriptorBase*>( xDescriptor.get() );
    if (!pImp)
        throw RuntimeException(u"Failed to get ScDataPilotDescriptor"_ustr, getXWeak());
 
    ScDPObject* pNewObj = pImp->GetDPObject();
    if (!pNewObj)
        throw RuntimeException(u"Failed to get DPObject"_ustr, getXWeak());
 
    ScRange aOutputRange(static_cast<SCCOL>(aOutputAddress.Column), static_cast<SCROW>(aOutputAddress.Row), static_cast<SCTAB>(aOutputAddress.Sheet),
                        static_cast<SCCOL>(aOutputAddress.Column), static_cast<SCROW>(aOutputAddress.Row), static_cast<SCTAB>(aOutputAddress.Sheet));
    pNewObj->SetOutRange(aOutputRange);
    OUString aName = aNewName;
    if (aName.isEmpty())
        aName = lcl_CreatePivotName( pDocShell );
    pNewObj->SetName(aName);
    OUString aTag = xDescriptor->getTag();
    pNewObj->SetTag(aTag);
 
    // todo: handle double fields (for more information see ScDPObject)
 
    ScDBDocFunc aFunc(*pDocShell);
    if (!aFunc.CreatePivotTable(*pNewObj, true, true))
        throw RuntimeException(u"Failed to create pivot table"_ustr, getXWeak());
}
 
void SAL_CALL ScDataPilotTablesObj::removeByName( const OUString& aName )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = lcl_GetDPObject( pDocShell, nTab, aName );
    if (!pDPObj || !pDocShell)
        throw RuntimeException();       // no other exceptions specified
 
    ScDBDocFunc aFunc(*pDocShell);
    aFunc.RemovePivotTable(*pDPObj, true, true);  // remove - incl. undo etc.
 
}
 
// XEnumerationAccess
 
Reference< XEnumeration > SAL_CALL ScDataPilotTablesObj::createEnumeration()
{
    SolarMutexGuard aGuard;
    return new ScIndexEnumeration(this, u"com.sun.star.sheet.DataPilotTablesEnumeration"_ustr);
}
 
// XIndexAccess
 
sal_Int32 SAL_CALL ScDataPilotTablesObj::getCount()
{
    SolarMutexGuard aGuard;
    if ( pDocShell )
    {
        ScDocument& rDoc = pDocShell->GetDocument();
        ScDPCollection* pColl = rDoc.GetDPCollection();
        if ( pColl )
        {
            //  count tables on this sheet
 
            sal_uInt16 nFound = 0;
            size_t nCount = pColl->GetCount();
            for (size_t i=0; i<nCount; ++i)
            {
                ScDPObject& rDPObj = (*pColl)[i];
                if ( rDPObj.GetOutRange().aStart.Tab() == nTab )
                    ++nFound;
            }
            return nFound;
        }
    }
 
    return 0;
}
 
Any SAL_CALL ScDataPilotTablesObj::getByIndex( sal_Int32 nIndex )
{
    SolarMutexGuard aGuard;
    Reference<XDataPilotTable2> xTable(GetObjectByIndex_Impl(nIndex));
    if (!xTable.is())
        throw IndexOutOfBoundsException();
    return Any( xTable );
}
 
uno::Type SAL_CALL ScDataPilotTablesObj::getElementType()
{
    return cppu::UnoType<XDataPilotTable2>::get();
}
 
sal_Bool SAL_CALL ScDataPilotTablesObj::hasElements()
{
    SolarMutexGuard aGuard;
    return ( getCount() != 0 );
}
 
// XNameAccess
 
Any SAL_CALL ScDataPilotTablesObj::getByName( const OUString& aName )
{
    SolarMutexGuard aGuard;
    Reference<XDataPilotTable2> xTable(GetObjectByName_Impl(aName));
    if (!xTable.is())
        throw NoSuchElementException();
    return Any( xTable );
}
 
Sequence<OUString> SAL_CALL ScDataPilotTablesObj::getElementNames()
{
    SolarMutexGuard aGuard;
    if (pDocShell)
    {
        ScDocument& rDoc = pDocShell->GetDocument();
        ScDPCollection* pColl = rDoc.GetDPCollection();
        if ( pColl )
        {
            //  count tables on this sheet
 
            sal_uInt16 nFound = 0;
            size_t nCount = pColl->GetCount();
            size_t i;
            for (i=0; i<nCount; ++i)
            {
                ScDPObject& rDPObj = (*pColl)[i];
                if ( rDPObj.GetOutRange().aStart.Tab() == nTab )
                    ++nFound;
            }
 
            sal_uInt16 nPos = 0;
            Sequence<OUString> aSeq(nFound);
            OUString* pAry = aSeq.getArray();
            for (i=0; i<nCount; ++i)
            {
                ScDPObject& rDPObj = (*pColl)[i];
                if ( rDPObj.GetOutRange().aStart.Tab() == nTab )
                    pAry[nPos++] = rDPObj.GetName();
            }
 
            return aSeq;
        }
    }
    return {};
}
 
sal_Bool SAL_CALL ScDataPilotTablesObj::hasByName( const OUString& aName )
{
    SolarMutexGuard aGuard;
    if (pDocShell)
    {
        ScDocument& rDoc = pDocShell->GetDocument();
        ScDPCollection* pColl = rDoc.GetDPCollection();
        if ( pColl )
        {
            size_t nCount = pColl->GetCount();
            for (size_t i=0; i<nCount; ++i)
            {
                ScDPObject& rDPObj = (*pColl)[i];
                if ( rDPObj.GetOutRange().aStart.Tab() == nTab &&
                     rDPObj.GetName() == aName )
                    return true;
            }
        }
    }
    return false;
}
 
ScDataPilotDescriptorBase::ScDataPilotDescriptorBase(ScDocShell& rDocSh) :
    maPropSet( lcl_GetDataPilotDescriptorBaseMap() ),
    pDocShell( &rDocSh )
{
    pDocShell->GetDocument().AddUnoObject(*this);
}
 
ScDataPilotDescriptorBase::~ScDataPilotDescriptorBase()
{
    SolarMutexGuard g;
 
    if (pDocShell)
        pDocShell->GetDocument().RemoveUnoObject(*this);
}
 
void ScDataPilotDescriptorBase::Notify( SfxBroadcaster&, const SfxHint& rHint )
{
    //! update of references ?
 
    if ( rHint.GetId() == SfxHintId::Dying )
    {
        pDocShell = nullptr;       // became invalid
    }
}
 
// XDataPilotDescriptor
 
CellRangeAddress SAL_CALL ScDataPilotDescriptorBase::getSourceRange()
{
    SolarMutexGuard aGuard;
 
    ScDPObject* pDPObject(GetDPObject());
    if (!pDPObject)
        throw RuntimeException(u"Failed to get DPObject"_ustr, getXWeak());
 
    CellRangeAddress aRet;
    if (pDPObject->IsSheetData())
        ScUnoConversion::FillApiRange( aRet, pDPObject->GetSheetDesc()->GetSourceRange() );
    return aRet;
}
 
void SAL_CALL ScDataPilotDescriptorBase::setSourceRange( const CellRangeAddress& aSourceRange )
{
    SolarMutexGuard aGuard;
 
    ScDPObject* pDPObject = GetDPObject();
    if (!pDPObject)
        throw RuntimeException(u"Failed to get DPObject"_ustr, getXWeak());
 
    ScSheetSourceDesc aSheetDesc(&pDocShell->GetDocument());
    if (pDPObject->IsSheetData())
        aSheetDesc = *pDPObject->GetSheetDesc();
 
    ScRange aRange;
    ScUnoConversion::FillScRange(aRange, aSourceRange);
    aSheetDesc.SetSourceRange(aRange);
    pDPObject->SetSheetDesc( aSheetDesc );
    SetDPObject( pDPObject );
}
 
Reference<XSheetFilterDescriptor> SAL_CALL ScDataPilotDescriptorBase::getFilterDescriptor()
{
    SolarMutexGuard aGuard;
    return new ScDataPilotFilterDescriptor( pDocShell, this );
}
 
Reference<XIndexAccess> SAL_CALL ScDataPilotDescriptorBase::getDataPilotFields()
{
    SolarMutexGuard aGuard;
    return new ScDataPilotFieldsObj( *this );
}
 
Reference<XIndexAccess> SAL_CALL ScDataPilotDescriptorBase::getColumnFields()
{
    SolarMutexGuard aGuard;
    return new ScDataPilotFieldsObj( *this, DataPilotFieldOrientation_COLUMN );
}
 
Reference<XIndexAccess> SAL_CALL ScDataPilotDescriptorBase::getRowFields()
{
    SolarMutexGuard aGuard;
    return new ScDataPilotFieldsObj( *this, DataPilotFieldOrientation_ROW );
}
 
Reference<XIndexAccess> SAL_CALL ScDataPilotDescriptorBase::getPageFields()
{
    SolarMutexGuard aGuard;
    return new ScDataPilotFieldsObj( *this, DataPilotFieldOrientation_PAGE );
}
 
Reference<XIndexAccess> SAL_CALL ScDataPilotDescriptorBase::getDataFields()
{
    SolarMutexGuard aGuard;
    return new ScDataPilotFieldsObj( *this, DataPilotFieldOrientation_DATA );
}
 
Reference<XIndexAccess> SAL_CALL ScDataPilotDescriptorBase::getHiddenFields()
{
    SolarMutexGuard aGuard;
    return new ScDataPilotFieldsObj( *this, DataPilotFieldOrientation_HIDDEN );
}
 
// XPropertySet
Reference< XPropertySetInfo > SAL_CALL ScDataPilotDescriptorBase::getPropertySetInfo(  )
{
    SolarMutexGuard aGuard;
    static Reference<XPropertySetInfo> aRef =
        new SfxItemPropertySetInfo( maPropSet.getPropertyMap() );
    return aRef;
}
 
void SAL_CALL ScDataPilotDescriptorBase::setPropertyValue( const OUString& aPropertyName, const Any& aValue )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObject = GetDPObject();
    if (!pDPObject)
        return;
 
    ScDPSaveData* pOldData = pDPObject->GetSaveData();
    OSL_ENSURE(pOldData, "Here should be a SaveData");
    if ( pOldData )
    {
        ScDPSaveData aNewData( *pOldData );
 
        if ( aPropertyName == SC_UNO_DP_COLGRAND )
        {
            aNewData.SetColumnGrand(::cppu::any2bool( aValue ));
        }
        else if ( aPropertyName == SC_UNO_DP_IGNORE_EMPTYROWS )
        {
            aNewData.SetIgnoreEmptyRows(::cppu::any2bool( aValue ));
        }
        else if ( aPropertyName == SC_UNO_DP_REPEATEMPTY )
        {
            aNewData.SetRepeatIfEmpty(::cppu::any2bool( aValue ));
        }
        else if ( aPropertyName == SC_UNO_DP_ROWGRAND )
        {
            aNewData.SetRowGrand(::cppu::any2bool( aValue ));
        }
        else if ( aPropertyName == SC_UNO_DP_SHOWFILTER )
        {
            aNewData.SetFilterButton(::cppu::any2bool( aValue ));
        }
        else if ( aPropertyName == SC_UNO_DP_DRILLDOWN )
        {
            aNewData.SetDrillDown(::cppu::any2bool( aValue ));
        }
        else if ( aPropertyName == SC_UNO_DP_GRANDTOTAL_NAME )
        {
            OUString aStrVal;
            if ( aValue >>= aStrVal )
                aNewData.SetGrandTotalName(aStrVal);
        }
        else if ( aPropertyName == SC_UNO_DP_IMPORTDESC )
        {
            uno::Sequence<beans::PropertyValue> aArgSeq;
            if ( aValue >>= aArgSeq )
            {
                ScImportSourceDesc aImportDesc(&pDocShell->GetDocument());
 
                const ScImportSourceDesc* pOldDesc = pDPObject->GetImportSourceDesc();
                if (pOldDesc)
                    aImportDesc = *pOldDesc;
 
                ScImportParam aParam;
                ScImportDescriptor::FillImportParam( aParam, aArgSeq );
 
                sheet::DataImportMode nNewType = sheet::DataImportMode_NONE;
                if ( aParam.bImport )
                {
                    if ( aParam.bSql )
                        nNewType = sheet::DataImportMode_SQL;
                    else if ( aParam.nType == ScDbQuery )
                        nNewType = sheet::DataImportMode_QUERY;
                    else
                        nNewType = sheet::DataImportMode_TABLE;
                }
                aImportDesc.nType   = nNewType;
                aImportDesc.aDBName = aParam.aDBName;
                aImportDesc.aObject = aParam.aStatement;
                aImportDesc.bNative = aParam.bNative;
 
                pDPObject->SetImportDesc( aImportDesc );
            }
        }
        else if ( aPropertyName == SC_UNO_DP_SOURCESERVICE )
        {
            OUString aStrVal;
            if ( aValue >>= aStrVal )
            {
                ScDPServiceDesc aServiceDesc(u""_ustr, u""_ustr, u""_ustr, u""_ustr, u""_ustr);
 
                const ScDPServiceDesc* pOldDesc = pDPObject->GetDPServiceDesc();
                if (pOldDesc)
                    aServiceDesc = *pOldDesc;
 
                aServiceDesc.aServiceName = aStrVal;
 
                pDPObject->SetServiceData( aServiceDesc );
            }
        }
        else if ( aPropertyName == SC_UNO_DP_SERVICEARG )
        {
            uno::Sequence<beans::PropertyValue> aArgSeq;
            if ( aValue >>= aArgSeq )
            {
                ScDPServiceDesc aServiceDesc(u""_ustr, u""_ustr, u""_ustr, u""_ustr, u""_ustr);
 
                const ScDPServiceDesc* pOldDesc = pDPObject->GetDPServiceDesc();
                if (pOldDesc)
                    aServiceDesc = *pOldDesc;
 
                OUString aStrVal;
                for (const beans::PropertyValue& rProp : aArgSeq)
                {
                    OUString aPropName(rProp.Name);
 
                    if (aPropName == SC_UNO_DP_SOURCENAME)
                    {
                        if ( rProp.Value >>= aStrVal )
                            aServiceDesc.aParSource = aStrVal;
                    }
                    else if (aPropName == SC_UNO_DP_OBJECTNAME)
                    {
                        if ( rProp.Value >>= aStrVal )
                            aServiceDesc.aParName = aStrVal;
                    }
                    else if (aPropName == SC_UNO_DP_USERNAME)
                    {
                        if ( rProp.Value >>= aStrVal )
                            aServiceDesc.aParUser = aStrVal;
                    }
                    else if (aPropName == SC_UNO_DP_PASSWORD)
                    {
                        if ( rProp.Value >>= aStrVal )
                            aServiceDesc.aParPass = aStrVal;
                    }
                }
 
                pDPObject->SetServiceData( aServiceDesc );
            }
        }
        else
            throw UnknownPropertyException(aPropertyName);
 
        pDPObject->SetSaveData( aNewData );
    }
 
    SetDPObject(pDPObject);
}
 
Any SAL_CALL ScDataPilotDescriptorBase::getPropertyValue( const OUString& aPropertyName )
{
    SolarMutexGuard aGuard;
    Any aRet;
 
    ScDPObject* pDPObject(GetDPObject());
    if (pDPObject)
    {
        ScDPSaveData* pOldData = pDPObject->GetSaveData();
        OSL_ENSURE(pOldData, "Here should be a SaveData");
        if ( pOldData )
        {
            ScDPSaveData aNewData( *pOldData );
 
            if ( aPropertyName == SC_UNO_DP_COLGRAND )
            {
                aRet <<= aNewData.GetColumnGrand();
            }
            else if ( aPropertyName == SC_UNO_DP_IGNORE_EMPTYROWS )
            {
                aRet <<= aNewData.GetIgnoreEmptyRows();
            }
            else if ( aPropertyName == SC_UNO_DP_REPEATEMPTY )
            {
                aRet <<= aNewData.GetRepeatIfEmpty();
            }
            else if ( aPropertyName == SC_UNO_DP_ROWGRAND )
            {
                aRet <<= aNewData.GetRowGrand();
            }
            else if ( aPropertyName == SC_UNO_DP_SHOWFILTER )
            {
                aRet <<= aNewData.GetFilterButton();
            }
            else if ( aPropertyName == SC_UNO_DP_DRILLDOWN )
            {
                aRet <<= aNewData.GetDrillDown();
            }
            else if ( aPropertyName == SC_UNO_DP_GRANDTOTAL_NAME )
            {
                const std::optional<OUString> & pGrandTotalName = aNewData.GetGrandTotalName();
                if (pGrandTotalName)
                    aRet <<= *pGrandTotalName;      // same behavior as in ScDPSource
            }
            else if ( aPropertyName == SC_UNO_DP_IMPORTDESC )
            {
                const ScImportSourceDesc* pImportDesc = pDPObject->GetImportSourceDesc();
                if ( pImportDesc )
                {
                    // fill ScImportParam so ScImportDescriptor::FillProperties can be used
                    ScImportParam aParam;
                    aParam.bImport    = ( pImportDesc->nType != sheet::DataImportMode_NONE );
                    aParam.aDBName    = pImportDesc->aDBName;
                    aParam.aStatement = pImportDesc->aObject;
                    aParam.bNative    = pImportDesc->bNative;
                    aParam.bSql       = ( pImportDesc->nType == sheet::DataImportMode_SQL );
                    aParam.nType      = static_cast<sal_uInt8>(( pImportDesc->nType == sheet::DataImportMode_QUERY ) ? ScDbQuery : ScDbTable);
 
                    uno::Sequence<beans::PropertyValue> aSeq( ScImportDescriptor::GetPropertyCount() );
                    ScImportDescriptor::FillProperties( aSeq, aParam );
                    aRet <<= aSeq;
                }
                else
                {
                    // empty sequence
                    uno::Sequence<beans::PropertyValue> aEmpty(0);
                    aRet <<= aEmpty;
                }
            }
            else if ( aPropertyName == SC_UNO_DP_SOURCESERVICE )
            {
                OUString aServiceName;
                const ScDPServiceDesc* pServiceDesc = pDPObject->GetDPServiceDesc();
                if (pServiceDesc)
                    aServiceName = pServiceDesc->aServiceName;
                aRet <<= aServiceName;      // empty string if no ServiceDesc set
            }
            else if ( aPropertyName == SC_UNO_DP_SERVICEARG )
            {
                const ScDPServiceDesc* pServiceDesc = pDPObject->GetDPServiceDesc();
                if (pServiceDesc)
                {
                    uno::Sequence<beans::PropertyValue> aSeq( comphelper::InitPropertySequence({
                            { SC_UNO_DP_SOURCENAME, Any(pServiceDesc->aParSource) },
                            { SC_UNO_DP_OBJECTNAME, Any(pServiceDesc->aParName) },
                            { SC_UNO_DP_USERNAME, Any(pServiceDesc->aParUser) },
                            { SC_UNO_DP_PASSWORD, Any(pServiceDesc->aParPass) }
                        }));
                    aRet <<= aSeq;
                }
                else
                {
                    // empty sequence
                    uno::Sequence<beans::PropertyValue> aEmpty;
                    aRet <<= aEmpty;
                }
            }
            else
                throw UnknownPropertyException(aPropertyName);
        }
    }
 
    return aRet;
}
 
void SAL_CALL ScDataPilotDescriptorBase::addPropertyChangeListener(
        const OUString& /* aPropertyName */, const Reference<XPropertyChangeListener >& /* xListener */ )
{
}
 
void SAL_CALL ScDataPilotDescriptorBase::removePropertyChangeListener(
        const OUString& /* aPropertyName */, const Reference<XPropertyChangeListener >& /* aListener */ )
{
}
 
void SAL_CALL ScDataPilotDescriptorBase::addVetoableChangeListener(
        const OUString& /* PropertyName */, const Reference<XVetoableChangeListener >& /* aListener */ )
{
}
 
void SAL_CALL ScDataPilotDescriptorBase::removeVetoableChangeListener(
        const OUString& /* PropertyName */, const Reference<XVetoableChangeListener >& /* aListener */ )
{
}
 
// XDataPilotDataLayoutFieldSupplier
 
Reference< XDataPilotField > SAL_CALL ScDataPilotDescriptorBase::getDataLayoutField()
{
    SolarMutexGuard aGuard;
    if( ScDPObject* pDPObject = GetDPObject() )
    {
        if( ScDPSaveData* pSaveData = pDPObject->GetSaveData() )
        {
            if( pSaveData->GetDataLayoutDimension() )
            {
                ScFieldIdentifier aFieldId( SC_DATALAYOUT_NAME, true );
                return new ScDataPilotFieldObj( *this, aFieldId );
            }
        }
    }
    return nullptr;
}
 
ScDataPilotTableObj::ScDataPilotTableObj(ScDocShell& rDocSh, SCTAB nT, OUString aN) :
    ScDataPilotDescriptorBase( rDocSh ),
    nTab( nT ),
    aName(std::move( aN )),
    aModifyListeners( 0 )
{
}
 
ScDataPilotTableObj::~ScDataPilotTableObj()
{
}
 
Any SAL_CALL ScDataPilotTableObj::queryInterface( const uno::Type& rType )
{
    // since we manually do resolve the query for XDataPilotTable2
    // we also need to do the same for XDataPilotTable
    uno::Any aReturn = ::cppu::queryInterface(rType,
                    static_cast<XDataPilotTable*>(this),
                    static_cast<XDataPilotTable2*>(this),
                    static_cast<XModifyBroadcaster*>(this));
    if ( aReturn.hasValue() )
        return aReturn;
 
    return ScDataPilotDescriptorBase::queryInterface( rType );
}
 
void SAL_CALL ScDataPilotTableObj::acquire() noexcept
{
    ScDataPilotDescriptorBase::acquire();
}
 
void SAL_CALL ScDataPilotTableObj::release() noexcept
{
    ScDataPilotDescriptorBase::release();
}
 
Sequence< uno::Type > SAL_CALL ScDataPilotTableObj::getTypes()
{
    return comphelper::concatSequences(
        ScDataPilotDescriptorBase::getTypes(),
        Sequence< uno::Type >
        {
            cppu::UnoType<XDataPilotTable2>::get(),
            cppu::UnoType<XModifyBroadcaster>::get()
        } );
}
 
Sequence<sal_Int8> SAL_CALL ScDataPilotTableObj::getImplementationId()
{
    return css::uno::Sequence<sal_Int8>();
}
 
ScDPObject* ScDataPilotTableObj::GetDPObject() const
{
    return lcl_GetDPObject(GetDocShell(), nTab, aName);
}
 
void ScDataPilotTableObj::SetDPObject( ScDPObject* pDPObject )
{
    ScDocShell* pDocSh = GetDocShell();
    ScDPObject* pDPObj = lcl_GetDPObject(pDocSh, nTab, aName);
    if ( pDPObj && pDocSh )
    {
        ScDBDocFunc aFunc(*pDocSh);
        aFunc.DataPilotUpdate( pDPObj, pDPObject, true, true );
    }
}
 
// "rest of XDataPilotDescriptor"
 
OUString SAL_CALL ScDataPilotTableObj::getName()
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = lcl_GetDPObject(GetDocShell(), nTab, aName);
    if (pDPObj)
        return pDPObj->GetName();
    return OUString();
}
 
void SAL_CALL ScDataPilotTableObj::setName( const OUString& aNewName )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = lcl_GetDPObject(GetDocShell(), nTab, aName);
    if (pDPObj)
    {
        //! test for existing names !!!
 
        pDPObj->SetName( aNewName );     //! Undo - DBDocFunc ???
        aName = aNewName;
 
        //  DataPilotUpdate would do too much (output table is not changed)
        GetDocShell()->SetDocumentModified();
    }
}
 
OUString SAL_CALL ScDataPilotTableObj::getTag()
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = lcl_GetDPObject(GetDocShell(), nTab, aName);
    if (pDPObj)
        return pDPObj->GetTag();
    return OUString();
}
 
void SAL_CALL ScDataPilotTableObj::setTag( const OUString& aNewTag )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = lcl_GetDPObject(GetDocShell(), nTab, aName);
    if (pDPObj)
    {
        pDPObj->SetTag( aNewTag );      //! Undo - DBDocFunc ???
 
        //  DataPilotUpdate would do too much (output table is not changed)
        GetDocShell()->SetDocumentModified();
    }
}
 
// XDataPilotTable
 
CellRangeAddress SAL_CALL ScDataPilotTableObj::getOutputRange()
{
    SolarMutexGuard aGuard;
    CellRangeAddress aRet;
    ScDPObject* pDPObj = lcl_GetDPObject(GetDocShell(), nTab, aName);
    if (pDPObj)
    {
        ScRange aRange(pDPObj->GetOutRange());
        aRet.Sheet       = aRange.aStart.Tab();
        aRet.StartColumn = aRange.aStart.Col();
        aRet.StartRow    = aRange.aStart.Row();
        aRet.EndColumn   = aRange.aEnd.Col();
        aRet.EndRow      = aRange.aEnd.Row();
    }
    return aRet;
}
 
void SAL_CALL ScDataPilotTableObj::refresh()
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = lcl_GetDPObject(GetDocShell(), nTab, aName);
    if (pDPObj)
    {
        ScDBDocFunc aFunc(*GetDocShell());
        aFunc.RefreshPivotTables(pDPObj, true);
    }
}
 
Sequence< Sequence<Any> > SAL_CALL ScDataPilotTableObj::getDrillDownData(const CellAddress& aAddr)
{
    SolarMutexGuard aGuard;
    Sequence< Sequence<Any> > aTabData;
    ScAddress aAddr2(static_cast<SCCOL>(aAddr.Column), static_cast<SCROW>(aAddr.Row), aAddr.Sheet);
    ScDPObject* pObj = GetDPObject();
    if (!pObj)
        throw RuntimeException(u"Failed to get DPObject"_ustr, getXWeak());
 
    pObj->GetDrillDownData(aAddr2, aTabData);
    return aTabData;
}
 
DataPilotTablePositionData SAL_CALL ScDataPilotTableObj::getPositionData(const CellAddress& aAddr)
{
    SolarMutexGuard aGuard;
    DataPilotTablePositionData aPosData;
    ScAddress aAddr2(static_cast<SCCOL>(aAddr.Column), static_cast<SCROW>(aAddr.Row), aAddr.Sheet);
    ScDPObject* pObj = GetDPObject();
    if (!pObj)
        throw RuntimeException(u"Failed to get DPObject"_ustr, getXWeak());
 
    pObj->GetPositionData(aAddr2, aPosData);
    return aPosData;
}
 
void SAL_CALL ScDataPilotTableObj::insertDrillDownSheet(const CellAddress& aAddr)
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = GetDPObject();
    if (!pDPObj)
        throw RuntimeException(u"Failed to get DPObject"_ustr, getXWeak());
    ScTabViewShell* pViewSh = GetDocShell()->GetBestViewShell();
    if (!pViewSh)
        throw RuntimeException(u"Failed to get ViewShell"_ustr, getXWeak());
 
    Sequence<DataPilotFieldFilter> aFilters;
    pDPObj->GetDataFieldPositionData(
        ScAddress(static_cast<SCCOL>(aAddr.Column), static_cast<SCROW>(aAddr.Row), aAddr.Sheet), aFilters);
    pViewSh->ShowDataPilotSourceData(*pDPObj, aFilters);
}
 
CellRangeAddress SAL_CALL ScDataPilotTableObj::getOutputRangeByType( sal_Int32 nType )
{
    SolarMutexGuard aGuard;
    if (nType < 0 || nType > DataPilotOutputRangeType::RESULT)
        throw IllegalArgumentException("nType must be between 0 and " +
                OUString::number(DataPilotOutputRangeType::RESULT) + ", got " + OUString::number(nType),
                getXWeak(), 0);
 
    CellRangeAddress aRet;
    if (ScDPObject* pDPObj = lcl_GetDPObject(GetDocShell(), nTab, aName))
        ScUnoConversion::FillApiRange( aRet, pDPObj->GetOutputRangeByType( nType ) );
    return aRet;
}
 
void SAL_CALL ScDataPilotTableObj::addModifyListener( const uno::Reference<util::XModifyListener>& aListener )
{
    SolarMutexGuard aGuard;
 
    aModifyListeners.emplace_back( aListener );
 
    if ( aModifyListeners.size() == 1 )
    {
        acquire();  // don't lose this object (one ref for all listeners)
    }
}
 
void SAL_CALL ScDataPilotTableObj::removeModifyListener( const uno::Reference<util::XModifyListener>& aListener )
{
    SolarMutexGuard aGuard;
 
    rtl::Reference<ScDataPilotTableObj> xSelfHold(this); // in case the listeners have the last ref
 
    sal_uInt16 nCount = aModifyListeners.size();
    for ( sal_uInt16 n=nCount; n--; )
    {
        uno::Reference<util::XModifyListener>& rObj = aModifyListeners[n];
        if ( rObj == aListener )
        {
            aModifyListeners.erase( aModifyListeners.begin() + n );
 
            if ( aModifyListeners.empty() )
            {
                release();      // release the ref for the listeners
            }
 
            break;
        }
    }
}
 
void ScDataPilotTableObj::Notify( SfxBroadcaster& rBC, const SfxHint& rHint )
{
    if ( rHint.GetId() == SfxHintId::ScDataPilotModified )
    {
        auto pDataPilotHint = static_cast<const ScDataPilotModifiedHint*>(&rHint);
        if (pDataPilotHint->GetName() == aName)
            Refreshed_Impl();
    }
    else if ( rHint.GetId() == SfxHintId::ScUpdateRef )
    {
        auto pRefHint = static_cast<const ScUpdateRefHint*>(&rHint);
        ScRange aRange( 0, 0, nTab );
        ScRangeList aRanges( aRange );
        if ( aRanges.UpdateReference( pRefHint->GetMode(), &GetDocShell()->GetDocument(), pRefHint->GetRange(),
                 pRefHint->GetDx(), pRefHint->GetDy(), pRefHint->GetDz() ) &&
             aRanges.size() == 1 )
        {
            nTab = aRanges.front().aStart.Tab();
        }
    }
 
    ScDataPilotDescriptorBase::Notify( rBC, rHint );
}
 
void ScDataPilotTableObj::Refreshed_Impl()
{
    lang::EventObject aEvent;
    aEvent.Source = getXWeak();
 
    // the EventObject holds a Ref to this object until after the listener calls
 
    ScDocument& rDoc = GetDocShell()->GetDocument();
    for (const uno::Reference<util::XModifyListener> & xModifyListener : aModifyListeners)
        rDoc.AddUnoListenerCall( xModifyListener, aEvent );
}
 
ScDataPilotDescriptor::ScDataPilotDescriptor(ScDocShell& rDocSh) :
    ScDataPilotDescriptorBase( rDocSh ),
    mpDPObject(new ScDPObject(&rDocSh.GetDocument()))
{
    ScDPSaveData aSaveData;
    // set defaults like in ScPivotParam constructor
    aSaveData.SetColumnGrand( true );
    aSaveData.SetRowGrand( true );
    aSaveData.SetIgnoreEmptyRows( false );
    aSaveData.SetRepeatIfEmpty( false );
    mpDPObject->SetSaveData(aSaveData);
    ScSheetSourceDesc aSheetDesc(&rDocSh.GetDocument());
    mpDPObject->SetSheetDesc(aSheetDesc);
}
 
ScDataPilotDescriptor::~ScDataPilotDescriptor()
{
}
 
ScDPObject* ScDataPilotDescriptor::GetDPObject() const
{
    return mpDPObject.get();
}
 
void ScDataPilotDescriptor::SetDPObject( ScDPObject* pDPObject )
{
    if (mpDPObject.get() != pDPObject)
    {
        mpDPObject.reset( pDPObject );
        OSL_FAIL("replace DPObject should not happen");
    }
}
 
// "rest of XDataPilotDescriptor"
 
OUString SAL_CALL ScDataPilotDescriptor::getName()
{
    SolarMutexGuard aGuard;
    return mpDPObject->GetName();
}
 
void SAL_CALL ScDataPilotDescriptor::setName( const OUString& aNewName )
{
    SolarMutexGuard aGuard;
    mpDPObject->SetName( aNewName );
}
 
OUString SAL_CALL ScDataPilotDescriptor::getTag()
{
    SolarMutexGuard aGuard;
    return mpDPObject->GetTag();
}
 
void SAL_CALL ScDataPilotDescriptor::setTag( const OUString& aNewTag )
{
    SolarMutexGuard aGuard;
    mpDPObject->SetTag( aNewTag );
}
 
ScDataPilotChildObjBase::ScDataPilotChildObjBase( ScDataPilotDescriptorBase& rParent ) :
    mxParent( &rParent )
{
}
 
ScDataPilotChildObjBase::ScDataPilotChildObjBase( ScDataPilotDescriptorBase& rParent, ScFieldIdentifier aFieldId ) :
    mxParent( &rParent ),
    maFieldId(std::move( aFieldId ))
{
}
 
ScDataPilotChildObjBase::~ScDataPilotChildObjBase()
{
}
 
ScDPObject* ScDataPilotChildObjBase::GetDPObject() const
{
    return mxParent->GetDPObject();
}
 
void ScDataPilotChildObjBase::SetDPObject( ScDPObject* pDPObject )
{
    mxParent->SetDPObject( pDPObject );
}
 
ScDPSaveDimension* ScDataPilotChildObjBase::GetDPDimension( ScDPObject** ppDPObject ) const
{
    if( ScDPObject* pDPObj = GetDPObject() )
    {
        if( ppDPObject ) *ppDPObject = pDPObj;
        if( ScDPSaveData* pSaveData = pDPObj->GetSaveData() )
        {
            if( maFieldId.mbDataLayout )
                return pSaveData->GetDataLayoutDimension();
 
            if( maFieldId.mnFieldIdx == 0 )
                return pSaveData->GetDimensionByName( maFieldId.maFieldName );
 
            // find dimension with specified index (search in duplicated dimensions)
            const ScDPSaveData::DimsType& rDims = pSaveData->GetDimensions();
 
            sal_Int32 nFoundIdx = 0;
            for (auto const& it : rDims)
            {
                if (it->IsDataLayout())
                    continue;
 
                OUString aSrcName = ScDPUtil::getSourceDimensionName(it->GetName());
                if (aSrcName == maFieldId.maFieldName)
                {
                    if( nFoundIdx == maFieldId.mnFieldIdx )
                        return it.get();
                    ++nFoundIdx;
                }
            }
        }
    }
    return nullptr;
}
 
sal_Int32 ScDataPilotChildObjBase::GetMemberCount() const
{
    sal_Int32 nRet = 0;
    Reference<XNameAccess> xMembersNA = GetMembers();
    if (xMembersNA.is())
    {
        Reference< XIndexAccess > xMembersIA( new ScNameToIndexAccess( xMembersNA ) );
        nRet = xMembersIA->getCount();
    }
    return nRet;
}
 
Reference< XMembersAccess > ScDataPilotChildObjBase::GetMembers() const
{
    Reference< XMembersAccess > xMembersNA;
    if( ScDPObject* pDPObj = GetDPObject() )
        pDPObj->GetMembersNA( lcl_GetObjectIndex( pDPObj, maFieldId ), xMembersNA );
    return xMembersNA;
}
 
ScDocShell* ScDataPilotChildObjBase::GetDocShell() const
{
    return mxParent->GetDocShell();
}
 
ScDataPilotFieldsObj::ScDataPilotFieldsObj( ScDataPilotDescriptorBase& rParent ) :
    ScDataPilotChildObjBase( rParent )
{
}
 
ScDataPilotFieldsObj::ScDataPilotFieldsObj( ScDataPilotDescriptorBase& rParent, DataPilotFieldOrientation eOrient ) :
    ScDataPilotChildObjBase( rParent ),
    maOrient( eOrient )
{
}
 
ScDataPilotFieldsObj::~ScDataPilotFieldsObj()
{
}
 
static sal_Int32 lcl_GetFieldCount( const Reference<XDimensionsSupplier>& rSource, const Any& rOrient )
{
    if (!rSource.is())
        throw NullPointerException();
 
    sal_Int32 nRet = 0;
 
    Reference<XNameAccess> xDimsName(rSource->getDimensions());
    Reference<XIndexAccess> xIntDims(new ScNameToIndexAccess( xDimsName ));
    sal_Int32 nIntCount = xIntDims->getCount();
    for (sal_Int32 i = 0; i < nIntCount; ++i)
    {
        Reference<XPropertySet> xDim(xIntDims->getByIndex(i), UNO_QUERY);
        const bool bMatch = xDim
                            && (rOrient.hasValue()
                                    // all fields of the specified orientation, including duplicated
                                    ? (xDim->getPropertyValue(SC_UNO_DP_ORIENTATION) == rOrient)
                                    // count all non-duplicated fields
                                    : !lcl_IsDuplicated(xDim));
        if (bMatch)
            ++nRet;
    }
 
    return nRet;
}
 
static bool lcl_GetFieldDataByIndex( const Reference<XDimensionsSupplier>& rSource,
                                const Any& rOrient, SCSIZE nIndex, ScFieldIdentifier& rFieldId )
{
    if (!rSource.is())
        throw NullPointerException();
 
    bool bOk = false;
    SCSIZE nPos = 0;
    sal_Int32 nDimIndex = 0;
 
    Reference<XNameAccess> xDimsName(rSource->getDimensions());
    Reference<XIndexAccess> xIntDims(new ScNameToIndexAccess( xDimsName ));
    sal_Int32 nIntCount = xIntDims->getCount();
    Reference<XPropertySet> xDim;
    for (sal_Int32 i = 0; i < nIntCount; ++i)
    {
        xDim.set(xIntDims->getByIndex(i), UNO_QUERY);
        const bool bMatch = xDim
                            && (rOrient.hasValue()
                                    ? (xDim->getPropertyValue(SC_UNO_DP_ORIENTATION) == rOrient)
                                    : !lcl_IsDuplicated(xDim));
        if (bMatch)
        {
            if (nPos == nIndex)
            {
                bOk = true;
                nDimIndex = i;
                break;
            }
            else
                ++nPos;
        }
    }
 
    if ( bOk )
    {
        xDim.set( xIntDims->getByIndex(nDimIndex), UNO_QUERY );
        Reference<XNamed> xDimName( xDim, UNO_QUERY );
        if ( xDimName.is() )
        {
            OUString sOriginalName( lcl_GetOriginalName( xDimName ) );
            rFieldId.maFieldName = sOriginalName;
            rFieldId.mbDataLayout = ScUnoHelpFunctions::GetBoolProperty( xDim,
                        SC_UNO_DP_ISDATALAYOUT );
 
            sal_Int32 nRepeat = 0;
            if ( rOrient.hasValue() && lcl_IsDuplicated( xDim ) )
            {
                // find the repeat count
                // (this relies on the original dimension always being before the duplicates)
 
                Reference<XNamed> xPrevName;
                for (sal_Int32 i = 0; i < nDimIndex; ++i)
                {
                    xPrevName.set( xIntDims->getByIndex(i), UNO_QUERY );
                    if ( xPrevName.is() && lcl_GetOriginalName( xPrevName ) == sOriginalName )
                        ++nRepeat;
                }
            }
            rFieldId.mnFieldIdx = nRepeat;
        }
        else
            bOk = false;
    }
 
    return bOk;
}
 
static bool lcl_GetFieldDataByName( ScDPObject* pDPObj, const OUString& rFieldName, ScFieldIdentifier& rFieldId )
{
    // "By name" is always the first match.
    // The name "Data" always refers to the data layout field.
    rFieldId.maFieldName = rFieldName;
    rFieldId.mnFieldIdx = 0;
    rFieldId.mbDataLayout = rFieldName == SC_DATALAYOUT_NAME;
 
    pDPObj->GetSource();    // IsDimNameInUse doesn't update source data
 
    // check if the named field exists (not for data layout)
    return rFieldId.mbDataLayout || pDPObj->IsDimNameInUse( rFieldName );
}
 
// XDataPilotFields
 
rtl::Reference<ScDataPilotFieldObj> ScDataPilotFieldsObj::GetObjectByIndex_Impl( sal_Int32 nIndex ) const
{
    if (ScDPObject* pObj = GetDPObject())
    {
        ScFieldIdentifier aFieldId;
        if (lcl_GetFieldDataByIndex( pObj->GetSource(), maOrient, nIndex, aFieldId ))
            return new ScDataPilotFieldObj( *mxParent, aFieldId, maOrient );
    }
    return nullptr;
}
 
rtl::Reference<ScDataPilotFieldObj> ScDataPilotFieldsObj::GetObjectByName_Impl(const OUString& aName) const
{
    if (ScDPObject* pDPObj = GetDPObject())
    {
        ScFieldIdentifier aFieldId;
        if (lcl_GetFieldDataByName( pDPObj, aName, aFieldId ))
            return new ScDataPilotFieldObj( *mxParent, aFieldId, maOrient );
    }
    return nullptr;
}
 
// XEnumerationAccess
 
Reference<XEnumeration> SAL_CALL ScDataPilotFieldsObj::createEnumeration()
{
    SolarMutexGuard aGuard;
    return new ScIndexEnumeration(this, u"com.sun.star.sheet.DataPilotFieldsEnumeration"_ustr);
}
 
// XIndexAccess
 
sal_Int32 SAL_CALL ScDataPilotFieldsObj::getCount()
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = GetDPObject();
    return pDPObj ? lcl_GetFieldCount( pDPObj->GetSource(), maOrient ) : 0;
}
 
Any SAL_CALL ScDataPilotFieldsObj::getByIndex( sal_Int32 nIndex )
{
    SolarMutexGuard aGuard;
    Reference< XPropertySet > xField( GetObjectByIndex_Impl( nIndex ) );
    if (!xField.is())
        throw IndexOutOfBoundsException();
    return Any( xField );
}
 
// XElementAccess
 
uno::Type SAL_CALL ScDataPilotFieldsObj::getElementType()
{
    return cppu::UnoType<XPropertySet>::get();
}
 
sal_Bool SAL_CALL ScDataPilotFieldsObj::hasElements()
{
    SolarMutexGuard aGuard;
    return ( getCount() != 0 );
}
 
// XNameAccess
 
Any SAL_CALL ScDataPilotFieldsObj::getByName( const OUString& aName )
{
    SolarMutexGuard aGuard;
    Reference<XPropertySet> xField(GetObjectByName_Impl(aName));
    if (!xField.is())
        throw NoSuchElementException();
    return Any( xField );
}
 
Sequence<OUString> SAL_CALL ScDataPilotFieldsObj::getElementNames()
{
    SolarMutexGuard aGuard;
    if (ScDPObject* pDPObj = GetDPObject())
    {
        Sequence< OUString > aSeq( lcl_GetFieldCount( pDPObj->GetSource(), maOrient ) );
        OUString* pAry = aSeq.getArray();
 
        const ScDPSaveData::DimsType& rDimensions = pDPObj->GetSaveData()->GetDimensions();
        for (auto const& it : rDimensions)
        {
            if(maOrient.hasValue() && (it->GetOrientation() == maOrient.get< DataPilotFieldOrientation >()))
            {
                *pAry = it->GetName();
                ++pAry;
            }
        }
        return aSeq;
    }
    return Sequence<OUString>();
}
 
sal_Bool SAL_CALL ScDataPilotFieldsObj::hasByName( const OUString& aName )
{
    SolarMutexGuard aGuard;
 
    return GetObjectByName_Impl(aName) != nullptr;
}
 
ScDataPilotFieldObj::ScDataPilotFieldObj(
        ScDataPilotDescriptorBase& rParent, const ScFieldIdentifier& rFieldId ) :
    ScDataPilotChildObjBase( rParent, rFieldId ),
    maPropSet( lcl_GetDataPilotFieldMap() )
{
}
 
ScDataPilotFieldObj::ScDataPilotFieldObj( ScDataPilotDescriptorBase& rParent,
        const ScFieldIdentifier& rFieldId, Any aOrient ) :
    ScDataPilotChildObjBase( rParent, rFieldId ),
    maPropSet( lcl_GetDataPilotFieldMap() ),
    maOrient(std::move( aOrient ))
{
}
 
ScDataPilotFieldObj::~ScDataPilotFieldObj()
{
}
 
// XNamed
 
OUString SAL_CALL ScDataPilotFieldObj::getName()
{
    SolarMutexGuard aGuard;
    OUString aName;
    if( ScDPSaveDimension* pDim = GetDPDimension() )
    {
        if( pDim->IsDataLayout() )
            aName = SC_DATALAYOUT_NAME;
        else
        {
            const std::optional<OUString> & pLayoutName = pDim->GetLayoutName();
            if (pLayoutName)
                aName = *pLayoutName;
            else
                aName = pDim->GetName();
        }
    }
    return aName;
}
 
void SAL_CALL ScDataPilotFieldObj::setName(const OUString& rName)
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    ScDPSaveDimension* pDim = GetDPDimension( &pDPObj );
    if( pDim && !pDim->IsDataLayout() )
    {
        pDim->SetLayoutName(rName);
        SetDPObject( pDPObj );
    }
}
 
// XPropertySet
 
Reference<XPropertySetInfo> SAL_CALL ScDataPilotFieldObj::getPropertySetInfo()
{
    SolarMutexGuard aGuard;
    static Reference<XPropertySetInfo> aRef(
        new SfxItemPropertySetInfo( maPropSet.getPropertyMap() ));
    return aRef;
}
 
void SAL_CALL ScDataPilotFieldObj::setPropertyValue( const OUString& aPropertyName, const Any& aValue )
{
    SolarMutexGuard aGuard;
    if ( aPropertyName == SC_UNONAME_FUNCTION )
    {
        // #i109350# use GetEnumFromAny because it also allows sal_Int32
        ScGeneralFunction eFunction = static_cast<ScGeneralFunction>(ScUnoHelpFunctions::GetEnumFromAny( aValue ));
        setFunction( eFunction );
    }
    else if ( aPropertyName == SC_UNONAME_FUNCTION2 )
    {
        ScGeneralFunction eFunction = static_cast<ScGeneralFunction>(ScUnoHelpFunctions::GetInt16FromAny( aValue ));
        setFunction( eFunction );
    }
    else if ( aPropertyName == SC_UNONAME_SUBTOTALS )
    {
        uno::Sequence<sheet::GeneralFunction> aSeq;
        if( aValue >>= aSeq)
        {
            std::vector< ScGeneralFunction > aSubTotals(aSeq.getLength());
            std::transform(std::cbegin(aSeq), std::cend(aSeq), aSubTotals.begin(),
                [](const sheet::GeneralFunction& rValue) -> ScGeneralFunction {
                    const int nValAsInt = static_cast<int>(rValue);
                    return static_cast<ScGeneralFunction>(nValAsInt);
                });
            setSubtotals( aSubTotals );
        }
    }
    else if ( aPropertyName == SC_UNONAME_SUBTOTALS2 )
    {
        Sequence< sal_Int16 > aSeq;
        if( aValue >>= aSeq )
        {
            std::vector< ScGeneralFunction > aSubTotals(aSeq.getLength());
            std::transform(std::cbegin(aSeq), std::cend(aSeq), aSubTotals.begin(),
                [](sal_Int16 nValue) -> ScGeneralFunction { return static_cast<ScGeneralFunction>(nValue); });
            setSubtotals( aSubTotals );
        }
    }
    else if ( aPropertyName == SC_UNONAME_ORIENT )
    {
        //! test for correct enum type?
        DataPilotFieldOrientation eOrient = static_cast<DataPilotFieldOrientation>(ScUnoHelpFunctions::GetEnumFromAny( aValue ));
        setOrientation( eOrient );
    }
    else if ( aPropertyName == SC_UNONAME_SELPAGE )
    {
        OUString sCurrentPage;
        if (aValue >>= sCurrentPage)
            setCurrentPage(sCurrentPage);
    }
    else if ( aPropertyName == SC_UNONAME_USESELPAGE )
    {
        setUseCurrentPage(cppu::any2bool(aValue));
    }
    else if ( aPropertyName == SC_UNONAME_HASAUTOSHOW )
    {
        if (!cppu::any2bool(aValue))
            setAutoShowInfo(nullptr);
    }
    else if ( aPropertyName == SC_UNONAME_AUTOSHOW )
    {
        DataPilotFieldAutoShowInfo aInfo;
        if (aValue >>= aInfo)
            setAutoShowInfo(&aInfo);
    }
    else if ( aPropertyName == SC_UNONAME_HASLAYOUTINFO )
    {
        if (!cppu::any2bool(aValue))
            setLayoutInfo(nullptr);
    }
    else if ( aPropertyName == SC_UNONAME_LAYOUTINFO )
    {
        DataPilotFieldLayoutInfo aInfo;
        if (aValue >>= aInfo)
            setLayoutInfo(&aInfo);
    }
    else if ( aPropertyName == SC_UNONAME_HASREFERENCE )
    {
        if (!cppu::any2bool(aValue))
            setReference(nullptr);
    }
    else if ( aPropertyName == SC_UNONAME_REFERENCE )
    {
        DataPilotFieldReference aRef;
        if (aValue >>= aRef)
            setReference(&aRef);
    }
    else if ( aPropertyName == SC_UNONAME_HASSORTINFO )
    {
        if (!cppu::any2bool(aValue))
            setSortInfo(nullptr);
    }
    else if ( aPropertyName == SC_UNONAME_SORTINFO )
    {
        DataPilotFieldSortInfo aInfo;
        if (aValue >>= aInfo)
            setSortInfo(&aInfo);
    }
    else if ( aPropertyName == SC_UNONAME_ISGROUP )
    {
        if (!cppu::any2bool(aValue))
            setGroupInfo(nullptr);
    }
    else if ( aPropertyName == SC_UNONAME_GROUPINFO )
    {
        DataPilotFieldGroupInfo aInfo;
        if (aValue >>= aInfo)
            setGroupInfo(&aInfo);
    }
    else if ( aPropertyName == SC_UNONAME_SHOWEMPTY )
    {
        setShowEmpty(cppu::any2bool(aValue));
    }
    else if ( aPropertyName == SC_UNONAME_REPEATITEMLABELS )
    {
        setRepeatItemLabels(cppu::any2bool(aValue));
    }
    else if (aPropertyName == SC_UNONAME_NAME)
    {
        OUString sName;
        if (aValue >>= sName)
            setName(sName);
    }
}
 
Any SAL_CALL ScDataPilotFieldObj::getPropertyValue( const OUString& aPropertyName )
{
    SolarMutexGuard aGuard;
    Any aRet;
 
    if ( aPropertyName == SC_UNONAME_FUNCTION )
    {
        sheet::GeneralFunction eVal;
        sal_Int16 nFunction = getFunction();
        if (nFunction == sheet::GeneralFunction2::MEDIAN)
        {
            eVal = sheet::GeneralFunction_NONE;
        }
        else
        {
            eVal = static_cast<sheet::GeneralFunction>(nFunction);
        }
        aRet <<= eVal;
    }
    else if ( aPropertyName == SC_UNONAME_FUNCTION2 )
        aRet <<= getFunction();
    else if ( aPropertyName == SC_UNONAME_SUBTOTALS )
    {
        const uno::Sequence<sal_Int16> aSeq = getSubtotals();
        uno::Sequence<sheet::GeneralFunction>  aNewSeq(aSeq.getLength());
        std::transform(aSeq.begin(), aSeq.end(), aNewSeq.getArray(),
            [](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_UNONAME_SUBTOTALS2 )
    {
        aRet <<= getSubtotals();
    }
    else if ( aPropertyName == SC_UNONAME_ORIENT )
        aRet <<= getOrientation();
    else if ( aPropertyName == SC_UNONAME_SELPAGE )
        aRet <<= OUString();
    else if ( aPropertyName == SC_UNONAME_USESELPAGE )
        aRet <<= false;
    else if ( aPropertyName == SC_UNONAME_HASAUTOSHOW )
        aRet <<= (getAutoShowInfo() != nullptr);
    else if ( aPropertyName == SC_UNONAME_AUTOSHOW )
    {
        const DataPilotFieldAutoShowInfo* pInfo = getAutoShowInfo();
        if (pInfo)
            aRet <<= *pInfo;
    }
    else if ( aPropertyName == SC_UNONAME_HASLAYOUTINFO )
        aRet <<= (getLayoutInfo() != nullptr);
    else if ( aPropertyName == SC_UNONAME_LAYOUTINFO )
    {
        const DataPilotFieldLayoutInfo* pInfo = getLayoutInfo();
        if (pInfo)
            aRet <<= *pInfo;
    }
    else if ( aPropertyName == SC_UNONAME_HASREFERENCE )
        aRet <<= (getReference() != nullptr);
    else if ( aPropertyName == SC_UNONAME_REFERENCE )
    {
        const DataPilotFieldReference* pRef = getReference();
        if (pRef)
            aRet <<= *pRef;
    }
    else if ( aPropertyName == SC_UNONAME_HASSORTINFO )
        aRet <<= (getSortInfo() != nullptr);
    else if ( aPropertyName == SC_UNONAME_SORTINFO )
    {
        const DataPilotFieldSortInfo* pInfo = getSortInfo();
        if (pInfo)
            aRet <<= *pInfo;
    }
    else if ( aPropertyName == SC_UNONAME_ISGROUP )
        aRet <<= hasGroupInfo();
    else if ( aPropertyName == SC_UNONAME_GROUPINFO )
    {
        aRet <<= getGroupInfo();
    }
    else if ( aPropertyName == SC_UNONAME_SHOWEMPTY )
        aRet <<= getShowEmpty();
    else if ( aPropertyName == SC_UNONAME_REPEATITEMLABELS )
        aRet <<= getRepeatItemLabels();
    else if (aPropertyName == SC_UNONAME_NAME)
        aRet <<= getName();
 
    return aRet;
}
 
// XDatePilotField
 
Reference<XIndexAccess> SAL_CALL ScDataPilotFieldObj::getItems()
{
    SolarMutexGuard aGuard;
    if (!mxItems.is())
        mxItems.set( new ScDataPilotItemsObj( *mxParent, maFieldId ) );
    return mxItems;
}
 
SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDataPilotFieldObj )
 
DataPilotFieldOrientation ScDataPilotFieldObj::getOrientation() const
{
    SolarMutexGuard aGuard;
    ScDPSaveDimension* pDim = GetDPDimension();
    return pDim ? pDim->GetOrientation() : DataPilotFieldOrientation_HIDDEN;
}
 
void ScDataPilotFieldObj::setOrientation(DataPilotFieldOrientation eNew)
{
    SolarMutexGuard aGuard;
    if (maOrient.hasValue() && (eNew == maOrient.get< DataPilotFieldOrientation >()))
        return;
 
    ScDPObject* pDPObj = nullptr;
    ScDPSaveDimension* pDim = GetDPDimension( &pDPObj );
    if(!pDim)
        return;
 
    ScDPSaveData* pSaveData = pDPObj->GetSaveData();
 
    /*  If the field was taken from getDataPilotFields(), don't reset the
        orientation for an existing use, but create a duplicated field
        instead (for "Data" orientation only). */
    if ( !maOrient.hasValue() && !maFieldId.mbDataLayout &&
         (pDim->GetOrientation() != DataPilotFieldOrientation_HIDDEN) &&
         (eNew == DataPilotFieldOrientation_DATA) )
    {
 
        ScDPSaveDimension* pNewDim = nullptr;
 
        // look for existing duplicate with orientation "hidden"
 
        sal_Int32 nFound = 0;
        const ScDPSaveData::DimsType& rDimensions = pSaveData->GetDimensions();
        for (auto const& it : rDimensions)
        {
            if ( !it->IsDataLayout() && (it->GetName() == maFieldId.maFieldName) )
            {
                if ( it->GetOrientation() == DataPilotFieldOrientation_HIDDEN )
                {
                    pNewDim = it.get();     // use this one
                    break;
                }
                else
                    ++nFound;               // count existing non-hidden occurrences
            }
        }
 
        if ( !pNewDim )     // if none found, create a new duplicated dimension
            pNewDim = &pSaveData->DuplicateDimension( *pDim );
 
        maFieldId.mnFieldIdx = nFound;      // keep accessing the new one
        pDim = pNewDim;
    }
 
    pDim->SetOrientation(eNew);
 
    // move changed field behind all other fields (make it the last field in dimension)
    pSaveData->SetPosition( pDim, pSaveData->GetDimensions().size() );
 
    SetDPObject( pDPObj );
 
    maOrient <<= eNew;   // modifying the same object's orientation again doesn't create another duplicate
}
 
sal_Int16 ScDataPilotFieldObj::getFunction() const
{
    SolarMutexGuard aGuard;
    sal_Int16 eRet = GeneralFunction2::NONE;
    if( ScDPSaveDimension* pDim = GetDPDimension() )
    {
        if( pDim->GetOrientation() != DataPilotFieldOrientation_DATA )
        {
            // for non-data fields, property Function is the subtotals
            tools::Long nSubCount = pDim->GetSubTotalsCount();
            if ( nSubCount > 0 )
                eRet = static_cast<sal_Int16>(pDim->GetSubTotalFunc(0));    // always use the first one
            // else keep NONE
        }
        else
            eRet = static_cast<sal_Int16>(pDim->GetFunction());
    }
    return eRet;
}
 
void ScDataPilotFieldObj::setFunction(ScGeneralFunction eNewFunc)
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    ScDPSaveDimension* pDim = GetDPDimension( &pDPObj );
    if(!pDim)
        return;
 
    if( pDim->GetOrientation() != DataPilotFieldOrientation_DATA )
    {
        // for non-data fields, property Function is the subtotals
        std::vector<ScGeneralFunction> nSubTotalFuncs;
        if ( eNewFunc != ScGeneralFunction::NONE )
        {
            nSubTotalFuncs.push_back( eNewFunc );
        }
        pDim->SetSubTotals( std::move(nSubTotalFuncs) );
    }
    else
        pDim->SetFunction( eNewFunc );
    SetDPObject( pDPObj );
}
 
Sequence< sal_Int16 > ScDataPilotFieldObj::getSubtotals() const
{
    SolarMutexGuard aGuard;
    Sequence< sal_Int16 > aRet;
    if( ScDPSaveDimension* pDim = GetDPDimension() )
    {
        if( pDim->GetOrientation() != DataPilotFieldOrientation_DATA )
        {
            // for non-data fields, property Functions is the sequence of subtotals
            sal_Int32 nCount = static_cast< sal_Int32 >( pDim->GetSubTotalsCount() );
            if ( nCount > 0 )
            {
                aRet.realloc( nCount );
                auto pRet = aRet.getArray();
                for( sal_Int32 nIdx = 0; nIdx < nCount; ++nIdx )
                    pRet[ nIdx ] = static_cast<sal_Int16>(pDim->GetSubTotalFunc( nIdx ));
            }
        }
    }
    return aRet;
}
 
void ScDataPilotFieldObj::setSubtotals( const std::vector< ScGeneralFunction >& rSubtotals )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    ScDPSaveDimension* pDim = GetDPDimension( &pDPObj );
    if(!pDim)
        return;
 
    if( pDim->GetOrientation() != DataPilotFieldOrientation_DATA )
    {
        sal_Int32 nCount = rSubtotals.size();
        if( nCount == 1 )
        {
            // count 1: all values are allowed (including NONE and AUTO)
            std::vector<ScGeneralFunction> nTmpFuncs;
            if( rSubtotals[ 0 ] != ScGeneralFunction::NONE )
            {
                nTmpFuncs.push_back( rSubtotals[ 0 ] );
            }
            pDim->SetSubTotals( std::move(nTmpFuncs) );
        }
        else if( nCount > 1 )
        {
            // set multiple functions, ignore NONE and AUTO in this case
            ::std::vector< ScGeneralFunction > aSubt;
            for( sal_Int32 nIdx = 0; nIdx < nCount; ++nIdx )
            {
                ScGeneralFunction eFunc = rSubtotals[ nIdx ];
                if( (eFunc != ScGeneralFunction::NONE) && (eFunc != ScGeneralFunction::AUTO) )
                {
                    // do not insert functions twice
                    if( ::std::find( aSubt.begin(), aSubt.end(), eFunc ) == aSubt.end() )
                        aSubt.push_back( eFunc );
                }
            }
            // set values from vector to ScDPSaveDimension
            pDim->SetSubTotals( std::move(aSubt) );
        }
    }
    SetDPObject( pDPObj );
}
 
void ScDataPilotFieldObj::setCurrentPage( const OUString& rPage )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    if( ScDPSaveDimension* pDim = GetDPDimension( &pDPObj ) )
    {
        pDim->SetCurrentPage( &rPage );
        SetDPObject( pDPObj );
    }
}
 
void ScDataPilotFieldObj::setUseCurrentPage( bool bUse )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    ScDPSaveDimension* pDim = GetDPDimension( &pDPObj );
    if(!pDim)
        return;
 
    if( bUse )
    {
        /*  It is somehow useless to set the property "HasSelectedPage" to
            true, because it is still needed to set an explicit page name. */
        const OUString aPage;
        pDim->SetCurrentPage( &aPage );
    }
    else
        pDim->SetCurrentPage( nullptr );
    SetDPObject( pDPObj );
}
 
const DataPilotFieldAutoShowInfo* ScDataPilotFieldObj::getAutoShowInfo() const
{
    SolarMutexGuard aGuard;
    ScDPSaveDimension* pDim = GetDPDimension();
    return pDim ? pDim->GetAutoShowInfo() : nullptr;
}
 
void ScDataPilotFieldObj::setAutoShowInfo( const DataPilotFieldAutoShowInfo* pInfo )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    if( ScDPSaveDimension* pDim = GetDPDimension( &pDPObj ) )
    {
        pDim->SetAutoShowInfo( pInfo );
        SetDPObject( pDPObj );
    }
}
 
const DataPilotFieldLayoutInfo* ScDataPilotFieldObj::getLayoutInfo() const
{
    SolarMutexGuard aGuard;
    ScDPSaveDimension* pDim = GetDPDimension();
    return pDim ? pDim->GetLayoutInfo() : nullptr;
}
 
void ScDataPilotFieldObj::setLayoutInfo( const DataPilotFieldLayoutInfo* pInfo )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    if( ScDPSaveDimension* pDim = GetDPDimension( &pDPObj ) )
    {
        pDim->SetLayoutInfo( pInfo );
        SetDPObject( pDPObj );
    }
}
 
const DataPilotFieldReference* ScDataPilotFieldObj::getReference() const
{
    SolarMutexGuard aGuard;
    ScDPSaveDimension* pDim = GetDPDimension();
    return pDim ? pDim->GetReferenceValue() : nullptr;
}
 
void ScDataPilotFieldObj::setReference( const DataPilotFieldReference* pInfo )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    if( ScDPSaveDimension* pDim = GetDPDimension( &pDPObj ) )
    {
        pDim->SetReferenceValue( pInfo );
        SetDPObject( pDPObj );
    }
}
 
const DataPilotFieldSortInfo* ScDataPilotFieldObj::getSortInfo() const
{
    SolarMutexGuard aGuard;
    ScDPSaveDimension* pDim = GetDPDimension();
    return pDim ? pDim->GetSortInfo() : nullptr;
}
 
void ScDataPilotFieldObj::setSortInfo( const DataPilotFieldSortInfo* pInfo )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    if( ScDPSaveDimension* pDim = GetDPDimension( &pDPObj ) )
    {
        pDim->SetSortInfo( pInfo );
        SetDPObject( pDPObj );
    }
}
 
bool ScDataPilotFieldObj::getShowEmpty() const
{
    SolarMutexGuard aGuard;
    ScDPSaveDimension* pDim = GetDPDimension();
    return pDim && pDim->GetShowEmpty();
}
 
void ScDataPilotFieldObj::setShowEmpty( bool bShow )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    if( ScDPSaveDimension* pDim = GetDPDimension( &pDPObj ) )
    {
        pDim->SetShowEmpty( bShow );
        SetDPObject( pDPObj );
    }
}
 
bool ScDataPilotFieldObj::getRepeatItemLabels() const
{
    SolarMutexGuard aGuard;
    ScDPSaveDimension* pDim = GetDPDimension();
    return pDim && pDim->GetRepeatItemLabels();
}
 
void ScDataPilotFieldObj::setRepeatItemLabels( bool bShow )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    if( ScDPSaveDimension* pDim = GetDPDimension( &pDPObj ) )
    {
        pDim->SetRepeatItemLabels( bShow );
        SetDPObject( pDPObj );
    }
}
 
bool ScDataPilotFieldObj::hasGroupInfo() const
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    if( ScDPSaveDimension* pDim = GetDPDimension( &pDPObj ) )
        if( const ScDPDimensionSaveData* pDimData = pDPObj->GetSaveData()->GetExistingDimensionData() )
            return pDimData->GetNamedGroupDim( pDim->GetName() ) || pDimData->GetNumGroupDim( pDim->GetName() );
    return false;
}
 
DataPilotFieldGroupInfo ScDataPilotFieldObj::getGroupInfo()
{
    SolarMutexGuard aGuard;
    DataPilotFieldGroupInfo aInfo;
    ScDPObject* pDPObj = nullptr;
    if( ScDPSaveDimension* pDim = GetDPDimension( &pDPObj ) )
    {
        if( const ScDPDimensionSaveData* pDimData = pDPObj->GetSaveData()->GetExistingDimensionData() )
        {
            if( const ScDPSaveGroupDimension* pGroupDim = pDimData->GetNamedGroupDim( pDim->GetName() ) )
            {
                // grouped by ...
                aInfo.GroupBy = pGroupDim->GetDatePart();
 
                // find source field
                try
                {
                    Reference< XNameAccess > xFields( mxParent->getDataPilotFields(), UNO_QUERY_THROW );
                    aInfo.SourceField.set( xFields->getByName( pGroupDim->GetSourceDimName() ), UNO_QUERY );
                }
                catch( Exception& )
                {
                }
 
                ScDataPilotConversion::FillGroupInfo( aInfo, pGroupDim->GetDateInfo() );
                if( pGroupDim->GetDatePart() == 0 )
                {
                    // fill vector of group and group member information
                    ScFieldGroups aGroups;
                    for( sal_Int32 nIdx = 0, nCount = pGroupDim->GetGroupCount(); nIdx < nCount; ++nIdx )
                    {
                        const ScDPSaveGroupItem& rGroup = pGroupDim->GetGroupByIndex( nIdx );
                        ScFieldGroup aGroup;
                        aGroup.maName = rGroup.GetGroupName();
                        for( sal_Int32 nMemIdx = 0, nMemCount = rGroup.GetElementCount(); nMemIdx < nMemCount; ++nMemIdx )
                            if (const OUString* pMem = rGroup.GetElementByIndex(nMemIdx))
                                aGroup.maMembers.push_back( *pMem );
                        aGroups.push_back( aGroup );
                    }
                    aInfo.Groups = new ScDataPilotFieldGroupsObj( std::move(aGroups) );
                }
            }
            else if( const ScDPSaveNumGroupDimension* pNumGroupDim = pDimData->GetNumGroupDim( pDim->GetName() ) )
            {
                if (pNumGroupDim->GetDatePart())
                {
                    ScDataPilotConversion::FillGroupInfo( aInfo, pNumGroupDim->GetDateInfo() );
                    aInfo.GroupBy = pNumGroupDim->GetDatePart();
                }
                else
                {
                    ScDataPilotConversion::FillGroupInfo( aInfo, pNumGroupDim->GetInfo() );
                }
            }
        }
    }
    return aInfo;
}
 
void ScDataPilotFieldObj::setGroupInfo( const DataPilotFieldGroupInfo* pInfo )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    if( /*ScDPSaveDimension* pDim =*/ !GetDPDimension( &pDPObj ) )
        return;
 
    ScDPSaveData* pSaveData = pDPObj->GetSaveData();
    if( pInfo && lclCheckMinMaxStep( *pInfo ) )
    {
        ScDPNumGroupInfo aInfo;
        aInfo.mbEnable = true;
        aInfo.mbDateValues = pInfo->HasDateValues;
        aInfo.mbAutoStart = pInfo->HasAutoStart;
        aInfo.mbAutoEnd = pInfo->HasAutoEnd;
        aInfo.mfStart = pInfo->Start;
        aInfo.mfEnd = pInfo->End;
        aInfo.mfStep = pInfo->Step;
        Reference< XNamed > xNamed( pInfo->SourceField, UNO_QUERY );
        if( xNamed.is() )
        {
            ScDPSaveGroupDimension aGroupDim( xNamed->getName(), getName() );
            if( pInfo->GroupBy )
                aGroupDim.SetDateInfo(aInfo, pInfo->GroupBy);
            else
            {
                Reference<XIndexAccess> xIndex(pInfo->Groups, UNO_QUERY);
                if (xIndex.is())
                {
                    sal_Int32 nCount(xIndex->getCount());
                    for(sal_Int32 i = 0; i < nCount; i++)
                    {
                        Reference<XNamed> xGroupNamed(xIndex->getByIndex(i), UNO_QUERY);
                        if (xGroupNamed.is())
                        {
                            ScDPSaveGroupItem aItem(xGroupNamed->getName());
                            Reference<XIndexAccess> xGroupIndex(xGroupNamed, UNO_QUERY);
                            if (xGroupIndex.is())
                            {
                                sal_Int32 nItemCount(xGroupIndex->getCount());
                                for (sal_Int32 j = 0; j < nItemCount; ++j)
                                {
                                    Reference<XNamed> xItemNamed(xGroupIndex->getByIndex(j), UNO_QUERY);
                                    if (xItemNamed.is())
                                        aItem.AddElement(xItemNamed->getName());
                                }
                            }
                            aGroupDim.AddGroupItem(aItem);
                        }
                    }
                }
            }
 
            // get dimension savedata or create new if none
            ScDPDimensionSaveData& rDimSaveData = *pSaveData->GetDimensionData();
            rDimSaveData.ReplaceGroupDimension( aGroupDim );
        }
        else    // no source field in group info -> numeric group
        {
            ScDPDimensionSaveData* pDimData = pSaveData->GetDimensionData();     // created if not there
 
            ScDPSaveNumGroupDimension* pExisting = pDimData->GetNumGroupDimAcc( getName() );
            if ( pExisting )
            {
                if (pInfo->GroupBy)
                    pExisting->SetDateInfo(aInfo, pInfo->GroupBy);
                // modify existing group dimension
                pExisting->SetGroupInfo( aInfo );
            }
            else if (pInfo->GroupBy)
            {
                // create new group dimension
                ScDPSaveNumGroupDimension aNumGroupDim( getName(), aInfo, pInfo->GroupBy );
                pDimData->AddNumGroupDimension( aNumGroupDim );
            }
            else
            {
                // create new group dimension
                ScDPSaveNumGroupDimension aNumGroupDim( getName(), aInfo );
                pDimData->AddNumGroupDimension( aNumGroupDim );
            }
        }
    }
    else    // null passed as argument
    {
        pSaveData->SetDimensionData( nullptr );
    }
 
    pDPObj->SetSaveData( *pSaveData );
    SetDPObject( pDPObj );
}
 
// XDataPilotFieldGrouping
Reference< XDataPilotField > SAL_CALL ScDataPilotFieldObj::createNameGroup( const Sequence< OUString >& rItems )
{
    SolarMutexGuard aGuard;
 
    if( !rItems.hasElements() )
        throw IllegalArgumentException(u"rItems is empty"_ustr, getXWeak(), 0);
 
    Reference< XMembersAccess > xMembers = GetMembers();
    if (!xMembers.is())
    {
        SAL_WARN("sc.ui", "Cannot access members of the field object.");
        throw RuntimeException(u"Cannot access members of the field object"_ustr, getXWeak());
    }
 
    for (const OUString& aEntryName : rItems)
    {
        if (!xMembers->hasByName(aEntryName))
        {
            SAL_WARN("sc.ui", "There is no member with that name: " + aEntryName + ".");
            throw IllegalArgumentException("There is no member with name \"" + aEntryName + "\"", getXWeak(), 0);
        }
    }
 
    Reference< XDataPilotField > xRet;
    OUString sNewDim;
    ScDPObject* pDPObj = nullptr;
    if( ScDPSaveDimension* pDim = GetDPDimension( &pDPObj ) )
    {
        const OUString& aDimName = pDim->GetName();
 
        ScDPSaveData aSaveData = *pDPObj->GetSaveData();
        ScDPDimensionSaveData* pDimData = aSaveData.GetDimensionData();     // created if not there
 
        // find original base
        OUString aBaseDimName( aDimName );
        const ScDPSaveGroupDimension* pBaseGroupDim = pDimData->GetNamedGroupDim( aDimName );
        if ( pBaseGroupDim )
        {
            // any entry's SourceDimName is the original base
            aBaseDimName = pBaseGroupDim->GetSourceDimName();
        }
 
        // find existing group dimension
        // (using the selected dim, can be intermediate group dim)
        ScDPSaveGroupDimension* pGroupDimension = pDimData->GetGroupDimAccForBase( aDimName );
 
        // remove the selected items from their groups
        // (empty groups are removed, too)
        if ( pGroupDimension )
        {
            for (const OUString& aEntryName : rItems)
            {
                if ( pBaseGroupDim )
                {
                    // for each selected (intermediate) group, remove all its items
                    // (same logic as for adding, below)
                    const ScDPSaveGroupItem* pBaseGroup = pBaseGroupDim->GetNamedGroup( aEntryName );
                    if ( pBaseGroup )
                        pBaseGroup->RemoveElementsFromGroups( *pGroupDimension );   // remove all elements
                    else
                        pGroupDimension->RemoveFromGroups( aEntryName );
                }
                else
                    pGroupDimension->RemoveFromGroups( aEntryName );
            }
        }
 
        std::unique_ptr<ScDPSaveGroupDimension> pNewGroupDim;
        if ( !pGroupDimension )
        {
            // create a new group dimension
            sNewDim = pDimData->CreateGroupDimName( aBaseDimName, *pDPObj, false, nullptr );
            pNewGroupDim.reset(new ScDPSaveGroupDimension( aBaseDimName, sNewDim ));
 
            pGroupDimension = pNewGroupDim.get();     // make changes to the new dim if none existed
 
            if ( pBaseGroupDim )
            {
                // If it's a higher-order group dimension, pre-allocate groups for all
                // non-selected original groups, so the individual base members aren't
                // used for automatic groups (this would make the original groups hard
                // to find).
                //! Also do this when removing groups?
                //! Handle this case dynamically with automatic groups?
 
                tools::Long nGroupCount = pBaseGroupDim->GetGroupCount();
                for ( tools::Long nGroup = 0; nGroup < nGroupCount; nGroup++ )
                {
                    const ScDPSaveGroupItem& rBaseGroup = pBaseGroupDim->GetGroupByIndex( nGroup );
 
                    if (comphelper::findValue(rItems, rBaseGroup.GetGroupName()) == -1)    //! ignore case?
                    {
                        // add an additional group for each item that is not in the selection
                        ScDPSaveGroupItem aGroup( rBaseGroup.GetGroupName() );
                        aGroup.AddElementsFromGroup( rBaseGroup );
                        pGroupDimension->AddGroupItem( aGroup );
                    }
                }
            }
        }
        OUString aGroupDimName = pGroupDimension->GetGroupDimName();
 
        ScDPSaveGroupItem aGroup(pGroupDimension->CreateGroupName(ScResId(STR_PIVOT_GROUP)));
        for (const OUString& aEntryName : rItems)
        {
            if ( pBaseGroupDim )
            {
                // for each selected (intermediate) group, add all its items
                const ScDPSaveGroupItem* pBaseGroup = pBaseGroupDim->GetNamedGroup( aEntryName );
                if ( pBaseGroup )
                    aGroup.AddElementsFromGroup( *pBaseGroup );
                else
                    aGroup.AddElement( aEntryName );    // no group found -> automatic group, add the item itself
            }
            else
                aGroup.AddElement( aEntryName );        // no group dimension, add all items directly
        }
 
        pGroupDimension->AddGroupItem( aGroup );
 
        if ( pNewGroupDim )
        {
            pDimData->AddGroupDimension( *pNewGroupDim );
            pNewGroupDim.reset();        // AddGroupDimension copies the object
            // don't access pGroupDimension after here
        }
        pGroupDimension = nullptr;
 
        // set orientation
        ScDPSaveDimension* pSaveDimension = aSaveData.GetDimensionByName( aGroupDimName );
        if ( pSaveDimension->GetOrientation() == DataPilotFieldOrientation_HIDDEN )
        {
            ScDPSaveDimension* pOldDimension = aSaveData.GetDimensionByName( aDimName );
            pSaveDimension->SetOrientation( pOldDimension->GetOrientation() );
            aSaveData.SetPosition( pSaveDimension, 0 ); //! before (immediate) base
        }
 
        // apply changes
        pDPObj->SetSaveData( aSaveData );
        ScDBDocFunc(*GetDocShell()).RefreshPivotTableGroups(pDPObj);
    }
 
    // if new grouping field has been created (on first group), return it
    if( !sNewDim.isEmpty() )
    {
        Reference< XNameAccess > xFields(mxParent->getDataPilotFields(), UNO_QUERY);
        if (xFields.is())
        {
            try
            {
                xRet.set(xFields->getByName(sNewDim), UNO_QUERY);
                SAL_WARN_IF(!xRet.is(), "sc.ui", "there is a name, so there should be also a field");
            }
            catch (const container::NoSuchElementException&)
            {
                css::uno::Any anyEx = cppu::getCaughtException();
                SAL_WARN("sc.ui", "Cannot find field with that name: " + sNewDim + ".");
                // Avoid throwing exception that's not specified in the method signature.
                throw css::lang::WrappedTargetRuntimeException(
                        "Cannot find field with name \"" + sNewDim + "\"",
                        getXWeak(), anyEx );
            }
        }
    }
    return xRet;
}
 
Reference < XDataPilotField > SAL_CALL ScDataPilotFieldObj::createDateGroup( const DataPilotFieldGroupInfo& rInfo )
{
    SolarMutexGuard aGuard;
    using namespace ::com::sun::star::sheet::DataPilotFieldGroupBy;
 
    if( !rInfo.HasDateValues )
        throw IllegalArgumentException(u"HasDateValues is not set"_ustr, getXWeak(), 0);
    if( !lclCheckMinMaxStep( rInfo ) )
        throw IllegalArgumentException(u"min/max/step"_ustr, getXWeak(), 0);
 
    // only a single date flag is allowed
    if( (rInfo.GroupBy == 0) || (rInfo.GroupBy > YEARS) || ((rInfo.GroupBy & (rInfo.GroupBy - 1)) != 0) )
        throw IllegalArgumentException("Invalid GroupBy value: " + OUString::number(rInfo.GroupBy), getXWeak(), 0);
 
    // step must be zero, if something else than DAYS is specified
    if( rInfo.Step >= ((rInfo.GroupBy == DAYS) ? 32768.0 : 1.0) )
        throw IllegalArgumentException("Invalid step value: " + OUString::number(rInfo.Step), getXWeak(), 0);
 
    OUString aGroupDimName;
    ScDPObject* pDPObj = nullptr;
    if( ScDPSaveDimension* pDim = GetDPDimension( &pDPObj ) )
    {
        ScDPNumGroupInfo aInfo;
        aInfo.mbEnable = true;
        aInfo.mbDateValues = (rInfo.GroupBy == DAYS) && (rInfo.Step >= 1.0);
        aInfo.mbAutoStart = rInfo.HasAutoStart;
        aInfo.mbAutoEnd = rInfo.HasAutoEnd;
        aInfo.mfStart = rInfo.Start;
        aInfo.mfEnd = rInfo.End;
        aInfo.mfStep = std::trunc( rInfo.Step );
 
        // create a local copy of the entire save data (will be written back below)
        ScDPSaveData aSaveData = *pDPObj->GetSaveData();
        // get or create dimension save data
        ScDPDimensionSaveData& rDimData = *aSaveData.GetDimensionData();
 
        // find source dimension name
        const OUString& rDimName = pDim->GetName();
        const ScDPSaveGroupDimension* pGroupDim = rDimData.GetNamedGroupDim( rDimName );
        OUString aSrcDimName = pGroupDim ? pGroupDim->GetSourceDimName() : rDimName;
 
        // find a group dimension for the base field, or get numeric grouping
        pGroupDim = rDimData.GetFirstNamedGroupDim( aSrcDimName );
        const ScDPSaveNumGroupDimension* pNumGroupDim = rDimData.GetNumGroupDim( aSrcDimName );
 
        // do not group by dates, if named groups or numeric grouping is present
        bool bHasNamedGrouping = pGroupDim && !pGroupDim->GetDateInfo().mbEnable;
        bool bHasNumGrouping = pNumGroupDim && pNumGroupDim->GetInfo().mbEnable && !pNumGroupDim->GetInfo().mbDateValues && !pNumGroupDim->GetDateInfo().mbEnable;
        if( bHasNamedGrouping || bHasNumGrouping )
            throw IllegalArgumentException();
 
        if( aInfo.mbDateValues )  // create day ranges grouping
        {
            // first remove all named group dimensions
            while( pGroupDim )
            {
                OUString aGroupDimName2 = pGroupDim->GetGroupDimName();
                // find next group dimension before deleting this group
                pGroupDim = rDimData.GetNextNamedGroupDim( aGroupDimName2 );
                // remove from dimension save data
                rDimData.RemoveGroupDimension( aGroupDimName2 );
                // also remove save data settings for the dimension that no longer exists
                aSaveData.RemoveDimensionByName( aGroupDimName2 );
            }
            // create or replace the number grouping dimension
            ScDPSaveNumGroupDimension aNumGroupDim( aSrcDimName, aInfo );
            rDimData.ReplaceNumGroupDimension( aNumGroupDim );
        }
        else    // create date grouping
        {
            // collect all existing date flags
            sal_Int32 nDateParts = rDimData.CollectDateParts( aSrcDimName );
            if( nDateParts == 0 )
            {
                // insert numeric group dimension, if no date groups exist yet (or replace day range grouping)
                ScDPSaveNumGroupDimension aNumGroupDim( aSrcDimName, aInfo, rInfo.GroupBy );
                rDimData.ReplaceNumGroupDimension( aNumGroupDim );
            }
            else if( (nDateParts & rInfo.GroupBy) == 0 )    // do nothing if date field exists already
            {
                // create new named group dimension for additional date groups
                aGroupDimName = rDimData.CreateDateGroupDimName( rInfo.GroupBy, *pDPObj, true, nullptr );
                ScDPSaveGroupDimension aGroupDim( aSrcDimName, aGroupDimName, aInfo, rInfo.GroupBy );
                rDimData.AddGroupDimension( aGroupDim );
 
                // set orientation of new named group dimension
                ScDPSaveDimension& rSaveDim = *aSaveData.GetDimensionByName( aGroupDimName );
                if( rSaveDim.GetOrientation() == DataPilotFieldOrientation_HIDDEN )
                {
                    ScDPSaveDimension& rOldDim = *aSaveData.GetDimensionByName( aSrcDimName );
                    rSaveDim.SetOrientation( rOldDim.GetOrientation() );
                    aSaveData.SetPosition( &rSaveDim, 0 );  //! before (immediate) base
                }
            }
        }
 
        // apply changes
        pDPObj->SetSaveData( aSaveData );
        ScDBDocFunc(*GetDocShell()).RefreshPivotTableGroups(pDPObj);
    }
 
    // return the UNO object of the new dimension, after writing back saved data
    Reference< XDataPilotField > xRet;
    if( !aGroupDimName.isEmpty() )
        try
        {
           Reference< XNameAccess > xFields( mxParent->getDataPilotFields(), UNO_QUERY_THROW );
           xRet.set( xFields->getByName( aGroupDimName ), UNO_QUERY );
        }
        catch( Exception& )
        {
        }
    return xRet;
}
 
namespace {
 
bool lclExtractGroupMembers( ScFieldGroupMembers& rMembers, const Any& rElement )
{
    // allow empty value to create a new group
    if( !rElement.hasValue() )
        return true;
 
    // try to extract a simple sequence of strings
    Sequence< OUString > aSeq;
    if( rElement >>= aSeq )
    {
        if( aSeq.hasElements() )
            rMembers.insert( rMembers.end(), std::cbegin(aSeq), std::cend(aSeq) );
        return true;
    }
 
    // try to use XIndexAccess providing objects that support XNamed
    Reference< XIndexAccess > xItemsIA( rElement, UNO_QUERY );
    if( xItemsIA.is() )
    {
        for( sal_Int32 nIdx = 0, nCount = xItemsIA->getCount(); nIdx < nCount; ++nIdx )
        {
            try // getByIndex() should not throw, but we cannot be sure
            {
                Reference< XNamed > xItemName( xItemsIA->getByIndex( nIdx ), UNO_QUERY_THROW );
                rMembers.push_back( xItemName->getName() );
            }
            catch( Exception& )
            {
                // ignore exceptions, go ahead with next element in the array
            }
        }
        return true;
    }
 
    // nothing valid inside the Any -> return false
    return false;
}
 
} // namespace
 
ScDataPilotFieldGroupsObj::ScDataPilotFieldGroupsObj( ScFieldGroups&& rGroups ) :
    maGroups( std::move(rGroups) )
{
}
 
ScDataPilotFieldGroupsObj::~ScDataPilotFieldGroupsObj()
{
}
 
// XNameAccess
 
Any SAL_CALL ScDataPilotFieldGroupsObj::getByName( const OUString& rName )
{
    SolarMutexGuard aGuard;
    if( implFindByName( rName ) == maGroups.end() )
        throw NoSuchElementException();
    return Any( Reference< XNameAccess >( new ScDataPilotFieldGroupObj( *this, rName ) ) );
}
 
Sequence< OUString > SAL_CALL ScDataPilotFieldGroupsObj::getElementNames()
{
    SolarMutexGuard aGuard;
    Sequence< OUString > aSeq;
    if( !maGroups.empty() )
    {
        aSeq.realloc( static_cast< sal_Int32 >( maGroups.size() ) );
        OUString* pName = aSeq.getArray();
        for( const auto& rGroup : maGroups )
        {
            *pName = rGroup.maName;
            ++pName;
        }
    }
    return aSeq;
}
 
sal_Bool SAL_CALL ScDataPilotFieldGroupsObj::hasByName( const OUString& rName )
{
    SolarMutexGuard aGuard;
    return implFindByName( rName ) != maGroups.end();
}
 
// XNameReplace
 
void SAL_CALL ScDataPilotFieldGroupsObj::replaceByName( const OUString& rName, const Any& rElement )
{
    SolarMutexGuard aGuard;
 
    if( rName.isEmpty() )
        throw IllegalArgumentException(u"Name is empty"_ustr, getXWeak(), 0);
 
    ScFieldGroups::iterator aIt = implFindByName( rName );
    if( aIt == maGroups.end() )
        throw NoSuchElementException("Name \"" + rName + "\" not found", getXWeak());
 
    // read all item names provided by the passed object
    ScFieldGroupMembers aMembers;
    if( !lclExtractGroupMembers( aMembers, rElement ) )
        throw IllegalArgumentException(u"Invalid element object"_ustr, getXWeak(), 0);
 
    // copy and forget, faster than vector assignment
    aIt->maMembers.swap( aMembers );
}
 
// XNameContainer
 
void SAL_CALL ScDataPilotFieldGroupsObj::insertByName( const OUString& rName, const Any& rElement )
{
    SolarMutexGuard aGuard;
 
    if( rName.isEmpty() )
        throw IllegalArgumentException(u"Name is empty"_ustr, getXWeak(), 0);
 
    ScFieldGroups::iterator aIt = implFindByName( rName );
    if( aIt != maGroups.end() )
        throw ElementExistException("Name \"" + rName + "\" already exists", getXWeak());
 
    // read all item names provided by the passed object
    ScFieldGroupMembers aMembers;
    if( !lclExtractGroupMembers( aMembers, rElement ) )
        throw IllegalArgumentException(u"Invalid element object"_ustr, getXWeak(), 0);
 
    // create the new entry if no error has been occurred
    maGroups.emplace_back();
    ScFieldGroup& rGroup = maGroups.back();
    rGroup.maName = rName;
    rGroup.maMembers.swap( aMembers );
}
 
void SAL_CALL ScDataPilotFieldGroupsObj::removeByName( const OUString& rName )
{
    SolarMutexGuard aGuard;
 
    if( rName.isEmpty() )
        throw IllegalArgumentException(u"Name is empty"_ustr, getXWeak(), 0);
 
    ScFieldGroups::iterator aIt = implFindByName( rName );
    if( aIt == maGroups.end() )
        throw NoSuchElementException("Name \"" + rName + "\" not found", getXWeak());
 
    maGroups.erase( aIt );
}
 
// XIndexAccess
 
sal_Int32 SAL_CALL ScDataPilotFieldGroupsObj::getCount()
{
    SolarMutexGuard aGuard;
    return static_cast< sal_Int32 >( maGroups.size() );
}
 
Any SAL_CALL ScDataPilotFieldGroupsObj::getByIndex( sal_Int32 nIndex )
{
    SolarMutexGuard aGuard;
    if ((nIndex < 0) || (o3tl::make_unsigned(nIndex) >= maGroups.size()))
        throw IndexOutOfBoundsException();
    return Any( Reference< XNameAccess >( new ScDataPilotFieldGroupObj( *this, maGroups[ nIndex ].maName ) ) );
}
 
// XEnumerationAccess
 
Reference<XEnumeration> SAL_CALL ScDataPilotFieldGroupsObj::createEnumeration()
{
    SolarMutexGuard aGuard;
    return new ScIndexEnumeration( this, u"com.sun.star.sheet.DataPilotFieldGroupsEnumeration"_ustr );
}
 
// XElementAccess
 
uno::Type SAL_CALL ScDataPilotFieldGroupsObj::getElementType()
{
    return cppu::UnoType<XNameAccess>::get();
}
 
sal_Bool SAL_CALL ScDataPilotFieldGroupsObj::hasElements()
{
    SolarMutexGuard aGuard;
    return !maGroups.empty();
}
 
// implementation
 
ScFieldGroup& ScDataPilotFieldGroupsObj::getFieldGroup( const OUString& rName )
{
    SolarMutexGuard aGuard;
    ScFieldGroups::iterator aIt = implFindByName( rName );
    if( aIt == maGroups.end() )
        throw RuntimeException("Field Group with name \"" + rName + "\" not found", getXWeak());
    return *aIt;
}
 
void ScDataPilotFieldGroupsObj::renameFieldGroup( const OUString& rOldName, const OUString& rNewName )
{
    SolarMutexGuard aGuard;
    ScFieldGroups::iterator aOldIt = implFindByName( rOldName );
    ScFieldGroups::iterator aNewIt = implFindByName( rNewName );
    if( aOldIt == maGroups.end() )
        throw RuntimeException("Field Group with name \"" + rOldName + "\" not found", getXWeak());
    // new name must not exist yet
    if( (aNewIt != maGroups.end()) && (aNewIt != aOldIt) )
        throw RuntimeException("Field Group with name \"" + rOldName + "\" already exists", getXWeak());
    aOldIt->maName = rNewName;
}
 
ScFieldGroups::iterator ScDataPilotFieldGroupsObj::implFindByName( const OUString& rName )
{
    return std::find_if(maGroups.begin(), maGroups.end(),
        [&rName](const ScFieldGroup& rGroup) { return rGroup.maName == rName; });
}
 
namespace {
 
OUString lclExtractMember( const Any& rElement )
{
    if( rElement.has< OUString >() )
        return rElement.get< OUString >();
 
    Reference< XNamed > xNamed( rElement, UNO_QUERY );
    if( xNamed.is() )
        return xNamed->getName();
 
    return OUString();
}
 
} // namespace
 
ScDataPilotFieldGroupObj::ScDataPilotFieldGroupObj( ScDataPilotFieldGroupsObj& rParent, OUString aGroupName ) :
    mxParent( &rParent ),
    maGroupName(std::move( aGroupName ))
{
}
 
ScDataPilotFieldGroupObj::~ScDataPilotFieldGroupObj()
{
}
 
// XNameAccess
 
Any SAL_CALL ScDataPilotFieldGroupObj::getByName( const OUString& rName )
{
    SolarMutexGuard aGuard;
    ScFieldGroupMembers& rMembers = mxParent->getFieldGroup( maGroupName ).maMembers;
    ScFieldGroupMembers::iterator aIt = ::std::find( rMembers.begin(), rMembers.end(), rName );
    if( aIt == rMembers.end() )
        throw NoSuchElementException("Name \"" + rName + "\" not found", getXWeak());
    return Any( Reference< XNamed >( new ScDataPilotFieldGroupItemObj( *this, *aIt ) ) );
}
 
Sequence< OUString > SAL_CALL ScDataPilotFieldGroupObj::getElementNames()
{
    SolarMutexGuard aGuard;
    return ::comphelper::containerToSequence( mxParent->getFieldGroup( maGroupName ).maMembers );
}
 
sal_Bool SAL_CALL ScDataPilotFieldGroupObj::hasByName( const OUString& rName )
{
    SolarMutexGuard aGuard;
    ScFieldGroupMembers& rMembers = mxParent->getFieldGroup( maGroupName ).maMembers;
    return ::std::find( rMembers.begin(), rMembers.end(), rName ) != rMembers.end();
}
 
// XNameReplace
 
void SAL_CALL ScDataPilotFieldGroupObj::replaceByName( const OUString& rName, const Any& rElement )
{
    SolarMutexGuard aGuard;
 
    // it should be possible to quickly rename an item -> accept string or XNamed
    OUString aNewName = lclExtractMember( rElement );
    if( rName.isEmpty() || aNewName.isEmpty() )
        throw IllegalArgumentException(u"Name is empty"_ustr, getXWeak(), 0);
    if( rName == aNewName )
        return;
 
    ScFieldGroupMembers& rMembers = mxParent->getFieldGroup( maGroupName ).maMembers;
    ScFieldGroupMembers::iterator aOldIt = ::std::find( rMembers.begin(), rMembers.end(), rName );
    ScFieldGroupMembers::iterator aNewIt = ::std::find( rMembers.begin(), rMembers.end(), aNewName );
    if( aOldIt == rMembers.end() )
        throw NoSuchElementException("Name \"" + rName + "\" not found", getXWeak());
    if( aNewIt != rMembers.end() )
        throw IllegalArgumentException("Name \"" + rName + "\" already exists", getXWeak(), 0);
    *aOldIt = aNewName;
}
 
// XNameContainer
 
void SAL_CALL ScDataPilotFieldGroupObj::insertByName( const OUString& rName, const Any& /*rElement*/ )
{
    SolarMutexGuard aGuard;
 
    // we will ignore the passed element and just try to insert the name
    if( rName.isEmpty() )
        throw IllegalArgumentException(u"Name is empty"_ustr, getXWeak(), 0);
 
    ScFieldGroupMembers& rMembers = mxParent->getFieldGroup( maGroupName ).maMembers;
    ScFieldGroupMembers::iterator aIt = ::std::find( rMembers.begin(), rMembers.end(), rName );
    if( aIt != rMembers.end() )
        throw IllegalArgumentException("Name \"" + rName + "\" already exists", getXWeak(), 0);
    rMembers.push_back( rName );
}
 
void SAL_CALL ScDataPilotFieldGroupObj::removeByName( const OUString& rName )
{
    SolarMutexGuard aGuard;
 
    if( rName.isEmpty() )
        throw IllegalArgumentException(u"Name is empty"_ustr, getXWeak(), 0);
    ScFieldGroupMembers& rMembers = mxParent->getFieldGroup( maGroupName ).maMembers;
    ScFieldGroupMembers::iterator aIt = ::std::find( rMembers.begin(), rMembers.end(), rName );
    if( aIt == rMembers.end() )
        throw NoSuchElementException("Name \"" + rName + "\" not found", getXWeak());
    rMembers.erase( aIt );
}
 
// XIndexAccess
 
sal_Int32 SAL_CALL ScDataPilotFieldGroupObj::getCount()
{
    SolarMutexGuard aGuard;
    return static_cast< sal_Int32 >( mxParent->getFieldGroup( maGroupName ).maMembers.size() );
}
 
Any SAL_CALL ScDataPilotFieldGroupObj::getByIndex( sal_Int32 nIndex )
{
    SolarMutexGuard aGuard;
    ScFieldGroupMembers& rMembers = mxParent->getFieldGroup( maGroupName ).maMembers;
    if ((nIndex < 0) || (o3tl::make_unsigned(nIndex) >= rMembers.size()))
        throw IndexOutOfBoundsException();
    return Any( Reference< XNamed >( new ScDataPilotFieldGroupItemObj( *this, rMembers[ nIndex ] ) ) );
}
 
// XEnumerationAccess
 
Reference< XEnumeration > SAL_CALL ScDataPilotFieldGroupObj::createEnumeration()
{
    SolarMutexGuard aGuard;
    return new ScIndexEnumeration( this, u"com.sun.star.sheet.DataPilotFieldGroupEnumeration"_ustr );
}
 
// XElementAccess
 
uno::Type SAL_CALL ScDataPilotFieldGroupObj::getElementType()
{
    return cppu::UnoType<XNamed>::get();
}
 
sal_Bool SAL_CALL ScDataPilotFieldGroupObj::hasElements()
{
    SolarMutexGuard aGuard;
    return !mxParent->getFieldGroup( maGroupName ).maMembers.empty();
}
 
// XNamed
 
OUString SAL_CALL ScDataPilotFieldGroupObj::getName()
{
    SolarMutexGuard aGuard;
    return maGroupName;
}
 
void SAL_CALL ScDataPilotFieldGroupObj::setName( const OUString& rName )
{
    SolarMutexGuard aGuard;
    mxParent->renameFieldGroup( maGroupName, rName );
    // if call to renameFieldGroup() did not throw, remember the new name
    maGroupName = rName;
}
 
ScDataPilotFieldGroupItemObj::ScDataPilotFieldGroupItemObj( ScDataPilotFieldGroupObj& rParent, OUString aName ) :
    mxParent( &rParent ),
    maName(std::move( aName ))
{
}
 
ScDataPilotFieldGroupItemObj::~ScDataPilotFieldGroupItemObj()
{
}
 
// XNamed
 
OUString SAL_CALL ScDataPilotFieldGroupItemObj::getName()
{
    SolarMutexGuard aGuard;
    return maName;
}
 
void SAL_CALL ScDataPilotFieldGroupItemObj::setName( const OUString& rName )
{
    SolarMutexGuard aGuard;
    mxParent->replaceByName( maName, Any( rName ) );
    // if call to replaceByName() did not throw, remember the new name
    maName = rName;
}
 
ScDataPilotItemsObj::ScDataPilotItemsObj( ScDataPilotDescriptorBase& rParent, const ScFieldIdentifier& rFieldId ) :
    ScDataPilotChildObjBase( rParent, rFieldId )
{
}
 
ScDataPilotItemsObj::~ScDataPilotItemsObj()
{
}
 
// XDataPilotItems
 
ScDataPilotItemObj* ScDataPilotItemsObj::GetObjectByIndex_Impl( sal_Int32 nIndex ) const
{
    return ((0 <= nIndex) && (nIndex < GetMemberCount())) ?
        new ScDataPilotItemObj( *mxParent, maFieldId, nIndex ) : nullptr;
}
 
// XNameAccess
 
Any SAL_CALL ScDataPilotItemsObj::getByName( const OUString& aName )
{
    SolarMutexGuard aGuard;
    Reference<XNameAccess> xMembers = GetMembers();
    if (xMembers.is())
    {
        Reference<XIndexAccess> xMembersIndex(new ScNameToIndexAccess( xMembers ));
        sal_Int32 nCount = xMembersIndex->getCount();
        sal_Int32 nItem = 0;
        while (nItem < nCount)
        {
            Reference<XNamed> xMember(xMembersIndex->getByIndex(nItem), UNO_QUERY);
            if (xMember.is() && (aName == xMember->getName()))
            {
                return Any( Reference< XPropertySet >( GetObjectByIndex_Impl( nItem ) ) );
            }
            ++nItem;
        }
        throw NoSuchElementException("Name \"" + aName + "\" not found", getXWeak());
    }
    return Any();
}
 
Sequence<OUString> SAL_CALL ScDataPilotItemsObj::getElementNames()
{
    SolarMutexGuard aGuard;
    Sequence< OUString > aSeq;
    if( ScDPObject* pDPObj = GetDPObject() )
        pDPObj->GetMemberNames( lcl_GetObjectIndex( pDPObj, maFieldId ), aSeq );
    return aSeq;
}
 
sal_Bool SAL_CALL ScDataPilotItemsObj::hasByName( const OUString& aName )
{
    SolarMutexGuard aGuard;
    bool bFound = false;
    Reference<XNameAccess> xMembers = GetMembers();
    if (xMembers.is())
    {
        Reference<XIndexAccess> xMembersIndex(new ScNameToIndexAccess( xMembers ));
        sal_Int32 nCount = xMembersIndex->getCount();
        sal_Int32 nItem = 0;
        while (nItem < nCount && !bFound )
        {
            Reference<XNamed> xMember(xMembersIndex->getByIndex(nItem), UNO_QUERY);
            if (xMember.is() && aName == xMember->getName())
                bFound = true;
            else
                nItem++;
        }
    }
    return bFound;
}
 
// XEnumerationAccess
 
Reference<XEnumeration> SAL_CALL ScDataPilotItemsObj::createEnumeration()
{
    SolarMutexGuard aGuard;
    return new ScIndexEnumeration(this, u"com.sun.star.sheet.DataPilotItemsEnumeration"_ustr);
}
 
// XIndexAccess
 
sal_Int32 SAL_CALL ScDataPilotItemsObj::getCount()
{
    SolarMutexGuard aGuard;
    return GetMemberCount();
}
 
Any SAL_CALL ScDataPilotItemsObj::getByIndex( sal_Int32 nIndex )
{
    SolarMutexGuard aGuard;
    Reference< XPropertySet > xItem( GetObjectByIndex_Impl( nIndex ) );
    if (!xItem.is())
        throw IndexOutOfBoundsException();
    return Any( xItem );
}
 
uno::Type SAL_CALL ScDataPilotItemsObj::getElementType()
{
    return cppu::UnoType<XPropertySet>::get();
}
 
sal_Bool SAL_CALL ScDataPilotItemsObj::hasElements()
{
    SolarMutexGuard aGuard;
    return ( getCount() != 0 );
}
 
ScDataPilotItemObj::ScDataPilotItemObj( ScDataPilotDescriptorBase& rParent, const ScFieldIdentifier& rFieldId, sal_Int32 nIndex ) :
    ScDataPilotChildObjBase( rParent, rFieldId ),
    maPropSet( lcl_GetDataPilotItemMap() ),
    mnIndex( nIndex )
{
}
 
ScDataPilotItemObj::~ScDataPilotItemObj()
{
}
 
                            // XNamed
OUString SAL_CALL ScDataPilotItemObj::getName()
{
    SolarMutexGuard aGuard;
    OUString sRet;
    Reference<XNameAccess> xMembers = GetMembers();
    if (xMembers.is())
    {
        Reference<XIndexAccess> xMembersIndex(new ScNameToIndexAccess( xMembers ));
        sal_Int32 nCount = xMembersIndex->getCount();
        if (mnIndex < nCount)
        {
            Reference<XNamed> xMember(xMembersIndex->getByIndex(mnIndex), UNO_QUERY);
            sRet = xMember->getName();
        }
    }
    return sRet;
}
 
void SAL_CALL ScDataPilotItemObj::setName( const OUString& /* aName */ )
{
}
 
                            // XPropertySet
Reference< XPropertySetInfo >
                            SAL_CALL ScDataPilotItemObj::getPropertySetInfo(  )
{
    SolarMutexGuard aGuard;
    static Reference<XPropertySetInfo> aRef =
        new SfxItemPropertySetInfo( maPropSet.getPropertyMap() );
    return aRef;
}
 
void SAL_CALL ScDataPilotItemObj::setPropertyValue( const OUString& aPropertyName, const Any& aValue )
{
    SolarMutexGuard aGuard;
    ScDPObject* pDPObj = nullptr;
    ScDPSaveDimension* pDim = GetDPDimension( &pDPObj );
    if(!pDim)
        return;
 
    Reference<XNameAccess> xMembers = GetMembers();
    if( !xMembers.is() )
        return;
 
    Reference<XIndexAccess> xMembersIndex( new ScNameToIndexAccess( xMembers ) );
    sal_Int32 nCount = xMembersIndex->getCount();
    if( mnIndex >= nCount )
        return;
 
    Reference<XNamed> xMember(xMembersIndex->getByIndex(mnIndex), UNO_QUERY);
    OUString sName(xMember->getName());
    ScDPSaveMember* pMember = pDim->GetMemberByName(sName);
    if (!pMember)
        return;
 
    bool bGetNewIndex = false;
    if ( aPropertyName == SC_UNONAME_SHOWDETAIL )
        pMember->SetShowDetails(cppu::any2bool(aValue));
    else if ( aPropertyName == SC_UNONAME_ISHIDDEN )
        pMember->SetIsVisible(!cppu::any2bool(aValue));
    else if ( aPropertyName == SC_UNONAME_POS )
    {
        sal_Int32 nNewPos = 0;
        if ( !( aValue >>= nNewPos ) || nNewPos < 0 || nNewPos >= nCount )
            throw IllegalArgumentException();
 
        pDim->SetMemberPosition( sName, nNewPos );
        // get new effective index (depends on sorting mode, which isn't modified)
        bGetNewIndex = true;
 
    }
    SetDPObject( pDPObj );
 
    if ( bGetNewIndex )     // after SetDPObject, get the new index
    {
        Sequence< OUString > aItemNames = xMembers->getElementNames();
        sal_Int32 nItemCount = aItemNames.getLength();
        for (sal_Int32 nItem=0; nItem<nItemCount; ++nItem)
            if (aItemNames[nItem] == sName)
                mnIndex = nItem;
    }
}
 
Any SAL_CALL ScDataPilotItemObj::getPropertyValue( const OUString& aPropertyName )
{
    SolarMutexGuard aGuard;
    Any aRet;
    if( ScDPSaveDimension* pDim = GetDPDimension() )
    {
        Reference< XNameAccess > xMembers = GetMembers();
        if( xMembers.is() )
        {
            Reference< XIndexAccess > xMembersIndex( new ScNameToIndexAccess( xMembers ) );
            sal_Int32 nCount = xMembersIndex->getCount();
            if( mnIndex < nCount )
            {
                Reference< XNamed > xMember( xMembersIndex->getByIndex( mnIndex ), UNO_QUERY );
                OUString sName( xMember->getName() );
                ScDPSaveMember* pMember = pDim->GetExistingMemberByName( sName );
                if ( aPropertyName == SC_UNONAME_SHOWDETAIL )
                {
                    if (pMember && pMember->HasShowDetails())
                    {
                        aRet <<= pMember->GetShowDetails();
                    }
                    else
                    {
                        Reference< XPropertySet > xMemberProps( xMember, UNO_QUERY );
                        if( xMemberProps.is() )
                            aRet = xMemberProps->getPropertyValue( SC_UNO_DP_SHOWDETAILS );
                        else
                            aRet <<= true;
                    }
                }
                else if ( aPropertyName == SC_UNONAME_ISHIDDEN )
                {
                    if (pMember && pMember->HasIsVisible())
                    {
                        aRet <<= !pMember->GetIsVisible();
                    }
                    else
                    {
                        Reference< XPropertySet > xMemberProps( xMember, UNO_QUERY );
                        if( xMemberProps.is() )
                            aRet <<= !cppu::any2bool( xMemberProps->getPropertyValue( SC_UNO_DP_ISVISIBLE ) );
                        else
                            aRet <<= false;
                    }
                }
                else if ( aPropertyName == SC_UNONAME_POS )
                {
                    aRet <<= mnIndex;
                }
            }
        }
    }
    return aRet;
}
 
void SAL_CALL ScDataPilotItemObj::addPropertyChangeListener(
        const OUString& /* aPropertyName */, const Reference< XPropertyChangeListener >& /* xListener */ )
{
}
 
void SAL_CALL ScDataPilotItemObj::removePropertyChangeListener(
        const OUString& /* aPropertyName */, const Reference< XPropertyChangeListener >& /* aListener */ )
{
}
 
void SAL_CALL ScDataPilotItemObj::addVetoableChangeListener(
        const OUString& /* PropertyName */, const Reference< XVetoableChangeListener >& /* aListener */ )
{
}
 
void SAL_CALL ScDataPilotItemObj::removeVetoableChangeListener(
        const OUString& /* PropertyName */, const Reference< XVetoableChangeListener >& /* aListener */ )
{
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V524 It is odd that the body of 'removePropertyChangeListener' function is fully equivalent to the body of 'addPropertyChangeListener' function.

V524 It is odd that the body of 'removeVetoableChangeListener' function is fully equivalent to the body of 'addVetoableChangeListener' function.

V1029 Numeric Truncation Error. Return value of the 'size' function is written to the 16-bit variable.