/* -*- 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 <AccessibleBase.hxx>
#include "AccessibleChartShape.hxx"
#include <ObjectHierarchy.hxx>
#include <ObjectIdentifier.hxx>
#include <chartview/ExplicitValueProvider.hxx>

#include <com/sun/star/accessibility/AccessibleEventId.hpp>
#include <com/sun/star/accessibility/AccessibleEventObject.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/accessibility/AccessibleRole.hpp>
#include <com/sun/star/drawing/LineStyle.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/view/XSelectionSupplier.hpp>
#include <com/sun/star/chart2/XChartDocument.hpp>
#include <sal/log.hxx>
#include <vcl/svapp.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <vcl/unohelp.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <vcl/window.hxx>
#include <vcl/settings.hxx>
#include <o3tl/functional.hxx>
#include <tools/diagnose_ex.h>
#include <unotools/accessiblestatesethelper.hxx>
#include <unotools/localedatawrapper.hxx>

#include <algorithm>
#include <iterator>

#include "ChartElementFactory.hxx"

using namespace ::com::sun::star;
using namespace ::com::sun::star::accessibility;

using ::com::sun::star::uno::UNO_QUERY;
using ::com::sun::star::uno::Reference;
using ::osl::MutexGuard;
using ::osl::ClearableMutexGuard;
using ::com::sun::star::uno::Any;

namespace chart
{

/** @param bMayHaveChildren is false per default
 */
AccessibleBase::AccessibleBase(
    const AccessibleElementInfo & rAccInfo,
    bool bMayHaveChildren,
    bool bAlwaysTransparent /* default: false */ ) :
        impl::AccessibleBase_Base( m_aMutex ),
        m_bIsDisposed( false ),
        m_bMayHaveChildren( bMayHaveChildren ),
        m_bChildrenInitialized( false ),
        m_nEventNotifierId(0),
        m_xStateSetHelper( new ::utl::AccessibleStateSetHelper() ),
        m_aAccInfo( rAccInfo ),
        m_bAlwaysTransparent( bAlwaysTransparent ),
        m_bStateSetInitialized( false )
{
    // initialize some states
    OSL_ASSERT( m_xStateSetHelper.is() );
    m_xStateSetHelper->AddState( AccessibleStateType::ENABLED );
    m_xStateSetHelper->AddState( AccessibleStateType::SHOWING );
    m_xStateSetHelper->AddState( AccessibleStateType::VISIBLE );
    m_xStateSetHelper->AddState( AccessibleStateType::SELECTABLE );
    m_xStateSetHelper->AddState( AccessibleStateType::FOCUSABLE );
}

AccessibleBase::~AccessibleBase()
{
    OSL_ASSERT( m_bIsDisposed );
}

bool AccessibleBase::CheckDisposeState( bool bThrowException /* default: true */ ) const
{
    if( bThrowException &&
        m_bIsDisposed )
    {
        throw lang::DisposedException("component has state DEFUNC",
            static_cast< uno::XWeak * >( const_cast< AccessibleBase * >( this )));
    }
    return m_bIsDisposed;
}

bool AccessibleBase::NotifyEvent( EventType eEventType, const AccessibleUniqueId & rId )
{
    if( GetId() == rId )
    {
        // event is addressed to this object

        css::uno::Any aEmpty;
        css::uno::Any aSelected;
        aSelected <<= AccessibleStateType::SELECTED;
        switch( eEventType )
        {
            case EventType::GOT_SELECTION:
                {
                    AddState( AccessibleStateType::SELECTED );
                    BroadcastAccEvent( AccessibleEventId::STATE_CHANGED, aSelected, aEmpty );

                    AddState( AccessibleStateType::FOCUSED );
                    aSelected <<= AccessibleStateType::FOCUSED;
                    BroadcastAccEvent( AccessibleEventId::STATE_CHANGED, aSelected, aEmpty, true );

                    SAL_INFO("chart2.accessibility", "Selection acquired by: " << getAccessibleName());
                }
                break;

            case EventType::LOST_SELECTION:
                {
                    RemoveState( AccessibleStateType::SELECTED );
                    BroadcastAccEvent( AccessibleEventId::STATE_CHANGED, aEmpty, aSelected );

                    AddState( AccessibleStateType::FOCUSED );
                    aSelected <<= AccessibleStateType::FOCUSED;
                    BroadcastAccEvent( AccessibleEventId::STATE_CHANGED, aEmpty, aSelected, true );
                    SAL_INFO("chart2.accessibility", "Selection lost by: " << getAccessibleName());
                }
                break;
        }
        return true;
    }
    else if( m_bMayHaveChildren )
    {
        bool bStop = false;

        ClearableMutexGuard aGuard( GetMutex() );
        // make local copy for notification
        ChildListVectorType aLocalChildList( m_aChildList );
        aGuard.clear();

        for (auto const& localChild : aLocalChildList)
        {
            // Note: at this place we must be sure to have an AccessibleBase
            // object in the UNO reference to XAccessible !
            bStop = (*static_cast< AccessibleBase * >
                     ( localChild.get() )).NotifyEvent( eEventType, rId );
            if (bStop)
                break;
        }
        return bStop;
    }

    return false;
}

void AccessibleBase::AddState( sal_Int16 aState )
{
    CheckDisposeState();
    OSL_ASSERT( m_xStateSetHelper.is() );
    m_xStateSetHelper->AddState( aState );
}

void AccessibleBase::RemoveState( sal_Int16 aState )
{
    CheckDisposeState();
    OSL_ASSERT( m_xStateSetHelper.is() );
    m_xStateSetHelper->RemoveState( aState );
}

bool AccessibleBase::UpdateChildren()
{
    bool bMustUpdateChildren = false;
    {
        MutexGuard aGuard( GetMutex() );
        if( ! m_bMayHaveChildren ||
            m_bIsDisposed )
            return false;

        bMustUpdateChildren = ( m_bMayHaveChildren &&
                                ! m_bChildrenInitialized );
    }

    // update unguarded
    if( bMustUpdateChildren )
        m_bChildrenInitialized = ImplUpdateChildren();

    return m_bChildrenInitialized;
}

bool AccessibleBase::ImplUpdateChildren()
{
    bool bResult = false;

    if( m_aAccInfo.m_spObjectHierarchy )
    {
        ObjectHierarchy::tChildContainer aModelChildren(
            m_aAccInfo.m_spObjectHierarchy->getChildren( GetId() ));
        std::vector< ChildOIDMap::key_type > aAccChildren;
        aAccChildren.reserve( aModelChildren.size());
        std::transform( m_aChildOIDMap.begin(), m_aChildOIDMap.end(),
                          std::back_inserter( aAccChildren ),
                          ::o3tl::select1st< ChildOIDMap::value_type >() );

        std::sort( aModelChildren.begin(), aModelChildren.end());

        std::vector< ObjectIdentifier > aChildrenToRemove, aChildrenToAdd;
        std::set_difference( aModelChildren.begin(), aModelChildren.end(),
                               aAccChildren.begin(), aAccChildren.end(),
                               std::back_inserter( aChildrenToAdd ));
        std::set_difference( aAccChildren.begin(), aAccChildren.end(),
                               aModelChildren.begin(), aModelChildren.end(),
                               std::back_inserter( aChildrenToRemove ));

        for (auto const& childToRemove : aChildrenToRemove)
        {
            RemoveChildByOId(childToRemove);
        }

        AccessibleElementInfo aAccInfo( GetInfo());
        aAccInfo.m_pParent = this;

        for (auto const& childToAdd : aChildrenToAdd)
        {
            aAccInfo.m_aOID = childToAdd;
            if ( childToAdd.isAutoGeneratedObject() )
            {
                AddChild( ChartElementFactory::CreateChartElement( aAccInfo ) );
            }
            else if ( childToAdd.isAdditionalShape() )
            {
                AddChild( new AccessibleChartShape( aAccInfo ) );
            }
        }
        bResult = true;
    }

    return bResult;
}

void AccessibleBase::AddChild( AccessibleBase * pChild  )
{
    OSL_ENSURE( pChild != nullptr, "Invalid Child" );
    if( pChild )
    {
        ClearableMutexGuard aGuard( GetMutex() );

        Reference< XAccessible > xChild( pChild );
        m_aChildList.push_back( xChild );

        m_aChildOIDMap[ pChild->GetId() ] = xChild;

        // inform listeners of new child
        if( m_bChildrenInitialized )
        {
            Any aEmpty, aNew;
            aNew <<= xChild;

            aGuard.clear();
            BroadcastAccEvent( AccessibleEventId::CHILD, aNew, aEmpty );
        }
    }
}

/** in this method we imply that the Reference< XAccessible > elements in the
    vector are AccessibleBase objects !
 */
void AccessibleBase::RemoveChildByOId( const ObjectIdentifier& rOId )
{
    ClearableMutexGuard aGuard( GetMutex() );

    ChildOIDMap::iterator aIt( m_aChildOIDMap.find( rOId ));
    if( aIt != m_aChildOIDMap.end())
    {
        Reference< XAccessible > xChild( aIt->second );

        // remove from map
        m_aChildOIDMap.erase( aIt );

        // search child in vector
        ChildListVectorType::iterator aVecIter =
            std::find( m_aChildList.begin(), m_aChildList.end(), xChild );

        OSL_ENSURE( aVecIter != m_aChildList.end(),
                    "Inconsistent ChildMap" );

        // remove child from vector
        m_aChildList.erase( aVecIter );
        bool bInitialized = m_bChildrenInitialized;

        // call listeners unguarded
        aGuard.clear();

        // inform listeners of removed child
        if( bInitialized )
        {
            Any aEmpty, aOld;
            aOld <<= xChild;

            BroadcastAccEvent( AccessibleEventId::CHILD, aEmpty, aOld );
        }

        // dispose the child
        Reference< lang::XComponent > xComp( xChild, UNO_QUERY );
        if( xComp.is())
            xComp->dispose();
    }
}

awt::Point AccessibleBase::GetUpperLeftOnScreen() const
{
    awt::Point aResult;
    if( m_aAccInfo.m_pParent )
    {
        ClearableMutexGuard aGuard( GetMutex() );
        AccessibleBase * pParent = m_aAccInfo.m_pParent;
        aGuard.clear();

        if( pParent )
        {
            aResult = pParent->GetUpperLeftOnScreen();
        }
        else
            OSL_FAIL( "Default position used is probably incorrect." );
    }

    return aResult;
}

void AccessibleBase::BroadcastAccEvent(
    sal_Int16 nId,
    const Any & rNew,
    const Any & rOld,
    bool bSendGlobally ) const
{
    ClearableMutexGuard aGuard( GetMutex() );

    if ( !m_nEventNotifierId && !bSendGlobally )
        return;
        // if we don't have a client id for the notifier, then we don't have listeners, then
        // we don't need to notify anything
        //except SendGlobally for focus handling?

    // the const cast is needed, because UNO parameters are never const
    const AccessibleEventObject aEvent(
        const_cast< uno::XWeak * >( static_cast< const uno::XWeak * >( this )),
        nId, rNew, rOld );

    if ( m_nEventNotifierId ) // let the notifier handle this event
        ::comphelper::AccessibleEventNotifier::addEvent( m_nEventNotifierId, aEvent );

    aGuard.clear();

    // send event to global message queue
    if( bSendGlobally )
    {
        vcl::unohelper::NotifyAccessibleStateEventGlobally( aEvent );
    }
}

void AccessibleBase::KillAllChildren()
{
    ClearableMutexGuard aGuard( GetMutex() );

    // make local copy for notification
    ChildListVectorType aLocalChildList( m_aChildList );

    // remove all children
    m_aChildList.clear();
    m_aChildOIDMap.clear();

    aGuard.clear();

    // call dispose for all children
    // and notify listeners
    Reference< lang::XComponent > xComp;
    Any aEmpty, aOld;
    for (auto const& localChild : aLocalChildList)
    {
        aOld <<= localChild;
        BroadcastAccEvent( AccessibleEventId::CHILD, aEmpty, aOld );

        xComp.set(localChild, UNO_QUERY);
        if( xComp.is())
            xComp->dispose();
    }
    m_bChildrenInitialized = false;
}

void AccessibleBase::SetInfo( const AccessibleElementInfo & rNewInfo )
{
    m_aAccInfo = rNewInfo;
    if( m_bMayHaveChildren )
    {
        KillAllChildren();
    }
    BroadcastAccEvent( AccessibleEventId::INVALIDATE_ALL_CHILDREN, uno::Any(), uno::Any(),
                       true /* global notification */ );
}

// ________ (XComponent::dispose) ________
void SAL_CALL AccessibleBase::disposing()
{
    {
        MutexGuard aGuard(GetMutex());
        OSL_ENSURE(!m_bIsDisposed, "dispose() called twice");

        // notify disposing to all AccessibleEvent listeners asynchron
        if (m_nEventNotifierId)
        {
            ::comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing(m_nEventNotifierId,
                                                                               *this);
            m_nEventNotifierId = 0;
        }

        // reset pointers
        m_aAccInfo.m_pParent = nullptr;

        // attach new empty state set helper to member reference
        ::utl::AccessibleStateSetHelper * pHelper = new ::utl::AccessibleStateSetHelper();
        pHelper->AddState(AccessibleStateType::DEFUNC);
        // release old helper and attach new one
        m_xStateSetHelper = pHelper;

        m_bIsDisposed = true;

    }
    // call listeners unguarded

    if( m_bMayHaveChildren )
    {
        KillAllChildren();
    }
    else
        OSL_ENSURE( m_aChildList.empty(), "Child list should be empty" );
}

// ________ XAccessible ________
Reference< XAccessibleContext > SAL_CALL AccessibleBase::getAccessibleContext()
{
    return this;
}

// ________ AccessibleBase::XAccessibleContext ________
sal_Int32 SAL_CALL AccessibleBase::getAccessibleChildCount()
{
    ClearableMutexGuard aGuard( GetMutex() );
    if( ! m_bMayHaveChildren ||
        m_bIsDisposed )
        return 0;

    bool bMustUpdateChildren = ( m_bMayHaveChildren &&
                                 ! m_bChildrenInitialized );

    aGuard.clear();

    // update unguarded
    if( bMustUpdateChildren )
        UpdateChildren();

    return ImplGetAccessibleChildCount();
}

sal_Int32 AccessibleBase::ImplGetAccessibleChildCount() const
{
    return m_aChildList.size();
}

Reference< XAccessible > SAL_CALL AccessibleBase::getAccessibleChild( sal_Int32 i )
{
    CheckDisposeState();
    Reference< XAccessible > xResult;

    ClearableMutexGuard aGuard( GetMutex() );
    bool bMustUpdateChildren = ( m_bMayHaveChildren &&
                                 ! m_bChildrenInitialized );

    aGuard.clear();

    if( bMustUpdateChildren )
        UpdateChildren();

    xResult.set( ImplGetAccessibleChildById( i ));

    return xResult;
}

Reference< XAccessible > AccessibleBase::ImplGetAccessibleChildById( sal_Int32 i ) const
{
    Reference< XAccessible > xResult;

    MutexGuard aGuard( GetMutex());
    if( ! m_bMayHaveChildren ||
        i < 0 ||
        static_cast< ChildListVectorType::size_type >( i ) >= m_aChildList.size() )
    {
        OUString aBuf = "Index " + OUString::number( i ) + " is invalid for range [ 0, " +
                        OUString::number( m_aChildList.size() - 1 ) +
                        " ]";
        lang::IndexOutOfBoundsException aEx( aBuf,
                                             const_cast< ::cppu::OWeakObject * >(
                                                 static_cast< const ::cppu::OWeakObject * >( this )));
        throw aEx;
    }
    else
        xResult.set( m_aChildList[ i ] );

    return xResult;
}

Reference< XAccessible > SAL_CALL AccessibleBase::getAccessibleParent()
{
    CheckDisposeState();
    Reference< XAccessible > aResult;
    if( m_aAccInfo.m_pParent )
        aResult.set( m_aAccInfo.m_pParent );

    return aResult;
}

sal_Int32 SAL_CALL AccessibleBase::getAccessibleIndexInParent()
{
    CheckDisposeState();

    if( m_aAccInfo.m_spObjectHierarchy )
        return m_aAccInfo.m_spObjectHierarchy->getIndexInParent( GetId() );
    return -1;
}

sal_Int16 SAL_CALL AccessibleBase::getAccessibleRole()
{
    return AccessibleRole::SHAPE;
}

Reference< XAccessibleRelationSet > SAL_CALL AccessibleBase::getAccessibleRelationSet()
{
    Reference< XAccessibleRelationSet > aResult;
    return aResult;
}

Reference< XAccessibleStateSet > SAL_CALL AccessibleBase::getAccessibleStateSet()
{
    if( ! m_bStateSetInitialized )
    {
        Reference< view::XSelectionSupplier > xSelSupp( GetInfo().m_xSelectionSupplier );
        if ( xSelSupp.is() )
        {
            ObjectIdentifier aOID( xSelSupp->getSelection() );
            if ( aOID.isValid() && GetId() == aOID )
            {
                AddState( AccessibleStateType::SELECTED );
                AddState( AccessibleStateType::FOCUSED );
            }
        }
        m_bStateSetInitialized = true;
    }

    return m_xStateSetHelper.get();
}

lang::Locale SAL_CALL AccessibleBase::getLocale()
{
    CheckDisposeState();

    return Application::GetSettings().GetLanguageTag().getLocale();
}

// ________ AccessibleBase::XAccessibleComponent ________
sal_Bool SAL_CALL AccessibleBase::containsPoint( const awt::Point& aPoint )
{
    awt::Rectangle aRect( getBounds() );

    // contains() works with relative coordinates
    aRect.X = 0;
    aRect.Y = 0;

    return ( aPoint.X >= aRect.X &&
             aPoint.Y >= aRect.Y &&
             aPoint.X < (aRect.X + aRect.Width) &&
             aPoint.Y < (aRect.Y + aRect.Height) );
}

Reference< XAccessible > SAL_CALL AccessibleBase::getAccessibleAtPoint( const awt::Point& aPoint )
{
    CheckDisposeState();
    Reference< XAccessible > aResult;
    awt::Rectangle aRect( getBounds());

    // children are positioned relative to this object, so translate bound rect
    aRect.X = 0;
    aRect.Y = 0;

    // children must be inside the own bound rect
    if( ( aRect.X <= aPoint.X && aPoint.X <= (aRect.X + aRect.Width) ) &&
        ( aRect.Y <= aPoint.Y && aPoint.Y <= (aRect.Y + aRect.Height)))
    {
        ClearableMutexGuard aGuard( GetMutex() );
        ChildListVectorType aLocalChildList( m_aChildList );
        aGuard.clear();

        Reference< XAccessibleComponent > aComp;
        for (auto const& localChild : aLocalChildList)
        {
            aComp.set(localChild, UNO_QUERY);
            if( aComp.is())
            {
                aRect = aComp->getBounds();
                if( ( aRect.X <= aPoint.X && aPoint.X <= (aRect.X + aRect.Width) ) &&
                    ( aRect.Y <= aPoint.Y && aPoint.Y <= (aRect.Y + aRect.Height)))
                {
                    aResult = localChild;
                    break;
                }
            }
        }
    }

    return aResult;
}

awt::Rectangle SAL_CALL AccessibleBase::getBounds()
{
    ExplicitValueProvider *pExplicitValueProvider(
        ExplicitValueProvider::getExplicitValueProvider( m_aAccInfo.m_xView ));
    if( pExplicitValueProvider )
    {
        VclPtr<vcl::Window> pWindow( VCLUnoHelper::GetWindow( m_aAccInfo.m_xWindow ));
        awt::Rectangle aLogicRect( pExplicitValueProvider->getRectangleOfObject( m_aAccInfo.m_aOID.getObjectCID() ));
        if( pWindow )
        {
            tools::Rectangle aRect( aLogicRect.X, aLogicRect.Y,
                             aLogicRect.X + aLogicRect.Width,
                             aLogicRect.Y + aLogicRect.Height );
            SolarMutexGuard aSolarGuard;
            aRect = pWindow->LogicToPixel( aRect );

            // aLogicRect is relative to the page, but we need a value relative
            // to the parent object
            awt::Point aParentLocOnScreen;
            uno::Reference< XAccessibleComponent > xParent( getAccessibleParent(), uno::UNO_QUERY );
            if( xParent.is() )
                aParentLocOnScreen = xParent->getLocationOnScreen();

            awt::Point aULOnScreen = GetUpperLeftOnScreen();
            awt::Point aOffset( aParentLocOnScreen.X - aULOnScreen.X,
                                aParentLocOnScreen.Y - aULOnScreen.Y );

            return awt::Rectangle( aRect.getX() - aOffset.X, aRect.getY() - aOffset.Y,
                                   aRect.getWidth(), aRect.getHeight());
        }
    }

    return awt::Rectangle();
}

awt::Point SAL_CALL AccessibleBase::getLocation()
{
    CheckDisposeState();
    awt::Rectangle aBBox( getBounds() );
    return awt::Point( aBBox.X, aBBox.Y );
}

awt::Point SAL_CALL AccessibleBase::getLocationOnScreen()
{
    CheckDisposeState();

    if (AccessibleBase* pParent = m_aAccInfo.m_pParent)
    {
        awt::Point aLocThisRel( getLocation());
        awt::Point aUpperLeft(pParent->getLocationOnScreen());

        return  awt::Point( aUpperLeft.X + aLocThisRel.X,
                            aUpperLeft.Y + aLocThisRel.Y );
    }
    else
        return getLocation();
}

awt::Size SAL_CALL AccessibleBase::getSize()
{
    CheckDisposeState();
    awt::Rectangle aBBox( getBounds() );
    return awt::Size( aBBox.Width, aBBox.Height );
}

void SAL_CALL AccessibleBase::grabFocus()
{
    CheckDisposeState();

    Reference< view::XSelectionSupplier > xSelSupp( GetInfo().m_xSelectionSupplier );
    if ( xSelSupp.is() )
    {
        xSelSupp->select( GetId().getAny() );
    }
}

sal_Int32 SAL_CALL AccessibleBase::getForeground()
{
    return sal_Int32(getColor( ACC_BASE_FOREGROUND ));
}

sal_Int32 SAL_CALL AccessibleBase::getBackground()
{
    return sal_Int32(getColor( ACC_BASE_BACKGROUND ));
}

Color AccessibleBase::getColor( eColorType eColType )
{
    Color nResult = COL_TRANSPARENT;
    if( m_bAlwaysTransparent )
        return nResult;

    ObjectIdentifier aOID( m_aAccInfo.m_aOID );
    ObjectType eType( aOID.getObjectType() );
    Reference< beans::XPropertySet > xObjProp;
    OUString aObjectCID = aOID.getObjectCID();
    if( eType == OBJECTTYPE_LEGEND_ENTRY )
    {
        // for colors get the data series/point properties
        OUString aParentParticle( ObjectIdentifier::getFullParentParticle( aObjectCID ));
        aObjectCID = ObjectIdentifier::createClassifiedIdentifierForParticle( aParentParticle );
    }

    xObjProp.set(
        ObjectIdentifier::getObjectPropertySet(
            aObjectCID, Reference< chart2::XChartDocument >( m_aAccInfo.m_xChartDocument )), uno::UNO_QUERY );
    if( xObjProp.is())
    {
        try
        {
            OUString aPropName;
            OUString aStylePropName;

            switch( eType )
            {
                case OBJECTTYPE_LEGEND_ENTRY:
                case OBJECTTYPE_DATA_SERIES:
                case OBJECTTYPE_DATA_POINT:
                    if( eColType == ACC_BASE_FOREGROUND )
                    {
                        aPropName = "BorderColor";
                        aStylePropName = "BorderTransparency";
                    }
                    else
                    {
                        aPropName = "Color";
                        aStylePropName = "Transparency";
                    }
                    break;
                default:
                    if( eColType == ACC_BASE_FOREGROUND )
                    {
                        aPropName = "LineColor";
                        aStylePropName = "LineTransparence";
                    }
                    else
                    {
                        aPropName = "FillColor";
                        aStylePropName = "FillTransparence";
                    }
                    break;
            }

            bool bTransparent = m_bAlwaysTransparent;
            Reference< beans::XPropertySetInfo > xInfo( xObjProp->getPropertySetInfo(), uno::UNO_QUERY );
            if( xInfo.is() &&
                xInfo->hasPropertyByName( aStylePropName ))
            {
                if( eColType == ACC_BASE_FOREGROUND )
                {
                    drawing::LineStyle aLStyle;
                    if( xObjProp->getPropertyValue( aStylePropName ) >>= aLStyle )
                        bTransparent = (aLStyle == drawing::LineStyle_NONE);
                }
                else
                {
                    drawing::FillStyle aFStyle;
                    if( xObjProp->getPropertyValue( aStylePropName ) >>= aFStyle )
                        bTransparent = (aFStyle == drawing::FillStyle_NONE);
                }
            }

            if( !bTransparent &&
                xInfo.is() &&
                xInfo->hasPropertyByName( aPropName ))
            {
                xObjProp->getPropertyValue( aPropName ) >>= nResult;
            }
        }
        catch( const uno::Exception & )
        {
            DBG_UNHANDLED_EXCEPTION("chart2");
        }
    }

    return nResult;
}

// ________ AccessibleBase::XServiceInfo ________
OUString SAL_CALL AccessibleBase::getImplementationName()
{
    return OUString( "AccessibleBase" );
}

sal_Bool SAL_CALL AccessibleBase::supportsService( const OUString& ServiceName )
{
    return cppu::supportsService( this, ServiceName );
}

uno::Sequence< OUString > SAL_CALL AccessibleBase::getSupportedServiceNames()
{
    return {
        "com.sun.star.accessibility.Accessible",
        "com.sun.star.accessibility.AccessibleContext"
    };
}

// ________ AccessibleBase::XEventListener ________
void SAL_CALL AccessibleBase::disposing( const lang::EventObject& /*Source*/ )
{
}

// ________ XAccessibleEventBroadcasters ________
void SAL_CALL AccessibleBase::addAccessibleEventListener( const Reference< XAccessibleEventListener >& xListener )
{
    MutexGuard aGuard( GetMutex() );

    if ( xListener.is() )
    {
        if ( !m_nEventNotifierId )
            m_nEventNotifierId = ::comphelper::AccessibleEventNotifier::registerClient();

        ::comphelper::AccessibleEventNotifier::addEventListener( m_nEventNotifierId, xListener );
    }
}

void SAL_CALL AccessibleBase::removeAccessibleEventListener( const Reference< XAccessibleEventListener >& xListener )
{
    MutexGuard aGuard( GetMutex() );

    if ( xListener.is() && m_nEventNotifierId)
    {
        sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( m_nEventNotifierId, xListener );
        if ( !nListenerCount )
        {
            // no listeners anymore
            ::comphelper::AccessibleEventNotifier::revokeClient( m_nEventNotifierId );
            m_nEventNotifierId = 0;
        }
    }
}

} // namespace chart

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
