summaryrefslogtreecommitdiffstats
path: root/svx/source/accessibility/AccessibleControlShape.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /svx/source/accessibility/AccessibleControlShape.cxx
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svx/source/accessibility/AccessibleControlShape.cxx')
-rw-r--r--svx/source/accessibility/AccessibleControlShape.cxx879
1 files changed, 879 insertions, 0 deletions
diff --git a/svx/source/accessibility/AccessibleControlShape.cxx b/svx/source/accessibility/AccessibleControlShape.cxx
new file mode 100644
index 000000000..45b7edd75
--- /dev/null
+++ b/svx/source/accessibility/AccessibleControlShape.cxx
@@ -0,0 +1,879 @@
+/* -*- 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/accessiblestatesethelper.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 <tools/diagnose_ex.h>
+
+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
+{
+ OUString lcl_getNamePropertyName( )
+ {
+ return "Name";
+ }
+ OUString lcl_getDescPropertyName( )
+ {
+ return "HelpText";
+ }
+ OUString lcl_getLabelPropertyName( )
+ {
+ return "Label";
+ }
+ OUString lcl_getLabelControlPropertyName( )
+ {
+ return "LabelControl";
+ }
+ // return the property which should be used as AccessibleName
+ OUString lcl_getPreferredAccNameProperty( const Reference< XPropertySetInfo >& _rxPSI )
+ {
+ if ( _rxPSI.is() && _rxPSI->hasPropertyByName( lcl_getLabelPropertyName() ) )
+ return lcl_getLabelPropertyName();
+ else
+ return lcl_getNamePropertyName();
+ }
+
+ // determines whether or not a state which belongs to the inner context needs to be forwarded to the "composed"
+ // context
+ bool isComposedState( const sal_Int16 _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 ) )
+ {
+ Reference< XAccessibleStateSet > xStates( getAccessibleStateSet( ) );
+ OSL_ENSURE( xStates.is(), "AccessibleControlShape::AccessibleControlShape: no inner state set!" );
+ m_pChildManager->setTransientChildren( !xStates.is() || xStates->contains( 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( lcl_getDescPropertyName() ) );
+ 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, lcl_getDescPropertyName() );
+ }
+ 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 == lcl_getNamePropertyName()
+ || _rEvent.PropertyName == lcl_getLabelPropertyName() )
+ {
+ SetAccessibleName(
+ CreateAccessibleName(),
+ AccessibleContextBase::AutomaticallyCreated);
+ }
+ else if ( _rEvent.PropertyName == lcl_getDescPropertyName() )
+ {
+ 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_Int16 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_Int32 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_Int32 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::getAccessibleChildCount: 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_Int32 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, lcl_getDescPropertyName() );
+
+ 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!" );
+ Reference< XContainer > xContainer = lcl_getControlContainer( maShapeTreeInfo.GetWindow()->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_Int16 _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;
+
+ // get our own state set implementation
+ ::utl::AccessibleStateSetHelper* pComposedStates = mxStateSet.get();
+ OSL_PRECOND( pComposedStates,
+ "AccessibleControlShape::initializeComposedState: no composed set!" );
+
+ // 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
+ pComposedStates->RemoveState( AccessibleStateType::ENABLED ); // this is controlled by the UNO-control
+ pComposedStates->RemoveState( AccessibleStateType::SENSITIVE ); // this is controlled by the UNO-control
+ pComposedStates->RemoveState( AccessibleStateType::FOCUSABLE ); // this is controlled by the UNO-control
+ pComposedStates->RemoveState( AccessibleStateType::SELECTABLE ); // this does not hold for an alive UNO-control
+#if OSL_DEBUG_LEVEL > 0
+ // now, only states which are not in the responsibility of the UNO control should be part of this state set
+ {
+ const Sequence< sal_Int16 > aInitStates = pComposedStates->getStates();
+ for ( sal_Int16 state : aInitStates )
+ OSL_ENSURE( !isComposedState( state ),
+ "AccessibleControlShape::initializeComposedState: invalid initial composed state (should be controlled by the UNO-control)!" );
+ }
+#endif
+
+ // 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
+ Reference< XAccessibleStateSet > xInnerStates( xInnerContext->getAccessibleStateSet() );
+ OSL_ENSURE( xInnerStates.is(), "AccessibleControlShape::initializeComposedState: no inner states!" );
+ Sequence< sal_Int16 > aInnerStates;
+ if ( xInnerStates.is() )
+ aInnerStates = xInnerStates->getStates();
+
+ // look which one are to be propagated to the composed context
+ for ( const sal_Int16 nState : std::as_const(aInnerStates) )
+ {
+ if ( isComposedState( nState ) && !pComposedStates->contains( nState ) )
+ {
+ pComposedStates->AddState( 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())
+ {
+ const OUString& rAccLabelControlProperty = lcl_getLabelControlPropertyName();
+ Any sCtlLabelBy;
+ // get the "label by" property value of the control
+ if (::comphelper::hasProperty(rAccLabelControlProperty, m_xControlModel))
+ {
+ sCtlLabelBy = m_xControlModel->getPropertyValue(rAccLabelControlProperty);
+ 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: */