/* -*- 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 <o3tl/safeint.hxx>
#include <svl/hint.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/sheet/NamedRangeFlag.hpp>
#include <com/sun/star/awt/XBitmap.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
 
#include <comphelper/servicehelper.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <unotools/charclass.hxx>
 
#include <nameuno.hxx>
#include <miscuno.hxx>
#include <cellsuno.hxx>
#include <convuno.hxx>
#include <targuno.hxx>
#include <tokenuno.hxx>
#include <tokenarray.hxx>
#include <docsh.hxx>
#include <docfunc.hxx>
#include <rangenam.hxx>
#include <unonames.hxx>
 
#include <scui_def.hxx>
 
using namespace ::com::sun::star;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Any;
 
static std::span<const SfxItemPropertyMapEntry> lcl_GetNamedRangeMap()
{
    static const SfxItemPropertyMapEntry aNamedRangeMap_Impl[] =
    {
        { SC_UNO_LINKDISPBIT,      0,  cppu::UnoType<awt::XBitmap>::get(), beans::PropertyAttribute::READONLY, 0 },
        { SC_UNO_LINKDISPNAME,     0,  cppu::UnoType<OUString>::get(),                beans::PropertyAttribute::READONLY, 0 },
        { SC_UNONAME_TOKENINDEX,   0,  cppu::UnoType<sal_Int32>::get(),                    beans::PropertyAttribute::READONLY, 0 },
        { SC_UNONAME_ISSHAREDFMLA, 0,  cppu::UnoType<bool>::get(),                          0, 0 },
    };
    return aNamedRangeMap_Impl;
}
 
static std::span<const SfxItemPropertyMapEntry> lcl_GetNamedRangesMap()
{
    static const SfxItemPropertyMapEntry aNamedRangesMap_Impl[] =
    {
        { SC_UNO_MODIFY_BROADCAST, 0,  cppu::UnoType<bool>::get(), 0, 0 },
    };
    return aNamedRangesMap_Impl;
}
 
constexpr OUString SCNAMEDRANGEOBJ_SERVICE = u"com.sun.star.sheet.NamedRange"_ustr;
 
SC_SIMPLE_SERVICE_INFO( ScLabelRangeObj, u"ScLabelRangeObj"_ustr, u"com.sun.star.sheet.LabelRange"_ustr )
SC_SIMPLE_SERVICE_INFO( ScLabelRangesObj, u"ScLabelRangesObj"_ustr, u"com.sun.star.sheet.LabelRanges"_ustr )
SC_SIMPLE_SERVICE_INFO( ScNamedRangesObj, u"ScNamedRangesObj"_ustr, u"com.sun.star.sheet.NamedRanges"_ustr )
 
// Database named ranges are not considered by getCount, hasByName, removeByName and getElementNames
// Note that hidden named ranges are considered by these methods
static bool lcl_UserVisibleName(const ScRangeData& rData)
{
    //! as method to ScRangeData
    return !rData.HasType(ScRangeData::Type::Database);
}
 
ScNamedRangeObj::ScNamedRangeObj( rtl::Reference< ScNamedRangesObj > xParent, ScDocShell* pDocSh, OUString aNm, Reference<container::XNamed> const & xSheet):
    mxParent(std::move(xParent)),
    pDocShell( pDocSh ),
    aName(std::move( aNm )),
    mxSheet( xSheet )
{
    pDocShell->GetDocument().AddUnoObject(*this);
}
 
ScNamedRangeObj::~ScNamedRangeObj()
{
    SolarMutexGuard g;
 
    if (pDocShell)
        pDocShell->GetDocument().RemoveUnoObject(*this);
}
 
void ScNamedRangeObj::Notify( SfxBroadcaster&, const SfxHint& rHint )
{
    // reference update is of no interest
 
    if ( rHint.GetId() == SfxHintId::Dying )
        pDocShell = nullptr;       // became invalid
}
 
// Helper functions
 
ScRangeData* ScNamedRangeObj::GetRangeData_Impl()
{
    ScRangeData* pRet = nullptr;
    if (pDocShell)
    {
        ScRangeName* pNames;
        SCTAB nTab = GetTab_Impl();
        if (nTab >= 0)
            pNames = pDocShell->GetDocument().GetRangeName(nTab);
        else
            pNames = pDocShell->GetDocument().GetRangeName();
        if (pNames)
        {
            pRet = pNames->findByUpperName(ScGlobal::getCharClass().uppercase(aName));
            if (pRet)
                pRet->ValidateTabRefs();        // adjust relative tab refs to valid tables
        }
    }
    return pRet;
}
 
SCTAB ScNamedRangeObj::GetTab_Impl()
{
    if (mxSheet.is())
    {
        if (!pDocShell)
            return -2;
        ScDocument& rDoc = pDocShell->GetDocument();
        SCTAB nTab;
        OUString sName = mxSheet->getName();
        bool bFound = rDoc.GetTable(sName, nTab);
        assert(bFound); (void)bFound;   // fouled up?
        return nTab;
    }
    else
        return -1;//global range name
}
 
// sheet::XNamedRange
 
void ScNamedRangeObj::Modify_Impl( const OUString* pNewName, const ScTokenArray* pNewTokens, const OUString* pNewContent,
                                    const ScAddress* pNewPos, const ScRangeData::Type* pNewType,
                                    const formula::FormulaGrammar::Grammar eGrammar )
{
    if (!pDocShell)
        return;
 
    ScDocument& rDoc = pDocShell->GetDocument();
    ScRangeName* pNames;
    SCTAB nTab = GetTab_Impl();
    if (nTab >= 0)
        pNames = rDoc.GetRangeName(nTab);
    else
        pNames = rDoc.GetRangeName();
    if (!pNames)
        return;
 
    const ScRangeData* pOld = pNames->findByUpperName(ScGlobal::getCharClass().uppercase(aName));
    if (!pOld)
        return;
 
    std::unique_ptr<ScRangeName> pNewRanges(new ScRangeName(*pNames));
 
    OUString aInsName = pOld->GetName();
    if (pNewName)
        aInsName = *pNewName;
 
    // Content string based => no problems with changed positions and such.
    OUString aContent = pOld->GetSymbol(eGrammar);
    if (pNewContent)
        aContent = *pNewContent;
 
    ScAddress aPos = pOld->GetPos();
    if (pNewPos)
        aPos = *pNewPos;
 
    ScRangeData::Type nType = pOld->GetType();
    if (pNewType)
        nType = *pNewType;
 
    ScRangeData* pNew = nullptr;
    if (pNewTokens)
        pNew = new ScRangeData( rDoc, aInsName, *pNewTokens, aPos, nType );
    else
        pNew = new ScRangeData( rDoc, aInsName, aContent, aPos, nType, eGrammar );
 
    pNew->SetIndex( pOld->GetIndex() );
 
    pNewRanges->erase(*pOld);
    if (pNewRanges->insert(pNew))
    {
        pDocShell->GetDocFunc().SetNewRangeNames(std::move(pNewRanges), mxParent->IsModifyAndBroadcast(), nTab);
 
        aName = aInsName;   //! broadcast?
    }
    else
    {
        pNew = nullptr;        //! uno::Exception/Error or something
    }
}
 
OUString SAL_CALL ScNamedRangeObj::getName()
{
    SolarMutexGuard aGuard;
    return aName;
}
 
void SAL_CALL ScNamedRangeObj::setName( const OUString& aNewName )
{
    SolarMutexGuard aGuard;
    //! adapt formulas ?????
 
    OUString aNewStr(aNewName);
    // GRAM_API for API compatibility.
    Modify_Impl( &aNewStr, nullptr, nullptr, nullptr, nullptr,formula::FormulaGrammar::GRAM_API );
 
    if ( aName != aNewStr )                 // some error occurred...
        throw uno::RuntimeException();      // no other exceptions specified
}
 
OUString SAL_CALL ScNamedRangeObj::getContent()
{
    SolarMutexGuard aGuard;
    OUString aContent;
    ScRangeData* pData = GetRangeData_Impl();
    if (pData)
        // GRAM_API for API compatibility.
        aContent = pData->GetSymbol(formula::FormulaGrammar::GRAM_API);
    return aContent;
}
 
void SAL_CALL ScNamedRangeObj::setContent( const OUString& aContent )
{
    SolarMutexGuard aGuard;
    OUString aContStr(aContent);
    // GRAM_API for API compatibility.
    Modify_Impl( nullptr, nullptr, &aContStr, nullptr, nullptr,formula::FormulaGrammar::GRAM_API );
}
 
table::CellAddress SAL_CALL ScNamedRangeObj::getReferencePosition()
{
    SolarMutexGuard aGuard;
    ScAddress aPos;
    ScRangeData* pData = GetRangeData_Impl();
    if (pData)
        aPos = pData->GetPos();
    table::CellAddress aAddress;
    aAddress.Column = aPos.Col();
    aAddress.Row    = aPos.Row();
    aAddress.Sheet  = aPos.Tab();
    if (pDocShell)
    {
        SCTAB nDocTabs = pDocShell->GetDocument().GetTableCount();
        if ( aAddress.Sheet >= nDocTabs && nDocTabs > 0 )
        {
            //  Even after ValidateTabRefs, the position can be invalid if
            //  the content points to preceding tables. The resulting string
            //  is invalid in any case, so the position is just shifted.
            aAddress.Sheet = nDocTabs - 1;
        }
    }
    return aAddress;
}
 
void SAL_CALL ScNamedRangeObj::setReferencePosition( const table::CellAddress& aReferencePosition )
{
    SolarMutexGuard aGuard;
    ScAddress aPos( static_cast<SCCOL>(aReferencePosition.Column), static_cast<SCROW>(aReferencePosition.Row), aReferencePosition.Sheet );
    // GRAM_API for API compatibility.
    Modify_Impl( nullptr, nullptr, nullptr, &aPos, nullptr,formula::FormulaGrammar::GRAM_API );
}
 
sal_Int32 SAL_CALL ScNamedRangeObj::getType()
{
    SolarMutexGuard aGuard;
    sal_Int32 nType=0;
    ScRangeData* pData = GetRangeData_Impl();
    if (pData)
    {
        // do not return internal ScRangeData::Type flags
        if ( pData->HasType(ScRangeData::Type::Criteria) )  nType |= sheet::NamedRangeFlag::FILTER_CRITERIA;
        if ( pData->HasType(ScRangeData::Type::PrintArea) ) nType |= sheet::NamedRangeFlag::PRINT_AREA;
        if ( pData->HasType(ScRangeData::Type::ColHeader) ) nType |= sheet::NamedRangeFlag::COLUMN_HEADER;
        if ( pData->HasType(ScRangeData::Type::RowHeader) ) nType |= sheet::NamedRangeFlag::ROW_HEADER;
        if ( pData->HasType(ScRangeData::Type::Hidden) )    nType |= sheet::NamedRangeFlag::HIDDEN;
    }
    return nType;
}
 
void SAL_CALL ScNamedRangeObj::setType( sal_Int32 nUnoType )
{
    SolarMutexGuard aGuard;
    ScRangeData::Type nNewType = ScRangeData::Type::Name;
    if ( nUnoType & sheet::NamedRangeFlag::FILTER_CRITERIA )    nNewType |= ScRangeData::Type::Criteria;
    if ( nUnoType & sheet::NamedRangeFlag::PRINT_AREA )         nNewType |= ScRangeData::Type::PrintArea;
    if ( nUnoType & sheet::NamedRangeFlag::COLUMN_HEADER )      nNewType |= ScRangeData::Type::ColHeader;
    if ( nUnoType & sheet::NamedRangeFlag::ROW_HEADER )         nNewType |= ScRangeData::Type::RowHeader;
    if ( nUnoType & sheet::NamedRangeFlag::HIDDEN )             nNewType |= ScRangeData::Type::Hidden;
 
    // GRAM_API for API compatibility.
    Modify_Impl( nullptr, nullptr, nullptr, nullptr, &nNewType,formula::FormulaGrammar::GRAM_API );
}
 
// XFormulaTokens
 
uno::Sequence<sheet::FormulaToken> SAL_CALL ScNamedRangeObj::getTokens()
{
    SolarMutexGuard aGuard;
    uno::Sequence<sheet::FormulaToken> aSequence;
    ScRangeData* pData = GetRangeData_Impl();
    if (pData && pDocShell)
    {
        ScTokenArray* pTokenArray = pData->GetCode();
        if ( pTokenArray )
            ScTokenConversion::ConvertToTokenSequence( pDocShell->GetDocument(), aSequence, *pTokenArray );
    }
    return aSequence;
}
 
void SAL_CALL ScNamedRangeObj::setTokens( const uno::Sequence<sheet::FormulaToken>& rTokens )
{
    SolarMutexGuard aGuard;
    if( pDocShell )
    {
        ScTokenArray aTokenArray(pDocShell->GetDocument());
        (void)ScTokenConversion::ConvertToTokenArray( pDocShell->GetDocument(), aTokenArray, rTokens );
        // GRAM_API for API compatibility.
        Modify_Impl( nullptr, &aTokenArray, nullptr, nullptr, nullptr, formula::FormulaGrammar::GRAM_API );
    }
}
 
// XCellRangeSource
 
uno::Reference<table::XCellRange> SAL_CALL ScNamedRangeObj::getReferredCells()
{
    SolarMutexGuard aGuard;
    ScRange aRange;
    ScRangeData* pData = GetRangeData_Impl();
    if ( pData && pData->IsValidReference( aRange ) )
    {
        //! static function to create ScCellObj/ScCellRangeObj at ScCellRangeObj ???
 
        if ( aRange.aStart == aRange.aEnd )
            return new ScCellObj( pDocShell, aRange.aStart );
        else
            return new ScCellRangeObj( pDocShell, aRange );
    }
    return nullptr;
}
 
// beans::XPropertySet
 
uno::Reference<beans::XPropertySetInfo> SAL_CALL ScNamedRangeObj::getPropertySetInfo()
{
    static uno::Reference< beans::XPropertySetInfo >  aRef(new SfxItemPropertySetInfo( lcl_GetNamedRangeMap() ));
    return aRef;
}
 
void SAL_CALL ScNamedRangeObj::setPropertyValue(
                        const OUString& rPropertyName, const uno::Any& /*aValue*/ )
{
    if ( rPropertyName == SC_UNONAME_ISSHAREDFMLA )
    {
        // Ignore this.
    }
}
 
uno::Any SAL_CALL ScNamedRangeObj::getPropertyValue( const OUString& rPropertyName )
{
    SolarMutexGuard aGuard;
    uno::Any aRet;
    if ( rPropertyName == SC_UNO_LINKDISPBIT )
    {
        //  no target bitmaps for individual entries (would be all equal)
        // ScLinkTargetTypeObj::SetLinkTargetBitmap( aRet, SC_LINKTARGETTYPE_RANGENAME );
    }
    else if ( rPropertyName == SC_UNO_LINKDISPNAME )
        aRet <<= aName;
    else if ( rPropertyName == SC_UNONAME_TOKENINDEX )
    {
        // get index for use in formula tokens (read-only)
        ScRangeData* pData = GetRangeData_Impl();
        if (pData)
            aRet <<= static_cast<sal_Int32>(pData->GetIndex());
    }
    else if ( rPropertyName == SC_UNONAME_ISSHAREDFMLA )
    {
        if (GetRangeData_Impl())
            aRet <<= false;
    }
    return aRet;
}
 
SC_IMPL_DUMMY_PROPERTY_LISTENER( ScNamedRangeObj )
 
// lang::XServiceInfo
 
OUString SAL_CALL ScNamedRangeObj::getImplementationName()
{
    return u"ScNamedRangeObj"_ustr;
}
 
sal_Bool SAL_CALL ScNamedRangeObj::supportsService( const OUString& rServiceName )
{
    return cppu::supportsService(this, rServiceName);
}
 
uno::Sequence<OUString> SAL_CALL ScNamedRangeObj::getSupportedServiceNames()
{
    return {SCNAMEDRANGEOBJ_SERVICE, SCLINKTARGET_SERVICE};
}
 
ScNamedRangesObj::ScNamedRangesObj(ScDocShell* pDocSh) :
    mbModifyAndBroadcast(true),
    pDocShell( pDocSh )
{
    pDocShell->GetDocument().AddUnoObject(*this);
}
 
ScNamedRangesObj::~ScNamedRangesObj()
{
    SolarMutexGuard g;
 
    if (pDocShell)
        pDocShell->GetDocument().RemoveUnoObject(*this);
}
 
void ScNamedRangesObj::Notify( SfxBroadcaster&, const SfxHint& rHint )
{
    // reference update is of no interest
 
    if ( rHint.GetId() == SfxHintId::Dying )
    {
        pDocShell = nullptr;       // became invalid
    }
}
 
// sheet::XNamedRanges
 
void SAL_CALL ScNamedRangesObj::addNewByName( const OUString& aName,
    const OUString& aContent, const table::CellAddress& aPosition,
    sal_Int32 nUnoType )
{
    SolarMutexGuard aGuard;
    ScAddress aPos( static_cast<SCCOL>(aPosition.Column), static_cast<SCROW>(aPosition.Row), aPosition.Sheet );
 
    ScRangeData::Type nNewType = ScRangeData::Type::Name;
    if ( nUnoType & sheet::NamedRangeFlag::FILTER_CRITERIA )    nNewType |= ScRangeData::Type::Criteria;
    if ( nUnoType & sheet::NamedRangeFlag::PRINT_AREA )         nNewType |= ScRangeData::Type::PrintArea;
    if ( nUnoType & sheet::NamedRangeFlag::COLUMN_HEADER )      nNewType |= ScRangeData::Type::ColHeader;
    if ( nUnoType & sheet::NamedRangeFlag::ROW_HEADER )         nNewType |= ScRangeData::Type::RowHeader;
    if ( nUnoType & sheet::NamedRangeFlag::HIDDEN )             nNewType |= ScRangeData::Type::Hidden;
 
    bool bDone = false;
    if (pDocShell)
    {
        ScDocument& rDoc = pDocShell->GetDocument();
        // tdf#119457 - check for a valid range name and cell reference
        switch (ScRangeData::IsNameValid(aName, rDoc))
        {
            case ScRangeData::IsNameValidType::NAME_INVALID_CELL_REF:
                throw uno::RuntimeException(
                    u"Invalid name. Reference to a cell, or a range of cells not allowed"_ustr,
                    getXWeak());
                break;
            case ScRangeData::IsNameValidType::NAME_INVALID_BAD_STRING:
                throw uno::RuntimeException(
                    u"Invalid name. Start with a letter, use only letters, numbers and underscore"_ustr,
                    getXWeak());
                break;
            case ScRangeData::IsNameValidType::NAME_VALID:
                if (ScRangeName* pNames = GetRangeName_Impl();
                    pNames
                    && !pNames->findByUpperName(ScGlobal::getCharClass().uppercase(aName)))
                {
                    std::unique_ptr<ScRangeName> pNewRanges(new ScRangeName( *pNames ));
                    // GRAM_API for API compatibility.
                    ScRangeData* pNew = new ScRangeData( rDoc, aName, aContent,
                                                        aPos, nNewType,formula::FormulaGrammar::GRAM_API );
                    if ( pNewRanges->insert(pNew) )
                    {
                        pDocShell->GetDocFunc().SetNewRangeNames(std::move(pNewRanges), mbModifyAndBroadcast, GetTab_Impl());
                        bDone = true;
                    }
                    else
                    {
                        pNew = nullptr;
                    }
                }
        }
    }
 
    if (!bDone)
        throw uno::RuntimeException();      // no other exceptions specified
}
 
void SAL_CALL ScNamedRangesObj::addNewFromTitles( const table::CellRangeAddress& aSource,
                                    sheet::Border aBorder )
{
    SolarMutexGuard aGuard;
    //! this cannot be an enum, because multiple bits can be set !!!
 
    bool bTop    = ( aBorder == sheet::Border_TOP );
    bool bLeft   = ( aBorder == sheet::Border_LEFT );
    bool bBottom = ( aBorder == sheet::Border_BOTTOM );
    bool bRight  = ( aBorder == sheet::Border_RIGHT );
 
    ScRange aRange;
    ScUnoConversion::FillScRange( aRange, aSource );
 
    CreateNameFlags nFlags = CreateNameFlags::NONE;
    if (bTop)    nFlags |= CreateNameFlags::Top;
    if (bLeft)   nFlags |= CreateNameFlags::Left;
    if (bBottom) nFlags |= CreateNameFlags::Bottom;
    if (bRight)  nFlags |= CreateNameFlags::Right;
 
    if (nFlags != CreateNameFlags::NONE)
        pDocShell->GetDocFunc().CreateNames( aRange, nFlags, true, GetTab_Impl() );
}
 
void SAL_CALL ScNamedRangesObj::removeByName( const OUString& aName )
{
    SolarMutexGuard aGuard;
    bool bDone = false;
    if (pDocShell)
    {
        ScRangeName* pNames = GetRangeName_Impl();
        if (pNames)
        {
            const ScRangeData* pData = pNames->findByUpperName(ScGlobal::getCharClass().uppercase(aName));
            if (pData && lcl_UserVisibleName(*pData))
            {
                std::unique_ptr<ScRangeName> pNewRanges(new ScRangeName(*pNames));
                pNewRanges->erase(*pData);
                pDocShell->GetDocFunc().SetNewRangeNames( std::move(pNewRanges), mbModifyAndBroadcast, GetTab_Impl());
                bDone = true;
            }
        }
    }
 
    if (!bDone)
        throw uno::RuntimeException();      // no other exceptions specified
}
 
void SAL_CALL ScNamedRangesObj::outputList( const table::CellAddress& aOutputPosition )
{
    SolarMutexGuard aGuard;
    ScAddress aPos( static_cast<SCCOL>(aOutputPosition.Column), static_cast<SCROW>(aOutputPosition.Row), aOutputPosition.Sheet );
    if (pDocShell)
        pDocShell->GetDocFunc().InsertNameList( aPos, true );
}
 
// container::XEnumerationAccess
 
uno::Reference<container::XEnumeration> SAL_CALL ScNamedRangesObj::createEnumeration()
{
    SolarMutexGuard aGuard;
    return new ScIndexEnumeration(this, u"com.sun.star.sheet.NamedRangesEnumeration"_ustr);
}
 
// container::XIndexAccess
 
sal_Int32 SAL_CALL ScNamedRangesObj::getCount()
{
    SolarMutexGuard aGuard;
    tools::Long nRet = 0;
    if (pDocShell)
    {
        ScRangeName* pNames = GetRangeName_Impl();
        if (pNames)
        {
            for (const auto& rName : *pNames)
                if (lcl_UserVisibleName(*rName.second))
                    ++nRet;
        }
    }
    return nRet;
}
 
uno::Any SAL_CALL ScNamedRangesObj::getByIndex( sal_Int32 nIndex )
{
    SolarMutexGuard aGuard;
    uno::Reference< sheet::XNamedRange >  xRange(GetObjectByIndex_Impl(static_cast<sal_uInt16>(nIndex)));
    if ( !xRange.is() )
        throw lang::IndexOutOfBoundsException();
 
    return uno::Any(xRange);
}
 
uno::Type SAL_CALL ScNamedRangesObj::getElementType()
{
    return cppu::UnoType<sheet::XNamedRange>::get();   // must be suitable for getByIndex
}
 
sal_Bool SAL_CALL ScNamedRangesObj::hasElements()
{
    SolarMutexGuard aGuard;
    return ( getCount() != 0 );
}
 
Reference<beans::XPropertySetInfo> SAL_CALL ScNamedRangesObj::getPropertySetInfo()
{
    static Reference<beans::XPropertySetInfo> aRef(
        new SfxItemPropertySetInfo(lcl_GetNamedRangesMap()));
    return aRef;
}
 
void SAL_CALL ScNamedRangesObj::setPropertyValue(
                        const OUString& rPropertyName, const uno::Any& aValue )
{
    if ( rPropertyName == SC_UNO_MODIFY_BROADCAST )
    {
        aValue >>= mbModifyAndBroadcast;
    }
}
 
Any SAL_CALL ScNamedRangesObj::getPropertyValue( const OUString& rPropertyName )
{
    Any aRet;
    if ( rPropertyName == SC_UNO_MODIFY_BROADCAST )
    {
        aRet <<= mbModifyAndBroadcast;
    }
 
    return aRet;
}
 
SC_IMPL_DUMMY_PROPERTY_LISTENER( ScNamedRangesObj )
 
uno::Any SAL_CALL ScNamedRangesObj::getByName( const OUString& aName )
{
    SolarMutexGuard aGuard;
    uno::Reference< sheet::XNamedRange >  xRange(GetObjectByName_Impl(aName));
    if ( !xRange.is() )
        throw container::NoSuchElementException();
 
    return uno::Any(xRange);
}
 
uno::Sequence<OUString> SAL_CALL ScNamedRangesObj::getElementNames()
{
    SolarMutexGuard aGuard;
    if (pDocShell)
    {
        ScRangeName* pNames = GetRangeName_Impl();
        if (pNames)
        {
            tools::Long nVisCount = getCount();            // names with lcl_UserVisibleName
            uno::Sequence<OUString> aSeq(nVisCount);
            OUString* pAry = aSeq.getArray();
            sal_uInt16 nVisPos = 0;
            for (const auto& rName : *pNames)
            {
                if (lcl_UserVisibleName(*rName.second))
                    pAry[nVisPos++] = rName.second->GetName();
            }
            return aSeq;
        }
    }
    return {};
}
 
sal_Bool SAL_CALL ScNamedRangesObj::hasByName( const OUString& aName )
{
    SolarMutexGuard aGuard;
    if (pDocShell)
    {
        ScRangeName* pNames = GetRangeName_Impl();
        if (pNames)
        {
            const ScRangeData* pData = pNames->findByUpperName(ScGlobal::getCharClass().uppercase(aName));
            if (pData && lcl_UserVisibleName(*pData))
                return true;
        }
    }
    return false;
}
 
/** called from the XActionLockable interface methods on initial locking */
void ScNamedRangesObj::lock()
{
    pDocShell->GetDocument().PreprocessRangeNameUpdate();
}
 
/** called from the XActionLockable interface methods on final unlock */
void ScNamedRangesObj::unlock()
{
    pDocShell->GetDocument().CompileHybridFormula();
}
 
// document::XActionLockable
 
sal_Bool ScNamedRangesObj::isActionLocked()
{
    SolarMutexGuard aGuard;
    return pDocShell->GetDocument().GetNamedRangesLockCount() != 0;
}
 
void ScNamedRangesObj::addActionLock()
{
    SolarMutexGuard aGuard;
    ScDocument& rDoc = pDocShell->GetDocument();
    sal_Int16 nLockCount = rDoc.GetNamedRangesLockCount();
    ++nLockCount;
    if ( nLockCount == 1 )
    {
        lock();
    }
    rDoc.SetNamedRangesLockCount( nLockCount );
}
 
void ScNamedRangesObj::removeActionLock()
{
    SolarMutexGuard aGuard;
    ScDocument& rDoc = pDocShell->GetDocument();
    sal_Int16 nLockCount = rDoc.GetNamedRangesLockCount();
    if ( nLockCount > 0 )
    {
        --nLockCount;
        if ( nLockCount == 0 )
        {
            unlock();
        }
        rDoc.SetNamedRangesLockCount( nLockCount );
    }
}
 
void ScNamedRangesObj::setActionLocks( sal_Int16 nLock )
{
    SolarMutexGuard aGuard;
    if ( nLock < 0 )
        return;
 
    ScDocument& rDoc = pDocShell->GetDocument();
    sal_Int16 nLockCount = rDoc.GetNamedRangesLockCount();
    if ( nLock == 0 && nLockCount > 0 )
    {
        unlock();
    }
    if ( nLock > 0 && nLockCount == 0 )
    {
        lock();
    }
    rDoc.SetNamedRangesLockCount( nLock );
}
 
sal_Int16 ScNamedRangesObj::resetActionLocks()
{
    SolarMutexGuard aGuard;
    ScDocument& rDoc = pDocShell->GetDocument();
    sal_Int16 nLockCount = rDoc.GetNamedRangesLockCount();
    if ( nLockCount > 0 )
    {
        unlock();
    }
    rDoc.SetNamedRangesLockCount( 0 );
    return nLockCount;
}
 
ScGlobalNamedRangesObj::ScGlobalNamedRangesObj(ScDocShell* pDocSh)
    : ScNamedRangesObj(pDocSh)
{
 
}
 
ScGlobalNamedRangesObj::~ScGlobalNamedRangesObj()
{
 
}
 
rtl::Reference<ScNamedRangeObj> ScGlobalNamedRangesObj::GetObjectByIndex_Impl(sal_uInt16 nIndex)
{
    if (!pDocShell)
        return nullptr;
 
    ScRangeName* pNames = pDocShell->GetDocument().GetRangeName();
    if (!pNames)
        return nullptr;
 
    sal_uInt16 nPos = 0;
    for (const auto& rName : *pNames)
    {
        if (lcl_UserVisibleName(*rName.second))
        {
            if (nPos == nIndex)
                return new ScNamedRangeObj(this, pDocShell, rName.second->GetName());
        }
        ++nPos;
    }
    return nullptr;
}
 
rtl::Reference<ScNamedRangeObj> ScGlobalNamedRangesObj::GetObjectByName_Impl(const OUString& aName)
{
    if ( pDocShell && hasByName(aName) )
        return new ScNamedRangeObj(this, pDocShell, aName);
    return nullptr;
}
 
ScRangeName* ScGlobalNamedRangesObj::GetRangeName_Impl()
{
    return pDocShell->GetDocument().GetRangeName();
}
 
SCTAB ScGlobalNamedRangesObj::GetTab_Impl()
{
    return -1;
}
 
ScLocalNamedRangesObj::ScLocalNamedRangesObj( ScDocShell* pDocSh, uno::Reference<container::XNamed> xSheet )
    : ScNamedRangesObj(pDocSh),
    mxSheet(std::move(xSheet))
{
 
}
 
ScLocalNamedRangesObj::~ScLocalNamedRangesObj()
{
 
}
 
rtl::Reference<ScNamedRangeObj> ScLocalNamedRangesObj::GetObjectByName_Impl(const OUString& aName)
{
    if ( pDocShell && hasByName( aName ) )
        return new ScNamedRangeObj( this, pDocShell, aName, mxSheet);
    return nullptr;
 
}
 
rtl::Reference<ScNamedRangeObj> ScLocalNamedRangesObj::GetObjectByIndex_Impl( sal_uInt16 nIndex )
{
    if (!pDocShell)
        return nullptr;
 
    OUString aName = mxSheet->getName();
    ScDocument& rDoc = pDocShell->GetDocument();
    SCTAB nTab;
    if (!rDoc.GetTable(aName, nTab))
        return nullptr;
 
    ScRangeName* pNames = rDoc.GetRangeName( nTab );
    if (!pNames)
        return nullptr;
 
    sal_uInt16 nPos = 0;
    for (const auto& rName : *pNames)
    {
        if (lcl_UserVisibleName(*rName.second))
        {
            if (nPos == nIndex)
                return new ScNamedRangeObj(this, pDocShell, rName.second->GetName(), mxSheet);
        }
        ++nPos;
    }
    return nullptr;
}
 
ScRangeName* ScLocalNamedRangesObj::GetRangeName_Impl()
{
    SCTAB nTab = GetTab_Impl();
    return pDocShell->GetDocument().GetRangeName( nTab );
}
 
SCTAB ScLocalNamedRangesObj::GetTab_Impl()
{
    SCTAB nTab;
    (void)pDocShell->GetDocument().GetTable(mxSheet->getName(), nTab);
    return nTab;
}
 
ScLabelRangeObj::ScLabelRangeObj(ScDocShell* pDocSh, bool bCol, const ScRange& rR) :
    pDocShell( pDocSh ),
    bColumn( bCol ),
    aRange( rR )
{
    pDocShell->GetDocument().AddUnoObject(*this);
}
 
ScLabelRangeObj::~ScLabelRangeObj()
{
    SolarMutexGuard g;
 
    if (pDocShell)
        pDocShell->GetDocument().RemoveUnoObject(*this);
}
 
void ScLabelRangeObj::Notify( SfxBroadcaster&, const SfxHint& rHint )
{
    //! Ref-Update !!!
 
    if ( rHint.GetId() == SfxHintId::Dying )
        pDocShell = nullptr;       // became invalid
}
 
// Helper functions
 
ScRangePair* ScLabelRangeObj::GetData_Impl()
{
    ScRangePair* pRet = nullptr;
    if (pDocShell)
    {
        ScDocument& rDoc = pDocShell->GetDocument();
        ScRangePairList* pList = bColumn ? rDoc.GetColNameRanges() : rDoc.GetRowNameRanges();
        if (pList)
            pRet = pList->Find( aRange );
    }
    return pRet;
}
 
void ScLabelRangeObj::Modify_Impl( const ScRange* pLabel, const ScRange* pData )
{
    if (!pDocShell)
        return;
 
    ScDocument& rDoc = pDocShell->GetDocument();
    ScRangePairList* pOldList = bColumn ? rDoc.GetColNameRanges() : rDoc.GetRowNameRanges();
    if (!pOldList)
        return;
 
    ScRangePairListRef xNewList(pOldList->Clone());
    ScRangePair* pEntry = xNewList->Find( aRange );
    if (!pEntry)
        return;
 
    if ( pLabel )
        pEntry->GetRange(0) = *pLabel;
    if ( pData )
        pEntry->GetRange(1) = *pData;
 
    xNewList->Join( *pEntry, true );
 
    if (bColumn)
        rDoc.GetColNameRangesRef() = std::move(xNewList);
    else
        rDoc.GetRowNameRangesRef() = std::move(xNewList);
 
    rDoc.CompileColRowNameFormula();
    pDocShell->PostPaint( 0,0,0, rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB, PaintPartFlags::Grid );
    pDocShell->SetDocumentModified();
 
    //! Undo ?!?! (here and from dialog)
 
    if ( pLabel )
        aRange = *pLabel;   // adapt object to find range again
}
 
// sheet::XLabelRange
 
table::CellRangeAddress SAL_CALL ScLabelRangeObj::getLabelArea()
{
    SolarMutexGuard aGuard;
    table::CellRangeAddress aRet;
    ScRangePair* pData = GetData_Impl();
    if (pData)
        ScUnoConversion::FillApiRange( aRet, pData->GetRange(0) );
    return aRet;
}
 
void SAL_CALL ScLabelRangeObj::setLabelArea( const table::CellRangeAddress& aLabelArea )
{
    SolarMutexGuard aGuard;
    ScRange aLabelRange;
    ScUnoConversion::FillScRange( aLabelRange, aLabelArea );
    Modify_Impl( &aLabelRange, nullptr );
}
 
table::CellRangeAddress SAL_CALL ScLabelRangeObj::getDataArea()
{
    SolarMutexGuard aGuard;
    table::CellRangeAddress aRet;
    ScRangePair* pData = GetData_Impl();
    if (pData)
        ScUnoConversion::FillApiRange( aRet, pData->GetRange(1) );
    return aRet;
}
 
void SAL_CALL ScLabelRangeObj::setDataArea( const table::CellRangeAddress& aDataArea )
{
    SolarMutexGuard aGuard;
    ScRange aDataRange;
    ScUnoConversion::FillScRange( aDataRange, aDataArea );
    Modify_Impl( nullptr, &aDataRange );
}
 
ScLabelRangesObj::ScLabelRangesObj(ScDocShell* pDocSh, bool bCol) :
    pDocShell( pDocSh ),
    bColumn( bCol )
{
    pDocShell->GetDocument().AddUnoObject(*this);
}
 
ScLabelRangesObj::~ScLabelRangesObj()
{
    SolarMutexGuard g;
 
    if (pDocShell)
        pDocShell->GetDocument().RemoveUnoObject(*this);
}
 
void ScLabelRangesObj::Notify( SfxBroadcaster&, const SfxHint& rHint )
{
    //  reference update is of no interest
 
    if ( rHint.GetId() == SfxHintId::Dying )
    {
        pDocShell = nullptr;       // became invalid
    }
}
 
// sheet::XLabelRanges
 
rtl::Reference<ScLabelRangeObj> ScLabelRangesObj::GetObjectByIndex_Impl(size_t nIndex)
{
    if (pDocShell)
    {
        ScDocument& rDoc = pDocShell->GetDocument();
        ScRangePairList* pList = bColumn ? rDoc.GetColNameRanges() : rDoc.GetRowNameRanges();
        if ( pList && nIndex < pList->size() )
        {
            ScRangePair & rData = (*pList)[nIndex];
            return new ScLabelRangeObj( pDocShell, bColumn, rData.GetRange(0) );
        }
    }
    return nullptr;
}
 
void SAL_CALL ScLabelRangesObj::addNew( const table::CellRangeAddress& aLabelArea,
                                const table::CellRangeAddress& aDataArea )
{
    SolarMutexGuard aGuard;
    if (!pDocShell)
        return;
 
    ScDocument& rDoc = pDocShell->GetDocument();
    ScRangePairList* pOldList = bColumn ? rDoc.GetColNameRanges() : rDoc.GetRowNameRanges();
    if (!pOldList)
        return;
 
    ScRangePairListRef xNewList(pOldList->Clone());
 
    ScRange aLabelRange;
    ScRange aDataRange;
    ScUnoConversion::FillScRange( aLabelRange, aLabelArea );
    ScUnoConversion::FillScRange( aDataRange,  aDataArea );
    xNewList->Join( ScRangePair( aLabelRange, aDataRange ) );
 
    if (bColumn)
        rDoc.GetColNameRangesRef() = std::move(xNewList);
    else
        rDoc.GetRowNameRangesRef() = std::move(xNewList);
 
    rDoc.CompileColRowNameFormula();
    pDocShell->PostPaint( 0,0,0, rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB, PaintPartFlags::Grid );
    pDocShell->SetDocumentModified();
 
    //! Undo ?!?! (here and from dialog)
}
 
void SAL_CALL ScLabelRangesObj::removeByIndex( sal_Int32 nIndex )
{
    SolarMutexGuard aGuard;
    bool bDone = false;
    if (pDocShell)
    {
        ScDocument& rDoc = pDocShell->GetDocument();
        ScRangePairList* pOldList = bColumn ? rDoc.GetColNameRanges() : rDoc.GetRowNameRanges();
 
        if ( pOldList && nIndex >= 0 && o3tl::make_unsigned(nIndex) < pOldList->size() )
        {
            ScRangePairListRef xNewList(pOldList->Clone());
 
            xNewList->Remove( nIndex );
 
            if (bColumn)
                rDoc.GetColNameRangesRef() = std::move(xNewList);
            else
                rDoc.GetRowNameRangesRef() = std::move(xNewList);
 
            rDoc.CompileColRowNameFormula();
            pDocShell->PostPaint( 0,0,0, rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB, PaintPartFlags::Grid );
            pDocShell->SetDocumentModified();
            bDone = true;
 
            //! Undo ?!?! (here and from dialog)
        }
    }
    if (!bDone)
        throw uno::RuntimeException();      // no other exceptions specified
}
 
// container::XEnumerationAccess
 
uno::Reference<container::XEnumeration> SAL_CALL ScLabelRangesObj::createEnumeration()
{
    SolarMutexGuard aGuard;
    return new ScIndexEnumeration(this, u"com.sun.star.sheet.LabelRangesEnumeration"_ustr);
}
 
// container::XIndexAccess
 
sal_Int32 SAL_CALL ScLabelRangesObj::getCount()
{
    SolarMutexGuard aGuard;
    if (pDocShell)
    {
        ScDocument& rDoc = pDocShell->GetDocument();
        ScRangePairList* pList = bColumn ? rDoc.GetColNameRanges() : rDoc.GetRowNameRanges();
        if (pList)
            return pList->size();
    }
    return 0;
}
 
uno::Any SAL_CALL ScLabelRangesObj::getByIndex( sal_Int32 nIndex )
{
    SolarMutexGuard aGuard;
    uno::Reference< sheet::XLabelRange >  xRange(GetObjectByIndex_Impl(static_cast<sal_uInt16>(nIndex)));
    if ( !xRange.is() )
        throw lang::IndexOutOfBoundsException();
 
    return uno::Any(xRange);
}
 
uno::Type SAL_CALL ScLabelRangesObj::getElementType()
{
    return cppu::UnoType<sheet::XLabelRange>::get();   // must be suitable for getByIndex
}
 
sal_Bool SAL_CALL ScLabelRangesObj::hasElements()
{
    SolarMutexGuard aGuard;
    return ( getCount() != 0 );
}
 
/* 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.

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.