Coverage Report

Created: 2025-12-08 09:28

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" reference (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< XAccessible > xControlAccessible( xControlModes, UNO_QUERY );
205
0
            Reference< XAccessibleContext > xNativeControlContext;
206
0
            if ( xControlAccessible.is() )
207
0
                xNativeControlContext = xControlAccessible->getAccessibleContext();
208
0
            OSL_ENSURE( xNativeControlContext.is(), "AccessibleControlShape::Init: no AccessibleContext for the control!" );
209
0
            m_aControlContext = WeakReference< XAccessibleContext >( xNativeControlContext );
210
211
            // add as listener to the context - we want to multiplex some states
212
0
            if (isControlInAliveMode() && xNativeControlContext.is() )
213
0
            {   // (but only in alive mode)
214
0
                startStateMultiplexing( );
215
0
            }
216
217
            // now that we have all information about our control, do some adjustments
218
0
            adjustAccessibleRole();
219
0
            initializeComposedState();
220
221
            // some initialization for our child manager, which is used in alive mode only
222
0
            if (isControlInAliveMode())
223
0
            {
224
0
                sal_Int64 nStates( getAccessibleStateSet( ) );
225
0
                m_pChildManager->setTransientChildren( nStates & AccessibleStateType::MANAGES_DESCENDANTS );
226
0
            }
227
228
            // finally, aggregate a proxy for the control context
229
            // first a factory for the proxy
230
0
            Reference< XProxyFactory > xFactory = ProxyFactory::create( comphelper::getProcessComponentContext() );
231
            // then the proxy itself
232
0
            if ( xNativeControlContext.is() )
233
0
            {
234
0
                m_xControlContextProxy = xFactory->createProxy( xNativeControlContext );
235
0
                m_xControlContextTypeAccess.set( xNativeControlContext, UNO_QUERY_THROW );
236
0
                m_xControlContextComponent.set( xNativeControlContext, UNO_QUERY_THROW );
237
238
                // aggregate the proxy
239
0
                osl_atomic_increment( &m_refCount );
240
0
                if ( m_xControlContextProxy.is() )
241
0
                {
242
                    // At this point in time, the proxy has a ref count of exactly one - in m_xControlContextProxy.
243
                    // Remember to _not_ reset this member unless the delegator of the proxy has been reset, too!
244
0
                    m_xControlContextProxy->setDelegator( *this );
245
0
                }
246
0
                osl_atomic_decrement( &m_refCount );
247
248
0
                m_bDisposeNativeContext = true;
249
250
                // Finally, we need to add ourself as mode listener to the control. In case the mode switches,
251
                // we need to dispose ourself.
252
0
                xControlModes->addModeChangeListener( this );
253
0
            }
254
0
        }
255
0
    }
256
0
    catch( const Exception& )
257
0
    {
258
0
        OSL_FAIL( "AccessibleControlShape::Init: could not \"aggregate\" the controls XAccessibleContext!" );
259
0
    }
260
0
}
261
262
void SAL_CALL AccessibleControlShape::grabFocus()
263
0
{
264
0
    if (!m_xUnoControl.is() || !isControlInAliveMode())
265
0
    {
266
        // in design mode, we simply forward the request to the base class
267
0
        AccessibleShape::grabFocus();
268
0
    }
269
0
    else
270
0
    {
271
0
        Reference< XWindow > xWindow( m_xUnoControl, UNO_QUERY );
272
0
        OSL_ENSURE( xWindow.is(), "AccessibleControlShape::grabFocus: invalid control!" );
273
0
        if ( xWindow.is() )
274
0
            xWindow->setFocus();
275
0
    }
276
0
}
277
278
OUString SAL_CALL AccessibleControlShape::getImplementationName()
279
0
{
280
0
    return u"com.sun.star.comp.accessibility.AccessibleControlShape"_ustr;
281
0
}
282
283
OUString AccessibleControlShape::CreateAccessibleBaseName()
284
0
{
285
0
    OUString sName;
286
287
0
    ShapeTypeId nShapeType = ShapeTypeHandler::Instance().GetTypeId (mxShape);
288
0
    switch (nShapeType)
289
0
    {
290
0
        case DRAWING_CONTROL:
291
0
            sName = "ControlShape";
292
0
            break;
293
0
        default:
294
0
            sName = "UnknownAccessibleControlShape";
295
0
            if (mxShape.is())
296
0
                sName += ": " + mxShape->getShapeType();
297
0
    }
298
299
0
    return sName;
300
0
}
301
302
OUString
303
    AccessibleControlShape::CreateAccessibleDescription()
304
0
{
305
0
    DescriptionGenerator aDG (mxShape);
306
0
    ShapeTypeId nShapeType = ShapeTypeHandler::Instance().GetTypeId (mxShape);
307
0
    switch (nShapeType)
308
0
    {
309
0
        case DRAWING_CONTROL:
310
0
        {
311
            // check if we can obtain the "Desc" property from the model
312
0
            OUString sDesc( getControlModelStringProperty( DESC_PROPERTY_NAME ) );
313
0
            if ( sDesc.isEmpty() )
314
0
            {   // no -> use the default
315
0
                aDG.Initialize (STR_ObjNameSingulUno);
316
0
                aDG.AddProperty (u"ControlBackground"_ustr, DescriptionGenerator::PropertyType::Color);
317
0
                aDG.AddProperty ( u"ControlBorder"_ustr, DescriptionGenerator::PropertyType::Integer);
318
0
            }
319
            // ensure that we are listening to the Name property
320
0
            m_bListeningForDesc = ensureListeningState( m_bListeningForDesc, true, DESC_PROPERTY_NAME );
321
0
        }
322
0
        break;
323
324
0
        default:
325
0
            aDG.Initialize (u"Unknown accessible control shape");
326
0
            if (mxShape.is())
327
0
            {
328
0
                aDG.AppendString (u"service name=");
329
0
                aDG.AppendString (mxShape->getShapeType());
330
0
            }
331
0
    }
332
333
0
    return aDG();
334
0
}
335
336
IMPLEMENT_FORWARD_REFCOUNT( AccessibleControlShape, AccessibleShape )
337
IMPLEMENT_GET_IMPLEMENTATION_ID( AccessibleControlShape )
338
339
void SAL_CALL AccessibleControlShape::propertyChange( const PropertyChangeEvent& _rEvent )
340
0
{
341
0
    ::osl::MutexGuard aGuard( m_aMutex );
342
343
    // check if it is the name or the description
344
0
    if  (   _rEvent.PropertyName == NAME_PROPERTY_NAME
345
0
            ||  _rEvent.PropertyName == LABEL_PROPERTY_NAME )
346
0
    {
347
0
        SetAccessibleName(
348
0
            CreateAccessibleName(),
349
0
            AccessibleContextBase::AutomaticallyCreated);
350
0
    }
351
0
    else if ( _rEvent.PropertyName == DESC_PROPERTY_NAME )
352
0
    {
353
0
        SetAccessibleDescription(
354
0
            CreateAccessibleDescription(),
355
0
            AccessibleContextBase::AutomaticallyCreated);
356
0
    }
357
#if OSL_DEBUG_LEVEL > 0
358
    else
359
    {
360
        OSL_FAIL( "AccessibleControlShape::propertyChange: where did this come from?" );
361
    }
362
#endif
363
0
}
364
365
Any SAL_CALL AccessibleControlShape::queryInterface( const Type& _rType )
366
0
{
367
0
    Any aReturn = AccessibleShape::queryInterface( _rType );
368
0
    if ( !aReturn.hasValue() )
369
0
    {
370
0
        aReturn = AccessibleControlShape_Base::queryInterface( _rType );
371
0
        if ( !aReturn.hasValue() && m_xControlContextProxy.is() )
372
0
            aReturn = m_xControlContextProxy->queryAggregation( _rType );
373
0
    }
374
0
    return aReturn;
375
0
}
376
377
Sequence< Type > SAL_CALL AccessibleControlShape::getTypes()
378
0
{
379
0
    Sequence< Type > aShapeTypes = AccessibleShape::getTypes();
380
0
    Sequence< Type > aOwnTypes = AccessibleControlShape_Base::getTypes();
381
382
0
    Sequence< Type > aAggregateTypes;
383
0
    if ( m_xControlContextTypeAccess.is() )
384
0
        aAggregateTypes = m_xControlContextTypeAccess->getTypes();
385
386
    // remove duplicates
387
0
    return comphelper::combineSequences(comphelper::concatSequences( aShapeTypes, aOwnTypes), aAggregateTypes );
388
0
}
389
390
void SAL_CALL AccessibleControlShape::notifyEvent( const AccessibleEventObject& _rEvent )
391
0
{
392
0
    if ( AccessibleEventId::STATE_CHANGED == _rEvent.EventId )
393
0
    {
394
        // multiplex this change
395
0
        sal_Int64 nLostState( 0 ), nGainedState( 0 );
396
0
        _rEvent.OldValue >>= nLostState;
397
0
        _rEvent.NewValue >>= nGainedState;
398
399
        // don't multiplex states which the inner context is not responsible for
400
0
        if  ( isComposedState( nLostState ) )
401
0
            AccessibleShape::ResetState( nLostState );
402
403
0
        if  ( isComposedState( nGainedState ) )
404
0
            AccessibleShape::SetState( nGainedState );
405
0
    }
406
0
    else
407
0
    {
408
0
        AccessibleEventObject aTranslatedEvent( _rEvent );
409
410
0
        {
411
0
            ::osl::MutexGuard aGuard( m_aMutex );
412
413
            // let the child manager translate the event
414
0
            aTranslatedEvent.Source = *this;
415
0
            m_pChildManager->translateAccessibleEvent( _rEvent, aTranslatedEvent );
416
417
            // see if any of these notifications affect our child manager
418
0
            m_pChildManager->handleChildNotification( _rEvent );
419
0
        }
420
421
0
        NotifyAccessibleEvent(_rEvent.EventId, _rEvent.OldValue, _rEvent.NewValue,
422
0
                              _rEvent.IndexHint);
423
0
    }
424
0
}
425
426
void SAL_CALL AccessibleControlShape::modeChanged(const ModeChangeEvent& rSource)
427
0
{
428
    // did it come from our inner context (the real one, not it's proxy!)?
429
0
    SAL_INFO("sw.uno", "AccessibleControlShape::modeChanged");
430
0
    Reference<XControl> xSource(rSource.Source, UNO_QUERY); // for faster compare
431
0
    if(xSource.get() != m_xUnoControl.get())
432
0
    {
433
0
        SAL_WARN("sw.uno", "AccessibleControlShape::modeChanged: where did this come from?");
434
0
        return;
435
0
    }
436
0
    SolarMutexGuard g;
437
    // If our "pseudo-aggregated" inner context does not live anymore,
438
    // we don't want to live, too.  This is accomplished by asking our
439
    // parent to replace this object with a new one.  Disposing this
440
    // object and sending notifications about the replacement are in
441
    // the responsibility of our parent.
442
0
    const bool bReplaced = mpParent->ReplaceChild(this, mxShape, 0, maShapeTreeInfo);
443
0
    SAL_WARN_IF(!bReplaced, "sw.uno", "AccessibleControlShape::modeChanged: replacing ourselves away did fail");
444
0
}
445
446
void SAL_CALL AccessibleControlShape::disposing (const EventObject& _rSource)
447
0
{
448
0
    AccessibleShape::disposing( _rSource );
449
0
}
450
451
bool AccessibleControlShape::ensureListeningState(
452
        const bool _bCurrentlyListening, const bool _bNeedNewListening,
453
        const OUString& _rPropertyName )
454
0
{
455
0
    if ( ( _bCurrentlyListening == _bNeedNewListening ) || !ensureControlModelAccess() )
456
        //  nothing to do
457
0
        return _bCurrentlyListening;
458
459
0
    try
460
0
    {
461
0
        if ( !m_xModelPropsMeta.is() || m_xModelPropsMeta->hasPropertyByName( _rPropertyName ) )
462
0
        {
463
            // add or revoke as listener
464
0
            if ( _bNeedNewListening )
465
0
                m_xControlModel->addPropertyChangeListener( _rPropertyName, static_cast< XPropertyChangeListener* >( this ) );
466
0
            else
467
0
                m_xControlModel->removePropertyChangeListener( _rPropertyName, static_cast< XPropertyChangeListener* >( this ) );
468
0
        }
469
0
        else
470
0
            OSL_FAIL( "AccessibleControlShape::ensureListeningState: this property does not exist at this model!" );
471
0
    }
472
0
    catch( const Exception& )
473
0
    {
474
0
        OSL_FAIL( "AccessibleControlShape::ensureListeningState: could not change the listening state!" );
475
0
    }
476
477
0
    return _bNeedNewListening;
478
0
}
479
480
sal_Int64 SAL_CALL AccessibleControlShape::getAccessibleChildCount( )
481
0
{
482
0
    if ( !m_xUnoControl.is() )
483
0
        return 0;
484
0
    else if (!isControlInAliveMode())
485
        // no special action required when in design mode
486
0
        return AccessibleShape::getAccessibleChildCount( );
487
0
    else
488
0
    {
489
        // in alive mode, we have the full control over our children - they are determined by the children
490
        // of the context of our UNO control
491
0
        Reference< XAccessibleContext > xControlContext( m_aControlContext );
492
0
        OSL_ENSURE( xControlContext.is(), "AccessibleControlShape::getAccessibleChildCount: control context already dead! How this!" );
493
0
        return xControlContext.is() ? xControlContext->getAccessibleChildCount() : 0;
494
0
    }
495
0
}
496
497
Reference< XAccessible > SAL_CALL AccessibleControlShape::getAccessibleChild( sal_Int64 i )
498
0
{
499
0
    Reference< XAccessible > xChild;
500
0
    if ( !m_xUnoControl.is() )
501
0
    {
502
0
        throw IndexOutOfBoundsException();
503
0
    }
504
0
    if (!isControlInAliveMode())
505
0
    {
506
        // no special action required when in design mode - let the base class handle this
507
0
        xChild = AccessibleShape::getAccessibleChild( i );
508
0
    }
509
0
    else
510
0
    {
511
        // in alive mode, we have the full control over our children - they are determined by the children
512
        // of the context of our UNO control
513
514
0
        Reference< XAccessibleContext > xControlContext( m_aControlContext );
515
0
        OSL_ENSURE( xControlContext.is(), "AccessibleControlShape::getAccessibleChild: control context already dead! How this!" );
516
0
        if ( xControlContext.is() )
517
0
        {
518
0
            Reference< XAccessible > xInnerChild( xControlContext->getAccessibleChild( i ) );
519
0
            OSL_ENSURE( xInnerChild.is(), "AccessibleControlShape::getAccessibleChild: control context returned nonsense!" );
520
0
            if ( xInnerChild.is() )
521
0
            {
522
                // we need to wrap this inner child into an own implementation
523
0
                xChild = m_pChildManager->getAccessibleWrapperFor( xInnerChild );
524
0
            }
525
0
        }
526
0
    }
527
528
#if OSL_DEBUG_LEVEL > 0
529
    sal_Int64 nChildIndex = -1;
530
    Reference< XAccessibleContext > xContext;
531
    if ( xChild.is() )
532
        xContext = xChild->getAccessibleContext( );
533
    if ( xContext.is() )
534
        nChildIndex = xContext->getAccessibleIndexInParent( );
535
    SAL_WARN_IF( nChildIndex != i, "svx", "AccessibleControlShape::getAccessibleChild: index mismatch,"
536
            " nChildIndex=" << nChildIndex << " vs i=" << i );
537
#endif
538
0
    return xChild;
539
0
}
540
541
Reference< XAccessibleRelationSet > SAL_CALL AccessibleControlShape::getAccessibleRelationSet(  )
542
0
{
543
0
    rtl::Reference<utl::AccessibleRelationSetHelper> pRelationSetHelper = new utl::AccessibleRelationSetHelper;
544
0
    ensureControlModelAccess();
545
0
    AccessibleControlShape* pCtlAccShape = GetLabeledByControlShape();
546
0
    if(pCtlAccShape)
547
0
    {
548
0
        css::uno::Sequence<css::uno::Reference<XAccessible>> aSequence { pCtlAccShape };
549
0
        if( getAccessibleRole() == AccessibleRole::RADIO_BUTTON )
550
0
        {
551
0
            pRelationSetHelper->AddRelation( AccessibleRelation( AccessibleRelationType_MEMBER_OF, aSequence ) );
552
0
        }
553
0
        else
554
0
        {
555
0
            pRelationSetHelper->AddRelation( AccessibleRelation( AccessibleRelationType_LABELED_BY, aSequence ) );
556
0
        }
557
0
    }
558
0
    return pRelationSetHelper;
559
0
}
560
561
OUString AccessibleControlShape::CreateAccessibleName()
562
0
{
563
0
    ensureControlModelAccess();
564
565
0
    OUString sName;
566
0
    sal_Int16 aAccessibleRole = getAccessibleRole();
567
0
    if ( aAccessibleRole != AccessibleRole::SHAPE
568
0
        && aAccessibleRole != AccessibleRole::RADIO_BUTTON  )
569
0
    {
570
0
        AccessibleControlShape* pCtlAccShape = GetLabeledByControlShape();
571
0
        if(pCtlAccShape)
572
0
        {
573
0
            sName = pCtlAccShape->CreateAccessibleName();
574
0
        }
575
0
    }
576
577
0
    if (sName.isEmpty())
578
0
    {
579
        // check if we can obtain the "Name" resp. "Label" property from the model
580
0
        const OUString aAccNameProperty = lcl_getPreferredAccNameProperty( m_xModelPropsMeta );
581
0
        sName = getControlModelStringProperty( aAccNameProperty );
582
0
        if ( !sName.getLength() )
583
0
        {   // no -> use the default
584
0
            sName = AccessibleShape::CreateAccessibleName();
585
0
        }
586
0
    }
587
588
    // now that somebody first asked us for our name, ensure that we are listening to name changes on the model
589
0
    m_bListeningForName = ensureListeningState( m_bListeningForName, true, lcl_getPreferredAccNameProperty( m_xModelPropsMeta ) );
590
591
0
    return sName;
592
0
}
593
594
void SAL_CALL AccessibleControlShape::disposing()
595
0
{
596
    // ensure we're not listening
597
0
    m_bListeningForName = ensureListeningState( m_bListeningForName, false, lcl_getPreferredAccNameProperty( m_xModelPropsMeta ) );
598
0
    m_bListeningForDesc = ensureListeningState( m_bListeningForDesc, false, DESC_PROPERTY_NAME );
599
600
0
    if ( m_bMultiplexingStates )
601
0
        stopStateMultiplexing( );
602
603
    // dispose the child cache/map
604
0
    m_pChildManager->dispose();
605
606
    // release the model
607
0
    m_xControlModel.clear();
608
0
    m_xModelPropsMeta.clear();
609
0
    m_aControlContext = WeakReference< XAccessibleContext >();
610
611
    // stop listening at the control container (should never be necessary here, but who knows...)
612
0
    if ( m_bWaitingForControl )
613
0
    {
614
0
        OSL_FAIL( "AccessibleControlShape::disposing: this should never happen!" );
615
0
        if (auto pWindow = maShapeTreeInfo.GetWindow())
616
0
        {
617
0
            Reference< XContainer > xContainer = lcl_getControlContainer( pWindow->GetOutDev(), maShapeTreeInfo.GetSdrView() );
618
0
            if ( xContainer.is() )
619
0
            {
620
0
                m_bWaitingForControl = false;
621
0
                xContainer->removeContainerListener( this );
622
0
            }
623
0
        }
624
0
    }
625
626
    // forward the disposal to our inner context
627
0
    if ( m_bDisposeNativeContext )
628
0
    {
629
        // don't listen for mode changes anymore
630
0
        Reference< XModeChangeBroadcaster > xControlModes( m_xUnoControl, UNO_QUERY );
631
0
        OSL_ENSURE( xControlModes.is(), "AccessibleControlShape::disposing: don't have a mode broadcaster anymore!" );
632
0
        if ( xControlModes.is() )
633
0
            xControlModes->removeModeChangeListener( this );
634
635
0
        if ( m_xControlContextComponent.is() )
636
0
            m_xControlContextComponent->dispose();
637
        // do _not_ clear m_xControlContextProxy! This has to be done in the dtor for correct ref-count handling
638
639
        // no need to dispose the proxy/inner context anymore
640
0
        m_bDisposeNativeContext = false;
641
0
    }
642
643
0
    m_xUnoControl.clear();
644
645
    // let the base do its stuff
646
0
    AccessibleShape::disposing();
647
0
}
648
649
bool AccessibleControlShape::ensureControlModelAccess()
650
0
{
651
0
    if ( m_xControlModel.is() )
652
0
        return true;
653
654
0
    try
655
0
    {
656
0
        Reference< XControlShape > xShape( mxShape, UNO_QUERY );
657
0
        if ( xShape.is() )
658
0
            m_xControlModel.set(xShape->getControl(), css::uno::UNO_QUERY);
659
660
0
        if ( m_xControlModel.is() )
661
0
            m_xModelPropsMeta = m_xControlModel->getPropertySetInfo();
662
0
    }
663
0
    catch( const Exception& )
664
0
    {
665
0
        TOOLS_WARN_EXCEPTION( "svx", "AccessibleControlShape::ensureControlModelAccess" );
666
0
    }
667
668
0
    return m_xControlModel.is();
669
0
}
670
671
void AccessibleControlShape::startStateMultiplexing()
672
0
{
673
0
    OSL_PRECOND( !m_bMultiplexingStates, "AccessibleControlShape::startStateMultiplexing: already multiplexing!" );
674
675
#if OSL_DEBUG_LEVEL > 0
676
    // we should have a control, and it should be in alive mode
677
    OSL_PRECOND(isControlInAliveMode(),
678
        "AccessibleControlShape::startStateMultiplexing: should be done in alive mode only!" );
679
#endif
680
    // we should have the native context of the control
681
0
    Reference< XAccessibleEventBroadcaster > xBroadcaster( m_aControlContext.get(), UNO_QUERY );
682
0
    OSL_ENSURE( xBroadcaster.is(), "AccessibleControlShape::startStateMultiplexing: no AccessibleEventBroadcaster on the native context!" );
683
684
0
    if ( xBroadcaster.is() )
685
0
    {
686
0
        xBroadcaster->addAccessibleEventListener( this );
687
0
        m_bMultiplexingStates = true;
688
0
    }
689
0
}
690
691
void AccessibleControlShape::stopStateMultiplexing()
692
0
{
693
0
    OSL_PRECOND( m_bMultiplexingStates, "AccessibleControlShape::stopStateMultiplexing: not multiplexing!" );
694
695
    // we should have the native context of the control
696
0
    Reference< XAccessibleEventBroadcaster > xBroadcaster( m_aControlContext.get(), UNO_QUERY );
697
0
    OSL_ENSURE( xBroadcaster.is(), "AccessibleControlShape::stopStateMultiplexing: no AccessibleEventBroadcaster on the native context!" );
698
699
0
    if ( xBroadcaster.is() )
700
0
    {
701
0
        xBroadcaster->removeAccessibleEventListener( this );
702
0
        m_bMultiplexingStates = false;
703
0
    }
704
0
}
705
706
OUString AccessibleControlShape::getControlModelStringProperty( const OUString& _rPropertyName ) const
707
0
{
708
0
    OUString sReturn;
709
0
    try
710
0
    {
711
0
        if ( const_cast< AccessibleControlShape* >( this )->ensureControlModelAccess() )
712
0
        {
713
0
            if ( !m_xModelPropsMeta.is() || m_xModelPropsMeta->hasPropertyByName( _rPropertyName ) )
714
                // ask only if a) the control does not have a PropertySetInfo object or b) it has, and the
715
                // property in question is available
716
0
                m_xControlModel->getPropertyValue( _rPropertyName ) >>= sReturn;
717
0
        }
718
0
    }
719
0
    catch( const Exception& )
720
0
    {
721
0
        TOOLS_WARN_EXCEPTION( "svx", "OAccessibleControlContext::getModelStringProperty" );
722
0
    }
723
0
    return sReturn;
724
0
}
725
726
void AccessibleControlShape::adjustAccessibleRole( )
727
0
{
728
    // if we're in design mode, we are a simple SHAPE, in alive mode, we use the role of our inner context
729
0
    if (!isControlInAliveMode())
730
0
        return;
731
732
    // we're in alive mode -> determine the role of the inner context
733
0
    Reference< XAccessibleContext > xNativeContext( m_aControlContext );
734
0
    OSL_PRECOND( xNativeContext.is(), "AccessibleControlShape::adjustAccessibleRole: no inner context!" );
735
0
    if ( xNativeContext.is() )
736
0
        SetAccessibleRole( xNativeContext->getAccessibleRole( ) );
737
0
}
738
739
#ifdef DBG_UTIL
740
741
bool AccessibleControlShape::SetState( sal_Int64 _nState )
742
{
743
    OSL_ENSURE(!isControlInAliveMode() || !isComposedState(_nState),
744
        "AccessibleControlShape::SetState: a state which should be determined by the control context is set from outside!" );
745
    return AccessibleShape::SetState( _nState );
746
}
747
#endif // DBG_UTIL
748
749
void AccessibleControlShape::initializeComposedState()
750
0
{
751
0
    if (!isControlInAliveMode())
752
        // no action necessary for design mode
753
0
        return;
754
755
    // we need to reset some states of the composed set, because they either do not apply
756
    // for controls in alive mode, or are in the responsibility of the UNO-control, anyway
757
0
    mnStateSet &= ~AccessibleStateType::ENABLED;       // this is controlled by the UNO-control
758
0
    mnStateSet &= ~AccessibleStateType::SENSITIVE;     // this is controlled by the UNO-control
759
0
    mnStateSet &= ~AccessibleStateType::FOCUSABLE;     // this is controlled by the UNO-control
760
0
    mnStateSet &= ~AccessibleStateType::SELECTABLE;    // this does not hold for an alive UNO-control
761
762
    // get my inner context
763
0
    Reference< XAccessibleContext > xInnerContext( m_aControlContext );
764
0
    OSL_PRECOND( xInnerContext.is(), "AccessibleControlShape::initializeComposedState: no inner context!" );
765
0
    if ( !xInnerContext.is() )
766
0
        return;
767
768
    // get all states of the inner context
769
0
    sal_Int64 nInnerStates( xInnerContext->getAccessibleStateSet() );
770
771
    // look which one are to be propagated to the composed context
772
0
    for ( int i = 0; i < 63; ++i )
773
0
    {
774
0
        sal_Int64 nState = sal_Int64(1) << i;
775
0
        if ( (nInnerStates & nState) && isComposedState( nState ) )
776
0
        {
777
0
            mnStateSet |= nState;
778
0
        }
779
0
    }
780
0
}
781
782
bool AccessibleControlShape::isControlInAliveMode()
783
0
{
784
0
    OSL_PRECOND(m_xUnoControl.is(), "AccessibleControlShape::isAliveMode: invalid control");
785
0
    return m_xUnoControl.is() && !m_xUnoControl->isDesignMode();
786
0
}
787
788
void SAL_CALL AccessibleControlShape::elementInserted( const css::container::ContainerEvent& _rEvent )
789
0
{
790
0
    Reference< XContainer > xContainer( _rEvent.Source, UNO_QUERY );
791
0
    Reference< XControl > xControl( _rEvent.Element, UNO_QUERY );
792
793
0
    OSL_ENSURE( xContainer.is() && xControl.is(),
794
0
        "AccessibleControlShape::elementInserted: invalid event description!" );
795
796
0
    if ( !xControl.is() )
797
0
        return;
798
799
0
    ensureControlModelAccess();
800
801
0
    Reference< XInterface > xNewNormalized( xControl->getModel(), UNO_QUERY );
802
0
    Reference< XInterface > xMyModelNormalized( m_xControlModel, UNO_QUERY );
803
0
    if ( !(xNewNormalized && xMyModelNormalized) )
804
0
        return;
805
806
    // now finally the control for the model we're responsible for has been inserted into the container
807
0
    Reference< XInterface > xKeepAlive( *this );
808
809
    // first, we're not interested in any more container events
810
0
    if ( xContainer.is() )
811
0
    {
812
0
        xContainer->removeContainerListener( this );
813
0
        m_bWaitingForControl = false;
814
0
    }
815
816
    // second, we need to replace ourself with a new version, which now can be based on the
817
    // control
818
0
    OSL_VERIFY( mpParent->ReplaceChild ( this, mxShape, 0, maShapeTreeInfo ) );
819
0
}
820
821
void SAL_CALL AccessibleControlShape::elementRemoved( const css::container::ContainerEvent& )
822
0
{
823
    // not interested in
824
0
}
825
826
void SAL_CALL AccessibleControlShape::elementReplaced( const css::container::ContainerEvent& )
827
0
{
828
    // not interested in
829
0
}
830
831
AccessibleControlShape* AccessibleControlShape::GetLabeledByControlShape( )
832
0
{
833
0
    if(m_xControlModel.is())
834
0
    {
835
0
        Any sCtlLabelBy;
836
        // get the "label by" property value of the control
837
0
        if (::comphelper::hasProperty(LABEL_CONTROL_PROPERTY_NAME, m_xControlModel))
838
0
        {
839
0
            sCtlLabelBy = m_xControlModel->getPropertyValue(LABEL_CONTROL_PROPERTY_NAME);
840
0
            if( sCtlLabelBy.hasValue() )
841
0
            {
842
0
                Reference< XPropertySet >  xAsSet (sCtlLabelBy, UNO_QUERY);
843
0
                AccessibleControlShape* pCtlAccShape = mpParent->GetAccControlShapeFromModel(xAsSet.get());
844
0
                return pCtlAccShape;
845
0
            }
846
0
        }
847
0
    }
848
0
    return nullptr;
849
0
}
850
851
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */