Coverage Report

Created: 2026-10-14 09:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/accessibility/AccessibleControlShape.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include "accessiblewrapper.hxx"
21
22
#include <svx/AccessibleControlShape.hxx>
23
#include <svx/AccessibleShapeInfo.hxx>
24
#include <DescriptionGenerator.hxx>
25
#include <com/sun/star/awt/XWindow.hpp>
26
#include <com/sun/star/beans/XPropertySet.hpp>
27
#include <com/sun/star/drawing/XControlShape.hpp>
28
#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
29
#include <com/sun/star/accessibility/AccessibleRole.hpp>
30
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
31
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
32
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
33
#include <com/sun/star/reflection/ProxyFactory.hpp>
34
#include <com/sun/star/util/XModeChangeBroadcaster.hpp>
35
#include <com/sun/star/container/XContainer.hpp>
36
#include <comphelper/processfactory.hxx>
37
#include <comphelper/property.hxx>
38
#include <unotools/accessiblerelationsethelper.hxx>
39
#include <svx/IAccessibleParent.hxx>
40
#include <svx/svdouno.hxx>
41
#include <svx/ShapeTypeHandler.hxx>
42
#include <svx/SvxShapeTypes.hxx>
43
#include <svx/svdview.hxx>
44
#include <svx/svdpagv.hxx>
45
#include <svx/strings.hrc>
46
#include <vcl/svapp.hxx>
47
#include <vcl/window.hxx>
48
#include <sal/log.hxx>
49
#include <tools/debug.hxx>
50
#include <comphelper/diagnose_ex.hxx>
51
#include <toolkit/controls/unocontrolcontainer.hxx>
52
53
using namespace ::accessibility;
54
using namespace ::com::sun::star::accessibility;
55
using namespace ::com::sun::star::uno;
56
using namespace ::com::sun::star::awt;
57
using namespace ::com::sun::star::beans;
58
using namespace ::com::sun::star::util;
59
using namespace ::com::sun::star::lang;
60
using namespace ::com::sun::star::reflection;
61
using namespace ::com::sun::star::drawing;
62
using namespace ::com::sun::star::container;
63
64
namespace
65
{
66
    constexpr OUString NAME_PROPERTY_NAME = u"Name"_ustr;
67
    constexpr OUString DESC_PROPERTY_NAME = u"HelpText"_ustr;
68
    constexpr OUString LABEL_PROPERTY_NAME = u"Label"_ustr;
69
    constexpr OUString LABEL_CONTROL_PROPERTY_NAME = u"LabelControl"_ustr;
70
71
    // return the property which should be used as AccessibleName
72
    const OUString & lcl_getPreferredAccNameProperty( const Reference< XPropertySetInfo >& _rxPSI )
73
0
    {
74
0
        if ( _rxPSI.is() && _rxPSI->hasPropertyByName( LABEL_PROPERTY_NAME ) )
75
0
            return LABEL_PROPERTY_NAME;
76
0
        else
77
0
            return NAME_PROPERTY_NAME;
78
0
    }
79
80
    // determines whether or not a state which belongs to the inner context needs to be forwarded to the "composed"
81
    // context
82
    bool    isComposedState( const sal_Int64 _nState )
83
0
    {
84
0
        return  (   ( AccessibleStateType::INVALID != _nState )
85
0
                &&  ( AccessibleStateType::DEFUNC != _nState )
86
0
                &&  ( AccessibleStateType::ICONIFIED != _nState )
87
0
                &&  ( AccessibleStateType::RESIZABLE != _nState )
88
0
                &&  ( AccessibleStateType::SELECTABLE != _nState )
89
0
                &&  ( AccessibleStateType::SHOWING != _nState )
90
0
                &&  ( AccessibleStateType::MANAGES_DESCENDANTS != _nState )
91
0
                &&  ( AccessibleStateType::VISIBLE != _nState )
92
0
                );
93
0
    }
94
}
95
96
AccessibleControlShape::AccessibleControlShape (
97
    const AccessibleShapeInfo& rShapeInfo,
98
    const AccessibleShapeTreeInfo& rShapeTreeInfo)
99
0
    :      AccessibleShape (rShapeInfo, rShapeTreeInfo)
100
0
    ,   m_bListeningForName( false )
101
0
    ,   m_bListeningForDesc( false )
102
0
    ,   m_bMultiplexingStates( false )
103
0
    ,   m_bDisposeNativeContext( false )
104
0
    ,   m_bWaitingForControl( false )
105
0
{
106
0
    m_pChildManager = new OWrappedAccessibleChildrenManager( comphelper::getProcessComponentContext() );
107
108
0
    osl_atomic_increment( &m_refCount );
109
0
    {
110
0
        m_pChildManager->setOwningAccessible( this );
111
0
    }
112
0
    osl_atomic_decrement( &m_refCount );
113
0
}
114
115
AccessibleControlShape::~AccessibleControlShape()
116
0
{
117
0
    m_pChildManager.clear();
118
119
0
    if ( m_xControlContextProxy.is() )
120
0
        m_xControlContextProxy->setDelegator( nullptr );
121
0
    m_xControlContextProxy.clear();
122
0
    m_xControlContextTypeAccess.clear();
123
0
    m_xControlContextComponent.clear();
124
        // this should remove the _only_ three "real" references (means not delegated to
125
        // ourself) to this proxy, and thus delete it
126
0
}
127
128
namespace {
129
    rtl::Reference< UnoControlContainer > lcl_getControlContainer( const OutputDevice* _pWin, const SdrView* _pView )
130
0
    {
131
0
        rtl::Reference< UnoControlContainer > xReturn;
132
0
        DBG_ASSERT( _pView, "lcl_getControlContainer: invalid view!" );
133
0
        if ( _pView  && _pView->GetSdrPageView())
134
0
        {
135
0
            xReturn = _pView->GetSdrPageView()->GetControlContainer( *_pWin );
136
0
        }
137
0
        return xReturn;
138
0
    }
139
}
140
141
void AccessibleControlShape::Init()
142
0
{
143
0
    AccessibleShape::Init();
144
145
0
    OSL_ENSURE( !m_xControlContextProxy.is(), "AccessibleControlShape::Init: already initialized!" );
146
0
    try
147
0
    {
148
        // What we need to do here is merge the functionality of the AccessibleContext of our UNO control
149
        // with our own AccessibleContext-related functionality.
150
151
        // The problem is that we do not know the interfaces our "inner" context supports - this may be any
152
        // XAccessibleXXX interface (or even any other) which makes sense for it.
153
154
        // In theory, we could implement all possible interfaces ourself, and re-route all functionality to
155
        // the inner context (except those we implement ourself, like XAccessibleComponent). But this is in no
156
        // way future-proof - as soon as an inner context appears which implements an additional interface,
157
        // we would need to adjust our implementation to support this new interface, too. Bad idea.
158
159
        // The usual solution for such a problem is aggregation. Aggregation means using UNO's own mechanism
160
        // for merging an inner with an outer component, and get a component which behaves as it is exactly one.
161
        // This is what XAggregation is for. Unfortunately, aggregation requires _exact_ control over the ref count
162
        // of the inner object, which we do not have at all.
163
        // Bad, too.
164
165
        // But there is a solution: com.sun.star.reflection.ProxyFactory. This service is able to create a proxy
166
        // for any component, which supports _exactly_ the same interfaces as the component. In addition, it can
167
        // be aggregated, as by definition the proxy's ref count is exactly 1 when returned from the factory.
168
        // Sounds better. Though this yields the problem of slightly degraded performance, it's the only solution
169
        // I'm aware of at the moment...
170
171
        // get the control which belongs to our model (relative to our view)
172
0
        const vcl::Window* pViewWindow = maShapeTreeInfo.GetWindow();
173
0
        SdrUnoObj* pUnoObjectImpl = dynamic_cast<SdrUnoObj*>(SdrObject::getSdrObjectFromXShape(mxShape));
174
0
        SdrView* pView = maShapeTreeInfo.GetSdrView();
175
0
        OSL_ENSURE( pView && pViewWindow && pUnoObjectImpl, "AccessibleControlShape::Init: no view, or no view window, no SdrUnoObj!" );
176
177
0
        if (!pView || !pViewWindow || !pUnoObjectImpl)
178
0
            return;
179
180
        // get the context of the control - it will be our "inner" context
181
0
        m_xUnoControl = pUnoObjectImpl->GetUnoControl( *pView, *pViewWindow->GetOutDev() );
182
183
0
        if ( !m_xUnoControl.is() )
184
0
        {
185
            // the control has not yet been created. Though speaking strictly, it is a bug that
186
            // our instance here is created without an existing control (because an AccessibleControlShape
187
            // is a representation of a view object, and can only live if the view it should represent
188
            // is complete, which implies a living control), it's by far the easiest and most riskless way
189
            // to fix this here in this class.
190
            // Okay, we will add as listener to the control container where we expect our control to appear.
191
0
            OSL_ENSURE( !m_bWaitingForControl, "AccessibleControlShape::Init: already waiting for the control!" );
192
193
0
            rtl::Reference< UnoControlContainer > xControlContainer = lcl_getControlContainer( pViewWindow->GetOutDev(), maShapeTreeInfo.GetSdrView() );
194
0
            OSL_ENSURE( xControlContainer.is(), "AccessibleControlShape::Init: unable to find my ControlContainer!" );
195
0
            if ( xControlContainer.is() )
196
0
            {
197
0
                xControlContainer->addContainerListener( this );
198
0
                m_bWaitingForControl = true;
199
0
            }
200
0
        }
201
0
        else
202
0
        {
203
0
            Reference< XModeChangeBroadcaster > xControlModes( m_xUnoControl, UNO_QUERY );
204
0
            Reference< XAccessibleContext > xNativeControlContext;
205
0
            if (UnoControl* pUnoControl = dynamic_cast<UnoControl*>(xControlModes.get()))
206
0
                xNativeControlContext = pUnoControl->getAccessible();
207
0
            OSL_ENSURE( xNativeControlContext.is(), "AccessibleControlShape::Init: no AccessibleContext for the control!" );
208
0
            m_aControlContext = WeakReference< XAccessibleContext >( xNativeControlContext );
209
210
            // add as listener to the context - we want to multiplex some states
211
0
            if (isControlInAliveMode() && xNativeControlContext.is() )
212
0
            {   // (but only in alive mode)
213
0
                startStateMultiplexing( );
214
0
            }
215
216
            // now that we have all information about our control, do some adjustments
217
0
            adjustAccessibleRole();
218
0
            initializeComposedState();
219
220
            // some initialization for our child manager, which is used in alive mode only
221
0
            if (isControlInAliveMode())
222
0
            {
223
0
                sal_Int64 nStates( getAccessibleStateSet( ) );
224
0
                m_pChildManager->setTransientChildren( nStates & AccessibleStateType::MANAGES_DESCENDANTS );
225
0
            }
226
227
            // finally, aggregate a proxy for the control context
228
            // first a factory for the proxy
229
0
            Reference< XProxyFactory > xFactory = ProxyFactory::create( comphelper::getProcessComponentContext() );
230
            // then the proxy itself
231
0
            if ( xNativeControlContext.is() )
232
0
            {
233
0
                m_xControlContextProxy = xFactory->createProxy( xNativeControlContext );
234
0
                m_xControlContextTypeAccess.set( xNativeControlContext, UNO_QUERY_THROW );
235
0
                m_xControlContextComponent.set( xNativeControlContext, UNO_QUERY_THROW );
236
237
                // aggregate the proxy
238
0
                osl_atomic_increment( &m_refCount );
239
0
                if ( m_xControlContextProxy.is() )
240
0
                {
241
                    // At this point in time, the proxy has a ref count of exactly one - in m_xControlContextProxy.
242
                    // Remember to _not_ reset this member unless the delegator of the proxy has been reset, too!
243
0
                    m_xControlContextProxy->setDelegator( *this );
244
0
                }
245
0
                osl_atomic_decrement( &m_refCount );
246
247
0
                m_bDisposeNativeContext = true;
248
249
                // Finally, we need to add ourself as mode listener to the control. In case the mode switches,
250
                // we need to dispose ourself.
251
0
                xControlModes->addModeChangeListener( this );
252
0
            }
253
0
        }
254
0
    }
255
0
    catch( const Exception& )
256
0
    {
257
0
        OSL_FAIL( "AccessibleControlShape::Init: could not \"aggregate\" the controls XAccessibleContext!" );
258
0
    }
259
0
}
260
261
void SAL_CALL AccessibleControlShape::grabFocus()
262
0
{
263
0
    if (!m_xUnoControl.is() || !isControlInAliveMode())
264
0
    {
265
        // in design mode, we simply forward the request to the base class
266
0
        AccessibleShape::grabFocus();
267
0
    }
268
0
    else
269
0
    {
270
0
        Reference< XWindow > xWindow( m_xUnoControl, UNO_QUERY );
271
0
        OSL_ENSURE( xWindow.is(), "AccessibleControlShape::grabFocus: invalid control!" );
272
0
        if ( xWindow.is() )
273
0
            xWindow->setFocus();
274
0
    }
275
0
}
276
277
OUString AccessibleControlShape::CreateAccessibleBaseName()
278
0
{
279
0
    OUString sName;
280
281
0
    ShapeTypeId nShapeType = ShapeTypeHandler::Instance().GetTypeId (mxShape);
282
0
    switch (nShapeType)
283
0
    {
284
0
        case DRAWING_CONTROL:
285
0
            sName = "ControlShape";
286
0
            break;
287
0
        default:
288
0
            sName = "UnknownAccessibleControlShape";
289
0
            if (mxShape.is())
290
0
                sName += ": " + mxShape->getShapeType();
291
0
    }
292
293
0
    return sName;
294
0
}
295
296
OUString
297
    AccessibleControlShape::CreateAccessibleDescription()
298
0
{
299
0
    DescriptionGenerator aDG (mxShape);
300
0
    ShapeTypeId nShapeType = ShapeTypeHandler::Instance().GetTypeId (mxShape);
301
0
    switch (nShapeType)
302
0
    {
303
0
        case DRAWING_CONTROL:
304
0
        {
305
            // check if we can obtain the "Desc" property from the model
306
0
            OUString sDesc( getControlModelStringProperty( DESC_PROPERTY_NAME ) );
307
0
            if ( sDesc.isEmpty() )
308
0
            {   // no -> use the default
309
0
                aDG.Initialize (STR_ObjNameSingulUno);
310
0
                aDG.AddProperty (u"ControlBackground"_ustr, DescriptionGenerator::PropertyType::Color);
311
0
                aDG.AddProperty ( u"ControlBorder"_ustr, DescriptionGenerator::PropertyType::Integer);
312
0
            }
313
            // ensure that we are listening to the Name property
314
0
            m_bListeningForDesc = ensureListeningState( m_bListeningForDesc, true, DESC_PROPERTY_NAME );
315
0
        }
316
0
        break;
317
318
0
        default:
319
0
            aDG.Initialize (u"Unknown accessible control shape");
320
0
            if (mxShape.is())
321
0
            {
322
0
                aDG.AppendString (u"service name=");
323
0
                aDG.AppendString (mxShape->getShapeType());
324
0
            }
325
0
    }
326
327
0
    return aDG();
328
0
}
329
330
IMPLEMENT_FORWARD_REFCOUNT( AccessibleControlShape, AccessibleShape )
331
IMPLEMENT_GET_IMPLEMENTATION_ID( AccessibleControlShape )
332
333
void SAL_CALL AccessibleControlShape::propertyChange( const PropertyChangeEvent& _rEvent )
334
0
{
335
0
    ::osl::MutexGuard aGuard( m_aMutex );
336
337
    // check if it is the name or the description
338
0
    if  (   _rEvent.PropertyName == NAME_PROPERTY_NAME
339
0
            ||  _rEvent.PropertyName == LABEL_PROPERTY_NAME )
340
0
    {
341
0
        SetAccessibleName(
342
0
            CreateAccessibleName(),
343
0
            AccessibleContextBase::AutomaticallyCreated);
344
0
    }
345
0
    else if ( _rEvent.PropertyName == DESC_PROPERTY_NAME )
346
0
    {
347
0
        SetAccessibleDescription(
348
0
            CreateAccessibleDescription(),
349
0
            AccessibleContextBase::AutomaticallyCreated);
350
0
    }
351
#if OSL_DEBUG_LEVEL > 0
352
    else
353
    {
354
        OSL_FAIL( "AccessibleControlShape::propertyChange: where did this come from?" );
355
    }
356
#endif
357
0
}
358
359
Any SAL_CALL AccessibleControlShape::queryInterface( const Type& _rType )
360
0
{
361
0
    Any aReturn = AccessibleShape::queryInterface( _rType );
362
0
    if ( !aReturn.hasValue() )
363
0
    {
364
0
        aReturn = AccessibleControlShape_Base::queryInterface( _rType );
365
0
        if ( !aReturn.hasValue() && m_xControlContextProxy.is() )
366
0
            aReturn = m_xControlContextProxy->queryAggregation( _rType );
367
0
    }
368
0
    return aReturn;
369
0
}
370
371
Sequence< Type > SAL_CALL AccessibleControlShape::getTypes()
372
0
{
373
0
    Sequence< Type > aShapeTypes = AccessibleShape::getTypes();
374
0
    Sequence< Type > aOwnTypes = AccessibleControlShape_Base::getTypes();
375
376
0
    Sequence< Type > aAggregateTypes;
377
0
    if ( m_xControlContextTypeAccess.is() )
378
0
        aAggregateTypes = m_xControlContextTypeAccess->getTypes();
379
380
    // remove duplicates
381
0
    return comphelper::combineSequences(comphelper::concatSequences( aShapeTypes, aOwnTypes), aAggregateTypes );
382
0
}
383
384
void SAL_CALL AccessibleControlShape::notifyEvent( const AccessibleEventObject& _rEvent )
385
0
{
386
0
    if ( AccessibleEventId::STATE_CHANGED == _rEvent.EventId )
387
0
    {
388
        // multiplex this change
389
0
        sal_Int64 nLostState( 0 ), nGainedState( 0 );
390
0
        _rEvent.OldValue >>= nLostState;
391
0
        _rEvent.NewValue >>= nGainedState;
392
393
        // don't multiplex states which the inner context is not responsible for
394
0
        if  ( isComposedState( nLostState ) )
395
0
            AccessibleShape::ResetState( nLostState );
396
397
0
        if  ( isComposedState( nGainedState ) )
398
0
            AccessibleShape::SetState( nGainedState );
399
0
    }
400
0
    else
401
0
    {
402
0
        AccessibleEventObject aTranslatedEvent( _rEvent );
403
404
0
        {
405
0
            ::osl::MutexGuard aGuard( m_aMutex );
406
407
            // let the child manager translate the event
408
0
            aTranslatedEvent.Source = *this;
409
0
            m_pChildManager->translateAccessibleEvent( _rEvent, aTranslatedEvent );
410
411
            // see if any of these notifications affect our child manager
412
0
            m_pChildManager->handleChildNotification( _rEvent );
413
0
        }
414
415
0
        NotifyAccessibleEvent(_rEvent.EventId, _rEvent.OldValue, _rEvent.NewValue,
416
0
                              _rEvent.IndexHint);
417
0
    }
418
0
}
419
420
void SAL_CALL AccessibleControlShape::modeChanged(const ModeChangeEvent& rSource)
421
0
{
422
    // did it come from our inner context (the real one, not it's proxy!)?
423
0
    SAL_INFO("sw.uno", "AccessibleControlShape::modeChanged");
424
0
    Reference<XControl> xSource(rSource.Source, UNO_QUERY); // for faster compare
425
0
    if(xSource.get() != m_xUnoControl.get())
426
0
    {
427
0
        SAL_WARN("sw.uno", "AccessibleControlShape::modeChanged: where did this come from?");
428
0
        return;
429
0
    }
430
0
    SolarMutexGuard g;
431
    // If our "pseudo-aggregated" inner context does not live anymore,
432
    // we don't want to live, too.  This is accomplished by asking our
433
    // parent to replace this object with a new one.  Disposing this
434
    // object and sending notifications about the replacement are in
435
    // the responsibility of our parent.
436
0
    const bool bReplaced = mpParent->ReplaceChild(this, mxShape, 0, maShapeTreeInfo);
437
0
    SAL_WARN_IF(!bReplaced, "sw.uno", "AccessibleControlShape::modeChanged: replacing ourselves away did fail");
438
0
}
439
440
void SAL_CALL AccessibleControlShape::disposing (const EventObject& _rSource)
441
0
{
442
0
    AccessibleShape::disposing( _rSource );
443
0
}
444
445
bool AccessibleControlShape::ensureListeningState(
446
        const bool _bCurrentlyListening, const bool _bNeedNewListening,
447
        const OUString& _rPropertyName )
448
0
{
449
0
    if ( ( _bCurrentlyListening == _bNeedNewListening ) || !ensureControlModelAccess() )
450
        //  nothing to do
451
0
        return _bCurrentlyListening;
452
453
0
    try
454
0
    {
455
0
        if ( !m_xModelPropsMeta.is() || m_xModelPropsMeta->hasPropertyByName( _rPropertyName ) )
456
0
        {
457
            // add or revoke as listener
458
0
            if ( _bNeedNewListening )
459
0
                m_xControlModel->addPropertyChangeListener( _rPropertyName, static_cast< XPropertyChangeListener* >( this ) );
460
0
            else
461
0
                m_xControlModel->removePropertyChangeListener( _rPropertyName, static_cast< XPropertyChangeListener* >( this ) );
462
0
        }
463
0
        else
464
0
            OSL_FAIL( "AccessibleControlShape::ensureListeningState: this property does not exist at this model!" );
465
0
    }
466
0
    catch( const Exception& )
467
0
    {
468
0
        OSL_FAIL( "AccessibleControlShape::ensureListeningState: could not change the listening state!" );
469
0
    }
470
471
0
    return _bNeedNewListening;
472
0
}
473
474
sal_Int64 SAL_CALL AccessibleControlShape::getAccessibleChildCount( )
475
0
{
476
0
    if ( !m_xUnoControl.is() )
477
0
        return 0;
478
0
    else if (!isControlInAliveMode())
479
        // no special action required when in design mode
480
0
        return AccessibleShape::getAccessibleChildCount( );
481
0
    else
482
0
    {
483
        // in alive mode, we have the full control over our children - they are determined by the children
484
        // of the context of our UNO control
485
0
        Reference< XAccessibleContext > xControlContext( m_aControlContext );
486
0
        OSL_ENSURE( xControlContext.is(), "AccessibleControlShape::getAccessibleChildCount: control context already dead! How this!" );
487
0
        return xControlContext.is() ? xControlContext->getAccessibleChildCount() : 0;
488
0
    }
489
0
}
490
491
Reference< XAccessible > SAL_CALL AccessibleControlShape::getAccessibleChild( sal_Int64 i )
492
0
{
493
0
    Reference< XAccessible > xChild;
494
0
    if ( !m_xUnoControl.is() )
495
0
    {
496
0
        throw IndexOutOfBoundsException();
497
0
    }
498
0
    if (!isControlInAliveMode())
499
0
    {
500
        // no special action required when in design mode - let the base class handle this
501
0
        xChild = AccessibleShape::getAccessibleChild( i );
502
0
    }
503
0
    else
504
0
    {
505
        // in alive mode, we have the full control over our children - they are determined by the children
506
        // of the context of our UNO control
507
508
0
        Reference< XAccessibleContext > xControlContext( m_aControlContext );
509
0
        OSL_ENSURE( xControlContext.is(), "AccessibleControlShape::getAccessibleChild: control context already dead! How this!" );
510
0
        if ( xControlContext.is() )
511
0
        {
512
0
            Reference< XAccessible > xInnerChild( xControlContext->getAccessibleChild( i ) );
513
0
            OSL_ENSURE( xInnerChild.is(), "AccessibleControlShape::getAccessibleChild: control context returned nonsense!" );
514
0
            if ( xInnerChild.is() )
515
0
            {
516
                // we need to wrap this inner child into an own implementation
517
0
                xChild = m_pChildManager->getAccessibleWrapperFor( xInnerChild );
518
0
            }
519
0
        }
520
0
    }
521
522
#if OSL_DEBUG_LEVEL > 0
523
    sal_Int64 nChildIndex = -1;
524
    Reference< XAccessibleContext > xContext;
525
    if ( xChild.is() )
526
        xContext = xChild->getAccessibleContext( );
527
    if ( xContext.is() )
528
        nChildIndex = xContext->getAccessibleIndexInParent( );
529
    SAL_WARN_IF( nChildIndex != i, "svx", "AccessibleControlShape::getAccessibleChild: index mismatch,"
530
            " nChildIndex=" << nChildIndex << " vs i=" << i );
531
#endif
532
0
    return xChild;
533
0
}
534
535
Reference< XAccessibleRelationSet > SAL_CALL AccessibleControlShape::getAccessibleRelationSet(  )
536
0
{
537
0
    rtl::Reference<utl::AccessibleRelationSetHelper> pRelationSetHelper = new utl::AccessibleRelationSetHelper;
538
0
    ensureControlModelAccess();
539
0
    AccessibleControlShape* pCtlAccShape = GetLabeledByControlShape();
540
0
    if(pCtlAccShape)
541
0
    {
542
0
        css::uno::Sequence<css::uno::Reference<XAccessible>> aSequence { pCtlAccShape };
543
0
        if( getAccessibleRole() == AccessibleRole::RADIO_BUTTON )
544
0
        {
545
0
            pRelationSetHelper->AddRelation( AccessibleRelation( AccessibleRelationType_MEMBER_OF, aSequence ) );
546
0
        }
547
0
        else
548
0
        {
549
0
            pRelationSetHelper->AddRelation( AccessibleRelation( AccessibleRelationType_LABELED_BY, aSequence ) );
550
0
        }
551
0
    }
552
0
    return pRelationSetHelper;
553
0
}
554
555
OUString AccessibleControlShape::CreateAccessibleName()
556
0
{
557
0
    ensureControlModelAccess();
558
559
0
    OUString sName;
560
0
    sal_Int16 aAccessibleRole = getAccessibleRole();
561
0
    if ( aAccessibleRole != AccessibleRole::SHAPE
562
0
        && aAccessibleRole != AccessibleRole::RADIO_BUTTON  )
563
0
    {
564
0
        AccessibleControlShape* pCtlAccShape = GetLabeledByControlShape();
565
0
        if(pCtlAccShape)
566
0
        {
567
0
            sName = pCtlAccShape->CreateAccessibleName();
568
0
        }
569
0
    }
570
571
0
    if (sName.isEmpty())
572
0
    {
573
        // check if we can obtain the "Name" resp. "Label" property from the model
574
0
        const OUString aAccNameProperty = lcl_getPreferredAccNameProperty( m_xModelPropsMeta );
575
0
        sName = getControlModelStringProperty( aAccNameProperty );
576
0
        if ( !sName.getLength() )
577
0
        {   // no -> use the default
578
0
            sName = AccessibleShape::CreateAccessibleName();
579
0
        }
580
0
    }
581
582
    // now that somebody first asked us for our name, ensure that we are listening to name changes on the model
583
0
    m_bListeningForName = ensureListeningState( m_bListeningForName, true, lcl_getPreferredAccNameProperty( m_xModelPropsMeta ) );
584
585
0
    return sName;
586
0
}
587
588
void SAL_CALL AccessibleControlShape::disposing()
589
0
{
590
    // ensure we're not listening
591
0
    m_bListeningForName = ensureListeningState( m_bListeningForName, false, lcl_getPreferredAccNameProperty( m_xModelPropsMeta ) );
592
0
    m_bListeningForDesc = ensureListeningState( m_bListeningForDesc, false, DESC_PROPERTY_NAME );
593
594
0
    if ( m_bMultiplexingStates )
595
0
        stopStateMultiplexing( );
596
597
    // dispose the child cache/map
598
0
    m_pChildManager->dispose();
599
600
    // release the model
601
0
    m_xControlModel.clear();
602
0
    m_xModelPropsMeta.clear();
603
0
    m_aControlContext = WeakReference< XAccessibleContext >();
604
605
    // stop listening at the control container (should never be necessary here, but who knows...)
606
0
    if ( m_bWaitingForControl )
607
0
    {
608
0
        OSL_FAIL( "AccessibleControlShape::disposing: this should never happen!" );
609
0
        if (auto pWindow = maShapeTreeInfo.GetWindow())
610
0
        {
611
0
            Reference< XContainer > xContainer = lcl_getControlContainer( pWindow->GetOutDev(), maShapeTreeInfo.GetSdrView() );
612
0
            if ( xContainer.is() )
613
0
            {
614
0
                m_bWaitingForControl = false;
615
0
                xContainer->removeContainerListener( this );
616
0
            }
617
0
        }
618
0
    }
619
620
    // forward the disposal to our inner context
621
0
    if ( m_bDisposeNativeContext )
622
0
    {
623
        // don't listen for mode changes anymore
624
0
        Reference< XModeChangeBroadcaster > xControlModes( m_xUnoControl, UNO_QUERY );
625
0
        OSL_ENSURE( xControlModes.is(), "AccessibleControlShape::disposing: don't have a mode broadcaster anymore!" );
626
0
        if ( xControlModes.is() )
627
0
            xControlModes->removeModeChangeListener( this );
628
629
0
        if ( m_xControlContextComponent.is() )
630
0
            m_xControlContextComponent->dispose();
631
        // do _not_ clear m_xControlContextProxy! This has to be done in the dtor for correct ref-count handling
632
633
        // no need to dispose the proxy/inner context anymore
634
0
        m_bDisposeNativeContext = false;
635
0
    }
636
637
0
    m_xUnoControl.clear();
638
639
    // let the base do its stuff
640
0
    AccessibleShape::disposing();
641
0
}
642
643
bool AccessibleControlShape::ensureControlModelAccess()
644
0
{
645
0
    if ( m_xControlModel.is() )
646
0
        return true;
647
648
0
    try
649
0
    {
650
0
        Reference< XControlShape > xShape( mxShape, UNO_QUERY );
651
0
        if ( xShape.is() )
652
0
            m_xControlModel.set(xShape->getControl(), css::uno::UNO_QUERY);
653
654
0
        if ( m_xControlModel.is() )
655
0
            m_xModelPropsMeta = m_xControlModel->getPropertySetInfo();
656
0
    }
657
0
    catch( const Exception& )
658
0
    {
659
0
        TOOLS_WARN_EXCEPTION( "svx", "AccessibleControlShape::ensureControlModelAccess" );
660
0
    }
661
662
0
    return m_xControlModel.is();
663
0
}
664
665
void AccessibleControlShape::startStateMultiplexing()
666
0
{
667
0
    OSL_PRECOND( !m_bMultiplexingStates, "AccessibleControlShape::startStateMultiplexing: already multiplexing!" );
668
669
#if OSL_DEBUG_LEVEL > 0
670
    // we should have a control, and it should be in alive mode
671
    OSL_PRECOND(isControlInAliveMode(),
672
        "AccessibleControlShape::startStateMultiplexing: should be done in alive mode only!" );
673
#endif
674
    // we should have the native context of the control
675
0
    Reference< XAccessibleEventBroadcaster > xBroadcaster( m_aControlContext.get(), UNO_QUERY );
676
0
    OSL_ENSURE( xBroadcaster.is(), "AccessibleControlShape::startStateMultiplexing: no AccessibleEventBroadcaster on the native context!" );
677
678
0
    if ( xBroadcaster.is() )
679
0
    {
680
0
        xBroadcaster->addAccessibleEventListener( this );
681
0
        m_bMultiplexingStates = true;
682
0
    }
683
0
}
684
685
void AccessibleControlShape::stopStateMultiplexing()
686
0
{
687
0
    OSL_PRECOND( m_bMultiplexingStates, "AccessibleControlShape::stopStateMultiplexing: not multiplexing!" );
688
689
    // we should have the native context of the control
690
0
    Reference< XAccessibleEventBroadcaster > xBroadcaster( m_aControlContext.get(), UNO_QUERY );
691
0
    OSL_ENSURE( xBroadcaster.is(), "AccessibleControlShape::stopStateMultiplexing: no AccessibleEventBroadcaster on the native context!" );
692
693
0
    if ( xBroadcaster.is() )
694
0
    {
695
0
        xBroadcaster->removeAccessibleEventListener( this );
696
0
        m_bMultiplexingStates = false;
697
0
    }
698
0
}
699
700
OUString AccessibleControlShape::getControlModelStringProperty( const OUString& _rPropertyName ) const
701
0
{
702
0
    OUString sReturn;
703
0
    try
704
0
    {
705
0
        if ( const_cast< AccessibleControlShape* >( this )->ensureControlModelAccess() )
706
0
        {
707
0
            if ( !m_xModelPropsMeta.is() || m_xModelPropsMeta->hasPropertyByName( _rPropertyName ) )
708
                // ask only if a) the control does not have a PropertySetInfo object or b) it has, and the
709
                // property in question is available
710
0
                m_xControlModel->getPropertyValue( _rPropertyName ) >>= sReturn;
711
0
        }
712
0
    }
713
0
    catch( const Exception& )
714
0
    {
715
0
        TOOLS_WARN_EXCEPTION( "svx", "OAccessibleControlContext::getModelStringProperty" );
716
0
    }
717
0
    return sReturn;
718
0
}
719
720
void AccessibleControlShape::adjustAccessibleRole( )
721
0
{
722
    // if we're in design mode, we are a simple SHAPE, in alive mode, we use the role of our inner context
723
0
    if (!isControlInAliveMode())
724
0
        return;
725
726
    // we're in alive mode -> determine the role of the inner context
727
0
    Reference< XAccessibleContext > xNativeContext( m_aControlContext );
728
0
    OSL_PRECOND( xNativeContext.is(), "AccessibleControlShape::adjustAccessibleRole: no inner context!" );
729
0
    if ( xNativeContext.is() )
730
0
        SetAccessibleRole( xNativeContext->getAccessibleRole( ) );
731
0
}
732
733
#ifdef DBG_UTIL
734
735
bool AccessibleControlShape::SetState( sal_Int64 _nState )
736
{
737
    OSL_ENSURE(!isControlInAliveMode() || !isComposedState(_nState),
738
        "AccessibleControlShape::SetState: a state which should be determined by the control context is set from outside!" );
739
    return AccessibleShape::SetState( _nState );
740
}
741
#endif // DBG_UTIL
742
743
void AccessibleControlShape::initializeComposedState()
744
0
{
745
0
    if (!isControlInAliveMode())
746
        // no action necessary for design mode
747
0
        return;
748
749
    // we need to reset some states of the composed set, because they either do not apply
750
    // for controls in alive mode, or are in the responsibility of the UNO-control, anyway
751
0
    mnStateSet &= ~AccessibleStateType::ENABLED;       // this is controlled by the UNO-control
752
0
    mnStateSet &= ~AccessibleStateType::SENSITIVE;     // this is controlled by the UNO-control
753
0
    mnStateSet &= ~AccessibleStateType::FOCUSABLE;     // this is controlled by the UNO-control
754
0
    mnStateSet &= ~AccessibleStateType::SELECTABLE;    // this does not hold for an alive UNO-control
755
756
    // get my inner context
757
0
    Reference< XAccessibleContext > xInnerContext( m_aControlContext );
758
0
    OSL_PRECOND( xInnerContext.is(), "AccessibleControlShape::initializeComposedState: no inner context!" );
759
0
    if ( !xInnerContext.is() )
760
0
        return;
761
762
    // get all states of the inner context
763
0
    sal_Int64 nInnerStates( xInnerContext->getAccessibleStateSet() );
764
765
    // look which one are to be propagated to the composed context
766
0
    for ( int i = 0; i < 63; ++i )
767
0
    {
768
0
        sal_Int64 nState = sal_Int64(1) << i;
769
0
        if ( (nInnerStates & nState) && isComposedState( nState ) )
770
0
        {
771
0
            mnStateSet |= nState;
772
0
        }
773
0
    }
774
0
}
775
776
bool AccessibleControlShape::isControlInAliveMode()
777
0
{
778
0
    OSL_PRECOND(m_xUnoControl.is(), "AccessibleControlShape::isAliveMode: invalid control");
779
0
    return m_xUnoControl.is() && !m_xUnoControl->isDesignMode();
780
0
}
781
782
void SAL_CALL AccessibleControlShape::elementInserted( const css::container::ContainerEvent& _rEvent )
783
0
{
784
0
    Reference< XContainer > xContainer( _rEvent.Source, UNO_QUERY );
785
0
    Reference< XControl > xControl( _rEvent.Element, UNO_QUERY );
786
787
0
    OSL_ENSURE( xContainer.is() && xControl.is(),
788
0
        "AccessibleControlShape::elementInserted: invalid event description!" );
789
790
0
    if ( !xControl.is() )
791
0
        return;
792
793
0
    ensureControlModelAccess();
794
795
0
    Reference< XInterface > xNewNormalized( xControl->getModel(), UNO_QUERY );
796
0
    Reference< XInterface > xMyModelNormalized( m_xControlModel, UNO_QUERY );
797
0
    if ( !(xNewNormalized && xMyModelNormalized) )
798
0
        return;
799
800
    // now finally the control for the model we're responsible for has been inserted into the container
801
0
    Reference< XInterface > xKeepAlive( *this );
802
803
    // first, we're not interested in any more container events
804
0
    if ( xContainer.is() )
805
0
    {
806
0
        xContainer->removeContainerListener( this );
807
0
        m_bWaitingForControl = false;
808
0
    }
809
810
    // second, we need to replace ourself with a new version, which now can be based on the
811
    // control
812
0
    OSL_VERIFY( mpParent->ReplaceChild ( this, mxShape, 0, maShapeTreeInfo ) );
813
0
}
814
815
void SAL_CALL AccessibleControlShape::elementRemoved( const css::container::ContainerEvent& )
816
0
{
817
    // not interested in
818
0
}
819
820
void SAL_CALL AccessibleControlShape::elementReplaced( const css::container::ContainerEvent& )
821
0
{
822
    // not interested in
823
0
}
824
825
AccessibleControlShape* AccessibleControlShape::GetLabeledByControlShape( )
826
0
{
827
0
    if(m_xControlModel.is())
828
0
    {
829
0
        Any sCtlLabelBy;
830
        // get the "label by" property value of the control
831
0
        if (::comphelper::hasProperty(LABEL_CONTROL_PROPERTY_NAME, m_xControlModel))
832
0
        {
833
0
            sCtlLabelBy = m_xControlModel->getPropertyValue(LABEL_CONTROL_PROPERTY_NAME);
834
0
            if( sCtlLabelBy.hasValue() )
835
0
            {
836
0
                Reference< XPropertySet >  xAsSet (sCtlLabelBy, UNO_QUERY);
837
0
                AccessibleControlShape* pCtlAccShape = mpParent->GetAccControlShapeFromModel(xAsSet.get());
838
0
                return pCtlAccShape;
839
0
            }
840
0
        }
841
0
    }
842
0
    return nullptr;
843
0
}
844
845
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */