diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /svx/source/accessibility | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svx/source/accessibility')
24 files changed, 10442 insertions, 0 deletions
diff --git a/svx/source/accessibility/AccessibleControlShape.cxx b/svx/source/accessibility/AccessibleControlShape.cxx new file mode 100644 index 0000000000..3a6605fda6 --- /dev/null +++ b/svx/source/accessibility/AccessibleControlShape.cxx @@ -0,0 +1,851 @@ +/* -*- 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 <svx/AccessibleControlShape.hxx> +#include <svx/AccessibleShapeInfo.hxx> +#include <DescriptionGenerator.hxx> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/XControlShape.hpp> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/reflection/ProxyFactory.hpp> +#include <com/sun/star/util/XModeChangeBroadcaster.hpp> +#include <com/sun/star/container/XContainer.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/property.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <svx/IAccessibleParent.hxx> +#include <svx/svdouno.hxx> +#include <svx/ShapeTypeHandler.hxx> +#include <svx/SvxShapeTypes.hxx> +#include <comphelper/accessiblewrapper.hxx> +#include <svx/svdview.hxx> +#include <svx/svdpagv.hxx> +#include <svx/strings.hrc> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> + +using namespace ::accessibility; +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::reflection; +using namespace ::com::sun::star::drawing; +using namespace ::com::sun::star::container; + +namespace +{ + constexpr OUString NAME_PROPERTY_NAME = u"Name"_ustr; + constexpr OUString DESC_PROPERTY_NAME = u"HelpText"_ustr; + constexpr OUString LABEL_PROPERTY_NAME = u"Label"_ustr; + constexpr OUString LABEL_CONTROL_PROPERTY_NAME = u"LabelControl"_ustr; + + // return the property which should be used as AccessibleName + OUString lcl_getPreferredAccNameProperty( const Reference< XPropertySetInfo >& _rxPSI ) + { + if ( _rxPSI.is() && _rxPSI->hasPropertyByName( LABEL_PROPERTY_NAME ) ) + return LABEL_PROPERTY_NAME; + else + return NAME_PROPERTY_NAME; + } + + // determines whether or not a state which belongs to the inner context needs to be forwarded to the "composed" + // context + bool isComposedState( const sal_Int64 _nState ) + { + return ( ( AccessibleStateType::INVALID != _nState ) + && ( AccessibleStateType::DEFUNC != _nState ) + && ( AccessibleStateType::ICONIFIED != _nState ) + && ( AccessibleStateType::RESIZABLE != _nState ) + && ( AccessibleStateType::SELECTABLE != _nState ) + && ( AccessibleStateType::SHOWING != _nState ) + && ( AccessibleStateType::MANAGES_DESCENDANTS != _nState ) + && ( AccessibleStateType::VISIBLE != _nState ) + ); + } + + /// determines whether the given control is in alive mode + bool isAliveMode( const Reference< XControl >& _rxControl ) + { + OSL_PRECOND( _rxControl.is(), "AccessibleControlShape::isAliveMode: invalid control" ); + return _rxControl.is() && !_rxControl->isDesignMode(); + } +} + +AccessibleControlShape::AccessibleControlShape ( + const AccessibleShapeInfo& rShapeInfo, + const AccessibleShapeTreeInfo& rShapeTreeInfo) + : AccessibleShape (rShapeInfo, rShapeTreeInfo) + , m_bListeningForName( false ) + , m_bListeningForDesc( false ) + , m_bMultiplexingStates( false ) + , m_bDisposeNativeContext( false ) + , m_bWaitingForControl( false ) +{ + m_pChildManager = new comphelper::OWrappedAccessibleChildrenManager( comphelper::getProcessComponentContext() ); + + osl_atomic_increment( &m_refCount ); + { + m_pChildManager->setOwningAccessible( this ); + } + osl_atomic_decrement( &m_refCount ); +} + +AccessibleControlShape::~AccessibleControlShape() +{ + m_pChildManager.clear(); + + if ( m_xControlContextProxy.is() ) + m_xControlContextProxy->setDelegator( nullptr ); + m_xControlContextProxy.clear(); + m_xControlContextTypeAccess.clear(); + m_xControlContextComponent.clear(); + // this should remove the _only_ three "real" reference (means not delegated to + // ourself) to this proxy, and thus delete it +} + +namespace { + Reference< XContainer > lcl_getControlContainer( const OutputDevice* _pWin, const SdrView* _pView ) + { + Reference< XContainer > xReturn; + DBG_ASSERT( _pView, "lcl_getControlContainer: invalid view!" ); + if ( _pView && _pView->GetSdrPageView()) + { + xReturn.set(_pView->GetSdrPageView()->GetControlContainer( *_pWin ), css::uno::UNO_QUERY); + } + return xReturn; + } +} + +void AccessibleControlShape::Init() +{ + AccessibleShape::Init(); + + OSL_ENSURE( !m_xControlContextProxy.is(), "AccessibleControlShape::Init: already initialized!" ); + try + { + // What we need to do here is merge the functionality of the AccessibleContext of our UNO control + // with our own AccessibleContext-related functionality. + + // The problem is that we do not know the interfaces our "inner" context supports - this may be any + // XAccessibleXXX interface (or even any other) which makes sense for it. + + // In theory, we could implement all possible interfaces ourself, and re-route all functionality to + // the inner context (except those we implement ourself, like XAccessibleComponent). But this is in no + // way future-proof - as soon as an inner context appears which implements an additional interface, + // we would need to adjust our implementation to support this new interface, too. Bad idea. + + // The usual solution for such a problem is aggregation. Aggregation means using UNO's own mechanism + // for merging an inner with an outer component, and get a component which behaves as it is exactly one. + // This is what XAggregation is for. Unfortunately, aggregation requires _exact_ control over the ref count + // of the inner object, which we do not have at all. + // Bad, too. + + // But there is a solution: com.sun.star.reflection.ProxyFactory. This service is able to create a proxy + // for any component, which supports _exactly_ the same interfaces as the component. In addition, it can + // be aggregated, as by definition the proxy's ref count is exactly 1 when returned from the factory. + // Sounds better. Though this yields the problem of slightly degraded performance, it's the only solution + // I'm aware of at the moment... + + // get the control which belongs to our model (relative to our view) + const vcl::Window* pViewWindow = maShapeTreeInfo.GetWindow(); + SdrUnoObj* pUnoObjectImpl = dynamic_cast<SdrUnoObj*>(SdrObject::getSdrObjectFromXShape(mxShape)); + SdrView* pView = maShapeTreeInfo.GetSdrView(); + OSL_ENSURE( pView && pViewWindow && pUnoObjectImpl, "AccessibleControlShape::Init: no view, or no view window, no SdrUnoObj!" ); + + if ( pView && pViewWindow && pUnoObjectImpl ) + { + // get the context of the control - it will be our "inner" context + m_xUnoControl = pUnoObjectImpl->GetUnoControl( *pView, *pViewWindow->GetOutDev() ); + + if ( !m_xUnoControl.is() ) + { + // the control has not yet been created. Though speaking strictly, it is a bug that + // our instance here is created without an existing control (because an AccessibleControlShape + // is a representation of a view object, and can only live if the view it should represent + // is complete, which implies a living control), it's by far the easiest and most riskless way + // to fix this here in this class. + // Okay, we will add as listener to the control container where we expect our control to appear. + OSL_ENSURE( !m_bWaitingForControl, "AccessibleControlShape::Init: already waiting for the control!" ); + + Reference< XContainer > xControlContainer = lcl_getControlContainer( pViewWindow->GetOutDev(), maShapeTreeInfo.GetSdrView() ); + OSL_ENSURE( xControlContainer.is(), "AccessibleControlShape::Init: unable to find my ControlContainer!" ); + if ( xControlContainer.is() ) + { + xControlContainer->addContainerListener( this ); + m_bWaitingForControl = true; + } + } + else + { + Reference< XModeChangeBroadcaster > xControlModes( m_xUnoControl, UNO_QUERY ); + Reference< XAccessible > xControlAccessible( xControlModes, UNO_QUERY ); + Reference< XAccessibleContext > xNativeControlContext; + if ( xControlAccessible.is() ) + xNativeControlContext = xControlAccessible->getAccessibleContext(); + OSL_ENSURE( xNativeControlContext.is(), "AccessibleControlShape::Init: no AccessibleContext for the control!" ); + m_aControlContext = WeakReference< XAccessibleContext >( xNativeControlContext ); + + // add as listener to the context - we want to multiplex some states + if ( isAliveMode( m_xUnoControl ) && xNativeControlContext.is() ) + { // (but only in alive mode) + startStateMultiplexing( ); + } + + // now that we have all information about our control, do some adjustments + adjustAccessibleRole(); + initializeComposedState(); + + // some initialization for our child manager, which is used in alive mode only + if ( isAliveMode( m_xUnoControl ) ) + { + sal_Int64 nStates( getAccessibleStateSet( ) ); + m_pChildManager->setTransientChildren( nStates & AccessibleStateType::MANAGES_DESCENDANTS ); + } + + // finally, aggregate a proxy for the control context + // first a factory for the proxy + Reference< XProxyFactory > xFactory = ProxyFactory::create( comphelper::getProcessComponentContext() ); + // then the proxy itself + if ( xNativeControlContext.is() ) + { + m_xControlContextProxy = xFactory->createProxy( xNativeControlContext ); + m_xControlContextTypeAccess.set( xNativeControlContext, UNO_QUERY_THROW ); + m_xControlContextComponent.set( xNativeControlContext, UNO_QUERY_THROW ); + + // aggregate the proxy + osl_atomic_increment( &m_refCount ); + if ( m_xControlContextProxy.is() ) + { + // At this point in time, the proxy has a ref count of exactly one - in m_xControlContextProxy. + // Remember to _not_ reset this member unless the delegator of the proxy has been reset, too! + m_xControlContextProxy->setDelegator( *this ); + } + osl_atomic_decrement( &m_refCount ); + + m_bDisposeNativeContext = true; + + // Finally, we need to add ourself as mode listener to the control. In case the mode switches, + // we need to dispose ourself. + xControlModes->addModeChangeListener( this ); + } + } + } + } + catch( const Exception& ) + { + OSL_FAIL( "AccessibleControlShape::Init: could not \"aggregate\" the controls XAccessibleContext!" ); + } +} + +void SAL_CALL AccessibleControlShape::grabFocus() +{ + if ( !m_xUnoControl.is() || !isAliveMode( m_xUnoControl ) ) + { + // in design mode, we simply forward the request to the base class + AccessibleShape::grabFocus(); + } + else + { + Reference< XWindow > xWindow( m_xUnoControl, UNO_QUERY ); + OSL_ENSURE( xWindow.is(), "AccessibleControlShape::grabFocus: invalid control!" ); + if ( xWindow.is() ) + xWindow->setFocus(); + } +} + +OUString SAL_CALL AccessibleControlShape::getImplementationName() +{ + return "com.sun.star.comp.accessibility.AccessibleControlShape"; +} + +OUString AccessibleControlShape::CreateAccessibleBaseName() +{ + OUString sName; + + ShapeTypeId nShapeType = ShapeTypeHandler::Instance().GetTypeId (mxShape); + switch (nShapeType) + { + case DRAWING_CONTROL: + sName = "ControlShape"; + break; + default: + sName = "UnknownAccessibleControlShape"; + if (mxShape.is()) + sName += ": " + mxShape->getShapeType(); + } + + return sName; +} + +OUString + AccessibleControlShape::CreateAccessibleDescription() +{ + DescriptionGenerator aDG (mxShape); + ShapeTypeId nShapeType = ShapeTypeHandler::Instance().GetTypeId (mxShape); + switch (nShapeType) + { + case DRAWING_CONTROL: + { + // check if we can obtain the "Desc" property from the model + OUString sDesc( getControlModelStringProperty( DESC_PROPERTY_NAME ) ); + if ( sDesc.isEmpty() ) + { // no -> use the default + aDG.Initialize (STR_ObjNameSingulUno); + aDG.AddProperty ("ControlBackground", DescriptionGenerator::PropertyType::Color); + aDG.AddProperty ( "ControlBorder", DescriptionGenerator::PropertyType::Integer); + } + // ensure that we are listening to the Name property + m_bListeningForDesc = ensureListeningState( m_bListeningForDesc, true, DESC_PROPERTY_NAME ); + } + break; + + default: + aDG.Initialize (u"Unknown accessible control shape"); + if (mxShape.is()) + { + aDG.AppendString (u"service name="); + aDG.AppendString (mxShape->getShapeType()); + } + } + + return aDG(); +} + +IMPLEMENT_FORWARD_REFCOUNT( AccessibleControlShape, AccessibleShape ) +IMPLEMENT_GET_IMPLEMENTATION_ID( AccessibleControlShape ) + +void SAL_CALL AccessibleControlShape::propertyChange( const PropertyChangeEvent& _rEvent ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + // check if it is the name or the description + if ( _rEvent.PropertyName == NAME_PROPERTY_NAME + || _rEvent.PropertyName == LABEL_PROPERTY_NAME ) + { + SetAccessibleName( + CreateAccessibleName(), + AccessibleContextBase::AutomaticallyCreated); + } + else if ( _rEvent.PropertyName == DESC_PROPERTY_NAME ) + { + SetAccessibleDescription( + CreateAccessibleDescription(), + AccessibleContextBase::AutomaticallyCreated); + } +#if OSL_DEBUG_LEVEL > 0 + else + { + OSL_FAIL( "AccessibleControlShape::propertyChange: where did this come from?" ); + } +#endif +} + +Any SAL_CALL AccessibleControlShape::queryInterface( const Type& _rType ) +{ + Any aReturn = AccessibleShape::queryInterface( _rType ); + if ( !aReturn.hasValue() ) + { + aReturn = AccessibleControlShape_Base::queryInterface( _rType ); + if ( !aReturn.hasValue() && m_xControlContextProxy.is() ) + aReturn = m_xControlContextProxy->queryAggregation( _rType ); + } + return aReturn; +} + +Sequence< Type > SAL_CALL AccessibleControlShape::getTypes() +{ + Sequence< Type > aShapeTypes = AccessibleShape::getTypes(); + Sequence< Type > aOwnTypes = AccessibleControlShape_Base::getTypes(); + + Sequence< Type > aAggregateTypes; + if ( m_xControlContextTypeAccess.is() ) + aAggregateTypes = m_xControlContextTypeAccess->getTypes(); + + // remove duplicates + return comphelper::combineSequences(comphelper::concatSequences( aShapeTypes, aOwnTypes), aAggregateTypes ); +} + +void SAL_CALL AccessibleControlShape::notifyEvent( const AccessibleEventObject& _rEvent ) +{ + if ( AccessibleEventId::STATE_CHANGED == _rEvent.EventId ) + { + // multiplex this change + sal_Int64 nLostState( 0 ), nGainedState( 0 ); + _rEvent.OldValue >>= nLostState; + _rEvent.NewValue >>= nGainedState; + + // don't multiplex states which the inner context is not responsible for + if ( isComposedState( nLostState ) ) + AccessibleShape::ResetState( nLostState ); + + if ( isComposedState( nGainedState ) ) + AccessibleShape::SetState( nGainedState ); + } + else + { + AccessibleEventObject aTranslatedEvent( _rEvent ); + + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // let the child manager translate the event + aTranslatedEvent.Source = *this; + m_pChildManager->translateAccessibleEvent( _rEvent, aTranslatedEvent ); + + // see if any of these notifications affect our child manager + m_pChildManager->handleChildNotification( _rEvent ); + } + + FireEvent( aTranslatedEvent ); + } +} + +void SAL_CALL AccessibleControlShape::modeChanged(const ModeChangeEvent& rSource) +{ + // did it come from our inner context (the real one, not it's proxy!)? + SAL_INFO("sw.uno", "AccessibleControlShape::modeChanged"); + Reference<XControl> xSource(rSource.Source, UNO_QUERY); // for faster compare + if(xSource.get() != m_xUnoControl.get()) + { + SAL_WARN("sw.uno", "AccessibleControlShape::modeChanged: where did this come from?"); + return; + } + SolarMutexGuard g; + // If our "pseudo-aggregated" inner context does not live anymore, + // we don't want to live, too. This is accomplished by asking our + // parent to replace this object with a new one. Disposing this + // object and sending notifications about the replacement are in + // the responsibility of our parent. + const bool bReplaced = mpParent->ReplaceChild(this, mxShape, 0, maShapeTreeInfo); + SAL_WARN_IF(!bReplaced, "sw.uno", "AccessibleControlShape::modeChanged: replacing ourselves away did fail"); +} + +void SAL_CALL AccessibleControlShape::disposing (const EventObject& _rSource) +{ + AccessibleShape::disposing( _rSource ); +} + +bool AccessibleControlShape::ensureListeningState( + const bool _bCurrentlyListening, const bool _bNeedNewListening, + const OUString& _rPropertyName ) +{ + if ( ( _bCurrentlyListening == _bNeedNewListening ) || !ensureControlModelAccess() ) + // nothing to do + return _bCurrentlyListening; + + try + { + if ( !m_xModelPropsMeta.is() || m_xModelPropsMeta->hasPropertyByName( _rPropertyName ) ) + { + // add or revoke as listener + if ( _bNeedNewListening ) + m_xControlModel->addPropertyChangeListener( _rPropertyName, static_cast< XPropertyChangeListener* >( this ) ); + else + m_xControlModel->removePropertyChangeListener( _rPropertyName, static_cast< XPropertyChangeListener* >( this ) ); + } + else + OSL_FAIL( "AccessibleControlShape::ensureListeningState: this property does not exist at this model!" ); + } + catch( const Exception& ) + { + OSL_FAIL( "AccessibleControlShape::ensureListeningState: could not change the listening state!" ); + } + + return _bNeedNewListening; +} + +sal_Int64 SAL_CALL AccessibleControlShape::getAccessibleChildCount( ) +{ + if ( !m_xUnoControl.is() ) + return 0; + else if ( !isAliveMode( m_xUnoControl ) ) + // no special action required when in design mode + return AccessibleShape::getAccessibleChildCount( ); + else + { + // in alive mode, we have the full control over our children - they are determined by the children + // of the context of our UNO control + Reference< XAccessibleContext > xControlContext( m_aControlContext ); + OSL_ENSURE( xControlContext.is(), "AccessibleControlShape::getAccessibleChildCount: control context already dead! How this!" ); + return xControlContext.is() ? xControlContext->getAccessibleChildCount() : 0; + } +} + +Reference< XAccessible > SAL_CALL AccessibleControlShape::getAccessibleChild( sal_Int64 i ) +{ + Reference< XAccessible > xChild; + if ( !m_xUnoControl.is() ) + { + throw IndexOutOfBoundsException(); + } + if ( !isAliveMode( m_xUnoControl ) ) + { + // no special action required when in design mode - let the base class handle this + xChild = AccessibleShape::getAccessibleChild( i ); + } + else + { + // in alive mode, we have the full control over our children - they are determined by the children + // of the context of our UNO control + + Reference< XAccessibleContext > xControlContext( m_aControlContext ); + OSL_ENSURE( xControlContext.is(), "AccessibleControlShape::getAccessibleChild: control context already dead! How this!" ); + if ( xControlContext.is() ) + { + Reference< XAccessible > xInnerChild( xControlContext->getAccessibleChild( i ) ); + OSL_ENSURE( xInnerChild.is(), "AccessibleControlShape::getAccessibleChild: control context returned nonsense!" ); + if ( xInnerChild.is() ) + { + // we need to wrap this inner child into an own implementation + xChild = m_pChildManager->getAccessibleWrapperFor( xInnerChild ); + } + } + } + +#if OSL_DEBUG_LEVEL > 0 + sal_Int64 nChildIndex = -1; + Reference< XAccessibleContext > xContext; + if ( xChild.is() ) + xContext = xChild->getAccessibleContext( ); + if ( xContext.is() ) + nChildIndex = xContext->getAccessibleIndexInParent( ); + SAL_WARN_IF( nChildIndex != i, "svx", "AccessibleControlShape::getAccessibleChild: index mismatch," + " nChildIndex=" << nChildIndex << " vs i=" << i ); +#endif + return xChild; +} + +Reference< XAccessibleRelationSet > SAL_CALL AccessibleControlShape::getAccessibleRelationSet( ) +{ + rtl::Reference<utl::AccessibleRelationSetHelper> pRelationSetHelper = new utl::AccessibleRelationSetHelper; + ensureControlModelAccess(); + AccessibleControlShape* pCtlAccShape = GetLabeledByControlShape(); + if(pCtlAccShape) + { + Reference < XAccessible > xAcc (pCtlAccShape->getAccessibleContext(), UNO_QUERY); + + css::uno::Sequence< css::uno::Reference< css::uno::XInterface > > aSequence { xAcc }; + if( getAccessibleRole() == AccessibleRole::RADIO_BUTTON ) + { + pRelationSetHelper->AddRelation( AccessibleRelation( AccessibleRelationType::MEMBER_OF, aSequence ) ); + } + else + { + pRelationSetHelper->AddRelation( AccessibleRelation( AccessibleRelationType::LABELED_BY, aSequence ) ); + } + } + return pRelationSetHelper; +} + +OUString AccessibleControlShape::CreateAccessibleName() +{ + ensureControlModelAccess(); + + OUString sName; + sal_Int16 aAccessibleRole = getAccessibleRole(); + if ( aAccessibleRole != AccessibleRole::SHAPE + && aAccessibleRole != AccessibleRole::RADIO_BUTTON ) + { + AccessibleControlShape* pCtlAccShape = GetLabeledByControlShape(); + if(pCtlAccShape) + { + sName = pCtlAccShape->CreateAccessibleName(); + } + } + + if (sName.isEmpty()) + { + // check if we can obtain the "Name" resp. "Label" property from the model + const OUString& rAccNameProperty = lcl_getPreferredAccNameProperty( m_xModelPropsMeta ); + sName = getControlModelStringProperty( rAccNameProperty ); + if ( !sName.getLength() ) + { // no -> use the default + sName = AccessibleShape::CreateAccessibleName(); + } + } + + // now that somebody first asked us for our name, ensure that we are listening to name changes on the model + m_bListeningForName = ensureListeningState( m_bListeningForName, true, lcl_getPreferredAccNameProperty( m_xModelPropsMeta ) ); + + return sName; +} + +void SAL_CALL AccessibleControlShape::disposing() +{ + // ensure we're not listening + m_bListeningForName = ensureListeningState( m_bListeningForName, false, lcl_getPreferredAccNameProperty( m_xModelPropsMeta ) ); + m_bListeningForDesc = ensureListeningState( m_bListeningForDesc, false, DESC_PROPERTY_NAME ); + + if ( m_bMultiplexingStates ) + stopStateMultiplexing( ); + + // dispose the child cache/map + m_pChildManager->dispose(); + + // release the model + m_xControlModel.clear(); + m_xModelPropsMeta.clear(); + m_aControlContext = WeakReference< XAccessibleContext >(); + + // stop listening at the control container (should never be necessary here, but who knows...) + if ( m_bWaitingForControl ) + { + OSL_FAIL( "AccessibleControlShape::disposing: this should never happen!" ); + if (auto pWindow = maShapeTreeInfo.GetWindow()) + { + Reference< XContainer > xContainer = lcl_getControlContainer( pWindow->GetOutDev(), maShapeTreeInfo.GetSdrView() ); + if ( xContainer.is() ) + { + m_bWaitingForControl = false; + xContainer->removeContainerListener( this ); + } + } + } + + // forward the disposal to our inner context + if ( m_bDisposeNativeContext ) + { + // don't listen for mode changes anymore + Reference< XModeChangeBroadcaster > xControlModes( m_xUnoControl, UNO_QUERY ); + OSL_ENSURE( xControlModes.is(), "AccessibleControlShape::disposing: don't have a mode broadcaster anymore!" ); + if ( xControlModes.is() ) + xControlModes->removeModeChangeListener( this ); + + if ( m_xControlContextComponent.is() ) + m_xControlContextComponent->dispose(); + // do _not_ clear m_xControlContextProxy! This has to be done in the dtor for correct ref-count handling + + // no need to dispose the proxy/inner context anymore + m_bDisposeNativeContext = false; + } + + m_xUnoControl.clear(); + + // let the base do its stuff + AccessibleShape::disposing(); +} + +bool AccessibleControlShape::ensureControlModelAccess() +{ + if ( m_xControlModel.is() ) + return true; + + try + { + Reference< XControlShape > xShape( mxShape, UNO_QUERY ); + if ( xShape.is() ) + m_xControlModel.set(xShape->getControl(), css::uno::UNO_QUERY); + + if ( m_xControlModel.is() ) + m_xModelPropsMeta = m_xControlModel->getPropertySetInfo(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "AccessibleControlShape::ensureControlModelAccess" ); + } + + return m_xControlModel.is(); +} + +void AccessibleControlShape::startStateMultiplexing() +{ + OSL_PRECOND( !m_bMultiplexingStates, "AccessibleControlShape::startStateMultiplexing: already multiplexing!" ); + +#if OSL_DEBUG_LEVEL > 0 + // we should have a control, and it should be in alive mode + OSL_PRECOND( isAliveMode( m_xUnoControl ), + "AccessibleControlShape::startStateMultiplexing: should be done in alive mode only!" ); +#endif + // we should have the native context of the control + Reference< XAccessibleEventBroadcaster > xBroadcaster( m_aControlContext.get(), UNO_QUERY ); + OSL_ENSURE( xBroadcaster.is(), "AccessibleControlShape::startStateMultiplexing: no AccessibleEventBroadcaster on the native context!" ); + + if ( xBroadcaster.is() ) + { + xBroadcaster->addAccessibleEventListener( this ); + m_bMultiplexingStates = true; + } +} + +void AccessibleControlShape::stopStateMultiplexing() +{ + OSL_PRECOND( m_bMultiplexingStates, "AccessibleControlShape::stopStateMultiplexing: not multiplexing!" ); + + // we should have the native context of the control + Reference< XAccessibleEventBroadcaster > xBroadcaster( m_aControlContext.get(), UNO_QUERY ); + OSL_ENSURE( xBroadcaster.is(), "AccessibleControlShape::stopStateMultiplexing: no AccessibleEventBroadcaster on the native context!" ); + + if ( xBroadcaster.is() ) + { + xBroadcaster->removeAccessibleEventListener( this ); + m_bMultiplexingStates = false; + } +} + +OUString AccessibleControlShape::getControlModelStringProperty( const OUString& _rPropertyName ) const +{ + OUString sReturn; + try + { + if ( const_cast< AccessibleControlShape* >( this )->ensureControlModelAccess() ) + { + if ( !m_xModelPropsMeta.is() || m_xModelPropsMeta->hasPropertyByName( _rPropertyName ) ) + // ask only if a) the control does not have a PropertySetInfo object or b) it has, and the + // property in question is available + m_xControlModel->getPropertyValue( _rPropertyName ) >>= sReturn; + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "OAccessibleControlContext::getModelStringProperty" ); + } + return sReturn; +} + +void AccessibleControlShape::adjustAccessibleRole( ) +{ + // if we're in design mode, we are a simple SHAPE, in alive mode, we use the role of our inner context + if ( !isAliveMode( m_xUnoControl ) ) + return; + + // we're in alive mode -> determine the role of the inner context + Reference< XAccessibleContext > xNativeContext( m_aControlContext ); + OSL_PRECOND( xNativeContext.is(), "AccessibleControlShape::adjustAccessibleRole: no inner context!" ); + if ( xNativeContext.is() ) + SetAccessibleRole( xNativeContext->getAccessibleRole( ) ); +} + +#ifdef DBG_UTIL + +bool AccessibleControlShape::SetState( sal_Int64 _nState ) +{ + OSL_ENSURE( !isAliveMode( m_xUnoControl ) || !isComposedState( _nState ), + "AccessibleControlShape::SetState: a state which should be determined by the control context is set from outside!" ); + return AccessibleShape::SetState( _nState ); +} +#endif // DBG_UTIL + +void AccessibleControlShape::initializeComposedState() +{ + if ( !isAliveMode( m_xUnoControl ) ) + // no action necessary for design mode + return; + + // we need to reset some states of the composed set, because they either do not apply + // for controls in alive mode, or are in the responsibility of the UNO-control, anyway + mnStateSet &= ~AccessibleStateType::ENABLED; // this is controlled by the UNO-control + mnStateSet &= ~AccessibleStateType::SENSITIVE; // this is controlled by the UNO-control + mnStateSet &= ~AccessibleStateType::FOCUSABLE; // this is controlled by the UNO-control + mnStateSet &= ~AccessibleStateType::SELECTABLE; // this does not hold for an alive UNO-control + + // get my inner context + Reference< XAccessibleContext > xInnerContext( m_aControlContext ); + OSL_PRECOND( xInnerContext.is(), "AccessibleControlShape::initializeComposedState: no inner context!" ); + if ( !xInnerContext.is() ) + return; + + // get all states of the inner context + sal_Int64 nInnerStates( xInnerContext->getAccessibleStateSet() ); + + // look which one are to be propagated to the composed context + for ( int i = 0; i < 63; ++i ) + { + sal_Int64 nState = sal_Int64(1) << i; + if ( (nInnerStates & nState) && isComposedState( nState ) ) + { + mnStateSet |= nState; + } + } +} + +void SAL_CALL AccessibleControlShape::elementInserted( const css::container::ContainerEvent& _rEvent ) +{ + Reference< XContainer > xContainer( _rEvent.Source, UNO_QUERY ); + Reference< XControl > xControl( _rEvent.Element, UNO_QUERY ); + + OSL_ENSURE( xContainer.is() && xControl.is(), + "AccessibleControlShape::elementInserted: invalid event description!" ); + + if ( !xControl.is() ) + return; + + ensureControlModelAccess(); + + Reference< XInterface > xNewNormalized( xControl->getModel(), UNO_QUERY ); + Reference< XInterface > xMyModelNormalized( m_xControlModel, UNO_QUERY ); + if ( !(xNewNormalized && xMyModelNormalized) ) + return; + + // now finally the control for the model we're responsible for has been inserted into the container + Reference< XInterface > xKeepAlive( *this ); + + // first, we're not interested in any more container events + if ( xContainer.is() ) + { + xContainer->removeContainerListener( this ); + m_bWaitingForControl = false; + } + + // second, we need to replace ourself with a new version, which now can be based on the + // control + OSL_VERIFY( mpParent->ReplaceChild ( this, mxShape, 0, maShapeTreeInfo ) ); +} + +void SAL_CALL AccessibleControlShape::elementRemoved( const css::container::ContainerEvent& ) +{ + // not interested in +} + +void SAL_CALL AccessibleControlShape::elementReplaced( const css::container::ContainerEvent& ) +{ + // not interested in +} + +AccessibleControlShape* AccessibleControlShape::GetLabeledByControlShape( ) +{ + if(m_xControlModel.is()) + { + Any sCtlLabelBy; + // get the "label by" property value of the control + if (::comphelper::hasProperty(LABEL_CONTROL_PROPERTY_NAME, m_xControlModel)) + { + sCtlLabelBy = m_xControlModel->getPropertyValue(LABEL_CONTROL_PROPERTY_NAME); + if( sCtlLabelBy.hasValue() ) + { + Reference< XPropertySet > xAsSet (sCtlLabelBy, UNO_QUERY); + AccessibleControlShape* pCtlAccShape = mpParent->GetAccControlShapeFromModel(xAsSet.get()); + return pCtlAccShape; + } + } + } + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/AccessibleEmptyEditSource.cxx b/svx/source/accessibility/AccessibleEmptyEditSource.cxx new file mode 100644 index 0000000000..7ff9ec6610 --- /dev/null +++ b/svx/source/accessibility/AccessibleEmptyEditSource.cxx @@ -0,0 +1,332 @@ +/* -*- 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 . + */ + + +// Global header + + +#include <memory> +#include <svl/itemset.hxx> +#include <editeng/editdata.hxx> +#include <editeng/outliner.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdpool.hxx> + + +// Project-local header + + +#include "AccessibleEmptyEditSource.hxx" +#include <svx/unoshtxt.hxx> + +namespace accessibility +{ + namespace { + + /** This class simply wraps a SvxTextEditSource, forwarding all + methods except the GetBroadcaster() call + */ + class AccessibleProxyEditSource_Impl : public SvxEditSource + { + public: + /** Construct AccessibleEmptyEditSource_Impl + + @param rBrdCast + + Proxy broadcaster to allow seamless flipping of edit source implementations. ProxyEditSource and EmptyEditSource + */ + AccessibleProxyEditSource_Impl( SdrObject& rObj, + SdrView& rView, + const OutputDevice& rViewWindow ); + + // from the SvxEditSource interface + SvxTextForwarder* GetTextForwarder() override; + SvxViewForwarder* GetViewForwarder() override; + SvxEditViewForwarder* GetEditViewForwarder( bool bCreate = false ) override; + + std::unique_ptr<SvxEditSource> Clone() const override; + + void UpdateData() override; + + SfxBroadcaster& GetBroadcaster() const override; + + private: + SvxTextEditSource maEditSource; + + }; + + /** Dummy class, faking exactly one empty paragraph for EditEngine accessibility + */ + class AccessibleEmptyEditSource_Impl : public SvxEditSource, public SvxViewForwarder, public SvxTextForwarder, public SfxBroadcaster + { + public: + + AccessibleEmptyEditSource_Impl() {} + + // SvxEditSource + SvxTextForwarder* GetTextForwarder() override { return this; } + SvxViewForwarder* GetViewForwarder() override { return this; } + std::unique_ptr<SvxEditSource> Clone() const override { return nullptr; } + void UpdateData() override {} + SfxBroadcaster& GetBroadcaster() const override { return *const_cast<AccessibleEmptyEditSource_Impl*>(this); } + + // SvxTextForwarder + sal_Int32 GetParagraphCount() const override { return 1; } + sal_Int32 GetTextLen( sal_Int32 /*nParagraph*/ ) const override { return 0; } + OUString GetText( const ESelection& /*rSel*/ ) const override { return OUString(); } + SfxItemSet GetAttribs( const ESelection& /*rSel*/, EditEngineAttribs /*nOnlyHardAttrib*/ = EditEngineAttribs::All ) const override + { + // AW: Very dangerous: The former implementation used a SfxItemPool created on the + // fly which of course was deleted again ASAP. Thus, the returned SfxItemSet was using + // a deleted Pool by design. + return SfxItemSet(SdrObject::GetGlobalDrawObjectItemPool()); + } + SfxItemSet GetParaAttribs( sal_Int32 /*nPara*/ ) const override { return GetAttribs(ESelection()); } + void SetParaAttribs( sal_Int32 /*nPara*/, const SfxItemSet& /*rSet*/ ) override {} + void RemoveAttribs( const ESelection& /*rSelection*/ ) override {} + void GetPortions( sal_Int32 /*nPara*/, std::vector<sal_Int32>& /*rList*/ ) const override {} + + OUString GetStyleSheet(sal_Int32 /*nPara*/) const override { return OUString(); } + void SetStyleSheet(sal_Int32 /*nPara*/, const OUString& /*rStyleName*/) override {} + + SfxItemState GetItemState( const ESelection& /*rSel*/, sal_uInt16 /*nWhich*/ ) const override { return SfxItemState::UNKNOWN; } + SfxItemState GetItemState( sal_Int32 /*nPara*/, sal_uInt16 /*nWhich*/ ) const override { return SfxItemState::UNKNOWN; } + + SfxItemPool* GetPool() const override { return nullptr; } + + void QuickInsertText( const OUString& /*rText*/, const ESelection& /*rSel*/ ) override {} + void QuickInsertField( const SvxFieldItem& /*rFld*/, const ESelection& /*rSel*/ ) override {} + void QuickSetAttribs( const SfxItemSet& /*rSet*/, const ESelection& /*rSel*/ ) override {} + void QuickInsertLineBreak( const ESelection& /*rSel*/ ) override {} + + const SfxItemSet * GetEmptyItemSetPtr() override { return nullptr; } + + void AppendParagraph() override {} + sal_Int32 AppendTextPortion( sal_Int32 /*nPara*/, const OUString & /*rText*/, const SfxItemSet & /*rSet*/ ) override { return 0; } + + //XTextCopy + void CopyText(const SvxTextForwarder& ) override {} + + OUString CalcFieldValue( const SvxFieldItem& /*rField*/, sal_Int32 /*nPara*/, sal_Int32 /*nPos*/, std::optional<Color>& /*rpTxtColor*/, std::optional<Color>& /*rpFldColor*/, std::optional<FontLineStyle>& /*rpFldLineStyle*/ ) override + { + return OUString(); + } + void FieldClicked( const SvxFieldItem& ) override {} + + bool IsValid() const override { return true; } + + LanguageType GetLanguage( sal_Int32, sal_Int32 ) const override { return LANGUAGE_DONTKNOW; } + sal_Int32 GetFieldCount( sal_Int32 ) const override { return 0; } + EFieldInfo GetFieldInfo( sal_Int32, sal_uInt16 ) const override { return EFieldInfo(); } + EBulletInfo GetBulletInfo( sal_Int32 ) const override { return EBulletInfo(); } + tools::Rectangle GetCharBounds( sal_Int32, sal_Int32 ) const override { return tools::Rectangle(); } + tools::Rectangle GetParaBounds( sal_Int32 ) const override { return tools::Rectangle(); } + MapMode GetMapMode() const override { return MapMode(); } + OutputDevice* GetRefDevice() const override { return nullptr; } + bool GetIndexAtPoint( const Point&, sal_Int32&, sal_Int32& ) const override { return false; } + bool GetWordIndices( sal_Int32, sal_Int32, sal_Int32&, sal_Int32& ) const override { return false; } + bool GetAttributeRun( sal_Int32&, sal_Int32&, sal_Int32, sal_Int32, bool ) const override { return false; } + sal_Int32 GetLineCount( sal_Int32 nPara ) const override { return nPara == 0 ? 1 : 0; } + sal_Int32 GetLineLen( sal_Int32, sal_Int32 ) const override { return 0; } + void GetLineBoundaries( /*out*/sal_Int32 & rStart, /*out*/sal_Int32 & rEnd, sal_Int32 /*nParagraph*/, sal_Int32 /*nLine*/ ) const override { rStart = rEnd = 0; } + sal_Int32 GetLineNumberAtIndex( sal_Int32 /*nPara*/, sal_Int32 /*nIndex*/ ) const override { return 0; } + + // the following two methods would, strictly speaking, require + // a switch to a real EditSource, too. Fortunately, the + // AccessibleEditableTextPara implementation currently always + // calls GetEditViewForwarder(true) before doing + // changes. Thus, we rely on this behaviour here (problem + // when that changes: via accessibility API, it would no + // longer be possible to enter text in previously empty + // shapes). + bool Delete( const ESelection& ) override { return false; } + bool InsertText( const OUString&, const ESelection& ) override { return false; } + bool QuickFormatDoc( bool ) override { return true; } + sal_Int16 GetDepth( sal_Int32 ) const override { return -1; } + bool SetDepth( sal_Int32, sal_Int16 ) override { return true; } + + Point LogicToPixel( const Point& rPoint, const MapMode& /*rMapMode*/ ) const override { return rPoint; } + Point PixelToLogic( const Point& rPoint, const MapMode& /*rMapMode*/ ) const override { return rPoint; } + + }; + + } + + // Implementing AccessibleProxyEditSource_Impl + + + AccessibleProxyEditSource_Impl::AccessibleProxyEditSource_Impl( SdrObject& rObj, + SdrView& rView, + const OutputDevice& rViewWindow ) : + maEditSource( rObj, nullptr, rView, rViewWindow ) + { + } + + SvxTextForwarder* AccessibleProxyEditSource_Impl::GetTextForwarder() + { + return maEditSource.GetTextForwarder(); + } + + SvxViewForwarder* AccessibleProxyEditSource_Impl::GetViewForwarder() + { + return maEditSource.GetViewForwarder(); + } + + SvxEditViewForwarder* AccessibleProxyEditSource_Impl::GetEditViewForwarder( bool bCreate ) + { + return maEditSource.GetEditViewForwarder( bCreate ); + } + + std::unique_ptr<SvxEditSource> AccessibleProxyEditSource_Impl::Clone() const + { + return maEditSource.Clone(); + } + + void AccessibleProxyEditSource_Impl::UpdateData() + { + maEditSource.UpdateData(); + } + + SfxBroadcaster& AccessibleProxyEditSource_Impl::GetBroadcaster() const + { + return maEditSource.GetBroadcaster(); + } + + + // Implementing AccessibleEmptyEditSource + + + AccessibleEmptyEditSource::AccessibleEmptyEditSource( SdrObject& rObj, + SdrView& rView, + const OutputDevice& rViewWindow ) : + mpEditSource( new AccessibleEmptyEditSource_Impl() ), + mrObj(rObj), + mrView(rView), + mrViewWindow(rViewWindow), + mbEditSourceEmpty( true ) + { + StartListening( mrObj.getSdrModelFromSdrObject() ); + } + + AccessibleEmptyEditSource::~AccessibleEmptyEditSource() + { + if( !mbEditSourceEmpty ) + { + // deregister as listener + if (mpEditSource) + EndListening( mpEditSource->GetBroadcaster() ); + } + else + { + EndListening( mrObj.getSdrModelFromSdrObject() ); + } + } + + SvxTextForwarder* AccessibleEmptyEditSource::GetTextForwarder() + { + if (!mpEditSource) + return nullptr; + + return mpEditSource->GetTextForwarder(); + } + + SvxViewForwarder* AccessibleEmptyEditSource::GetViewForwarder() + { + if (!mpEditSource) + return nullptr; + + return mpEditSource->GetViewForwarder(); + } + + void AccessibleEmptyEditSource::Switch2ProxyEditSource() + { + // deregister EmptyEditSource model listener + EndListening( mrObj.getSdrModelFromSdrObject() ); + + ::std::unique_ptr< SvxEditSource > pProxySource( new AccessibleProxyEditSource_Impl(mrObj, mrView, mrViewWindow) ); + mpEditSource.swap(pProxySource); + + // register as listener + StartListening( mpEditSource->GetBroadcaster() ); + + // we've irrevocably a full EditSource now. + mbEditSourceEmpty = false; + } + + SvxEditViewForwarder* AccessibleEmptyEditSource::GetEditViewForwarder( bool bCreate ) + { + if (!mpEditSource) + return nullptr; + + // switch edit source, if not yet done + if( mbEditSourceEmpty && bCreate ) + Switch2ProxyEditSource(); + + return mpEditSource->GetEditViewForwarder( bCreate ); + } + + std::unique_ptr<SvxEditSource> AccessibleEmptyEditSource::Clone() const + { + if (!mpEditSource) + return nullptr; + + return mpEditSource->Clone(); + } + + void AccessibleEmptyEditSource::UpdateData() + { + if (mpEditSource) + mpEditSource->UpdateData(); + } + + SfxBroadcaster& AccessibleEmptyEditSource::GetBroadcaster() const + { + return *const_cast<AccessibleEmptyEditSource*>(this); + } + + void AccessibleEmptyEditSource::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) + { + const SdrHint* pSdrHint = ( rHint.GetId() == SfxHintId::ThisIsAnSdrHint ? static_cast<const SdrHint*>(&rHint) : nullptr ); + + if( pSdrHint && pSdrHint->GetKind() == SdrHintKind::BeginEdit && + &mrObj == pSdrHint->GetObject() && mpEditSource ) + { + // switch edit source, if not yet done. This is necessary + // to become a full-fledged EditSource the first time a + // user start entering text in a previously empty object. + if( mbEditSourceEmpty ) + Switch2ProxyEditSource(); + } + else if (pSdrHint && pSdrHint->GetObject()!=nullptr) + { + // When the SdrObject just got a para outliner object then + // switch the edit source. + if (pSdrHint->GetObject()->GetOutlinerParaObject() != nullptr) + Switch2ProxyEditSource(); + } + + // forward messages + Broadcast( rHint ); + } + +} // end of namespace accessibility + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/AccessibleEmptyEditSource.hxx b/svx/source/accessibility/AccessibleEmptyEditSource.hxx new file mode 100644 index 0000000000..6cfceeda46 --- /dev/null +++ b/svx/source/accessibility/AccessibleEmptyEditSource.hxx @@ -0,0 +1,93 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_ACCESSIBILITY_ACCESSIBLEEMPTYEDITSOURCE_HXX +#define INCLUDED_SVX_SOURCE_ACCESSIBILITY_ACCESSIBLEEMPTYEDITSOURCE_HXX + +#include <svl/SfxBroadcaster.hxx> +#include <svl/lstner.hxx> + +#include <memory> +#include <editeng/unoedsrc.hxx> + +class SdrObject; +class SdrView; +class OutputDevice; + +namespace accessibility +{ + /** Proxy edit source for shapes without text + + Extracted from old SvxDummyEditSource + */ + class AccessibleEmptyEditSource : public SvxEditSource, public SfxListener, public SfxBroadcaster + { + public: + /** Create proxy edit source for shapes without text + + Since the views don't broadcast their dying, make sure that + this object gets destroyed if the view becomes invalid + + The window is necessary, since our views can display on multiple windows + + Make sure you only create such an object if the shape _really_ + does not contain text. + */ + AccessibleEmptyEditSource( SdrObject& rObj, SdrView& rView, const OutputDevice& rViewWindow ); + virtual ~AccessibleEmptyEditSource() override; + + // from the SvxEditSource interface + SvxTextForwarder* GetTextForwarder() override; + SvxViewForwarder* GetViewForwarder() override; + + std::unique_ptr<SvxEditSource> Clone() const override; + + // this method internally switches from empty to proxy mode, + // creating an SvxTextEditSource for the functionality. + SvxEditViewForwarder* GetEditViewForwarder( bool bCreate = false ) override; + + void UpdateData() override; + SfxBroadcaster& GetBroadcaster() const override; + + // from the SfxListener interface + void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; + + private: + void Switch2ProxyEditSource(); + + /** Pointer to edit source implementation. This is switched on + a GetEditViewForwarder( true ) call, to actually create a + SvxTextEditSource. + + @dyn + */ + std::unique_ptr< SvxEditSource > mpEditSource; + + SdrObject& mrObj; + SdrView& mrView; + const OutputDevice& mrViewWindow; + + bool mbEditSourceEmpty; + }; + +} // namespace accessibility + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/AccessibleFrameSelector.cxx b/svx/source/accessibility/AccessibleFrameSelector.cxx new file mode 100644 index 0000000000..39bdaa88fd --- /dev/null +++ b/svx/source/accessibility/AccessibleFrameSelector.cxx @@ -0,0 +1,381 @@ +/* -*- 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 <AccessibleFrameSelector.hxx> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <unotools/accessiblerelationsethelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <svx/frmsel.hxx> +#include <svx/dialmgr.hxx> + +#include <frmsel.hrc> + +namespace svx::a11y { + +using ::com::sun::star::lang::IndexOutOfBoundsException; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::RuntimeException; + +using namespace ::com::sun::star::accessibility; + + +AccFrameSelector::AccFrameSelector(FrameSelector& rFrameSel) + : mpFrameSel(&rFrameSel) +{ +} + +AccFrameSelector::~AccFrameSelector() +{ +} + +Reference< XAccessibleContext > AccFrameSelector::getAccessibleContext( ) +{ + return this; +} + +sal_Int64 AccFrameSelector::getAccessibleChildCount( ) +{ + SolarMutexGuard aGuard; + IsValid(); + return mpFrameSel->GetEnabledBorderCount(); +} + +Reference< XAccessible > AccFrameSelector::getAccessibleChild( sal_Int64 i ) +{ + SolarMutexGuard aGuard; + IsValid(); + + if (i < 0 || i >= getAccessibleChildCount()) + throw IndexOutOfBoundsException(); + + Reference< XAccessible > xRet = mpFrameSel->GetChildAccessible( i ); + if( !xRet.is() ) + throw RuntimeException(); + return xRet; +} + +Reference< XAccessible > AccFrameSelector::getAccessibleParent( ) +{ + SolarMutexGuard aGuard; + IsValid(); + Reference< XAccessible > xRet = mpFrameSel->getAccessibleParent(); + return xRet; +} + +sal_Int16 AccFrameSelector::getAccessibleRole( ) +{ + return AccessibleRole::OPTION_PANE; +} + +OUString AccFrameSelector::getAccessibleDescription( ) +{ + SolarMutexGuard aGuard; + IsValid(); + return SvxResId(RID_SVXSTR_FRMSEL_DESCRIPTIONS[0].first); +} + +OUString AccFrameSelector::getAccessibleName( ) +{ + SolarMutexGuard aGuard; + IsValid(); + return SvxResId(RID_SVXSTR_FRMSEL_TEXTS[0].first); +} + +Reference< XAccessibleRelationSet > AccFrameSelector::getAccessibleRelationSet( ) +{ + SolarMutexGuard aGuard; + IsValid(); + return mpFrameSel->get_accessible_relation_set(); +} + +sal_Int64 AccFrameSelector::getAccessibleStateSet( ) +{ + SolarMutexGuard aGuard; + sal_Int64 nStateSet = 0; + + if(!mpFrameSel) + nStateSet |= AccessibleStateType::DEFUNC; + else + { + // add standard states + nStateSet |= + AccessibleStateType::EDITABLE | + AccessibleStateType::FOCUSABLE | + AccessibleStateType::MULTI_SELECTABLE | + AccessibleStateType::SELECTABLE | + AccessibleStateType::SHOWING | + AccessibleStateType::VISIBLE | + AccessibleStateType::OPAQUE; + if(mpFrameSel->IsEnabled()) + { + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::SENSITIVE; + } + + if (mpFrameSel->HasFocus()) + { + nStateSet |= AccessibleStateType::ACTIVE; + nStateSet |= AccessibleStateType::FOCUSED; + nStateSet |= AccessibleStateType::SELECTED; + } + } + return nStateSet; +} + +Reference< XAccessible > AccFrameSelector::getAccessibleAtPoint( + const css::awt::Point& aPt ) +{ + SolarMutexGuard aGuard; + IsValid(); + //aPt is relative to the frame selector + return mpFrameSel->GetChildAccessible( Point( aPt.X, aPt.Y ) ); +} + +void AccFrameSelector::grabFocus( ) +{ + SolarMutexGuard aGuard; + IsValid(); + mpFrameSel->GrabFocus(); +} + +sal_Int32 AccFrameSelector::getForeground( ) +{ + SolarMutexGuard aGuard; + + //see FrameSelector::Paint + const StyleSettings& rStyles = Application::GetSettings().GetStyleSettings(); + return sal_Int32(rStyles.GetLabelTextColor()); +} + +sal_Int32 AccFrameSelector::getBackground( ) +{ + SolarMutexGuard aGuard; + + //see FrameSelector::Paint + const StyleSettings& rStyles = Application::GetSettings().GetStyleSettings(); + return sal_Int32(rStyles.GetDialogColor()); +} + +css::awt::Rectangle AccFrameSelector::implGetBounds() +{ + SolarMutexGuard aGuard; + IsValid(); + + css::awt::Rectangle aRet; + + const Point aOutPos; + Size aOutSize(mpFrameSel->GetOutputSizePixel()); + + aRet.X = aOutPos.X(); + aRet.Y = aOutPos.Y(); + aRet.Width = aOutSize.Width(); + aRet.Height = aOutSize.Height(); + + return aRet; +} + +css::awt::Point AccFrameSelector::getLocationOnScreen() +{ + SolarMutexGuard aGuard; + IsValid(); + + css::awt::Point aScreenLoc(0, 0); + + if (weld::DrawingArea* pDrawingArea = mpFrameSel->GetDrawingArea()) + { + AbsoluteScreenPixelPoint aPos = pDrawingArea->get_accessible_location_on_screen(); + aScreenLoc.X = aPos.X(); + aScreenLoc.Y = aPos.Y(); + } + + return aScreenLoc; +} + +void AccFrameSelector::IsValid() +{ + if(!mpFrameSel) + throw RuntimeException(); +} + +void AccFrameSelector::Invalidate() +{ + mpFrameSel = nullptr; +} + +AccFrameSelectorChild::AccFrameSelectorChild(FrameSelector& rFrameSel, FrameBorderType eBorder) + : mpFrameSel(&rFrameSel) + , meBorder(eBorder) +{ +} + +AccFrameSelectorChild::~AccFrameSelectorChild() +{ +} + +Reference< XAccessibleContext > AccFrameSelectorChild::getAccessibleContext( ) +{ + return this; +} + +sal_Int64 AccFrameSelectorChild::getAccessibleChildCount( ) +{ + SolarMutexGuard aGuard; + IsValid(); + return 0; +} + +Reference< XAccessible > AccFrameSelectorChild::getAccessibleChild( sal_Int64 ) +{ + throw RuntimeException(); +} + +Reference< XAccessible > AccFrameSelectorChild::getAccessibleParent( ) +{ + SolarMutexGuard aGuard; + IsValid(); + Reference< XAccessible > xRet = mpFrameSel->CreateAccessible(); + return xRet; +} + +sal_Int16 AccFrameSelectorChild::getAccessibleRole( ) +{ + return AccessibleRole::CHECK_BOX; +} + +OUString AccFrameSelectorChild::getAccessibleDescription( ) +{ + SolarMutexGuard aGuard; + IsValid(); + return SvxResId(RID_SVXSTR_FRMSEL_DESCRIPTIONS[static_cast<sal_uInt32>(meBorder)].first); +} + +OUString AccFrameSelectorChild::getAccessibleName( ) +{ + SolarMutexGuard aGuard; + IsValid(); + return SvxResId(RID_SVXSTR_FRMSEL_TEXTS[static_cast<sal_uInt32>(meBorder)].first); +} + +Reference< XAccessibleRelationSet > AccFrameSelectorChild::getAccessibleRelationSet( ) +{ + SolarMutexGuard aGuard; + IsValid(); + Reference< XAccessibleRelationSet > xRet = new utl::AccessibleRelationSetHelper; + return xRet; +} + +sal_Int64 AccFrameSelectorChild::getAccessibleStateSet( ) +{ + SolarMutexGuard aGuard; + sal_Int64 nStateSet = 0; + + if(!mpFrameSel) + nStateSet |= AccessibleStateType::DEFUNC; + else + { + nStateSet |= + AccessibleStateType::EDITABLE | + AccessibleStateType::FOCUSABLE | + AccessibleStateType::MULTI_SELECTABLE | + AccessibleStateType::SELECTABLE | + AccessibleStateType::SHOWING | + AccessibleStateType::VISIBLE | + AccessibleStateType::OPAQUE; + if(mpFrameSel->IsEnabled()) + { + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::SENSITIVE; + } + + if (mpFrameSel->HasFocus() && mpFrameSel->IsBorderSelected(meBorder)) + { + nStateSet |= AccessibleStateType::ACTIVE; + nStateSet |= AccessibleStateType::FOCUSED; + nStateSet |= AccessibleStateType::SELECTED; + } + } + return nStateSet; +} + +Reference< XAccessible > AccFrameSelectorChild::getAccessibleAtPoint( + const css::awt::Point& aPt ) +{ + SolarMutexGuard aGuard; + IsValid(); + //aPt is relative to the frame selector + return mpFrameSel->GetChildAccessible( Point( aPt.X, aPt.Y ) ); +} + +css::awt::Rectangle AccFrameSelectorChild::implGetBounds( ) +{ + SolarMutexGuard aGuard; + IsValid(); + const tools::Rectangle aSpot = mpFrameSel->GetClickBoundRect( meBorder ); + Point aPos = aSpot.TopLeft(); + Size aSz = aSpot.GetSize(); + css::awt::Rectangle aRet; + aRet.X = aPos.X(); + aRet.Y = aPos.Y(); + aRet.Width = aSz.Width(); + aRet.Height = aSz.Height(); + return aRet; +} + +void AccFrameSelectorChild::grabFocus( ) +{ + SolarMutexGuard aGuard; + IsValid(); + mpFrameSel->GrabFocus(); +} + +sal_Int32 AccFrameSelectorChild::getForeground( ) +{ + SolarMutexGuard aGuard; + + //see FrameSelector::Paint + const StyleSettings& rStyles = Application::GetSettings().GetStyleSettings(); + return sal_Int32(rStyles.GetLabelTextColor()); +} + +sal_Int32 AccFrameSelectorChild::getBackground( ) +{ + SolarMutexGuard aGuard; + + //see FrameSelector::Paint + const StyleSettings& rStyles = Application::GetSettings().GetStyleSettings(); + return sal_Int32(rStyles.GetDialogColor()); +} + +void AccFrameSelectorChild::IsValid() +{ + if(!mpFrameSel) + throw RuntimeException(); +} + +void AccFrameSelectorChild::Invalidate() +{ + mpFrameSel = nullptr; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/AccessibleGraphicShape.cxx b/svx/source/accessibility/AccessibleGraphicShape.cxx new file mode 100644 index 0000000000..d94ce4886a --- /dev/null +++ b/svx/source/accessibility/AccessibleGraphicShape.cxx @@ -0,0 +1,144 @@ +/* -*- 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 <svx/AccessibleGraphicShape.hxx> + +#include <svx/ShapeTypeHandler.hxx> +#include <svx/SvxShapeTypes.hxx> +#include <svx/svdobj.hxx> + +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XShapeDescriptor.hpp> +#include <comphelper/sequence.hxx> +#include <cppuhelper/queryinterface.hxx> + +using namespace ::accessibility; +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +// internal +AccessibleGraphicShape::AccessibleGraphicShape ( + const AccessibleShapeInfo& rShapeInfo, + const AccessibleShapeTreeInfo& rShapeTreeInfo) + : AccessibleShape (rShapeInfo, rShapeTreeInfo) +{ +} + + +AccessibleGraphicShape::~AccessibleGraphicShape() +{ +} + +// XAccessibleImage +OUString SAL_CALL AccessibleGraphicShape::getAccessibleImageDescription() +{ + if (m_pShape) + return m_pShape->GetTitle(); + return AccessibleShape::getAccessibleDescription (); +} + + +sal_Int32 SAL_CALL AccessibleGraphicShape::getAccessibleImageHeight() +{ + return AccessibleShape::getSize().Height; +} + + +sal_Int32 SAL_CALL AccessibleGraphicShape::getAccessibleImageWidth() +{ + return AccessibleShape::getSize().Width; +} + +// XInterface +css::uno::Any SAL_CALL + AccessibleGraphicShape::queryInterface (const css::uno::Type & rType) +{ + css::uno::Any aReturn = AccessibleShape::queryInterface (rType); + if ( ! aReturn.hasValue()) + aReturn = ::cppu::queryInterface (rType, + static_cast<XAccessibleImage*>(this)); + return aReturn; +} + + +void SAL_CALL + AccessibleGraphicShape::acquire() + noexcept +{ + AccessibleShape::acquire (); +} + + +void SAL_CALL + AccessibleGraphicShape::release() + noexcept +{ + AccessibleShape::release (); +} + +// XServiceInfo +OUString SAL_CALL + AccessibleGraphicShape::getImplementationName() +{ + return "AccessibleGraphicShape"; +} + + +css::uno::Sequence< OUString> SAL_CALL + AccessibleGraphicShape::getSupportedServiceNames() +{ + ThrowIfDisposed (); + const css::uno::Sequence<OUString> vals { "com.sun.star.drawing.AccessibleGraphicShape" }; + return comphelper::concatSequences(AccessibleShape::getSupportedServiceNames(), vals); +} + +// XTypeProvider +uno::Sequence<uno::Type> SAL_CALL + AccessibleGraphicShape::getTypes() +{ + // Get list of types from the context base implementation... + return comphelper::concatSequences(AccessibleShape::getTypes(), + uno::Sequence { cppu::UnoType<XAccessibleImage>::get() }); +} + + +/// Create the base name of this object, i.e. the name without appended number. +OUString + AccessibleGraphicShape::CreateAccessibleBaseName() +{ + OUString sName; + + ShapeTypeId nShapeType = ShapeTypeHandler::Instance().GetTypeId (mxShape); + switch (nShapeType) + { + case DRAWING_GRAPHIC_OBJECT: + sName = "GraphicObjectShape"; + break; + + default: + sName = "UnknownAccessibleGraphicShape"; + uno::Reference<drawing::XShapeDescriptor> xDescriptor (mxShape); + if (xDescriptor.is()) + sName += ": " + xDescriptor->getShapeType(); + } + + return sName; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/AccessibleOLEShape.cxx b/svx/source/accessibility/AccessibleOLEShape.cxx new file mode 100644 index 0000000000..9275ad1803 --- /dev/null +++ b/svx/source/accessibility/AccessibleOLEShape.cxx @@ -0,0 +1,172 @@ +/* -*- 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 <svx/AccessibleOLEShape.hxx> + +#include <svx/ShapeTypeHandler.hxx> +#include <svx/SvxShapeTypes.hxx> +#include <svx/svdoole2.hxx> + +#include <comphelper/sequence.hxx> +#include <cppuhelper/queryinterface.hxx> + +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XShapeDescriptor.hpp> + +using namespace ::accessibility; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::accessibility; + +// internal +AccessibleOLEShape::AccessibleOLEShape ( + const AccessibleShapeInfo& rShapeInfo, + const AccessibleShapeTreeInfo& rShapeTreeInfo) + : AccessibleShape (rShapeInfo, rShapeTreeInfo) +{ +} + + +AccessibleOLEShape::~AccessibleOLEShape() +{ +} + +// XAccessibleAction +sal_Int32 SAL_CALL AccessibleOLEShape::getAccessibleActionCount() +{ + return 0; +} + + +sal_Bool SAL_CALL AccessibleOLEShape::doAccessibleAction (sal_Int32 /*nIndex*/) +{ + throw lang::IndexOutOfBoundsException(); +} + + +OUString SAL_CALL AccessibleOLEShape::getAccessibleActionDescription (sal_Int32 /*nIndex*/) +{ + throw lang::IndexOutOfBoundsException(); +} + + +Reference<XAccessibleKeyBinding> SAL_CALL AccessibleOLEShape::getAccessibleActionKeyBinding (sal_Int32 /*nIndex*/) +{ + throw lang::IndexOutOfBoundsException(); +} + +// XInterface +css::uno::Any SAL_CALL + AccessibleOLEShape::queryInterface (const css::uno::Type & rType) +{ + css::uno::Any aReturn = AccessibleShape::queryInterface (rType); + if ( ! aReturn.hasValue()) + aReturn = ::cppu::queryInterface (rType, + static_cast<XAccessibleAction*>(this)); + return aReturn; +} + + +void SAL_CALL + AccessibleOLEShape::acquire() + noexcept +{ + AccessibleShape::acquire (); +} + + +void SAL_CALL + AccessibleOLEShape::release() + noexcept +{ + AccessibleShape::release (); +} + +// XServiceInfo +OUString SAL_CALL + AccessibleOLEShape::getImplementationName() +{ + return "AccessibleOLEShape"; +} + + +css::uno::Sequence< OUString> SAL_CALL + AccessibleOLEShape::getSupportedServiceNames() +{ + ThrowIfDisposed(); + const css::uno::Sequence<OUString> vals { "com.sun.star.drawing.AccessibleOLEShape" }; + return comphelper::concatSequences(AccessibleShape::getSupportedServiceNames(), vals); +} + +// XTypeProvider +uno::Sequence<uno::Type> SAL_CALL AccessibleOLEShape::getTypes() +{ + // Get list of types from the context base implementation... + return comphelper::concatSequences(AccessibleShape::getTypes(), + uno::Sequence { cppu::UnoType<XAccessibleAction>::get() } ); +} + +// XAccessibleExtendedAttributes +uno::Any SAL_CALL AccessibleOLEShape::getExtendedAttributes() +{ + uno::Any strRet; + OUString style; + if( m_pShape ) + { + style = "style:" + static_cast<SdrOle2Obj*>(m_pShape)->GetStyleString(); + } + style += ";"; + strRet <<= style; + return strRet; +} + +/// Set this object's name if is different to the current name. +OUString + AccessibleOLEShape::CreateAccessibleBaseName() +{ + OUString sName; + + ShapeTypeId nShapeType = ShapeTypeHandler::Instance().GetTypeId (mxShape); + switch (nShapeType) + { + case DRAWING_APPLET: + sName = "AppletOLEShape"; + break; + case DRAWING_FRAME: + sName = "FrameOLEShape"; + break; + case DRAWING_OLE: + sName = "OLEShape"; + break; + case DRAWING_PLUGIN: + sName = "PluginOLEShape"; + break; + + default: + sName = "UnknownAccessibleOLEShape"; + uno::Reference<drawing::XShapeDescriptor> xDescriptor (mxShape); + if (xDescriptor.is()) + sName += ": " + xDescriptor->getShapeType(); + } + + return sName; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/AccessibleShape.cxx b/svx/source/accessibility/AccessibleShape.cxx new file mode 100644 index 0000000000..1edc2c8a26 --- /dev/null +++ b/svx/source/accessibility/AccessibleShape.cxx @@ -0,0 +1,1294 @@ +/* -*- 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 <svx/AccessibleShape.hxx> +#include <svx/AccessibleShapeInfo.hxx> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/document/XShapeEventBroadcaster.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/text/XText.hpp> +#include <sal/log.hxx> +#include <editeng/unoedsrc.hxx> +#include <svx/AccessibleTextHelper.hxx> +#include <svx/ChildrenManager.hxx> +#include <svx/IAccessibleParent.hxx> +#include <svx/IAccessibleViewForwarder.hxx> +#include <svx/unoshtxt.hxx> +#include <svx/svdobj.hxx> +#include <svx/unoapi.hxx> +#include <svx/svdpage.hxx> +#include <svx/ShapeTypeHandler.hxx> +#include <svx/SvxShapeTypes.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <svx/svdview.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <comphelper/sequence.hxx> +#include "AccessibleEmptyEditSource.hxx" + +#include <algorithm> +#include <memory> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::lang::IndexOutOfBoundsException; +using ::com::sun::star::uno::RuntimeException; + +namespace accessibility { + +namespace { + +OUString GetOptionalProperty ( + const Reference<beans::XPropertySet>& rxSet, + const OUString& rsPropertyName) +{ + OUString sValue; + + if (rxSet.is()) + { + const Reference<beans::XPropertySetInfo> xInfo (rxSet->getPropertySetInfo()); + if ( ! xInfo.is() || xInfo->hasPropertyByName(rsPropertyName)) + { + try + { + rxSet->getPropertyValue(rsPropertyName) >>= sValue; + } + catch (beans::UnknownPropertyException&) + { + // This exception should only be thrown when the property + // does not exits (of course) and the XPropertySetInfo is + // not available. + } + } + } + return sValue; +} + +} // end of anonymous namespace + +// internal +AccessibleShape::AccessibleShape ( + const AccessibleShapeInfo& rShapeInfo, + const AccessibleShapeTreeInfo& rShapeTreeInfo) + : AccessibleContextBase (rShapeInfo.mxParent,AccessibleRole::SHAPE), + mxShape (rShapeInfo.mxShape), + maShapeTreeInfo (rShapeTreeInfo), + m_nIndexInParent(-1), + mpParent (rShapeInfo.mpChildrenManager) +{ + m_pShape = SdrObject::getSdrObjectFromXShape(mxShape); + UpdateNameAndDescription(); +} + +AccessibleShape::~AccessibleShape() +{ + mpChildrenManager.reset(); + mpText.reset(); + SAL_INFO("svx", "~AccessibleShape"); + + // Unregistering from the various broadcasters should be unnecessary + // since this destructor would not have been called if one of the + // broadcasters would still hold a strong reference to this object. +} + +void AccessibleShape::Init() +{ + // Update the OPAQUE and SELECTED shape. + UpdateStates (); + + // Create a children manager when this shape has children of its own. + Reference<drawing::XShapes> xShapes (mxShape, uno::UNO_QUERY); + if (xShapes.is() && xShapes->getCount() > 0) + mpChildrenManager.reset( new ChildrenManager ( + this, xShapes, maShapeTreeInfo, *this) ); + if (mpChildrenManager != nullptr) + mpChildrenManager->Update(); + + // Register at model as document::XEventListener. + if (mxShape.is() && maShapeTreeInfo.GetModelBroadcaster().is()) + maShapeTreeInfo.GetModelBroadcaster()->addShapeEventListener(mxShape, + static_cast<document::XShapeEventListener*>(this)); + + // Beware! Here we leave the paths of the UNO API and descend into the + // depths of the core. Necessary for making the edit engine + // accessible. + Reference<text::XText> xText (mxShape, uno::UNO_QUERY); + if (!xText.is()) + return; + + SdrView* pView = maShapeTreeInfo.GetSdrView (); + const vcl::Window* pWindow = maShapeTreeInfo.GetWindow (); + if (!(pView != nullptr && pWindow != nullptr && mxShape.is())) + return; + + // #107948# Determine whether shape text is empty + SdrObject* pSdrObject = SdrObject::getSdrObjectFromXShape(mxShape); + if( !pSdrObject ) + return; + + SdrTextObj* pTextObj = DynCastSdrTextObj( pSdrObject ); + const bool hasOutlinerParaObject = (pTextObj && pTextObj->CanCreateEditOutlinerParaObject()) || (pSdrObject->GetOutlinerParaObject() != nullptr); + + // create AccessibleTextHelper to handle this shape's text + if( !hasOutlinerParaObject ) + { + // empty text -> use proxy edit source to delay creation of EditEngine + mpText.reset( new AccessibleTextHelper( std::make_unique<AccessibleEmptyEditSource >(*pSdrObject, *pView, *pWindow->GetOutDev()) ) ); + } + else + { + // non-empty text -> use full-fledged edit source right away + mpText.reset( new AccessibleTextHelper( std::make_unique<SvxTextEditSource >(*pSdrObject, nullptr, *pView, *pWindow->GetOutDev()) ) ); + } + if( pWindow->HasFocus() ) + mpText->SetFocus(); + + mpText->SetEventSource(this); +} + + +void AccessibleShape::UpdateStates() +{ + // Set the opaque state for certain shape types when their fill style is + // solid. + bool bShapeIsOpaque = false; + switch (ShapeTypeHandler::Instance().GetTypeId (mxShape)) + { + case DRAWING_PAGE: + case DRAWING_RECTANGLE: + case DRAWING_TEXT: + { + uno::Reference<beans::XPropertySet> xSet (mxShape, uno::UNO_QUERY); + if (xSet.is()) + { + try + { + drawing::FillStyle aFillStyle; + bShapeIsOpaque = ( xSet->getPropertyValue ("FillStyle") >>= aFillStyle) + && aFillStyle == drawing::FillStyle_SOLID; + } + catch (css::beans::UnknownPropertyException&) + { + // Ignore. + } + } + } + } + if (bShapeIsOpaque) + mnStateSet |= AccessibleStateType::OPAQUE; + else + mnStateSet &= ~AccessibleStateType::OPAQUE; + + // Set the selected state. + bool bShapeIsSelected = false; + // XXX fix_me this has to be done with an extra interface later on + if ( m_pShape && maShapeTreeInfo.GetSdrView() ) + { + bShapeIsSelected = maShapeTreeInfo.GetSdrView()->IsObjMarked(m_pShape); + } + + if (bShapeIsSelected) + mnStateSet |= AccessibleStateType::SELECTED; + else + mnStateSet &= ~AccessibleStateType::SELECTED; +} + +OUString AccessibleShape::GetStyle() const +{ + return ShapeTypeHandler::CreateAccessibleBaseName( mxShape ); +} + +bool AccessibleShape::SetState (sal_Int64 aState) +{ + bool bStateHasChanged = false; + + if (aState == AccessibleStateType::FOCUSED && mpText != nullptr) + { + // Offer FOCUSED state to edit engine and detect whether the state + // changes. + bool bIsFocused = mpText->HaveFocus (); + mpText->SetFocus(); + bStateHasChanged = (bIsFocused != mpText->HaveFocus ()); + } + else + bStateHasChanged = AccessibleContextBase::SetState (aState); + + return bStateHasChanged; +} + + +bool AccessibleShape::ResetState (sal_Int64 aState) +{ + bool bStateHasChanged = false; + + if (aState == AccessibleStateType::FOCUSED && mpText != nullptr) + { + // Try to remove FOCUSED state from the edit engine and detect + // whether the state changes. + bool bIsFocused = mpText->HaveFocus (); + mpText->SetFocus (false); + bStateHasChanged = (bIsFocused != mpText->HaveFocus ()); + } + else + bStateHasChanged = AccessibleContextBase::ResetState (aState); + + return bStateHasChanged; +} + + +bool AccessibleShape::GetState (sal_Int64 aState) +{ + if (aState == AccessibleStateType::FOCUSED && mpText != nullptr) + { + // Just delegate the call to the edit engine. The state is not + // merged into the state set. + return mpText->HaveFocus(); + } + else + return AccessibleContextBase::GetState (aState); +} + +// OverWrite the parent's getAccessibleName method +OUString SAL_CALL AccessibleShape::getAccessibleName() +{ + ThrowIfDisposed (); + if (m_pShape && !m_pShape->GetTitle().isEmpty()) + return CreateAccessibleName() + " " + m_pShape->GetTitle(); + else + return CreateAccessibleName(); +} + +OUString SAL_CALL AccessibleShape::getAccessibleDescription() +{ + ThrowIfDisposed (); + if( m_pShape && !m_pShape->GetDescription().isEmpty()) + return m_pShape->GetDescription() ; + else + return " "; +} + +// XAccessibleContext +/** The children of this shape come from two sources: The children from + group or scene shapes and the paragraphs of text. +*/ +sal_Int64 SAL_CALL + AccessibleShape::getAccessibleChildCount () +{ + if (IsDisposed()) + { + return 0; + } + + sal_Int64 nChildCount = 0; + + // Add the number of shapes that are children of this shape. + if (mpChildrenManager != nullptr) + nChildCount += mpChildrenManager->GetChildCount (); + // Add the number text paragraphs. + if (mpText != nullptr) + nChildCount += mpText->GetChildCount (); + + return nChildCount; +} + + +/** Forward the request to the shape. Return the requested shape or throw + an exception for a wrong index. +*/ +uno::Reference<XAccessible> SAL_CALL + AccessibleShape::getAccessibleChild (sal_Int64 nIndex) +{ + ThrowIfDisposed (); + + uno::Reference<XAccessible> xChild; + + // Depending on the index decide whether to delegate this call to the + // children manager or the edit engine. + if ((mpChildrenManager != nullptr) + && (nIndex < mpChildrenManager->GetChildCount())) + { + xChild = mpChildrenManager->GetChild (nIndex); + } + else if (mpText != nullptr) + { + sal_Int64 nI = nIndex; + if (mpChildrenManager != nullptr) + nI -= mpChildrenManager->GetChildCount(); + xChild = mpText->GetChild (nI); + } + else + throw lang::IndexOutOfBoundsException ( + "shape has no child with index " + OUString::number(nIndex), + getXWeak()); + + return xChild; +} + +uno::Reference<XAccessibleRelationSet> SAL_CALL + AccessibleShape::getAccessibleRelationSet() +{ + ::osl::MutexGuard aGuard (m_aMutex); + if (mpParent == nullptr) + return uno::Reference<XAccessibleRelationSet>(); + + rtl::Reference<::utl::AccessibleRelationSetHelper> pRelationSet = new utl::AccessibleRelationSetHelper; + + //this mxshape is the captioned shape + uno::Sequence< uno::Reference< uno::XInterface > > aSequence { mpParent->GetAccessibleCaption(mxShape) }; + if(aSequence[0]) + { + pRelationSet->AddRelation( + AccessibleRelation( AccessibleRelationType::DESCRIBED_BY, aSequence ) ); + } + return pRelationSet; +} + +/** Return a copy of the state set. + Possible states are: + ENABLED + SHOWING + VISIBLE +*/ +sal_Int64 SAL_CALL + AccessibleShape::getAccessibleStateSet() +{ + ::osl::MutexGuard aGuard (m_aMutex); + + if (IsDisposed()) + { + // Return a minimal state set that only contains the DEFUNC state. + return AccessibleContextBase::getAccessibleStateSet (); + } + + // Merge current FOCUSED state from edit engine. + if (mpText) + { + if (mpText->HaveFocus()) + mnStateSet |= AccessibleStateType::FOCUSED; + else + mnStateSet &= ~AccessibleStateType::FOCUSED; + } + //Just when the document is not read-only,set states EDITABLE,RESIZABLE,MOVEABLE + css::uno::Reference<XAccessible> xTempAcc = getAccessibleParent(); + if( xTempAcc.is() ) + { + css::uno::Reference<XAccessibleContext> + xTempAccContext = xTempAcc->getAccessibleContext(); + if( xTempAccContext.is() ) + { + sal_Int64 nState = xTempAccContext->getAccessibleStateSet(); + if (nState & AccessibleStateType::EDITABLE) + { + mnStateSet |= AccessibleStateType::EDITABLE; + mnStateSet |= AccessibleStateType::RESIZABLE; + mnStateSet |= AccessibleStateType::MOVEABLE; + } + } + } + + sal_Int64 nRetStateSet = mnStateSet; + + if (mpParent && mpParent->IsDocumentSelAll()) + { + nRetStateSet |= AccessibleStateType::SELECTED; + } + + return nRetStateSet; +} + +// XAccessibleComponent +/** The implementation below is at the moment straightforward. It iterates + over all children (and thereby instances all children which have not + been already instantiated) until a child covering the specified point is + found. + This leaves room for improvement. For instance, first iterate only over + the already instantiated children and only if no match is found + instantiate the remaining ones. +*/ +uno::Reference<XAccessible > SAL_CALL + AccessibleShape::getAccessibleAtPoint ( + const awt::Point& aPoint) +{ + ::osl::MutexGuard aGuard (m_aMutex); + + sal_Int64 nChildCount = getAccessibleChildCount (); + for (sal_Int64 i = 0; i < nChildCount; ++i) + { + Reference<XAccessible> xChild (getAccessibleChild (i)); + if (xChild.is()) + { + Reference<XAccessibleComponent> xChildComponent ( + xChild->getAccessibleContext(), uno::UNO_QUERY); + if (xChildComponent.is()) + { + awt::Rectangle aBBox (xChildComponent->getBounds()); + if ( (aPoint.X >= aBBox.X) + && (aPoint.Y >= aBBox.Y) + && (aPoint.X < aBBox.X+aBBox.Width) + && (aPoint.Y < aBBox.Y+aBBox.Height) ) + return xChild; + } + } + } + + // Have not found a child under the given point. Returning empty + // reference to indicate this. + return uno::Reference<XAccessible>(); +} + + +awt::Rectangle SAL_CALL AccessibleShape::getBounds() +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard (m_aMutex); + + ThrowIfDisposed (); + awt::Rectangle aBoundingBox; + if ( mxShape.is() ) + { + + static constexpr OUString sBoundRectName = u"BoundRect"_ustr; + static constexpr OUString sAnchorPositionName = u"AnchorPosition"_ustr; + + // Get the shape's bounding box in internal coordinates (in 100th of + // mm). Use the property BoundRect. Only if that is not supported ask + // the shape for its position and size directly. + Reference<beans::XPropertySet> xSet (mxShape, uno::UNO_QUERY); + Reference<beans::XPropertySetInfo> xSetInfo; + bool bFoundBoundRect = false; + if (xSet.is()) + { + xSetInfo = xSet->getPropertySetInfo (); + if (xSetInfo.is()) + { + if (xSetInfo->hasPropertyByName (sBoundRectName)) + { + try + { + uno::Any aValue = xSet->getPropertyValue (sBoundRectName); + aValue >>= aBoundingBox; + bFoundBoundRect = true; + } + catch (beans::UnknownPropertyException const&) + { + // Handled below (bFoundBoundRect stays false). + } + } + else + SAL_WARN("svx", "no property BoundRect"); + } + } + + // Fallback when there is no BoundRect Property. + if ( ! bFoundBoundRect ) + { + awt::Point aPosition (mxShape->getPosition()); + awt::Size aSize (mxShape->getSize()); + aBoundingBox = awt::Rectangle ( + aPosition.X, aPosition.Y, + aSize.Width, aSize.Height); + + // While BoundRects have absolute positions, the position returned + // by XPosition::getPosition is relative. Get the anchor position + // (usually not (0,0) for Writer shapes). + if (xSetInfo.is()) + { + if (xSetInfo->hasPropertyByName (sAnchorPositionName)) + { + uno::Any aPos = xSet->getPropertyValue (sAnchorPositionName); + awt::Point aAnchorPosition; + aPos >>= aAnchorPosition; + aBoundingBox.X += aAnchorPosition.X; + aBoundingBox.Y += aAnchorPosition.Y; + } + } + } + + // Transform coordinates from internal to pixel. + if (maShapeTreeInfo.GetViewForwarder() == nullptr) + throw uno::RuntimeException ( + "AccessibleShape has no valid view forwarder", + getXWeak()); + ::Size aPixelSize = maShapeTreeInfo.GetViewForwarder()->LogicToPixel ( + ::Size (aBoundingBox.Width, aBoundingBox.Height)); + ::Point aPixelPosition = maShapeTreeInfo.GetViewForwarder()->LogicToPixel ( + ::Point (aBoundingBox.X, aBoundingBox.Y)); + + // Clip the shape's bounding box with the bounding box of its parent. + Reference<XAccessibleComponent> xParentComponent ( + getAccessibleParent(), uno::UNO_QUERY); + if (xParentComponent.is()) + { + // Make the coordinates relative to the parent. + awt::Point aParentLocation (xParentComponent->getLocationOnScreen()); + int x = aPixelPosition.getX() - aParentLocation.X; + int y = aPixelPosition.getY() - aParentLocation.Y; + + // Clip with parent (with coordinates relative to itself). + ::tools::Rectangle aBBox ( + x, y, x + aPixelSize.getWidth(), y + aPixelSize.getHeight()); + awt::Size aParentSize (xParentComponent->getSize()); + ::tools::Rectangle aParentBBox (0,0, aParentSize.Width, aParentSize.Height); + aBBox = aBBox.GetIntersection (aParentBBox); + aBoundingBox = awt::Rectangle ( + aBBox.Left(), + aBBox.Top(), + aBBox.getOpenWidth(), + aBBox.getOpenHeight()); + } + else + { + SAL_INFO("svx", "parent does not support component"); + aBoundingBox = awt::Rectangle ( + aPixelPosition.getX(), aPixelPosition.getY(), + aPixelSize.getWidth(), aPixelSize.getHeight()); + } + } + + return aBoundingBox; +} + + +awt::Point SAL_CALL AccessibleShape::getLocation() +{ + ThrowIfDisposed (); + awt::Rectangle aBoundingBox (getBounds()); + return awt::Point (aBoundingBox.X, aBoundingBox.Y); +} + + +awt::Point SAL_CALL AccessibleShape::getLocationOnScreen() +{ + ThrowIfDisposed (); + + // Get relative position... + awt::Point aLocation (getLocation ()); + + // ... and add absolute position of the parent. + uno::Reference<XAccessibleComponent> xParentComponent ( + getAccessibleParent(), uno::UNO_QUERY); + if (xParentComponent.is()) + { + awt::Point aParentLocation (xParentComponent->getLocationOnScreen()); + aLocation.X += aParentLocation.X; + aLocation.Y += aParentLocation.Y; + } + else + SAL_WARN("svx", "parent does not support XAccessibleComponent"); + return aLocation; +} + + +awt::Size SAL_CALL AccessibleShape::getSize() +{ + ThrowIfDisposed (); + awt::Rectangle aBoundingBox (getBounds()); + return awt::Size (aBoundingBox.Width, aBoundingBox.Height); +} + + +sal_Int32 SAL_CALL AccessibleShape::getForeground() +{ + ThrowIfDisposed (); + sal_Int32 nColor (0x0ffffffL); + + try + { + uno::Reference<beans::XPropertySet> aSet (mxShape, uno::UNO_QUERY); + if (aSet.is()) + { + uno::Any aColor; + aColor = aSet->getPropertyValue ("LineColor"); + aColor >>= nColor; + } + } + catch (const css::beans::UnknownPropertyException &) + { + // Ignore exception and return default color. + } + return nColor; +} + + +sal_Int32 SAL_CALL AccessibleShape::getBackground() +{ + ThrowIfDisposed (); + Color nColor; + + try + { + uno::Reference<beans::XPropertySet> aSet (mxShape, uno::UNO_QUERY); + if (aSet.is()) + { + uno::Any aColor; + aColor = aSet->getPropertyValue ("FillColor"); + aColor >>= nColor; + aColor = aSet->getPropertyValue ("FillTransparence"); + short nTrans=0; + aColor >>= nTrans; + Color crBk(nColor); + if (nTrans == 0 ) + { + crBk.SetAlpha(0); + } + else + { + nTrans = short(256 - nTrans / 100. * 256); + crBk.SetAlpha(255 - sal_uInt8(nTrans)); + } + nColor = crBk; + } + } + catch (const css::beans::UnknownPropertyException &) + { + // Ignore exception and return default color. + } + return sal_Int32(nColor); +} + +// XAccessibleEventBroadcaster +void SAL_CALL AccessibleShape::addAccessibleEventListener ( + const Reference<XAccessibleEventListener >& rxListener) +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + uno::Reference<uno::XInterface> xThis ( + static_cast<lang::XComponent *>(this), uno::UNO_QUERY); + rxListener->disposing (lang::EventObject (xThis)); + } + else + { + AccessibleContextBase::addAccessibleEventListener (rxListener); + if (mpText != nullptr) + mpText->AddEventListener (rxListener); + } +} + + +void SAL_CALL AccessibleShape::removeAccessibleEventListener ( + const Reference<XAccessibleEventListener >& rxListener) +{ + AccessibleContextBase::removeAccessibleEventListener (rxListener); + if (mpText != nullptr) + mpText->RemoveEventListener (rxListener); +} + +// XInterface +css::uno::Any SAL_CALL + AccessibleShape::queryInterface (const css::uno::Type & rType) +{ + css::uno::Any aReturn = AccessibleContextBase::queryInterface (rType); + if ( ! aReturn.hasValue()) + aReturn = ::cppu::queryInterface (rType, + static_cast<XAccessibleComponent*>(this), + static_cast<XAccessibleExtendedComponent*>(this), + static_cast< css::accessibility::XAccessibleSelection* >(this), + static_cast< css::accessibility::XAccessibleExtendedAttributes* >(this), + static_cast<document::XShapeEventListener*>(this), + static_cast<lang::XUnoTunnel*>(this), + static_cast<XAccessibleGroupPosition*>(this), + static_cast<XAccessibleHypertext*>(this) + ); + return aReturn; +} + + +void SAL_CALL + AccessibleShape::acquire() + noexcept +{ + AccessibleContextBase::acquire (); +} + + +void SAL_CALL + AccessibleShape::release() + noexcept +{ + AccessibleContextBase::release (); +} + +// XAccessibleSelection +void SAL_CALL AccessibleShape::selectAccessibleChild( sal_Int64 ) +{ +} + + +sal_Bool SAL_CALL AccessibleShape::isAccessibleChildSelected( sal_Int64 nChildIndex ) +{ + uno::Reference<XAccessible> xAcc = getAccessibleChild( nChildIndex ); + uno::Reference<XAccessibleContext> xContext; + if( xAcc.is() ) + { + xContext = xAcc->getAccessibleContext(); + } + + if( xContext.is() ) + { + if( xContext->getAccessibleRole() == AccessibleRole::PARAGRAPH ) + { + uno::Reference< css::accessibility::XAccessibleText > + xText(xAcc, uno::UNO_QUERY); + if( xText.is() ) + { + if( xText->getSelectionStart() >= 0 ) return true; + } + } + else if( xContext->getAccessibleRole() == AccessibleRole::SHAPE ) + { + sal_Int64 pRState = xContext->getAccessibleStateSet(); + + return bool(pRState & AccessibleStateType::SELECTED); + } + } + + return false; +} + + +void SAL_CALL AccessibleShape::clearAccessibleSelection( ) +{ +} + + +void SAL_CALL AccessibleShape::selectAllAccessibleChildren( ) +{ +} + + +sal_Int64 SAL_CALL AccessibleShape::getSelectedAccessibleChildCount() +{ + sal_Int64 nCount = 0; + sal_Int64 TotalCount = getAccessibleChildCount(); + for( sal_Int64 i = 0; i < TotalCount; i++ ) + if( isAccessibleChildSelected(i) ) nCount++; + + return nCount; +} + + +Reference<XAccessible> SAL_CALL AccessibleShape::getSelectedAccessibleChild( sal_Int64 nSelectedChildIndex ) +{ + if ( nSelectedChildIndex > getSelectedAccessibleChildCount() ) + throw IndexOutOfBoundsException(); + for (sal_Int64 i1 = 0, i2 = 0; i1 < getAccessibleChildCount(); i1++) + if( isAccessibleChildSelected(i1) ) + { + if( i2 == nSelectedChildIndex ) + return getAccessibleChild( i1 ); + i2++; + } + return Reference<XAccessible>(); +} + + +void SAL_CALL AccessibleShape::deselectAccessibleChild( sal_Int64 ) +{ + +} + +// XAccessibleExtendedAttributes +uno::Any SAL_CALL AccessibleShape::getExtendedAttributes() +{ + uno::Any strRet; + OUString style; + if( getAccessibleRole() != AccessibleRole::SHAPE ) return strRet; + if( m_pShape ) + { + style = "style:" + GetStyle(); + } + style += ";"; + strRet <<= style; + return strRet; +} + +// XServiceInfo +OUString SAL_CALL + AccessibleShape::getImplementationName() +{ + return "AccessibleShape"; +} + + +uno::Sequence<OUString> SAL_CALL + AccessibleShape::getSupportedServiceNames() +{ + ThrowIfDisposed (); + const css::uno::Sequence<OUString> vals { "com.sun.star.drawing.AccessibleShape" }; + return comphelper::concatSequences(AccessibleContextBase::getSupportedServiceNames(), vals); +} + +// XTypeProvider +uno::Sequence<uno::Type> SAL_CALL + AccessibleShape::getTypes() +{ + ThrowIfDisposed (); + // Get list of types from the context base implementation, ... + uno::Sequence<uno::Type> aTypeList (AccessibleContextBase::getTypes()); + // ... get list of types from component base implementation, ... + uno::Sequence<uno::Type> aComponentTypeList (AccessibleComponentBase::getTypes()); + // ... define local types + uno::Sequence<uno::Type> localTypesList = { + cppu::UnoType<lang::XEventListener>::get(), + cppu::UnoType<document::XEventListener>::get(), + cppu::UnoType<lang::XUnoTunnel>::get() + }; + + return comphelper::concatSequences(aTypeList, aComponentTypeList, localTypesList); +} + +// lang::XEventListener +/** Disposing calls are accepted only from the model: Just reset the + reference to the model in the shape tree info. Otherwise this object + remains functional. +*/ +void AccessibleShape::disposing (const lang::EventObject& aEvent) +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard (m_aMutex); + + try + { + if (aEvent.Source == maShapeTreeInfo.GetModelBroadcaster()) + { + // Remove reference to model broadcaster to allow it to pass + // away. + maShapeTreeInfo.SetModelBroadcaster(nullptr); + } + + } + catch (uno::RuntimeException const&) + { + TOOLS_WARN_EXCEPTION("svx", "caught exception while disposing"); + } + mpChildrenManager.reset(); + mxShape.clear(); + maShapeTreeInfo.dispose(); + mpText.reset(); +} + +// document::XShapeEventListener +void SAL_CALL + AccessibleShape::notifyShapeEvent (const document::EventObject& rEventObject) +{ + if (rEventObject.EventName != "ShapeModified") + return; + + //Need to update text children when receiving ShapeModified hint when exiting edit mode for text box + if (mpText) + mpText->UpdateChildren(); + + + // Some property of a shape has been modified. Send an event + // that indicates a change of the visible data to all listeners. + CommitChange ( + AccessibleEventId::VISIBLE_DATA_CHANGED, + uno::Any(), + uno::Any(), -1); + + // Name and Description may have changed. Update the local + // values accordingly. + UpdateNameAndDescription(); +} + +// lang::XUnoTunnel +UNO3_GETIMPLEMENTATION_IMPL(AccessibleShape) + +// IAccessibleViewForwarderListener +void AccessibleShape::ViewForwarderChanged() +{ + // Inform all listeners that the graphical representation (i.e. size + // and/or position) of the shape has changed. + CommitChange (AccessibleEventId::VISIBLE_DATA_CHANGED, + uno::Any(), + uno::Any(), -1); + + // Tell children manager of the modified view forwarder. + if (mpChildrenManager != nullptr) + mpChildrenManager->ViewForwarderChanged(); + + // update our children that our screen position might have changed + if( mpText ) + mpText->UpdateChildren(); +} + +// protected internal +// Set this object's name if is different to the current name. +OUString AccessibleShape::CreateAccessibleBaseName() +{ + return ShapeTypeHandler::CreateAccessibleBaseName( mxShape ); +} + + +OUString AccessibleShape::CreateAccessibleName() +{ + return GetFullAccessibleName(this); +} + +OUString AccessibleShape::GetFullAccessibleName (AccessibleShape *shape) +{ + OUString sName (shape->CreateAccessibleBaseName()); + // Append the shape's index to the name to disambiguate between shapes + // of the same type. If such an index where not given to the + // constructor then use the z-order instead. If even that does not exist + // we throw an exception. + OUString nameStr; + if (shape->m_pShape) + nameStr = shape->m_pShape->GetName(); + if (nameStr.isEmpty()) + { + sName += " "; + } + else + { + sName = nameStr; + } + + //If the new produced name if not the same with last,notify name changed + //Event + if (aAccName != sName && !aAccName.isEmpty()) + { + uno::Any aOldValue, aNewValue; + aOldValue <<= aAccName; + aNewValue <<= sName; + CommitChange( + AccessibleEventId::NAME_CHANGED, + aNewValue, + aOldValue, -1); + } + aAccName = sName; + return sName; +} + +// protected +void AccessibleShape::disposing() +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard (m_aMutex); + + // Make sure to send an event that this object loses the focus in the + // case that it has the focus. + mnStateSet &= ~AccessibleStateType::FOCUSED; + + // Unregister from model. + if (mxShape.is() && maShapeTreeInfo.GetModelBroadcaster().is()) + maShapeTreeInfo.GetModelBroadcaster()->removeShapeEventListener(mxShape, + static_cast<document::XShapeEventListener*>(this)); + + // Release the child containers. + if (mpChildrenManager != nullptr) + { + mpChildrenManager.reset(); + } + if (mpText != nullptr) + { + mpText->Dispose(); + mpText.reset(); + } + + // Cleanup. Remove references to objects to allow them to be + // destroyed. + mxShape = nullptr; + maShapeTreeInfo.dispose(); + + // Call base classes. + AccessibleContextBase::dispose (); +} + +sal_Int64 SAL_CALL + AccessibleShape::getAccessibleIndexInParent() +{ + ThrowIfDisposed (); + // Use a simple but slow solution for now. Optimize later. + + sal_Int64 nIndex = m_nIndexInParent; + if ( -1 == nIndex ) + nIndex = AccessibleContextBase::getAccessibleIndexInParent(); + return nIndex; +} + + +void AccessibleShape::UpdateNameAndDescription() +{ + // Ignore missing title, name, or description. There are fallbacks for + // them. + try + { + Reference<beans::XPropertySet> xSet (mxShape, uno::UNO_QUERY_THROW); + + // Get the accessible name. + OUString sString = GetOptionalProperty(xSet, "Title"); + if (!sString.isEmpty()) + { + SetAccessibleName(sString, AccessibleContextBase::FromShape); + } + else + { + sString = GetOptionalProperty(xSet, "Name"); + if (!sString.isEmpty()) + SetAccessibleName(sString, AccessibleContextBase::FromShape); + } + + // Get the accessible description. + sString = GetOptionalProperty(xSet, "Description"); + if (!sString.isEmpty()) + SetAccessibleDescription(sString, AccessibleContextBase::FromShape); + } + catch (uno::RuntimeException&) + { + } +} + +// Return this object's role. +sal_Int16 SAL_CALL AccessibleShape::getAccessibleRole() +{ + sal_Int16 nAccessibleRole = AccessibleRole::SHAPE ; + switch (ShapeTypeHandler::Instance().GetTypeId (mxShape)) + { + case DRAWING_GRAPHIC_OBJECT: + nAccessibleRole = AccessibleRole::GRAPHIC ; break; + case DRAWING_OLE: + nAccessibleRole = AccessibleRole::EMBEDDED_OBJECT ; break; + + default: + nAccessibleRole = AccessibleContextBase::getAccessibleRole(); + break; + } + + return nAccessibleRole; +} + +namespace { + +//sort the drawing objects from up to down, from left to right +struct XShapePosCompareHelper +{ + bool operator() ( const uno::Reference<drawing::XShape>& xshape1, + const uno::Reference<drawing::XShape>& xshape2 ) const + { + SdrObject* pObj1 = SdrObject::getSdrObjectFromXShape(xshape1); + SdrObject* pObj2 = SdrObject::getSdrObjectFromXShape(xshape2); + if(pObj1 && pObj2) + return pObj1->GetOrdNum() < pObj2->GetOrdNum(); + else + return false; + } +}; + +} +//end of group position + +// XAccessibleGroupPosition +uno::Sequence< sal_Int32 > SAL_CALL +AccessibleShape::getGroupPosition( const uno::Any& ) +{ + // we will return the: + // [0] group level + // [1] similar items counts in the group + // [2] the position of the object in the group + uno::Sequence< sal_Int32 > aRet{ 0, 0, 0 }; + + css::uno::Reference<XAccessible> xParent = getAccessibleParent(); + if (!xParent.is()) + { + return aRet; + } + SdrObject *pObj = SdrObject::getSdrObjectFromXShape(mxShape); + + + if(pObj == nullptr ) + { + return aRet; + } + + // Compute object's group level. + sal_Int32 nGroupLevel = 0; + SdrObject * pUper = pObj->getParentSdrObjectFromSdrObject(); + while( pUper ) + { + ++nGroupLevel; + pUper = pUper->getParentSdrObjectFromSdrObject(); + } + + css::uno::Reference<XAccessibleContext> xParentContext = xParent->getAccessibleContext(); + if( xParentContext->getAccessibleRole() == AccessibleRole::DOCUMENT || + xParentContext->getAccessibleRole() == AccessibleRole::DOCUMENT_PRESENTATION || + xParentContext->getAccessibleRole() == AccessibleRole::DOCUMENT_SPREADSHEET || + xParentContext->getAccessibleRole() == AccessibleRole::DOCUMENT_TEXT )//Document + { + Reference< XAccessibleGroupPosition > xGroupPosition( xParent,uno::UNO_QUERY ); + if ( xGroupPosition.is() ) + { + aRet = xGroupPosition->getGroupPosition( uno::Any( getAccessibleContext() ) ); + } + return aRet; + } + if (xParentContext->getAccessibleRole() != AccessibleRole::SHAPE) + { + return aRet; + } + + SdrObjList *pGrpList = nullptr; + if( pObj->getParentSdrObjectFromSdrObject() ) + pGrpList = pObj->getParentSdrObjectFromSdrObject()->GetSubList(); + else + return aRet; + + std::vector< uno::Reference<drawing::XShape> > vXShapes; + if (pGrpList) + { + const size_t nObj = pGrpList->GetObjCount(); + for(size_t i = 0 ; i < nObj ; ++i) + { + SdrObject *pSubObj = pGrpList->GetObj(i); + if (pSubObj && + xParentContext->getAccessibleChild(i)->getAccessibleContext()->getAccessibleRole() != AccessibleRole::GROUP_BOX) + { + vXShapes.push_back( GetXShapeForSdrObject(pSubObj) ); + } + } + } + + std::sort( vXShapes.begin(), vXShapes.end(), XShapePosCompareHelper() ); + + //get the index of the selected object in the group + //we start counting position from 1 + sal_Int32 nPos = 1; + for ( const auto& rpShape : vXShapes ) + { + if ( rpShape.get() == mxShape.get() ) + { + sal_Int32* pArray = aRet.getArray(); + pArray[0] = nGroupLevel; + pArray[1] = vXShapes.size(); + pArray[2] = nPos; + break; + } + nPos++; + } + + return aRet; +} + +OUString AccessibleShape::getObjectLink( const uno::Any& ) +{ + OUString aRet; + + SdrObject *pObj = SdrObject::getSdrObjectFromXShape(mxShape); + if(pObj == nullptr ) + { + return aRet; + } + if (maShapeTreeInfo.GetDocumentWindow().is()) + { + Reference< XAccessibleGroupPosition > xGroupPosition( maShapeTreeInfo.GetDocumentWindow(), uno::UNO_QUERY ); + if (xGroupPosition.is()) + { + aRet = xGroupPosition->getObjectLink( uno::Any( getAccessibleContext() ) ); + } + } + return aRet; +} + +// XAccessibleHypertext +sal_Int32 SAL_CALL AccessibleShape::getHyperLinkCount() +{ + // MT: Introduced with IA2 CWS, but SvxAccessibleHyperlink was redundant to svx::AccessibleHyperlink which we introduced meanwhile. + // Code need to be adapted... + return 0; + + /* + SvxAccessibleHyperlink* pLink = new SvxAccessibleHyperlink(m_pShape,this); + if (pLink->IsValidHyperlink()) + return 1; + else + return 0; + */ +} +uno::Reference< XAccessibleHyperlink > SAL_CALL + AccessibleShape::getHyperLink( sal_Int32 ) +{ + uno::Reference< XAccessibleHyperlink > xRet; + // MT: Introduced with IA2 CWS, but SvxAccessibleHyperlink was redundant to svx::AccessibleHyperlink which we introduced meanwhile. + // Code need to be adapted... + /* + SvxAccessibleHyperlink* pLink = new SvxAccessibleHyperlink(m_pShape,this); + if (pLink->IsValidHyperlink()) + xRet = pLink; + if( !xRet.is() ) + throw css::lang::IndexOutOfBoundsException(); + */ + return xRet; +} +sal_Int32 SAL_CALL AccessibleShape::getHyperLinkIndex( sal_Int32 ) +{ + return 0; +} +// XAccessibleText +sal_Int32 SAL_CALL AccessibleShape::getCaretPosition( ){return 0;} +sal_Bool SAL_CALL AccessibleShape::setCaretPosition( sal_Int32 ){return false;} +sal_Unicode SAL_CALL AccessibleShape::getCharacter( sal_Int32 ){return 0;} +css::uno::Sequence< css::beans::PropertyValue > SAL_CALL AccessibleShape::getCharacterAttributes( sal_Int32, const css::uno::Sequence< OUString >& ) +{ + uno::Sequence< css::beans::PropertyValue > aValues(0); + return aValues; +} +css::awt::Rectangle SAL_CALL AccessibleShape::getCharacterBounds( sal_Int32 ) +{ + return css::awt::Rectangle(0, 0, 0, 0 ); +} +sal_Int32 SAL_CALL AccessibleShape::getCharacterCount( ){return 0;} +sal_Int32 SAL_CALL AccessibleShape::getIndexAtPoint( const css::awt::Point& ){return 0;} +OUString SAL_CALL AccessibleShape::getSelectedText( ){return OUString();} +sal_Int32 SAL_CALL AccessibleShape::getSelectionStart( ){return 0;} +sal_Int32 SAL_CALL AccessibleShape::getSelectionEnd( ){return 0;} +sal_Bool SAL_CALL AccessibleShape::setSelection( sal_Int32, sal_Int32 ){return true;} +OUString SAL_CALL AccessibleShape::getText( ){return OUString();} +OUString SAL_CALL AccessibleShape::getTextRange( sal_Int32, sal_Int32 ){return OUString();} +css::accessibility::TextSegment SAL_CALL AccessibleShape::getTextAtIndex( sal_Int32, sal_Int16 ) +{ + css::accessibility::TextSegment aResult; + return aResult; +} +css::accessibility::TextSegment SAL_CALL AccessibleShape::getTextBeforeIndex( sal_Int32, sal_Int16 ) +{ + css::accessibility::TextSegment aResult; + return aResult; +} +css::accessibility::TextSegment SAL_CALL AccessibleShape::getTextBehindIndex( sal_Int32, sal_Int16 ) +{ + css::accessibility::TextSegment aResult; + return aResult; +} +sal_Bool SAL_CALL AccessibleShape::copyText( sal_Int32, sal_Int32 ){return true;} +sal_Bool SAL_CALL AccessibleShape::scrollSubstringTo( sal_Int32, sal_Int32, AccessibleScrollType ){return false;} + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/AccessibleShapeInfo.cxx b/svx/source/accessibility/AccessibleShapeInfo.cxx new file mode 100644 index 0000000000..8904480adc --- /dev/null +++ b/svx/source/accessibility/AccessibleShapeInfo.cxx @@ -0,0 +1,64 @@ +/* -*- 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 <svx/AccessibleShapeInfo.hxx> +#include <utility> + + +namespace accessibility { + +AccessibleShapeInfo::AccessibleShapeInfo ( + css::uno::Reference<css::drawing::XShape> xShape, + css::uno::Reference<css::accessibility::XAccessible> xParent, + IAccessibleParent* pChildrenManager) + : mxShape (std::move(xShape)), + mxParent (std::move(xParent)), + mpChildrenManager (pChildrenManager) +{ + // empty. +} + + +AccessibleShapeInfo::AccessibleShapeInfo ( + css::uno::Reference<css::drawing::XShape> xShape, + css::uno::Reference<css::accessibility::XAccessible> xParent) + : mxShape (std::move(xShape)), + mxParent (std::move(xParent)), + mpChildrenManager (nullptr) +{ + // empty. +} + +AccessibleShapeInfo::AccessibleShapeInfo (const AccessibleShapeInfo &rOther) + : mxShape (rOther.mxShape), + mxParent (rOther.mxParent), + mpChildrenManager (rOther.mpChildrenManager) +{ + // empty. +} + + +AccessibleShapeInfo::~AccessibleShapeInfo() +{ + // empty. +} + +} // end of namespace accessibility. + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/AccessibleShapeTreeInfo.cxx b/svx/source/accessibility/AccessibleShapeTreeInfo.cxx new file mode 100644 index 0000000000..70fd6e44d8 --- /dev/null +++ b/svx/source/accessibility/AccessibleShapeTreeInfo.cxx @@ -0,0 +1,120 @@ +/* -*- 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 <svx/AccessibleShapeTreeInfo.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using ::com::sun::star::uno::Reference; + +namespace accessibility { + +AccessibleShapeTreeInfo::AccessibleShapeTreeInfo() + : mpView (nullptr), + mpWindow (nullptr), + mpViewForwarder (nullptr) +{ + // Empty. +} + + +AccessibleShapeTreeInfo::AccessibleShapeTreeInfo (const AccessibleShapeTreeInfo& rInfo) + : mxDocumentWindow (rInfo.mxDocumentWindow), + mxModelBroadcaster (rInfo.mxModelBroadcaster), + mpView (rInfo.mpView), + mxController (rInfo.mxController), + mpWindow (rInfo.mpWindow), + mpViewForwarder (rInfo.mpViewForwarder) +{ + // Empty. +} + +void AccessibleShapeTreeInfo::dispose() +{ + mxDocumentWindow.clear(); + mxModelBroadcaster.clear(); + mpView = nullptr; + mxController.clear(); + mpWindow.reset(); + mpViewForwarder = nullptr; +} + +AccessibleShapeTreeInfo& AccessibleShapeTreeInfo::operator= (const AccessibleShapeTreeInfo& rInfo) +{ + if ( this != &rInfo ) + { + mxDocumentWindow = rInfo.mxDocumentWindow; + mxModelBroadcaster = rInfo.mxModelBroadcaster; + mpView = rInfo.mpView; + mxController = rInfo.mxController; + mpWindow = rInfo.mpWindow; + mpViewForwarder = rInfo.mpViewForwarder; + } + return *this; +} + +AccessibleShapeTreeInfo::~AccessibleShapeTreeInfo() +{ + if (mpWindow) + { + SolarMutexGuard g; + mpWindow.reset(); + } +} + +void AccessibleShapeTreeInfo::SetDocumentWindow ( + const Reference<XAccessibleComponent>& rxDocumentWindow) +{ + if (mxDocumentWindow != rxDocumentWindow) + mxDocumentWindow = rxDocumentWindow; +} + +void AccessibleShapeTreeInfo::SetModelBroadcaster ( + const Reference<document::XShapeEventBroadcaster>& rxModelBroadcaster) +{ + mxModelBroadcaster = rxModelBroadcaster; +} + +void AccessibleShapeTreeInfo::SetSdrView (SdrView* pView) +{ + mpView = pView; +} + +void AccessibleShapeTreeInfo::SetController ( + const Reference<frame::XController>& rxController) +{ + mxController = rxController; +} + +void AccessibleShapeTreeInfo::SetWindow(vcl::Window* pDevice) +{ + mpWindow = pDevice; +} + +void AccessibleShapeTreeInfo::SetViewForwarder (const IAccessibleViewForwarder* pViewForwarder) +{ + mpViewForwarder = pViewForwarder; +} + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/AccessibleTextEventQueue.cxx b/svx/source/accessibility/AccessibleTextEventQueue.cxx new file mode 100644 index 0000000000..a39123c45e --- /dev/null +++ b/svx/source/accessibility/AccessibleTextEventQueue.cxx @@ -0,0 +1,92 @@ +/* -*- 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 <memory> +#include "AccessibleTextEventQueue.hxx" + +#include <editeng/unoedhlp.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdpntv.hxx> + +namespace accessibility +{ + + + // EventQueue implementation + + + AccessibleTextEventQueue::AccessibleTextEventQueue() + { + } + + AccessibleTextEventQueue::~AccessibleTextEventQueue() + { + Clear(); + } + + void AccessibleTextEventQueue::Append( const SdrHint& rHint ) + { + // only enqueue the events we actually care about in + // AccessibleTextHelper_Impl::ProcessQueue(), because + // the cost of some events adds up. + auto eKind = rHint.GetKind(); + if (eKind == SdrHintKind::BeginEdit + || eKind == SdrHintKind::EndEdit) + maEventQueue.push_back( new SdrHint( rHint ) ); + } + + void AccessibleTextEventQueue::Append( const TextHint& rHint ) + { + maEventQueue.push_back( new TextHint( rHint ) ); + } + + void AccessibleTextEventQueue::Append( const SvxViewChangedHint& rHint ) + { + maEventQueue.push_back( new SvxViewChangedHint( rHint ) ); + } + + void AccessibleTextEventQueue::Append( const SvxEditSourceHint& rHint ) + { + maEventQueue.push_back( new SvxEditSourceHint( rHint ) ); + } + + ::std::unique_ptr< SfxHint > AccessibleTextEventQueue::PopFront() + { + ::std::unique_ptr< SfxHint > aRes( *(maEventQueue.begin()) ); + maEventQueue.pop_front(); + return aRes; + } + + bool AccessibleTextEventQueue::IsEmpty() const + { + return maEventQueue.empty(); + } + + void AccessibleTextEventQueue::Clear() + { + // clear queue + for( auto p : maEventQueue) + delete p; + maEventQueue.clear(); + } + +} // end of namespace accessibility + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/AccessibleTextEventQueue.hxx b/svx/source/accessibility/AccessibleTextEventQueue.hxx new file mode 100644 index 0000000000..23dbf9faa6 --- /dev/null +++ b/svx/source/accessibility/AccessibleTextEventQueue.hxx @@ -0,0 +1,90 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_ACCESSIBILITY_ACCESSIBLETEXTEVENTQUEUE_HXX +#define INCLUDED_SVX_SOURCE_ACCESSIBILITY_ACCESSIBLETEXTEVENTQUEUE_HXX + +#include <memory> +#include <deque> +#include <algorithm> + +class SfxHint; +class SdrHint; +class TextHint; +class SvxViewChangedHint; +class SvxEditSourceHint; + +namespace accessibility +{ + /** This class handles the notification events for the + AccessibleTextHelper class. + + For various reasons, we cannot process EditEngine events as + they arrive, but have to queue and handle them in a batch. + */ + class AccessibleTextEventQueue + { + public: + typedef ::std::deque< SfxHint* > EventQueue; + + AccessibleTextEventQueue(); + ~AccessibleTextEventQueue(); + + /// Append event to end of queue + void Append( const SdrHint& rHint ); + /// Append event to end of queue + void Append( const TextHint& rHint ); + /// Append event to end of queue + void Append( const SvxViewChangedHint& rHint ); + /// Append event to end of queue + void Append( const SvxEditSourceHint& rHint ); + + /** Pop first queue element + + return first queue element, ownership transfers to caller + */ + ::std::unique_ptr< SfxHint > PopFront(); + + /** Apply functor to every queue member + + @param rFunctor + Functor to apply. Functor receives queue element as + parameter: void func( const SfxHint* ); + */ + template < typename Functor > void ForEach( Functor& rFunctor ) const + { + // #109864# Make sure results are put back into rFunctor + rFunctor = ::std::for_each( maEventQueue.begin(), maEventQueue.end(), rFunctor ); + } + + /// Query whether queue is empty + bool IsEmpty() const; + + /// Clear event queue + void Clear(); + + private: + EventQueue maEventQueue; + }; + +} // end of namespace accessibility + +#endif // INCLUDED_SVX_SOURCE_ACCESSIBILITY_ACCESSIBLETEXTEVENTQUEUE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/AccessibleTextHelper.cxx b/svx/source/accessibility/AccessibleTextHelper.cxx new file mode 100644 index 0000000000..f12281793b --- /dev/null +++ b/svx/source/accessibility/AccessibleTextHelper.cxx @@ -0,0 +1,1795 @@ +/* -*- 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 <sal/config.h> + +#include <cstdlib> +#include <memory> +#include <mutex> +#include <utility> +#include <algorithm> +#include <sal/log.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <comphelper/accessibleeventnotifier.hxx> +#include <vcl/svapp.hxx> +#include <vcl/textdata.hxx> +#include <vcl/unohelp.hxx> + + +// Project-local header + + +#include "AccessibleTextEventQueue.hxx" +#include <svx/AccessibleTextHelper.hxx> + +#include <editeng/unoedhlp.hxx> +#include <editeng/unoedprx.hxx> +#include <editeng/AccessibleParaManager.hxx> +#include <editeng/AccessibleEditableTextPara.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdpntv.hxx> +#include <cell.hxx> +#include "../table/accessiblecell.hxx" +#include <editeng/editdata.hxx> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +namespace accessibility +{ + +// AccessibleTextHelper_Impl declaration + + template < typename first_type, typename second_type > + static ::std::pair< first_type, second_type > makeSortedPair( first_type first, + second_type second ) + { + if( first > second ) + return ::std::make_pair( second, first ); + else + return ::std::make_pair( first, second ); + } + + class AccessibleTextHelper_Impl : public SfxListener + { + public: + typedef ::std::vector< sal_Int16 > VectorOfStates; + + // receive pointer to our frontend class and view window + AccessibleTextHelper_Impl(); + virtual ~AccessibleTextHelper_Impl() override; + + // XAccessibleContext child handling methods + sal_Int64 getAccessibleChildCount() const; + uno::Reference< XAccessible > getAccessibleChild( sal_Int64 i ); + + // XAccessibleEventBroadcaster child related methods + void addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ); + void removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ); + + // XAccessibleComponent child related methods + uno::Reference< XAccessible > getAccessibleAtPoint( const awt::Point& aPoint ); + + SvxEditSourceAdapter& GetEditSource() const; + + void SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource ); + + void SetEventSource( const uno::Reference< XAccessible >& rInterface ) + { + mxFrontEnd = rInterface; + } + + void SetOffset( const Point& ); + Point GetOffset() const + { + std::scoped_lock aGuard( maMutex ); Point aPoint( maOffset ); + return aPoint; + } + + void SetStartIndex( sal_Int32 nOffset ); + sal_Int32 GetStartIndex() const + { + // Strictly correct only with locked solar mutex, // but + // here we rely on the fact that sal_Int32 access is + // atomic + return mnStartIndex; + } + + void SetAdditionalChildStates( sal_Int64 nChildStates ); + + void Dispose(); + + // do NOT hold object mutex when calling this! Danger of deadlock + void FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue = uno::Any(), const uno::Any& rOldValue = uno::Any() ) const; + void FireEvent( const AccessibleEventObject& rEvent ) const; + + void SetFocus( bool bHaveFocus ); + bool HaveFocus() const + { + // No locking of solar mutex here, since we rely on the fact + // that sal_Bool access is atomic + return mbThisHasFocus; + } + void SetChildFocus( sal_Int32 nChild, bool bHaveFocus ); + void SetShapeFocus( bool bHaveFocus ); + void ChangeChildFocus( sal_Int32 nNewChild ); + +#ifdef DBG_UTIL + void CheckInvariants() const; +#endif + + // checks all children for visibility, throws away invisible ones + void UpdateVisibleChildren( bool bBroadcastEvents=true ); + + // check all children for changes in position and size + void UpdateBoundRect(); + + // calls SetSelection on the forwarder and updates maLastSelection + // cache. + void UpdateSelection(); + + private: + + // Process event queue + void ProcessQueue(); + + // syntactic sugar for FireEvent + void GotPropertyEvent( const uno::Any& rNewValue, const sal_Int16 nEventId ) const { FireEvent( nEventId, rNewValue ); } + + // shutdown usage of current edit source on myself and the children. + void ShutdownEditSource(); + + void ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast ); + + virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; + + comphelper::AccessibleEventNotifier::TClientId getNotifierClientId() const { return mnNotifierClientId; } + + // lock solar mutex before + SvxTextForwarder& GetTextForwarder() const; + // lock solar mutex before + SvxViewForwarder& GetViewForwarder() const; + // lock solar mutex before + SvxEditViewForwarder& GetEditViewForwarder() const; + + // are we in edit mode? + bool IsActive() const; + + // our frontend class (the one implementing the actual + // interface). That's not necessarily the one containing the impl + // pointer! + uno::Reference< XAccessible > mxFrontEnd; + + // a wrapper for the text forwarders (guarded by solar mutex) + mutable SvxEditSourceAdapter maEditSource; + + // store last selection (to correctly report selection changes, guarded by solar mutex) + ESelection maLastSelection; + + // cache range of visible children (guarded by solar mutex) + sal_Int32 mnFirstVisibleChild; + sal_Int32 mnLastVisibleChild; + + // offset to add to all our children (unguarded, relying on + // the fact that sal_Int32 access is atomic) + sal_Int32 mnStartIndex; + + // the object handling our children (guarded by solar mutex) + ::accessibility::AccessibleParaManager maParaManager; + + // Queued events from Notify() (guarded by solar mutex) + AccessibleTextEventQueue maEventQueue; + + // spin lock to prevent notify in notify (guarded by solar mutex) + bool mbInNotify; + + // whether the object or its children has the focus set (guarded by solar mutex) + bool mbGroupHasFocus; + + // whether we (this object) has the focus set (guarded by solar mutex) + bool mbThisHasFocus; + + mutable std::mutex maMutex; + + /// our current offset to the containing shape/cell (guarded by maMutex) + Point maOffset; + + /// client Id from AccessibleEventNotifier + comphelper::AccessibleEventNotifier::TClientId mnNotifierClientId; + static constexpr comphelper::AccessibleEventNotifier::TClientId snNotifierClientRevoked + = std::numeric_limits<comphelper::AccessibleEventNotifier::TClientId>::max(); + }; + + AccessibleTextHelper_Impl::AccessibleTextHelper_Impl() : + maLastSelection( EE_PARA_NOT_FOUND,EE_INDEX_NOT_FOUND,EE_PARA_NOT_FOUND,EE_INDEX_NOT_FOUND ), + mnFirstVisibleChild( -1 ), + mnLastVisibleChild( -2 ), + mnStartIndex( 0 ), + mbInNotify( false ), + mbGroupHasFocus( false ), + mbThisHasFocus( false ), + maOffset(0,0), + // well, that's strictly exception safe, though not really + // robust. We rely on the fact that this member is constructed + // last, and that the constructor body is empty, thus no + // chance for exceptions once the Id is fetched. Nevertheless, + // normally should employ RAII here... + mnNotifierClientId(::comphelper::AccessibleEventNotifier::registerClient()) + { + SAL_INFO("svx", "received ID: " << mnNotifierClientId ); + } + + AccessibleTextHelper_Impl::~AccessibleTextHelper_Impl() + { + SolarMutexGuard aGuard; + + try + { + // call Dispose here, too, since we've some resources not + // automatically freed otherwise + Dispose(); + } + catch( const uno::Exception& ) {} + } + + SvxTextForwarder& AccessibleTextHelper_Impl::GetTextForwarder() const + { + if( !maEditSource.IsValid() ) + throw uno::RuntimeException("Unknown edit source", mxFrontEnd); + + SvxTextForwarder* pTextForwarder = maEditSource.GetTextForwarder(); + + if( !pTextForwarder ) + throw uno::RuntimeException("Unable to fetch text forwarder, model might be dead", mxFrontEnd); + + if( !pTextForwarder->IsValid() ) + throw uno::RuntimeException("Text forwarder is invalid, model might be dead", mxFrontEnd); + + return *pTextForwarder; + } + + SvxViewForwarder& AccessibleTextHelper_Impl::GetViewForwarder() const + { + if( !maEditSource.IsValid() ) + throw uno::RuntimeException("Unknown edit source", mxFrontEnd); + + SvxViewForwarder* pViewForwarder = maEditSource.GetViewForwarder(); + + if( !pViewForwarder ) + throw uno::RuntimeException("Unable to fetch view forwarder, model might be dead", mxFrontEnd); + + if( !pViewForwarder->IsValid() ) + throw uno::RuntimeException("View forwarder is invalid, model might be dead", mxFrontEnd); + + return *pViewForwarder; + } + + SvxEditViewForwarder& AccessibleTextHelper_Impl::GetEditViewForwarder() const + { + if( !maEditSource.IsValid() ) + throw uno::RuntimeException("Unknown edit source", mxFrontEnd); + + SvxEditViewForwarder* pViewForwarder = maEditSource.GetEditViewForwarder(); + + if( !pViewForwarder ) + { + throw uno::RuntimeException("No edit view forwarder, object not in edit mode", mxFrontEnd); + } + + if( !pViewForwarder->IsValid() ) + { + throw uno::RuntimeException("View forwarder is invalid, object not in edit mode", mxFrontEnd); + } + + return *pViewForwarder; + } + + SvxEditSourceAdapter& AccessibleTextHelper_Impl::GetEditSource() const + { + if( !maEditSource.IsValid() ) + throw uno::RuntimeException("AccessibleTextHelper_Impl::GetEditSource: no edit source", mxFrontEnd ); + return maEditSource; + } + + namespace { + + // functor for sending child events (no stand-alone function, they are maybe not inlined) + class AccessibleTextHelper_OffsetChildIndex + { + public: + explicit AccessibleTextHelper_OffsetChildIndex( sal_Int32 nDifference ) : mnDifference(nDifference) {} + void operator()( ::accessibility::AccessibleEditableTextPara& rPara ) + { + rPara.SetIndexInParent( rPara.GetIndexInParent() + mnDifference ); + } + + private: + const sal_Int32 mnDifference; + }; + + } + + void AccessibleTextHelper_Impl::SetStartIndex( sal_Int32 nOffset ) + { + sal_Int32 nOldOffset( mnStartIndex ); + + mnStartIndex = nOffset; + + if( nOldOffset != nOffset ) + { + // update children + AccessibleTextHelper_OffsetChildIndex aFunctor( nOffset - nOldOffset ); + + ::std::for_each( maParaManager.begin(), maParaManager.end(), + AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_OffsetChildIndex > (aFunctor) ); + } + } + + void AccessibleTextHelper_Impl::SetAdditionalChildStates( sal_Int64 nChildStates ) + { + maParaManager.SetAdditionalChildStates( nChildStates ); + } + + void AccessibleTextHelper_Impl::SetChildFocus( sal_Int32 nChild, bool bHaveFocus ) + { + if( bHaveFocus ) + { + if( mbThisHasFocus ) + SetShapeFocus( false ); + + maParaManager.SetFocus( nChild ); + + // we just received the focus, also send caret event then + UpdateSelection(); + + SAL_INFO("svx", "Paragraph " << nChild << " received focus"); + } + else + { + maParaManager.SetFocus( -1 ); + + SAL_INFO("svx", "Paragraph " << nChild << " lost focus"); + + if( mbGroupHasFocus ) + SetShapeFocus( true ); + } + } + + void AccessibleTextHelper_Impl::ChangeChildFocus( sal_Int32 nNewChild ) + { + if( mbThisHasFocus ) + SetShapeFocus( false ); + + mbGroupHasFocus = true; + maParaManager.SetFocus( nNewChild ); + + SAL_INFO("svx", "Paragraph " << nNewChild << " received focus"); + } + + void AccessibleTextHelper_Impl::SetShapeFocus( bool bHaveFocus ) + { + bool bOldFocus( mbThisHasFocus ); + + mbThisHasFocus = bHaveFocus; + + if( bOldFocus == bHaveFocus ) + return; + + if( bHaveFocus ) + { + if( mxFrontEnd.is() ) + { + AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() ); + if ( !pAccessibleCell ) + GotPropertyEvent( uno::Any(AccessibleStateType::FOCUSED), AccessibleEventId::STATE_CHANGED ); + else // the focus event on cell should be fired on table directly + { + AccessibleTableShape* pAccTable = pAccessibleCell->GetParentTable(); + if (pAccTable) + pAccTable->SetStateDirectly(AccessibleStateType::FOCUSED); + } + } + SAL_INFO("svx", "Parent object received focus" ); + } + else + { + // The focus state should be reset directly on table. + //LostPropertyEvent( uno::makeAny(AccessibleStateType::FOCUSED), AccessibleEventId::STATE_CHANGED ); + if( mxFrontEnd.is() ) + { + AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() ); + if ( !pAccessibleCell ) + FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any(), uno::Any(AccessibleStateType::FOCUSED) ); + else + { + AccessibleTableShape* pAccTable = pAccessibleCell->GetParentTable(); + if (pAccTable) + pAccTable->ResetStateDirectly(AccessibleStateType::FOCUSED); + } + } + SAL_INFO("svx", "Parent object lost focus" ); + } + } + + void AccessibleTextHelper_Impl::SetFocus( bool bHaveFocus ) + { + bool bOldFocus( mbGroupHasFocus ); + + mbGroupHasFocus = bHaveFocus; + + if( IsActive() ) + { + try + { + // find the one with the cursor and get/set focus accordingly + ESelection aSelection; + if( GetEditViewForwarder().GetSelection( aSelection ) ) + SetChildFocus( aSelection.nEndPara, bHaveFocus ); + } + catch( const uno::Exception& ) {} + } + else if( bOldFocus != bHaveFocus ) + { + SetShapeFocus( bHaveFocus ); + } + + SAL_INFO("svx", "focus changed, Object " << this << ", state: " << (bHaveFocus ? "focused" : "not focused") ); + } + + bool AccessibleTextHelper_Impl::IsActive() const + { + try + { + SvxEditSource& rEditSource = GetEditSource(); + SvxEditViewForwarder* pViewForwarder = rEditSource.GetEditViewForwarder(); + + if( !pViewForwarder ) + return false; + + if( mxFrontEnd.is() ) + { + AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() ); + if ( pAccessibleCell ) + { + sdr::table::CellRef xCell = pAccessibleCell->getCellRef(); + if ( xCell.is() ) + return xCell->IsActiveCell(); + } + } + return pViewForwarder->IsValid(); + } + catch( const uno::RuntimeException& ) + { + return false; + } + } + + void AccessibleTextHelper_Impl::UpdateSelection() + { + try + { + ESelection aSelection; + if( GetEditViewForwarder().GetSelection( aSelection ) ) + { + if( maLastSelection != aSelection && + aSelection.nEndPara < maParaManager.GetNum() ) + { + // #103998# Not that important, changed from assertion to trace + if( mbThisHasFocus ) + { + SAL_INFO("svx", "Parent has focus!"); + } + + sal_Int32 nMaxValidParaIndex( GetTextForwarder().GetParagraphCount() - 1 ); + + // notify all affected paragraphs (TODO: may be suboptimal, + // since some paragraphs might stay selected) + if( maLastSelection.nStartPara != EE_PARA_NOT_FOUND ) + { + // Did the caret move from one paragraph to another? + // #100530# no caret events if not focused. + if( mbGroupHasFocus && + maLastSelection.nEndPara != aSelection.nEndPara ) + { + if( maLastSelection.nEndPara < maParaManager.GetNum() ) + { + maParaManager.FireEvent( ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex ), + ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex )+1, + AccessibleEventId::CARET_CHANGED, + uno::Any(static_cast<sal_Int32>(-1)), + uno::Any(maLastSelection.nEndPos) ); + } + + ChangeChildFocus( aSelection.nEndPara ); + + SAL_INFO( + "svx", + "focus changed, Object: " << this + << ", Paragraph: " << aSelection.nEndPara + << ", Last paragraph: " + << maLastSelection.nEndPara); + } + } + + // #100530# no caret events if not focused. + if( mbGroupHasFocus ) + { + uno::Any aOldCursor; + + // #i13705# The old cursor can only contain valid + // values if it's the same paragraph! + if( maLastSelection.nStartPara != EE_PARA_NOT_FOUND && + maLastSelection.nEndPara == aSelection.nEndPara ) + { + aOldCursor <<= maLastSelection.nEndPos; + } + else + { + aOldCursor <<= static_cast<sal_Int32>(-1); + } + + maParaManager.FireEvent( aSelection.nEndPara, + aSelection.nEndPara+1, + AccessibleEventId::CARET_CHANGED, + uno::Any(aSelection.nEndPos), + aOldCursor ); + } + + SAL_INFO( + "svx", + "caret changed, Object: " << this << ", New pos: " + << aSelection.nEndPos << ", Old pos: " + << maLastSelection.nEndPos << ", New para: " + << aSelection.nEndPara << ", Old para: " + << maLastSelection.nEndPara); + + // #108947# Sort new range before calling FireEvent + ::std::pair<sal_Int32, sal_Int32> sortedSelection( + makeSortedPair(::std::min( aSelection.nStartPara, nMaxValidParaIndex ), + ::std::min( aSelection.nEndPara, nMaxValidParaIndex ) ) ); + + // #108947# Sort last range before calling FireEvent + ::std::pair<sal_Int32, sal_Int32> sortedLastSelection( + makeSortedPair(::std::min( maLastSelection.nStartPara, nMaxValidParaIndex ), + ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex ) ) ); + + // event TEXT_SELECTION_CHANGED has to be submitted. (#i27299#) + const sal_Int16 nTextSelChgEventId = + AccessibleEventId::TEXT_SELECTION_CHANGED; + // #107037# notify selection change + if( maLastSelection.nStartPara == EE_PARA_NOT_FOUND ) + { + // last selection is undefined + // use method <ESelection::HasRange()> (#i27299#) + if ( aSelection.HasRange() ) + { + // selection was undefined, now is on + maParaManager.FireEvent( sortedSelection.first, + sortedSelection.second+1, + nTextSelChgEventId ); + } + } + else + { + // last selection is valid + // use method <ESelection::HasRange()> (#i27299#) + if ( maLastSelection.HasRange() && + !aSelection.HasRange() ) + { + // selection was on, now is empty + maParaManager.FireEvent( sortedLastSelection.first, + sortedLastSelection.second+1, + nTextSelChgEventId ); + } + // use method <ESelection::HasRange()> (#i27299#) + else if( !maLastSelection.HasRange() && + aSelection.HasRange() ) + { + // selection was empty, now is on + maParaManager.FireEvent( sortedSelection.first, + sortedSelection.second+1, + nTextSelChgEventId ); + } + // no event TEXT_SELECTION_CHANGED event, if new and + // last selection are empty. (#i27299#) + else if ( maLastSelection.HasRange() && + aSelection.HasRange() ) + { + // use sorted last and new selection + ESelection aTmpLastSel( maLastSelection ); + aTmpLastSel.Adjust(); + ESelection aTmpSel( aSelection ); + aTmpSel.Adjust(); + // first submit event for new and changed selection + sal_Int32 nPara = aTmpSel.nStartPara; + for ( ; nPara <= aTmpSel.nEndPara; ++nPara ) + { + if ( nPara < aTmpLastSel.nStartPara || + nPara > aTmpLastSel.nEndPara ) + { + // new selection on paragraph <nPara> + maParaManager.FireEvent( nPara, + nTextSelChgEventId ); + } + else + { + // check for changed selection on paragraph <nPara> + const sal_Int32 nParaStartPos = + nPara == aTmpSel.nStartPara + ? aTmpSel.nStartPos : 0; + const sal_Int32 nParaEndPos = + nPara == aTmpSel.nEndPara + ? aTmpSel.nEndPos : -1; + const sal_Int32 nLastParaStartPos = + nPara == aTmpLastSel.nStartPara + ? aTmpLastSel.nStartPos : 0; + const sal_Int32 nLastParaEndPos = + nPara == aTmpLastSel.nEndPara + ? aTmpLastSel.nEndPos : -1; + if ( nParaStartPos != nLastParaStartPos || + nParaEndPos != nLastParaEndPos ) + { + maParaManager.FireEvent( + nPara, nTextSelChgEventId ); + } + } + } + // second submit event for 'old' selections + nPara = aTmpLastSel.nStartPara; + for ( ; nPara <= aTmpLastSel.nEndPara; ++nPara ) + { + if ( nPara < aTmpSel.nStartPara || + nPara > aTmpSel.nEndPara ) + { + maParaManager.FireEvent( nPara, + nTextSelChgEventId ); + } + } + } + } + + maLastSelection = aSelection; + } + } + } + // no selection? no update actions + catch( const uno::RuntimeException& ) {} + } + + void AccessibleTextHelper_Impl::ShutdownEditSource() + { + // This should only be called with solar mutex locked, i.e. from the main office thread + + // This here is somewhat clumsy: As soon as our children have + // a NULL EditSource (maParaManager.SetEditSource()), they + // enter the disposed state and cannot be reanimated. Thus, it + // is unavoidable and a hard requirement to let go and create + // from scratch each and every child. + + // invalidate children + maParaManager.Dispose(); + maParaManager.SetNum(0); + + // lost all children + if( mxFrontEnd.is() ) + FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN); + + // quit listen on stale edit source + if( maEditSource.IsValid() ) + EndListening( maEditSource.GetBroadcaster() ); + + maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() ); + } + + void AccessibleTextHelper_Impl::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource ) + { + // This should only be called with solar mutex locked, i.e. from the main office thread + + // shutdown old edit source + ShutdownEditSource(); + + // set new edit source + maEditSource.SetEditSource( std::move(pEditSource) ); + + // init child vector to the current child count + if( maEditSource.IsValid() ) + { + maParaManager.SetNum( GetTextForwarder().GetParagraphCount() ); + + // listen on new edit source + StartListening( maEditSource.GetBroadcaster() ); + + UpdateVisibleChildren(); + } + } + + void AccessibleTextHelper_Impl::SetOffset( const Point& rPoint ) + { + // guard against non-atomic access to maOffset data structure + { + std::scoped_lock aGuard( maMutex ); + maOffset = rPoint; + } + + maParaManager.SetEEOffset( rPoint ); + + // in all cases, check visibility afterwards. + UpdateVisibleChildren(); + UpdateBoundRect(); + } + + void AccessibleTextHelper_Impl::UpdateVisibleChildren( bool bBroadcastEvents ) + { + try + { + SvxTextForwarder& rCacheTF = GetTextForwarder(); + sal_Int32 nParas=rCacheTF.GetParagraphCount(); + + // GetTextForwarder might have replaced everything, update + // paragraph count in case it's outdated + maParaManager.SetNum( nParas ); + + mnFirstVisibleChild = -1; + mnLastVisibleChild = -2; + + for( sal_Int32 nCurrPara=0; nCurrPara<nParas; ++nCurrPara ) + { + if (nCurrPara == 0) + mnFirstVisibleChild = nCurrPara; + mnLastVisibleChild = nCurrPara; + if (mxFrontEnd.is() && bBroadcastEvents) + { + // child not yet created? + if (!maParaManager.HasCreatedChild(nCurrPara)) + { + GotPropertyEvent( uno::Any( maParaManager.CreateChild( nCurrPara - mnFirstVisibleChild, + mxFrontEnd, GetEditSource(), nCurrPara ).first ), + AccessibleEventId::CHILD ); + } + } + } + } + catch( const uno::Exception& ) + { + OSL_FAIL("AccessibleTextHelper_Impl::UpdateVisibleChildren error while determining visible children"); + + // something failed - currently no children + mnFirstVisibleChild = -1; + mnLastVisibleChild = -2; + maParaManager.SetNum(0); + + // lost all children + if( bBroadcastEvents ) + FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN); + } + } + + void AccessibleTextHelper_Impl::UpdateBoundRect() + { + // send BOUNDRECT_CHANGED to affected children + for(auto it = maParaManager.begin(); it != maParaManager.end(); ++it) + { + ::accessibility::AccessibleParaManager::WeakChild& rChild = *it; + // retrieve hard reference from weak one + auto aHardRef( rChild.first.get() ); + + if( aHardRef.is() ) + { + awt::Rectangle aNewRect = aHardRef->getBounds(); + const awt::Rectangle& aOldRect = rChild.second; + + if( aNewRect.X != aOldRect.X || + aNewRect.Y != aOldRect.Y || + aNewRect.Width != aOldRect.Width || + aNewRect.Height != aOldRect.Height ) + { + // visible data changed + aHardRef->FireEvent( AccessibleEventId::BOUNDRECT_CHANGED ); + + // update internal bounds + rChild = ::accessibility::AccessibleParaManager::WeakChild( rChild.first, aNewRect ); + } + } + } + } + +#ifdef DBG_UTIL + void AccessibleTextHelper_Impl::CheckInvariants() const + { + if( mnFirstVisibleChild >= 0 && + mnFirstVisibleChild > mnLastVisibleChild ) + { + OSL_FAIL( "AccessibleTextHelper: range invalid" ); + } + } +#endif + + namespace { + + // functor for sending child events (no stand-alone function, they are maybe not inlined) + class AccessibleTextHelper_LostChildEvent + { + public: + explicit AccessibleTextHelper_LostChildEvent( AccessibleTextHelper_Impl& rImpl ) : mrImpl(rImpl) {} + void operator()( const ::accessibility::AccessibleParaManager::WeakChild& rPara ) + { + // retrieve hard reference from weak one + auto aHardRef( rPara.first.get() ); + + if( aHardRef.is() ) + mrImpl.FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::Any(css::uno::Reference<css::accessibility::XAccessible>(aHardRef)) ); + } + + private: + AccessibleTextHelper_Impl& mrImpl; + }; + + } + + void AccessibleTextHelper_Impl::ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast ) + { + const sal_Int32 nParas = GetTextForwarder().GetParagraphCount(); + + /* rotate paragraphs + * ================= + * + * Three cases: + * + * 1. + * ... nParagraph ... nParam1 ... nParam2 ... + * |______________[xxxxxxxxxxx] + * becomes + * [xxxxxxxxxxx]|______________ + * + * tail is 0 + * + * 2. + * ... nParam1 ... nParagraph ... nParam2 ... + * [xxxxxxxxxxx|xxxxxxxxxxxxxx]____________ + * becomes + * ____________[xxxxxxxxxxx|xxxxxxxxxxxxxx] + * + * tail is nParagraph - nParam1 + * + * 3. + * ... nParam1 ... nParam2 ... nParagraph ... + * [xxxxxxxxxxx]___________|____________ + * becomes + * ___________|____________[xxxxxxxxxxx] + * + * tail is nParam2 - nParam1 + */ + + // sort nParagraph, nParam1 and nParam2 in ascending order, calc range + if( nMiddle < nFirst ) + { + ::std::swap(nFirst, nMiddle); + } + else if( nMiddle < nLast ) + { + nLast = nLast + nMiddle - nFirst; + } + else + { + ::std::swap(nMiddle, nLast); + nLast = nLast + nMiddle - nFirst; + } + + if( !(nFirst < nParas && nMiddle < nParas && nLast < nParas) ) + return; + + // since we have no "paragraph index + // changed" event on UAA, remove + // [first,last] and insert again later (in + // UpdateVisibleChildren) + + // maParaManager.Rotate( nFirst, nMiddle, nLast ); + + // send CHILD_EVENT to affected children + ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin(); + ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin; + + ::std::advance( begin, nFirst ); + ::std::advance( end, nLast+1 ); + + // TODO: maybe optimize here in the following way. If the + // number of removed children exceeds a certain threshold, + // use InvalidateFlags::Children + AccessibleTextHelper_LostChildEvent aFunctor( *this ); + + ::std::for_each( begin, end, aFunctor ); + + maParaManager.Release(nFirst, nLast+1); + // should be no need for UpdateBoundRect, since all affected children are cleared. + } + + namespace { + + // functor for sending child events (no stand-alone function, they are maybe not inlined) + class AccessibleTextHelper_ChildrenTextChanged + { + public: + void operator()( ::accessibility::AccessibleEditableTextPara& rPara ) + { + rPara.TextChanged(); + } + }; + + /** functor processing queue events + + Reacts on SfxHintId::TextParaInserted/REMOVED events and stores + their content + */ + class AccessibleTextHelper_QueueFunctor + { + public: + AccessibleTextHelper_QueueFunctor() : + mnParasChanged( 0 ), + mnParaIndex(-1), + mnHintId(SfxHintId::NONE) + {} + void operator()( const SfxHint* pEvent ) + { + if( !pEvent || mnParasChanged == -1 ) + return; + + // determine hint type + const TextHint* pTextHint = dynamic_cast<const TextHint*>( pEvent ); + const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( pEvent ); + + if( !(!pEditSourceHint && pTextHint && + (pTextHint->GetId() == SfxHintId::TextParaInserted || + pTextHint->GetId() == SfxHintId::TextParaRemoved )) ) + return; + + if( pTextHint->GetValue() == EE_PARA_ALL ) + { + mnParasChanged = -1; + } + else + { + mnHintId = pTextHint->GetId(); + mnParaIndex = pTextHint->GetValue(); + ++mnParasChanged; + } + } + + /** Query number of paragraphs changed during queue processing. + + @return number of changed paragraphs, -1 for + "every paragraph changed" + */ + sal_Int32 GetNumberOfParasChanged() const { return mnParasChanged; } + /** Query index of last added/removed paragraph + + @return index of lastly added paragraphs, -1 for none + added so far. + */ + sal_Int32 GetParaIndex() const { return mnParaIndex; } + /** Query hint id of last interesting event + + @return hint id of last interesting event (REMOVED/INSERTED). + */ + SfxHintId GetHintId() const { return mnHintId; } + + private: + /** number of paragraphs changed during queue processing. -1 for + "every paragraph changed" + */ + sal_Int32 mnParasChanged; + /// index of paragraph added/removed last + sal_Int32 mnParaIndex; + /// TextHint ID (removed/inserted) of last interesting event + SfxHintId mnHintId; + }; + + } + + void AccessibleTextHelper_Impl::ProcessQueue() + { + // inspect queue for paragraph insert/remove events. If there + // is exactly _one_ of those in the queue, and the number of + // paragraphs has changed by exactly one, use that event to + // determine a priori which paragraph was added/removed. This + // is necessary, since I must sync right here with the + // EditEngine state (number of paragraphs etc.), since I'm + // potentially sending listener events right away. + AccessibleTextHelper_QueueFunctor aFunctor; + maEventQueue.ForEach( aFunctor ); + + const sal_Int32 nNewParas( GetTextForwarder().GetParagraphCount() ); + const sal_Int32 nCurrParas( maParaManager.GetNum() ); + + // whether every paragraph already is updated (no need to + // repeat that later on, e.g. for PARA_MOVED events) + bool bEverythingUpdated( false ); + + if( std::abs( nNewParas - nCurrParas ) == 1 && + aFunctor.GetNumberOfParasChanged() == 1 ) + { + // #103483# Exactly one paragraph added/removed. This is + // the normal case, optimize event handling here. + + if( aFunctor.GetHintId() == SfxHintId::TextParaInserted ) + { + // update num of paras + maParaManager.SetNum( nNewParas ); + + // release everything from the insertion position until the end + maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas); + + // TODO: Clarify whether this behaviour _really_ saves + // anybody anything! + // update children, _don't_ broadcast + UpdateVisibleChildren( false ); + UpdateBoundRect(); + + // send insert event + // #109864# Enforce creation of this paragraph + try + { + GotPropertyEvent( uno::Any( getAccessibleChild( aFunctor.GetParaIndex() - + mnFirstVisibleChild + GetStartIndex() ) ), + AccessibleEventId::CHILD ); + } + catch( const uno::Exception& ) + { + OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue: could not create new paragraph"); + } + } + else if( aFunctor.GetHintId() == SfxHintId::TextParaRemoved ) + { + ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin(); + ::std::advance( begin, aFunctor.GetParaIndex() ); + ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin; + ::std::advance( end, 1 ); + + // #i61812# remember para to be removed for later notification + // AFTER the new state is applied (that after the para got removed) + ::uno::Reference< XAccessible > xPara(begin->first.get()); + + // release everything from the remove position until the end + maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas); + + // update num of paras + maParaManager.SetNum( nNewParas ); + + // TODO: Clarify whether this behaviour _really_ saves + // anybody anything! + // update children, _don't_ broadcast + UpdateVisibleChildren( false ); + UpdateBoundRect(); + + // #i61812# notification for removed para + if (xPara.is()) + FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::Any( xPara) ); + } +#ifdef DBG_UTIL + else + OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue() invalid hint id"); +#endif + } + else if( nNewParas != nCurrParas ) + { + // release all paras + maParaManager.Release(0, nCurrParas); + + // update num of paras + maParaManager.SetNum( nNewParas ); + + // #109864# create from scratch, don't broadcast + UpdateVisibleChildren( false ); + UpdateBoundRect(); + + // number of paragraphs somehow changed - but we have no + // chance determining how. Thus, throw away everything and + // create from scratch. + // (child events should be broadcast after the changes are done...) + FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN); + + // no need for further updates later on + bEverythingUpdated = true; + } + + bool bUpdatedBoundRectAndVisibleChildren(false); + + while( !maEventQueue.IsEmpty() ) + { + ::std::unique_ptr< SfxHint > pHint( maEventQueue.PopFront() ); + if (pHint) + { + const SfxHint& rHint = *pHint; + + // Note, if you add events here, you need to update the AccessibleTextEventQueue::Append + // code, because only the events we process here, are actually queued there. + + try + { + + if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint) + { + const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint ); + + switch( pSdrHint->GetKind() ) + { + case SdrHintKind::BeginEdit: + { + if(!IsActive()) + { + break; + } + // change children state + maParaManager.SetActive(); + + // per definition, edit mode text has the focus + SetFocus( true ); + break; + } + + case SdrHintKind::EndEdit: + { + // focused child now loses focus + ESelection aSelection; + if( GetEditViewForwarder().GetSelection( aSelection ) ) + SetChildFocus( aSelection.nEndPara, false ); + + // change children state + maParaManager.SetActive( false ); + + maLastSelection = ESelection( EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND, + EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND); + break; + } + default: + break; + } + } + else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) ) + { + switch( pEditSourceHint->GetId() ) + { + case SfxHintId::EditSourceParasMoved: + { + DBG_ASSERT( pEditSourceHint->GetStartValue() < GetTextForwarder().GetParagraphCount() && + pEditSourceHint->GetEndValue() < GetTextForwarder().GetParagraphCount(), + "AccessibleTextHelper_Impl::NotifyHdl: Invalid notification"); + + if( !bEverythingUpdated ) + { + ParagraphsMoved(pEditSourceHint->GetStartValue(), + pEditSourceHint->GetValue(), + pEditSourceHint->GetEndValue()); + + // in all cases, check visibility afterwards. + UpdateVisibleChildren(); + } + break; + } + + case SfxHintId::EditSourceSelectionChanged: + // notify listeners + try + { + UpdateSelection(); + } + // maybe we're not in edit mode (this is not an error) + catch( const uno::Exception& ) {} + break; + default: break; + } + } + else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) ) + { + const sal_Int32 nParas = GetTextForwarder().GetParagraphCount(); + + switch( pTextHint->GetId() ) + { + case SfxHintId::TextModified: + { + // notify listeners + sal_Int32 nPara( pTextHint->GetValue() ); + + // #108900# Delegate change event to children + AccessibleTextHelper_ChildrenTextChanged aNotifyChildrenFunctor; + + if( nPara == EE_PARA_ALL ) + { + // #108900# Call every child + ::std::for_each( maParaManager.begin(), maParaManager.end(), + AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) ); + } + else + if( nPara < nParas ) + { + // #108900# Call child at index nPara + ::std::for_each( maParaManager.begin()+nPara, maParaManager.begin()+nPara+1, + AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) ); + } + break; + } + + case SfxHintId::TextParaInserted: + // already happened above + break; + + case SfxHintId::TextParaRemoved: + // already happened above + break; + + case SfxHintId::TextHeightChanged: + // visibility changed, done below + break; + + case SfxHintId::TextViewScrolled: + // visibility changed, done below + break; + default: break; + } + + // in all cases, check visibility afterwards. + if (!bUpdatedBoundRectAndVisibleChildren) + { + UpdateVisibleChildren(); + UpdateBoundRect(); + bUpdatedBoundRectAndVisibleChildren = true; + } + } + else if (rHint.GetId() == SfxHintId::SvxViewChanged) + { + // just check visibility + if (!bUpdatedBoundRectAndVisibleChildren) + { + UpdateVisibleChildren(); + UpdateBoundRect(); + bUpdatedBoundRectAndVisibleChildren = true; + } + } + // it's VITAL to keep the SfxSimpleHint last! It's the base of some classes above! + else if( rHint.GetId() == SfxHintId::Dying) + { + // edit source is dying under us, become defunc then + try + { + // make edit source inaccessible + // Note: cannot destroy it here, since we're called from there! + ShutdownEditSource(); + } + catch( const uno::Exception& ) {} + } + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + } + + void AccessibleTextHelper_Impl::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) + { + // precondition: solar mutex locked + DBG_TESTSOLARMUTEX(); + + // precondition: not in a recursion + if( mbInNotify ) + return; + + mbInNotify = true; + + try + { + // Process notification event, arranged in order of likelihood of + // occurrence to avoid unnecessary dynamic_cast. Note that + // SvxEditSourceHint is derived from TextHint, so has to be checked + // before that. + if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint) + { + const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint ); + // process drawing layer events right away, if not + // within an open EE notification frame. Otherwise, + // event processing would be delayed until next EE + // notification sequence. + maEventQueue.Append( *pSdrHint ); + } + else if (rHint.GetId() == SfxHintId::SvxViewChanged) + { + const SvxViewChangedHint* pViewHint = static_cast<const SvxViewChangedHint*>(&rHint); + // process visibility right away, if not within an + // open EE notification frame. Otherwise, event + // processing would be delayed until next EE + // notification sequence. + maEventQueue.Append( *pViewHint ); + } + else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) ) + { + // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#) + maEventQueue.Append( *pEditSourceHint ); + } + else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) ) + { + // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#) + if(pTextHint->GetId() == SfxHintId::TextProcessNotifications) + ProcessQueue(); + else + maEventQueue.Append( *pTextHint ); + } + // it's VITAL to keep the SfxHint last! It's the base of the classes above! + else if( rHint.GetId() == SfxHintId::Dying ) + { + // handle this event _at once_, because after that, objects are invalid + // edit source is dying under us, become defunc then + maEventQueue.Clear(); + try + { + // make edit source inaccessible + // Note: cannot destroy it here, since we're called from there! + ShutdownEditSource(); + } + catch( const uno::Exception& ) {} + } + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + mbInNotify = false; + } + + mbInNotify = false; + } + + void AccessibleTextHelper_Impl::Dispose() + { + if( getNotifierClientId() != snNotifierClientRevoked) + { + try + { + // #106234# Unregister from EventNotifier + ::comphelper::AccessibleEventNotifier::revokeClient( getNotifierClientId() ); + SAL_INFO("svx", "disposed ID: " << mnNotifierClientId ); + } + catch( const uno::Exception& ) {} + + mnNotifierClientId = snNotifierClientRevoked; + } + + try + { + // dispose children + maParaManager.Dispose(); + } + catch( const uno::Exception& ) {} + + // quit listen on stale edit source + if( maEditSource.IsValid() ) + EndListening( maEditSource.GetBroadcaster() ); + + // clear references + maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() ); + mxFrontEnd = nullptr; + } + + void AccessibleTextHelper_Impl::FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue, const uno::Any& rOldValue ) const + { + // -- object locked -- + AccessibleEventObject aEvent; + { + std::scoped_lock aGuard(maMutex); + + DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper::FireEvent: no event source set"); + + if (mxFrontEnd.is()) + aEvent = AccessibleEventObject(mxFrontEnd->getAccessibleContext(), nEventId, + rNewValue, rOldValue, -1); + else + aEvent = AccessibleEventObject(uno::Reference<uno::XInterface>(), nEventId, + rNewValue, rOldValue, -1); + + // no locking necessary, FireEvent internally copies listeners + // if someone removes/adds in between Further locking, + // actually, might lead to deadlocks, since we're calling out + // of this object + } + // -- until here -- + + FireEvent(aEvent); + } + + void AccessibleTextHelper_Impl::FireEvent( const AccessibleEventObject& rEvent ) const + { + // #106234# Delegate to EventNotifier + if (getNotifierClientId() != snNotifierClientRevoked) + ::comphelper::AccessibleEventNotifier::addEvent( getNotifierClientId(), rEvent ); + } + + // XAccessibleContext + sal_Int64 AccessibleTextHelper_Impl::getAccessibleChildCount() const + { + return mnLastVisibleChild - mnFirstVisibleChild + 1; + } + + uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleChild( sal_Int64 i ) + { + i -= GetStartIndex(); + + if( 0 > i || i >= getAccessibleChildCount() || + GetTextForwarder().GetParagraphCount() <= i ) + { + throw lang::IndexOutOfBoundsException("Invalid child index", mxFrontEnd); + } + + DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper_Impl::UpdateVisibleChildren: no frontend set"); + + if( mxFrontEnd.is() ) + return maParaManager.CreateChild( i, mxFrontEnd, GetEditSource(), mnFirstVisibleChild + i ).first; + else + return nullptr; + } + + void AccessibleTextHelper_Impl::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) + { + if( getNotifierClientId() != snNotifierClientRevoked ) + ::comphelper::AccessibleEventNotifier::addEventListener( getNotifierClientId(), xListener ); + } + + void AccessibleTextHelper_Impl::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) + { + if( getNotifierClientId() == snNotifierClientRevoked ) + return; + + const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), xListener ); + if ( !nListenerCount ) + { + // no listeners anymore + // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), + // and at least to us not firing any events anymore, in case somebody calls + // NotifyAccessibleEvent, again + ::comphelper::AccessibleEventNotifier::TClientId nId( getNotifierClientId() ); + mnNotifierClientId = snNotifierClientRevoked; + ::comphelper::AccessibleEventNotifier::revokeClient( nId ); + } + } + + uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleAtPoint( const awt::Point& _aPoint ) + { + // make given position relative + if( !mxFrontEnd.is() ) + throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd ); + + uno::Reference< XAccessibleContext > xFrontEndContext = mxFrontEnd->getAccessibleContext(); + + if( !xFrontEndContext.is() ) + throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd ); + + uno::Reference< XAccessibleComponent > xFrontEndComponent( xFrontEndContext, uno::UNO_QUERY_THROW ); + + // #103862# No longer need to make given position relative + Point aPoint( _aPoint.X, _aPoint.Y ); + + // respect EditEngine offset to surrounding shape/cell + aPoint -= GetOffset(); + + // convert to EditEngine coordinate system + SvxTextForwarder& rCacheTF = GetTextForwarder(); + Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) ); + + // iterate over all visible children (including those not yet created) + sal_Int64 nChild; + for( nChild=mnFirstVisibleChild; nChild <= mnLastVisibleChild; ++nChild ) + { + DBG_ASSERT(nChild >= 0, + "AccessibleTextHelper_Impl::getAccessibleAt: index value overflow"); + + tools::Rectangle aParaBounds( rCacheTF.GetParaBounds( nChild ) ); + + if( aParaBounds.Contains( aLogPoint ) ) + return getAccessibleChild( nChild - mnFirstVisibleChild + GetStartIndex() ); + } + + // found none + return nullptr; + } + + + // AccessibleTextHelper implementation (simply forwards to impl) + + AccessibleTextHelper::AccessibleTextHelper( ::std::unique_ptr< SvxEditSource > && pEditSource ) : + mpImpl( new AccessibleTextHelper_Impl() ) + { + SolarMutexGuard aGuard; + + SetEditSource( std::move(pEditSource) ); + } + + AccessibleTextHelper::~AccessibleTextHelper() + { + } + + const SvxEditSource& AccessibleTextHelper::GetEditSource() const + { +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); + + const SvxEditSource& aEditSource = mpImpl->GetEditSource(); + + mpImpl->CheckInvariants(); + + return aEditSource; +#else + return mpImpl->GetEditSource(); +#endif + } + + void AccessibleTextHelper::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource ) + { +#ifdef DBG_UTIL + // precondition: solar mutex locked + DBG_TESTSOLARMUTEX(); + + mpImpl->CheckInvariants(); +#endif + + mpImpl->SetEditSource( std::move(pEditSource) ); + +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); +#endif + } + + void AccessibleTextHelper::SetEventSource( const uno::Reference< XAccessible >& rInterface ) + { +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); +#endif + + mpImpl->SetEventSource( rInterface ); + +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); +#endif + } + + void AccessibleTextHelper::SetFocus( bool bHaveFocus ) + { +#ifdef DBG_UTIL + // precondition: solar mutex locked + DBG_TESTSOLARMUTEX(); + + mpImpl->CheckInvariants(); +#endif + + mpImpl->SetFocus( bHaveFocus ); + +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); +#endif + } + + bool AccessibleTextHelper::HaveFocus() + { +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); + + bool bRet( mpImpl->HaveFocus() ); + + mpImpl->CheckInvariants(); + + return bRet; +#else + return mpImpl->HaveFocus(); +#endif + } + + void AccessibleTextHelper::SetOffset( const Point& rPoint ) + { +#ifdef DBG_UTIL + // precondition: solar mutex locked + DBG_TESTSOLARMUTEX(); + + mpImpl->CheckInvariants(); +#endif + + mpImpl->SetOffset( rPoint ); + +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); +#endif + } + + void AccessibleTextHelper::SetStartIndex( sal_Int32 nOffset ) + { +#ifdef DBG_UTIL + // precondition: solar mutex locked + DBG_TESTSOLARMUTEX(); + + mpImpl->CheckInvariants(); +#endif + + mpImpl->SetStartIndex( nOffset ); + +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); +#endif + } + + sal_Int32 AccessibleTextHelper::GetStartIndex() const + { +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); + + sal_Int32 nOffset = mpImpl->GetStartIndex(); + + mpImpl->CheckInvariants(); + + return nOffset; +#else + return mpImpl->GetStartIndex(); +#endif + } + + void AccessibleTextHelper::SetAdditionalChildStates( sal_Int64 nChildStates ) + { + mpImpl->SetAdditionalChildStates( nChildStates ); + } + + void AccessibleTextHelper::UpdateChildren() + { +#ifdef DBG_UTIL + // precondition: solar mutex locked + DBG_TESTSOLARMUTEX(); + + mpImpl->CheckInvariants(); +#endif + + mpImpl->UpdateVisibleChildren(); + mpImpl->UpdateBoundRect(); + + mpImpl->UpdateSelection(); + +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); +#endif + } + + void AccessibleTextHelper::UpdateSelection() + { +#ifdef DBG_UTIL + // precondition: solar mutex locked + DBG_TESTSOLARMUTEX(); + mpImpl->CheckInvariants(); +#endif + + mpImpl->UpdateSelection(); + +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); +#endif + } + + void AccessibleTextHelper::Dispose() + { + // As Dispose calls ShutdownEditSource, which in turn + // deregisters as listener on the edit source, have to lock + // here + SolarMutexGuard aGuard; + +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); +#endif + + mpImpl->Dispose(); + +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); +#endif + } + + // XAccessibleContext + sal_Int64 AccessibleTextHelper::GetChildCount() const + { + SolarMutexGuard aGuard; + +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); + + sal_Int64 nRet = mpImpl->getAccessibleChildCount(); + + mpImpl->CheckInvariants(); + + return nRet; +#else + return mpImpl->getAccessibleChildCount(); +#endif + } + + uno::Reference< XAccessible > AccessibleTextHelper::GetChild( sal_Int64 i ) + { + SolarMutexGuard aGuard; + +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); + + uno::Reference< XAccessible > xRet = mpImpl->getAccessibleChild( i ); + + mpImpl->CheckInvariants(); + + return xRet; +#else + return mpImpl->getAccessibleChild( i ); +#endif + } + + void AccessibleTextHelper::AddEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) + { +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); + + mpImpl->addAccessibleEventListener( xListener ); + + mpImpl->CheckInvariants(); +#else + mpImpl->addAccessibleEventListener( xListener ); +#endif + } + + void AccessibleTextHelper::RemoveEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) + { +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); + + mpImpl->removeAccessibleEventListener( xListener ); + + mpImpl->CheckInvariants(); +#else + mpImpl->removeAccessibleEventListener( xListener ); +#endif + } + + // XAccessibleComponent + uno::Reference< XAccessible > AccessibleTextHelper::GetAt( const awt::Point& aPoint ) + { + SolarMutexGuard aGuard; + +#ifdef DBG_UTIL + mpImpl->CheckInvariants(); + + uno::Reference< XAccessible > xChild = mpImpl->getAccessibleAtPoint( aPoint ); + + mpImpl->CheckInvariants(); + + return xChild; +#else + return mpImpl->getAccessibleAtPoint( aPoint ); +#endif + } + +} // end of namespace accessibility + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/ChildrenManager.cxx b/svx/source/accessibility/ChildrenManager.cxx new file mode 100644 index 0000000000..2cac5153a5 --- /dev/null +++ b/svx/source/accessibility/ChildrenManager.cxx @@ -0,0 +1,115 @@ +/* -*- 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 <svx/ChildrenManager.hxx> +#include "ChildrenManagerImpl.hxx" +#include <sal/log.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using ::com::sun::star::uno::Reference; + +namespace accessibility { + +// AccessibleChildrenManager +ChildrenManager::ChildrenManager ( + const css::uno::Reference<XAccessible>& rxParent, + const css::uno::Reference<drawing::XShapes>& rxShapeList, + const AccessibleShapeTreeInfo& rShapeTreeInfo, + AccessibleContextBase& rContext) + : mpImpl( + new ChildrenManagerImpl( + rxParent, rxShapeList, rShapeTreeInfo, rContext)) +{ + mpImpl->Init (); +} + + +ChildrenManager::~ChildrenManager() +{ + mpImpl->dispose(); + + // empty + SAL_INFO("svx", "~ChildrenManager"); +} + +sal_Int64 ChildrenManager::GetChildCount() const noexcept +{ + return mpImpl->GetChildCount(); +} + +css::uno::Reference<XAccessible> ChildrenManager::GetChild (sal_Int64 nIndex) +{ + return mpImpl->GetChild (nIndex); +} + +const css::uno::Reference<css::drawing::XShape>& ChildrenManager::GetChildShape(sal_Int64 nIndex) +{ + return mpImpl->GetChildShape(nIndex); +} + +void ChildrenManager::Update (bool bCreateNewObjectsOnDemand) +{ + mpImpl->Update (bCreateNewObjectsOnDemand); +} + +void ChildrenManager::SetShapeList (const css::uno::Reference<css::drawing::XShapes>& xShapeList) +{ + mpImpl->SetShapeList (xShapeList); +} + +void ChildrenManager::AddAccessibleShape (rtl::Reference<AccessibleShape> const & shape) +{ + mpImpl->AddAccessibleShape (shape); +} + +void ChildrenManager::ClearAccessibleShapeList() +{ + mpImpl->ClearAccessibleShapeList (); +} + +void ChildrenManager::SetInfo (AccessibleShapeTreeInfo const & rShapeTreeInfo) +{ + mpImpl->SetInfo (rShapeTreeInfo); +} + +void ChildrenManager::UpdateSelection() +{ + mpImpl->UpdateSelection (); +} + +bool ChildrenManager::HasFocus() const +{ + return mpImpl->HasFocus (); +} + +void ChildrenManager::RemoveFocus() +{ + mpImpl->RemoveFocus (); +} + +// IAccessibleViewForwarderListener +void ChildrenManager::ViewForwarderChanged() +{ + mpImpl->ViewForwarderChanged(); +} + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/ChildrenManagerImpl.cxx b/svx/source/accessibility/ChildrenManagerImpl.cxx new file mode 100644 index 0000000000..5271f1887b --- /dev/null +++ b/svx/source/accessibility/ChildrenManagerImpl.cxx @@ -0,0 +1,1123 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> + +#include "ChildrenManagerImpl.hxx" +#include <svx/ShapeTypeHandler.hxx> +#include <svx/AccessibleControlShape.hxx> +#include <svx/AccessibleShape.hxx> +#include <svx/AccessibleShapeInfo.hxx> +#include <svx/IAccessibleViewForwarder.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/document/XShapeEventBroadcaster.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <comphelper/lok.hxx> +#include <comphelper/types.hxx> +#include <o3tl/safeint.hxx> +#include <rtl/ustring.hxx> +#include <tools/debug.hxx> +#include <svx/SvxShapeTypes.hxx> +#include <vcl/window.hxx> +#include <shapecollection.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using ::com::sun::star::uno::Reference; + +namespace accessibility { + +namespace +{ +void adjustIndexInParentOfShapes(ChildDescriptorListType& _rList) +{ + sal_Int32 i=0; + for (auto& rItem : _rList) + { + rItem.setIndexAtAccessibleShape(i); + ++i; + } +} +} + +// AccessibleChildrenManager +ChildrenManagerImpl::ChildrenManagerImpl ( + uno::Reference<XAccessible> xParent, + uno::Reference<drawing::XShapes> xShapeList, + const AccessibleShapeTreeInfo& rShapeTreeInfo, + AccessibleContextBase& rContext) + : mxShapeList (std::move(xShapeList)), + mxParent (std::move(xParent)), + maShapeTreeInfo (rShapeTreeInfo), + mrContext (rContext), + mpFocusedShape(nullptr) +{ +} + + +ChildrenManagerImpl::~ChildrenManagerImpl() +{ + DBG_ASSERT (m_bDisposed, "~AccessibleDrawDocumentView: object has not been disposed"); +} + + +void ChildrenManagerImpl::Init() +{ + // Register as view::XSelectionChangeListener. + Reference<frame::XController> xController(maShapeTreeInfo.GetController()); + Reference<view::XSelectionSupplier> xSelectionSupplier ( + xController, uno::UNO_QUERY); + if (xSelectionSupplier.is()) + { + xController->addEventListener( + static_cast<document::XEventListener*>(this)); + + xSelectionSupplier->addSelectionChangeListener ( + static_cast<view::XSelectionChangeListener*>(this)); + } + + // Register at model as document::XEventListener. + if (maShapeTreeInfo.GetModelBroadcaster().is()) + maShapeTreeInfo.GetModelBroadcaster()->addEventListener ( + static_cast<document::XEventListener*>(this)); +} + + +sal_Int64 ChildrenManagerImpl::GetChildCount() const noexcept +{ + return maVisibleChildren.size(); +} + + +const css::uno::Reference<css::drawing::XShape>& ChildrenManagerImpl::GetChildShape(sal_Int64 nIndex) +{ + // Check whether the given index is valid. + if (nIndex < 0 || o3tl::make_unsigned(nIndex) >= maVisibleChildren.size()) + throw lang::IndexOutOfBoundsException ( + "no accessible child with index " + OUString::number(nIndex), + mxParent); + return maVisibleChildren[nIndex].mxShape; +} + +/** Return the requested accessible child object. Create it if it is not + yet in the cache. +*/ +uno::Reference<XAccessible> + ChildrenManagerImpl::GetChild (sal_Int64 nIndex) +{ + // Check whether the given index is valid. + if (nIndex < 0 || o3tl::make_unsigned(nIndex) >= maVisibleChildren.size()) + throw lang::IndexOutOfBoundsException ( + "no accessible child with index " + OUString::number(nIndex), + mxParent); + + return GetChild (maVisibleChildren[nIndex],nIndex); +} + + +/** Return the requested accessible child object. Create it if it is not + yet in the cache. +*/ +uno::Reference<XAccessible> + ChildrenManagerImpl::GetChild (ChildDescriptor& rChildDescriptor,sal_Int32 _nIndex) +{ + if ( ! rChildDescriptor.mxAccessibleShape.is()) + { + SolarMutexGuard g; + // Make sure that the requested accessible object has not been + // created while locking the global mutex. + if ( ! rChildDescriptor.mxAccessibleShape.is()) + { + AccessibleShapeInfo aShapeInfo( + rChildDescriptor.mxShape, + mxParent, + this); + // Create accessible object that corresponds to the descriptor's + // shape. + rtl::Reference<AccessibleShape> pShape( + ShapeTypeHandler::Instance().CreateAccessibleObject ( + aShapeInfo, + maShapeTreeInfo)); + rChildDescriptor.mxAccessibleShape = pShape; + if ( pShape.is() ) + { + pShape->Init(); + pShape->setIndexInParent(_nIndex); + } + } + } + + return rChildDescriptor.mxAccessibleShape; +} + + +/** Find all shapes among the specified shapes that lie fully or partially + inside the visible area. Put those shapes into the cleared cache. The + corresponding accessible objects will be created on demand. + + At the moment, first all accessible objects are removed from the cache + and the appropriate listeners are informed of this. Next, the list is + created again. This should be optimized in the future to not remove and + create objects that will be in the list before and after the update + method. +*/ +void ChildrenManagerImpl::Update (bool bCreateNewObjectsOnDemand) +{ + if (maShapeTreeInfo.GetViewForwarder() == nullptr) + return; + tools::Rectangle aVisibleArea = maShapeTreeInfo.GetViewForwarder()->GetVisibleArea(); + + // 1. Create a local list of visible shapes. + ChildDescriptorListType aChildList; + CreateListOfVisibleShapes (aChildList); + + // 2. Replace the current list of visible shapes with the new one. Do + // the same with the visible area. + { + SolarMutexGuard g; + + // Use swap to copy the contents of the new list in constant time. + maVisibleChildren.swap (aChildList); + + // 3. Merge the information that is already known about the visible + // shapes from the previous list into the new list and identify + // old children that are now unused + std::vector<ChildDescriptor*> aUnusedChildList = MergeAccessibilityInformation (aChildList); + + adjustIndexInParentOfShapes(maVisibleChildren); + + // aChildList now contains all the old children, while maVisibleChildren + // contains all the current children + + // 4. Find all shapes in the old list that are not in the current list, + // send appropriate events and remove the accessible shape. + + // Do this *after* we have set our new list of children, because + // removing a child may cause + + // ChildDescriptor::disposeAccessibleObject --> + // AccessibleContextBase::CommitChange --> + // AtkListener::notifyEvent -> + // AtkListener::handleChildRemoved -> + // AtkListener::updateChildList + // AccessibleDrawDocumentView::getAccessibleChildCount -> + // ChildrenManagerImpl::GetChildCount -> + // maVisibleChildren.size() + + // to be fired, and so the operations will take place on + // the list we are trying to replace + + RemoveNonVisibleChildren (aUnusedChildList); + + aUnusedChildList.clear(); + aChildList.clear(); + + maVisibleArea = aVisibleArea; + } + + // 5. If the visible area has changed then send events that signal a + // change of their bounding boxes for all shapes that are members of + // both the current and the new list of visible shapes. + if (maVisibleArea != aVisibleArea) + SendVisibleAreaEvents (maVisibleChildren); + + // 6. If children have to be created immediately and not on demand then + // create the missing accessible objects now. + if (bCreateNewObjectsOnDemand) + return; + + //operate on a copy of the list and restore it afterwards to guard + //against the pathological case where maVisibleChildren gets modified + //by other calls to this object while CreateAccessibilityObjects + //executes which can happen when java is disabled and the "enable-java" + //dialog appears during the instantiation of the linguistic components + //triggered by the creation of shapes belonging to the a11y objects + // + //i.e. launch start-center, launch impress with java disabled and + //a java-using linguistic component installed + maVisibleChildren.swap(aChildList); + CreateAccessibilityObjects(aChildList); + maVisibleChildren.swap(aChildList); +} + +void ChildrenManagerImpl::CreateListOfVisibleShapes ( + ChildDescriptorListType& raDescriptorList) +{ + SolarMutexGuard g; + + OSL_ASSERT (maShapeTreeInfo.GetViewForwarder() != nullptr); + + tools::Rectangle aVisibleArea = maShapeTreeInfo.GetViewForwarder()->GetVisibleArea(); + + // Add the visible shapes for which the accessible objects already exist. + for (const auto& rpShape : maAccessibleShapes) + { + if (rpShape.is()) + { + uno::Reference<XAccessibleComponent> xComponent ( + rpShape->getAccessibleContext(), uno::UNO_QUERY); + if (xComponent.is()) + { + // The bounding box of the object already is clipped to the + // visible area. The object is therefore visible if the + // bounding box has non-zero extensions. + awt::Rectangle aPixelBBox (xComponent->getBounds()); + if ((aPixelBBox.Width > 0) && (aPixelBBox.Height > 0)) + raDescriptorList.emplace_back(rpShape); + } + } + } + + // Add the visible shapes for which only the XShapes exist. + if (!mxShapeList.is() || !mxShapeList->hasElements()) + return; + + sal_Int32 nShapeCount = mxShapeList->getCount(); + raDescriptorList.reserve( nShapeCount ); + awt::Point aPos; + awt::Size aSize; + tools::Rectangle aBoundingBox; + uno::Reference<drawing::XShape> xShape; + for (sal_Int32 i=0; i<nShapeCount; ++i) + { + mxShapeList->getByIndex(i) >>= xShape; + aPos = xShape->getPosition(); + aSize = xShape->getSize(); + + aBoundingBox.SetLeft( aPos.X ); + aBoundingBox.SetTop( aPos.Y ); + aBoundingBox.SetRight( aPos.X + aSize.Width ); + aBoundingBox.SetBottom( aPos.Y + aSize.Height ); + + // Insert shape if it is visible, i.e. its bounding box overlaps + // the visible area. In the LOK case we skip the overlap check + // since we could remove a shape that is visible on the client. + if ( aBoundingBox.Overlaps(aVisibleArea) || comphelper::LibreOfficeKit::isActive()) + raDescriptorList.emplace_back(xShape); + } +} + +namespace +{ + +bool childDescriptorLess(const ChildDescriptor& lhs, const ChildDescriptor& rhs) +{ + + auto pLhsShape = lhs.mxShape.get(); + auto pRhsShape = rhs.mxShape.get(); + if (pLhsShape || pRhsShape) + return pLhsShape < pRhsShape; + return lhs.mxAccessibleShape.get() < rhs.mxAccessibleShape.get(); +} + +bool childDescriptorPtrLess(const ChildDescriptor* lhs, const ChildDescriptor* rhs) +{ + return childDescriptorLess(*lhs, *rhs); +} + +} + +void ChildrenManagerImpl::RemoveNonVisibleChildren ( + const std::vector<ChildDescriptor*>& rNonVisibleChildren) +{ + for (ChildDescriptor* pChild : rNonVisibleChildren) + { + // The child is disposed when there is a UNO shape from which + // the accessible shape can be created when the shape becomes + // visible again. When there is no such UNO shape then simply + // reset the descriptor but keep the accessibility object. + if (pChild->mxShape.is()) + { + UnregisterAsDisposeListener (pChild->mxShape); + pChild->disposeAccessibleObject (mrContext); + } + else + { + AccessibleShape* pAccessibleShape = pChild->GetAccessibleShape(); + pAccessibleShape->ResetState (AccessibleStateType::VISIBLE); + pChild->mxAccessibleShape = nullptr; + } + } +} + +std::vector<ChildDescriptor*> ChildrenManagerImpl::MergeAccessibilityInformation ( + ChildDescriptorListType& raOldChildList) + +{ + // create a working copy of the vector of current children with pointers to elements, + // sort the old list and copy by mxShape, and then walk old/current lists in parallel, + // which avoids an O(n^2) loop + // (order of maVisibleChildren must remain unchanged to not randomly change a11y tree) + std::vector<ChildDescriptor*> aSortedVisibleChildren(maVisibleChildren.size()); + std::transform(maVisibleChildren.begin(), maVisibleChildren.end(), + aSortedVisibleChildren.begin(), [](auto& e) {return &e;}); + std::sort(aSortedVisibleChildren.begin(), aSortedVisibleChildren.end(), childDescriptorPtrLess); + + // old list can be reordered without problems + std::sort(raOldChildList.begin(), raOldChildList.end(), childDescriptorLess); + + ChildDescriptorListType::const_iterator aOldChildDescriptor = raOldChildList.begin(); + ChildDescriptorListType::const_iterator aEndOldChildren = raOldChildList.end(); + for (ChildDescriptor* pChild : aSortedVisibleChildren) + { + while (aOldChildDescriptor != aEndOldChildren && childDescriptorLess(*aOldChildDescriptor, *pChild)) + { + aOldChildDescriptor++; + } + + // Copy accessible shape if that exists in the old descriptor. + if (aOldChildDescriptor != aEndOldChildren && *aOldChildDescriptor == *pChild && + aOldChildDescriptor->mxAccessibleShape.is()) + { + pChild->mxAccessibleShape = aOldChildDescriptor->mxAccessibleShape; + pChild->mbCreateEventPending = false; + } + else + RegisterAsDisposeListener (pChild->mxShape); + } + + // collect list of children that are in the old, but not the new vector + std::vector<ChildDescriptor*> aObsoleteChildren; + + auto newIt = aSortedVisibleChildren.begin(); + auto newEnd = aSortedVisibleChildren.end(); + for (ChildDescriptor& rOldChild : raOldChildList) + { + while (newIt != newEnd && childDescriptorLess(**newIt, rOldChild)) + newIt++; + if (newIt == newEnd || !(**newIt == rOldChild) ) + aObsoleteChildren.push_back(&rOldChild); + } + + return aObsoleteChildren; +} + +void ChildrenManagerImpl::SendVisibleAreaEvents ( + ChildDescriptorListType& raNewChildList) +{ + for (const auto& rChild : raNewChildList) + { + // Tell shape of changed visible area. To do this, fake a + // change of the view forwarder. (Actually we usually get here + // as a result of a change of the view forwarder). + AccessibleShape* pShape = rChild.GetAccessibleShape (); + if (pShape != nullptr) + pShape->ViewForwarderChanged(); + } +} + + +void ChildrenManagerImpl::CreateAccessibilityObjects ( + ChildDescriptorListType& raNewChildList) +{ + sal_Int32 nPos = 0; + for ( auto& rChild : raNewChildList) + { + // Create the associated accessible object when the flag says so and + // it does not yet exist. + if ( ! rChild.mxAccessibleShape.is() ) + GetChild (rChild, nPos); + if (rChild.mxAccessibleShape.is() && rChild.mbCreateEventPending) + { + rChild.mbCreateEventPending = false; + mrContext.CommitChange ( + AccessibleEventId::CHILD, + uno::Any(uno::Reference<XAccessible>(rChild.mxAccessibleShape)), + uno::Any(), -1); + } + ++nPos; + } +} + + +void ChildrenManagerImpl::AddShape (const Reference<drawing::XShape>& rxShape) +{ + if (!rxShape.is()) + return; + + SolarMutexClearableGuard aGuard; + + // Test visibility of the shape. + tools::Rectangle aVisibleArea = maShapeTreeInfo.GetViewForwarder()->GetVisibleArea(); + awt::Point aPos = rxShape->getPosition(); + awt::Size aSize = rxShape->getSize(); + + tools::Rectangle aBoundingBox ( + aPos.X, + aPos.Y, + aPos.X + aSize.Width, + aPos.Y + aSize.Height); + + // Add the shape only when it belongs to the list of shapes stored + // in mxShapeList (which is either a page or a group shape). + Reference<container::XChild> xChild (rxShape, uno::UNO_QUERY); + if (!xChild.is()) + return; + + Reference<drawing::XShapes> xParent (xChild->getParent(), uno::UNO_QUERY); + if (xParent != mxShapeList) + return; + + if (!aBoundingBox.Overlaps(aVisibleArea) && !comphelper::LibreOfficeKit::isActive()) + return; + + // Add shape to list of visible shapes. + maVisibleChildren.emplace_back(rxShape); + + // Create accessibility object. + ChildDescriptor& rDescriptor = maVisibleChildren.back(); + GetChild (rDescriptor, maVisibleChildren.size()-1); + + // Inform listeners about new child. + uno::Any aNewShape; + aNewShape <<= uno::Reference<XAccessible>(rDescriptor.mxAccessibleShape); + aGuard.clear(); + mrContext.CommitChange ( + AccessibleEventId::CHILD, + aNewShape, + uno::Any(), + maVisibleChildren.size() - 1); + RegisterAsDisposeListener(rxShape); +} + + +void ChildrenManagerImpl::RemoveShape (const Reference<drawing::XShape>& rxShape) +{ + if (!rxShape.is()) + return; + + SolarMutexGuard g; + + // Search shape in list of visible children. + ChildDescriptorListType::iterator I ( + ::std::find (maVisibleChildren.begin(), maVisibleChildren.end(), + ChildDescriptor (rxShape))); + if (I == maVisibleChildren.end()) + return; + + // Remove descriptor from that list. + Reference<XAccessible> xHoldAlive(I->mxAccessibleShape); + + UnregisterAsDisposeListener (I->mxShape); + // Dispose the accessible object. + I->disposeAccessibleObject (mrContext); + + // Now we can safely remove the child descriptor and thus + // invalidate the iterator. + maVisibleChildren.erase (I); + + adjustIndexInParentOfShapes(maVisibleChildren); +} + + +void ChildrenManagerImpl::SetShapeList (const css::uno::Reference<css::drawing::XShapes>& xShapeList) +{ + mxShapeList = xShapeList; +} + + +void ChildrenManagerImpl::AddAccessibleShape (rtl::Reference<AccessibleShape> const & shape) +{ + assert(shape.is()); + maAccessibleShapes.push_back (shape); +} + + +void ChildrenManagerImpl::ClearAccessibleShapeList() +{ + // Copy the list of (visible) shapes to local lists and clear the + // originals. + ChildDescriptorListType aLocalVisibleChildren; + aLocalVisibleChildren.swap(maVisibleChildren); + AccessibleShapeList aLocalAccessibleShapes; + aLocalAccessibleShapes.swap(maAccessibleShapes); + + // Tell the listeners that all children are gone. + mrContext.CommitChange ( + AccessibleEventId::INVALIDATE_ALL_CHILDREN, + uno::Any(), + uno::Any(), -1); + + // Now the objects in the local lists can be safely disposed without + // having problems with callers that want to update their child lists. + + // Clear the list of visible accessible objects. Objects not created on + // demand for XShapes are treated below. + for (auto& rChild : aLocalVisibleChildren) + if ( rChild.mxAccessibleShape.is() && rChild.mxShape.is() ) + { + rChild.mxAccessibleShape->dispose(); + rChild.mxAccessibleShape = nullptr; + } + + // Dispose all objects in the accessible shape list. + for (auto& rpShape : aLocalAccessibleShapes) + if (rpShape.is()) + { + // Dispose the object. + rpShape->dispose(); + rpShape = nullptr; + } +} + + +/** If the broadcasters change at which this object is registered then + unregister at old and register at new broadcasters. +*/ +void ChildrenManagerImpl::SetInfo (const AccessibleShapeTreeInfo& rShapeTreeInfo) +{ + // Remember the current broadcasters and exchange the shape tree info. + Reference<document::XEventBroadcaster> xCurrentBroadcaster; + Reference<frame::XController> xCurrentController; + Reference<view::XSelectionSupplier> xCurrentSelectionSupplier; + { + SolarMutexGuard g; + xCurrentBroadcaster = maShapeTreeInfo.GetModelBroadcaster(); + xCurrentController = maShapeTreeInfo.GetController(); + xCurrentSelectionSupplier.set( xCurrentController, uno::UNO_QUERY); + maShapeTreeInfo = rShapeTreeInfo; + } + + // Move registration to new model. + if (maShapeTreeInfo.GetModelBroadcaster() != xCurrentBroadcaster) + { + // Register at new broadcaster. + if (maShapeTreeInfo.GetModelBroadcaster().is()) + maShapeTreeInfo.GetModelBroadcaster()->addEventListener ( + static_cast<document::XEventListener*>(this)); + + // Unregister at old broadcaster. + if (xCurrentBroadcaster.is()) + xCurrentBroadcaster->removeEventListener ( + static_cast<document::XEventListener*>(this)); + } + + // Move registration to new selection supplier. + Reference<frame::XController> xNewController(maShapeTreeInfo.GetController()); + Reference<view::XSelectionSupplier> xNewSelectionSupplier ( + xNewController, uno::UNO_QUERY); + if (xNewSelectionSupplier == xCurrentSelectionSupplier) + return; + + // Register at new broadcaster. + if (xNewSelectionSupplier.is()) + { + xNewController->addEventListener( + static_cast<document::XEventListener*>(this)); + + xNewSelectionSupplier->addSelectionChangeListener ( + static_cast<view::XSelectionChangeListener*>(this)); + } + + // Unregister at old broadcaster. + if (xCurrentSelectionSupplier.is()) + { + xCurrentSelectionSupplier->removeSelectionChangeListener ( + static_cast<view::XSelectionChangeListener*>(this)); + + xCurrentController->removeEventListener( + static_cast<document::XEventListener*>(this)); + } +} + +// lang::XEventListener +void SAL_CALL + ChildrenManagerImpl::disposing (const lang::EventObject& rEventObject) +{ + if (rEventObject.Source == maShapeTreeInfo.GetModelBroadcaster() + || rEventObject.Source == maShapeTreeInfo.GetController()) + { + impl_dispose(); + } + + // Handle disposing UNO shapes. + else + { + Reference<drawing::XShape> xShape (rEventObject.Source, uno::UNO_QUERY); + + // Find the descriptor for the given shape. + ChildDescriptorListType::iterator I ( + ::std::find (maVisibleChildren.begin(), maVisibleChildren.end(), + ChildDescriptor (xShape))); + if (I != maVisibleChildren.end()) + { + // Clear the descriptor. + I->disposeAccessibleObject (mrContext); + I->mxShape = nullptr; + } + } +} + +// document::XEventListener +/** Listen for new and removed shapes. +*/ +void SAL_CALL + ChildrenManagerImpl::notifyEvent ( + const document::EventObject& rEventObject) +{ + // tdf#158169 if we are already disposed, execute no actions, but inform the + // caller that we are disposed + if ( m_bDisposed ) + throw lang::DisposedException(); + + if (rEventObject.EventName == "ShapeInserted") + AddShape (Reference<drawing::XShape>(rEventObject.Source, uno::UNO_QUERY)); + else if (rEventObject.EventName == "ShapeRemoved") + RemoveShape (Reference<drawing::XShape>(rEventObject.Source, uno::UNO_QUERY)); + // else ignore unknown event. +} + +// view::XSelectionChangeListener +void SAL_CALL + ChildrenManagerImpl::selectionChanged (const lang::EventObject& /*rEvent*/) +{ + UpdateSelection (); +} + + +void ChildrenManagerImpl::impl_dispose() +{ + Reference<frame::XController> xController(maShapeTreeInfo.GetController()); + // Remove from broadcasters. + try + { + Reference<view::XSelectionSupplier> xSelectionSupplier ( + xController, uno::UNO_QUERY); + if (xSelectionSupplier.is()) + { + xSelectionSupplier->removeSelectionChangeListener ( + static_cast<view::XSelectionChangeListener*>(this)); + } + } + catch( uno::RuntimeException&) + {} + + try + { + if (xController.is()) + xController->removeEventListener( + static_cast<document::XEventListener*>(this)); + } + catch( uno::RuntimeException&) + {} + + maShapeTreeInfo.SetController (nullptr); + + try + { + // Remove from broadcaster. + if (maShapeTreeInfo.GetModelBroadcaster().is()) + maShapeTreeInfo.GetModelBroadcaster()->removeEventListener ( + static_cast<document::XEventListener*>(this)); + maShapeTreeInfo.SetModelBroadcaster (nullptr); + } + catch( uno::RuntimeException& ) + {} + + ClearAccessibleShapeList (); + SetShapeList (nullptr); +} + + +void ChildrenManagerImpl::disposing(std::unique_lock<std::mutex>&) +{ + impl_dispose(); +} + +// IAccessibleViewForwarderListener +void ChildrenManagerImpl::ViewForwarderChanged() +{ + Update(false); +} + +// IAccessibleParent +bool ChildrenManagerImpl::ReplaceChild ( + AccessibleShape* pCurrentChild, + const css::uno::Reference< css::drawing::XShape >& _rxShape, + const tools::Long /*_nIndex*/, + const AccessibleShapeTreeInfo& _rShapeTreeInfo) +{ + // Iterate over the visible children. If one of them has an already + // created accessible object that matches pCurrentChild then replace + // it. Otherwise the child to replace is either not in the list or has + // not ye been created (and is therefore not in the list, too) and a + // replacement is not necessary. + auto I = std::find_if(maVisibleChildren.begin(), maVisibleChildren.end(), + [&pCurrentChild](const ChildDescriptor& rChild) { return rChild.GetAccessibleShape() == pCurrentChild; }); + + if (I != maVisibleChildren.end()) + { + // Dispose the current child and send an event about its deletion. + pCurrentChild->dispose(); + mrContext.CommitChange ( + AccessibleEventId::CHILD, + uno::Any(), + uno::Any (uno::Reference<XAccessible>(I->mxAccessibleShape)), -1); + + // Replace with replacement and send an event about existence + // of the new child. + AccessibleShapeInfo aShapeInfo( _rxShape, pCurrentChild->getAccessibleParent(), this ); + // create the new child + rtl::Reference<AccessibleShape> pNewChild(ShapeTypeHandler::Instance().CreateAccessibleObject ( + aShapeInfo, + _rShapeTreeInfo + )); + if ( pNewChild.is() ) + pNewChild->Init(); + + I->mxAccessibleShape = pNewChild.get(); + mrContext.CommitChange ( + AccessibleEventId::CHILD, + uno::Any (uno::Reference<XAccessible>(I->mxAccessibleShape)), + uno::Any(), -1); + + return true; + } + + // When not found among the visible children we have to search the list + // of accessible shapes. This is not yet implemented. + return false; +} + +// Add the impl method for IAccessibleParent interface +AccessibleControlShape * ChildrenManagerImpl::GetAccControlShapeFromModel(css::beans::XPropertySet* pSet) +{ + sal_Int64 count = GetChildCount(); + for (sal_Int64 index=0;index<count;index++) + { + AccessibleShape* pAccShape = maVisibleChildren[index].GetAccessibleShape(); + if (pAccShape && ::accessibility::ShapeTypeHandler::Instance().GetTypeId(pAccShape->GetXShape()) == DRAWING_CONTROL) + { + auto* pCtlAccShape = static_cast<::accessibility::AccessibleControlShape*>(pAccShape); + if (pCtlAccShape->GetControlModel() == pSet) + return pCtlAccShape; + } + } + return nullptr; +} +uno::Reference<XAccessible> + ChildrenManagerImpl::GetAccessibleCaption (const uno::Reference<drawing::XShape>& xShape) +{ + auto I = std::find_if(maVisibleChildren.begin(), maVisibleChildren.end(), + [&xShape](const ChildDescriptor& rChild) { return rChild.mxShape.get() == xShape.get(); }); + if (I != maVisibleChildren.end()) + return I->mxAccessibleShape; + return uno::Reference<XAccessible> (); +} + +/** Update the <const>SELECTED</const> and the <const>FOCUSED</const> state + of all visible children. Maybe this should be changed to all children. + + Iterate over all descriptors of visible accessible shapes and look them + up in the selection. + + If there is no valid controller then all shapes are deselected and + unfocused. If the controller's frame is not active then all shapes are + unfocused. +*/ +void ChildrenManagerImpl::UpdateSelection() +{ + // Remember the current and new focused shape. + AccessibleShape* pCurrentlyFocusedShape = nullptr; + AccessibleShape* pNewFocusedShape = nullptr; + typedef std::pair< AccessibleShape* , sal_Bool > PAIR_SHAPE;//sal_Bool Selected,UnSelected. + typedef std::vector< PAIR_SHAPE > VEC_SHAPE; + VEC_SHAPE vecSelect; + int nAddSelect=0; + bool bHasSelectedShape=false; + if (!maVisibleChildren.empty()) + { + Reference<frame::XController> xController(maShapeTreeInfo.GetController()); + Reference<view::XSelectionSupplier> xSelectionSupplier ( + xController, uno::UNO_QUERY); + + // Try to cast the selection both to a multi selection and to a single + // selection. + Reference<container::XIndexAccess> xSelectedShapeAccess; + Reference<drawing::XShape> xSelectedShape; + if (xSelectionSupplier.is()) + { + xSelectedShapeAccess.set( xSelectionSupplier->getSelection(), uno::UNO_QUERY); + xSelectedShape.set( xSelectionSupplier->getSelection(), uno::UNO_QUERY); + } + + // tdf#139220 to quickly find if a given drawing::XShape is selected + std::vector<css::uno::Reference<css::drawing::XShape>> aSortedSelectedShapes; + if (!xSelectedShape.is() && xSelectedShapeAccess.is()) + { + sal_Int32 nCount = xSelectedShapeAccess->getCount(); + aSortedSelectedShapes.reserve(nCount); + if (auto pSvxShape = dynamic_cast<SvxShapeCollection*>(xSelectedShapeAccess.get())) + { + pSvxShape->getAllShapes(aSortedSelectedShapes); + } + else + for (sal_Int32 i = 0; i < nCount; ++i) + { + css::uno::Reference<css::drawing::XShape> xShape(xSelectedShapeAccess->getByIndex(i), uno::UNO_QUERY); + aSortedSelectedShapes.push_back(xShape); + } + std::sort(aSortedSelectedShapes.begin(), aSortedSelectedShapes.end()); + } + + for (const auto& rChild : maVisibleChildren) + { + AccessibleShape* pAccessibleShape = rChild.GetAccessibleShape(); + if (rChild.mxAccessibleShape.is() && rChild.mxShape.is() && pAccessibleShape!=nullptr) + { + short nRole = pAccessibleShape->getAccessibleRole(); + bool bDrawShape = ( + nRole == AccessibleRole::GRAPHIC || + nRole == AccessibleRole::EMBEDDED_OBJECT || + nRole == AccessibleRole::SHAPE || + nRole == AccessibleRole::IMAGE_MAP || + nRole == AccessibleRole::TABLE_CELL || + nRole == AccessibleRole::TABLE ); + bool bShapeIsSelected = false; + + // Look up the shape in the (single or multi-) selection. + if (xSelectedShape.is()) + { + if (rChild.mxShape == xSelectedShape) + { + bShapeIsSelected = true; + pNewFocusedShape = pAccessibleShape; + } + } + else if (!aSortedSelectedShapes.empty()) + { + if (std::binary_search(aSortedSelectedShapes.begin(), aSortedSelectedShapes.end(), rChild.mxShape)) + { + bShapeIsSelected = true; + // In a multi-selection no shape has the focus. + if (aSortedSelectedShapes.size() == 1) + pNewFocusedShape = pAccessibleShape; + } + } + + // Set or reset the SELECTED state. + if (bShapeIsSelected) + { + if (pAccessibleShape->SetState (AccessibleStateType::SELECTED)) + { + if (bDrawShape) + { + vecSelect.emplace_back(pAccessibleShape,true); + ++nAddSelect; + } + } + else + {//Selected not change,has selected shape before + bHasSelectedShape=true; + } + } + else + //pAccessibleShape->ResetState (AccessibleStateType::SELECTED); + { + if(pAccessibleShape->ResetState (AccessibleStateType::SELECTED)) + { + if(bDrawShape) + { + vecSelect.emplace_back(pAccessibleShape,false); + } + } + } + // Does the shape have the current selection? + if (pAccessibleShape->GetState (AccessibleStateType::FOCUSED)) + pCurrentlyFocusedShape = pAccessibleShape; + } + } + } + + vcl::Window *pParentWindow = maShapeTreeInfo.GetWindow(); + bool bShapeActive= false; + // For table cell, the table's parent must be checked to make sure it has focus. + if (pParentWindow) + { + vcl::Window *pPWindow = pParentWindow->GetParent(); + if (pParentWindow->HasFocus() || (pPWindow && pPWindow->HasFocus())) + bShapeActive =true; + } + // Move focus from current to newly focused shape. + if (pCurrentlyFocusedShape != pNewFocusedShape) + { + if (pCurrentlyFocusedShape != nullptr) + pCurrentlyFocusedShape->ResetState (AccessibleStateType::FOCUSED); + if (pNewFocusedShape != nullptr && bShapeActive) + pNewFocusedShape->SetState (AccessibleStateType::FOCUSED); + } + + if (nAddSelect >= 10 )//fire selection within + { + mrContext.CommitChange(AccessibleEventId::SELECTION_CHANGED_WITHIN,uno::Any(),uno::Any(), -1); + nAddSelect =0 ;//not fire selection event + } + for (VEC_SHAPE::reverse_iterator vi = vecSelect.rbegin(), aEndVecSelect = vecSelect.rend(); vi != aEndVecSelect ;++vi) + { + PAIR_SHAPE &pairShape= *vi; + Reference< XAccessible > xShape(pairShape.first); + uno::Any anyShape; + anyShape <<= xShape; + + if (pairShape.second)//Selection add + { + if (bHasSelectedShape) + { + if ( nAddSelect > 0 ) + { + mrContext.CommitChange(AccessibleEventId::SELECTION_CHANGED_ADD,anyShape,uno::Any(), -1); + } + } + else + { + //if has not selected shape ,first selected shape is fire selection event; + if (nAddSelect > 0 ) + { + mrContext.CommitChange(AccessibleEventId::SELECTION_CHANGED,anyShape,uno::Any(), -1); + } + if (nAddSelect > 1 )//check other selected shape fire selection add event + { + bHasSelectedShape=true; + } + } + } + else //selection remove + { + mrContext.CommitChange(AccessibleEventId::SELECTION_CHANGED_REMOVE,anyShape,uno::Any(), -1); + } + } + + // We need to know when text content is no more edited but shape is still selected. + // For instance when ESC is pressed. + // The only difference is provided by nSelectedChildCount: on editing is equal to 1. + // In the following a shape get selected, but it was already selected, anyway editing is no more active. + if (comphelper::LibreOfficeKit::isActive() && pNewFocusedShape && nAddSelect == 0) + { + sal_Int64 nChildCount = pNewFocusedShape->getAccessibleChildCount(); + sal_Int64 nSelectedChildCount = pNewFocusedShape->getSelectedAccessibleChildCount(); + if (nChildCount > 0 && nSelectedChildCount == 0) + { + Reference< XAccessible > xShape(pNewFocusedShape); + uno::Any anyShape; + anyShape <<= xShape; + mrContext.CommitChange(AccessibleEventId::SELECTION_CHANGED,anyShape,uno::Any(), -1); + } + } + + // Remember whether there is a shape that now has the focus. + mpFocusedShape = pNewFocusedShape; +} + + +bool ChildrenManagerImpl::HasFocus() const +{ + return mpFocusedShape != nullptr; +} + + +void ChildrenManagerImpl::RemoveFocus() +{ + if (mpFocusedShape != nullptr) + { + mpFocusedShape->ResetState (AccessibleStateType::FOCUSED); + mpFocusedShape = nullptr; + } +} + + +void ChildrenManagerImpl::RegisterAsDisposeListener ( + const Reference<drawing::XShape>& xShape) +{ + Reference<lang::XComponent> xComponent (xShape, uno::UNO_QUERY); + if (xComponent.is()) + xComponent->addEventListener ( + static_cast<document::XEventListener*>(this)); +} + + +void ChildrenManagerImpl::UnregisterAsDisposeListener ( + const Reference<drawing::XShape>& xShape) +{ + Reference<lang::XComponent> xComponent (xShape, uno::UNO_QUERY); + if (xComponent.is()) + xComponent->removeEventListener ( + static_cast<document::XEventListener*>(this)); +} + +// AccessibleChildDescriptor +ChildDescriptor::ChildDescriptor (const Reference<drawing::XShape>& xShape) + : mxShape (xShape), + mbCreateEventPending (true) +{ + // Empty. +} + + +ChildDescriptor::ChildDescriptor (const rtl::Reference<AccessibleShape>& rxAccessibleShape) + : mxAccessibleShape (rxAccessibleShape), + mbCreateEventPending (true) +{ + // Make sure that the accessible object has the <const>VISIBLE</const> + // state set. + AccessibleShape* pAccessibleShape = GetAccessibleShape(); + pAccessibleShape->SetState (AccessibleStateType::VISIBLE); +} + +void ChildDescriptor::setIndexAtAccessibleShape(sal_Int32 _nIndex) +{ + AccessibleShape* pShape = GetAccessibleShape(); + if ( pShape ) + pShape->setIndexInParent(_nIndex); +} + + +void ChildDescriptor::disposeAccessibleObject (AccessibleContextBase& rParent) +{ + if (!mxAccessibleShape.is()) + return; + + // Send event that the shape has been removed. + uno::Any aOldValue; + aOldValue <<= uno::Reference<XAccessible>(mxAccessibleShape); + rParent.CommitChange ( + AccessibleEventId::CHILD, + uno::Any(), + aOldValue, -1); + + // Dispose and remove the object. + if (mxAccessibleShape.is()) + mxAccessibleShape->dispose(); + + mxAccessibleShape = nullptr; +} + + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/ChildrenManagerImpl.hxx b/svx/source/accessibility/ChildrenManagerImpl.hxx new file mode 100644 index 0000000000..2de34e10da --- /dev/null +++ b/svx/source/accessibility/ChildrenManagerImpl.hxx @@ -0,0 +1,497 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_ACCESSIBILITY_CHILDRENMANAGERIMPL_HXX +#define INCLUDED_SVX_SOURCE_ACCESSIBILITY_CHILDRENMANAGERIMPL_HXX + +#include <svx/IAccessibleViewForwarderListener.hxx> +#include <svx/IAccessibleParent.hxx> +#include <svx/AccessibleShapeTreeInfo.hxx> +#include <editeng/AccessibleContextBase.hxx> +#include <comphelper/compbase.hxx> +#include <tools/gen.hxx> +#include <vector> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/document/XEventListener.hpp> +#include <com/sun/star/view/XSelectionChangeListener.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> + +namespace accessibility { + +class AccessibleShape; + +class ChildDescriptor; // See below for declaration. +typedef ::std::vector<ChildDescriptor> ChildDescriptorListType; + +// Re-using MutexOwner class defined in AccessibleContextBase.hxx + +/** This class contains the actual implementation of the children manager. + + <p>It maintains a set of visible accessible shapes in + <member>maVisibleChildren</member>. The objects in this list stem from + two sources. The first is a list of UNO shapes like the list of shapes + in a draw page. A reference to this list is held in + <member>maShapeList</member>. Accessible objects for these shapes are + created on demand. The list can be replaced by calls to the + <member>SetShapeList</member> method. The second source is a list of + already accessible objects. It can be modified by calls to the + <member>AddAccessibleShape</member> and + <member>ClearAccessibleShapeList</member> methods.</p> + + <p>Each call of the <member>Update</member> method leads to a + re-calculation of the visible shapes which then can be queried with the + <member>GetChildCount</member> and <member>GetChild</member> methods. + Events are sent informing all listeners about the removed shapes which are + not visible anymore and about the added shapes.</p> + + <p> The visible area which is used to determine the visibility of the + shapes is taken from the view forwarder. Thus, to signal a change of + the visible area call <member>ViewForwarderChanged</member>.</p> + + <p>The children manager adds itself as disposing() listener at every UNO + shape it creates an accessible object for so that when the UNO shape + passes away it can dispose() the associated accessible object.</p> + + @see ChildrenManager +*/ +class ChildrenManagerImpl final + : public comphelper::WeakComponentImplHelper< + css::document::XEventListener, + css::view::XSelectionChangeListener>, + public IAccessibleViewForwarderListener, + public IAccessibleParent +{ +public: + /** Create a children manager, which manages the children of the given + parent. The parent is used for creating accessible objects. The + list of shapes for which to create those objects is not derived from + the parent and has to be provided separately by calling one of the + update methods. + @param rxParent + The parent of the accessible objects which will be created + on demand at some point of time in the future. + @param rxShapeList + List of UNO shapes to manage. + @param rShapeTreeInfo + Bundle of information passed down the shape tree. + @param rContext + An accessible context object that is called for firing events + for new and deleted children, i.e. that holds a list of + listeners to be informed. + */ + ChildrenManagerImpl (css::uno::Reference<css::accessibility::XAccessible> xParent, + css::uno::Reference<css::drawing::XShapes> xShapeList, + const AccessibleShapeTreeInfo& rShapeTreeInfo, + AccessibleContextBase& rContext); + + /** If there still are managed children these are disposed and + released. + */ + virtual ~ChildrenManagerImpl() override; + + /** Do that part of the initialization that you can not or should not do + in the constructor like registering at broadcasters. + */ + void Init(); + + /** Return the number of currently visible accessible children. + @return + If there are no children a 0 is returned. + */ + sal_Int64 GetChildCount() const noexcept; + + /// @throws css::uno::RuntimeException + /// @throws css::lang::IndexOutOfBoundsException + const css::uno::Reference<css::drawing::XShape>& GetChildShape(sal_Int64 nIndex); + /** Return the requested accessible child or throw and + IndexOutOfBoundsException if the given index is invalid. + @param nIndex + Index of the requested child. Call getChildCount for obtaining + the number of children. + @return + In case of a valid index this method returns a reference to the + requested accessible child. This reference is empty if it has + not been possible to create the accessible object of the + corresponding shape. + @throws + Throws an IndexOutOfBoundsException if the index is not valid. + */ + css::uno::Reference<css::accessibility::XAccessible> + GetChild (sal_Int64 nIndex); + + /** Return the requested accessible child. + @param aChildDescriptor + This object contains references to the original shape and its + associated accessible object. + @param _nIndex + The index which will be used in getAccessibleIndexInParent of the accessible shape. + @return + Returns a reference to the requested accessible child. This + reference is empty if it has not been possible to create the + accessible object of the corresponding shape. + @throws css::uno::RuntimeException + */ + css::uno::Reference<css::accessibility::XAccessible> + GetChild (ChildDescriptor& aChildDescriptor,sal_Int32 _nIndex); + + /** Update the child manager. Take care of a modified set of children + and modified visible area. This method can optimize the update + process with respect separate updates of a modified children list + and visible area. + @param bCreateNewObjectsOnDemand + If </true> then accessible objects associated with the visible + shapes are created only when asked for. No event is sent on + creation. If </false> then the accessible objects are created + before this method returns and events are sent to inform the + listeners of the new object. + */ + void Update (bool bCreateNewObjectsOnDemand); + + /** Set the list of UNO shapes to the given list. This removes the old + list and does not add to it. The list of accessible shapes that is + build up by calls to <member>AddAccessibleShape</member> is not + modified. Neither is the list of visible children. Accessible + objects are created on demand. + @param xShapeList + The list of UNO shapes that replaces the old list. + */ + void SetShapeList (const css::uno::Reference<css::drawing::XShapes>& xShapeList); + + /** Add an accessible shape. This does not modify the list of UNO shapes + or the list of visible shapes. Accessible shapes are, at the + moment, not tested against the visible area but are always appended + to the list of visible children. + @param shape + The new shape that is added to the list of accessible shapes; must + be non-null. + */ + void AddAccessibleShape (rtl::Reference<AccessibleShape> const & shape); + + /** Clear the lists of accessible shapes and that of visible accessible + shapes. The list of UNO shapes is not modified. + */ + void ClearAccessibleShapeList(); + + /** Set a new event shape tree info. Call this method to inform the + children manager of a change of the info bundle. + @param rShapeTreeInfo + The new info that replaces the current one. + */ + void SetInfo (const AccessibleShapeTreeInfo& rShapeTreeInfo); + + /** Update the SELECTED and FOCUSED states of all visible children + according to the given selection. This includes setting + <em>and</em> resetting the states. + */ + void UpdateSelection(); + + /** Return whether one of the shapes managed by this object has + currently the focus. + @return + Returns <true/> when there is a shape that has the focus and + <false/> when there is no such shape. + */ + bool HasFocus() const; + + /** When there is a shape that currently has the focus, + i.e. <member>HasFocus()</member> returns <true/> then remove the + focus from that shape. Otherwise nothing changes. + */ + void RemoveFocus(); + + // lang::XEventListener + virtual void SAL_CALL + disposing (const css::lang::EventObject& rEventObject) override; + + // document::XEventListener + virtual void SAL_CALL + notifyEvent (const css::document::EventObject& rEventObject) override; + + // view::XSelectionChangeListener + virtual void SAL_CALL + selectionChanged (const css::lang::EventObject& rEvent) override; + + // IAccessibleViewForwarderListener + /** Informs this children manager and its children about a change of one + (or more) aspect of the view forwarder. + @param aChangeType + A change type of <const>VISIBLE_AREA</const> leads to a call to + the <member>Update</member> which creates accessible objects of + new shapes immediately. Other change types are passed to the + visible accessible children without calling + <member>Update</member>. + @param pViewForwarder + The modified view forwarder. Use this one from now on. + */ + virtual void ViewForwarderChanged() override; + + // IAccessibleParent + /** Replace the specified child with a replacement. + @param pCurrentChild + This child is to be replaced. + @param pReplacement + The replacement for the current child. + @return + The returned value indicates whether the replacement has been + finished successfully. + */ + virtual bool ReplaceChild ( + AccessibleShape* pCurrentChild, + const css::uno::Reference< css::drawing::XShape >& _rxShape, + const tools::Long _nIndex, + const AccessibleShapeTreeInfo& _rShapeTreeInfo + ) override; + + // Add the impl method for IAccessibleParent interface + virtual AccessibleControlShape* GetAccControlShapeFromModel + (css::beans::XPropertySet* pSet) override; + virtual css::uno::Reference<css::accessibility::XAccessible> + GetAccessibleCaption (const css::uno::Reference<css::drawing::XShape>& xShape) override; + +private: + /** This list holds the descriptors of all currently visible shapes and + associated accessible object. + + <p>With the descriptors it maintains a mapping of shapes to + accessible objects. It acts as a cache in that accessible objects + are only created on demand and released with every update (where the + latter may be optimized by the update methods).<p> + + <p>The list is realized as a vector because it remains unchanged + between updates (i.e. complete rebuilds of the list) and allows a + fast (constant time) access to its elements for given indices.</p> + */ + ChildDescriptorListType maVisibleChildren; + + /** The original list of UNO shapes. The visible shapes are inserted + into the list of visible children + <member>maVisibleChildren</member>. + */ + css::uno::Reference<css::drawing::XShapes> mxShapeList; + + /** This list of additional accessible shapes that can or shall not be + created by the shape factory. + */ + typedef std::vector< rtl::Reference< AccessibleShape> > AccessibleShapeList; + AccessibleShapeList maAccessibleShapes; + + /** Rectangle that describes the visible area in which a shape has to lie + at least partly, to be accessible through this class. Used to + detect changes of the visible area after changes of the view forwarder. + */ + tools::Rectangle maVisibleArea; + + /** The parent of the shapes. It is used for creating accessible + objects for given shapes. + */ + css::uno::Reference<css::accessibility::XAccessible> mxParent; + + /** Bundle of information passed down the shape tree. + */ + AccessibleShapeTreeInfo maShapeTreeInfo; + + /** Reference to an accessible context object that is used to inform its + listeners of new and removed children. + */ + AccessibleContextBase& mrContext; + + /** This method is called from the component helper base class while + disposing. + */ + virtual void disposing(std::unique_lock<std::mutex>&) override; + + void impl_dispose(); + + ChildrenManagerImpl (const ChildrenManagerImpl&) = delete; + ChildrenManagerImpl& operator= (const ChildrenManagerImpl&) = delete; + + /** This member points to the currently focused shape. It is NULL when + there is no focused shape. + */ + AccessibleShape* mpFocusedShape; + + /** Three helper functions for the <member>Update</member> method. + */ + + /** Create a list of visible shapes from the list of UNO shapes + <member>maShapeList</member> and the list of accessible objects. + @param raChildList + For every visible shape from the two sources mentioned above one + descriptor is added to this list. + */ + void CreateListOfVisibleShapes (ChildDescriptorListType& raChildList); + + /** From the old list of (former) visible shapes remove those that + are not member of the new list. Send appropriate events for every + such shape. + @param raNewChildList + The new list of visible children against which the old one + is compared. + @param raOldChildList + The old list of visible children against which the new one + is compared. + */ + void RemoveNonVisibleChildren ( + const std::vector<ChildDescriptor*>& rNonVisibleChildren); + + /** Merge the information that is already known about the visible shapes + from the old list into the current list, and return a list of + children that are in the old list, but not the current one. + @param raChildList + Information is merged to the current list of visible children + from this list. The old list can get reordered. + @return + Vector of children that are in the old list, but not the current + one. + */ + std::vector<ChildDescriptor*> MergeAccessibilityInformation (ChildDescriptorListType& raChildList); + + /** If the visible area has changed then send events that signal a + change of their bounding boxes for all shapes that are members of + both the current and the new list of visible shapes. + @param raChildList + Events are sent to all entries of this list that already contain + an accessible object. + */ + static void SendVisibleAreaEvents (ChildDescriptorListType& raChildList); + + /** If children have to be created immediately and not on demand the + create the missing accessible objects now. + @param raDescriptorList + Create an accessible object for every member of this list where + that object does not already exist. + */ + void CreateAccessibilityObjects (ChildDescriptorListType& raChildList); + + /** Add a single shape. Update all relevant data structures + accordingly. Use this method instead of <member>Update()</member> + when only a single shape has been added. + */ + void AddShape (const css::uno::Reference<css::drawing::XShape>& xShape); + + /** Remove a single shape. Update all relevant data structures + accordingly. Use this method instead of <member>Update()</member> + when only a single shape has been removed. + */ + void RemoveShape (const css::uno::Reference<css::drawing::XShape>& xShape); + + /** Add the children manager as dispose listener at the given shape so + that the associated accessible object can be disposed when the shape + is disposed. + @param xShape + Register at this shape as dispose listener. + */ + void RegisterAsDisposeListener (const css::uno::Reference<css::drawing::XShape>& xShape); + + /** Remove the children manager as dispose listener at the given shape + @param xShape + Unregister at this shape as dispose listener. + */ + void UnregisterAsDisposeListener (const css::uno::Reference<css::drawing::XShape>& xShape); +}; + + +/** A child descriptor holds a reference to a UNO shape and the + corresponding accessible object. There are two use cases: + <ol><li>The accessible object is only created on demand and is then + initially empty.</li> + <li>There is no UNO shape. The accessible object is given as argument + to the constructor.</li> + </ol> + In both cases the child descriptor assumes ownership over the accessible + object. +*/ +class ChildDescriptor +{ +public: + /** Reference to a (partially) visible shape. + */ + css::uno::Reference<css::drawing::XShape> mxShape; + + /** The corresponding accessible object. This reference is initially + empty and only replaced by a reference to a new object when that is + requested from the outside. + */ + rtl::Reference<AccessibleShape> mxAccessibleShape; + + /** Return a pointer to the implementation object of the accessible + shape of this descriptor. + @return + The result is NULL if either the UNO reference to the accessible + shape is empty or it can not be transformed into a pointer to + the desired class. + */ + AccessibleShape* GetAccessibleShape() const { return mxAccessibleShape.get(); } + + /** set the index _nIndex at the accessible shape + @param _nIndex + The new index in parent. + */ + void setIndexAtAccessibleShape(sal_Int32 _nIndex); + + /** This flag is set during the visibility calculation and indicates + that at one time in this process an event is sent that informs the + listeners of the creation of a new accessible object. This flags is + not reset afterwards. Don't use it unless you know exactly what you + are doing. + */ + bool mbCreateEventPending; + + /** Create a new descriptor for the specified shape with empty reference + to accessible object. + */ + explicit ChildDescriptor (const css::uno::Reference<css::drawing::XShape>& xShape); + + /** Create a new descriptor for the specified shape with empty reference + to the original shape. + */ + explicit ChildDescriptor (const rtl::Reference<AccessibleShape>& rxAccessibleShape); + + /** Dispose the accessible object of this descriptor. If that object + does not exist then do nothing. + @param rParent + The parent of the accessible object to dispose. A child event + is sent in its name. + */ + void disposeAccessibleObject (AccessibleContextBase& rParent); + + /** Compare two child descriptors. Take into account that a child + descriptor may be based on a UNO shape or, already, on an accessible + shape. + */ + bool operator == (const ChildDescriptor& aDescriptor) const + { + return ( + this == &aDescriptor || + ( + (mxShape.get() == aDescriptor.mxShape.get() ) && + (mxShape.is() || mxAccessibleShape.get() == aDescriptor.mxAccessibleShape.get()) + ) + ); + } + +}; + + +} // end of namespace accessibility + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/DescriptionGenerator.cxx b/svx/source/accessibility/DescriptionGenerator.cxx new file mode 100644 index 0000000000..ad133fa8f4 --- /dev/null +++ b/svx/source/accessibility/DescriptionGenerator.cxx @@ -0,0 +1,188 @@ +/* -*- 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 <DescriptionGenerator.hxx> +#include <com/sun/star/beans/PropertyState.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <utility> +#include <vcl/svapp.hxx> + +// Includes for string resources. +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> + +#include "lookupcolorname.hxx" + +using namespace ::com::sun::star; + +namespace accessibility +{ +DescriptionGenerator::DescriptionGenerator(uno::Reference<drawing::XShape> xShape) + : mxShape(std::move(xShape)) + , mxSet(mxShape, uno::UNO_QUERY) + , mbIsFirstProperty(true) +{ +} + +DescriptionGenerator::~DescriptionGenerator() {} + +void DescriptionGenerator::Initialize(TranslateId pResourceId) +{ + // Get the string from the resource for the specified id. + OUString sPrefix; + { + SolarMutexGuard aGuard; + sPrefix = SvxResId(pResourceId); + } + + // Forward the call with the resulting string. + Initialize(sPrefix); +} + +void DescriptionGenerator::Initialize(std::u16string_view sPrefix) +{ + msDescription = sPrefix; + if (!mxSet.is()) + return; + + { + SolarMutexGuard aGuard; + + msDescription.append(' '); + msDescription.append(SvxResId(RID_SVXSTR_A11Y_WITH)); + msDescription.append(' '); + + msDescription.append(SvxResId(RID_SVXSTR_A11Y_STYLE)); + msDescription.append('='); + } + + try + { + if (mxSet.is()) + { + uno::Any aValue = mxSet->getPropertyValue("Style"); + uno::Reference<container::XNamed> xStyle(aValue, uno::UNO_QUERY); + if (xStyle.is()) + msDescription.append(xStyle->getName()); + } + else + msDescription.append("<no style>"); + } + catch (const css::beans::UnknownPropertyException&) + { + msDescription.append("<unknown>"); + } +} + +OUString DescriptionGenerator::operator()() +{ + msDescription.append('.'); + return msDescription.makeStringAndClear(); +} + +void DescriptionGenerator::AddProperty(const OUString& sPropertyName, PropertyType aType) +{ + uno::Reference<beans::XPropertyState> xState(mxShape, uno::UNO_QUERY); + if (!xState.is() + || xState->getPropertyState(sPropertyName) == beans::PropertyState_DEFAULT_VALUE) + return; + + if (!mxSet.is()) + return; + + // Append a separator from previous Properties. + if (!mbIsFirstProperty) + msDescription.append(','); + else + { + SolarMutexGuard aGuard; + + msDescription.append(' '); + msDescription.append(SvxResId(RID_SVXSTR_A11Y_AND)); + msDescription.append(' '); + mbIsFirstProperty = false; + } + + // Delegate to type specific property handling. + switch (aType) + { + case PropertyType::Color: + AddColor(sPropertyName); + break; + case PropertyType::Integer: + AddInteger(sPropertyName); + break; + } +} + +void DescriptionGenerator::AppendString(std::u16string_view sString) +{ + msDescription.append(sString); +} + +/** Search for the given color in the global color table. If found append + its name to the description. Otherwise append its RGB tuple. +*/ +void DescriptionGenerator::AddColor(const OUString& sPropertyName) +{ + msDescription.append('='); + + try + { + tools::Long nValue(0); + if (mxSet.is()) + { + uno::Any aValue = mxSet->getPropertyValue(sPropertyName); + aValue >>= nValue; + } + + msDescription.append(lookUpColorName(nValue)); + } + catch (const css::beans::UnknownPropertyException&) + { + msDescription.append("<unknown>"); + } +} + +void DescriptionGenerator::AddInteger(const OUString& sPropertyName) +{ + msDescription.append('='); + + try + { + if (mxSet.is()) + { + uno::Any aValue = mxSet->getPropertyValue(sPropertyName); + tools::Long nValue = 0; + aValue >>= nValue; + msDescription.append(nValue); + } + } + catch (const css::beans::UnknownPropertyException&) + { + msDescription.append("<unknown>"); + } +} + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/GraphCtlAccessibleContext.cxx b/svx/source/accessibility/GraphCtlAccessibleContext.cxx new file mode 100644 index 0000000000..1287f7e9ea --- /dev/null +++ b/svx/source/accessibility/GraphCtlAccessibleContext.cxx @@ -0,0 +1,777 @@ +/* -*- 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 <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/IllegalAccessibleComponentStateException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <o3tl/safeint.hxx> +#include <osl/mutex.hxx> +#include <tools/gen.hxx> +#include <svtools/colorcfg.hxx> +#include <comphelper/accessibleeventnotifier.hxx> +#include <svx/sdrpaintwindow.hxx> + +#include <svx/ShapeTypeHandler.hxx> +#include <svx/AccessibleShapeInfo.hxx> +#include <GraphCtlAccessibleContext.hxx> +#include <svx/graphctl.hxx> +#include <svx/strings.hrc> +#include <svx/svdpage.hxx> +#include <svx/dialmgr.hxx> +#include <svx/sdrhittesthelper.hxx> + +// namespaces +using namespace ::cppu; +using namespace ::osl; +using namespace ::accessibility; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::accessibility; + +// internal +/** initialize this component and set default values */ +SvxGraphCtrlAccessibleContext::SvxGraphCtrlAccessibleContext( + GraphCtrl& rRepr ) : + + SvxGraphCtrlAccessibleContext_Base( m_aMutex ), + mpControl( &rRepr ), + mpModel (nullptr), + mpPage (nullptr), + mpView (nullptr), + mnClientId( 0 ), + mbDisposed( false ) +{ + if (mpControl != nullptr) + { + mpModel = mpControl->GetSdrModel(); + if (mpModel != nullptr) + mpPage = mpModel->GetPage( 0 ); + mpView = mpControl->GetSdrView(); + + if( mpModel == nullptr || mpPage == nullptr || mpView == nullptr ) + { + mbDisposed = true; + // Set all the pointers to NULL just in case they are used as + // a disposed flag. + mpModel = nullptr; + mpPage = nullptr; + mpView = nullptr; + } + } + + { + ::SolarMutexGuard aSolarGuard; + msName = SvxResId( RID_SVXSTR_GRAPHCTRL_ACC_NAME ); + msDescription = SvxResId( RID_SVXSTR_GRAPHCTRL_ACC_DESCRIPTION ); + } + + maTreeInfo.SetSdrView( mpView ); + maTreeInfo.SetWindow(mpControl->GetDrawingArea()->get_ref_device().GetOwnerWindow()); + maTreeInfo.SetViewForwarder( this ); +} + + +/** on destruction, this component is disposed and all dispose listeners + are called, except if this component was already disposed */ +SvxGraphCtrlAccessibleContext::~SvxGraphCtrlAccessibleContext() +{ + disposing(); +} + + +/** returns the XAccessible interface for a given SdrObject. + Multiple calls for the same SdrObject return the same XAccessible. +*/ +Reference< XAccessible > SvxGraphCtrlAccessibleContext::getAccessible( const SdrObject* pObj ) +{ + Reference<XAccessible> xAccessibleShape; + + if( pObj ) + { + // see if we already created an XAccessible for the given SdrObject + ShapesMapType::const_iterator iter = mxShapes.find( pObj ); + + if( iter != mxShapes.end() ) + { + // if we already have one, return it + xAccessibleShape = (*iter).second.get(); + } + else + { + // create a new one and remember in our internal map + Reference< XShape > xShape( Reference< XShape >::query( const_cast<SdrObject*>(pObj)->getUnoShape() ) ); + + css::uno::Reference<css::accessibility::XAccessible> xParent(getAccessibleParent()); + AccessibleShapeInfo aShapeInfo (xShape,xParent); + // Create accessible object that corresponds to the descriptor's shape. + rtl::Reference<AccessibleShape> pAcc(ShapeTypeHandler::Instance().CreateAccessibleObject( + aShapeInfo, maTreeInfo)); + xAccessibleShape = pAcc.get(); + if (pAcc.is()) + { + pAcc->Init (); + } + mxShapes[pObj] = pAcc; + + // Create event and inform listeners of the object creation. + CommitChange( AccessibleEventId::CHILD, Any( xAccessibleShape ), Any( Reference<XAccessible>() ) ); + } + } + + return xAccessibleShape; +} + +// XAccessible +Reference< XAccessibleContext > SAL_CALL SvxGraphCtrlAccessibleContext::getAccessibleContext() +{ + return this; +} + +// XAccessibleComponent +sal_Bool SAL_CALL SvxGraphCtrlAccessibleContext::containsPoint( const awt::Point& rPoint ) +{ + // no guard -> done in getSize() + awt::Size aSize (getSize()); + return (rPoint.X >= 0) + && (rPoint.X < aSize.Width) + && (rPoint.Y >= 0) + && (rPoint.Y < aSize.Height); +} + + +Reference< XAccessible > SAL_CALL SvxGraphCtrlAccessibleContext::getAccessibleAtPoint( const awt::Point& rPoint ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + Reference< XAccessible > xAccessible; + + if( !mpControl ) + { + throw DisposedException(); + } + + Point aPnt( rPoint.X, rPoint.Y ); + aPnt = mpControl->GetDrawingArea()->get_ref_device().PixelToLogic(aPnt); + + SdrObject* pObj = nullptr; + + if(mpView && mpView->GetSdrPageView()) + { + pObj = SdrObjListPrimitiveHit(*mpPage, aPnt, {1, 1}, *mpView->GetSdrPageView(), nullptr, false); + } + + if( pObj ) + xAccessible = getAccessible( pObj ); + + return xAccessible; +} + +awt::Rectangle SAL_CALL SvxGraphCtrlAccessibleContext::getBounds() +{ + const SolarMutexGuard aSolarGuard; + + if (nullptr == mpControl) + throw DisposedException(); + + const Point aOutPos; + const Size aOutSize( mpControl->GetOutputSizePixel() ); + awt::Rectangle aRet; + + aRet.X = aOutPos.X(); + aRet.Y = aOutPos.Y(); + aRet.Width = aOutSize.Width(); + aRet.Height = aOutSize.Height(); + + return aRet; +} + +awt::Point SAL_CALL SvxGraphCtrlAccessibleContext::getLocation() +{ + const SolarMutexGuard aSolarGuard; + + if (nullptr == mpControl) + throw DisposedException(); + + const awt::Rectangle aRect( getBounds() ); + awt::Point aRet; + + aRet.X = aRect.X; + aRet.Y = aRect.Y; + + return aRet; +} + +awt::Point SAL_CALL SvxGraphCtrlAccessibleContext::getLocationOnScreen() +{ + const SolarMutexGuard aSolarGuard; + + if (nullptr == mpControl) + throw DisposedException(); + + awt::Point aScreenLoc(0, 0); + + auto xParent(getAccessibleParent()); + if (xParent) + { + css::uno::Reference<css::accessibility::XAccessibleContext> xParentContext(xParent->getAccessibleContext()); + css::uno::Reference<css::accessibility::XAccessibleComponent> xParentComponent(xParentContext, css::uno::UNO_QUERY); + OSL_ENSURE( xParentComponent.is(), "ValueSetAcc::getLocationOnScreen: no parent component!" ); + if ( xParentComponent.is() ) + { + awt::Point aParentScreenLoc( xParentComponent->getLocationOnScreen() ); + awt::Point aOwnRelativeLoc( getLocation() ); + aScreenLoc.X = aParentScreenLoc.X + aOwnRelativeLoc.X; + aScreenLoc.Y = aParentScreenLoc.Y + aOwnRelativeLoc.Y; + } + } + + return aScreenLoc; +} + +awt::Size SAL_CALL SvxGraphCtrlAccessibleContext::getSize() +{ + const SolarMutexGuard aSolarGuard; + + if (nullptr == mpControl) + throw DisposedException(); + + const awt::Rectangle aRect( getBounds() ); + awt::Size aRet; + + aRet.Width = aRect.Width; + aRet.Height = aRect.Height; + + return aRet; +} + +// XAccessibleContext +sal_Int64 SAL_CALL SvxGraphCtrlAccessibleContext::getAccessibleChildCount() +{ + ::SolarMutexGuard aGuard; + + if( nullptr == mpPage ) + throw DisposedException(); + + return mpPage->GetObjCount(); +} + + +/** returns the SdrObject at index nIndex from the model of this graph */ +SdrObject* SvxGraphCtrlAccessibleContext::getSdrObject( sal_Int64 nIndex ) +{ + ::SolarMutexGuard aGuard; + + if( nullptr == mpPage ) + throw DisposedException(); + + if( (nIndex < 0) || ( o3tl::make_unsigned(nIndex) >= mpPage->GetObjCount() ) ) + throw lang::IndexOutOfBoundsException(); + + return mpPage->GetObj( nIndex ); +} + + +/** sends an AccessibleEventObject to all added XAccessibleEventListeners */ +void SvxGraphCtrlAccessibleContext::CommitChange ( + sal_Int16 nEventId, + const uno::Any& rNewValue, + const uno::Any& rOldValue) +{ + AccessibleEventObject aEvent ( + getXWeak(), + nEventId, + rNewValue, + rOldValue, -1); + + if (mnClientId) + comphelper::AccessibleEventNotifier::addEvent( mnClientId, aEvent ); +} + +Reference< XAccessible > SAL_CALL SvxGraphCtrlAccessibleContext::getAccessibleChild( sal_Int64 nIndex ) +{ + ::SolarMutexGuard aGuard; + + return getAccessible( getSdrObject( nIndex ) ); +} + +Reference< XAccessible > SAL_CALL SvxGraphCtrlAccessibleContext::getAccessibleParent() +{ + ::SolarMutexGuard aGuard; + + if( nullptr == mpControl ) + throw DisposedException(); + + return mpControl->GetDrawingArea()->get_accessible_parent(); +} + +sal_Int64 SAL_CALL SvxGraphCtrlAccessibleContext::getAccessibleIndexInParent() +{ + ::SolarMutexGuard aGuard; + // Use a simple but slow solution for now. Optimize later. + + // Iterate over all the parent's children and search for this object. + css::uno::Reference<css::accessibility::XAccessible> xParent(getAccessibleParent()); + if (xParent.is()) + { + Reference< XAccessibleContext > xParentContext( xParent->getAccessibleContext() ); + if( xParentContext.is() ) + { + sal_Int64 nChildCount = xParentContext->getAccessibleChildCount(); + for( sal_Int64 i = 0 ; i < nChildCount ; ++i ) + { + Reference< XAccessible > xChild( xParentContext->getAccessibleChild( i ) ); + if( xChild.is() ) + { + Reference< XAccessibleContext > xChildContext = xChild->getAccessibleContext(); + if( xChildContext == static_cast<XAccessibleContext*>(this) ) + return i; + } + } + } + } + + // Return -1 to indicate that this object's parent does not know about the + // object. + return -1; +} + + +sal_Int16 SAL_CALL SvxGraphCtrlAccessibleContext::getAccessibleRole() +{ + return AccessibleRole::PANEL; +} + + +OUString SAL_CALL SvxGraphCtrlAccessibleContext::getAccessibleDescription() +{ + ::SolarMutexGuard aGuard; + return msDescription; +} + + +OUString SAL_CALL SvxGraphCtrlAccessibleContext::getAccessibleName() +{ + ::SolarMutexGuard aGuard; + return msName; +} + + +/** Return empty reference to indicate that the relation set is not + supported. +*/ +Reference< XAccessibleRelationSet > SAL_CALL SvxGraphCtrlAccessibleContext::getAccessibleRelationSet() +{ + return Reference< XAccessibleRelationSet >(); +} + + +sal_Int64 SAL_CALL SvxGraphCtrlAccessibleContext::getAccessibleStateSet() +{ + ::SolarMutexGuard aGuard; + + sal_Int64 nStateSet = 0; + + if ( rBHelper.bDisposed || mbDisposed ) + { + nStateSet |= AccessibleStateType::DEFUNC; + } + else + { + nStateSet |= AccessibleStateType::FOCUSABLE; + if( mpControl->HasFocus() ) + nStateSet |= AccessibleStateType::FOCUSED; + nStateSet |= AccessibleStateType::OPAQUE; + nStateSet |= AccessibleStateType::SHOWING; + nStateSet |= AccessibleStateType::VISIBLE; + } + + return nStateSet; +} + + +lang::Locale SAL_CALL SvxGraphCtrlAccessibleContext::getLocale() +{ + ::SolarMutexGuard aGuard; + + css::uno::Reference<css::accessibility::XAccessible> xParent(getAccessibleParent()); + if (xParent.is()) + { + Reference< XAccessibleContext > xParentContext( xParent->getAccessibleContext() ); + if( xParentContext.is() ) + return xParentContext->getLocale(); + } + + // No parent. Therefore throw exception to indicate this cluelessness. + throw IllegalAccessibleComponentStateException(); +} + +// XAccessibleEventListener +void SAL_CALL SvxGraphCtrlAccessibleContext::addAccessibleEventListener( const Reference< XAccessibleEventListener >& xListener ) +{ + if (xListener.is()) + { + ::SolarMutexGuard aGuard; + if (!mnClientId) + mnClientId = comphelper::AccessibleEventNotifier::registerClient( ); + comphelper::AccessibleEventNotifier::addEventListener( mnClientId, xListener ); + } +} + + +void SAL_CALL SvxGraphCtrlAccessibleContext::removeAccessibleEventListener( const Reference< XAccessibleEventListener >& xListener ) +{ + if (!xListener.is()) + return; + + ::SolarMutexGuard aGuard; + + sal_Int32 nListenerCount = comphelper::AccessibleEventNotifier::removeEventListener( mnClientId, xListener ); + if ( !nListenerCount ) + { + // no listeners anymore + // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), + // and at least to us not firing any events anymore, in case somebody calls + // NotifyAccessibleEvent, again + comphelper::AccessibleEventNotifier::revokeClient( mnClientId ); + mnClientId = 0; + } +} + +void SAL_CALL SvxGraphCtrlAccessibleContext::grabFocus() +{ + ::SolarMutexGuard aGuard; + + if( nullptr == mpControl ) + throw DisposedException(); + + mpControl->GrabFocus(); +} + +sal_Int32 SAL_CALL SvxGraphCtrlAccessibleContext::getForeground() +{ + svtools::ColorConfig aColorConfig; + Color nColor = aColorConfig.GetColorValue( svtools::FONTCOLOR ).nColor; + return static_cast<sal_Int32>(nColor); +} + +sal_Int32 SAL_CALL SvxGraphCtrlAccessibleContext::getBackground() +{ + Color nColor = Application::GetSettings().GetStyleSettings().GetWindowColor(); + return static_cast<sal_Int32>(nColor); +} + +// XServiceInfo +OUString SAL_CALL SvxGraphCtrlAccessibleContext::getImplementationName() +{ + return "com.sun.star.comp.ui.SvxGraphCtrlAccessibleContext"; +} + +sal_Bool SAL_CALL SvxGraphCtrlAccessibleContext::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +Sequence< OUString > SAL_CALL SvxGraphCtrlAccessibleContext::getSupportedServiceNames() +{ + return { "com.sun.star.accessibility.Accessible", + "com.sun.star.accessibility.AccessibleContext", + "com.sun.star.drawing.AccessibleGraphControl" }; +} + +// XTypeProvider +Sequence<sal_Int8> SAL_CALL SvxGraphCtrlAccessibleContext::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XServiceName +OUString SvxGraphCtrlAccessibleContext::getServiceName() +{ + return "com.sun.star.accessibility.AccessibleContext"; +} + +// XAccessibleSelection +void SAL_CALL SvxGraphCtrlAccessibleContext::selectAccessibleChild( sal_Int64 nIndex ) +{ + ::SolarMutexGuard aGuard; + + if( nullptr == mpView ) + throw DisposedException(); + + if (nIndex < 0 || nIndex >= getAccessibleChildCount()) + throw lang::IndexOutOfBoundsException(); + + SdrObject* pObj = getSdrObject( nIndex ); + + if( pObj ) + mpView->MarkObj( pObj, mpView->GetSdrPageView()); +} + + +sal_Bool SAL_CALL SvxGraphCtrlAccessibleContext::isAccessibleChildSelected( sal_Int64 nIndex ) +{ + ::SolarMutexGuard aGuard; + + if( nullptr == mpView ) + throw DisposedException(); + + if (nIndex < 0 || nIndex >= getAccessibleChildCount()) + throw lang::IndexOutOfBoundsException(); + + return mpView->IsObjMarked( getSdrObject( nIndex ) ); +} + + +void SAL_CALL SvxGraphCtrlAccessibleContext::clearAccessibleSelection() +{ + ::SolarMutexGuard aGuard; + + if( nullptr == mpView ) + throw DisposedException(); + + mpView->UnmarkAllObj(); +} + + +void SAL_CALL SvxGraphCtrlAccessibleContext::selectAllAccessibleChildren() +{ + ::SolarMutexGuard aGuard; + + if( nullptr == mpView ) + throw DisposedException(); + + mpView->MarkAllObj(); +} + + +sal_Int64 SAL_CALL SvxGraphCtrlAccessibleContext::getSelectedAccessibleChildCount() +{ + ::SolarMutexGuard aGuard; + + if( nullptr == mpView ) + throw DisposedException(); + + const SdrMarkList& rList = mpView->GetMarkedObjectList(); + return static_cast<sal_Int64>(rList.GetMarkCount()); +} + + +Reference< XAccessible > SAL_CALL SvxGraphCtrlAccessibleContext::getSelectedAccessibleChild( sal_Int64 nIndex ) +{ + ::SolarMutexGuard aGuard; + + checkChildIndexOnSelection( nIndex ); + + Reference< XAccessible > xAccessible; + + const SdrMarkList& rList = mpView->GetMarkedObjectList(); + SdrObject* pObj = rList.GetMark(static_cast<size_t>(nIndex))->GetMarkedSdrObj(); + if( pObj ) + xAccessible = getAccessible( pObj ); + + return xAccessible; +} + + +void SAL_CALL SvxGraphCtrlAccessibleContext::deselectAccessibleChild( sal_Int64 nIndex ) +{ + ::SolarMutexGuard aGuard; + + checkChildIndexOnSelection( nIndex ); + + if( !mpView ) + return; + + const SdrMarkList& rList = mpView->GetMarkedObjectList(); + + SdrObject* pObj = getSdrObject( nIndex ); + if( !pObj ) + return; + + SdrMarkList aRefList( rList ); + + SdrPageView* pPV = mpView->GetSdrPageView(); + mpView->UnmarkAllObj( pPV ); + + const size_t nCount = aRefList.GetMarkCount(); + for( size_t nMark = 0; nMark < nCount; ++nMark ) + { + if( aRefList.GetMark(nMark)->GetMarkedSdrObj() != pObj ) + mpView->MarkObj( aRefList.GetMark(nMark)->GetMarkedSdrObj(), pPV ); + } +} + +// internals +void SvxGraphCtrlAccessibleContext::checkChildIndexOnSelection(sal_Int64 nIndex ) +{ + if( nIndex < 0 || nIndex >= getSelectedAccessibleChildCount() ) + throw lang::IndexOutOfBoundsException(); +} + + +/** Replace the model, page, and view pointers by the ones provided + (explicitly and implicitly). +*/ +void SvxGraphCtrlAccessibleContext::setModelAndView ( + SdrModel* pModel, + SdrView* pView) +{ + ::SolarMutexGuard aGuard; + + mpModel = pModel; + if (mpModel != nullptr) + mpPage = mpModel->GetPage( 0 ); + mpView = pView; + + if (mpModel == nullptr || mpPage == nullptr || mpView == nullptr) + { + mbDisposed = true; + + // Set all the pointers to NULL just in case they are used as + // a disposed flag. + mpModel = nullptr; + mpPage = nullptr; + mpView = nullptr; + } + + maTreeInfo.SetSdrView (mpView); +} + + +void SAL_CALL SvxGraphCtrlAccessibleContext::disposing() +{ + ::SolarMutexGuard aGuard; + + if( mbDisposed ) + return; + + mbDisposed = true; + + mpControl = nullptr; // object dies with representation + mpView = nullptr; + mpPage = nullptr; + + { + for (const auto& rEntry : mxShapes) + { + rtl::Reference<XAccessible> pAcc(rEntry.second); + Reference< XComponent > xComp( pAcc.get(), UNO_QUERY ); + if( xComp.is() ) + xComp->dispose(); + } + + mxShapes.clear(); + } + + // Send a disposing to all listeners. + if ( mnClientId ) + { + comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( mnClientId, *this ); + mnClientId = 0; + } +} + +void SvxGraphCtrlAccessibleContext::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) +{ + if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint) + { + const SdrHint* pSdrHint = static_cast<const SdrHint*>( &rHint ); + switch( pSdrHint->GetKind() ) + { + case SdrHintKind::ObjectChange: + { + ShapesMapType::iterator iter = mxShapes.find( pSdrHint->GetObject() ); + + if( iter != mxShapes.end() ) + { + // if we already have one, return it + rtl::Reference<AccessibleShape> pShape((*iter).second); + + if( pShape.is() ) + pShape->CommitChange( AccessibleEventId::VISIBLE_DATA_CHANGED, uno::Any(), uno::Any(), -1 ); + } + } + break; + + case SdrHintKind::ObjectInserted: + CommitChange( AccessibleEventId::CHILD, Any( getAccessible( pSdrHint->GetObject() ) ) , uno::Any()); + break; + case SdrHintKind::ObjectRemoved: + CommitChange( AccessibleEventId::CHILD, uno::Any(), Any( getAccessible( pSdrHint->GetObject() ) ) ); + break; + case SdrHintKind::ModelCleared: + dispose(); + break; + default: + break; + } + } + else + { + // Has our SdDrawDocument just died? + if(rHint.GetId() == SfxHintId::Dying) + { + dispose(); + } + } +} + +// IAccessibleViewforwarder +tools::Rectangle SvxGraphCtrlAccessibleContext::GetVisibleArea() const +{ + tools::Rectangle aVisArea; + + if( mpView && mpView->PaintWindowCount()) + { + SdrPaintWindow* pPaintWindow = mpView->GetPaintWindow(0); + aVisArea = pPaintWindow->GetVisibleArea(); + } + + return aVisArea; +} + +Point SvxGraphCtrlAccessibleContext::LogicToPixel (const Point& rPoint) const +{ + if( mpControl ) + { + return mpControl->GetDrawingArea()->get_ref_device().LogicToPixel (rPoint) + mpControl->GetPositionInDialog(); + } + else + { + return rPoint; + } +} + +Size SvxGraphCtrlAccessibleContext::LogicToPixel (const Size& rSize) const +{ + if( mpControl ) + return mpControl->GetDrawingArea()->get_ref_device().LogicToPixel(rSize); + else + return rSize; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/ShapeTypeHandler.cxx b/svx/source/accessibility/ShapeTypeHandler.cxx new file mode 100644 index 0000000000..1b169c761a --- /dev/null +++ b/svx/source/accessibility/ShapeTypeHandler.cxx @@ -0,0 +1,306 @@ +/* -*- 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 <svx/ShapeTypeHandler.hxx> +#include <svx/SvxShapeTypes.hxx> +#include <svx/AccessibleShapeInfo.hxx> +#include <vcl/svapp.hxx> +#include <svx/AccessibleShape.hxx> +#include <svx/dialmgr.hxx> + +#include <svx/svdoashp.hxx> + +#include <svx/strings.hrc> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +namespace accessibility { + +// Pointer to the shape type handler singleton. +ShapeTypeHandler* ShapeTypeHandler::instance = nullptr; + + +// Create an empty reference to an accessible object. +static rtl::Reference<AccessibleShape> + CreateEmptyShapeReference ( + const AccessibleShapeInfo& /*rShapeInfo*/, + const AccessibleShapeTreeInfo& /*rShapeTreeInfo*/, + ShapeTypeId /*nId*/) +{ + return nullptr; +} + + +ShapeTypeHandler& ShapeTypeHandler::Instance() +{ + // Using double check pattern to make sure that exactly one instance of + // the shape type handler is instantiated. + if (instance == nullptr) + { + SolarMutexGuard aGuard; + if (instance == nullptr) + { + // Create the single instance of the shape type handler. + instance = new ShapeTypeHandler; + + // Register the basic SVX shape types. + RegisterDrawShapeTypes (); + } + } + + return *instance; +} + + +/** The given service name is first transformed into a slot id that + identifies the place of the type descriptor. From that descriptor the + shape type id is returned. +*/ +ShapeTypeId ShapeTypeHandler::GetTypeId (const OUString& aServiceName) const +{ + tServiceNameToSlotId::const_iterator I (maServiceNameToSlotId.find (aServiceName)); + if (I != maServiceNameToSlotId.end()) + { + return maShapeTypeDescriptorList[I->second].mnShapeTypeId; + } + else + return -1; +} + + +/** Extract the specified shape's service name and forward the request to + the appropriate method. +*/ +ShapeTypeId ShapeTypeHandler::GetTypeId (const uno::Reference<drawing::XShape>& rxShape) const +{ + if (rxShape.is()) + return GetTypeId (rxShape->getShapeType()); + else + return -1; +} + + +/** This factory method determines the type descriptor for the type of the + given shape, then calls the descriptor's create function, and finally + initializes the new object. +*/ +rtl::Reference<AccessibleShape> + ShapeTypeHandler::CreateAccessibleObject ( + const AccessibleShapeInfo& rShapeInfo, + const AccessibleShapeTreeInfo& rShapeTreeInfo) const +{ + ShapeTypeId nSlotId (GetSlotId (rShapeInfo.mxShape)); + rtl::Reference<AccessibleShape> pShape( + maShapeTypeDescriptorList[nSlotId].maCreateFunction ( + rShapeInfo, + rShapeTreeInfo, + maShapeTypeDescriptorList[nSlotId].mnShapeTypeId)); + return pShape; +} + + +/** Create the single instance of this class and initialize its list of + type descriptors with an entry of an unknown type. +*/ +ShapeTypeHandler::ShapeTypeHandler() + : maShapeTypeDescriptorList (1) +{ + // Make sure that at least the UNKNOWN entry is present. + // Resize the list, if necessary, so that the new type can be inserted. + maShapeTypeDescriptorList[0].mnShapeTypeId = UNKNOWN_SHAPE_TYPE; + maShapeTypeDescriptorList[0].msServiceName = "UNKNOWN_SHAPE_TYPE"; + maShapeTypeDescriptorList[0].maCreateFunction = CreateEmptyShapeReference; + maServiceNameToSlotId[maShapeTypeDescriptorList[0].msServiceName] = 0; +} + + +ShapeTypeHandler::~ShapeTypeHandler() +{ + // Because this class is a singleton and the only instance, whose + // destructor has just been called, is pointed to from instance, + // we reset the static variable instance, so that further calls to + // getInstance do not return an undefined object but create a new + // singleton. + instance = nullptr; +} + + +void ShapeTypeHandler::AddShapeTypeList (int nDescriptorCount, + ShapeTypeDescriptor const aDescriptorList[]) +{ + SolarMutexGuard aGuard; + + // Determine first id of new type descriptor(s). + int nFirstId = maShapeTypeDescriptorList.size(); + + // Resize the list, if necessary, so that the types can be inserted. + maShapeTypeDescriptorList.resize (nFirstId + nDescriptorCount); + + for (int i=0; i<nDescriptorCount; i++) + { + // Fill Type descriptor. + maShapeTypeDescriptorList[nFirstId+i].mnShapeTypeId = aDescriptorList[i].mnShapeTypeId; + maShapeTypeDescriptorList[nFirstId+i].msServiceName = aDescriptorList[i].msServiceName; + maShapeTypeDescriptorList[nFirstId+i].maCreateFunction = aDescriptorList[i].maCreateFunction; + + // Update inverse mapping from service name to the descriptor's position. + maServiceNameToSlotId[aDescriptorList[i].msServiceName] = nFirstId+i; + } +} + + +tools::Long ShapeTypeHandler::GetSlotId (const OUString& aServiceName) const +{ + tServiceNameToSlotId::const_iterator I (maServiceNameToSlotId.find (aServiceName)); + if (I != maServiceNameToSlotId.end()) + return I->second; + else + return 0; +} + + +// Extract the given shape's service name and forward request to appropriate +// method. +tools::Long ShapeTypeHandler::GetSlotId (const uno::Reference<drawing::XShape>& rxShape) const +{ + if (rxShape.is()) + return GetSlotId (rxShape->getShapeType()); + else + return 0; +} + +/// get the accessible base name for an object +OUString ShapeTypeHandler::CreateAccessibleBaseName (const uno::Reference<drawing::XShape>& rxShape) +{ + TranslateId pResourceId; + OUString sName; + + switch (ShapeTypeHandler::Instance().GetTypeId (rxShape)) + { + // case DRAWING_3D_POLYGON: was removed in original code in + // AccessibleShape::CreateAccessibleBaseName. See issue 11190 for details. + // Id can be removed from SvxShapeTypes.hxx as well. + case DRAWING_3D_CUBE: + pResourceId = STR_ObjNameSingulCube3d; + break; + case DRAWING_3D_EXTRUDE: + pResourceId = STR_ObjNameSingulExtrude3d; + break; + case DRAWING_3D_LATHE: + pResourceId = STR_ObjNameSingulLathe3d; + break; + case DRAWING_3D_SCENE: + pResourceId = STR_ObjNameSingulScene3d; + break; + case DRAWING_3D_SPHERE: + pResourceId = STR_ObjNameSingulSphere3d; + break; + case DRAWING_CAPTION: + pResourceId = STR_ObjNameSingulCAPTION; + break; + case DRAWING_CLOSED_BEZIER: + pResourceId = STR_ObjNameSingulPATHFILL; + break; + case DRAWING_CLOSED_FREEHAND: + pResourceId = STR_ObjNameSingulFREEFILL; + break; + case DRAWING_CONNECTOR: + pResourceId = STR_ObjNameSingulEDGE; + break; + case DRAWING_CONTROL: + pResourceId = STR_ObjNameSingulUno; + break; + case DRAWING_ELLIPSE: + pResourceId = STR_ObjNameSingulCIRCE; + break; + case DRAWING_GROUP: + pResourceId = STR_ObjNameSingulGRUP; + break; + case DRAWING_LINE: + pResourceId = STR_ObjNameSingulLINE; + break; + case DRAWING_MEASURE: + pResourceId = STR_ObjNameSingulMEASURE; + break; + case DRAWING_OPEN_BEZIER: + pResourceId = STR_ObjNameSingulPATHLINE; + break; + case DRAWING_OPEN_FREEHAND: + pResourceId = STR_ObjNameSingulFREELINE; + break; + case DRAWING_PAGE: + pResourceId = STR_ObjNameSingulPAGE; + break; + case DRAWING_POLY_LINE: + pResourceId = STR_ObjNameSingulPLIN; + break; + case DRAWING_POLY_LINE_PATH: + pResourceId = STR_ObjNameSingulPLIN; + break; + case DRAWING_POLY_POLYGON: + pResourceId = STR_ObjNameSingulPOLY; + break; + case DRAWING_POLY_POLYGON_PATH: + pResourceId = STR_ObjNameSingulPOLY; + break; + case DRAWING_RECTANGLE: + pResourceId = STR_ObjNameSingulRECT; + break; + case DRAWING_CUSTOM: + pResourceId = STR_ObjNameSingulCUSTOMSHAPE; + + if (SdrObject* pSdrObject = SdrObject::getSdrObjectFromXShape(rxShape)) + { + if (auto pCustomShape = dynamic_cast<SdrObjCustomShape*>(pSdrObject)) + { + if (pCustomShape->IsTextPath()) + pResourceId = STR_ObjNameSingulFONTWORK; + else + { + pResourceId = {}; + sName = pCustomShape->GetCustomShapeName(); + } + } + } + break; + case DRAWING_TEXT: + pResourceId = STR_ObjNameSingulTEXT; + break; + default: + pResourceId = {}; + sName = "UnknownAccessibleShape"; + if (rxShape.is()) + sName += ": " + rxShape->getShapeType(); + break; + } + + if (pResourceId) + { + SolarMutexGuard aGuard; + sName = SvxResId(pResourceId); + } + + return sName; +} + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/SvxShapeTypes.cxx b/svx/source/accessibility/SvxShapeTypes.cxx new file mode 100644 index 0000000000..b02a153a4c --- /dev/null +++ b/svx/source/accessibility/SvxShapeTypes.cxx @@ -0,0 +1,165 @@ +/* -*- 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 <svx/SvxShapeTypes.hxx> +#include <svx/AccessibleShape.hxx> +#include <svx/AccessibleGraphicShape.hxx> +#include <svx/AccessibleOLEShape.hxx> +#include <svx/AccessibleControlShape.hxx> +#include <svx/ShapeTypeHandler.hxx> +#include <AccessibleTableShape.hxx> + +namespace accessibility { + +static rtl::Reference<AccessibleShape> CreateSvxAccessibleShape ( + const AccessibleShapeInfo& rShapeInfo, + const AccessibleShapeTreeInfo& rShapeTreeInfo, + ShapeTypeId nId) +{ + switch (nId) + { + case DRAWING_3D_CUBE: + case DRAWING_3D_EXTRUDE: + case DRAWING_3D_LATHE: + case DRAWING_3D_SCENE: + case DRAWING_3D_SPHERE: + case DRAWING_CAPTION: + case DRAWING_CLOSED_BEZIER: + case DRAWING_CLOSED_FREEHAND: + case DRAWING_CONNECTOR: + case DRAWING_ELLIPSE: + case DRAWING_GROUP: + case DRAWING_LINE: + case DRAWING_MEASURE: + case DRAWING_OPEN_BEZIER: + case DRAWING_OPEN_FREEHAND: + case DRAWING_PAGE: + case DRAWING_POLY_POLYGON: + case DRAWING_POLY_LINE: + case DRAWING_POLY_POLYGON_PATH: + case DRAWING_POLY_LINE_PATH: + case DRAWING_RECTANGLE: + case DRAWING_TEXT: + // Default accessibility shape for + // css::drawing::CustomShape (#i37790#) + case DRAWING_CUSTOM: + // Default accessibility shape for + // css::drawing::MediaShape (#i85429#) + case DRAWING_MEDIA: + return new AccessibleShape (rShapeInfo, rShapeTreeInfo); + + case DRAWING_CONTROL: + return new AccessibleControlShape (rShapeInfo, rShapeTreeInfo); + + case DRAWING_GRAPHIC_OBJECT: + return new AccessibleGraphicShape (rShapeInfo, rShapeTreeInfo); + + case DRAWING_APPLET: + case DRAWING_FRAME: + case DRAWING_OLE: + case DRAWING_PLUGIN: + return new AccessibleOLEShape (rShapeInfo, rShapeTreeInfo); + + case DRAWING_TABLE: + return new AccessibleTableShape( rShapeInfo, rShapeTreeInfo ); + + default: + return nullptr; + } +} + +void RegisterDrawShapeTypes() +{ + /** List of shape type descriptors corresponding to the + <type>SvxShapeTypes</type> enum. + */ + static ShapeTypeDescriptor const aSvxShapeTypeList[] = { + ShapeTypeDescriptor ( DRAWING_TEXT, "com.sun.star.drawing.TextShape", + CreateSvxAccessibleShape), + ShapeTypeDescriptor (DRAWING_RECTANGLE, "com.sun.star.drawing.RectangleShape", + CreateSvxAccessibleShape), + ShapeTypeDescriptor ( DRAWING_ELLIPSE, "com.sun.star.drawing.EllipseShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_CONTROL, "com.sun.star.drawing.ControlShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_CONNECTOR, "com.sun.star.drawing.ConnectorShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_MEASURE, "com.sun.star.drawing.MeasureShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_LINE, "com.sun.star.drawing.LineShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_POLY_POLYGON, "com.sun.star.drawing.PolyPolygonShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_POLY_LINE, "com.sun.star.drawing.PolyLineShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_OPEN_BEZIER, "com.sun.star.drawing.OpenBezierShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_CLOSED_BEZIER, "com.sun.star.drawing.ClosedBezierShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_OPEN_FREEHAND, "com.sun.star.drawing.OpenFreeHandShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_CLOSED_FREEHAND, "com.sun.star.drawing.ClosedFreeHandShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_POLY_POLYGON_PATH, "com.sun.star.drawing.PolyPolygonPathShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_POLY_LINE_PATH, "com.sun.star.drawing.PolyLinePathShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_GRAPHIC_OBJECT, "com.sun.star.drawing.GraphicObjectShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_GROUP, "com.sun.star.drawing.GroupShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_OLE, "com.sun.star.drawing.OLE2Shape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_PAGE, "com.sun.star.drawing.PageShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_CAPTION, "com.sun.star.drawing.CaptionShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_FRAME, "com.sun.star.drawing.FrameShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_PLUGIN, "com.sun.star.drawing.PluginShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_APPLET, "com.sun.star.drawing.AppletShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_3D_SCENE, "com.sun.star.drawing.Shape3DSceneObject", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_3D_CUBE, "com.sun.star.drawing.Shape3DCubeObject", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_3D_SPHERE, "com.sun.star.drawing.Shape3DSphereObject", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_3D_LATHE, "com.sun.star.drawing.Shape3DLatheObject", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_3D_EXTRUDE, "com.sun.star.drawing.Shape3DExtrudeObject", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_CUSTOM, "com.sun.star.drawing.CustomShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_TABLE, "com.sun.star.drawing.TableShape", + CreateSvxAccessibleShape ), + ShapeTypeDescriptor ( DRAWING_MEDIA, "com.sun.star.drawing.MediaShape", + CreateSvxAccessibleShape ), + + }; + + // Crash while inserting callout with activated accessibility (#i37790#) + ShapeTypeHandler::Instance().AddShapeTypeList ( DRAWING_END, aSvxShapeTypeList); +} + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/charmapacc.cxx b/svx/source/accessibility/charmapacc.cxx new file mode 100644 index 0000000000..b5a0544d75 --- /dev/null +++ b/svx/source/accessibility/charmapacc.cxx @@ -0,0 +1,586 @@ +/* -*- 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 <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <stdio.h> +#include <svx/charmap.hxx> +#include <charmapacc.hxx> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <o3tl/temporary.hxx> +#include <osl/interlck.h> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <comphelper/accessiblecontexthelper.hxx> +#include <comphelper/types.hxx> + +namespace svx +{ + using namespace comphelper; + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::accessibility; + +SvxShowCharSetItem::SvxShowCharSetItem( SvxShowCharSet& rParent,SvxShowCharSetAcc* _pParent,sal_uInt16 _nPos ) : + mrParent( rParent ) + ,mnId( _nPos ) + ,m_pParent(_pParent) +{ +} + +SvxShowCharSetItem::~SvxShowCharSetItem() +{ + if ( m_xItem.is() ) + { + m_xItem->ParentDestroyed(); + m_xItem.clear(); + } +} + +rtl::Reference<SvxShowCharSetItemAcc> SvxShowCharSetItem::GetAccessible() +{ + if( !m_xItem.is() ) + { + m_xItem = new SvxShowCharSetItemAcc( this ); + } + + return m_xItem; +} + +SvxShowCharSetAcc::SvxShowCharSetAcc(SvxShowCharSet* pParent) + : m_pParent(pParent) +{ + osl_atomic_increment(&m_refCount); + { + lateInit(this); + } + osl_atomic_decrement(&m_refCount); +} + +SvxShowCharSetAcc::~SvxShowCharSetAcc() +{ + ensureDisposed(); +} + +void SAL_CALL SvxShowCharSetAcc::disposing() +{ + OAccessibleSelectionHelper::disposing(); + for (auto& rxChild : m_aChildren) + rxChild->dispose(); + + m_aChildren.clear(); + m_pParent = nullptr; +} + +bool SvxShowCharSetAcc::implIsSelected( sal_Int64 nAccessibleChildIndex ) +{ + if (!m_pParent) + return false; + + if (nAccessibleChildIndex < 0 || nAccessibleChildIndex >= getAccessibleChildCount()) + throw IndexOutOfBoundsException(); + + return m_pParent->IsSelected(sal::static_int_cast<sal_uInt16>(nAccessibleChildIndex)); +} + +// select the specified child => watch for special ChildIndexes (ACCESSIBLE_SELECTION_CHILD_xxx) +void SvxShowCharSetAcc::implSelect(sal_Int64 nAccessibleChildIndex, bool bSelect) +{ + if (!m_pParent) + return; + + if (nAccessibleChildIndex < 0 || nAccessibleChildIndex >= getAccessibleChildCount()) + throw IndexOutOfBoundsException(); + + if (bSelect) + m_pParent->SelectIndex(nAccessibleChildIndex, true); + else + m_pParent->DeSelect(); +} + +css::awt::Rectangle SvxShowCharSetAcc::implGetBounds() +{ + awt::Rectangle aRet; + + if (m_pParent) + { + const Point aOutPos;//( m_pParent->GetPosPixel() ); + Size aOutSize( m_pParent->GetOutputSizePixel()); + + aRet.X = aOutPos.X(); + aRet.Y = aOutPos.Y(); + aRet.Width = aOutSize.Width(); + aRet.Height = aOutSize.Height(); + } + + return aRet; +} + +sal_Int64 SAL_CALL SvxShowCharSetAcc::getAccessibleChildCount() +{ + OExternalLockGuard aGuard( this ); + + return m_pParent->getMaxCharCount(); +} + +uno::Reference< css::accessibility::XAccessible > SAL_CALL SvxShowCharSetAcc::getAccessibleChild( sal_Int64 i ) +{ + OExternalLockGuard aGuard( this ); + + rtl::Reference< SvxShowCharSetItemAcc > xRet; + SvxShowCharSetItem* pItem = m_pParent->ImplGetItem( static_cast< sal_uInt16 >( i ) ); + + if( !pItem ) + throw lang::IndexOutOfBoundsException(); + + pItem->m_pParent = this; + xRet = pItem->GetAccessible(); + m_aChildren.push_back(xRet); + + return xRet; +} + +uno::Reference< css::accessibility::XAccessible > SAL_CALL SvxShowCharSetAcc::getAccessibleParent() +{ + OExternalLockGuard aGuard( this ); + + if (m_pParent) + return m_pParent->getAccessibleParent(); + return uno::Reference<css::accessibility::XAccessible>(); +} + +sal_Int16 SAL_CALL SvxShowCharSetAcc::getAccessibleRole() +{ + return css::accessibility::AccessibleRole::TABLE; +} + +OUString SAL_CALL SvxShowCharSetAcc::getAccessibleDescription() +{ + OExternalLockGuard aGuard( this ); + return SvxResId( RID_SVXSTR_CHARACTER_SELECTION ); +} + + +OUString SAL_CALL SvxShowCharSetAcc::getAccessibleName() +{ + OExternalLockGuard aGuard( this ); + + return SvxResId( RID_SVXSTR_CHAR_SEL_DESC ); +} + + +uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL SvxShowCharSetAcc::getAccessibleRelationSet() +{ + return uno::Reference< css::accessibility::XAccessibleRelationSet >(); +} + + +sal_Int64 SAL_CALL SvxShowCharSetAcc::getAccessibleStateSet() +{ + OExternalLockGuard aGuard( this ); + + sal_Int64 nStateSet = 0; + + if (m_pParent) + { + // SELECTABLE + nStateSet |= AccessibleStateType::FOCUSABLE; + if (m_pParent->HasFocus()) + { + nStateSet |= AccessibleStateType::FOCUSED; + nStateSet |= AccessibleStateType::ACTIVE; + } + if (m_pParent->IsEnabled()) + { + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::SENSITIVE; + } + if (m_pParent->IsVisible()) + nStateSet |= AccessibleStateType::VISIBLE; + + nStateSet |= AccessibleStateType::MANAGES_DESCENDANTS; + } + + return nStateSet; +} + + +uno::Reference< css::accessibility::XAccessible > SAL_CALL SvxShowCharSetAcc::getAccessibleAtPoint( const awt::Point& aPoint ) +{ + OExternalLockGuard aGuard( this ); + + uno::Reference< css::accessibility::XAccessible > xRet; + const sal_uInt16 nItemId = sal::static_int_cast<sal_uInt16>( + m_pParent->PixelToMapIndex( Point( aPoint.X, aPoint.Y ) )); + + if( sal_uInt16(-1) != nItemId ) + { + SvxShowCharSetItem* pItem = m_pParent->ImplGetItem( nItemId ); + xRet = pItem->GetAccessible(); + } + return xRet; +} + +void SAL_CALL SvxShowCharSetAcc::grabFocus() +{ + OExternalLockGuard aGuard( this ); + + m_pParent->GrabFocus(); +} + +sal_Int32 SAL_CALL SvxShowCharSetAcc::getAccessibleRowCount( ) +{ + return ((getAccessibleChildCount()-1) / COLUMN_COUNT) + 1; +} + +sal_Int32 SAL_CALL SvxShowCharSetAcc::getAccessibleColumnCount( ) +{ + return COLUMN_COUNT; +} + +OUString SAL_CALL SvxShowCharSetAcc::getAccessibleRowDescription( sal_Int32 /*nRow*/ ) +{ + return OUString(); +} + +OUString SAL_CALL SvxShowCharSetAcc::getAccessibleColumnDescription( sal_Int32 /*nColumn*/ ) +{ + return OUString(); +} + +sal_Int32 SAL_CALL SvxShowCharSetAcc::getAccessibleRowExtentAt( sal_Int32 /*nRow*/, sal_Int32 /*nColumn*/ ) +{ + return 1; +} + +sal_Int32 SAL_CALL SvxShowCharSetAcc::getAccessibleColumnExtentAt( sal_Int32 /*nRow*/, sal_Int32 /*nColumn*/ ) +{ + return 1; +} + +Reference< XAccessibleTable > SAL_CALL SvxShowCharSetAcc::getAccessibleRowHeaders( ) +{ + return Reference< XAccessibleTable >(); +} + +Reference< XAccessibleTable > SAL_CALL SvxShowCharSetAcc::getAccessibleColumnHeaders( ) +{ + return Reference< XAccessibleTable >(); +} + +Sequence< sal_Int32 > SAL_CALL SvxShowCharSetAcc::getSelectedAccessibleRows( ) +{ + OExternalLockGuard aGuard( this ); + + return { SvxShowCharSet::GetRowPos(m_pParent->GetSelectIndexId()) }; +} + +Sequence< sal_Int32 > SAL_CALL SvxShowCharSetAcc::getSelectedAccessibleColumns( ) +{ + OExternalLockGuard aGuard( this ); + + return { SvxShowCharSet::GetColumnPos(m_pParent->GetSelectIndexId()) }; +} + +sal_Bool SAL_CALL SvxShowCharSetAcc::isAccessibleRowSelected( sal_Int32 nRow ) +{ + OExternalLockGuard aGuard( this ); + + return SvxShowCharSet::GetRowPos(m_pParent->GetSelectIndexId()) == nRow; +} + +sal_Bool SAL_CALL SvxShowCharSetAcc::isAccessibleColumnSelected( sal_Int32 nColumn ) +{ + OExternalLockGuard aGuard( this ); + ensureAlive(); + return SvxShowCharSet::GetColumnPos(m_pParent->GetSelectIndexId()) == nColumn; +} + +Reference< XAccessible > SAL_CALL SvxShowCharSetAcc::getAccessibleCellAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + OExternalLockGuard aGuard( this ); + + svx::SvxShowCharSetItem* pItem = m_pParent->ImplGetItem( + sal::static_int_cast<sal_uInt16>(getAccessibleIndex(nRow,nColumn) )); + if ( !pItem ) + throw IndexOutOfBoundsException(); + return pItem->GetAccessible(); +} + +Reference< XAccessible > SAL_CALL SvxShowCharSetAcc::getAccessibleCaption( ) +{ + return Reference< XAccessible >(); +} + +Reference< XAccessible > SAL_CALL SvxShowCharSetAcc::getAccessibleSummary( ) +{ + return Reference< XAccessible >(); +} + +sal_Bool SAL_CALL SvxShowCharSetAcc::isAccessibleSelected( sal_Int32 nRow, sal_Int32 nColumn ) +{ + OExternalLockGuard aGuard( this ); + + return m_pParent->GetSelectIndexId() == getAccessibleIndex(nRow,nColumn); +} + +sal_Int64 SAL_CALL SvxShowCharSetAcc::getAccessibleIndex( sal_Int32 nRow, sal_Int32 nColumn ) +{ + return (static_cast<sal_Int64>(nRow) * COLUMN_COUNT) + nColumn; +} + +sal_Int32 SAL_CALL SvxShowCharSetAcc::getAccessibleRow( sal_Int64 nChildIndex ) +{ + OExternalLockGuard aGuard( this ); + + return SvxShowCharSet::GetRowPos(sal::static_int_cast<sal_uInt16>(nChildIndex)); +} + +sal_Int32 SAL_CALL SvxShowCharSetAcc::getAccessibleColumn( sal_Int64 nChildIndex ) +{ + OExternalLockGuard aGuard( this ); + + return SvxShowCharSet::GetColumnPos(sal::static_int_cast<sal_uInt16>(nChildIndex)); +} + + +SvxShowCharSetItemAcc::SvxShowCharSetItemAcc( SvxShowCharSetItem* pParent ) : mpParent( pParent ) +{ + OSL_ENSURE(pParent,"NO parent supplied!"); + osl_atomic_increment(&m_refCount); + { // #b6211265 # + lateInit(this); + } + osl_atomic_decrement(&m_refCount); +} + + +SvxShowCharSetItemAcc::~SvxShowCharSetItemAcc() +{ + ensureDisposed(); +} + +void SvxShowCharSetItemAcc::ParentDestroyed() +{ + const ::osl::MutexGuard aGuard( GetMutex() ); + mpParent = nullptr; +} + +sal_Int64 SAL_CALL SvxShowCharSetItemAcc::getAccessibleChildCount() +{ + return 0; +} + + +uno::Reference< css::accessibility::XAccessible > SAL_CALL SvxShowCharSetItemAcc::getAccessibleChild( sal_Int64 /*i*/ ) +{ + throw lang::IndexOutOfBoundsException(); +} + + +uno::Reference< css::accessibility::XAccessible > SAL_CALL SvxShowCharSetItemAcc::getAccessibleParent() +{ + OExternalLockGuard aGuard( this ); + + return mpParent->m_pParent; +} + + +sal_Int16 SAL_CALL SvxShowCharSetItemAcc::getAccessibleRole() +{ + return css::accessibility::AccessibleRole::TABLE_CELL; +} + + +OUString SAL_CALL SvxShowCharSetItemAcc::getAccessibleDescription() +{ + OExternalLockGuard aGuard( this ); + + OUString sDescription; + + const OUString aCharStr( mpParent->maText); + const sal_UCS4 c = aCharStr.iterateCodePoints( &o3tl::temporary(sal_Int32(0)) ); + const int tmp_len = (c < 0x10000) ? 4 : 6; + char buf[16] = "0x0000"; + sal_UCS4 c_Shifted = c; + for( int i = 0; i < tmp_len; ++i ) + { + char h = static_cast<char>(c_Shifted & 0x0F); + buf[tmp_len+1-i] = (h > 9) ? (h - 10 + 'A') : (h + '0'); + c_Shifted >>= 4; + } + if( c < 256 ) + snprintf( buf+6, 10, " (%" SAL_PRIuUINT32 ")", c ); + + sDescription = SvxResId( RID_SVXSTR_CHARACTER_CODE ) + + " " + + OUString(buf, strlen(buf), RTL_TEXTENCODING_ASCII_US); + + return sDescription; +} + + +OUString SAL_CALL SvxShowCharSetItemAcc::getAccessibleName() +{ + OExternalLockGuard aGuard( this ); + + OUString aRet; + + if( mpParent ) + { + aRet = mpParent->maText; + + if (aRet.isEmpty()) + aRet = getAccessibleDescription(); + } + + return aRet; +} + + +uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL SvxShowCharSetItemAcc::getAccessibleRelationSet() +{ + return uno::Reference< css::accessibility::XAccessibleRelationSet >(); +} + + +sal_Int64 SAL_CALL SvxShowCharSetItemAcc::getAccessibleStateSet() +{ + OExternalLockGuard aGuard( this ); + + sal_Int64 nStateSet = 0; + + if( mpParent ) + { + if (mpParent->mrParent.IsEnabled()) + { + nStateSet |= css::accessibility::AccessibleStateType::ENABLED; + // SELECTABLE + nStateSet |= css::accessibility::AccessibleStateType::SELECTABLE; + nStateSet |= css::accessibility::AccessibleStateType::FOCUSABLE; + } + + // SELECTED + if( mpParent->mrParent.GetSelectIndexId() == mpParent->mnId ) + { + nStateSet |= css::accessibility::AccessibleStateType::SELECTED; + if (mpParent->mrParent.HasChildFocus()) + nStateSet |= css::accessibility::AccessibleStateType::FOCUSED; + } + if ( mpParent->mnId >= mpParent->mrParent.FirstInView() && mpParent->mnId <= mpParent->mrParent.LastInView() ) + { + nStateSet |= AccessibleStateType::VISIBLE; + nStateSet |= AccessibleStateType::SHOWING; + } + nStateSet |= AccessibleStateType::TRANSIENT; + } + + return nStateSet; +} + + +sal_Int32 SvxShowCharSetItemAcc::getAccessibleActionCount() +{ + return 1; +} + + +sal_Bool SvxShowCharSetItemAcc::doAccessibleAction ( sal_Int32 nIndex ) +{ + OExternalLockGuard aGuard( this ); + + if( nIndex == 0 ) + { + mpParent->mrParent.OutputIndex( mpParent->mnId ); + return true; + } + throw IndexOutOfBoundsException(); +} + + +OUString SvxShowCharSetItemAcc::getAccessibleActionDescription ( sal_Int32 nIndex ) +{ + if( nIndex == 0 ) + return "press"; + throw IndexOutOfBoundsException(); +} + + +Reference< css::accessibility::XAccessibleKeyBinding > SvxShowCharSetItemAcc::getAccessibleActionKeyBinding( sal_Int32 nIndex ) +{ + if( nIndex == 0 ) + return Reference< css::accessibility::XAccessibleKeyBinding >(); + throw IndexOutOfBoundsException(); +} + + +void SAL_CALL SvxShowCharSetItemAcc::grabFocus() +{ + // nothing to do +} + +awt::Rectangle SvxShowCharSetItemAcc::implGetBounds( ) +{ + awt::Rectangle aRet; + + if( mpParent ) + { + tools::Rectangle aRect( mpParent->maRect ); + tools::Rectangle aParentRect(Point(), mpParent->mrParent.GetOutputSizePixel()); + + aRect.Intersection( aParentRect ); + + aRet.X = aRect.Left(); + aRet.Y = aRect.Top(); + aRet.Width = aRect.GetWidth(); + aRet.Height = aRect.GetHeight(); + } + + return aRet; +} + +uno::Reference< css::accessibility::XAccessible > SAL_CALL SvxShowCharSetItemAcc::getAccessibleAtPoint( const awt::Point& /*aPoint*/ ) +{ + return uno::Reference< css::accessibility::XAccessible >(); +} + +sal_Int32 SAL_CALL SvxShowCharSetAcc::getForeground( ) +{ + OExternalLockGuard aGuard( this ); + + //see SvxShowCharSet::InitSettings + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + return static_cast<sal_Int32>(rStyleSettings.GetDialogTextColor()); +} + +sal_Int32 SAL_CALL SvxShowCharSetAcc::getBackground( ) +{ + OExternalLockGuard aGuard( this ); + + //see SvxShowCharSet::InitSettings + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + return static_cast<sal_Int32>(rStyleSettings.GetWindowColor()); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/lookupcolorname.cxx b/svx/source/accessibility/lookupcolorname.cxx new file mode 100644 index 0000000000..373dae4693 --- /dev/null +++ b/svx/source/accessibility/lookupcolorname.cxx @@ -0,0 +1,121 @@ +/* -*- 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 <sal/config.h> + +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/drawing/ColorTable.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <comphelper/processfactory.hxx> +#include <rtl/ustring.hxx> +#include <vcl/svapp.hxx> + +#include "lookupcolorname.hxx" +#include <unordered_map> + +namespace +{ +class ColorNameMap +{ +public: + ColorNameMap(); + ColorNameMap(const ColorNameMap&) = delete; + ColorNameMap& operator=(const ColorNameMap&) = delete; + + OUString lookUp(tools::Long color) const; + +private: + typedef std::unordered_map<tools::Long, OUString> Map; + + Map map_; +}; + +ColorNameMap::ColorNameMap() +{ + css::uno::Sequence<OUString> aNames; + css::uno::Reference<css::container::XNameAccess> xNA; + + try + { + // Create color table in which to look up the given color. + css::uno::Reference<css::container::XNameContainer> xColorTable + = css::drawing::ColorTable::create(comphelper::getProcessComponentContext()); + + // Get list of color names in order to iterate over the color table. + + // Lock the solar mutex here as workaround for missing lock in + // called function. + SolarMutexGuard aGuard; + xNA = xColorTable; + aNames = xColorTable->getElementNames(); + } + catch (css::uno::RuntimeException const&) + { + // When an exception occurred then we have an empty name sequence + // and the loop below is not entered. + } + + // Fill the map to convert from numerical color values to names. + if (!xNA.is()) + return; + + for (const auto& rName : std::as_const(aNames)) + { + // Get the numerical value for the i-th color name. + try + { + css::uno::Any aColor = xNA->getByName(rName); + tools::Long nColor = 0; + aColor >>= nColor; + map_[nColor] = rName; + } + catch (css::uno::RuntimeException const&) + { + // Ignore the exception: the color who lead to the exception + // is not included into the map. + } + } +} + +OUString ColorNameMap::lookUp(tools::Long color) const +{ + Map::const_iterator i(map_.find(color)); + if (i != map_.end()) + { + return i->second; + } + // Did not find the given color; return its RGB tuple representation: + return "#" + OUString::number(color, 16); +} +} + +namespace accessibility +{ +OUString lookUpColorName(tools::Long color) +{ + static ColorNameMap theColorNameMap; + return theColorNameMap.lookUp(color); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/lookupcolorname.hxx b/svx/source/accessibility/lookupcolorname.hxx new file mode 100644 index 0000000000..0d752c0fab --- /dev/null +++ b/svx/source/accessibility/lookupcolorname.hxx @@ -0,0 +1,57 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_ACCESSIBILITY_LOOKUPCOLORNAME_HXX +#define INCLUDED_SVX_SOURCE_ACCESSIBILITY_LOOKUPCOLORNAME_HXX + +#include <sal/config.h> + +#include <rtl/ustring.hxx> +#include <tools/long.hxx> + +namespace accessibility +{ +/** This is a color name lookup targeted to be used by the accessibility + <type>DescriptionGenerator</type> class. It encapsulates a + <type>com.sun.star.drawing.ColorTable</type> and provides an inverse look + up of color names for given numerical color descriptions (the RGB values + encoded as an integer). + + <p>The implementation uses as singleton so that the + <type>com.sun.star.drawing.ColorTable</type> object needs to be created + only once. That singleton instance for now lives until the application + terminates. However, the color table from which it takes its values may + change during this time. Reacting to these changes remains a task for the + future.</p> + + @param nColor + This integer is the sum of the 8 Bit red value shifted left 16 Bits, the + green value shifted left 8 Bits, and the unshifted blue value. + + @return + The returned string is either the color name of the specified color or, + when no name exists, a string of the form "#RRGGBB" with two hexadecimal + digits for each color component. +*/ +OUString lookUpColorName(tools::Long color); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/svxpixelctlaccessiblecontext.cxx b/svx/source/accessibility/svxpixelctlaccessiblecontext.cxx new file mode 100644 index 0000000000..8c0e0ca1c0 --- /dev/null +++ b/svx/source/accessibility/svxpixelctlaccessiblecontext.cxx @@ -0,0 +1,444 @@ +/* -*- 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 <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <toolkit/helper/convert.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <osl/mutex.hxx> +#include <tools/debug.hxx> +#include <tools/gen.hxx> + +#include <svx/dlgctrl.hxx> + +#include <svxpixelctlaccessiblecontext.hxx> + +using namespace ::cppu; +using namespace ::osl; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::accessibility; + +SvxPixelCtlAccessible::SvxPixelCtlAccessible(SvxPixelCtl* pControl) + : mpPixelCtl(pControl) +{ +} + +SvxPixelCtlAccessible::~SvxPixelCtlAccessible() +{ + ensureDisposed(); +} + +uno::Reference< XAccessibleContext > SvxPixelCtlAccessible::getAccessibleContext( ) +{ + return this; +} + +sal_Int64 SvxPixelCtlAccessible::getAccessibleChildCount( ) +{ + return SvxPixelCtl::GetSquares(); +} +uno::Reference< XAccessible > SvxPixelCtlAccessible::getAccessibleChild( sal_Int64 i ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if ( i < 0 || i >= getAccessibleChildCount()) + throw lang::IndexOutOfBoundsException(); + Reference< XAccessible > xChild; + if (mpPixelCtl) + xChild = CreateChild(i, mpPixelCtl->IndexToPoint(i)); + return xChild; +} + +uno::Reference< XAccessible > SvxPixelCtlAccessible::getAccessibleParent( ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (mpPixelCtl) + return mpPixelCtl->getAccessibleParent(); + return uno::Reference<css::accessibility::XAccessible>(); +} + +sal_Int16 SvxPixelCtlAccessible::getAccessibleRole( ) +{ + return AccessibleRole::LIST; +} + +OUString SvxPixelCtlAccessible::getAccessibleDescription( ) +{ + + ::osl::MutexGuard aGuard( m_aMutex ); + return mpPixelCtl ? mpPixelCtl->GetAccessibleDescription() : ""; +} + +OUString SvxPixelCtlAccessible::getAccessibleName( ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return mpPixelCtl ? mpPixelCtl->GetAccessibleName() : ""; +} + +Reference< XAccessibleRelationSet > SAL_CALL SvxPixelCtlAccessible::getAccessibleRelationSet() +{ + if (mpPixelCtl) + return mpPixelCtl->get_accessible_relation_set(); + return uno::Reference<css::accessibility::XAccessibleRelationSet>(); +} + +sal_Int64 SvxPixelCtlAccessible::getAccessibleStateSet( ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + sal_Int64 nStateSet = 0; + + if (mpPixelCtl) + { + nStateSet |= + AccessibleStateType::FOCUSABLE | + AccessibleStateType::SELECTABLE | + AccessibleStateType::SHOWING | + AccessibleStateType::VISIBLE | + AccessibleStateType::OPAQUE; + if (mpPixelCtl->IsEnabled()) + nStateSet |= AccessibleStateType::ENABLED; + if (mpPixelCtl->HasFocus()) + nStateSet |= AccessibleStateType::FOCUSED; + nStateSet |= AccessibleStateType::MANAGES_DESCENDANTS; + } + + return nStateSet; +} + +uno::Reference<XAccessible > SAL_CALL SvxPixelCtlAccessible::getAccessibleAtPoint ( + const awt::Point& rPoint) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + Reference< XAccessible > xRet; + + if (mpPixelCtl) + { + tools::Long nIndex = mpPixelCtl->PointToIndex(Point(rPoint.X, rPoint.Y)); + xRet = CreateChild(nIndex, mpPixelCtl->IndexToPoint(nIndex)); + } + + return xRet; +} + +awt::Rectangle SvxPixelCtlAccessible::implGetBounds() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + awt::Rectangle aRet; + + if (mpPixelCtl) + { + const Point aOutPos; + Size aOutSize(mpPixelCtl->GetOutputSizePixel()); + + aRet.X = aOutPos.X(); + aRet.Y = aOutPos.Y(); + aRet.Width = aOutSize.Width(); + aRet.Height = aOutSize.Height(); + } + + return aRet; +} + +void SvxPixelCtlAccessible::grabFocus( ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (mpPixelCtl) + mpPixelCtl->GrabFocus(); +} + +sal_Int32 SvxPixelCtlAccessible::getForeground( ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + //see SvxPixelCtl::Paint + const StyleSettings& rStyles = Application::GetSettings().GetStyleSettings(); + return sal_Int32(rStyles.GetLabelTextColor()); +} + +sal_Int32 SvxPixelCtlAccessible::getBackground( ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + //see SvxPixelCtl::Paint + const StyleSettings& rStyles = Application::GetSettings().GetStyleSettings(); + return sal_Int32(rStyles.GetDialogColor()); +} + +void SvxPixelCtlAccessible::implSelect(sal_Int64 nChildIndex, bool bSelect) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( nChildIndex < 0 || nChildIndex >= getAccessibleChildCount()) + throw lang::IndexOutOfBoundsException(); + + if (!mpPixelCtl) + return; + + tools::Long nIndex = mpPixelCtl->ShowPosition(mpPixelCtl->IndexToPoint(nChildIndex)); + NotifyChild(nIndex, bSelect, false); +} + +bool SvxPixelCtlAccessible::implIsSelected(sal_Int64 nChildIndex) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if (!mpPixelCtl) + return false; + + return mpPixelCtl->GetFocusPosIndex() == nChildIndex; +} + +void SAL_CALL SvxPixelCtlAccessible::disposing() +{ + ::osl::MutexGuard aGuard(m_aMutex); + OAccessibleSelectionHelper::disposing(); + m_xCurChild.clear(); + mpPixelCtl = nullptr; +} + +void SvxPixelCtlAccessible::NotifyChild(tools::Long nIndex,bool bSelect ,bool bCheck) +{ + DBG_ASSERT( !(!bSelect && !bCheck),"" );//non is false + + rtl::Reference<SvxPixelCtlAccessibleChild> pChild = m_xCurChild; + if (pChild && pChild->getAccessibleIndexInParent() == nIndex ) + { + if (bSelect) + { + pChild->SelectChild(true); + } + if (bCheck) + { + pChild->ChangePixelColorOrBG(mpPixelCtl->GetBitmapPixel(sal_uInt16(nIndex)) != 0); + pChild->CheckChild(); + } + return; + } + rtl::Reference<SvxPixelCtlAccessibleChild> xNewChild = CreateChild(nIndex, mpPixelCtl->IndexToPoint(nIndex)); + DBG_ASSERT(xNewChild,"Child Must be Valid"); + + Any aNewValue,aOldValue; + aNewValue <<= uno::Reference<XAccessible>(xNewChild); + NotifyAccessibleEvent(AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, aOldValue, aNewValue); + + if (bSelect) + { + if (pChild) + { + pChild->SelectChild(false); + } + xNewChild->SelectChild(true); + } + if (bCheck) + { + xNewChild->CheckChild(); + } + m_xCurChild = xNewChild; +} + +rtl::Reference<SvxPixelCtlAccessibleChild> SvxPixelCtlAccessible::CreateChild (tools::Long nIndex,Point mPoint) +{ + bool bPixelColorOrBG = mpPixelCtl->GetBitmapPixel(sal_uInt16(nIndex)) != 0; + Size size(mpPixelCtl->GetWidth() / SvxPixelCtl::GetLineCount(), mpPixelCtl->GetHeight() / SvxPixelCtl::GetLineCount()); + rtl::Reference<SvxPixelCtlAccessibleChild> xChild = new SvxPixelCtlAccessibleChild(*mpPixelCtl, + bPixelColorOrBG, + tools::Rectangle(mPoint,size), + this, + nIndex); + + return xChild; +} + +void SvxPixelCtlAccessibleChild::CheckChild() +{ + Any aChecked; + aChecked <<= AccessibleStateType::CHECKED; + + if (m_bPixelColorOrBG)//Current Child State + { + NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, Any(), aChecked); + } + else + { + NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, aChecked, Any()); + } +} + +void SvxPixelCtlAccessibleChild::SelectChild( bool bSelect) +{ + Any aSelected; + aSelected <<= AccessibleStateType::SELECTED; + + if (bSelect) + { + NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, Any(), aSelected); + } + else + { + NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, aSelected, Any()); + } +} + +SvxPixelCtlAccessibleChild::SvxPixelCtlAccessibleChild( SvxPixelCtl& rWindow, bool bPixelColorOrBG, + const tools::Rectangle& rBoundingBox, rtl::Reference<SvxPixelCtlAccessible> xParent, + tools::Long nIndexInParent) + : mrParentWindow( rWindow ) + , mxParent(std::move(xParent)) + , m_bPixelColorOrBG(bPixelColorOrBG) + , maBoundingBox( rBoundingBox ) + , mnIndexInParent( nIndexInParent ) +{ +} + +SvxPixelCtlAccessibleChild::~SvxPixelCtlAccessibleChild() +{ + ensureDisposed(); +} + +// XAccessible +uno::Reference< XAccessibleContext> SAL_CALL SvxPixelCtlAccessibleChild::getAccessibleContext() +{ + return this; +} + +uno::Reference< XAccessible > SAL_CALL SvxPixelCtlAccessibleChild::getAccessibleAtPoint( const awt::Point& ) +{ + return uno::Reference< XAccessible >(); +} + +void SAL_CALL SvxPixelCtlAccessibleChild::grabFocus() +{ +} + +sal_Int32 SvxPixelCtlAccessibleChild::getForeground() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return mxParent.is() ? mxParent->getForeground() : -1; +} + +sal_Int32 SvxPixelCtlAccessibleChild::getBackground() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return mxParent.is() ? mxParent->getBackground() : -1; +} + +// XAccessibleContext +sal_Int64 SAL_CALL SvxPixelCtlAccessibleChild::getAccessibleChildCount() +{ + return 0; +} + +uno::Reference< XAccessible > SAL_CALL SvxPixelCtlAccessibleChild::getAccessibleChild( sal_Int64 ) +{ + throw lang::IndexOutOfBoundsException(); +} + +uno::Reference< XAccessible > SAL_CALL SvxPixelCtlAccessibleChild::getAccessibleParent() +{ + return mxParent; +} + +sal_Int16 SAL_CALL SvxPixelCtlAccessibleChild::getAccessibleRole() +{ + return AccessibleRole::CHECK_BOX; +} + +OUString SAL_CALL SvxPixelCtlAccessibleChild::getAccessibleDescription() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + return GetName(); +} + +OUString SAL_CALL SvxPixelCtlAccessibleChild::getAccessibleName() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return GetName(); +} + +/** Return empty uno::Reference to indicate that the relation set is not + supported. +*/ +uno::Reference<XAccessibleRelationSet> SAL_CALL SvxPixelCtlAccessibleChild::getAccessibleRelationSet() +{ + return uno::Reference< XAccessibleRelationSet >(); +} + +sal_Int64 SAL_CALL SvxPixelCtlAccessibleChild::getAccessibleStateSet() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + sal_Int64 nStateSet = 0; + + if (!rBHelper.bDisposed) + { + nStateSet |= AccessibleStateType::TRANSIENT; + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::OPAQUE; + nStateSet |= AccessibleStateType::SELECTABLE; + nStateSet |= AccessibleStateType::SHOWING; + nStateSet |= AccessibleStateType::VISIBLE; + + tools::Long nIndex = mrParentWindow.GetFocusPosIndex(); + if ( nIndex == mnIndexInParent) + { + nStateSet |= AccessibleStateType::SELECTED; + } + if (mrParentWindow.GetBitmapPixel(sal_uInt16(mnIndexInParent))) + { + nStateSet |= AccessibleStateType::CHECKED; + } + } + else + nStateSet |= AccessibleStateType::DEFUNC; + + return nStateSet; +} + +void SAL_CALL SvxPixelCtlAccessibleChild::disposing() +{ + OAccessibleComponentHelper::disposing(); + mxParent.clear(); +} + +awt::Rectangle SvxPixelCtlAccessibleChild::implGetBounds() +{ + // no guard necessary, because no one changes maBoundingBox after creating it + return AWTRectangle(maBoundingBox); +} + +OUString SvxPixelCtlAccessibleChild::GetName() const +{ + sal_Int32 nXIndex = mnIndexInParent % SvxPixelCtl::GetLineCount(); + sal_Int32 nYIndex = mnIndexInParent / SvxPixelCtl::GetLineCount(); + + OUString str = "(" + + OUString::number(nXIndex) + + "," + + OUString::number(nYIndex) + + ")"; + return str; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/accessibility/svxrectctaccessiblecontext.cxx b/svx/source/accessibility/svxrectctaccessiblecontext.cxx new file mode 100644 index 0000000000..45360e6c86 --- /dev/null +++ b/svx/source/accessibility/svxrectctaccessiblecontext.cxx @@ -0,0 +1,635 @@ +/* -*- 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 <svxrectctaccessiblecontext.hxx> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <toolkit/helper/convert.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <osl/mutex.hxx> +#include <tools/debug.hxx> +#include <tools/gen.hxx> +#include <sal/log.hxx> +#include <vcl/settings.hxx> +#include <svx/strings.hrc> +#include <svx/dlgctrl.hxx> +#include <svx/dialmgr.hxx> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <unotools/accessiblerelationsethelper.hxx> + +using namespace ::cppu; +using namespace ::osl; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::accessibility; + +using namespace ::com::sun::star::lang; + +#define MAX_NUM_OF_CHILDREN 9 +#define NOCHILDSELECTED -1 + +// internal +namespace +{ + struct ChildIndexToPointData + { + TranslateId pResIdName; + TranslateId pResIdDescr; + RectPoint ePoint; + }; +} + + +static const ChildIndexToPointData* IndexToPoint( tools::Long nIndex ) +{ + DBG_ASSERT( nIndex < 9 && nIndex >= 0, "-IndexToPoint(): invalid child index! You have been warned..." ); + + // corners are counted from left to right and top to bottom + static const ChildIndexToPointData pCornerData[] = + { // index + { RID_SVXSTR_RECTCTL_ACC_CHLD_LT, RID_SVXSTR_RECTCTL_ACC_CHLD_LT, RectPoint::LT }, // 0 + { RID_SVXSTR_RECTCTL_ACC_CHLD_MT, RID_SVXSTR_RECTCTL_ACC_CHLD_MT, RectPoint::MT }, // 1 + { RID_SVXSTR_RECTCTL_ACC_CHLD_RT, RID_SVXSTR_RECTCTL_ACC_CHLD_RT, RectPoint::RT }, // 2 + { RID_SVXSTR_RECTCTL_ACC_CHLD_LM, RID_SVXSTR_RECTCTL_ACC_CHLD_LM, RectPoint::LM }, // 3 + { RID_SVXSTR_RECTCTL_ACC_CHLD_MM, RID_SVXSTR_RECTCTL_ACC_CHLD_MM, RectPoint::MM }, // 4 + { RID_SVXSTR_RECTCTL_ACC_CHLD_RM, RID_SVXSTR_RECTCTL_ACC_CHLD_RM, RectPoint::RM }, // 5 + { RID_SVXSTR_RECTCTL_ACC_CHLD_LB, RID_SVXSTR_RECTCTL_ACC_CHLD_LB, RectPoint::LB }, // 6 + { RID_SVXSTR_RECTCTL_ACC_CHLD_MB, RID_SVXSTR_RECTCTL_ACC_CHLD_MB, RectPoint::MB }, // 7 + { RID_SVXSTR_RECTCTL_ACC_CHLD_RB, RID_SVXSTR_RECTCTL_ACC_CHLD_RB, RectPoint::RB } // 8 + }; + + return pCornerData + nIndex; +} + + +static tools::Long PointToIndex( RectPoint ePoint ) +{ + tools::Long nRet( static_cast<tools::Long>(ePoint) ); + // corner control + // corners are counted from left to right and top to bottom + DBG_ASSERT( int(RectPoint::LT) == 0 && int(RectPoint::MT) == 1 && int(RectPoint::RT) == 2 && int(RectPoint::LM) == 3 && int(RectPoint::MM) == 4 && int(RectPoint::RM) == 5 && + int(RectPoint::LB) == 6 && int(RectPoint::MB) == 7 && int(RectPoint::RB) == 8, "*PointToIndex(): unexpected enum value!" ); + + nRet = static_cast<tools::Long>(ePoint); + + return nRet; +} + +SvxRectCtlAccessibleContext::SvxRectCtlAccessibleContext(SvxRectCtl* pRepr) + : mpRepr(pRepr) + , mnSelectedChild(NOCHILDSELECTED) +{ + { + ::SolarMutexGuard aSolarGuard; + msName = SvxResId( RID_SVXSTR_RECTCTL_ACC_CORN_NAME ); + msDescription = SvxResId( RID_SVXSTR_RECTCTL_ACC_CORN_DESCR ); + } + + mvChildren.resize(MAX_NUM_OF_CHILDREN); +} + +SvxRectCtlAccessibleContext::~SvxRectCtlAccessibleContext() +{ + ensureDisposed(); +} + +Reference< XAccessible > SAL_CALL SvxRectCtlAccessibleContext::getAccessibleAtPoint( const awt::Point& rPoint ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + Reference< XAccessible > xRet; + + tools::Long nChild = mpRepr ? PointToIndex(mpRepr->GetApproxRPFromPixPt(rPoint)) : NOCHILDSELECTED; + + if (nChild != NOCHILDSELECTED) + xRet = getAccessibleChild( nChild ); + + return xRet; +} + +// XAccessibleContext +sal_Int64 SAL_CALL SvxRectCtlAccessibleContext::getAccessibleChildCount() +{ + return SvxRectCtl::NO_CHILDREN; +} + +Reference< XAccessible > SAL_CALL SvxRectCtlAccessibleContext::getAccessibleChild( sal_Int64 nIndex ) +{ + checkChildIndex( nIndex ); + + Reference< XAccessible > xChild(mvChildren[ nIndex ]); + if( !xChild.is() ) + { + ::SolarMutexGuard aSolarGuard; + + ::osl::MutexGuard aGuard( m_aMutex ); + + xChild = mvChildren[ nIndex ].get(); + + if (!xChild.is() && mpRepr) + { + const ChildIndexToPointData* p = IndexToPoint( nIndex ); + + tools::Rectangle aFocusRect( mpRepr->CalculateFocusRectangle( p->ePoint ) ); + + rtl::Reference<SvxRectCtlChildAccessibleContext> pChild = new SvxRectCtlChildAccessibleContext(this, + SvxResId(p->pResIdName), SvxResId(p->pResIdDescr), aFocusRect, nIndex ); + mvChildren[ nIndex ] = pChild; + xChild = pChild; + + // set actual state + if( mnSelectedChild == nIndex ) + pChild->setStateChecked( true ); + } + } + + return xChild; +} + +Reference< XAccessible > SAL_CALL SvxRectCtlAccessibleContext::getAccessibleParent() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (mpRepr) + return mpRepr->getAccessibleParent(); + return uno::Reference<css::accessibility::XAccessible>(); +} + +sal_Int16 SAL_CALL SvxRectCtlAccessibleContext::getAccessibleRole() +{ + return AccessibleRole::PANEL; +} + +OUString SAL_CALL SvxRectCtlAccessibleContext::getAccessibleDescription() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return msDescription + " Please use arrow key to selection."; +} + +OUString SAL_CALL SvxRectCtlAccessibleContext::getAccessibleName() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return msName; +} + +/** Return empty reference to indicate that the relation set is not + supported. +*/ +Reference< XAccessibleRelationSet > SAL_CALL SvxRectCtlAccessibleContext::getAccessibleRelationSet() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (mpRepr) + return mpRepr->get_accessible_relation_set(); + return uno::Reference<css::accessibility::XAccessibleRelationSet>(); +} + +sal_Int64 SAL_CALL SvxRectCtlAccessibleContext::getAccessibleStateSet() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + sal_Int64 nStateSet = 0; + + if (mpRepr) + { + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::FOCUSABLE; + if( mpRepr->HasFocus() ) + nStateSet |= AccessibleStateType::FOCUSED; + nStateSet |= AccessibleStateType::OPAQUE; + + nStateSet |= AccessibleStateType::SHOWING; + + if( mpRepr->IsVisible() ) + nStateSet |= AccessibleStateType::VISIBLE; + } + else + nStateSet |= AccessibleStateType::DEFUNC; + + return nStateSet; +} + +void SAL_CALL SvxRectCtlAccessibleContext::grabFocus() +{ + ::SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + if (mpRepr) + mpRepr->GrabFocus(); +} + +sal_Int32 SvxRectCtlAccessibleContext::getForeground() +{ + ::SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + //see SvxRectCtl::Paint + const StyleSettings& rStyles = Application::GetSettings().GetStyleSettings(); + return sal_Int32(rStyles.GetLabelTextColor()); +} + +sal_Int32 SvxRectCtlAccessibleContext::getBackground( ) +{ + ::SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + //see SvxRectCtl::Paint + const StyleSettings& rStyles = Application::GetSettings().GetStyleSettings(); + return sal_Int32(rStyles.GetDialogColor()); +} + +// XAccessibleSelection +void SvxRectCtlAccessibleContext::implSelect(sal_Int64 nIndex, bool bSelect) +{ + ::SolarMutexGuard aSolarGuard; + + ::osl::MutexGuard aGuard( m_aMutex ); + + checkChildIndex( nIndex ); + + const ChildIndexToPointData* pData = IndexToPoint( nIndex ); + + DBG_ASSERT(pData, "SvxRectCtlAccessibleContext::selectAccessibleChild(): this is an impossible state! Or at least should be..."); + + if (mpRepr) + { + if (bSelect) + { + // this does all what is needed, including the change of the child's state! + mpRepr->SetActualRP( pData->ePoint ); + } + else + { + SAL_WARN( "svx", "SvxRectCtlAccessibleContext::clearAccessibleSelection() is not possible!" ); + } + } +} + +bool SvxRectCtlAccessibleContext::implIsSelected( sal_Int64 nIndex ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + checkChildIndex( nIndex ); + + return nIndex == mnSelectedChild; +} + +// internals +void SvxRectCtlAccessibleContext::checkChildIndex( sal_Int64 nIndex ) +{ + if( nIndex < 0 || nIndex >= getAccessibleChildCount() ) + throw lang::IndexOutOfBoundsException(); +} + +void SvxRectCtlAccessibleContext::FireChildFocus( RectPoint eButton ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + tools::Long nNew = PointToIndex( eButton ); + tools::Long nNumOfChildren = getAccessibleChildCount(); + if( nNew < nNumOfChildren ) + { + // select new child + mnSelectedChild = nNew; + if( nNew != NOCHILDSELECTED ) + { + if( mvChildren[ nNew ].is() ) + mvChildren[ nNew ]->FireFocusEvent(); + } + else + { + Any aOld; + Any aNew; + aNew <<= AccessibleStateType::FOCUSED; + NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, aOld, aNew); + } + } + else + mnSelectedChild = NOCHILDSELECTED; +} + +void SvxRectCtlAccessibleContext::selectChild( tools::Long nNew ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if( nNew == mnSelectedChild ) + return; + + tools::Long nNumOfChildren = getAccessibleChildCount(); + if( nNew < nNumOfChildren ) + { // valid index + if( mnSelectedChild != NOCHILDSELECTED ) + { // deselect old selected child if one is selected + SvxRectCtlChildAccessibleContext* pChild = mvChildren[ mnSelectedChild ].get(); + if( pChild ) + pChild->setStateChecked( false ); + } + + // select new child + mnSelectedChild = nNew; + + if( nNew != NOCHILDSELECTED ) + { + if( mvChildren[ nNew ].is() ) + mvChildren[ nNew ]->setStateChecked( true ); + } + } + else + mnSelectedChild = NOCHILDSELECTED; +} + +void SvxRectCtlAccessibleContext::selectChild(RectPoint eButton ) +{ + // no guard -> is done in next selectChild + selectChild(PointToIndex( eButton )); +} + +void SAL_CALL SvxRectCtlAccessibleContext::disposing() +{ + ::osl::MutexGuard aGuard(m_aMutex); + OAccessibleSelectionHelper::disposing(); + for (auto & rxChild : mvChildren) + { + if( rxChild.is() ) + rxChild->dispose(); + } + mvChildren.clear(); + mpRepr = nullptr; +} + +awt::Rectangle SvxRectCtlAccessibleContext::implGetBounds() +{ + ::SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + awt::Rectangle aRet; + + if (mpRepr) + { + const Point aOutPos; + Size aOutSize(mpRepr->GetOutputSizePixel()); + + aRet.X = aOutPos.X(); + aRet.Y = aOutPos.Y(); + aRet.Width = aOutSize.Width(); + aRet.Height = aOutSize.Height(); + } + + return aRet; +} + +SvxRectCtlChildAccessibleContext::SvxRectCtlChildAccessibleContext( + const Reference<XAccessible>& rxParent, + OUString aName, + OUString aDescription, + const tools::Rectangle& rBoundingBox, + tools::Long nIndexInParent ) + : msDescription(std::move( aDescription )) + , msName(std::move( aName )) + , mxParent(rxParent) + , maBoundingBox( rBoundingBox ) + , mnIndexInParent( nIndexInParent ) + , mbIsChecked( false ) +{ +} + +SvxRectCtlChildAccessibleContext::~SvxRectCtlChildAccessibleContext() +{ + ensureDisposed(); +} + +Reference< XAccessible > SAL_CALL SvxRectCtlChildAccessibleContext::getAccessibleAtPoint( const awt::Point& /*rPoint*/ ) +{ + return Reference< XAccessible >(); +} + +void SAL_CALL SvxRectCtlChildAccessibleContext::grabFocus() +{ +} + +sal_Int32 SvxRectCtlChildAccessibleContext::getForeground( ) +{ + ::SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + //see SvxRectCtl::Paint + const StyleSettings& rStyles = Application::GetSettings().GetStyleSettings(); + return sal_Int32(rStyles.GetLabelTextColor()); +} + +sal_Int32 SvxRectCtlChildAccessibleContext::getBackground( ) +{ + ::SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + //see SvxRectCtl::Paint + const StyleSettings& rStyles = Application::GetSettings().GetStyleSettings(); + return sal_Int32(rStyles.GetDialogColor()); +} + +// XAccessibleContext +sal_Int64 SAL_CALL SvxRectCtlChildAccessibleContext::getAccessibleChildCount() +{ + return 0; +} + +Reference< XAccessible > SAL_CALL SvxRectCtlChildAccessibleContext::getAccessibleChild( sal_Int64 /*nIndex*/ ) +{ + throw lang::IndexOutOfBoundsException(); +} + +Reference< XAccessible > SAL_CALL SvxRectCtlChildAccessibleContext::getAccessibleParent() +{ + return mxParent; +} + +sal_Int16 SAL_CALL SvxRectCtlChildAccessibleContext::getAccessibleRole() +{ + return AccessibleRole::RADIO_BUTTON; +} + +OUString SAL_CALL SvxRectCtlChildAccessibleContext::getAccessibleDescription() +{ + return msDescription; +} + +OUString SAL_CALL SvxRectCtlChildAccessibleContext::getAccessibleName() +{ + return msName; +} + +/** Return empty reference to indicate that the relation set is not + supported. +*/ +Reference<XAccessibleRelationSet> SAL_CALL SvxRectCtlChildAccessibleContext::getAccessibleRelationSet() +{ + rtl::Reference<utl::AccessibleRelationSetHelper> pRelationSetHelper = new utl::AccessibleRelationSetHelper; + if( mxParent.is() ) + { + uno::Sequence< uno::Reference< uno::XInterface > > aSequence { mxParent }; + pRelationSetHelper->AddRelation( css::accessibility::AccessibleRelation( css::accessibility::AccessibleRelationType::MEMBER_OF, aSequence ) ); + } + + return pRelationSetHelper; +} + +sal_Int64 SAL_CALL SvxRectCtlChildAccessibleContext::getAccessibleStateSet() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + sal_Int64 nStateSet = 0; + + if (!rBHelper.bDisposed) + { + if( mbIsChecked ) + { + nStateSet |= AccessibleStateType::CHECKED; + } + + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::SENSITIVE; + nStateSet |= AccessibleStateType::OPAQUE; + nStateSet |= AccessibleStateType::SELECTABLE; + nStateSet |= AccessibleStateType::SHOWING; + nStateSet |= AccessibleStateType::VISIBLE; + } + else + nStateSet |= AccessibleStateType::DEFUNC; + + return nStateSet; +} + +// XAccessibleValue +Any SAL_CALL SvxRectCtlChildAccessibleContext::getCurrentValue() +{ + Any aRet; + aRet <<= ( mbIsChecked? 1.0 : 0.0 ); + return aRet; +} + +sal_Bool SAL_CALL SvxRectCtlChildAccessibleContext::setCurrentValue( const Any& /*aNumber*/ ) +{ + return false; +} + +Any SAL_CALL SvxRectCtlChildAccessibleContext::getMaximumValue() +{ + Any aRet; + aRet <<= 1.0; + return aRet; +} + +Any SAL_CALL SvxRectCtlChildAccessibleContext::getMinimumValue() +{ + Any aRet; + aRet <<= 0.0; + return aRet; +} + +Any SAL_CALL SvxRectCtlChildAccessibleContext::getMinimumIncrement() +{ + Any aRet; + aRet <<= 1.0; + return aRet; +} + + +// XAccessibleAction + + +sal_Int32 SvxRectCtlChildAccessibleContext::getAccessibleActionCount( ) +{ + return 1; +} + + +sal_Bool SvxRectCtlChildAccessibleContext::doAccessibleAction ( sal_Int32 nIndex ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( nIndex < 0 || nIndex >= getAccessibleActionCount() ) + throw IndexOutOfBoundsException(); + + Reference<XAccessibleSelection> xSelection( mxParent, UNO_QUERY); + + xSelection->selectAccessibleChild(mnIndexInParent); + + return true; +} + + +OUString SvxRectCtlChildAccessibleContext::getAccessibleActionDescription ( sal_Int32 nIndex ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( nIndex < 0 || nIndex >= getAccessibleActionCount() ) + throw IndexOutOfBoundsException(); + + return "select"; +} + + +Reference< XAccessibleKeyBinding > SvxRectCtlChildAccessibleContext::getAccessibleActionKeyBinding( sal_Int32 nIndex ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( nIndex < 0 || nIndex >= getAccessibleActionCount() ) + throw IndexOutOfBoundsException(); + + return Reference< XAccessibleKeyBinding >(); +} + +void SAL_CALL SvxRectCtlChildAccessibleContext::disposing() +{ + OAccessibleComponentHelper::disposing(); + mxParent.clear(); +} + +awt::Rectangle SvxRectCtlChildAccessibleContext::implGetBounds( ) +{ + // no guard necessary, because no one changes maBoundingBox after creating it + return AWTRectangle(maBoundingBox); +} + +void SvxRectCtlChildAccessibleContext::setStateChecked( bool bChecked ) +{ + if( mbIsChecked == bChecked ) + return; + + mbIsChecked = bChecked; + + Any aOld; + Any aNew; + Any& rMod = bChecked? aNew : aOld; + + //Send the STATE_CHANGED(Focused) event to accessible + rMod <<= AccessibleStateType::FOCUSED; + NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, aOld, aNew); + + rMod <<= AccessibleStateType::CHECKED; + + NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, aOld, aNew); +} + +void SvxRectCtlChildAccessibleContext::FireFocusEvent() +{ + Any aOld; + Any aNew; + aNew <<= AccessibleStateType::FOCUSED; + NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, aOld, aNew); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |