/* -*- 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 #include "AccessibleChartShape.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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( AccessibleElementInfo aAccInfo, 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(std::move( aAccInfo )), 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 ); 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 ); SAL_INFO("chart2.accessibility", "Selection lost by: " << getAccessibleName()); } break; } return true; } else if( m_bMayHaveChildren ) { bool bStop = false; ClearableMutexGuard aGuard( m_aMutex ); // 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( m_aMutex ); 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 ).get() ); } 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 ) return; ClearableMutexGuard aGuard( m_aMutex ); 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( m_aMutex ); ChildOIDMap::iterator aIt( m_aChildOIDMap.find( rOId )); if( aIt == m_aChildOIDMap.end()) return; 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( m_aMutex ); 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 ) const { ClearableMutexGuard aGuard( m_aMutex ); if ( !m_nEventNotifierId ) 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 // 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 ); // let the notifier handle this event ::comphelper::AccessibleEventNotifier::addEvent( m_nEventNotifierId, aEvent ); aGuard.clear(); } void AccessibleBase::KillAllChildren() { ClearableMutexGuard aGuard( m_aMutex ); // make local copy for notification, and remove all children ChildListVectorType aLocalChildList; aLocalChildList.swap( m_aChildList ); 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()); } // ________ (XComponent::dispose) ________ void SAL_CALL AccessibleBase::disposing() { { MutexGuard aGuard(m_aMutex); OSL_ENSURE(!m_bIsDisposed, "dispose() called twice"); // notify disposing to all AccessibleEvent listeners asynchronous 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 rtl::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( m_aMutex ); 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( m_aMutex ); 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( m_aMutex); if( ! m_bMayHaveChildren || i < 0 || o3tl::make_unsigned( 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; } 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( m_aMutex ); 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( comphelper::getFromUnoTunnel( m_aAccInfo.m_xView )); if( pExplicitValueProvider ) { VclPtr 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.Left() - aOffset.X, aRect.Top() - 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 std::u16string_view aParentParticle( ObjectIdentifier::getFullParentParticle( aObjectCID )); aObjectCID = ObjectIdentifier::createClassifiedIdentifierForParticle( aParentParticle ); } xObjProp = ObjectIdentifier::getObjectPropertySet( aObjectCID, m_aAccInfo.m_xChartDocument ); 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(); 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 "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( m_aMutex ); 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( m_aMutex ); 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: */