Files
core/chart2/source/controller/accessibility/AccessibleBase.cxx
Michael Weghorn 671b8cad97 a11y: Rename OAccessibleComponentHelper to OAccessible
The helper class used to implement less functionality
in the past and was used less than it is now.
But by now it is the central helper/base class used
by most a11y classes.

Since

    commit 22049f633b
    Author: Michael Weghorn <m.weghorn@posteo.de>
    Date:   Mon May 26 12:24:52 2025 +0200

        vcl a11y: Let OAccessibleComponentHelper implement XAccessible

it also implements the XAccessible interface.

That will allow to switch parameters and class members
from the abstract XAccessible interface to using this
more concrete C++ base class in various places.

Rename the class to OAccessible to account for the
new responsibilities, and adjust its doc accordingly.

Renaming source and header file will be done in a
separate commit to simplify (git) keeping track.

Change-Id: I33210b47cda5f71820a3c0fa657e4c3548d45732
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/186952
Tested-by: Jenkins
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
2025-06-26 13:09:41 +02:00

684 lines
20 KiB
C++

/* -*- 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 <AccessibleBase.hxx>
#include "AccessibleChartShape.hxx"
#include <ObjectHierarchy.hxx>
#include <ObjectIdentifier.hxx>
#include <ChartView.hxx>
#include <ChartController.hxx>
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/accessibility/AccessibleRole.hpp>
#include <com/sun/star/drawing/LineStyle.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <sal/log.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <vcl/window.hxx>
#include <vcl/settings.hxx>
#include <o3tl/functional.hxx>
#include <o3tl/safeint.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <algorithm>
#include <iterator>
#include "ChartElementFactory.hxx"
using namespace ::com::sun::star;
using namespace ::com::sun::star::accessibility;
using ::com::sun::star::uno::UNO_QUERY;
using ::com::sun::star::uno::Reference;
using ::osl::MutexGuard;
using ::osl::ClearableMutexGuard;
using ::com::sun::star::uno::Any;
namespace chart
{
/** @param bMayHaveChildren is false per default
*/
AccessibleBase::AccessibleBase(
AccessibleElementInfo aAccInfo,
bool bMayHaveChildren,
bool bAlwaysTransparent /* default: false */ ) :
m_bMayHaveChildren( bMayHaveChildren ),
m_bChildrenInitialized( false ),
m_nStateSet( 0 ),
m_aAccInfo(std::move( aAccInfo )),
m_bAlwaysTransparent( bAlwaysTransparent ),
m_bStateSetInitialized( false )
{
// initialize some states
m_nStateSet |= AccessibleStateType::ENABLED;
m_nStateSet |= AccessibleStateType::SHOWING;
m_nStateSet |= AccessibleStateType::VISIBLE;
m_nStateSet |= AccessibleStateType::SELECTABLE;
m_nStateSet |= AccessibleStateType::FOCUSABLE;
}
AccessibleBase::~AccessibleBase()
{
OSL_ASSERT(!isAlive());
}
bool AccessibleBase::NotifyEvent( EventType eEventType, const AccessibleUniqueId & rId )
{
if( GetId() == rId )
{
// event is addressed to this object
css::uno::Any aEmpty;
css::uno::Any aSelected;
aSelected <<= AccessibleStateType::SELECTED;
switch( eEventType )
{
case EventType::GOT_SELECTION:
{
AddState( AccessibleStateType::SELECTED );
NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, aEmpty, aSelected);
AddState( AccessibleStateType::FOCUSED );
aSelected <<= AccessibleStateType::FOCUSED;
NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, aEmpty, aSelected);
SAL_INFO("chart2.accessibility", "Selection acquired by: " << getAccessibleName());
}
break;
case EventType::LOST_SELECTION:
{
RemoveState( AccessibleStateType::SELECTED );
NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, aSelected, aEmpty);
AddState( AccessibleStateType::FOCUSED );
aSelected <<= AccessibleStateType::FOCUSED;
NotifyAccessibleEvent(AccessibleEventId::STATE_CHANGED, aSelected, aEmpty);
SAL_INFO("chart2.accessibility", "Selection lost by: " << getAccessibleName());
}
break;
}
return true;
}
else if( m_bMayHaveChildren )
{
bool bStop = false;
ClearableMutexGuard aGuard( m_aMutex );
// make local copy for notification
std::vector<rtl::Reference<AccessibleBase>> aLocalChildList(m_aChildList);
aGuard.clear();
for (auto const& localChild : aLocalChildList)
{
bStop = localChild->NotifyEvent(eEventType, rId);
if (bStop)
break;
}
return bStop;
}
return false;
}
void AccessibleBase::AddState( sal_Int64 aState )
{
ensureAlive();
m_nStateSet |= aState;
}
void AccessibleBase::RemoveState( sal_Int64 aState )
{
ensureAlive();
m_nStateSet &= ~aState;
}
bool AccessibleBase::UpdateChildren()
{
bool bMustUpdateChildren = false;
{
MutexGuard aGuard( m_aMutex );
if (!m_bMayHaveChildren || !isAlive())
return false;
bMustUpdateChildren = ( m_bMayHaveChildren &&
! m_bChildrenInitialized );
}
// update unguarded
if( bMustUpdateChildren )
m_bChildrenInitialized = ImplUpdateChildren();
return m_bChildrenInitialized;
}
bool AccessibleBase::ImplUpdateChildren()
{
bool bResult = false;
if( m_aAccInfo.m_spObjectHierarchy )
{
ObjectHierarchy::tChildContainer aModelChildren(
m_aAccInfo.m_spObjectHierarchy->getChildren( GetId() ));
std::vector< ChildOIDMap::key_type > aAccChildren;
aAccChildren.reserve( aModelChildren.size());
std::transform( m_aChildOIDMap.begin(), m_aChildOIDMap.end(),
std::back_inserter( aAccChildren ),
::o3tl::select1st< ChildOIDMap::value_type >() );
std::sort( aModelChildren.begin(), aModelChildren.end());
std::vector< ObjectIdentifier > aChildrenToRemove, aChildrenToAdd;
std::set_difference( aModelChildren.begin(), aModelChildren.end(),
aAccChildren.begin(), aAccChildren.end(),
std::back_inserter( aChildrenToAdd ));
std::set_difference( aAccChildren.begin(), aAccChildren.end(),
aModelChildren.begin(), aModelChildren.end(),
std::back_inserter( aChildrenToRemove ));
for (auto const& childToRemove : aChildrenToRemove)
{
RemoveChildByOId(childToRemove);
}
AccessibleElementInfo aAccInfo( GetInfo());
aAccInfo.m_pParent = this;
for (auto const& childToAdd : aChildrenToAdd)
{
aAccInfo.m_aOID = childToAdd;
if ( childToAdd.isAutoGeneratedObject() )
{
AddChild( ChartElementFactory::CreateChartElement( aAccInfo ).get() );
}
else if ( childToAdd.isAdditionalShape() )
{
AddChild( new AccessibleChartShape( aAccInfo ) );
}
}
bResult = true;
}
return bResult;
}
void AccessibleBase::AddChild( AccessibleBase * pChild )
{
OSL_ENSURE( pChild != nullptr, "Invalid Child" );
if( !pChild )
return;
ClearableMutexGuard aGuard( m_aMutex );
rtl::Reference<AccessibleBase> xChild(pChild);
m_aChildList.push_back( xChild );
m_aChildOIDMap[ pChild->GetId() ] = xChild;
// inform listeners of new child
if( m_bChildrenInitialized )
{
Any aEmpty, aNew;
aNew <<= uno::Reference<XAccessible>(xChild);
aGuard.clear();
NotifyAccessibleEvent(AccessibleEventId::CHILD, aEmpty, aNew);
}
}
void AccessibleBase::RemoveChildByOId( const ObjectIdentifier& rOId )
{
ClearableMutexGuard aGuard( m_aMutex );
ChildOIDMap::iterator aIt( m_aChildOIDMap.find( rOId ));
if( aIt == m_aChildOIDMap.end())
return;
rtl::Reference<AccessibleBase> xChild(aIt->second);
// remove from map
m_aChildOIDMap.erase( aIt );
// search child in vector
auto aVecIter = std::find(m_aChildList.begin(), m_aChildList.end(), xChild);
OSL_ENSURE( aVecIter != m_aChildList.end(),
"Inconsistent ChildMap" );
// remove child from vector
m_aChildList.erase( aVecIter );
bool bInitialized = m_bChildrenInitialized;
// call listeners unguarded
aGuard.clear();
// inform listeners of removed child
if( bInitialized )
{
Any aEmpty, aOld;
aOld <<= uno::Reference<XAccessible>(xChild);
NotifyAccessibleEvent(AccessibleEventId::CHILD, aOld, aEmpty);
}
// dispose the child
if (xChild.is())
xChild->dispose();
}
awt::Point AccessibleBase::GetUpperLeftOnScreen() const
{
awt::Point aResult;
if( m_aAccInfo.m_pParent )
{
ClearableMutexGuard aGuard( m_aMutex );
AccessibleBase * pParent = m_aAccInfo.m_pParent;
aGuard.clear();
if( pParent )
{
aResult = pParent->GetUpperLeftOnScreen();
}
else
OSL_FAIL( "Default position used is probably incorrect." );
}
return aResult;
}
void AccessibleBase::KillAllChildren()
{
ClearableMutexGuard aGuard( m_aMutex );
// make local copy for notification, and remove all children
std::vector<rtl::Reference<AccessibleBase>> aLocalChildList;
aLocalChildList.swap( m_aChildList );
m_aChildOIDMap.clear();
aGuard.clear();
// call dispose for all children
// and notify listeners
Any aEmpty, aOld;
for (auto const& localChild : aLocalChildList)
{
aOld <<= uno::Reference<XAccessible>(localChild);
NotifyAccessibleEvent(AccessibleEventId::CHILD, aOld, aEmpty);
if (localChild.is())
localChild->dispose();
}
m_bChildrenInitialized = false;
}
void AccessibleBase::SetInfo( const AccessibleElementInfo & rNewInfo )
{
m_aAccInfo = rNewInfo;
if( m_bMayHaveChildren )
{
KillAllChildren();
}
NotifyAccessibleEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN, uno::Any(), uno::Any());
}
// ________ (XComponent::dispose) ________
void SAL_CALL AccessibleBase::disposing()
{
{
MutexGuard aGuard(m_aMutex);
OSL_ENSURE(isAlive(), "dispose() called twice");
OAccessible::disposing();
// reset pointers
m_aAccInfo.m_pWindow.reset();
m_aAccInfo.m_pParent = nullptr;
m_nStateSet = AccessibleStateType::DEFUNC;
}
// call listeners unguarded
if( m_bMayHaveChildren )
{
KillAllChildren();
}
else
OSL_ENSURE( m_aChildList.empty(), "Child list should be empty" );
}
// ________ AccessibleBase::XAccessibleContext ________
sal_Int64 SAL_CALL AccessibleBase::getAccessibleChildCount()
{
ClearableMutexGuard aGuard( m_aMutex );
if (!m_bMayHaveChildren || !isAlive())
return 0;
bool bMustUpdateChildren = ( m_bMayHaveChildren &&
! m_bChildrenInitialized );
aGuard.clear();
// update unguarded
if( bMustUpdateChildren )
UpdateChildren();
return ImplGetAccessibleChildCount();
}
sal_Int64 AccessibleBase::ImplGetAccessibleChildCount() const
{
return m_aChildList.size();
}
Reference< XAccessible > SAL_CALL AccessibleBase::getAccessibleChild( sal_Int64 i )
{
ensureAlive();
Reference< XAccessible > xResult;
ClearableMutexGuard aGuard( m_aMutex );
bool bMustUpdateChildren = ( m_bMayHaveChildren &&
! m_bChildrenInitialized );
aGuard.clear();
if( bMustUpdateChildren )
UpdateChildren();
xResult.set( ImplGetAccessibleChildById( i ));
return xResult;
}
Reference< XAccessible > AccessibleBase::ImplGetAccessibleChildById( sal_Int64 i ) const
{
rtl::Reference<AccessibleBase> xResult;
MutexGuard aGuard( m_aMutex);
if( ! m_bMayHaveChildren ||
i < 0 ||
o3tl::make_unsigned( i ) >= m_aChildList.size() )
{
OUString aBuf = "Index " + OUString::number( i ) + " is invalid for range [ 0, " +
OUString::number( m_aChildList.size() - 1 ) +
" ]";
lang::IndexOutOfBoundsException aEx( aBuf,
const_cast< ::cppu::OWeakObject * >(
static_cast< const ::cppu::OWeakObject * >( this )));
throw aEx;
}
else
xResult = m_aChildList[i];
return xResult;
}
Reference< XAccessible > SAL_CALL AccessibleBase::getAccessibleParent()
{
ensureAlive();
Reference< XAccessible > aResult;
if( m_aAccInfo.m_pParent )
aResult.set( m_aAccInfo.m_pParent );
return aResult;
}
sal_Int64 SAL_CALL AccessibleBase::getAccessibleIndexInParent()
{
ensureAlive();
if( m_aAccInfo.m_spObjectHierarchy )
return m_aAccInfo.m_spObjectHierarchy->getIndexInParent( GetId() );
return -1;
}
sal_Int16 SAL_CALL AccessibleBase::getAccessibleRole()
{
return AccessibleRole::SHAPE;
}
Reference< XAccessibleRelationSet > SAL_CALL AccessibleBase::getAccessibleRelationSet()
{
Reference< XAccessibleRelationSet > aResult;
return aResult;
}
sal_Int64 SAL_CALL AccessibleBase::getAccessibleStateSet()
{
if( ! m_bStateSetInitialized )
{
rtl::Reference< ::chart::ChartController > xSelSupp( GetInfo().m_xChartController );
if ( xSelSupp.is() )
{
ObjectIdentifier aOID( xSelSupp->getSelection() );
if ( aOID.isValid() && GetId() == aOID )
{
AddState( AccessibleStateType::SELECTED );
AddState( AccessibleStateType::FOCUSED );
}
}
m_bStateSetInitialized = true;
}
return m_nStateSet;
}
lang::Locale SAL_CALL AccessibleBase::getLocale()
{
ensureAlive();
return Application::GetSettings().GetLanguageTag().getLocale();
}
// ________ AccessibleBase::XAccessibleComponent ________
Reference< XAccessible > SAL_CALL AccessibleBase::getAccessibleAtPoint( const awt::Point& aPoint )
{
ensureAlive();
rtl::Reference< AccessibleBase > aResult;
awt::Rectangle aRect( implGetBounds());
// children are positioned relative to this object, so translate bound rect
aRect.X = 0;
aRect.Y = 0;
// children must be inside the own bound rect
if( ( aRect.X <= aPoint.X && aPoint.X <= (aRect.X + aRect.Width) ) &&
( aRect.Y <= aPoint.Y && aPoint.Y <= (aRect.Y + aRect.Height)))
{
ClearableMutexGuard aGuard( m_aMutex );
std::vector<rtl::Reference<AccessibleBase>> aLocalChildList(m_aChildList);
aGuard.clear();
for (const rtl::Reference<AccessibleBase>& xLocalChild : aLocalChildList)
{
if (xLocalChild.is())
{
aRect = xLocalChild->implGetBounds();
if( ( aRect.X <= aPoint.X && aPoint.X <= (aRect.X + aRect.Width) ) &&
( aRect.Y <= aPoint.Y && aPoint.Y <= (aRect.Y + aRect.Height)))
{
aResult = xLocalChild;
break;
}
}
}
}
return aResult;
}
css::awt::Rectangle AccessibleBase::implGetBounds()
{
rtl::Reference<ChartView> pChartView = m_aAccInfo.m_xView.get();
if( pChartView )
{
VclPtr<vcl::Window> pWindow = m_aAccInfo.m_pWindow;
awt::Rectangle aLogicRect( pChartView->getRectangleOfObject( m_aAccInfo.m_aOID.getObjectCID() ));
if( pWindow )
{
tools::Rectangle aRect( aLogicRect.X, aLogicRect.Y,
aLogicRect.X + aLogicRect.Width,
aLogicRect.Y + aLogicRect.Height );
SolarMutexGuard aSolarGuard;
aRect = pWindow->LogicToPixel( aRect );
// aLogicRect is relative to the page, but we need a value relative
// to the parent object
awt::Point aParentLocOnScreen;
uno::Reference< XAccessibleComponent > xParent( getAccessibleParent(), uno::UNO_QUERY );
if( xParent.is() )
aParentLocOnScreen = xParent->getLocationOnScreen();
awt::Point aULOnScreen = GetUpperLeftOnScreen();
awt::Point aOffset( aParentLocOnScreen.X - aULOnScreen.X,
aParentLocOnScreen.Y - aULOnScreen.Y );
return awt::Rectangle( aRect.Left() - aOffset.X, aRect.Top() - aOffset.Y,
aRect.getOpenWidth(), aRect.getOpenHeight());
}
}
return awt::Rectangle();
}
void SAL_CALL AccessibleBase::grabFocus()
{
ensureAlive();
rtl::Reference< ::chart::ChartController > xSelSupp( GetInfo().m_xChartController );
if ( xSelSupp.is() )
{
xSelSupp->select( GetId().getAny() );
}
}
sal_Int32 SAL_CALL AccessibleBase::getForeground()
{
return sal_Int32(getColor( ACC_BASE_FOREGROUND ));
}
sal_Int32 SAL_CALL AccessibleBase::getBackground()
{
return sal_Int32(getColor( ACC_BASE_BACKGROUND ));
}
Color AccessibleBase::getColor( eColorType eColType )
{
Color nResult = COL_TRANSPARENT;
if( m_bAlwaysTransparent )
return nResult;
ObjectIdentifier aOID( m_aAccInfo.m_aOID );
ObjectType eType( aOID.getObjectType() );
Reference< beans::XPropertySet > xObjProp;
OUString aObjectCID = aOID.getObjectCID();
if( eType == OBJECTTYPE_LEGEND_ENTRY )
{
// for colors get the data series/point properties
std::u16string_view aParentParticle( ObjectIdentifier::getFullParentParticle( aObjectCID ));
aObjectCID = ObjectIdentifier::createClassifiedIdentifierForParticle( aParentParticle );
}
xObjProp =
ObjectIdentifier::getObjectPropertySet(
aObjectCID, m_aAccInfo.m_xChartDocument );
if( xObjProp.is())
{
try
{
OUString aPropName;
OUString aStylePropName;
switch( eType )
{
case OBJECTTYPE_LEGEND_ENTRY:
case OBJECTTYPE_DATA_SERIES:
case OBJECTTYPE_DATA_POINT:
if( eColType == ACC_BASE_FOREGROUND )
{
aPropName = "BorderColor";
aStylePropName = "BorderTransparency";
}
else
{
aPropName = "Color";
aStylePropName = "Transparency";
}
break;
default:
if( eColType == ACC_BASE_FOREGROUND )
{
aPropName = "LineColor";
aStylePropName = "LineTransparence";
}
else
{
aPropName = "FillColor";
aStylePropName = "FillTransparence";
}
break;
}
bool bTransparent = m_bAlwaysTransparent;
Reference< beans::XPropertySetInfo > xInfo = xObjProp->getPropertySetInfo();
if( xInfo.is() &&
xInfo->hasPropertyByName( aStylePropName ))
{
if( eColType == ACC_BASE_FOREGROUND )
{
drawing::LineStyle aLStyle = drawing::LineStyle_SOLID;
if( xObjProp->getPropertyValue( aStylePropName ) >>= aLStyle )
bTransparent = (aLStyle == drawing::LineStyle_NONE);
}
else
{
drawing::FillStyle aFStyle = drawing::FillStyle_SOLID;
if( xObjProp->getPropertyValue( aStylePropName ) >>= aFStyle )
bTransparent = (aFStyle == drawing::FillStyle_NONE);
}
}
if( !bTransparent &&
xInfo.is() &&
xInfo->hasPropertyByName( aPropName ))
{
xObjProp->getPropertyValue( aPropName ) >>= nResult;
}
}
catch( const uno::Exception & )
{
DBG_UNHANDLED_EXCEPTION("chart2");
}
}
return nResult;
}
} // namespace chart
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */