diff options
Diffstat (limited to 'vcl/unx/gtk3/a11y')
24 files changed, 8031 insertions, 0 deletions
diff --git a/vcl/unx/gtk3/a11y/TODO b/vcl/unx/gtk3/a11y/TODO new file mode 100644 index 0000000000..1048bd96ef --- /dev/null +++ b/vcl/unx/gtk3/a11y/TODO @@ -0,0 +1,49 @@ +cws 'atkbridge' +#Issue number: i#47890# +Submitted by: mmeeks + +Hacked up prototype of atk bridge + + +Serious problems + + Threading/locking: + + incoming CORBA calls & the GDK lock + + how are these being processed & on what thread ? + + are we holding the GDK_THREADS lock ? + + can we even do that ? + + is it really necessary to be thread safe ? + + how does this work in combination with the (unsafe) GAIL code ? + + what should incoming CORBA calls be doing ? + + esp. since we can't tell if they're coming from + in-proc or not either [ though this is unlikely ] + + +Test: + + in-line text editing, does the TEXT_CHANGED signal get it right, + + why not copy/paste/delete etc. ? + + check vs. writer & other bits ... + + AtkSelection + + AtkHyper* + +* At-poke + + implement non-gui mode - for to-console event logging + + logging + + more detail from remaining events + + add a Tree navigation thing instead (?) + + poke a sub-child (?) + + embed a tree widget inside the tree view ? + + AtkHyperText testing (?) + + +Known bugs: + + AtkText + + selection interface - multiple selections ? + + word boundary issues + + copy AccessibleTextImpl.java's getAfterIndex eg. + + the 'getFoo' methods need to use UNO_QUERY_THROW & + throw an exception to avoid null pointer dereferences. + + AtkAttributeSet (etc.) + + AtkEditableText + + finish/test AtkTable + + HyperLink 'link_activated', HyperText 'link_selected' (?) + + tooltips create new toplevels with broken roles. diff --git a/vcl/unx/gtk3/a11y/atkaction.cxx b/vcl/unx/gtk3/a11y/atkaction.cxx new file mode 100644 index 0000000000..0a39d3bdf9 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkaction.cxx @@ -0,0 +1,269 @@ +/* -*- 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 "atkwrapper.hxx" + +#include <com/sun/star/accessibility/XAccessibleAction.hpp> +#include <com/sun/star/accessibility/XAccessibleKeyBinding.hpp> + +#include <com/sun/star/awt/Key.hpp> +#include <com/sun/star/awt/KeyModifier.hpp> + +#include <rtl/strbuf.hxx> +#include <algorithm> +#include <map> + +using namespace ::com::sun::star; + +// FIXME +static const gchar * +getAsConst( const OString& rString ) +{ + static const int nMax = 10; + static OString aUgly[nMax]; + static int nIdx = 0; + nIdx = (nIdx + 1) % nMax; + aUgly[nIdx] = rString; + return aUgly[ nIdx ].getStr(); +} + +/// @throws uno::RuntimeException +static css::uno::Reference<css::accessibility::XAccessibleAction> + getAction( AtkAction *action ) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( action ); + + if( pWrap ) + { + if( !pWrap->mpAction.is() ) + { + pWrap->mpAction.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpAction; + } + + return css::uno::Reference<css::accessibility::XAccessibleAction>(); +} + +extern "C" { + +static gboolean +action_wrapper_do_action (AtkAction *action, + gint i) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleAction> pAction + = getAction( action ); + if( pAction.is() ) + return pAction->doAccessibleAction( i ); + } + catch(const uno::Exception&) { + g_warning( "Exception in doAccessibleAction()" ); + } + + return FALSE; +} + +static gint +action_wrapper_get_n_actions (AtkAction *action) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleAction> pAction + = getAction( action ); + if( pAction.is() ) + return pAction->getAccessibleActionCount(); + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleActionCount()" ); + } + + return 0; +} + +static const gchar * +action_wrapper_get_description (AtkAction *, gint) +{ + // GAIL implement this only for cells + g_warning( "Not implemented: get_description()" ); + return ""; +} + +static const gchar * +action_wrapper_get_localized_name (AtkAction *, gint) +{ + // GAIL doesn't implement this as well + g_warning( "Not implemented: get_localized_name()" ); + return ""; +} + +static const gchar * +action_wrapper_get_name (AtkAction *action, + gint i) +{ + static std::map< OUString, const gchar * > aNameMap { + { "click", "click" }, + { "select", "click" } , + { "togglePopup", "push" } + }; + + try { + css::uno::Reference<css::accessibility::XAccessibleAction> pAction + = getAction( action ); + if( pAction.is() ) + { + std::map< OUString, const gchar * >::iterator iter; + + OUString aDesc( pAction->getAccessibleActionDescription( i ) ); + + iter = aNameMap.find( aDesc ); + if( iter != aNameMap.end() ) + return iter->second; + + std::pair< const OUString, const gchar * > aNewVal( aDesc, + g_strdup( OUStringToConstGChar(aDesc) ) ); + + if( aNameMap.insert( aNewVal ).second ) + return aNewVal.second; + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleActionDescription()" ); + } + + return ""; +} + +/* +* GNOME Expects a string in the format: +* +* <mnemonic>;<full-path>;<accelerator> +* +* The keybindings in <full-path> should be separated by ":" +*/ + +static void +appendKeyStrokes(OStringBuffer& rBuffer, const uno::Sequence< awt::KeyStroke >& rKeyStrokes) +{ + for( const auto& rKeyStroke : rKeyStrokes ) + { + if( rKeyStroke.Modifiers & awt::KeyModifier::SHIFT ) + rBuffer.append("<Shift>"); + if( rKeyStroke.Modifiers & awt::KeyModifier::MOD1 ) + rBuffer.append("<Control>"); + if( rKeyStroke.Modifiers & awt::KeyModifier::MOD2 ) + rBuffer.append("<Alt>"); + + if( ( rKeyStroke.KeyCode >= awt::Key::A ) && ( rKeyStroke.KeyCode <= awt::Key::Z ) ) + rBuffer.append( static_cast<char>( 'a' + ( rKeyStroke.KeyCode - awt::Key::A ) ) ); + else + { + char c = '\0'; + + switch( rKeyStroke.KeyCode ) + { + case awt::Key::TAB: c = '\t'; break; + case awt::Key::SPACE: c = ' '; break; + case awt::Key::ADD: c = '+'; break; + case awt::Key::SUBTRACT: c = '-'; break; + case awt::Key::MULTIPLY: c = '*'; break; + case awt::Key::DIVIDE: c = '/'; break; + case awt::Key::POINT: c = '.'; break; + case awt::Key::COMMA: c = ','; break; + case awt::Key::LESS: c = '<'; break; + case awt::Key::GREATER: c = '>'; break; + case awt::Key::EQUAL: c = '='; break; + case 0: + break; + default: + g_warning( "Unmapped KeyCode: %d", rKeyStroke.KeyCode ); + break; + } + + if( c != '\0' ) + rBuffer.append( c ); + else + { + // The KeyCode approach did not work, probably a non ascii character + // let's hope that there is a character given in KeyChar. + rBuffer.append(OUStringToOString(OUStringChar(rKeyStroke.KeyChar), RTL_TEXTENCODING_UTF8)); + } + } + } +} + +static const gchar * +action_wrapper_get_keybinding (AtkAction *action, + gint i) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleAction> pAction + = getAction( action ); + if( pAction.is() ) + { + uno::Reference< accessibility::XAccessibleKeyBinding > xBinding( pAction->getAccessibleActionKeyBinding( i )); + + if( xBinding.is() ) + { + OStringBuffer aRet; + + sal_Int32 nmax = std::min( xBinding->getAccessibleKeyBindingCount(), sal_Int32(3) ); + for( sal_Int32 n = 0; n < nmax; n++ ) + { + appendKeyStrokes( aRet, xBinding->getAccessibleKeyBinding( n ) ); + + if( n < 2 ) + aRet.append( ';' ); + } + + // !! FIXME !! remember keystroke in wrapper object ? + return getAsConst( aRet.makeStringAndClear() ); + } + } + } + catch(const uno::Exception&) { + g_warning( "Exception in get_keybinding()" ); + } + + return ""; +} + +static gboolean +action_wrapper_set_description (AtkAction *, gint, const gchar *) +{ + return FALSE; +} + +} // extern "C" + +void +actionIfaceInit (AtkActionIface *iface) +{ + g_return_if_fail (iface != nullptr); + + iface->do_action = action_wrapper_do_action; + iface->get_n_actions = action_wrapper_get_n_actions; + iface->get_description = action_wrapper_get_description; + iface->get_keybinding = action_wrapper_get_keybinding; + iface->get_name = action_wrapper_get_name; + iface->get_localized_name = action_wrapper_get_localized_name; + iface->set_description = action_wrapper_set_description; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkbridge.cxx b/vcl/unx/gtk3/a11y/atkbridge.cxx new file mode 100644 index 0000000000..c7cb32ca3c --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkbridge.cxx @@ -0,0 +1,32 @@ +/* -*- 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 <unx/gtk/atkbridge.hxx> + +#include "atkutil.hxx" + +bool InitAtkBridge() +{ + ooo_atk_util_ensure_event_listener(); + return true; +} + +void DeInitAtkBridge() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkcomponent.cxx b/vcl/unx/gtk3/a11y/atkcomponent.cxx new file mode 100644 index 0000000000..154f0117f1 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkcomponent.cxx @@ -0,0 +1,431 @@ +/* -*- 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 "atkwrapper.hxx" +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <sal/log.hxx> +#include <gtk/gtk.h> + +using namespace ::com::sun::star; + +static AtkObjectWrapper* getObjectWrapper(AtkComponent *pComponent) +{ + AtkObjectWrapper *pWrap = nullptr; + if (ATK_IS_OBJECT_WRAPPER(pComponent)) + pWrap = ATK_OBJECT_WRAPPER(pComponent); + else if (GTK_IS_DRAWING_AREA(pComponent)) //when using a GtkDrawingArea as a custom widget in welded gtk3 + { + GtkWidget* pDrawingArea = GTK_WIDGET(pComponent); + AtkObject* pAtkObject = gtk_widget_get_accessible(pDrawingArea); + pWrap = ATK_IS_OBJECT_WRAPPER(pAtkObject) ? ATK_OBJECT_WRAPPER(pAtkObject) : nullptr; + } + return pWrap; +} + +/// @throws uno::RuntimeException +static css::uno::Reference<css::accessibility::XAccessibleComponent> + getComponent(AtkObjectWrapper *pWrap) +{ + if (pWrap) + { + if (!pWrap->mpComponent.is()) + pWrap->mpComponent.set(pWrap->mpContext, css::uno::UNO_QUERY); + return pWrap->mpComponent; + } + + return css::uno::Reference<css::accessibility::XAccessibleComponent>(); +} + +static awt::Point +lcl_getLocationInWindow(AtkComponent* pAtkComponent, + css::uno::Reference<accessibility::XAccessibleComponent> const& xComponent) +{ + // calculate position in window by adding the component's position in the parent + // to the parent's position in the window (unless parent is a window itself) + awt::Point aPos = xComponent->getLocation(); + AtkObject* pParent = atk_object_get_parent(ATK_OBJECT(pAtkComponent)); + if (ATK_IS_COMPONENT(pParent) && pParent->role != AtkRole::ATK_ROLE_DIALOG + && pParent->role != AtkRole::ATK_ROLE_FILE_CHOOSER + && pParent->role != AtkRole::ATK_ROLE_FRAME + && pParent->role != AtkRole::ATK_ROLE_WINDOW) + { + int nX; + int nY; + atk_component_get_extents(ATK_COMPONENT(pParent), &nX, &nY, nullptr, nullptr, ATK_XY_WINDOW); + aPos.X += nX; + aPos.Y += nY; + } + + return aPos; +} + +/*****************************************************************************/ + +static awt::Point +translatePoint( AtkComponent* pAtkComponent, + css::uno::Reference<accessibility::XAccessibleComponent> const & pComponent, + gint x, gint y, AtkCoordType t) +{ + awt::Point aOrigin( 0, 0 ); + if( t == ATK_XY_SCREEN ) + aOrigin = pComponent->getLocationOnScreen(); + else if (t == ATK_XY_WINDOW) + aOrigin = lcl_getLocationInWindow(pAtkComponent, pComponent); + return awt::Point( x - aOrigin.X, y - aOrigin.Y ); +} + +/*****************************************************************************/ + +extern "C" { + +static gboolean +component_wrapper_grab_focus (AtkComponent *component) +{ + AtkObjectWrapper* obj = getObjectWrapper(component); + //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y + if (obj && obj->mpOrig) + return atk_component_grab_focus(ATK_COMPONENT(obj->mpOrig)); + + try + { + css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent + = getComponent(obj); + if( pComponent.is() ) + { + pComponent->grabFocus(); + return true; + } + } + catch( const uno::Exception & ) + { + g_warning( "Exception in grabFocus()" ); + } + + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +component_wrapper_contains (AtkComponent *component, + gint x, + gint y, + AtkCoordType coord_type) +{ + AtkObjectWrapper* obj = getObjectWrapper(component); + //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y + if (obj && obj->mpOrig) + return atk_component_contains(ATK_COMPONENT(obj->mpOrig), x, y, coord_type); + + try + { + css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent + = getComponent(obj); + if( pComponent.is() ) + return pComponent->containsPoint( + translatePoint(component, pComponent, x, y, coord_type)); + } + catch( const uno::Exception & ) + { + g_warning( "Exception in containsPoint()" ); + } + + return FALSE; +} + +/*****************************************************************************/ + +static AtkObject * +component_wrapper_ref_accessible_at_point (AtkComponent *component, + gint x, + gint y, + AtkCoordType coord_type) +{ + AtkObjectWrapper* obj = getObjectWrapper(component); + //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y + if (obj && obj->mpOrig) + return atk_component_ref_accessible_at_point(ATK_COMPONENT(obj->mpOrig), x, y, coord_type); + + try + { + css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent + = getComponent(obj); + + if( pComponent.is() ) + { + uno::Reference< accessibility::XAccessible > xAccessible = pComponent->getAccessibleAtPoint( + translatePoint(component, pComponent, x, y, coord_type)); + return atk_object_wrapper_ref( xAccessible ); + } + } + catch( const uno::Exception & ) + { + g_warning( "Exception in getAccessibleAtPoint()" ); + } + + return nullptr; +} + +/*****************************************************************************/ + +static void +component_wrapper_get_position (AtkComponent *component, + gint *x, + gint *y, + AtkCoordType coord_type) +{ + AtkObjectWrapper* obj = getObjectWrapper(component); + //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y + if (obj && obj->mpOrig) + { + atk_component_get_extents(ATK_COMPONENT(obj->mpOrig), x, y, nullptr, nullptr, coord_type); + return; + } + + *x = *y = -1; + + try + { + css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent + = getComponent(obj); + if( pComponent.is() ) + { + awt::Point aPos; + + if( coord_type == ATK_XY_SCREEN ) + aPos = pComponent->getLocationOnScreen(); + else if (coord_type == ATK_XY_WINDOW) + aPos = lcl_getLocationInWindow(component, pComponent); +#if ATK_CHECK_VERSION(2, 30, 0) + else if (coord_type == ATK_XY_PARENT) +#else + // ATK_XY_PARENT added in ATK 2.30, so can't use the constant here + else +#endif + aPos = pComponent->getLocation(); +#if ATK_CHECK_VERSION(2, 30, 0) + else + { + SAL_WARN("vcl.gtk", + "component_wrapper_get_position called with unknown AtkCoordType " + << coord_type); + return; + } +#endif + + *x = aPos.X; + *y = aPos.Y; + } + } + catch( const uno::Exception & ) + { + g_warning( "Exception in getLocation[OnScreen]()" ); + } +} + +/*****************************************************************************/ + +static void +component_wrapper_get_size (AtkComponent *component, + gint *width, + gint *height) +{ + AtkObjectWrapper* obj = getObjectWrapper(component); + //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y + if (obj && obj->mpOrig) + { + atk_component_get_extents(ATK_COMPONENT(obj->mpOrig), nullptr, nullptr, width, height, ATK_XY_WINDOW); + return; + } + + *width = *height = -1; + + try + { + css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent + = getComponent(obj); + if( pComponent.is() ) + { + awt::Size aSize = pComponent->getSize(); + *width = aSize.Width; + *height = aSize.Height; + } + } + catch( const uno::Exception & ) + { + g_warning( "Exception in getSize()" ); + } +} + +/*****************************************************************************/ + +static void +component_wrapper_get_extents (AtkComponent *component, + gint *x, + gint *y, + gint *width, + gint *height, + AtkCoordType coord_type) +{ + component_wrapper_get_position( component, x, y, coord_type ); + component_wrapper_get_size( component, width, height ); +} + +/*****************************************************************************/ + +static gboolean +component_wrapper_set_extents (AtkComponent *, gint, gint, gint, gint, AtkCoordType) +{ + g_warning( "AtkComponent::set_extents unimplementable" ); + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +component_wrapper_set_position (AtkComponent *, gint, gint, AtkCoordType) +{ + g_warning( "AtkComponent::set_position unimplementable" ); + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +component_wrapper_set_size (AtkComponent *, gint, gint) +{ + g_warning( "AtkComponent::set_size unimplementable" ); + return FALSE; +} + +/*****************************************************************************/ + +static AtkLayer +component_wrapper_get_layer (AtkComponent *component) +{ + AtkRole role = atk_object_get_role( ATK_OBJECT( component ) ); + AtkLayer layer = ATK_LAYER_WIDGET; + + switch (role) + { + case ATK_ROLE_POPUP_MENU: + case ATK_ROLE_MENU_ITEM: + case ATK_ROLE_CHECK_MENU_ITEM: + case ATK_ROLE_SEPARATOR: + case ATK_ROLE_LIST_ITEM: + layer = ATK_LAYER_POPUP; + break; + case ATK_ROLE_MENU: + { + AtkObject * parent = atk_object_get_parent( ATK_OBJECT( component ) ); + if( atk_object_get_role( parent ) != ATK_ROLE_MENU_BAR ) + layer = ATK_LAYER_POPUP; + } + break; + + case ATK_ROLE_LIST: + { + AtkObject * parent = atk_object_get_parent( ATK_OBJECT( component ) ); + if( atk_object_get_role( parent ) == ATK_ROLE_COMBO_BOX ) + layer = ATK_LAYER_POPUP; + } + break; + + default: + ; + } + + return layer; +} + +/*****************************************************************************/ + +static gint +component_wrapper_get_mdi_zorder (AtkComponent *) +{ + // only needed for ATK_LAYER_MDI (not used) or ATK_LAYER_WINDOW (inherited from GAIL) + return G_MININT; +} + +/*****************************************************************************/ + +// This code is mostly stolen from libgail .. + +static guint +component_wrapper_add_focus_handler (AtkComponent *component, + AtkFocusHandler handler) +{ + GSignalMatchType match_type; + gulong ret; + guint signal_id; + + match_type = GSignalMatchType(G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_FUNC); + signal_id = g_signal_lookup( "focus-event", ATK_TYPE_OBJECT ); + + ret = g_signal_handler_find( component, match_type, signal_id, 0, nullptr, + static_cast<gpointer>(&handler), nullptr); + if (!ret) + { + return g_signal_connect_closure_by_id (component, + signal_id, 0, + g_cclosure_new ( + G_CALLBACK (handler), nullptr, + nullptr), + FALSE); + } + else + { + return 0; + } +} + +/*****************************************************************************/ + +static void +component_wrapper_remove_focus_handler (AtkComponent *component, + guint handler_id) +{ + g_signal_handler_disconnect (component, handler_id); +} + +/*****************************************************************************/ + +} // extern "C" + +void +componentIfaceInit (AtkComponentIface *iface) +{ + g_return_if_fail (iface != nullptr); + + iface->add_focus_handler = component_wrapper_add_focus_handler; + iface->contains = component_wrapper_contains; + iface->get_extents = component_wrapper_get_extents; + iface->get_layer = component_wrapper_get_layer; + iface->get_mdi_zorder = component_wrapper_get_mdi_zorder; + iface->get_position = component_wrapper_get_position; + iface->get_size = component_wrapper_get_size; + iface->grab_focus = component_wrapper_grab_focus; + iface->ref_accessible_at_point = component_wrapper_ref_accessible_at_point; + iface->remove_focus_handler = component_wrapper_remove_focus_handler; + iface->set_extents = component_wrapper_set_extents; + iface->set_position = component_wrapper_set_position; + iface->set_size = component_wrapper_set_size; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkeditabletext.cxx b/vcl/unx/gtk3/a11y/atkeditabletext.cxx new file mode 100644 index 0000000000..f240c32324 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkeditabletext.cxx @@ -0,0 +1,193 @@ +/* -*- 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 "atkwrapper.hxx" +#include "atktextattributes.hxx" + +#include <com/sun/star/accessibility/XAccessibleEditableText.hpp> + +#include <string.h> + +using namespace ::com::sun::star; + +/// @throws uno::RuntimeException +static css::uno::Reference<css::accessibility::XAccessibleEditableText> + getEditableText( AtkEditableText *pEditableText ) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pEditableText ); + if( pWrap ) + { + if( !pWrap->mpEditableText.is() ) + { + pWrap->mpEditableText.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpEditableText; + } + + return css::uno::Reference<css::accessibility::XAccessibleEditableText>(); +} + +/*****************************************************************************/ + +extern "C" { + +static gboolean +editable_text_wrapper_set_run_attributes( AtkEditableText *text, + AtkAttributeSet *attribute_set, + gint nStartOffset, + gint nEndOffset) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleEditableText> + pEditableText = getEditableText( text ); + if( pEditableText.is() ) + { + uno::Sequence< beans::PropertyValue > aAttributeList; + + if( attribute_set_map_to_property_values( attribute_set, aAttributeList ) ) + return pEditableText->setAttributes(nStartOffset, nEndOffset, aAttributeList); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in setAttributes()" ); + } + + return FALSE; +} + +static void +editable_text_wrapper_set_text_contents( AtkEditableText *text, + const gchar *string ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleEditableText> + pEditableText = getEditableText( text ); + if( pEditableText.is() ) + { + OUString aString ( string, strlen(string), RTL_TEXTENCODING_UTF8 ); + pEditableText->setText( aString ); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in setText()" ); + } +} + +static void +editable_text_wrapper_insert_text( AtkEditableText *text, + const gchar *string, + gint length, + gint *pos ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleEditableText> + pEditableText = getEditableText( text ); + if( pEditableText.is() ) + { + OUString aString ( string, length, RTL_TEXTENCODING_UTF8 ); + if( pEditableText->insertText( aString, *pos ) ) + *pos += length; + } + } + catch(const uno::Exception&) { + g_warning( "Exception in insertText()" ); + } +} + +static void +editable_text_wrapper_cut_text( AtkEditableText *text, + gint start, + gint end ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleEditableText> + pEditableText = getEditableText( text ); + if( pEditableText.is() ) + pEditableText->cutText( start, end ); + } + catch(const uno::Exception&) { + g_warning( "Exception in cutText()" ); + } +} + +static void +editable_text_wrapper_delete_text( AtkEditableText *text, + gint start, + gint end ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleEditableText> + pEditableText = getEditableText( text ); + if( pEditableText.is() ) + pEditableText->deleteText( start, end ); + } + catch(const uno::Exception&) { + g_warning( "Exception in deleteText()" ); + } +} + +static void +editable_text_wrapper_paste_text( AtkEditableText *text, + gint pos ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleEditableText> + pEditableText = getEditableText( text ); + if( pEditableText.is() ) + pEditableText->pasteText( pos ); + } + catch(const uno::Exception&) { + g_warning( "Exception in pasteText()" ); + } +} + +static void +editable_text_wrapper_copy_text( AtkEditableText *text, + gint start, + gint end ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleEditableText> + pEditableText = getEditableText( text ); + if( pEditableText.is() ) + pEditableText->copyText( start, end ); + } + catch(const uno::Exception&) { + g_warning( "Exception in copyText()" ); + } +} + +} // extern "C" + +void +editableTextIfaceInit (AtkEditableTextIface *iface) +{ + g_return_if_fail (iface != nullptr); + + iface->set_text_contents = editable_text_wrapper_set_text_contents; + iface->insert_text = editable_text_wrapper_insert_text; + iface->copy_text = editable_text_wrapper_copy_text; + iface->cut_text = editable_text_wrapper_cut_text; + iface->delete_text = editable_text_wrapper_delete_text; + iface->paste_text = editable_text_wrapper_paste_text; + iface->set_run_attributes = editable_text_wrapper_set_run_attributes; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkfactory.cxx b/vcl/unx/gtk3/a11y/atkfactory.cxx new file mode 100644 index 0000000000..2fc407b7bc --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkfactory.cxx @@ -0,0 +1,186 @@ +/* -*- 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 <unx/gtk/gtkframe.hxx> +#include <vcl/window.hxx> +#include "atkwrapper.hxx" +#include "atkfactory.hxx" +#include "atkregistry.hxx" + +using namespace ::com::sun::star; + +extern "C" { + +/* + * Instances of this dummy object class are returned whenever we have to + * create an AtkObject, but can't touch the OOo object anymore since it + * is already disposed. + */ + +static AtkStateSet * +noop_wrapper_ref_state_set( AtkObject * ) +{ + AtkStateSet *state_set = atk_state_set_new(); + atk_state_set_add_state( state_set, ATK_STATE_DEFUNCT ); + return state_set; +} + +static void +atk_noop_object_wrapper_class_init(AtkNoOpObjectClass *klass) +{ + AtkObjectClass *atk_class = ATK_OBJECT_CLASS( klass ); + atk_class->ref_state_set = noop_wrapper_ref_state_set; +} + +static GType +atk_noop_object_wrapper_get_type() +{ + static GType type = 0; + + if (!type) + { + static const GTypeInfo typeInfo = + { + sizeof (AtkNoOpObjectClass), + nullptr, + nullptr, + reinterpret_cast<GClassInitFunc>(atk_noop_object_wrapper_class_init), + nullptr, + nullptr, + sizeof (AtkObjectWrapper), + 0, + nullptr, + nullptr + } ; + + type = g_type_register_static (ATK_TYPE_OBJECT, "OOoAtkNoOpObj", &typeInfo, GTypeFlags(0)) ; + } + return type; +} + +static AtkObject* +atk_noop_object_wrapper_new() +{ + AtkObject *accessible; + + accessible = static_cast<AtkObject *>(g_object_new (atk_noop_object_wrapper_get_type(), nullptr)); + g_return_val_if_fail (accessible != nullptr, nullptr); + + accessible->role = ATK_ROLE_INVALID; + accessible->layer = ATK_LAYER_INVALID; + + return accessible; +} + +/* + * The wrapper factory + */ + +static GType +wrapper_factory_get_accessible_type() +{ + return atk_object_wrapper_get_type(); +} + +static AtkObject* +wrapper_factory_create_accessible( GObject *obj ) +{ + GtkWidget* pEventBox = gtk_widget_get_parent(GTK_WIDGET(obj)); + + // gail_container_real_remove_gtk tries to re-instantiate an accessible + // for a widget that is about to vanish .. + if (!pEventBox) + return atk_noop_object_wrapper_new(); + + GtkWidget* pTopLevelGrid = gtk_widget_get_parent(pEventBox); + if (!pTopLevelGrid) + return atk_noop_object_wrapper_new(); + + GtkWidget* pTopLevel = gtk_widget_get_parent(pTopLevelGrid); + if (!pTopLevel) + return atk_noop_object_wrapper_new(); + + GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel); + g_return_val_if_fail(pFrame != nullptr, atk_noop_object_wrapper_new()); + + vcl::Window* pFrameWindow = pFrame->GetWindow(); + if( pFrameWindow ) + { + vcl::Window* pWindow = pFrameWindow; + + // skip accessible objects already exposed by the frame objects + if( WindowType::BORDERWINDOW == pWindow->GetType() ) + pWindow = pFrameWindow->GetAccessibleChildWindow(0); + + if( pWindow ) + { + uno::Reference< accessibility::XAccessible > xAccessible = pWindow->GetAccessible(); + if( xAccessible.is() ) + { + AtkObject *accessible = ooo_wrapper_registry_get( xAccessible ); + + if( accessible ) + g_object_ref( G_OBJECT(accessible) ); + else + accessible = atk_object_wrapper_new( xAccessible, gtk_widget_get_accessible(pTopLevel) ); + + return accessible; + } + } + } + + return atk_noop_object_wrapper_new(); +} + +AtkObject* ooo_fixed_get_accessible(GtkWidget *obj) +{ + return wrapper_factory_create_accessible(G_OBJECT(obj)); +} + +static void +wrapper_factory_class_init( AtkObjectFactoryClass *klass ) +{ + klass->create_accessible = wrapper_factory_create_accessible; + klass->get_accessible_type = wrapper_factory_get_accessible_type; +} + +GType +wrapper_factory_get_type() +{ + static GType t = 0; + + if (!t) { + static const GTypeInfo tinfo = + { + sizeof (AtkObjectFactoryClass), + nullptr, nullptr, reinterpret_cast<GClassInitFunc>(wrapper_factory_class_init), + nullptr, nullptr, sizeof (AtkObjectFactory), 0, nullptr, nullptr + }; + + t = g_type_register_static ( + ATK_TYPE_OBJECT_FACTORY, "OOoAtkObjectWrapperFactory", + &tinfo, GTypeFlags(0)); + } + + return t; +} + +} // extern C + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkfactory.hxx b/vcl/unx/gtk3/a11y/atkfactory.hxx new file mode 100644 index 0000000000..66c52eab76 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkfactory.hxx @@ -0,0 +1,30 @@ +/* -*- 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 . + */ + +#pragma once + +#include <atk/atk.h> + +extern "C" { + +GType wrapper_factory_get_type(); + +} // extern "C" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkhypertext.cxx b/vcl/unx/gtk3/a11y/atkhypertext.cxx new file mode 100644 index 0000000000..83f6817fc9 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkhypertext.cxx @@ -0,0 +1,277 @@ +/* -*- 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 "atkwrapper.hxx" + +#include <com/sun/star/accessibility/XAccessibleHypertext.hpp> + +using namespace ::com::sun::star; + +// ---------------------- AtkHyperlink ---------------------- + +namespace { + +struct HyperLink { + AtkHyperlink const atk_hyper_link; + + uno::Reference< accessibility::XAccessibleHyperlink > xLink; +}; + +} + +static uno::Reference< accessibility::XAccessibleHyperlink > const & + getHyperlink( AtkHyperlink *pHyperlink ) +{ + HyperLink *pLink = reinterpret_cast<HyperLink *>(pHyperlink); + return pLink->xLink; +} + +static GObjectClass *hyper_parent_class = nullptr; + +extern "C" { + +static void +hyper_link_finalize (GObject *obj) +{ + HyperLink *hl = reinterpret_cast<HyperLink *>(obj); + hl->xLink.clear(); + hyper_parent_class->finalize (obj); +} + +static gchar * +hyper_link_get_uri( AtkHyperlink *pLink, + gint i ) +{ + try { + uno::Any aAny = getHyperlink( pLink )->getAccessibleActionObject( i ); + OUString aUri = aAny.get< OUString > (); + return OUStringToGChar(aUri); + } + catch(const uno::Exception&) { + g_warning( "Exception in hyper_link_get_uri" ); + } + return nullptr; +} + +static AtkObject * +hyper_link_get_object( AtkHyperlink *, + gint ) +{ + g_warning( "FIXME: hyper_link_get_object unimplemented" ); + return nullptr; +} +static gint +hyper_link_get_end_index( AtkHyperlink *pLink ) +{ + try { + return getHyperlink( pLink )->getEndIndex(); + } + catch(const uno::Exception&) { + } + return -1; +} +static gint +hyper_link_get_start_index( AtkHyperlink *pLink ) +{ + try { + return getHyperlink( pLink )->getStartIndex(); + } + catch(const uno::Exception&) { + } + return -1; +} +static gboolean +hyper_link_is_valid( AtkHyperlink *pLink ) +{ + try { + return getHyperlink( pLink )->isValid(); + } + catch(const uno::Exception&) { + } + return FALSE; +} +static gint +hyper_link_get_n_anchors( AtkHyperlink *pLink ) +{ + try { + return getHyperlink( pLink )->getAccessibleActionCount(); + } + catch(const uno::Exception&) { + } + return 0; +} + +static guint +hyper_link_link_state( AtkHyperlink * ) +{ + g_warning( "FIXME: hyper_link_link_state unimplemented" ); + return 0; +} +static gboolean +hyper_link_is_selected_link( AtkHyperlink * ) +{ + g_warning( "FIXME: hyper_link_is_selected_link unimplemented" ); + return FALSE; +} + +static void +hyper_link_class_init (AtkHyperlinkClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = hyper_link_finalize; + + hyper_parent_class = static_cast<GObjectClass *>(g_type_class_peek_parent (klass)); + + klass->get_uri = hyper_link_get_uri; + klass->get_object = hyper_link_get_object; + klass->get_end_index = hyper_link_get_end_index; + klass->get_start_index = hyper_link_get_start_index; + klass->is_valid = hyper_link_is_valid; + klass->get_n_anchors = hyper_link_get_n_anchors; + klass->link_state = hyper_link_link_state; + klass->is_selected_link = hyper_link_is_selected_link; +} + +static GType +hyper_link_get_type() +{ + static GType type = 0; + + if (!type) { + static const GTypeInfo tinfo = { + sizeof (AtkHyperlinkClass), + nullptr, /* base init */ + nullptr, /* base finalize */ + reinterpret_cast<GClassInitFunc>(hyper_link_class_init), + nullptr, /* class finalize */ + nullptr, /* class data */ + sizeof (HyperLink), /* instance size */ + 0, /* nb preallocs */ + nullptr, /* instance init */ + nullptr /* value table */ + }; + + static const GInterfaceInfo atk_action_info = { + reinterpret_cast<GInterfaceInitFunc>(actionIfaceInit), + nullptr, + nullptr + }; + + type = g_type_register_static (ATK_TYPE_HYPERLINK, + "OOoAtkObjHyperLink", &tinfo, + GTypeFlags(0)); + g_type_add_interface_static (type, ATK_TYPE_ACTION, + &atk_action_info); + } + + return type; +} + +// ---------------------- AtkHyperText ---------------------- + +/// @throws uno::RuntimeException +static css::uno::Reference<css::accessibility::XAccessibleHypertext> + getHypertext( AtkHypertext *pHypertext ) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pHypertext ); + if( pWrap ) + { + if( !pWrap->mpHypertext.is() ) + { + pWrap->mpHypertext.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpHypertext; + } + + return css::uno::Reference<css::accessibility::XAccessibleHypertext>(); +} + +static AtkHyperlink * +hypertext_get_link( AtkHypertext *hypertext, + gint link_index) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext + = getHypertext( hypertext ); + if( pHypertext.is() ) + { + HyperLink *pLink = static_cast<HyperLink *>(g_object_new( hyper_link_get_type(), nullptr )); + pLink->xLink = pHypertext->getHyperLink( link_index ); + if( !pLink->xLink.is() ) { + g_object_unref( G_OBJECT( pLink ) ); + pLink = nullptr; + } + return ATK_HYPERLINK( pLink ); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getHyperLink()" ); + } + + return nullptr; +} + +static gint +hypertext_get_n_links( AtkHypertext *hypertext ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext + = getHypertext( hypertext ); + if( pHypertext.is() ) + return pHypertext->getHyperLinkCount(); + } + catch(const uno::Exception&) { + g_warning( "Exception in getHyperLinkCount()" ); + } + + return 0; +} + +static gint +hypertext_get_link_index( AtkHypertext *hypertext, + gint index) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext + = getHypertext( hypertext ); + if( pHypertext.is() ) + return pHypertext->getHyperLinkIndex( index ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getHyperLinkIndex()" ); + } + + return 0; +} + +} // extern "C" + +void +hypertextIfaceInit (AtkHypertextIface *iface) +{ + g_return_if_fail (iface != nullptr); + + iface->get_link = hypertext_get_link; + iface->get_n_links = hypertext_get_n_links; + iface->get_link_index = hypertext_get_link_index; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkimage.cxx b/vcl/unx/gtk3/a11y/atkimage.cxx new file mode 100644 index 0000000000..da4965b868 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkimage.cxx @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <string_view> + +#include "atkwrapper.hxx" + +#include <com/sun/star/accessibility/XAccessibleImage.hpp> + +using namespace ::com::sun::star; + +// FIXME +static const gchar * +getAsConst( std::u16string_view rString ) +{ + static const int nMax = 10; + static OString aUgly[nMax]; + static int nIdx = 0; + nIdx = (nIdx + 1) % nMax; + aUgly[nIdx] = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 ); + return aUgly[ nIdx ].getStr(); +} + +/// @throws uno::RuntimeException +static css::uno::Reference<css::accessibility::XAccessibleImage> + getImage( AtkImage *pImage ) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pImage ); + if( pWrap ) + { + if( !pWrap->mpImage.is() ) + { + pWrap->mpImage.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpImage; + } + + return css::uno::Reference<css::accessibility::XAccessibleImage>(); +} + +extern "C" { + +static const gchar * +image_get_image_description( AtkImage *image ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleImage> pImage + = getImage( image ); + if( pImage.is() ) + return getAsConst( pImage->getAccessibleImageDescription() ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleImageDescription()" ); + } + + return nullptr; +} + +static void +image_get_image_position( AtkImage *image, + gint *x, + gint *y, + AtkCoordType coord_type ) +{ + *x = *y = -1; + if( ATK_IS_COMPONENT( image ) ) + { + gint nWidth = -1; + gint nHeight = -1; + atk_component_get_extents(ATK_COMPONENT(image), x, y, &nWidth, &nHeight, coord_type); + } + else + g_warning( "FIXME: no image position information" ); +} + +static void +image_get_image_size( AtkImage *image, + gint *width, + gint *height ) +{ + *width = *height = -1; + try { + css::uno::Reference<css::accessibility::XAccessibleImage> pImage + = getImage( image ); + if( pImage.is() ) + { + *width = pImage->getAccessibleImageWidth(); + *height = pImage->getAccessibleImageHeight(); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleImageHeight() or Width" ); + } +} + +static gboolean +image_set_image_description( AtkImage *, const gchar * ) +{ + g_warning ("FIXME: no set image description"); + return FALSE; +} + +} // extern "C" + +void +imageIfaceInit (AtkImageIface *iface) +{ + g_return_if_fail (iface != nullptr); + + iface->set_image_description = image_set_image_description; + iface->get_image_description = image_get_image_description; + iface->get_image_position = image_get_image_position; + iface->get_image_size = image_get_image_size; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atklistener.cxx b/vcl/unx/gtk3/a11y/atklistener.cxx new file mode 100644 index 0000000000..b826ea0306 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atklistener.cxx @@ -0,0 +1,783 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/accessibility/TextSegment.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp> +#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> +#include <com/sun/star/accessibility/XAccessibleContext3.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> + +#include "atklistener.hxx" +#include "atkwrapper.hxx" +#include <comphelper/sequence.hxx> +#include <vcl/svapp.hxx> + +#include <sal/log.hxx> + +#define DEBUG_ATK_LISTENER 0 + +#if DEBUG_ATK_LISTENER +#include <iostream> +#include <sstream> +#endif + +using namespace com::sun::star; + +AtkListener::AtkListener( AtkObjectWrapper* pWrapper ) : mpWrapper( pWrapper ) +{ + if( mpWrapper ) + { + g_object_ref( mpWrapper ); + updateChildList( mpWrapper->mpContext ); + } +} + +AtkListener::~AtkListener() +{ + if( mpWrapper ) + g_object_unref( mpWrapper ); +} + +/*****************************************************************************/ + +static AtkStateType mapState( const uno::Any &rAny ) +{ + sal_Int64 nState = accessibility::AccessibleStateType::INVALID; + rAny >>= nState; + return mapAtkState( nState ); +} + +/*****************************************************************************/ + +extern "C" { + // rhbz#1001768 - down to horrific problems releasing the solar mutex + // while destroying a Window - which occurs inside these notifications. + static gboolean + idle_defunc_state_change( AtkObject *atk_obj ) + { + SolarMutexGuard aGuard; + + // This is an equivalent to a state change to DEFUNC(T). + atk_object_notify_state_change( atk_obj, ATK_STATE_DEFUNCT, true ); + if( atk_get_focus_object() == atk_obj ) + { + SAL_WNODEPRECATED_DECLARATIONS_PUSH + atk_focus_tracker_notify( nullptr ); + SAL_WNODEPRECATED_DECLARATIONS_POP + } + g_object_unref( G_OBJECT( atk_obj ) ); + return false; + } +} + +// XEventListener implementation +void AtkListener::disposing( const lang::EventObject& ) +{ + if( !mpWrapper ) + return; + + AtkObject *atk_obj = ATK_OBJECT( mpWrapper ); + + // Release all interface references to avoid shutdown problems with + // global mutex + atk_object_wrapper_dispose( mpWrapper ); + + g_idle_add( reinterpret_cast<GSourceFunc>(idle_defunc_state_change), + g_object_ref( G_OBJECT( atk_obj ) ) ); + + // Release the wrapper object so that it can vanish .. + g_object_unref( mpWrapper ); + mpWrapper = nullptr; +} + +/*****************************************************************************/ + +static AtkObject *getObjFromAny( const uno::Any &rAny ) +{ + uno::Reference< accessibility::XAccessible > xAccessible; + rAny >>= xAccessible; + return xAccessible.is() ? atk_object_wrapper_ref( xAccessible ) : nullptr; +} + +/*****************************************************************************/ + +// Updates the child list held to provide the old IndexInParent on children_changed::remove +void AtkListener::updateChildList( + css::uno::Reference<css::accessibility::XAccessibleContext> const & + pContext) +{ + m_aChildList.clear(); + + sal_Int64 nStateSet = pContext->getAccessibleStateSet(); + if( (nStateSet & accessibility::AccessibleStateType::DEFUNC) + || (nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS) ) + return; + + css::uno::Reference<css::accessibility::XAccessibleContext3> xContext3(pContext, css::uno::UNO_QUERY); + if (xContext3.is()) + { + m_aChildList = comphelper::sequenceToContainer<std::vector<css::uno::Reference< css::accessibility::XAccessible >>>(xContext3->getAccessibleChildren()); + } + else + { + sal_Int64 nChildren = pContext->getAccessibleChildCount(); + assert(o3tl::make_unsigned(nChildren) < m_aChildList.max_size()); + m_aChildList.resize(nChildren); + for(sal_Int64 n = 0; n < nChildren; n++) + { + try + { + m_aChildList[n] = pContext->getAccessibleChild(n); + } + catch (lang::IndexOutOfBoundsException const&) + { + sal_Int64 nChildren2 = pContext->getAccessibleChildCount(); + assert(nChildren2 <= n && "consistency?"); + m_aChildList.resize(std::min(nChildren2, n)); + break; + } + } + } +} + +/*****************************************************************************/ + +void AtkListener::handleChildAdded( + const uno::Reference< accessibility::XAccessibleContext >& rxParent, + const uno::Reference< accessibility::XAccessible>& rxAccessible, + sal_Int32 nIndexHint) +{ + AtkObject * pChild = rxAccessible.is() ? atk_object_wrapper_ref( rxAccessible ) : nullptr; + + if( !pChild ) + return; + + if (nIndexHint != -1 && (nIndexHint < 0 || nIndexHint >= static_cast<sal_Int32>(m_aChildList.size()))) + { + SAL_WARN("vcl", "index hint out of range, ignoring"); + nIndexHint = -1; + } + + bool bNeedToFullFullChildList = true; + if (nIndexHint != -1) + { + bNeedToFullFullChildList = false; + sal_Int64 nStateSet = rxParent->getAccessibleStateSet(); + if( !(nStateSet & accessibility::AccessibleStateType::DEFUNC) + || (nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS) ) + { + m_aChildList.insert(m_aChildList.begin() + nIndexHint, rxAccessible); + if (m_aChildList[nIndexHint] != rxParent->getAccessibleChild(nIndexHint)) + { + SAL_WARN("vcl", "wrong index hint, falling back to updating full child list"); + bNeedToFullFullChildList = true; + } + } + } + if (bNeedToFullFullChildList) + updateChildList(rxParent); + + atk_object_wrapper_add_child( mpWrapper, pChild, + atk_object_get_index_in_parent( pChild )); + + g_object_unref( pChild ); +} + +/*****************************************************************************/ + +void AtkListener::handleChildRemoved( + const uno::Reference< accessibility::XAccessibleContext >& rxParent, + const uno::Reference< accessibility::XAccessible>& rxChild, + sal_Int32 nChildIndexHint) +{ + sal_Int32 nIndex = nChildIndexHint; + if (nIndex != -1 && (nIndex < 0 || nIndex >= static_cast<sal_Int32>(m_aChildList.size()))) + { + SAL_WARN("vcl", "index hint out of range, ignoring"); + nIndex = -1; + } + if (nIndex != -1 && rxChild != m_aChildList[nIndex]) + { + SAL_WARN("vcl", "index hint points to wrong child, somebody forgot to send accessibility update event"); + nIndex = -1; + } + + // if the hint did not work, search + const size_t nmax = m_aChildList.size(); + if (nIndex == -1) + // Locate the child in the children list + for( size_t n = 0; n < nmax; ++n ) + { + // Comparing via uno::Reference::operator== is expensive + // with lots of objects, so assume we can find it the cheap way + // first, which works most of the time. + if( rxChild.get() == m_aChildList[n].get() ) + { + nIndex = n; + break; + } + } + // The cheap way failed, find it via the more expensive path + if (nIndex == -1) + for( size_t n = 0; n < nmax; ++n ) + { + if( rxChild == m_aChildList[n] ) + { + nIndex = n; + break; + } + } + + // FIXME: two problems here: + // a) we get child-removed events for objects that are no real children + // in the accessibility hierarchy or have been removed before due to + // some child removing batch. + // b) spi_atk_bridge_signal_listener ignores the given parameters + // for children_changed events and always asks the parent for the + // 0. child, which breaks somehow on vanishing list boxes. + // Ignoring "remove" events for objects not in the m_aChildList + // for now. + if( nIndex < 0 ) + return; + + uno::Reference<accessibility::XAccessibleEventBroadcaster> xBroadcaster( + rxChild->getAccessibleContext(), uno::UNO_QUERY); + + if (xBroadcaster.is()) + { + uno::Reference<accessibility::XAccessibleEventListener> xListener(this); + xBroadcaster->removeAccessibleEventListener(xListener); + } + + // update child list + sal_Int64 nStateSet = rxParent->getAccessibleStateSet(); + if(!( (nStateSet & accessibility::AccessibleStateType::DEFUNC) + || (nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS) )) + { + m_aChildList.erase(m_aChildList.begin() + nIndex); + } + + AtkObject * pChild = atk_object_wrapper_ref( rxChild, false ); + if( pChild ) + { + atk_object_wrapper_remove_child( mpWrapper, pChild, nIndex ); + g_object_unref( pChild ); + } +} + +/*****************************************************************************/ + +void AtkListener::handleInvalidateChildren( + const uno::Reference< accessibility::XAccessibleContext >& rxParent) +{ + // Send notifications for all previous children + size_t n = m_aChildList.size(); + while( n-- > 0 ) + { + if( m_aChildList[n].is() ) + { + AtkObject * pChild = atk_object_wrapper_ref( m_aChildList[n], false ); + if( pChild ) + { + atk_object_wrapper_remove_child( mpWrapper, pChild, n ); + g_object_unref( pChild ); + } + } + } + + updateChildList(rxParent); + + // Send notifications for all new children + size_t nmax = m_aChildList.size(); + for( n = 0; n < nmax; ++n ) + { + if( m_aChildList[n].is() ) + { + AtkObject * pChild = atk_object_wrapper_ref( m_aChildList[n] ); + + if( pChild ) + { + atk_object_wrapper_add_child( mpWrapper, pChild, n ); + g_object_unref( pChild ); + } + } + } +} + +/*****************************************************************************/ + +static uno::Reference< accessibility::XAccessibleContext > +getAccessibleContextFromSource( const uno::Reference< uno::XInterface >& rxSource ) +{ + uno::Reference< accessibility::XAccessibleContext > xContext(rxSource, uno::UNO_QUERY); + if( ! xContext.is() ) + { + g_warning( "ERROR: Event source does not implement XAccessibleContext" ); + + // Second try - query for XAccessible, which should give us access to + // XAccessibleContext. + uno::Reference< accessibility::XAccessible > xAccessible(rxSource, uno::UNO_QUERY); + if( xAccessible.is() ) + xContext = xAccessible->getAccessibleContext(); + } + + return xContext; +} + +#if DEBUG_ATK_LISTENER + +namespace { + +void printNotifyEvent( const accessibility::AccessibleEventObject& rEvent ) +{ + static std::vector<const char*> aLabels = { + 0, + "NAME_CHANGED", // 01 + "DESCRIPTION_CHANGED", // 02 + "ACTION_CHANGED", // 03 + "STATE_CHANGED", // 04 + "ACTIVE_DESCENDANT_CHANGED", // 05 + "BOUNDRECT_CHANGED", // 06 + "CHILD", // 07 + "INVALIDATE_ALL_CHILDREN", // 08 + "SELECTION_CHANGED", // 09 + "VISIBLE_DATA_CHANGED", // 10 + "VALUE_CHANGED", // 11 + "CONTENT_FLOWS_FROM_RELATION_CHANGED", // 12 + "CONTENT_FLOWS_TO_RELATION_CHANGED", // 13 + "CONTROLLED_BY_RELATION_CHANGED", // 14 + "CONTROLLER_FOR_RELATION_CHANGED", // 15 + "LABEL_FOR_RELATION_CHANGED", // 16 + "LABELED_BY_RELATION_CHANGED", // 17 + "MEMBER_OF_RELATION_CHANGED", // 18 + "SUB_WINDOW_OF_RELATION_CHANGED", // 19 + "CARET_CHANGED", // 20 + "TEXT_SELECTION_CHANGED", // 21 + "TEXT_CHANGED", // 22 + "TEXT_ATTRIBUTE_CHANGED", // 23 + "HYPERTEXT_CHANGED", // 24 + "TABLE_CAPTION_CHANGED", // 25 + "TABLE_COLUMN_DESCRIPTION_CHANGED", // 26 + "TABLE_COLUMN_HEADER_CHANGED", // 27 + "TABLE_MODEL_CHANGED", // 28 + "TABLE_ROW_DESCRIPTION_CHANGED", // 29 + "TABLE_ROW_HEADER_CHANGED", // 30 + "TABLE_SUMMARY_CHANGED", // 31 + "LISTBOX_ENTRY_EXPANDED", // 32 + "LISTBOX_ENTRY_COLLAPSED", // 33 + "ACTIVE_DESCENDANT_CHANGED_NOFOCUS", // 34 + "SELECTION_CHANGED_ADD", // 35 + "SELECTION_CHANGED_REMOVE", // 36 + "SELECTION_CHANGED_WITHIN", // 37 + "PAGE_CHANGED", // 38 + "SECTION_CHANGED", // 39 + "COLUMN_CHANGED", // 40 + "ROLE_CHANGED", // 41 + }; + + static std::vector<const char*> aStates = { + "INVALID", // 00 + "ACTIVE", // 01 + "ARMED", // 02 + "BUSY", // 03 + "CHECKED", // 04 + "DEFUNC", // 05 + "EDITABLE", // 06 + "ENABLED", // 07 + "EXPANDABLE", // 08 + "EXPANDED", // 09 + "FOCUSABLE", // 10 + "FOCUSED", // 11 + "HORIZONTAL", // 12 + "ICONIFIED", // 13 + "INDETERMINATE", // 14 + "MANAGES_DESCENDANTS", // 15 + "MODAL", // 16 + "MULTI_LINE", // 17 + "MULTI_SELECTABLE", // 18 + "OPAQUE", // 19 + "PRESSED", // 20 + "RESIZABLE", // 21 + "SELECTABLE", // 22 + "SELECTED", // 23 + "SENSITIVE", // 24 + "SHOWING", // 25 + "SINGLE_LINE", // 26 + "STALE", // 27 + "TRANSIENT", // 28 + "VERTICAL", // 29 + "VISIBLE", // 30 + "MOVEABLE", // 31 + "DEFAULT", // 32 + "OFFSCREEN", // 33 + "COLLAPSE", // 34 + }; + + auto getOrUnknown = [](const std::vector<const char*>& rCont, size_t nIndex) -> std::string + { + return (nIndex < rCont.size()) ? rCont[nIndex] : "<unknown>"; + }; + + std::ostringstream os; + os << "--" << std::endl; + os << "* event = " << getOrUnknown(aLabels, rEvent.EventId) << std::endl; + + switch (rEvent.EventId) + { + case accessibility::AccessibleEventId::STATE_CHANGED: + { + sal_Int64 nState; + if (rEvent.OldValue >>= nState) + os << " * old state = " << getOrUnknown(aStates, nState); + if (rEvent.NewValue >>= nState) + os << " * new state = " << getOrUnknown(aStates, nState); + + os << std::endl; + break; + } + default: + ; + } + + std::cout << os.str(); +} + +} + +#endif + +void AtkListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent ) +{ + if( !mpWrapper ) + return; + + AtkObject *atk_obj = ATK_OBJECT( mpWrapper ); + + switch( aEvent.EventId ) + { + // AtkObject signals: + // Hierarchy signals + case accessibility::AccessibleEventId::CHILD: + { + uno::Reference< accessibility::XAccessibleContext > xParent; + uno::Reference< accessibility::XAccessible > xChild; + + xParent = getAccessibleContextFromSource(aEvent.Source); + g_return_if_fail( xParent.is() ); + + if( aEvent.OldValue >>= xChild ) + handleChildRemoved(xParent, xChild, aEvent.IndexHint); + + if( aEvent.NewValue >>= xChild ) + handleChildAdded(xParent, xChild, aEvent.IndexHint); + break; + } + + case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN: + { + uno::Reference< accessibility::XAccessibleContext > xParent = getAccessibleContextFromSource(aEvent.Source); + g_return_if_fail( xParent.is() ); + + handleInvalidateChildren(xParent); + break; + } + + case accessibility::AccessibleEventId::NAME_CHANGED: + { + OUString aName; + if( aEvent.NewValue >>= aName ) + { + atk_object_set_name(atk_obj, + OUStringToOString(aName, RTL_TEXTENCODING_UTF8).getStr()); + } + break; + } + + case accessibility::AccessibleEventId::DESCRIPTION_CHANGED: + { + OUString aDescription; + if( aEvent.NewValue >>= aDescription ) + { + atk_object_set_description(atk_obj, + OUStringToOString(aDescription, RTL_TEXTENCODING_UTF8).getStr()); + } + break; + } + + case accessibility::AccessibleEventId::STATE_CHANGED: + { + AtkStateType eOldState = mapState( aEvent.OldValue ); + AtkStateType eNewState = mapState( aEvent.NewValue ); + + bool bState = eNewState != ATK_STATE_INVALID; + AtkStateType eRealState = bState ? eNewState : eOldState; + + atk_object_notify_state_change( atk_obj, eRealState, bState ); + break; + } + + case accessibility::AccessibleEventId::BOUNDRECT_CHANGED: + + if( ATK_IS_COMPONENT( atk_obj ) ) + { + AtkRectangle rect; + + atk_component_get_extents( ATK_COMPONENT( atk_obj ), + &rect.x, + &rect.y, + &rect.width, + &rect.height, + ATK_XY_SCREEN ); + + g_signal_emit_by_name( atk_obj, "bounds-changed", &rect ); + } + else + g_warning( "bounds-changed event for object not implementing AtkComponent\n"); + break; + + case accessibility::AccessibleEventId::VISIBLE_DATA_CHANGED: + g_signal_emit_by_name( atk_obj, "visible-data-changed" ); + break; + + case accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED: + { + AtkObject *pChild = getObjFromAny( aEvent.NewValue ); + if( pChild ) + { + g_signal_emit_by_name( atk_obj, "active-descendant-changed", pChild ); + g_object_unref( pChild ); + } + break; + } + + //ACTIVE_DESCENDANT_CHANGED_NOFOCUS (sic) appears to have been added + //as a workaround or an aid for the ia2 winaccessibility implementation + //so ignore it silently without warning here + case accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED_NOFOCUS: + break; + + // #i92103# + case accessibility::AccessibleEventId::LISTBOX_ENTRY_EXPANDED: + { + AtkObject *pChild = getObjFromAny( aEvent.NewValue ); + if( pChild ) + { + atk_object_notify_state_change( pChild, ATK_STATE_EXPANDED, true ); + g_object_unref( pChild ); + } + break; + } + + case accessibility::AccessibleEventId::LISTBOX_ENTRY_COLLAPSED: + { + AtkObject *pChild = getObjFromAny( aEvent.NewValue ); + if( pChild ) + { + atk_object_notify_state_change( pChild, ATK_STATE_EXPANDED, false ); + g_object_unref( pChild ); + } + break; + } + + // AtkAction signals ... + case accessibility::AccessibleEventId::ACTION_CHANGED: + g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-actions"); + break; + + // AtkText + case accessibility::AccessibleEventId::CARET_CHANGED: + { + sal_Int32 nPos=0; + aEvent.NewValue >>= nPos; + g_signal_emit_by_name( atk_obj, "text_caret_moved", nPos ); + break; + } + case accessibility::AccessibleEventId::TEXT_CHANGED: + { + // cf. comphelper/source/misc/accessibletexthelper.cxx (implInitTextChangedEvent) + accessibility::TextSegment aDeletedText; + accessibility::TextSegment aInsertedText; + + if( aEvent.OldValue >>= aDeletedText ) + { + const OString aDeletedTextUtf8 = OUStringToOString(aDeletedText.SegmentText, RTL_TEXTENCODING_UTF8); + g_signal_emit_by_name( atk_obj, "text-remove", + static_cast<gint>(aDeletedText.SegmentStart), + static_cast<gint>(aDeletedText.SegmentEnd - aDeletedText.SegmentStart), + g_strdup(aDeletedTextUtf8.getStr())); + + } + if( aEvent.NewValue >>= aInsertedText ) + { + const OString aInsertedTextUtf8 = OUStringToOString(aInsertedText.SegmentText, RTL_TEXTENCODING_UTF8); + g_signal_emit_by_name( atk_obj, "text-insert", + static_cast<gint>(aInsertedText.SegmentStart), + static_cast<gint>(aInsertedText.SegmentEnd - aInsertedText.SegmentStart), + g_strdup(aInsertedTextUtf8.getStr())); + } + break; + } + + case accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED: + { + g_signal_emit_by_name( atk_obj, "text-selection-changed" ); + break; + } + + case accessibility::AccessibleEventId::TEXT_ATTRIBUTE_CHANGED: + g_signal_emit_by_name( atk_obj, "text-attributes-changed" ); + break; + + // AtkValue + case accessibility::AccessibleEventId::VALUE_CHANGED: + g_object_notify( G_OBJECT( atk_obj ), "accessible-value" ); + break; + + case accessibility::AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED: + case accessibility::AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED: + case accessibility::AccessibleEventId::CONTROLLED_BY_RELATION_CHANGED: + case accessibility::AccessibleEventId::CONTROLLER_FOR_RELATION_CHANGED: + case accessibility::AccessibleEventId::LABEL_FOR_RELATION_CHANGED: + case accessibility::AccessibleEventId::LABELED_BY_RELATION_CHANGED: + case accessibility::AccessibleEventId::MEMBER_OF_RELATION_CHANGED: + case accessibility::AccessibleEventId::SUB_WINDOW_OF_RELATION_CHANGED: + // FIXME: ask Bill how Atk copes with this little lot ... + break; + + // AtkTable + case accessibility::AccessibleEventId::TABLE_MODEL_CHANGED: + { + accessibility::AccessibleTableModelChange aChange; + aEvent.NewValue >>= aChange; + + sal_Int32 nRowsChanged = aChange.LastRow - aChange.FirstRow + 1; + sal_Int32 nColumnsChanged = aChange.LastColumn - aChange.FirstColumn + 1; + + switch( aChange.Type ) + { + case accessibility::AccessibleTableModelChangeType::COLUMNS_INSERTED: + g_signal_emit_by_name(G_OBJECT(atk_obj), "column-inserted", + aChange.FirstColumn, nColumnsChanged); + break; + case accessibility::AccessibleTableModelChangeType::COLUMNS_REMOVED: + g_signal_emit_by_name(G_OBJECT(atk_obj), "column-deleted", + aChange.FirstColumn, nColumnsChanged); + break; + case accessibility::AccessibleTableModelChangeType::ROWS_INSERTED: + g_signal_emit_by_name(G_OBJECT(atk_obj), "row-inserted", + aChange.FirstRow, nRowsChanged); + break; + case accessibility::AccessibleTableModelChangeType::ROWS_REMOVED: + g_signal_emit_by_name(G_OBJECT(atk_obj), "row-deleted", + aChange.FirstRow, nRowsChanged); + break; + case accessibility::AccessibleTableModelChangeType::UPDATE: + // This is not really a model change, is it ? + break; + default: + g_warning( "TESTME: unusual table model change %d\n", aChange.Type ); + break; + } + g_signal_emit_by_name( G_OBJECT( atk_obj ), "model-changed" ); + break; + } + + case accessibility::AccessibleEventId::TABLE_COLUMN_HEADER_CHANGED: + { + accessibility::AccessibleTableModelChange aChange; + aEvent.NewValue >>= aChange; + + AtkPropertyValues values; + memset(&values, 0, sizeof(AtkPropertyValues)); + g_value_init (&values.new_value, G_TYPE_INT); + values.property_name = "accessible-table-column-header"; + + for (sal_Int32 nChangedColumn = aChange.FirstColumn; nChangedColumn <= aChange.LastColumn; ++nChangedColumn) + { + g_value_set_int (&values.new_value, nChangedColumn); + g_signal_emit_by_name(G_OBJECT(atk_obj), "property_change::accessible-table-column-header", &values, nullptr); + } + break; + } + + case accessibility::AccessibleEventId::TABLE_CAPTION_CHANGED: + g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-caption"); + break; + + case accessibility::AccessibleEventId::TABLE_COLUMN_DESCRIPTION_CHANGED: + g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-column-description"); + break; + + case accessibility::AccessibleEventId::TABLE_ROW_DESCRIPTION_CHANGED: + g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-row-description"); + break; + + case accessibility::AccessibleEventId::TABLE_ROW_HEADER_CHANGED: + g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-row-header"); + break; + + case accessibility::AccessibleEventId::TABLE_SUMMARY_CHANGED: + g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-summary"); + break; + + case accessibility::AccessibleEventId::SELECTION_CHANGED: + case accessibility::AccessibleEventId::SELECTION_CHANGED_ADD: + case accessibility::AccessibleEventId::SELECTION_CHANGED_REMOVE: + case accessibility::AccessibleEventId::SELECTION_CHANGED_WITHIN: + if (ATK_IS_SELECTION(atk_obj)) + g_signal_emit_by_name(G_OBJECT(atk_obj), "selection_changed"); + else + { + // e.g. tdf#122353, when such dialogs become native the problem will go away anyway + SAL_INFO("vcl.gtk", "selection change from obj which doesn't support XAccessibleSelection"); + } + break; + + case accessibility::AccessibleEventId::HYPERTEXT_CHANGED: + g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-hypertext-offset"); + break; + + case accessibility::AccessibleEventId::ROLE_CHANGED: + { + uno::Reference< accessibility::XAccessibleContext > xContext = getAccessibleContextFromSource( aEvent.Source ); + atk_object_wrapper_set_role(mpWrapper, xContext->getAccessibleRole(), xContext->getAccessibleStateSet()); + break; + } + + case accessibility::AccessibleEventId::PAGE_CHANGED: + { + /* // If we implemented AtkDocument then I imagine this is what this + // handler should look like + sal_Int32 nPos=0; + aEvent.NewValue >>= nPos; + g_signal_emit_by_name( G_OBJECT( atk_obj ), "page_changed", nPos ); + */ + break; + } + + default: + SAL_WARN("vcl.gtk", "Unknown event notification: " << aEvent.EventId); + break; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atklistener.hxx b/vcl/unx/gtk3/a11y/atklistener.hxx new file mode 100644 index 0000000000..e286f40e5a --- /dev/null +++ b/vcl/unx/gtk3/a11y/atklistener.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/accessibility/XAccessibleEventListener.hpp> +#include <cppuhelper/implbase.hxx> + +#include <vector> + +#include "atkwrapper.hxx" + +class AtkListener : public ::cppu::WeakImplHelper< css::accessibility::XAccessibleEventListener > +{ +public: + explicit AtkListener(AtkObjectWrapper * pWrapper); + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + // XAccessibleEventListener + virtual void SAL_CALL notifyEvent( const css::accessibility::AccessibleEventObject& aEvent ) override; + +private: + + AtkObjectWrapper *mpWrapper; + std::vector< css::uno::Reference< css::accessibility::XAccessible > > + m_aChildList; + + virtual ~AtkListener() override; + + // Updates the child list held to provide the old IndexInParent on children_changed::remove + void updateChildList( + css::uno::Reference<css::accessibility::XAccessibleContext> const & + pContext); + + // Process CHILD_EVENT notifications with a new child added + void handleChildAdded( + const css::uno::Reference< css::accessibility::XAccessibleContext >& rxParent, + const css::uno::Reference< css::accessibility::XAccessible>& rxChild, + sal_Int32 nIndexHint); + + // Process CHILD_EVENT notifications with a child removed + void handleChildRemoved( + const css::uno::Reference< css::accessibility::XAccessibleContext >& rxParent, + const css::uno::Reference< css::accessibility::XAccessible>& rxChild, + sal_Int32 nIndexHint); + + // Process INVALIDATE_ALL_CHILDREN notification + void handleInvalidateChildren( + const css::uno::Reference< css::accessibility::XAccessibleContext >& rxParent); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkregistry.cxx b/vcl/unx/gtk3/a11y/atkregistry.cxx new file mode 100644 index 0000000000..ff96378c41 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkregistry.cxx @@ -0,0 +1,66 @@ +/* -*- 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 "atkregistry.hxx" + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; + +static GHashTable *uno_to_gobject = nullptr; + +/*****************************************************************************/ + +AtkObject * +ooo_wrapper_registry_get(const Reference< XAccessible >& rxAccessible) +{ + if( uno_to_gobject ) + { + gpointer cached = + g_hash_table_lookup(uno_to_gobject, static_cast<gpointer>(rxAccessible.get())); + + if( cached ) + return ATK_OBJECT( cached ); + } + + return nullptr; +} + +/*****************************************************************************/ + +void +ooo_wrapper_registry_add(const Reference< XAccessible >& rxAccessible, AtkObject *obj) +{ + if( !uno_to_gobject ) + uno_to_gobject = g_hash_table_new (nullptr, nullptr); + + g_hash_table_insert( uno_to_gobject, static_cast<gpointer>(rxAccessible.get()), obj ); +} + +/*****************************************************************************/ + +void +ooo_wrapper_registry_remove( + css::uno::Reference<css::accessibility::XAccessible> const & pAccessible) +{ + if( uno_to_gobject ) + g_hash_table_remove( + uno_to_gobject, static_cast<gpointer>(pAccessible.get()) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkregistry.hxx b/vcl/unx/gtk3/a11y/atkregistry.hxx new file mode 100644 index 0000000000..b26b838407 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkregistry.hxx @@ -0,0 +1,34 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <atk/atk.h> + +AtkObject* +ooo_wrapper_registry_get(const css::uno::Reference<css::accessibility::XAccessible>& rxAccessible); + +void ooo_wrapper_registry_add( + const css::uno::Reference<css::accessibility::XAccessible>& rxAccessible, AtkObject* obj); + +void ooo_wrapper_registry_remove( + css::uno::Reference<css::accessibility::XAccessible> const& pAccessible); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkselection.cxx b/vcl/unx/gtk3/a11y/atkselection.cxx new file mode 100644 index 0000000000..0e74f1c295 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkselection.cxx @@ -0,0 +1,206 @@ +/* -*- 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 "atkwrapper.hxx" + +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> +#include <sal/log.hxx> + +using namespace ::com::sun::star; + +/// @throws uno::RuntimeException +static css::uno::Reference<css::accessibility::XAccessibleSelection> + getSelection( AtkSelection *pSelection ) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pSelection ); + if( pWrap ) + { + if( !pWrap->mpSelection.is() ) + { + pWrap->mpSelection.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpSelection; + } + + return css::uno::Reference<css::accessibility::XAccessibleSelection>(); +} + +extern "C" { + +static gboolean +selection_add_selection( AtkSelection *selection, + gint i ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection + = getSelection( selection ); + if( pSelection.is() ) + { + pSelection->selectAccessibleChild( i ); + return true; + } + } + catch(const uno::Exception&) { + g_warning( "Exception in selectAccessibleChild()" ); + } + + return FALSE; +} + +static gboolean +selection_clear_selection( AtkSelection *selection ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection + = getSelection( selection ); + if( pSelection.is() ) + { + pSelection->clearAccessibleSelection(); + return true; + } + } + catch(const uno::Exception&) { + g_warning( "Exception in clearAccessibleSelection()" ); + } + + return FALSE; +} + +static AtkObject* +selection_ref_selection( AtkSelection *selection, + gint i ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection + = getSelection( selection ); + if( pSelection.is() ) + return atk_object_wrapper_ref( pSelection->getSelectedAccessibleChild( i ) ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getSelectedAccessibleChild()" ); + } + + return nullptr; +} + +static gint +selection_get_selection_count( AtkSelection *selection) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection + = getSelection( selection ); + if( pSelection.is() ) + { + sal_Int64 nSelected = pSelection->getSelectedAccessibleChildCount(); + if (nSelected > std::numeric_limits<gint>::max()) + { + SAL_WARN("vcl.gtk", "selection_get_selection_count: Count exceeds maximum gint value, " + "using max gint."); + nSelected = std::numeric_limits<gint>::max(); + } + return nSelected; + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getSelectedAccessibleChildCount()" ); + } + + return -1; +} + +static gboolean +selection_is_child_selected( AtkSelection *selection, + gint i) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection + = getSelection( selection ); + if( pSelection.is() ) + return pSelection->isAccessibleChildSelected( i ); + } + catch(const uno::Exception&) { + g_warning( "Exception in isAccessibleChildSelected()" ); + } + + return FALSE; +} + +static gboolean +selection_remove_selection( AtkSelection *selection, + gint i ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection + = getSelection( selection ); + if( pSelection.is() ) + { + css::uno::Reference<css::accessibility::XAccessible> xAcc = pSelection->getSelectedAccessibleChild(i); + if (!xAcc.is()) + return false; + + css::uno::Reference<css::accessibility::XAccessibleContext> xAccContext = xAcc->getAccessibleContext(); + const sal_Int64 nChildIndex = xAccContext->getAccessibleIndexInParent(); + pSelection->deselectAccessibleChild(nChildIndex); + return true; + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getSelectedAccessibleChild(), getAccessibleIndexInParent() or deselectAccessibleChild()" ); + } + + return FALSE; +} + +static gboolean +selection_select_all_selection( AtkSelection *selection) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection + = getSelection( selection ); + if( pSelection.is() ) + { + pSelection->selectAllAccessibleChildren(); + return true; + } + } + catch(const uno::Exception&) { + g_warning( "Exception in selectAllAccessibleChildren()" ); + } + + return FALSE; +} + +} // extern "C" + +void +selectionIfaceInit( AtkSelectionIface *iface) +{ + g_return_if_fail (iface != nullptr); + + iface->add_selection = selection_add_selection; + iface->clear_selection = selection_clear_selection; + iface->ref_selection = selection_ref_selection; + iface->get_selection_count = selection_get_selection_count; + iface->is_child_selected = selection_is_child_selected; + iface->remove_selection = selection_remove_selection; + iface->select_all_selection = selection_select_all_selection; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atktable.cxx b/vcl/unx/gtk3/a11y/atktable.cxx new file mode 100644 index 0000000000..021f3c19c4 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atktable.cxx @@ -0,0 +1,647 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <string_view> + +#include "atkwrapper.hxx" + +#include <com/sun/star/accessibility/XAccessibleTable.hpp> +#include <com/sun/star/accessibility/XAccessibleTableSelection.hpp> +#include <comphelper/sequence.hxx> +#include <sal/log.hxx> + +using namespace ::com::sun::star; + +static AtkObject * +atk_object_wrapper_conditional_ref( const uno::Reference< accessibility::XAccessible >& rxAccessible ) +{ + if( rxAccessible.is() ) + return atk_object_wrapper_ref( rxAccessible ); + + return nullptr; +} + +/*****************************************************************************/ + +// FIXME +static const gchar * +getAsConst( std::u16string_view rString ) +{ + static const int nMax = 10; + static OString aUgly[nMax]; + static int nIdx = 0; + nIdx = (nIdx + 1) % nMax; + aUgly[nIdx] = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 ); + return aUgly[ nIdx ].getStr(); +} + +/*****************************************************************************/ + +/// @throws uno::RuntimeException +static css::uno::Reference<css::accessibility::XAccessibleTable> + getTable( AtkTable *pTable ) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pTable ); + if( pWrap ) + { + if( !pWrap->mpTable.is() ) + { + pWrap->mpTable.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpTable; + } + + return css::uno::Reference<css::accessibility::XAccessibleTable>(); +} + +static css::uno::Reference<css::accessibility::XAccessibleTableSelection> + getTableSelection(AtkTable *pTable) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER(pTable); + if (pWrap) + { + if (!pWrap->mpTableSelection.is()) + { + pWrap->mpTableSelection.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpTableSelection; + } + + return css::uno::Reference<css::accessibility::XAccessibleTableSelection>(); +} + +/*****************************************************************************/ + +extern "C" { + +static AtkObject* +table_wrapper_ref_at (AtkTable *table, + gint row, + gint column) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable = getTable( table ); + if( pTable.is() ) + return atk_object_wrapper_conditional_ref( pTable->getAccessibleCellAt( row, column ) ); + } + + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleCellAt()" ); + } + + return nullptr; +} + +/*****************************************************************************/ + +static gint +table_wrapper_get_index_at (AtkTable *table, + gint row, + gint column) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + { + sal_Int64 nIndex = pTable->getAccessibleIndex( row, column ); + if (nIndex > std::numeric_limits<gint>::max()) + { + // use -2 when the child index is too large to fit into 32 bit to neither use the + // valid index of another cell nor -1, which might easily be interpreted as the cell + // not/no longer being valid + SAL_WARN("vcl.gtk", "table_wrapper_get_index_at: Child index exceeds maximum gint value, " + "returning -2."); + nIndex = -2; + } + return nIndex; + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleIndex()" ); + } + + return -1; +} + +/*****************************************************************************/ + +static gint +table_wrapper_get_column_at_index (AtkTable *table, + gint nIndex) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return pTable->getAccessibleColumn( nIndex ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleColumn()" ); + } + + return -1; +} + +/*****************************************************************************/ + +static gint +table_wrapper_get_row_at_index( AtkTable *table, + gint nIndex ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return pTable->getAccessibleRow( nIndex ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleRow()" ); + } + + return -1; +} + +/*****************************************************************************/ + +static gint +table_wrapper_get_n_columns( AtkTable *table ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return pTable->getAccessibleColumnCount(); + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleColumnCount()" ); + } + + return -1; +} + +/*****************************************************************************/ + +static gint +table_wrapper_get_n_rows( AtkTable *table ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return pTable->getAccessibleRowCount(); + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleRowCount()" ); + } + + return -1; +} + +/*****************************************************************************/ + +static gint +table_wrapper_get_column_extent_at( AtkTable *table, + gint row, + gint column ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return pTable->getAccessibleColumnExtentAt( row, column ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleColumnExtentAt()" ); + } + + return -1; +} + +/*****************************************************************************/ + +static gint +table_wrapper_get_row_extent_at( AtkTable *table, + gint row, + gint column ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return pTable->getAccessibleRowExtentAt( row, column ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleRowExtentAt()" ); + } + + return -1; +} + +/*****************************************************************************/ + +static AtkObject * +table_wrapper_get_caption( AtkTable *table ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return atk_object_wrapper_conditional_ref( pTable->getAccessibleCaption() ); + } + + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleCaption()" ); + } + + return nullptr; +} + +/*****************************************************************************/ + +static const gchar * +table_wrapper_get_row_description( AtkTable *table, + gint row ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return getAsConst( pTable->getAccessibleRowDescription( row ) ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleRowDescription()" ); + } + + return nullptr; +} + +/*****************************************************************************/ + +static const gchar * +table_wrapper_get_column_description( AtkTable *table, + gint column ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return getAsConst( pTable->getAccessibleColumnDescription( column ) ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleColumnDescription()" ); + } + + return nullptr; +} + +/*****************************************************************************/ + +static AtkObject * +table_wrapper_get_row_header( AtkTable *table, + gint row ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + { + uno::Reference< accessibility::XAccessibleTable > xRowHeaders( pTable->getAccessibleRowHeaders() ); + if( xRowHeaders.is() ) + return atk_object_wrapper_conditional_ref( xRowHeaders->getAccessibleCellAt( row, 0 ) ); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleRowHeaders()" ); + } + + return nullptr; +} + +/*****************************************************************************/ + +static AtkObject * +table_wrapper_get_column_header( AtkTable *table, + gint column ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + { + uno::Reference< accessibility::XAccessibleTable > xColumnHeaders( pTable->getAccessibleColumnHeaders() ); + if( xColumnHeaders.is() ) + return atk_object_wrapper_conditional_ref( xColumnHeaders->getAccessibleCellAt( 0, column ) ); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleColumnHeaders()" ); + } + + return nullptr; +} + +/*****************************************************************************/ + +static AtkObject * +table_wrapper_get_summary( AtkTable *table ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + { + return atk_object_wrapper_conditional_ref( pTable->getAccessibleSummary() ); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleSummary()" ); + } + + return nullptr; +} + +/*****************************************************************************/ + +static gint +convertToGIntArray( const uno::Sequence< ::sal_Int32 >& aSequence, gint **pSelected ) +{ + if( aSequence.hasElements() ) + { + *pSelected = g_new( gint, aSequence.getLength() ); + + *pSelected = comphelper::sequenceToArray(*pSelected, aSequence); + } + + return aSequence.getLength(); +} + +/*****************************************************************************/ + +static gint +table_wrapper_get_selected_columns( AtkTable *table, + gint **pSelected ) +{ + *pSelected = nullptr; + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return convertToGIntArray( pTable->getSelectedAccessibleColumns(), pSelected ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getSelectedAccessibleColumns()" ); + } + + return 0; +} + +/*****************************************************************************/ + +static gint +table_wrapper_get_selected_rows( AtkTable *table, + gint **pSelected ) +{ + *pSelected = nullptr; + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return convertToGIntArray( pTable->getSelectedAccessibleRows(), pSelected ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getSelectedAccessibleRows()" ); + } + + return 0; +} + +/*****************************************************************************/ + +static gboolean +table_wrapper_is_column_selected( AtkTable *table, + gint column ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return pTable->isAccessibleColumnSelected( column ); + } + catch(const uno::Exception&) { + g_warning( "Exception in isAccessibleColumnSelected()" ); + } + + return 0; +} + +/*****************************************************************************/ + +static gboolean +table_wrapper_is_row_selected( AtkTable *table, + gint row ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return pTable->isAccessibleRowSelected( row ); + } + catch(const uno::Exception&) { + g_warning( "Exception in isAccessibleRowSelected()" ); + } + + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +table_wrapper_is_selected( AtkTable *table, + gint row, + gint column ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTable> pTable + = getTable( table ); + if( pTable.is() ) + return pTable->isAccessibleSelected( row, column ); + } + catch(const uno::Exception&) { + g_warning( "Exception in isAccessibleSelected()" ); + } + + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +table_wrapper_add_row_selection(AtkTable *pTable, gint row) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTableSelection> xTableSelection = getTableSelection(pTable); + if (xTableSelection.is()) + return xTableSelection->selectRow(row); + } + catch(const uno::Exception&) { + g_warning( "Exception in selectRow()" ); + } + + return false; +} + +/*****************************************************************************/ + +static gboolean +table_wrapper_remove_row_selection(AtkTable *pTable, gint row) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTableSelection> xTableSelection = getTableSelection(pTable); + if (xTableSelection.is()) + return xTableSelection->unselectRow(row); + } + catch(const uno::Exception&) { + g_warning( "Exception in unselectRow()" ); + } + + return false; +} + +/*****************************************************************************/ + +static gboolean +table_wrapper_add_column_selection(AtkTable *pTable, gint column) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTableSelection> xTableSelection = getTableSelection(pTable); + if (xTableSelection.is()) + return xTableSelection->selectColumn(column); + } + catch(const uno::Exception&) { + g_warning( "Exception in selectColumn()" ); + } + + return false; +} + +/*****************************************************************************/ + +static gboolean +table_wrapper_remove_column_selection(AtkTable *pTable, gint column) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleTableSelection> xTableSelection = getTableSelection(pTable); + if (xTableSelection.is()) + return xTableSelection->unselectColumn(column); + } + catch(const uno::Exception&) { + g_warning( "Exception in unselectColumn()" ); + } + + return false; +} + +/*****************************************************************************/ + +static void +table_wrapper_set_caption( AtkTable *, AtkObject * ) +{ // meaningless helper +} + +/*****************************************************************************/ + +static void +table_wrapper_set_column_description( AtkTable *, gint, const gchar * ) +{ // meaningless helper +} + +/*****************************************************************************/ + +static void +table_wrapper_set_column_header( AtkTable *, gint, AtkObject * ) +{ // meaningless helper +} + +/*****************************************************************************/ + +static void +table_wrapper_set_row_description( AtkTable *, gint, const gchar * ) +{ // meaningless helper +} + +/*****************************************************************************/ + +static void +table_wrapper_set_row_header( AtkTable *, gint, AtkObject * ) +{ // meaningless helper +} + +/*****************************************************************************/ + +static void +table_wrapper_set_summary( AtkTable *, AtkObject * ) +{ // meaningless helper +} + +/*****************************************************************************/ + +} // extern "C" + +void +tableIfaceInit (AtkTableIface *iface) +{ + g_return_if_fail (iface != nullptr); + + iface->ref_at = table_wrapper_ref_at; + iface->get_n_rows = table_wrapper_get_n_rows; + iface->get_n_columns = table_wrapper_get_n_columns; + iface->get_index_at = table_wrapper_get_index_at; + iface->get_column_at_index = table_wrapper_get_column_at_index; + iface->get_row_at_index = table_wrapper_get_row_at_index; + iface->is_row_selected = table_wrapper_is_row_selected; + iface->is_selected = table_wrapper_is_selected; + iface->get_selected_rows = table_wrapper_get_selected_rows; + iface->add_row_selection = table_wrapper_add_row_selection; + iface->remove_row_selection = table_wrapper_remove_row_selection; + iface->add_column_selection = table_wrapper_add_column_selection; + iface->remove_column_selection = table_wrapper_remove_column_selection; + iface->get_selected_columns = table_wrapper_get_selected_columns; + iface->is_column_selected = table_wrapper_is_column_selected; + iface->get_column_extent_at = table_wrapper_get_column_extent_at; + iface->get_row_extent_at = table_wrapper_get_row_extent_at; + iface->get_row_header = table_wrapper_get_row_header; + iface->set_row_header = table_wrapper_set_row_header; + iface->get_column_header = table_wrapper_get_column_header; + iface->set_column_header = table_wrapper_set_column_header; + iface->get_caption = table_wrapper_get_caption; + iface->set_caption = table_wrapper_set_caption; + iface->get_summary = table_wrapper_get_summary; + iface->set_summary = table_wrapper_set_summary; + iface->get_row_description = table_wrapper_get_row_description; + iface->set_row_description = table_wrapper_set_row_description; + iface->get_column_description = table_wrapper_get_column_description; + iface->set_column_description = table_wrapper_set_column_description; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atktablecell.cxx b/vcl/unx/gtk3/a11y/atktablecell.cxx new file mode 100644 index 0000000000..35d681b062 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atktablecell.cxx @@ -0,0 +1,269 @@ +/* -*- 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/. + */ + +#include "atkwrapper.hxx" + +#include <com/sun/star/accessibility/XAccessibleTable.hpp> +#include <com/sun/star/accessibility/XAccessibleTableSelection.hpp> +#include <sal/log.hxx> + +static css::uno::Reference<css::accessibility::XAccessibleContext> +getContext(AtkTableCell* pTableCell) +{ + AtkObjectWrapper* pWrap = ATK_OBJECT_WRAPPER(pTableCell); + if (pWrap) + { + return pWrap->mpContext; + } + + return css::uno::Reference<css::accessibility::XAccessibleContext>(); +} + +static css::uno::Reference<css::accessibility::XAccessibleTable> +getTableParent(AtkTableCell* pTableCell) +{ + AtkObject* pParent = atk_object_get_parent(ATK_OBJECT(pTableCell)); + if (!pParent) + return css::uno::Reference<css::accessibility::XAccessibleTable>(); + + AtkObjectWrapper* pWrap = ATK_OBJECT_WRAPPER(pParent); + if (pWrap) + { + if (!pWrap->mpTable.is()) + { + pWrap->mpTable.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpTable; + } + + return css::uno::Reference<css::accessibility::XAccessibleTable>(); +} + +extern "C" { + +static int tablecell_wrapper_get_column_span(AtkTableCell* cell) +{ + int nColumnExtent = -1; + try + { + css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell); + if (!xContext.is()) + return -1; + + css::uno::Reference<css::accessibility::XAccessibleTable> xTable = getTableParent(cell); + if (xTable.is()) + { + const sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent(); + const sal_Int32 nRow = xTable->getAccessibleRow(nChildIndex); + const sal_Int32 nColumn = xTable->getAccessibleColumn(nChildIndex); + nColumnExtent = xTable->getAccessibleColumnExtentAt(nRow, nColumn); + } + } + catch (const css::uno::Exception&) + { + g_warning("Exception in tablecell_wrapper_get_column_span"); + } + + return nColumnExtent; +} + +static GPtrArray* tablecell_wrapper_get_column_header_cells(AtkTableCell* cell) +{ + GPtrArray* pHeaderCells = g_ptr_array_new(); + try + { + css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell); + if (!xContext.is()) + return pHeaderCells; + + css::uno::Reference<css::accessibility::XAccessibleTable> xTable = getTableParent(cell); + if (xTable.is()) + { + const sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent(); + const sal_Int32 nCol = xTable->getAccessibleColumn(nChildIndex); + css::uno::Reference<css::accessibility::XAccessibleTable> xHeaders + = xTable->getAccessibleColumnHeaders(); + if (!xHeaders.is()) + return pHeaderCells; + + for (sal_Int32 nRow = 0; nRow < xHeaders->getAccessibleRowCount(); nRow++) + { + css::uno::Reference<css::accessibility::XAccessible> xCell + = xHeaders->getAccessibleCellAt(nRow, nCol); + AtkObject* pCell = atk_object_wrapper_ref(xCell); + g_ptr_array_add(pHeaderCells, pCell); + } + } + } + catch (const css::uno::Exception&) + { + g_warning("Exception in tablecell_wrapper_get_column_header_cells"); + } + + return pHeaderCells; +} + +static gboolean tablecell_wrapper_get_position(AtkTableCell* cell, gint* row, gint* column) +{ + try + { + css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell); + if (!xContext.is()) + return false; + + css::uno::Reference<css::accessibility::XAccessibleTable> xTable = getTableParent(cell); + if (xTable.is()) + { + const sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent(); + *row = xTable->getAccessibleRow(nChildIndex); + *column = xTable->getAccessibleColumn(nChildIndex); + return true; + } + } + catch (const css::uno::Exception&) + { + g_warning("Exception in tablecell_wrapper_get_position()"); + } + + return false; +} + +static gint tablecell_wrapper_get_row_span(AtkTableCell* cell) +{ + int nRowExtent = -1; + try + { + css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell); + if (!xContext.is()) + return -1; + + css::uno::Reference<css::accessibility::XAccessibleTable> xTable = getTableParent(cell); + if (xTable.is()) + { + const sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent(); + const sal_Int32 nRow = xTable->getAccessibleRow(nChildIndex); + const sal_Int32 nColumn = xTable->getAccessibleColumn(nChildIndex); + nRowExtent = xTable->getAccessibleRowExtentAt(nRow, nColumn); + } + } + catch (const css::uno::Exception&) + { + g_warning("Exception in tablecell_wrapper_get_row_span"); + } + + return nRowExtent; +} + +static GPtrArray* tablecell_wrapper_get_row_header_cells(AtkTableCell* cell) +{ + GPtrArray* pHeaderCells = g_ptr_array_new(); + try + { + css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell); + if (!xContext.is()) + return pHeaderCells; + + css::uno::Reference<css::accessibility::XAccessibleTable> xTable = getTableParent(cell); + if (xTable.is()) + { + const sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent(); + const sal_Int32 nRow = xTable->getAccessibleRow(nChildIndex); + css::uno::Reference<css::accessibility::XAccessibleTable> xHeaders + = xTable->getAccessibleRowHeaders(); + if (!xHeaders.is()) + return pHeaderCells; + + for (sal_Int32 nCol = 0; nCol < xHeaders->getAccessibleColumnCount(); nCol++) + { + css::uno::Reference<css::accessibility::XAccessible> xCell + = xHeaders->getAccessibleCellAt(nRow, nCol); + AtkObject* pCell = atk_object_wrapper_ref(xCell); + g_ptr_array_add(pHeaderCells, pCell); + } + } + } + catch (const css::uno::Exception&) + { + g_warning("Exception in tablecell_wrapper_get_row_header_cells"); + } + + return pHeaderCells; +} + +static gboolean tablecell_wrapper_get_row_column_span(AtkTableCell* cell, gint* row, gint* column, + gint* row_span, gint* column_span) +{ + try + { + css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell); + if (!xContext.is()) + return -1; + + css::uno::Reference<css::accessibility::XAccessibleTable> xTable = getTableParent(cell); + if (xTable.is()) + { + const sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent(); + const sal_Int32 nRow = xTable->getAccessibleRow(nChildIndex); + const sal_Int32 nColumn = xTable->getAccessibleColumn(nChildIndex); + *row = nRow; + *column = nColumn; + *row_span = xTable->getAccessibleRowExtentAt(nRow, nColumn); + *column_span = xTable->getAccessibleColumnExtentAt(nRow, nColumn); + return true; + } + } + catch (const css::uno::Exception&) + { + g_warning("Exception in tablecell_wrapper_get_row_column_span"); + } + + return false; +} + +static AtkObject* tablecell_wrapper_get_table(AtkTableCell* cell) +{ + try + { + css::uno::Reference<css::accessibility::XAccessibleContext> xContext = getContext(cell); + if (!xContext.is()) + return nullptr; + + css::uno::Reference<css::accessibility::XAccessible> xParent + = getContext(cell)->getAccessibleParent(); + if (!xParent.is()) + return nullptr; + + return atk_object_wrapper_ref(xParent); + } + + catch (const css::uno::Exception&) + { + g_warning("Exception in tablecell_wrapper_get_table()"); + } + + return nullptr; +} + +} // extern "C" + +void tablecellIfaceInit(AtkTableCellIface* iface) +{ + g_return_if_fail(iface != nullptr); + + iface->get_column_span = tablecell_wrapper_get_column_span; + iface->get_column_header_cells = tablecell_wrapper_get_column_header_cells; + iface->get_position = tablecell_wrapper_get_position; + iface->get_row_span = tablecell_wrapper_get_row_span; + iface->get_row_header_cells = tablecell_wrapper_get_row_header_cells; + iface->get_row_column_span = tablecell_wrapper_get_row_column_span; + iface->get_table = tablecell_wrapper_get_table; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atktext.cxx b/vcl/unx/gtk3/a11y/atktext.cxx new file mode 100644 index 0000000000..8b1ab67422 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atktext.cxx @@ -0,0 +1,881 @@ +/* -*- 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 "atkwrapper.hxx" +#include "atktextattributes.hxx" +#include <algorithm> + +#include <osl/diagnose.h> +#include <rtl/character.hxx> + +#include <com/sun/star/accessibility/AccessibleScrollType.hpp> +#include <com/sun/star/accessibility/AccessibleTextType.hpp> +#include <com/sun/star/accessibility/TextSegment.hpp> +#include <com/sun/star/accessibility/XAccessibleMultiLineText.hpp> +#include <com/sun/star/accessibility/XAccessibleText.hpp> +#include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp> +#include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/text/TextMarkupType.hpp> + +using namespace ::com::sun::star; + +static sal_Int16 +text_type_from_boundary(AtkTextBoundary boundary_type) +{ + switch(boundary_type) + { + case ATK_TEXT_BOUNDARY_CHAR: + return accessibility::AccessibleTextType::CHARACTER; + case ATK_TEXT_BOUNDARY_WORD_START: + case ATK_TEXT_BOUNDARY_WORD_END: + return accessibility::AccessibleTextType::WORD; + case ATK_TEXT_BOUNDARY_SENTENCE_START: + case ATK_TEXT_BOUNDARY_SENTENCE_END: + return accessibility::AccessibleTextType::SENTENCE; + case ATK_TEXT_BOUNDARY_LINE_START: + case ATK_TEXT_BOUNDARY_LINE_END: + return accessibility::AccessibleTextType::LINE; + default: + return -1; + } +} + +/*****************************************************************************/ + +#if ATK_CHECK_VERSION(2,32,0) +static accessibility::AccessibleScrollType +scroll_type_from_scroll_type(AtkScrollType type) +{ + switch(type) + { + case ATK_SCROLL_TOP_LEFT: + return accessibility::AccessibleScrollType_SCROLL_TOP_LEFT; + case ATK_SCROLL_BOTTOM_RIGHT: + return accessibility::AccessibleScrollType_SCROLL_BOTTOM_RIGHT; + case ATK_SCROLL_TOP_EDGE: + return accessibility::AccessibleScrollType_SCROLL_TOP_EDGE; + case ATK_SCROLL_BOTTOM_EDGE: + return accessibility::AccessibleScrollType_SCROLL_BOTTOM_EDGE; + case ATK_SCROLL_LEFT_EDGE: + return accessibility::AccessibleScrollType_SCROLL_LEFT_EDGE; + case ATK_SCROLL_RIGHT_EDGE: + return accessibility::AccessibleScrollType_SCROLL_RIGHT_EDGE; + case ATK_SCROLL_ANYWHERE: + return accessibility::AccessibleScrollType_SCROLL_ANYWHERE; + default: + throw lang::NoSupportException(); + } +} +#endif + +/*****************************************************************************/ + +static gchar * +adjust_boundaries( css::uno::Reference<css::accessibility::XAccessibleText> const & pText, + accessibility::TextSegment const & rTextSegment, + AtkTextBoundary boundary_type, + gint * start_offset, gint * end_offset ) +{ + accessibility::TextSegment aTextSegment; + OUString aString; + gint start = 0, end = 0; + + if( !rTextSegment.SegmentText.isEmpty() ) + { + switch(boundary_type) + { + case ATK_TEXT_BOUNDARY_CHAR: + if ((rTextSegment.SegmentEnd - rTextSegment.SegmentStart) == 1 + && rtl::isSurrogate(rTextSegment.SegmentText[0])) + return nullptr; + [[fallthrough]]; + case ATK_TEXT_BOUNDARY_LINE_START: + case ATK_TEXT_BOUNDARY_LINE_END: + case ATK_TEXT_BOUNDARY_SENTENCE_START: + start = rTextSegment.SegmentStart; + end = rTextSegment.SegmentEnd; + aString = rTextSegment.SegmentText; + break; + + // the OOo break iterator behaves as SENTENCE_START + case ATK_TEXT_BOUNDARY_SENTENCE_END: + start = rTextSegment.SegmentStart; + end = rTextSegment.SegmentEnd; + + if( start > 0 ) + --start; + if( end > 0 && end < pText->getCharacterCount() - 1 ) + --end; + + aString = pText->getTextRange(start, end); + break; + + case ATK_TEXT_BOUNDARY_WORD_START: + start = rTextSegment.SegmentStart; + + // Determine the start index of the next segment + aTextSegment = pText->getTextBehindIndex(rTextSegment.SegmentEnd, + text_type_from_boundary(boundary_type)); + if( !aTextSegment.SegmentText.isEmpty() ) + end = aTextSegment.SegmentStart; + else + end = pText->getCharacterCount(); + + aString = pText->getTextRange(start, end); + break; + + case ATK_TEXT_BOUNDARY_WORD_END: + end = rTextSegment.SegmentEnd; + + // Determine the end index of the previous segment + aTextSegment = pText->getTextBeforeIndex(rTextSegment.SegmentStart, + text_type_from_boundary(boundary_type)); + if( !aTextSegment.SegmentText.isEmpty() ) + start = aTextSegment.SegmentEnd; + else + start = 0; + + aString = pText->getTextRange(start, end); + break; + + default: + return nullptr; + } + } + + *start_offset = start; + *end_offset = end; + + return OUStringToGChar(aString); +} + +/*****************************************************************************/ + +/// @throws uno::RuntimeException +static css::uno::Reference<css::accessibility::XAccessibleText> + getText( AtkText *pText ) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText ); + if( pWrap ) + { + if( !pWrap->mpText.is() ) + { + pWrap->mpText.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpText; + } + + return css::uno::Reference<css::accessibility::XAccessibleText>(); +} + +/*****************************************************************************/ + +/// @throws uno::RuntimeException +static css::uno::Reference<css::accessibility::XAccessibleTextMarkup> + getTextMarkup( AtkText *pText ) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText ); + if( pWrap ) + { + if( !pWrap->mpTextMarkup.is() ) + { + pWrap->mpTextMarkup.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpTextMarkup; + } + + return css::uno::Reference<css::accessibility::XAccessibleTextMarkup>(); +} + +/*****************************************************************************/ + +/// @throws uno::RuntimeException +static css::uno::Reference<css::accessibility::XAccessibleTextAttributes> + getTextAttributes( AtkText *pText ) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText ); + if( pWrap ) + { + if( !pWrap->mpTextAttributes.is() ) + { + pWrap->mpTextAttributes.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpTextAttributes; + } + + return css::uno::Reference<css::accessibility::XAccessibleTextAttributes>(); +} + +/*****************************************************************************/ + +/// @throws uno::RuntimeException +static css::uno::Reference<css::accessibility::XAccessibleMultiLineText> + getMultiLineText( AtkText *pText ) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText ); + if( pWrap ) + { + if( !pWrap->mpMultiLineText.is() ) + { + pWrap->mpMultiLineText.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpMultiLineText; + } + + return css::uno::Reference<css::accessibility::XAccessibleMultiLineText>(); +} + +/*****************************************************************************/ + +extern "C" { + +static gchar * +text_wrapper_get_text (AtkText *text, + gint start_offset, + gint end_offset) +{ + gchar * ret = nullptr; + + g_return_val_if_fail( (end_offset == -1) || (end_offset >= start_offset), nullptr ); + + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + { + OUString aText; + sal_Int32 n = pText->getCharacterCount(); + + if( start_offset < n ) + { + if( -1 == end_offset ) + aText = pText->getTextRange(start_offset, n - start_offset); + else + aText = pText->getTextRange(start_offset, end_offset); + } + + ret = g_strdup( OUStringToOString(aText, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getText()" ); + } + + return ret; +} + +static gchar * +text_wrapper_get_text_after_offset (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + { + accessibility::TextSegment aTextSegment = pText->getTextBehindIndex(offset, text_type_from_boundary(boundary_type)); + return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in get_text_after_offset()" ); + } + + return nullptr; +} + +static gchar * +text_wrapper_get_text_at_offset (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + { + /* If the user presses the 'End' key, the caret will be placed behind the last character, + * which is the same index as the first character of the next line. In atk the magic offset + * '-2' is used to cover this special case. + */ + if ( + -2 == offset && + (ATK_TEXT_BOUNDARY_LINE_START == boundary_type || + ATK_TEXT_BOUNDARY_LINE_END == boundary_type) + ) + { + css::uno::Reference< + css::accessibility::XAccessibleMultiLineText> pMultiLineText + = getMultiLineText( text ); + if( pMultiLineText.is() ) + { + accessibility::TextSegment aTextSegment = pMultiLineText->getTextAtLineWithCaret(); + return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset); + } + } + + accessibility::TextSegment aTextSegment = pText->getTextAtIndex(offset, text_type_from_boundary(boundary_type)); + return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in get_text_at_offset()" ); + } + + return nullptr; +} + +static gunichar +text_wrapper_get_character_at_offset (AtkText *text, + gint offset) +{ + gint start, end; + gunichar uc = 0xFFFFFFFF; + + gchar * char_as_string = + text_wrapper_get_text_at_offset(text, offset, ATK_TEXT_BOUNDARY_CHAR, + &start, &end); + if( char_as_string ) + { + uc = g_utf8_get_char( char_as_string ); + g_free( char_as_string ); + } + + return uc; +} + +static gchar * +text_wrapper_get_text_before_offset (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + { + accessibility::TextSegment aTextSegment = pText->getTextBeforeIndex(offset, text_type_from_boundary(boundary_type)); + return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in text_before_offset()" ); + } + + return nullptr; +} + +static gint +text_wrapper_get_caret_offset (AtkText *text) +{ + gint offset = -1; + + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + offset = pText->getCaretPosition(); + } + catch(const uno::Exception&) { + g_warning( "Exception in getCaretPosition()" ); + } + + return offset; +} + +static gboolean +text_wrapper_set_caret_offset (AtkText *text, + gint offset) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + return pText->setCaretPosition( offset ); + } + catch(const uno::Exception&) { + g_warning( "Exception in setCaretPosition()" ); + } + + return FALSE; +} + +// #i92232# +static AtkAttributeSet* +handle_text_markup_as_run_attribute( css::uno::Reference<css::accessibility::XAccessibleTextMarkup> const & pTextMarkup, + const gint nTextMarkupType, + const gint offset, + AtkAttributeSet* pSet, + gint *start_offset, + gint *end_offset ) +{ + const gint nTextMarkupCount( pTextMarkup->getTextMarkupCount( nTextMarkupType ) ); + for ( gint nTextMarkupIndex = 0; + nTextMarkupIndex < nTextMarkupCount; + ++nTextMarkupIndex ) + { + accessibility::TextSegment aTextSegment = + pTextMarkup->getTextMarkup( nTextMarkupIndex, nTextMarkupType ); + const gint nStartOffsetTextMarkup = aTextSegment.SegmentStart; + const gint nEndOffsetTextMarkup = aTextSegment.SegmentEnd; + if ( nStartOffsetTextMarkup <= offset ) + { + if ( offset < nEndOffsetTextMarkup ) + { + // text markup at <offset> + *start_offset = ::std::max( *start_offset, + nStartOffsetTextMarkup ); + *end_offset = ::std::min( *end_offset, + nEndOffsetTextMarkup ); + switch ( nTextMarkupType ) + { + case css::text::TextMarkupType::SPELLCHECK: + { + pSet = attribute_set_prepend_misspelled( pSet ); + } + break; + case css::text::TextMarkupType::TRACK_CHANGE_INSERTION: + { + pSet = attribute_set_prepend_tracked_change_insertion( pSet ); + } + break; + case css::text::TextMarkupType::TRACK_CHANGE_DELETION: + { + pSet = attribute_set_prepend_tracked_change_deletion( pSet ); + } + break; + case css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE: + { + pSet = attribute_set_prepend_tracked_change_formatchange( pSet ); + } + break; + default: + { + OSL_ASSERT( false ); + } + } + break; // no further iteration needed. + } + else + { + *start_offset = ::std::max( *start_offset, + nEndOffsetTextMarkup ); + // continue iteration. + } + } + else + { + *end_offset = ::std::min( *end_offset, + nStartOffsetTextMarkup ); + break; // no further iteration. + } + } + + return pSet; +} + +static AtkAttributeSet * +text_wrapper_get_run_attributes( AtkText *text, + gint offset, + gint *start_offset, + gint *end_offset) +{ + AtkAttributeSet *pSet = nullptr; + + try { + bool bOffsetsAreValid = false; + + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is()) + { + uno::Sequence< beans::PropertyValue > aAttributeList; + + css::uno::Reference<css::accessibility::XAccessibleTextAttributes> + pTextAttributes = getTextAttributes( text ); + if(pTextAttributes.is()) // Text attributes are available for paragraphs only + { + aAttributeList = pTextAttributes->getRunAttributes( offset, uno::Sequence< OUString > () ); + } + else // For other text objects use character attributes + { + aAttributeList = pText->getCharacterAttributes( offset, uno::Sequence< OUString > () ); + } + + pSet = attribute_set_new_from_property_values( aAttributeList, true, text ); + // #i100938# + // - always provide start_offset and end_offset + { + accessibility::TextSegment aTextSegment = + pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN); + + *start_offset = aTextSegment.SegmentStart; + // #i100938# + // Do _not_ increment the end_offset provide by <accessibility::TextSegment> instance + *end_offset = aTextSegment.SegmentEnd; + bOffsetsAreValid = true; + } + } + + // Special handling for misspelled text + // #i92232# + // - add special handling for tracked changes and refactor the + // corresponding code for handling misspelled text. + css::uno::Reference<css::accessibility::XAccessibleTextMarkup> + pTextMarkup = getTextMarkup( text ); + if( pTextMarkup.is() ) + { + // Get attribute run here if it hasn't been done before + if (!bOffsetsAreValid && pText.is()) + { + accessibility::TextSegment aAttributeTextSegment = + pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN); + *start_offset = aAttributeTextSegment.SegmentStart; + *end_offset = aAttributeTextSegment.SegmentEnd; + } + // handle misspelled text + pSet = handle_text_markup_as_run_attribute( + pTextMarkup, + css::text::TextMarkupType::SPELLCHECK, + offset, pSet, start_offset, end_offset ); + // handle tracked changes + pSet = handle_text_markup_as_run_attribute( + pTextMarkup, + css::text::TextMarkupType::TRACK_CHANGE_INSERTION, + offset, pSet, start_offset, end_offset ); + pSet = handle_text_markup_as_run_attribute( + pTextMarkup, + css::text::TextMarkupType::TRACK_CHANGE_DELETION, + offset, pSet, start_offset, end_offset ); + pSet = handle_text_markup_as_run_attribute( + pTextMarkup, + css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE, + offset, pSet, start_offset, end_offset ); + } + } + catch(const uno::Exception&){ + + g_warning( "Exception in get_run_attributes()" ); + + if( pSet ) + { + atk_attribute_set_free( pSet ); + pSet = nullptr; + } + } + + return pSet; +} + +/*****************************************************************************/ + +static AtkAttributeSet * +text_wrapper_get_default_attributes( AtkText *text ) +{ + AtkAttributeSet *pSet = nullptr; + + try { + css::uno::Reference<css::accessibility::XAccessibleTextAttributes> + pTextAttributes = getTextAttributes( text ); + if( pTextAttributes.is() ) + { + uno::Sequence< beans::PropertyValue > aAttributeList = + pTextAttributes->getDefaultAttributes( uno::Sequence< OUString > () ); + + pSet = attribute_set_new_from_property_values( aAttributeList, false, text ); + } + } + catch(const uno::Exception&) { + + g_warning( "Exception in get_default_attributes()" ); + + if( pSet ) + { + atk_attribute_set_free( pSet ); + pSet = nullptr; + } + } + + return pSet; +} + +/*****************************************************************************/ + +static void +text_wrapper_get_character_extents( AtkText *text, + gint offset, + gint *x, + gint *y, + gint *width, + gint *height, + AtkCoordType coords ) +{ + *x = *y = *width = *height = -1; + + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + { + awt::Rectangle aRect = pText->getCharacterBounds( offset ); + + gint origin_x = 0; + gint origin_y = 0; + + if (coords == ATK_XY_SCREEN || coords == ATK_XY_WINDOW) + { + g_return_if_fail( ATK_IS_COMPONENT( text ) ); + gint nWidth = -1; + gint nHeight = -1; + atk_component_get_extents(ATK_COMPONENT(text), &origin_x, &origin_y, &nWidth, &nHeight, coords); + } + + *x = aRect.X + origin_x; + *y = aRect.Y + origin_y; + *width = aRect.Width; + *height = aRect.Height; + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getCharacterBounds" ); + } +} + +static gint +text_wrapper_get_character_count (AtkText *text) +{ + gint rv = 0; + + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + rv = pText->getCharacterCount(); + } + catch(const uno::Exception&) { + g_warning( "Exception in getCharacterCount" ); + } + + return rv; +} + +static gint +text_wrapper_get_offset_at_point (AtkText *text, + gint x, + gint y, + AtkCoordType coords) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + { + gint origin_x = 0; + gint origin_y = 0; + + if (coords == ATK_XY_SCREEN || coords == ATK_XY_WINDOW) + { + g_return_val_if_fail( ATK_IS_COMPONENT( text ), -1 ); + gint nWidth = -1; + gint nHeight = -1; + atk_component_get_extents(ATK_COMPONENT(text), &origin_x, &origin_y, &nWidth, &nHeight, coords); + } + + return pText->getIndexAtPoint( awt::Point(x - origin_x, y - origin_y) ); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getIndexAtPoint" ); + } + + return -1; +} + +// FIXME: the whole series of selections API is problematic ... + +static gint +text_wrapper_get_n_selections (AtkText *text) +{ + gint rv = 0; + + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + rv = ( pText->getSelectionEnd() > pText->getSelectionStart() ) ? 1 : 0; + } + catch(const uno::Exception&) { + g_warning( "Exception in getSelectionEnd() or getSelectionStart()" ); + } + + return rv; +} + +static gchar * +text_wrapper_get_selection (AtkText *text, + gint selection_num, + gint *start_offset, + gint *end_offset) +{ + g_return_val_if_fail( selection_num == 0, FALSE ); + + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + { + *start_offset = pText->getSelectionStart(); + *end_offset = pText->getSelectionEnd(); + + return OUStringToGChar( pText->getSelectedText() ); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getSelectionEnd(), getSelectionStart() or getSelectedText()" ); + } + + return nullptr; +} + +static gboolean +text_wrapper_add_selection (AtkText *text, + gint start_offset, + gint end_offset) +{ + // FIXME: can we try to be more compatible by expanding an + // existing adjacent selection ? + + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + return pText->setSelection( start_offset, end_offset ); // ? + } + catch(const uno::Exception&) { + g_warning( "Exception in setSelection()" ); + } + + return FALSE; +} + +static gboolean +text_wrapper_remove_selection (AtkText *text, + gint selection_num) +{ + g_return_val_if_fail( selection_num == 0, FALSE ); + + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + return pText->setSelection( 0, 0 ); // ? + } + catch(const uno::Exception&) { + g_warning( "Exception in setSelection()" ); + } + + return FALSE; +} + +static gboolean +text_wrapper_set_selection (AtkText *text, + gint selection_num, + gint start_offset, + gint end_offset) +{ + g_return_val_if_fail( selection_num == 0, FALSE ); + + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + if( pText.is() ) + return pText->setSelection( start_offset, end_offset ); + } + catch(const uno::Exception&) { + g_warning( "Exception in setSelection()" ); + } + + return FALSE; +} + +#if ATK_CHECK_VERSION(2,32,0) +static gboolean +text_wrapper_scroll_substring_to(AtkText *text, + gint start_offset, + gint end_offset, + AtkScrollType scroll_type) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleText> pText + = getText( text ); + + if( pText.is() ) + return pText->scrollSubstringTo( start_offset, end_offset, + scroll_type_from_scroll_type( scroll_type ) ); + } + catch(const uno::Exception&) { + g_warning( "Exception in scrollSubstringTo()" ); + } + + return FALSE; +} +#endif + +} // extern "C" + +void +textIfaceInit (AtkTextIface *iface) +{ + g_return_if_fail (iface != nullptr); + + iface->get_text = text_wrapper_get_text; + iface->get_character_at_offset = text_wrapper_get_character_at_offset; + iface->get_text_before_offset = text_wrapper_get_text_before_offset; + iface->get_text_at_offset = text_wrapper_get_text_at_offset; + iface->get_text_after_offset = text_wrapper_get_text_after_offset; + iface->get_caret_offset = text_wrapper_get_caret_offset; + iface->set_caret_offset = text_wrapper_set_caret_offset; + iface->get_character_count = text_wrapper_get_character_count; + iface->get_n_selections = text_wrapper_get_n_selections; + iface->get_selection = text_wrapper_get_selection; + iface->add_selection = text_wrapper_add_selection; + iface->remove_selection = text_wrapper_remove_selection; + iface->set_selection = text_wrapper_set_selection; + iface->get_run_attributes = text_wrapper_get_run_attributes; + iface->get_default_attributes = text_wrapper_get_default_attributes; + iface->get_character_extents = text_wrapper_get_character_extents; + iface->get_offset_at_point = text_wrapper_get_offset_at_point; +#if ATK_CHECK_VERSION(2,32,0) + iface->scroll_substring_to = text_wrapper_scroll_substring_to; +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atktextattributes.cxx b/vcl/unx/gtk3/a11y/atktextattributes.cxx new file mode 100644 index 0000000000..25b43f480a --- /dev/null +++ b/vcl/unx/gtk3/a11y/atktextattributes.cxx @@ -0,0 +1,1388 @@ +/* -*- 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 "atktextattributes.hxx" + +#include <com/sun/star/awt/FontSlant.hpp> +#include <com/sun/star/awt/FontStrikeout.hpp> +#include <com/sun/star/awt/FontUnderline.hpp> + +#include <com/sun/star/style/CaseMap.hpp> +#include <com/sun/star/style/LineSpacing.hpp> +#include <com/sun/star/style/LineSpacingMode.hpp> +#include <com/sun/star/style/ParagraphAdjust.hpp> +#include <com/sun/star/style/TabAlign.hpp> +#include <com/sun/star/style/TabStop.hpp> + +#include <com/sun/star/text/WritingMode2.hpp> + +#include "atkwrapper.hxx" + +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> + +#include <i18nlangtag/languagetag.hxx> +#include <tools/UnitConversion.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> + +#include <stdio.h> +#include <string.h> + +using namespace ::com::sun::star; + +typedef gchar* (* AtkTextAttrFunc) ( const uno::Any& rAny ); +typedef bool (* TextPropertyValueFunc) ( uno::Any& rAny, const gchar * value ); + +#define STRNCMP_PARAM( s ) s,sizeof( s )-1 + +/*****************************************************************************/ + +static AtkTextAttribute atk_text_attribute_paragraph_style = ATK_TEXT_ATTR_INVALID; +static AtkTextAttribute atk_text_attribute_font_effect = ATK_TEXT_ATTR_INVALID; +static AtkTextAttribute atk_text_attribute_decoration = ATK_TEXT_ATTR_INVALID; +static AtkTextAttribute atk_text_attribute_line_height = ATK_TEXT_ATTR_INVALID; +static AtkTextAttribute atk_text_attribute_rotation = ATK_TEXT_ATTR_INVALID; +static AtkTextAttribute atk_text_attribute_shadow = ATK_TEXT_ATTR_INVALID; +static AtkTextAttribute atk_text_attribute_tab_interval = ATK_TEXT_ATTR_INVALID; +static AtkTextAttribute atk_text_attribute_tab_stops = ATK_TEXT_ATTR_INVALID; +static AtkTextAttribute atk_text_attribute_writing_mode = ATK_TEXT_ATTR_INVALID; +static AtkTextAttribute atk_text_attribute_vertical_align = ATK_TEXT_ATTR_INVALID; +static AtkTextAttribute atk_text_attribute_misspelled = ATK_TEXT_ATTR_INVALID; +// #i92232# +static AtkTextAttribute atk_text_attribute_tracked_change = ATK_TEXT_ATTR_INVALID; +// #i92233# +static AtkTextAttribute atk_text_attribute_mm_to_pixel_ratio = ATK_TEXT_ATTR_INVALID; + +/*****************************************************************************/ + +/** + * !! IMPORTANT NOTE !! : when adding items to this list, KEEP THE LIST SORTED + * and re-arrange the enum values accordingly. + */ + +namespace { + +enum ExportedAttribute +{ + TEXT_ATTRIBUTE_BACKGROUND_COLOR = 0, + TEXT_ATTRIBUTE_CASEMAP, + TEXT_ATTRIBUTE_FOREGROUND_COLOR, + TEXT_ATTRIBUTE_CONTOURED, + TEXT_ATTRIBUTE_CHAR_ESCAPEMENT, + TEXT_ATTRIBUTE_BLINKING, + TEXT_ATTRIBUTE_FONT_NAME, + TEXT_ATTRIBUTE_HEIGHT, + TEXT_ATTRIBUTE_HIDDEN, + TEXT_ATTRIBUTE_KERNING, + TEXT_ATTRIBUTE_LOCALE, + TEXT_ATTRIBUTE_POSTURE, + TEXT_ATTRIBUTE_RELIEF, + TEXT_ATTRIBUTE_ROTATION, + TEXT_ATTRIBUTE_SCALE, + TEXT_ATTRIBUTE_SHADOWED, + TEXT_ATTRIBUTE_STRIKETHROUGH, + TEXT_ATTRIBUTE_UNDERLINE, + TEXT_ATTRIBUTE_WEIGHT, + // #i92233# + TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO, + TEXT_ATTRIBUTE_JUSTIFICATION, + TEXT_ATTRIBUTE_BOTTOM_MARGIN, + TEXT_ATTRIBUTE_FIRST_LINE_INDENT, + TEXT_ATTRIBUTE_LEFT_MARGIN, + TEXT_ATTRIBUTE_LINE_SPACING, + TEXT_ATTRIBUTE_RIGHT_MARGIN, + TEXT_ATTRIBUTE_STYLE_NAME, + TEXT_ATTRIBUTE_TAB_STOPS, + TEXT_ATTRIBUTE_TOP_MARGIN, + TEXT_ATTRIBUTE_WRITING_MODE, + TEXT_ATTRIBUTE_LAST +}; + +} + +static const char * ExportedTextAttributes[TEXT_ATTRIBUTE_LAST] = +{ + "CharBackColor", // TEXT_ATTRIBUTE_BACKGROUND_COLOR + "CharCaseMap", // TEXT_ATTRIBUTE_CASEMAP + "CharColor", // TEXT_ATTRIBUTE_FOREGROUND_COLOR + "CharContoured", // TEXT_ATTRIBUTE_CONTOURED + "CharEscapement", // TEXT_ATTRIBUTE_CHAR_ESCAPEMENT + "CharFlash", // TEXT_ATTRIBUTE_BLINKING + "CharFontName", // TEXT_ATTRIBUTE_FONT_NAME + "CharHeight", // TEXT_ATTRIBUTE_HEIGHT + "CharHidden", // TEXT_ATTRIBUTE_HIDDEN + "CharKerning", // TEXT_ATTRIBUTE_KERNING + "CharLocale", // TEXT_ATTRIBUTE_LOCALE + "CharPosture", // TEXT_ATTRIBUTE_POSTURE + "CharRelief", // TEXT_ATTRIBUTE_RELIEF + "CharRotation", // TEXT_ATTRIBUTE_ROTATION + "CharScaleWidth", // TEXT_ATTRIBUTE_SCALE + "CharShadowed", // TEXT_ATTRIBUTE_SHADOWED + "CharStrikeout", // TEXT_ATTRIBUTE_STRIKETHROUGH + "CharUnderline", // TEXT_ATTRIBUTE_UNDERLINE + "CharWeight", // TEXT_ATTRIBUTE_WEIGHT + // #i92233# + "MMToPixelRatio", // TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO + "ParaAdjust", // TEXT_ATTRIBUTE_JUSTIFICATION + "ParaBottomMargin", // TEXT_ATTRIBUTE_BOTTOM_MARGIN + "ParaFirstLineIndent", // TEXT_ATTRIBUTE_FIRST_LINE_INDENT + "ParaLeftMargin", // TEXT_ATTRIBUTE_LEFT_MARGIN + "ParaLineSpacing", // TEXT_ATTRIBUTE_LINE_SPACING + "ParaRightMargin", // TEXT_ATTRIBUTE_RIGHT_MARGIN + "ParaStyleName", // TEXT_ATTRIBUTE_STYLE_NAME + "ParaTabStops", // TEXT_ATTRIBUTE_TAB_STOPS + "ParaTopMargin", // TEXT_ATTRIBUTE_TOP_MARGIN + "WritingMode" // TEXT_ATTRIBUTE_WRITING_MODE +}; + +/*****************************************************************************/ + +static gchar* +get_value( const uno::Sequence< beans::PropertyValue >& rAttributeList, + sal_Int32 nIndex, AtkTextAttrFunc func ) +{ + if( nIndex != -1 ) + return func(rAttributeList[nIndex].Value); + + return nullptr; +} + +#define get_bool_value( list, index ) get_value( list, index, Bool2String ) +#define get_height_value( list, index ) get_value( list, index, Float2String ) +#define get_justification_value( list, index ) get_value( list, index, Adjust2Justification ) +#define get_cmm_value( list, index ) get_value( list, index, CMM2UnitString ) +#define get_scale_width( list, index ) get_value( list, index, Scale2String ) +#define get_strikethrough_value( list, index ) get_value( list, index, Strikeout2String ) +#define get_string_value( list, index ) get_value( list, index, GetString ) +#define get_style_value( list, index ) get_value( list, index, FontSlant2Style ) +#define get_underline_value( list, index ) get_value( list, index, Underline2String ) +#define get_variant_value( list, index ) get_value( list, index, CaseMap2String ) +#define get_weight_value( list, index ) get_value( list, index, Weight2String ) +#define get_language_string( list, index ) get_value( list, index, Locale2String ) + +/*****************************************************************************/ + +static bool +InvalidValue( uno::Any&, const gchar * ) +{ + return false; +} + +/*****************************************************************************/ + +static gchar* +Float2String(const uno::Any& rAny) +{ + return g_strdup_printf( "%g", rAny.get<float>() ); +} + +static bool +String2Float( uno::Any& rAny, const gchar * value ) +{ + float fval; + + if( 1 != sscanf( value, "%g", &fval ) ) + return false; + + rAny <<= fval; + return true; +} + +/*****************************************************************************/ + +/// @throws uno::RuntimeException +static css::uno::Reference<css::accessibility::XAccessibleComponent> + getComponent( AtkText *pText ) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText ); + if( pWrap ) + { + if( !pWrap->mpComponent.is() ) + { + pWrap->mpComponent.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpComponent; + } + + return css::uno::Reference<css::accessibility::XAccessibleComponent>(); +} + +static gchar* +get_color_value(const uno::Sequence< beans::PropertyValue >& rAttributeList, + const sal_Int32 * pIndexArray, + ExportedAttribute attr, + AtkText * text) +{ + sal_Int32 nColor = -1; // AUTOMATIC + sal_Int32 nIndex = pIndexArray[attr]; + + if( nIndex != -1 ) + nColor = rAttributeList[nIndex].Value.get<sal_Int32>(); + + /* + * Check for color value for 100% alpha white, which means + * "automatic". Grab the RGB value from XAccessibleComponent + * in this case. + */ + + if( (nColor == -1) && text ) + { + try + { + css::uno::Reference<css::accessibility::XAccessibleComponent> + pComponent = getComponent( text ); + if( pComponent.is() ) + { + switch( attr ) + { + case TEXT_ATTRIBUTE_BACKGROUND_COLOR: + nColor = pComponent->getBackground(); + break; + case TEXT_ATTRIBUTE_FOREGROUND_COLOR: + nColor = pComponent->getForeground(); + break; + default: + break; + } + } + } + + catch(const uno::Exception&) { + g_warning( "Exception in get[Fore|Back]groundColor()" ); + } + } + + if( nColor != -1 ) + { + sal_uInt8 blue = nColor & 0xFF; + sal_uInt8 green = (nColor >> 8) & 0xFF; + sal_uInt8 red = (nColor >> 16) & 0xFF; + + return g_strdup_printf( "%u,%u,%u", red, green, blue ); + } + + return nullptr; +} + +static bool +String2Color( uno::Any& rAny, const gchar * value ) +{ + int red, green, blue; + + if( 3 != sscanf( value, "%d,%d,%d", &red, &green, &blue ) ) + return false; + + sal_Int32 nColor = static_cast<sal_Int32>(blue) | ( static_cast<sal_Int32>(green) << 8 ) | ( static_cast<sal_Int32>(red) << 16 ); + rAny <<= nColor; + return true; +} + +/*****************************************************************************/ + +static gchar* +FontSlant2Style(const uno::Any& rAny) +{ + const gchar * value = nullptr; + + awt::FontSlant aFontSlant; + if(!(rAny >>= aFontSlant)) + return nullptr; + + switch( aFontSlant ) + { + case awt::FontSlant_NONE: + value = "normal"; + break; + + case awt::FontSlant_OBLIQUE: + value = "oblique"; + break; + + case awt::FontSlant_ITALIC: + value = "italic"; + break; + + case awt::FontSlant_REVERSE_OBLIQUE: + value = "reverse oblique"; + break; + + case awt::FontSlant_REVERSE_ITALIC: + value = "reverse italic"; + break; + + default: + break; + } + + if( value ) + return g_strdup( value ); + + return nullptr; +} + +static bool +Style2FontSlant( uno::Any& rAny, const gchar * value ) +{ + awt::FontSlant aFontSlant; + + if( strncmp( value, STRNCMP_PARAM( "normal" ) ) == 0 ) + aFontSlant = awt::FontSlant_NONE; + else if( strncmp( value, STRNCMP_PARAM( "oblique" ) ) == 0 ) + aFontSlant = awt::FontSlant_OBLIQUE; + else if( strncmp( value, STRNCMP_PARAM( "italic" ) ) == 0 ) + aFontSlant = awt::FontSlant_ITALIC; + else if( strncmp( value, STRNCMP_PARAM( "reverse oblique" ) ) == 0 ) + aFontSlant = awt::FontSlant_REVERSE_OBLIQUE; + else if( strncmp( value, STRNCMP_PARAM( "reverse italic" ) ) == 0 ) + aFontSlant = awt::FontSlant_REVERSE_ITALIC; + else + return false; + + rAny <<= aFontSlant; + return true; +} + +/*****************************************************************************/ + +static gchar* +Weight2String(const uno::Any& rAny) +{ + return g_strdup_printf( "%g", rAny.get<float>() * 4 ); +} + +static bool +String2Weight( uno::Any& rAny, const gchar * value ) +{ + float weight; + + if( 1 != sscanf( value, "%g", &weight ) ) + return false; + + rAny <<= weight / 4; + return true; +} + +/*****************************************************************************/ + +static gchar* +Adjust2Justification(const uno::Any& rAny) +{ + const gchar * value = nullptr; + + switch( static_cast<style::ParagraphAdjust>(rAny.get<short>()) ) + { + case style::ParagraphAdjust_LEFT: + value = "left"; + break; + + case style::ParagraphAdjust_RIGHT: + value = "right"; + break; + + case style::ParagraphAdjust_BLOCK: + case style::ParagraphAdjust_STRETCH: + value = "fill"; + break; + + case style::ParagraphAdjust_CENTER: + value = "center"; + break; + + default: + break; + } + + if( value ) + return g_strdup( value ); + + return nullptr; +} + +static bool +Justification2Adjust( uno::Any& rAny, const gchar * value ) +{ + style::ParagraphAdjust nParagraphAdjust; + + if( strncmp( value, STRNCMP_PARAM( "left" ) ) == 0 ) + nParagraphAdjust = style::ParagraphAdjust_LEFT; + else if( strncmp( value, STRNCMP_PARAM( "right" ) ) == 0 ) + nParagraphAdjust = style::ParagraphAdjust_RIGHT; + else if( strncmp( value, STRNCMP_PARAM( "fill" ) ) == 0 ) + nParagraphAdjust = style::ParagraphAdjust_BLOCK; + else if( strncmp( value, STRNCMP_PARAM( "center" ) ) == 0 ) + nParagraphAdjust = style::ParagraphAdjust_CENTER; + else + return false; + + rAny <<= static_cast<short>(nParagraphAdjust); + return true; +} + +/*****************************************************************************/ + +const gchar * const font_strikethrough[] = { + "none", // FontStrikeout::NONE + "single", // FontStrikeout::SINGLE + "double", // FontStrikeout::DOUBLE + nullptr, // FontStrikeout::DONTKNOW + "bold", // FontStrikeout::BOLD + "with /", // FontStrikeout::SLASH + "with X" // FontStrikeout::X +}; + +static gchar* +Strikeout2String(const uno::Any& rAny) +{ + sal_Int16 n = rAny.get<sal_Int16>(); + + if( n >= 0 && o3tl::make_unsigned(n) < SAL_N_ELEMENTS(font_strikethrough) ) + return g_strdup( font_strikethrough[n] ); + + return nullptr; +} + +static bool +String2Strikeout( uno::Any& rAny, const gchar * value ) +{ + for( sal_Int16 n=0; n < sal_Int16(SAL_N_ELEMENTS(font_strikethrough)); ++n ) + { + if( ( nullptr != font_strikethrough[n] ) && + 0 == strncmp( value, font_strikethrough[n], strlen( font_strikethrough[n] ) ) ) + { + rAny <<= n; + return true; + } + } + + return false; +} + +/*****************************************************************************/ + +static gchar* +Underline2String(const uno::Any& rAny) +{ + const gchar * value = nullptr; + + switch( rAny.get<sal_Int16>() ) + { + case awt::FontUnderline::NONE: + value = "none"; + break; + + case awt::FontUnderline::SINGLE: + value = "single"; + break; + + case awt::FontUnderline::DOUBLE: + value = "double"; + break; + + default: + break; + } + + if( value ) + return g_strdup( value ); + + return nullptr; +} + +static bool +String2Underline( uno::Any& rAny, const gchar * value ) +{ + short nUnderline; + + if( strncmp( value, STRNCMP_PARAM( "none" ) ) == 0 ) + nUnderline = awt::FontUnderline::NONE; + else if( strncmp( value, STRNCMP_PARAM( "single" ) ) == 0 ) + nUnderline = awt::FontUnderline::SINGLE; + else if( strncmp( value, STRNCMP_PARAM( "double" ) ) == 0 ) + nUnderline = awt::FontUnderline::DOUBLE; + else + return false; + + rAny <<= nUnderline; + return true; +} + +/*****************************************************************************/ + +static gchar* +GetString(const uno::Any& rAny) +{ + OString aFontName = OUStringToOString( rAny.get< OUString > (), RTL_TEXTENCODING_UTF8 ); + + if( !aFontName.isEmpty() ) + return g_strdup( aFontName.getStr() ); + + return nullptr; +} + +static bool +SetString( uno::Any& rAny, const gchar * value ) +{ + OString aFontName( value ); + + if( !aFontName.isEmpty() ) + { + rAny <<= OStringToOUString( aFontName, RTL_TEXTENCODING_UTF8 ); + return true; + } + + return false; +} + +/*****************************************************************************/ + +// @see http://developer.gnome.org/doc/API/2.0/atk/AtkText.html#AtkTextAttribute + +// CMM = 100th of mm +static gchar* +CMM2UnitString(const uno::Any& rAny) +{ + double fValue = rAny.get<sal_Int32>(); + fValue = fValue * 0.01; + + return g_strdup_printf( "%gmm", fValue ); +} + +static bool +UnitString2CMM( uno::Any& rAny, const gchar * value ) +{ + float fValue = 0.0; // pb: don't use double here because of warning on linux + + if( 1 != sscanf( value, "%gmm", &fValue ) ) + return false; + + fValue = fValue * 100; + + rAny <<= static_cast<sal_Int32>(fValue); + return true; +} + +/*****************************************************************************/ + +static const gchar * bool_values[] = { "true", "false" }; + +static gchar * +Bool2String( const uno::Any& rAny ) +{ + int n = 1; + + if( rAny.get<bool>() ) + n = 0; + + return g_strdup( bool_values[n] ); +} + +static bool +String2Bool( uno::Any& rAny, const gchar * value ) +{ + bool bValue; + + if( strncmp( value, STRNCMP_PARAM( "true" ) ) == 0 ) + bValue = true; + else if( strncmp( value, STRNCMP_PARAM( "false" ) ) == 0 ) + bValue = false; + else + return false; + + rAny <<= bValue; + return true; +} + +/*****************************************************************************/ + +static gchar* +Scale2String( const uno::Any& rAny ) +{ + return g_strdup_printf( "%g", static_cast<double>(rAny.get< sal_Int16 > ()) / 100 ); +} + +static bool +String2Scale( uno::Any& rAny, const gchar * value ) +{ + double dval; + + if( 1 != sscanf( value, "%lg", &dval ) ) + return false; + + rAny <<= static_cast<sal_Int16>(dval * 100); + return true; +} + +/*****************************************************************************/ + +static gchar * +CaseMap2String( const uno::Any& rAny ) +{ + const gchar * value; + + switch( rAny.get<short>() ) + { + case style::CaseMap::SMALLCAPS: + value = "small_caps"; + break; + + default: + value = "normal"; + break; + } + + return g_strdup(value); +} + +static bool +String2CaseMap( uno::Any& rAny, const gchar * value ) +{ + short nCaseMap; + + if( strncmp( value, STRNCMP_PARAM( "normal" ) ) == 0 ) + nCaseMap = style::CaseMap::NONE; + else if( strncmp( value, STRNCMP_PARAM( "small_caps" ) ) == 0 ) + nCaseMap = style::CaseMap::SMALLCAPS; + else + return false; + + rAny <<= nCaseMap; + return true; +} + +/*****************************************************************************/ + +const gchar * const font_stretch[] = { + "ultra_condensed", + "extra_condensed", + "condensed", + "semi_condensed", + "normal", + "semi_expanded", + "expanded", + "extra_expanded", + "ultra_expanded" +}; + +static gchar* +Kerning2Stretch(const uno::Any& rAny) +{ + sal_Int16 n = rAny.get<sal_Int16>(); + int i = 4; + + // No good idea for a mapping - just return the basic info + if( n < 0 ) + i=2; + else if( n > 0 ) + i=6; + + return g_strdup(font_stretch[i]); +} + +/*****************************************************************************/ + +static gchar* +Locale2String(const uno::Any& rAny) +{ + /* FIXME-BCP47: support language tags? And why is country lowercase? */ + lang::Locale aLocale = rAny.get<lang::Locale> (); + LanguageTag aLanguageTag( aLocale); + return g_strdup_printf( "%s-%s", + OUStringToOString( aLanguageTag.getLanguage(), RTL_TEXTENCODING_ASCII_US).getStr(), + OUStringToOString( aLanguageTag.getCountry(), RTL_TEXTENCODING_ASCII_US).toAsciiLowerCase().getStr() ); +} + +static bool +String2Locale( uno::Any& rAny, const gchar * value ) +{ + /* FIXME-BCP47: support language tags? */ + bool ret = false; + + gchar ** str_array = g_strsplit_set( value, "-.@", -1 ); + if( str_array[0] != nullptr ) + { + ret = true; + + lang::Locale aLocale; + + aLocale.Language = OUString::createFromAscii(str_array[0]); + if( str_array[1] != nullptr ) + { + gchar * country = g_ascii_strup(str_array[1], -1); + aLocale.Country = OUString::createFromAscii(country); + g_free(country); + } + + rAny <<= aLocale; + } + + g_strfreev(str_array); + return ret; +} + +/*****************************************************************************/ + +// @see http://www.w3.org/TR/2002/WD-css3-fonts-20020802/#font-effect-prop +static const gchar * relief[] = { "none", "emboss", "engrave" }; +const gchar * const outline = "outline"; + +static gchar * +get_font_effect(const uno::Sequence< beans::PropertyValue >& rAttributeList, + sal_Int32 nContourIndex, sal_Int32 nReliefIndex) +{ + if( nContourIndex != -1 ) + { + if( rAttributeList[nContourIndex].Value.get<bool>() ) + return g_strdup(outline); + } + + if( nReliefIndex != -1 ) + { + sal_Int16 n = rAttributeList[nReliefIndex].Value.get<sal_Int16>(); + if( n < 3) + return g_strdup(relief[n]); + } + + return nullptr; +} + +/*****************************************************************************/ + +// @see http://www.w3.org/TR/REC-CSS2/text.html#lining-striking-props + +enum +{ + DECORATION_NONE = 0, + DECORATION_BLINK, + DECORATION_UNDERLINE, + DECORATION_LINE_THROUGH +}; + +static const gchar * decorations[] = { "none", "blink", "underline", "line-through" }; + +static gchar * +get_text_decoration(const uno::Sequence< beans::PropertyValue >& rAttributeList, + sal_Int32 nBlinkIndex, sal_Int32 nUnderlineIndex, + sal_Int16 nStrikeoutIndex) +{ + gchar * value_list[4] = { nullptr, nullptr, nullptr, nullptr }; + gint count = 0; + + // no property value found + if( ( nBlinkIndex == -1 ) && (nUnderlineIndex == -1 ) && (nStrikeoutIndex == -1)) + return nullptr; + + if( nBlinkIndex != -1 ) + { + if( rAttributeList[nBlinkIndex].Value.get<bool>() ) + value_list[count++] = const_cast <gchar *> (decorations[DECORATION_BLINK]); + } + if( nUnderlineIndex != -1 ) + { + sal_Int16 n = rAttributeList[nUnderlineIndex].Value.get<sal_Int16> (); + if( n != awt::FontUnderline::NONE ) + value_list[count++] = const_cast <gchar *> (decorations[DECORATION_UNDERLINE]); + } + if( nStrikeoutIndex != -1 ) + { + sal_Int16 n = rAttributeList[nStrikeoutIndex].Value.get<sal_Int16> (); + if( n != awt::FontStrikeout::NONE && n != awt::FontStrikeout::DONTKNOW ) + value_list[count++] = const_cast <gchar *> (decorations[DECORATION_LINE_THROUGH]); + } + + if( count == 0 ) + value_list[count++] = const_cast <gchar *> (decorations[DECORATION_NONE]); + + return g_strjoinv(" ", value_list); +} + +/*****************************************************************************/ + +// @see http://www.w3.org/TR/REC-CSS2/text.html#propdef-text-shadow + +static const gchar * shadow_values[] = { "none", "black" }; + +static gchar * +Bool2Shadow( const uno::Any& rAny ) +{ + int n = 0; + + if( rAny.get<bool>() ) + n = 1; + + return g_strdup( shadow_values[n] ); +} + +/*****************************************************************************/ + +static gchar * +Short2Degree( const uno::Any& rAny ) +{ + float f = rAny.get<sal_Int16>() / 10.0; + return g_strdup_printf( "%g", f ); +} + +/*****************************************************************************/ + +const gchar * const directions[] = { "ltr", "rtl", "rtl", "ltr", "none" }; + +static gchar * +WritingMode2Direction( const uno::Any& rAny ) +{ + sal_Int16 n = rAny.get<sal_Int16>(); + + if( 0 <= n && n <= text::WritingMode2::PAGE ) + return g_strdup(directions[n]); + + return nullptr; +} + +// @see http://www.w3.org/TR/2001/WD-css3-text-20010517/#PrimaryTextAdvanceDirection + +const gchar * const writing_modes[] = { "lr-tb", "rl-tb", "tb-rl", "tb-lr", "none" }; +static gchar * +WritingMode2String( const uno::Any& rAny ) +{ + sal_Int16 n = rAny.get<sal_Int16>(); + + if( 0 <= n && n <= text::WritingMode2::PAGE ) + return g_strdup(writing_modes[n]); + + return nullptr; +} + +/*****************************************************************************/ + +const char * const baseline_values[] = { "baseline", "sub", "super" }; + +// @see http://www.w3.org/TR/REC-CSS2/visudet.html#propdef-vertical-align +static gchar * +Escapement2VerticalAlign( const uno::Any& rAny ) +{ + sal_Int16 n = rAny.get<sal_Int16>(); + gchar * ret = nullptr; + + // Values are in %, 101% means "automatic" + if( n == 0 ) + ret = g_strdup(baseline_values[0]); + else if( n == 101 ) + ret = g_strdup(baseline_values[2]); + else if( n == -101 ) + ret = g_strdup(baseline_values[1]); + else + ret = g_strdup_printf( "%d%%", n ); + + return ret; +} + +/*****************************************************************************/ + +// @see http://www.w3.org/TR/REC-CSS2/visudet.html#propdef-line-height +static gchar * +LineSpacing2LineHeight( const uno::Any& rAny ) +{ + style::LineSpacing ls; + gchar * ret = nullptr; + + if( rAny >>= ls ) + { + if( ls.Mode == style::LineSpacingMode::PROP ) + ret = g_strdup_printf( "%d%%", ls.Height ); + else if( ls.Mode == style::LineSpacingMode::FIX ) + ret = g_strdup_printf("%.3gpt", convertMm100ToPoint<double>(ls.Height)); + } + + return ret; +} + +/*****************************************************************************/ + +// @see http://www.w3.org/People/howcome/t/970224HTMLERB-CSS/WD-tabs-970117.html +static gchar * +TabStopList2String( const uno::Any& rAny, bool default_tabs ) +{ + uno::Sequence< style::TabStop > theTabStops; + gchar * ret = nullptr; + + if( rAny >>= theTabStops) + { + sal_Unicode lastFillChar = ' '; + + for( const auto& rTabStop : std::as_const(theTabStops) ) + { + bool is_default_tab = (style::TabAlign_DEFAULT == rTabStop.Alignment); + + if( is_default_tab != default_tabs ) + continue; + + double fValue = rTabStop.Position; + fValue = fValue * 0.01; + + const gchar * tab_align = ""; + switch( rTabStop.Alignment ) + { + case style::TabAlign_LEFT : + tab_align = "left "; + break; + case style::TabAlign_CENTER : + tab_align = "center "; + break; + case style::TabAlign_RIGHT : + tab_align = "right "; + break; + case style::TabAlign_DECIMAL : + tab_align = "decimal "; + break; + default: + break; + } + + const gchar * lead_char = ""; + + if( rTabStop.FillChar != lastFillChar ) + { + lastFillChar = rTabStop.FillChar; + switch (lastFillChar) + { + case ' ': + lead_char = "blank "; + break; + + case '.': + lead_char = "dotted "; + break; + + case '-': + lead_char = "dashed "; + break; + + case '_': + lead_char = "lined "; + break; + + default: + lead_char = "custom "; + break; + } + } + + gchar * tab_str = g_strdup_printf( "%s%s%gmm", lead_char, tab_align, fValue ); + + if( ret ) + { + gchar * old_tab_str = ret; + ret = g_strconcat(old_tab_str, " ", tab_str, nullptr); + g_free( tab_str ); + g_free( old_tab_str ); + } + else + ret = tab_str; + } + } + + return ret; +} + +static gchar * +TabStops2String( const uno::Any& rAny ) +{ + return TabStopList2String(rAny, false); +} + +static gchar * +DefaultTabStops2String( const uno::Any& rAny ) +{ + return TabStopList2String(rAny, true); +} + +/*****************************************************************************/ + +extern "C" { + +static int +attr_compare(const void *p1,const void *p2) +{ + const rtl_uString * pustr = static_cast<const rtl_uString *>(p1); + const char * pc = *static_cast<const char * const *>(p2); + + return rtl_ustr_ascii_compare_WithLength(pustr->buffer, pustr->length, pc); +} + +} + +static void +find_exported_attributes( sal_Int32 *pArray, + const css::uno::Sequence< css::beans::PropertyValue >& rAttributeList ) +{ + for( sal_Int32 i = 0; i < rAttributeList.getLength(); i++ ) + { + const char ** pAttr = static_cast<const char **>(bsearch(rAttributeList[i].Name.pData, + ExportedTextAttributes, TEXT_ATTRIBUTE_LAST, sizeof(const char *), + attr_compare)); + + if( pAttr ) + { + sal_Int32 nIndex = pAttr - ExportedTextAttributes; + pArray[nIndex] = i; + } + } +} + +/*****************************************************************************/ + +static AtkAttributeSet* +attribute_set_prepend( AtkAttributeSet* attribute_set, + AtkTextAttribute attribute, + gchar * value ) +{ + if( value ) + { + AtkAttribute *at = static_cast<AtkAttribute *>(g_malloc( sizeof (AtkAttribute) )); + at->name = g_strdup( atk_text_attribute_get_name( attribute ) ); + at->value = value; + + return g_slist_prepend(attribute_set, at); + } + + return attribute_set; +} + +/*****************************************************************************/ + +AtkAttributeSet* +attribute_set_new_from_property_values( + const uno::Sequence< beans::PropertyValue >& rAttributeList, + bool run_attributes_only, + AtkText *text) +{ + AtkAttributeSet* attribute_set = nullptr; + + sal_Int32 aIndexList[TEXT_ATTRIBUTE_LAST] = { -1 }; + + // Initialize index array with -1 + for(sal_Int32 & rn : aIndexList) + rn = -1; + + find_exported_attributes(aIndexList, rAttributeList); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_BG_COLOR, + get_color_value(rAttributeList, aIndexList, TEXT_ATTRIBUTE_BACKGROUND_COLOR, run_attributes_only ? nullptr : text ) ); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_FG_COLOR, + get_color_value(rAttributeList, aIndexList, TEXT_ATTRIBUTE_FOREGROUND_COLOR, run_attributes_only ? nullptr : text) ); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_INVISIBLE, + get_bool_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_HIDDEN])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_UNDERLINE, + get_underline_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_UNDERLINE])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STRIKETHROUGH, + get_strikethrough_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_STRIKETHROUGH])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_SIZE, + get_height_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_HEIGHT])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_WEIGHT, + get_weight_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WEIGHT])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_FAMILY_NAME, + get_string_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_FONT_NAME])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_VARIANT, + get_variant_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CASEMAP])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STYLE, + get_style_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_POSTURE])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_SCALE, + get_scale_width(rAttributeList, aIndexList[TEXT_ATTRIBUTE_SCALE])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_LANGUAGE, + get_language_string(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LOCALE])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_DIRECTION, + get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WRITING_MODE], WritingMode2Direction)); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STRETCH, + get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_KERNING], Kerning2Stretch)); + + if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_font_effect ) + atk_text_attribute_font_effect = atk_text_attribute_register("font-effect"); + + attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_font_effect, + get_font_effect(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CONTOURED], aIndexList[TEXT_ATTRIBUTE_RELIEF])); + + if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_decoration ) + atk_text_attribute_decoration = atk_text_attribute_register("text-decoration"); + + attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_decoration, + get_text_decoration(rAttributeList, aIndexList[TEXT_ATTRIBUTE_BLINKING], + aIndexList[TEXT_ATTRIBUTE_UNDERLINE], aIndexList[TEXT_ATTRIBUTE_STRIKETHROUGH])); + + if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_rotation ) + atk_text_attribute_rotation = atk_text_attribute_register("text-rotation"); + + attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_rotation, + get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_ROTATION], Short2Degree)); + + if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_shadow ) + atk_text_attribute_shadow = atk_text_attribute_register("text-shadow"); + + attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_shadow, + get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_SHADOWED], Bool2Shadow)); + + if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_writing_mode ) + atk_text_attribute_writing_mode = atk_text_attribute_register("writing-mode"); + + attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_writing_mode, + get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WRITING_MODE], WritingMode2String)); + + if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_vertical_align ) + atk_text_attribute_vertical_align = atk_text_attribute_register("vertical-align"); + + attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_vertical_align, + get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CHAR_ESCAPEMENT], Escapement2VerticalAlign)); + + if( run_attributes_only ) + return attribute_set; + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_LEFT_MARGIN, + get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LEFT_MARGIN])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_RIGHT_MARGIN, + get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_RIGHT_MARGIN])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_INDENT, + get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_FIRST_LINE_INDENT])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_PIXELS_ABOVE_LINES, + get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TOP_MARGIN])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_PIXELS_BELOW_LINES, + get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_BOTTOM_MARGIN])); + + attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_JUSTIFICATION, + get_justification_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_JUSTIFICATION])); + + if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_paragraph_style ) + atk_text_attribute_paragraph_style = atk_text_attribute_register("paragraph-style"); + + attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_paragraph_style, + get_string_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_STYLE_NAME])); + + if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_line_height ) + atk_text_attribute_line_height = atk_text_attribute_register("line-height"); + + attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_line_height, + get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LINE_SPACING], LineSpacing2LineHeight)); + + if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tab_interval ) + atk_text_attribute_tab_interval = atk_text_attribute_register("tab-interval"); + + attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_tab_interval, + get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TAB_STOPS], DefaultTabStops2String)); + + if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tab_stops ) + atk_text_attribute_tab_stops = atk_text_attribute_register("tab-stops"); + + attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_tab_stops, + get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TAB_STOPS], TabStops2String)); + + // #i92233# + if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_mm_to_pixel_ratio ) + atk_text_attribute_mm_to_pixel_ratio = atk_text_attribute_register("mm-to-pixel-ratio"); + + attribute_set = attribute_set_prepend( attribute_set, atk_text_attribute_mm_to_pixel_ratio, + get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO], Float2String)); + + return attribute_set; +} + +AtkAttributeSet* +attribute_set_new_from_extended_attributes( + const css::uno::Reference< css::accessibility::XAccessibleExtendedAttributes >& rExtendedAttributes ) +{ + AtkAttributeSet *pSet = nullptr; + + // extended attributes is a string of colon-separated pairs of property and value, + // with pairs separated by semicolons. Example: "heading-level:2;weight:bold;" + uno::Any anyVal = rExtendedAttributes->getExtendedAttributes(); + OUString sExtendedAttrs; + anyVal >>= sExtendedAttrs; + sal_Int32 nIndex = 0; + do + { + OUString sProperty = sExtendedAttrs.getToken( 0, ';', nIndex ); + + sal_Int32 nColonPos = 0; + OString sPropertyName = OUStringToOString( o3tl::getToken(sProperty, 0, ':', nColonPos ), + RTL_TEXTENCODING_UTF8 ); + OString sPropertyValue = OUStringToOString( o3tl::getToken(sProperty, 0, ':', nColonPos ), + RTL_TEXTENCODING_UTF8 ); + + pSet = attribute_set_prepend( pSet, + atk_text_attribute_register( sPropertyName.getStr() ), + g_strdup_printf( "%s", sPropertyValue.getStr() ) ); + } + while ( nIndex >= 0 && nIndex < sExtendedAttrs.getLength() ); + + return pSet; +} + +AtkAttributeSet* attribute_set_prepend_misspelled( AtkAttributeSet* attribute_set ) +{ + if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_misspelled ) + atk_text_attribute_misspelled = atk_text_attribute_register( "text-spelling" ); + + attribute_set = attribute_set_prepend( attribute_set, atk_text_attribute_misspelled, + g_strdup_printf( "misspelled" ) ); + + return attribute_set; +} + +// #i92232# +AtkAttributeSet* attribute_set_prepend_tracked_change_insertion( AtkAttributeSet* attribute_set ) +{ + if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change ) + { + atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" ); + } + + attribute_set = attribute_set_prepend( attribute_set, + atk_text_attribute_tracked_change, + g_strdup_printf( "insertion" ) ); + + return attribute_set; +} + +AtkAttributeSet* attribute_set_prepend_tracked_change_deletion( AtkAttributeSet* attribute_set ) +{ + if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change ) + { + atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" ); + } + + attribute_set = attribute_set_prepend( attribute_set, + atk_text_attribute_tracked_change, + g_strdup_printf( "deletion" ) ); + + return attribute_set; +} + +AtkAttributeSet* attribute_set_prepend_tracked_change_formatchange( AtkAttributeSet* attribute_set ) +{ + if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change ) + { + atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" ); + } + + attribute_set = attribute_set_prepend( attribute_set, + atk_text_attribute_tracked_change, + g_strdup_printf( "attribute-change" ) ); + + return attribute_set; +} + +/*****************************************************************************/ + +namespace { + +struct AtkTextAttrMapping +{ + const char * name; + TextPropertyValueFunc const toPropertyValue; +}; + +} + +const AtkTextAttrMapping g_TextAttrMap[] = +{ + { "", InvalidValue }, // ATK_TEXT_ATTR_INVALID = 0 + { "ParaLeftMargin", UnitString2CMM }, // ATK_TEXT_ATTR_LEFT_MARGIN + { "ParaRightMargin", UnitString2CMM }, // ATK_TEXT_ATTR_RIGHT_MARGIN + { "ParaFirstLineIndent", UnitString2CMM }, // ATK_TEXT_ATTR_INDENT + { "CharHidden", String2Bool }, // ATK_TEXT_ATTR_INVISIBLE + { "", InvalidValue }, // ATK_TEXT_ATTR_EDITABLE + { "ParaTopMargin", UnitString2CMM }, // ATK_TEXT_ATTR_PIXELS_ABOVE_LINES + { "ParaBottomMargin", UnitString2CMM }, // ATK_TEXT_ATTR_PIXELS_BELOW_LINES + { "", InvalidValue }, // ATK_TEXT_ATTR_PIXELS_INSIDE_WRAP + { "", InvalidValue }, // ATK_TEXT_ATTR_BG_FULL_HEIGHT + { "", InvalidValue }, // ATK_TEXT_ATTR_RISE + { "CharUnderline", String2Underline }, // ATK_TEXT_ATTR_UNDERLINE + { "CharStrikeout", String2Strikeout }, // ATK_TEXT_ATTR_STRIKETHROUGH + { "CharHeight", String2Float }, // ATK_TEXT_ATTR_SIZE + { "CharScaleWidth", String2Scale }, // ATK_TEXT_ATTR_SCALE + { "CharWeight", String2Weight }, // ATK_TEXT_ATTR_WEIGHT + { "CharLocale", String2Locale }, // ATK_TEXT_ATTR_LANGUAGE + { "CharFontName", SetString }, // ATK_TEXT_ATTR_FAMILY_NAME + { "CharBackColor", String2Color }, // ATK_TEXT_ATTR_BG_COLOR + { "CharColor", String2Color }, // ATK_TEXT_ATTR_FG_COLOR + { "", InvalidValue }, // ATK_TEXT_ATTR_BG_STIPPLE + { "", InvalidValue }, // ATK_TEXT_ATTR_FG_STIPPLE + { "", InvalidValue }, // ATK_TEXT_ATTR_WRAP_MODE + { "", InvalidValue }, // ATK_TEXT_ATTR_DIRECTION + { "ParaAdjust", Justification2Adjust }, // ATK_TEXT_ATTR_JUSTIFICATION + { "", InvalidValue }, // ATK_TEXT_ATTR_STRETCH + { "CharCaseMap", String2CaseMap }, // ATK_TEXT_ATTR_VARIANT + { "CharPosture", Style2FontSlant } // ATK_TEXT_ATTR_STYLE +}; + +/*****************************************************************************/ + +bool +attribute_set_map_to_property_values( + AtkAttributeSet* attribute_set, + uno::Sequence< beans::PropertyValue >& rValueList ) +{ + // Ensure enough space .. + uno::Sequence< beans::PropertyValue > aAttributeList (SAL_N_ELEMENTS(g_TextAttrMap)); + auto pAttributeList = aAttributeList.getArray(); + + sal_Int32 nIndex = 0; + for( GSList * item = attribute_set; item != nullptr; item = g_slist_next( item ) ) + { + AtkAttribute* attribute = reinterpret_cast<AtkAttribute *>(item); + + AtkTextAttribute text_attr = atk_text_attribute_for_name( attribute->name ); + if( text_attr < SAL_N_ELEMENTS(g_TextAttrMap) ) + { + if( g_TextAttrMap[text_attr].name[0] != '\0' ) + { + if( ! g_TextAttrMap[text_attr].toPropertyValue( pAttributeList[nIndex].Value, attribute->value) ) + return false; + + pAttributeList[nIndex].Name = OUString::createFromAscii( g_TextAttrMap[text_attr].name ); + pAttributeList[nIndex].State = beans::PropertyState_DIRECT_VALUE; + ++nIndex; + } + } + else + { + // Unsupported text attribute + return false; + } + } + + aAttributeList.realloc( nIndex ); + rValueList = aAttributeList; + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atktextattributes.hxx b/vcl/unx/gtk3/a11y/atktextattributes.hxx new file mode 100644 index 0000000000..716ec45bd3 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atktextattributes.hxx @@ -0,0 +1,45 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp> + +#include <atk/atk.h> + +AtkAttributeSet* attribute_set_new_from_property_values( + const css::uno::Sequence<css::beans::PropertyValue>& rAttributeList, bool run_attributes_only, + AtkText* text); + +AtkAttributeSet* attribute_set_new_from_extended_attributes( + const css::uno::Reference<css::accessibility::XAccessibleExtendedAttributes>& + rExtendedAttributes); + +bool attribute_set_map_to_property_values( + AtkAttributeSet* attribute_set, css::uno::Sequence<css::beans::PropertyValue>& rValueList); + +AtkAttributeSet* attribute_set_prepend_misspelled(AtkAttributeSet* attribute_set); +// #i92232# +AtkAttributeSet* attribute_set_prepend_tracked_change_insertion(AtkAttributeSet* attribute_set); +AtkAttributeSet* attribute_set_prepend_tracked_change_deletion(AtkAttributeSet* attribute_set); +AtkAttributeSet* attribute_set_prepend_tracked_change_formatchange(AtkAttributeSet* attribute_set); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkutil.cxx b/vcl/unx/gtk3/a11y/atkutil.cxx new file mode 100644 index 0000000000..fc15351374 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkutil.cxx @@ -0,0 +1,626 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/XAccessibleText.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/weakref.hxx> +#include <sal/log.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <vcl/menu.hxx> +#include <vcl/toolbox.hxx> + +#include <unx/gtk/gtkdata.hxx> +#include "atkwrapper.hxx" +#include "atkutil.hxx" + +#include <cassert> +#include <set> + +using namespace ::com::sun::star; + +namespace +{ + uno::WeakReference< accessibility::XAccessible > theNextFocusObject; +} + +static guint focus_notify_handler = 0; + +/*****************************************************************************/ + +extern "C" { + +static gboolean +atk_wrapper_focus_idle_handler (gpointer data) +{ + SolarMutexGuard aGuard; + + focus_notify_handler = 0; + + uno::Reference< accessibility::XAccessible > xAccessible = theNextFocusObject; + if( xAccessible.get() == static_cast < accessibility::XAccessible * > (data) ) + { + AtkObject *atk_obj = xAccessible.is() ? atk_object_wrapper_ref( xAccessible ) : nullptr; + // Gail does not notify focus changes to NULL, so do we .. + if( atk_obj ) + { + SAL_WNODEPRECATED_DECLARATIONS_PUSH + atk_focus_tracker_notify(atk_obj); + SAL_WNODEPRECATED_DECLARATIONS_POP + // #i93269# + // emit text_caret_moved event for <XAccessibleText> object, + // if cursor is inside the <XAccessibleText> object. + // also emit state-changed:focused event under the same condition. + { + AtkObjectWrapper* wrapper_obj = ATK_OBJECT_WRAPPER (atk_obj); + if( wrapper_obj && !wrapper_obj->mpText.is() ) + { + wrapper_obj->mpText.set(wrapper_obj->mpContext, css::uno::UNO_QUERY); + if ( wrapper_obj->mpText.is() ) + { + gint caretPos = -1; + + try { + caretPos = wrapper_obj->mpText->getCaretPosition(); + } + catch(const uno::Exception&) { + g_warning( "Exception in getCaretPosition()" ); + } + + if ( caretPos != -1 ) + { + atk_object_notify_state_change( atk_obj, ATK_STATE_FOCUSED, true ); + g_signal_emit_by_name( atk_obj, "text_caret_moved", caretPos ); + } + } + } + } + g_object_unref(atk_obj); + } + } + + return false; +} + +} // extern "C" + +/*****************************************************************************/ + +static void +atk_wrapper_focus_tracker_notify_when_idle( const uno::Reference< accessibility::XAccessible > &xAccessible ) +{ + if( focus_notify_handler ) + g_source_remove(focus_notify_handler); + + theNextFocusObject = xAccessible; + + focus_notify_handler = g_idle_add (atk_wrapper_focus_idle_handler, xAccessible.get()); +} + +/*****************************************************************************/ + +void DocumentFocusListener::disposing( const lang::EventObject& aEvent ) +{ + + // Unref the object here, but do not remove as listener since the object + // might no longer be in a state that safely allows this. + if( aEvent.Source.is() ) + m_aRefList.erase(aEvent.Source); + +} + +/*****************************************************************************/ + +void DocumentFocusListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent ) +{ + try { + switch( aEvent.EventId ) + { + case accessibility::AccessibleEventId::STATE_CHANGED: + { + sal_Int64 nState = accessibility::AccessibleStateType::INVALID; + aEvent.NewValue >>= nState; + + if( accessibility::AccessibleStateType::FOCUSED == nState ) + atk_wrapper_focus_tracker_notify_when_idle( getAccessible(aEvent) ); + + break; + } + + case accessibility::AccessibleEventId::CHILD: + { + uno::Reference< accessibility::XAccessible > xChild; + if( (aEvent.OldValue >>= xChild) && xChild.is() ) + detachRecursive(xChild); + + if( (aEvent.NewValue >>= xChild) && xChild.is() ) + attachRecursive(xChild); + + break; + } + + case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN: + { + if (uno::Reference< accessibility::XAccessible > xAcc = getAccessible(aEvent)) + detachRecursive(xAcc); + break; + } + + default: + break; + } + } + catch( const lang::IndexOutOfBoundsException& ) + { + g_warning("DocumentFocusListener: Focused object has invalid index in parent"); + } +} + +/*****************************************************************************/ + +uno::Reference< accessibility::XAccessible > DocumentFocusListener::getAccessible(const lang::EventObject& aEvent ) +{ + uno::Reference< accessibility::XAccessible > xAccessible(aEvent.Source, uno::UNO_QUERY); + + if( xAccessible.is() ) + return xAccessible; + + uno::Reference< accessibility::XAccessibleContext > xContext(aEvent.Source, uno::UNO_QUERY); + + if( xContext.is() ) + { + uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() ); + if( xParent.is() ) + { + uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() ); + if( xParentContext.is() ) + { + return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() ); + } + } + } + + return uno::Reference< accessibility::XAccessible >(); +} + +/*****************************************************************************/ + +void DocumentFocusListener::attachRecursive( + const uno::Reference< accessibility::XAccessible >& xAccessible +) +{ + uno::Reference< accessibility::XAccessibleContext > xContext = + xAccessible->getAccessibleContext(); + + if( xContext.is() ) + attachRecursive(xAccessible, xContext); +} + +/*****************************************************************************/ + +void DocumentFocusListener::attachRecursive( + const uno::Reference< accessibility::XAccessible >& xAccessible, + const uno::Reference< accessibility::XAccessibleContext >& xContext +) +{ + sal_Int64 nStateSet = xContext->getAccessibleStateSet(); + attachRecursive(xAccessible, xContext, nStateSet); +} + +/*****************************************************************************/ + +void DocumentFocusListener::attachRecursive( + const uno::Reference< accessibility::XAccessible >& xAccessible, + const uno::Reference< accessibility::XAccessibleContext >& xContext, + sal_Int64 nStateSet +) +{ + if( nStateSet & accessibility::AccessibleStateType::FOCUSED ) + atk_wrapper_focus_tracker_notify_when_idle( xAccessible ); + + uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY); + + if (!xBroadcaster.is()) + return; + + // If not already done, add the broadcaster to the list and attach as listener. + const uno::Reference< uno::XInterface >& xInterface = xBroadcaster; + if( !m_aRefList.insert(xInterface).second ) + return; + + xBroadcaster->addAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this)); + + if( ! (nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS) ) + { + sal_Int64 n, nmax = xContext->getAccessibleChildCount(); + for( n = 0; n < nmax; n++ ) + { + uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) ); + + if( xChild.is() ) + attachRecursive(xChild); + } + } +} + +/*****************************************************************************/ + +void DocumentFocusListener::detachRecursive( + const uno::Reference< accessibility::XAccessible >& xAccessible +) +{ + uno::Reference< accessibility::XAccessibleContext > xContext = + xAccessible->getAccessibleContext(); + + if( xContext.is() ) + detachRecursive(xContext); +} + +/*****************************************************************************/ + +void DocumentFocusListener::detachRecursive( + const uno::Reference< accessibility::XAccessibleContext >& xContext +) +{ + sal_Int64 nStateSet = xContext->getAccessibleStateSet(); + + detachRecursive(xContext, nStateSet); +} + +/*****************************************************************************/ + +void DocumentFocusListener::detachRecursive( + const uno::Reference< accessibility::XAccessibleContext >& xContext, + sal_Int64 nStateSet +) +{ + uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY); + + if( !xBroadcaster.is() || 0 >= m_aRefList.erase(xBroadcaster) ) + return; + + xBroadcaster->removeAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this)); + + if( ! (nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS) ) + { + sal_Int64 n, nmax = xContext->getAccessibleChildCount(); + for( n = 0; n < nmax; n++ ) + { + uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) ); + + if( xChild.is() ) + detachRecursive(xChild); + } + } +} + +/*****************************************************************************/ + +/* + * page tabs in gtk are widgets, so we need to simulate focus events for those + */ + +static void handle_tabpage_activated(vcl::Window *pWindow) +{ + uno::Reference< accessibility::XAccessible > xAccessible = + pWindow->GetAccessible(); + + if( ! xAccessible.is() ) + return; + + uno::Reference< accessibility::XAccessibleSelection > xSelection( + xAccessible->getAccessibleContext(), uno::UNO_QUERY); + + if( xSelection.is() ) + atk_wrapper_focus_tracker_notify_when_idle( xSelection->getSelectedAccessibleChild(0) ); +} + +/*****************************************************************************/ + +/* + * toolbar items in gtk are widgets, so we need to simulate focus events for those + */ + +static void notify_toolbox_item_focus(ToolBox *pToolBox) +{ + uno::Reference< accessibility::XAccessible > xAccessible = + pToolBox->GetAccessible(); + + if( ! xAccessible.is() ) + return; + + uno::Reference< accessibility::XAccessibleContext > xContext = + xAccessible->getAccessibleContext(); + + if( ! xContext.is() ) + return; + + ToolBox::ImplToolItems::size_type nPos = pToolBox->GetItemPos( pToolBox->GetHighlightItemId() ); + if( nPos != ToolBox::ITEM_NOTFOUND ) + atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) ); +} + +static void handle_toolbox_highlight(vcl::Window *pWindow) +{ + ToolBox *pToolBox = static_cast <ToolBox *> (pWindow); + + // Make sure either the toolbox or its parent toolbox has the focus + if ( ! pToolBox->HasFocus() ) + { + ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pToolBox->GetParent() ); + if ( ! pToolBoxParent || ! pToolBoxParent->HasFocus() ) + return; + } + + notify_toolbox_item_focus(pToolBox); +} + +static void handle_toolbox_highlightoff(vcl::Window const *pWindow) +{ + ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pWindow->GetParent() ); + + // Notify when leaving sub toolboxes + if( pToolBoxParent && pToolBoxParent->HasFocus() ) + notify_toolbox_item_focus( pToolBoxParent ); +} + +/*****************************************************************************/ + +static void create_wrapper_for_child( + const uno::Reference< accessibility::XAccessibleContext >& xContext, + sal_Int32 index) +{ + if( xContext.is() ) + { + uno::Reference< accessibility::XAccessible > xChild(xContext->getAccessibleChild(index)); + if( xChild.is() ) + { + // create the wrapper object - it will survive the unref unless it is a transient object + g_object_unref( atk_object_wrapper_ref( xChild ) ); + } + } +} + +/*****************************************************************************/ + +static void handle_toolbox_buttonchange(VclWindowEvent const *pEvent) +{ + vcl::Window* pWindow = pEvent->GetWindow(); + sal_Int32 index = static_cast<sal_Int32>(reinterpret_cast<sal_IntPtr>(pEvent->GetData())); + + if( pWindow && pWindow->IsReallyVisible() ) + { + uno::Reference< accessibility::XAccessible > xAccessible(pWindow->GetAccessible()); + if( xAccessible.is() ) + { + create_wrapper_for_child(xAccessible->getAccessibleContext(), index); + } + } +} + +/*****************************************************************************/ + +namespace { + +struct WindowList { + ~WindowList() { assert(list.empty()); }; + // needs to be empty already on DeInitVCL, but at least check it's empty + // on exit + + std::set< VclPtr<vcl::Window> > list; +}; + +WindowList g_aWindowList; + +} + +rtl::Reference<DocumentFocusListener> GtkSalData::GetDocumentFocusListener() +{ + rtl::Reference<DocumentFocusListener> xDFL = m_xDocumentFocusListener.get(); + if (!xDFL) + { + xDFL = new DocumentFocusListener; + m_xDocumentFocusListener = xDFL.get(); + } + return xDFL; +} + +static void handle_get_focus(::VclWindowEvent const * pEvent) +{ + GtkSalData *const pSalData(GetGtkSalData()); + assert(pSalData); + + rtl::Reference<DocumentFocusListener> xDocumentFocusListener(pSalData->GetDocumentFocusListener()); + + vcl::Window *pWindow = pEvent->GetWindow(); + + // The menu bar is handled through VclEventId::MenuHighlightED + if( ! pWindow || !pWindow->IsReallyVisible() || pWindow->GetType() == WindowType::MENUBARWINDOW ) + return; + + // ToolBoxes are handled through VclEventId::ToolboxHighlight + if( pWindow->GetType() == WindowType::TOOLBOX ) + return; + + if( pWindow->GetType() == WindowType::TABCONTROL ) + { + handle_tabpage_activated( pWindow ); + return; + } + + uno::Reference< accessibility::XAccessible > xAccessible = + pWindow->GetAccessible(); + + if( ! xAccessible.is() ) + return; + + uno::Reference< accessibility::XAccessibleContext > xContext = + xAccessible->getAccessibleContext(); + + if( ! xContext.is() ) + return; + + sal_Int64 nStateSet = xContext->getAccessibleStateSet(); + +/* the UNO ToolBox wrapper does not (yet?) support XAccessibleSelection, so we + * need to add listeners to the children instead of re-using the tabpage stuff + */ + if( (nStateSet & accessibility::AccessibleStateType::FOCUSED) && + ( pWindow->GetType() != WindowType::TREELISTBOX ) ) + { + atk_wrapper_focus_tracker_notify_when_idle( xAccessible ); + } + else + { + if( g_aWindowList.list.insert(pWindow).second ) + { + try + { + xDocumentFocusListener->attachRecursive(xAccessible, xContext, nStateSet); + } + catch (const uno::Exception&) + { + g_warning( "Exception caught processing focus events" ); + } + } + } +} + +/*****************************************************************************/ + +static void handle_menu_highlighted(::VclMenuEvent const * pEvent) +{ + try + { + Menu* pMenu = pEvent->GetMenu(); + sal_uInt16 nPos = pEvent->GetItemPos(); + + if( pMenu && nPos != 0xFFFF) + { + uno::Reference< accessibility::XAccessible > xAccessible ( pMenu->GetAccessible() ); + + if( xAccessible.is() ) + { + uno::Reference< accessibility::XAccessibleContext > xContext ( xAccessible->getAccessibleContext() ); + + if( xContext.is() ) + atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) ); + } + } + } + catch (const uno::Exception&) + { + g_warning( "Exception caught processing menu highlight events" ); + } +} + +/*****************************************************************************/ + +static void WindowEventHandler(void *, VclSimpleEvent& rEvent) +{ + try + { + switch (rEvent.GetId()) + { + case VclEventId::WindowShow: + break; + case VclEventId::WindowHide: + break; + case VclEventId::WindowClose: + break; + case VclEventId::WindowGetFocus: + handle_get_focus(static_cast< ::VclWindowEvent const * >(&rEvent)); + break; + case VclEventId::WindowLoseFocus: + break; + case VclEventId::WindowMinimize: + break; + case VclEventId::WindowNormalize: + break; + case VclEventId::WindowKeyInput: + case VclEventId::WindowKeyUp: + case VclEventId::WindowCommand: + case VclEventId::WindowMouseMove: + break; + + case VclEventId::MenuHighlight: + if (const VclMenuEvent* pMenuEvent = dynamic_cast<const VclMenuEvent*>(&rEvent)) + { + handle_menu_highlighted(pMenuEvent); + } + break; + + case VclEventId::ToolboxHighlight: + handle_toolbox_highlight(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow()); + break; + + case VclEventId::ToolboxButtonStateChanged: + handle_toolbox_buttonchange(static_cast< ::VclWindowEvent const * >(&rEvent)); + break; + + case VclEventId::ObjectDying: + g_aWindowList.list.erase( static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow() ); + [[fallthrough]]; + case VclEventId::ToolboxHighlightOff: + handle_toolbox_highlightoff(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow()); + break; + + case VclEventId::TabpageActivate: + handle_tabpage_activated(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow()); + break; + + case VclEventId::ComboboxSetText: + // This looks quite strange to me. Stumbled over this when fixing #i104290#. + // This kicked in when leaving the combobox in the toolbar, after that the events worked. + // I guess this was a try to work around missing combobox events, which didn't do the full job, and shouldn't be necessary anymore. + // Fix for #i104290# was done in toolkit/source/awt/vclxaccessiblecomponent, FOCUSED state for compound controls in general. + // create_wrapper_for_children(static_cast< ::VclWindowEvent const * >(pEvent)->GetWindow()); + break; + + default: + break; + } + } + catch (const lang::IndexOutOfBoundsException&) + { + g_warning("WindowEventHandler: Focused object has invalid index in parent"); + } +} + +static Link<VclSimpleEvent&,void> g_aEventListenerLink( nullptr, WindowEventHandler ); + +/*****************************************************************************/ + +void ooo_atk_util_ensure_event_listener() +{ + static bool bInited; + if (!bInited) + { + Application::AddEventListener( g_aEventListenerLink ); + bInited = true; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkutil.hxx b/vcl/unx/gtk3/a11y/atkutil.hxx new file mode 100644 index 0000000000..bc3d9d73b9 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkutil.hxx @@ -0,0 +1,26 @@ +/* -*- 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 . + */ + +#pragma once + +#include <atk/atk.h> + +void ooo_atk_util_ensure_event_listener(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkvalue.cxx b/vcl/unx/gtk3/a11y/atkvalue.cxx new file mode 100644 index 0000000000..014164da20 --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkvalue.cxx @@ -0,0 +1,154 @@ +/* -*- 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 "atkwrapper.hxx" + +#include <com/sun/star/accessibility/XAccessibleValue.hpp> + +#include <cmath> +#include <string.h> + +using namespace ::com::sun::star; + +/// @throws uno::RuntimeException +static css::uno::Reference<css::accessibility::XAccessibleValue> + getValue( AtkValue *pValue ) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pValue ); + if( pWrap ) + { + if( !pWrap->mpValue.is() ) + { + pWrap->mpValue.set(pWrap->mpContext, css::uno::UNO_QUERY); + } + + return pWrap->mpValue; + } + + return css::uno::Reference<css::accessibility::XAccessibleValue>(); +} + +static void anyToGValue( const uno::Any& aAny, GValue *pValue ) +{ + // FIXME: expand to lots of types etc. + double aDouble=0; + aAny >>= aDouble; + + memset( pValue, 0, sizeof( GValue ) ); + g_value_init( pValue, G_TYPE_DOUBLE ); + g_value_set_double( pValue, aDouble ); +} + +extern "C" { + +static void +value_wrapper_get_current_value( AtkValue *value, + GValue *gval ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleValue> pValue + = getValue( value ); + if( pValue.is() ) + anyToGValue( pValue->getCurrentValue(), gval ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getCurrentValue()" ); + } +} + +static void +value_wrapper_get_maximum_value( AtkValue *value, + GValue *gval ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleValue> pValue + = getValue( value ); + if( pValue.is() ) + anyToGValue( pValue->getMaximumValue(), gval ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getCurrentValue()" ); + } +} + +static void +value_wrapper_get_minimum_value( AtkValue *value, + GValue *gval ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleValue> pValue + = getValue( value ); + if( pValue.is() ) + anyToGValue( pValue->getMinimumValue(), gval ); + } + catch(const uno::Exception&) { + g_warning( "Exception in getCurrentValue()" ); + } +} + +static gboolean +value_wrapper_set_current_value( AtkValue *value, + const GValue *gval ) +{ + try { + css::uno::Reference<css::accessibility::XAccessibleValue> pValue + = getValue( value ); + if( pValue.is() ) + { + double aDouble = g_value_get_double( gval ); + + // Different types of numerical values for XAccessibleValue are possible. + // If current value has an integer type, also use that for the new value, to make + // sure underlying implementations expecting that can handle the value properly. + const css::uno::Any aCurrentValue = pValue->getCurrentValue(); + if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_LONG) + { + const sal_Int32 nValue = std::round<sal_Int32>(aDouble); + return pValue->setCurrentValue(css::uno::Any(nValue)); + } + else if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_HYPER) + { + const sal_Int64 nValue = std::round<sal_Int64>(aDouble); + return pValue->setCurrentValue(css::uno::Any(nValue)); + } + + return pValue->setCurrentValue( uno::Any(aDouble) ); + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getCurrentValue()" ); + } + + return FALSE; +} + +} // extern "C" + +void +valueIfaceInit (AtkValueIface *iface) +{ + g_return_if_fail (iface != nullptr); + + iface->get_current_value = value_wrapper_get_current_value; + iface->get_maximum_value = value_wrapper_get_maximum_value; + iface->get_minimum_value = value_wrapper_get_minimum_value; + iface->set_current_value = value_wrapper_set_current_value; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkwrapper.cxx b/vcl/unx/gtk3/a11y/atkwrapper.cxx new file mode 100644 index 0000000000..c946a6e3da --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkwrapper.cxx @@ -0,0 +1,1103 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Type.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleRelation.hpp> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleText.hpp> +#include <com/sun/star/accessibility/XAccessibleValue.hpp> +#include <com/sun/star/accessibility/XAccessibleAction.hpp> +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleContext2.hpp> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> +#include <com/sun/star/accessibility/XAccessibleRelationSet.hpp> +#include <com/sun/star/accessibility/XAccessibleTable.hpp> +#include <com/sun/star/accessibility/XAccessibleEditableText.hpp> +#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp> +#include <com/sun/star/accessibility/XAccessibleImage.hpp> +#include <com/sun/star/accessibility/XAccessibleHypertext.hpp> +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> +#include <com/sun/star/awt/XWindow.hpp> + +#include <rtl/strbuf.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/syschild.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolkit/unowrap.hxx> + +#include "atkwrapper.hxx" +#include "atkregistry.hxx" +#include "atklistener.hxx" +#include "atktextattributes.hxx" + +#include <vector> +#include <dlfcn.h> + +using namespace ::com::sun::star; + +static GObjectClass *parent_class = nullptr; + +static AtkRelationType mapRelationType( sal_Int16 nRelation ) +{ + AtkRelationType type = ATK_RELATION_NULL; + + switch( nRelation ) + { + case accessibility::AccessibleRelationType::CONTENT_FLOWS_FROM: + type = ATK_RELATION_FLOWS_FROM; + break; + + case accessibility::AccessibleRelationType::CONTENT_FLOWS_TO: + type = ATK_RELATION_FLOWS_TO; + break; + + case accessibility::AccessibleRelationType::CONTROLLED_BY: + type = ATK_RELATION_CONTROLLED_BY; + break; + + case accessibility::AccessibleRelationType::CONTROLLER_FOR: + type = ATK_RELATION_CONTROLLER_FOR; + break; + + case accessibility::AccessibleRelationType::LABEL_FOR: + type = ATK_RELATION_LABEL_FOR; + break; + + case accessibility::AccessibleRelationType::LABELED_BY: + type = ATK_RELATION_LABELLED_BY; + break; + + case accessibility::AccessibleRelationType::MEMBER_OF: + type = ATK_RELATION_MEMBER_OF; + break; + + case accessibility::AccessibleRelationType::SUB_WINDOW_OF: + type = ATK_RELATION_SUBWINDOW_OF; + break; + + case accessibility::AccessibleRelationType::NODE_CHILD_OF: + type = ATK_RELATION_NODE_CHILD_OF; + break; + + default: + break; + } + + return type; +} + +AtkStateType mapAtkState( sal_Int64 nState ) +{ + AtkStateType type = ATK_STATE_INVALID; + + // A perfect / complete mapping ... + switch( nState ) + { +#define MAP_DIRECT( a ) \ + case accessibility::AccessibleStateType::a: \ + type = ATK_STATE_##a; break + + MAP_DIRECT( INVALID ); + MAP_DIRECT( ACTIVE ); + MAP_DIRECT( ARMED ); + MAP_DIRECT( BUSY ); + MAP_DIRECT( CHECKABLE ); + MAP_DIRECT( CHECKED ); + MAP_DIRECT( EDITABLE ); + MAP_DIRECT( ENABLED ); + MAP_DIRECT( EXPANDABLE ); + MAP_DIRECT( EXPANDED ); + MAP_DIRECT( FOCUSABLE ); + MAP_DIRECT( FOCUSED ); + MAP_DIRECT( HORIZONTAL ); + MAP_DIRECT( ICONIFIED ); + MAP_DIRECT( INDETERMINATE ); + MAP_DIRECT( MANAGES_DESCENDANTS ); + MAP_DIRECT( MODAL ); + MAP_DIRECT( MULTI_LINE ); + MAP_DIRECT( OPAQUE ); + MAP_DIRECT( PRESSED ); + MAP_DIRECT( RESIZABLE ); + MAP_DIRECT( SELECTABLE ); + MAP_DIRECT( SELECTED ); + MAP_DIRECT( SENSITIVE ); + MAP_DIRECT( SHOWING ); + MAP_DIRECT( SINGLE_LINE ); + MAP_DIRECT( STALE ); + MAP_DIRECT( TRANSIENT ); + MAP_DIRECT( VERTICAL ); + MAP_DIRECT( VISIBLE ); + MAP_DIRECT( DEFAULT ); + // a spelling error ... + case accessibility::AccessibleStateType::DEFUNC: + type = ATK_STATE_DEFUNCT; break; + case accessibility::AccessibleStateType::MULTI_SELECTABLE: + type = ATK_STATE_MULTISELECTABLE; break; + default: + //Mis-use ATK_STATE_LAST_DEFINED to check if a state is unmapped + //NOTE! Do not report it + type = ATK_STATE_LAST_DEFINED; + break; + } + + return type; +} + +static AtkRole mapToAtkRole(sal_Int16 nRole, sal_Int64 nStates) +{ + switch (nRole) + { + case accessibility::AccessibleRole::UNKNOWN: + return ATK_ROLE_UNKNOWN; + case accessibility::AccessibleRole::ALERT: + return ATK_ROLE_ALERT; + case accessibility::AccessibleRole::BLOCK_QUOTE: + return ATK_ROLE_BLOCK_QUOTE; + case accessibility::AccessibleRole::COLUMN_HEADER: + return ATK_ROLE_COLUMN_HEADER; + case accessibility::AccessibleRole::CANVAS: + return ATK_ROLE_CANVAS; + case accessibility::AccessibleRole::CHECK_BOX: + return ATK_ROLE_CHECK_BOX; + case accessibility::AccessibleRole::CHECK_MENU_ITEM: + return ATK_ROLE_CHECK_MENU_ITEM; + case accessibility::AccessibleRole::COLOR_CHOOSER: + return ATK_ROLE_COLOR_CHOOSER; + case accessibility::AccessibleRole::COMBO_BOX: + return ATK_ROLE_COMBO_BOX; + case accessibility::AccessibleRole::DATE_EDITOR: + return ATK_ROLE_DATE_EDITOR; + case accessibility::AccessibleRole::DESKTOP_ICON: + return ATK_ROLE_DESKTOP_ICON; + case accessibility::AccessibleRole::DESKTOP_PANE: + return ATK_ROLE_DESKTOP_FRAME; + case accessibility::AccessibleRole::DIRECTORY_PANE: + return ATK_ROLE_DIRECTORY_PANE; + case accessibility::AccessibleRole::DIALOG: + return ATK_ROLE_DIALOG; + case accessibility::AccessibleRole::DOCUMENT: + return ATK_ROLE_DOCUMENT_FRAME; + case accessibility::AccessibleRole::EMBEDDED_OBJECT: + return ATK_ROLE_EMBEDDED; + case accessibility::AccessibleRole::END_NOTE: + return ATK_ROLE_FOOTNOTE; + case accessibility::AccessibleRole::FILE_CHOOSER: + return ATK_ROLE_FILE_CHOOSER; + case accessibility::AccessibleRole::FILLER: + return ATK_ROLE_FILLER; + case accessibility::AccessibleRole::FONT_CHOOSER: + return ATK_ROLE_FONT_CHOOSER; + case accessibility::AccessibleRole::FOOTER: + return ATK_ROLE_FOOTER; + case accessibility::AccessibleRole::FOOTNOTE: + return ATK_ROLE_FOOTNOTE; + case accessibility::AccessibleRole::FRAME: + return ATK_ROLE_FRAME; + case accessibility::AccessibleRole::GLASS_PANE: + return ATK_ROLE_GLASS_PANE; + case accessibility::AccessibleRole::GRAPHIC: + return ATK_ROLE_IMAGE; + case accessibility::AccessibleRole::GROUP_BOX: + return ATK_ROLE_GROUPING; + case accessibility::AccessibleRole::HEADER: + return ATK_ROLE_HEADER; + case accessibility::AccessibleRole::HEADING: + return ATK_ROLE_HEADING; + case accessibility::AccessibleRole::HYPER_LINK: + return ATK_ROLE_LINK; + case accessibility::AccessibleRole::ICON: + return ATK_ROLE_ICON; + case accessibility::AccessibleRole::INTERNAL_FRAME: + return ATK_ROLE_INTERNAL_FRAME; + case accessibility::AccessibleRole::LABEL: + return ATK_ROLE_LABEL; + case accessibility::AccessibleRole::LAYERED_PANE: + return ATK_ROLE_LAYERED_PANE; + case accessibility::AccessibleRole::LIST: + return ATK_ROLE_LIST; + case accessibility::AccessibleRole::LIST_ITEM: + return ATK_ROLE_LIST_ITEM; + case accessibility::AccessibleRole::MENU: + return ATK_ROLE_MENU; + case accessibility::AccessibleRole::MENU_BAR: + return ATK_ROLE_MENU_BAR; + case accessibility::AccessibleRole::MENU_ITEM: + return ATK_ROLE_MENU_ITEM; + case accessibility::AccessibleRole::OPTION_PANE: + return ATK_ROLE_OPTION_PANE; + case accessibility::AccessibleRole::PAGE_TAB: + return ATK_ROLE_PAGE_TAB; + case accessibility::AccessibleRole::PAGE_TAB_LIST: + return ATK_ROLE_PAGE_TAB_LIST; + case accessibility::AccessibleRole::PANEL: + return ATK_ROLE_PANEL; + case accessibility::AccessibleRole::PARAGRAPH: + return ATK_ROLE_PARAGRAPH; + case accessibility::AccessibleRole::PASSWORD_TEXT: + return ATK_ROLE_PASSWORD_TEXT; + case accessibility::AccessibleRole::POPUP_MENU: + return ATK_ROLE_POPUP_MENU; + case accessibility::AccessibleRole::PUSH_BUTTON: + return ATK_ROLE_PUSH_BUTTON; + case accessibility::AccessibleRole::PROGRESS_BAR: + return ATK_ROLE_PROGRESS_BAR; + case accessibility::AccessibleRole::RADIO_BUTTON: + return ATK_ROLE_RADIO_BUTTON; + case accessibility::AccessibleRole::RADIO_MENU_ITEM: + return ATK_ROLE_RADIO_MENU_ITEM; + case accessibility::AccessibleRole::ROW_HEADER: + return ATK_ROLE_ROW_HEADER; + case accessibility::AccessibleRole::ROOT_PANE: + return ATK_ROLE_ROOT_PANE; + case accessibility::AccessibleRole::SCROLL_BAR: + return ATK_ROLE_SCROLL_BAR; + case accessibility::AccessibleRole::SCROLL_PANE: + return ATK_ROLE_SCROLL_PANE; + case accessibility::AccessibleRole::SHAPE: + return ATK_ROLE_PANEL; + case accessibility::AccessibleRole::SEPARATOR: + return ATK_ROLE_SEPARATOR; + case accessibility::AccessibleRole::SLIDER: + return ATK_ROLE_SLIDER; + case accessibility::AccessibleRole::SPIN_BOX: + return ATK_ROLE_SPIN_BUTTON; + case accessibility::AccessibleRole::SPLIT_PANE: + return ATK_ROLE_SPLIT_PANE; + case accessibility::AccessibleRole::STATUS_BAR: + return ATK_ROLE_STATUSBAR; + case accessibility::AccessibleRole::TABLE: + return ATK_ROLE_TABLE; + case accessibility::AccessibleRole::TABLE_CELL: + return ATK_ROLE_TABLE_CELL; + case accessibility::AccessibleRole::TEXT: + return ATK_ROLE_TEXT; + case accessibility::AccessibleRole::TEXT_FRAME: + return ATK_ROLE_PANEL; + case accessibility::AccessibleRole::TOGGLE_BUTTON: + return ATK_ROLE_TOGGLE_BUTTON; + case accessibility::AccessibleRole::TOOL_BAR: + return ATK_ROLE_TOOL_BAR; + case accessibility::AccessibleRole::TOOL_TIP: + return ATK_ROLE_TOOL_TIP; + case accessibility::AccessibleRole::TREE: + return ATK_ROLE_TREE; + case accessibility::AccessibleRole::VIEW_PORT: + return ATK_ROLE_VIEWPORT; + case accessibility::AccessibleRole::WINDOW: + return ATK_ROLE_WINDOW; + case accessibility::AccessibleRole::BUTTON_DROPDOWN: + { + if (nStates & css::accessibility::AccessibleStateType::CHECKABLE) + return ATK_ROLE_TOGGLE_BUTTON; + return ATK_ROLE_PUSH_BUTTON; + } + case accessibility::AccessibleRole::BUTTON_MENU: +#if ATK_CHECK_VERSION(2, 46, 0) + return ATK_ROLE_PUSH_BUTTON_MENU; +#else + return ATK_ROLE_PUSH_BUTTON; +#endif + case accessibility::AccessibleRole::CAPTION: + return ATK_ROLE_CAPTION; + case accessibility::AccessibleRole::CHART: + return ATK_ROLE_CHART; + case accessibility::AccessibleRole::EDIT_BAR: + return ATK_ROLE_EDITBAR; + case accessibility::AccessibleRole::FORM: + return ATK_ROLE_FORM; + case accessibility::AccessibleRole::IMAGE_MAP: + return ATK_ROLE_IMAGE_MAP; + case accessibility::AccessibleRole::NOTE: + return ATK_ROLE_COMMENT; + case accessibility::AccessibleRole::PAGE: + return ATK_ROLE_PAGE; + case accessibility::AccessibleRole::RULER: + return ATK_ROLE_RULER; + case accessibility::AccessibleRole::SECTION: + return ATK_ROLE_SECTION; + case accessibility::AccessibleRole::TREE_ITEM: + return ATK_ROLE_TREE_ITEM; + case accessibility::AccessibleRole::TREE_TABLE: + return ATK_ROLE_TREE_TABLE; + case accessibility::AccessibleRole::COMMENT: + return ATK_ROLE_COMMENT; + case accessibility::AccessibleRole::COMMENT_END: + return ATK_ROLE_UNKNOWN; + case accessibility::AccessibleRole::DOCUMENT_PRESENTATION: + return ATK_ROLE_DOCUMENT_PRESENTATION; + case accessibility::AccessibleRole::DOCUMENT_SPREADSHEET: + return ATK_ROLE_DOCUMENT_SPREADSHEET; + case accessibility::AccessibleRole::DOCUMENT_TEXT: + return ATK_ROLE_DOCUMENT_TEXT; + case accessibility::AccessibleRole::STATIC: + return ATK_ROLE_STATIC; + case accessibility::AccessibleRole::NOTIFICATION: + return ATK_ROLE_NOTIFICATION; + default: + SAL_WARN("vcl.gtk", "Unmapped accessible role: " << nRole); + return ATK_ROLE_UNKNOWN; + } +} + +/*****************************************************************************/ + +extern "C" { + +/*****************************************************************************/ + +static const gchar* +wrapper_get_name( AtkObject *atk_obj ) +{ + AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj); + + if( obj->mpContext.is() ) + { + try { + OString aName = + OUStringToOString( + obj->mpContext->getAccessibleName(), + RTL_TEXTENCODING_UTF8); + + int nCmp = atk_obj->name ? rtl_str_compare( atk_obj->name, aName.getStr() ) : -1; + if( nCmp != 0 ) + { + if( atk_obj->name ) + g_free(atk_obj->name); + atk_obj->name = g_strdup(aName.getStr()); + + return atk_obj->name; + } + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleName()" ); + } + } + + return ATK_OBJECT_CLASS (parent_class)->get_name(atk_obj); +} + +/*****************************************************************************/ + +static const gchar* +wrapper_get_description( AtkObject *atk_obj ) +{ + AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj); + + if( obj->mpContext.is() ) + { + try { + OString aDescription = + OUStringToOString( + obj->mpContext->getAccessibleDescription(), + RTL_TEXTENCODING_UTF8); + + g_free(atk_obj->description); + atk_obj->description = g_strdup(aDescription.getStr()); + + return atk_obj->description; + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleDescription()" ); + } + } + + return ATK_OBJECT_CLASS (parent_class)->get_description(atk_obj); + +} + +/*****************************************************************************/ + +static AtkAttributeSet * +wrapper_get_attributes( AtkObject *atk_obj ) +{ + AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER( atk_obj ); + AtkAttributeSet *pSet = nullptr; + + try + { + uno::Reference< accessibility::XAccessibleExtendedAttributes > + xExtendedAttrs( obj->mpContext, uno::UNO_QUERY ); + if( xExtendedAttrs.is() ) + pSet = attribute_set_new_from_extended_attributes( xExtendedAttrs ); + } + catch(const uno::Exception&) + { + g_warning( "Exception in getAccessibleAttributes()" ); + } + + return pSet; +} + +/*****************************************************************************/ + +static gint +wrapper_get_n_children( AtkObject *atk_obj ) +{ + AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj); + + if (obj->mpSysObjChild) + return 1; + + gint n = 0; + + if( obj->mpContext.is() ) + { + try { + sal_Int64 nChildCount = obj->mpContext->getAccessibleChildCount(); + if (nChildCount > std::numeric_limits<gint>::max()) + { + SAL_WARN("vcl.gtk", "wrapper_get_n_children: Child count exceeds maximum gint value, " + "returning max gint."); + nChildCount = std::numeric_limits<gint>::max(); + } + n = nChildCount; + } + catch(const uno::Exception&) { + TOOLS_WARN_EXCEPTION( "vcl", "Exception" ); + } + } + + return n; +} + +/*****************************************************************************/ + +static AtkObject * +wrapper_ref_child( AtkObject *atk_obj, + gint i ) +{ + SolarMutexGuard aGuard; + + AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj); + + if (obj->mpSysObjChild) + { + g_object_ref(obj->mpSysObjChild); + return obj->mpSysObjChild; + } + + AtkObject* child = nullptr; + + // see comments above atk_object_wrapper_remove_child + if( -1 < i && obj->index_of_child_about_to_be_removed == i ) + { + g_object_ref( obj->child_about_to_be_removed ); + return obj->child_about_to_be_removed; + } + + if( obj->mpContext.is() ) + { + try { + uno::Reference< accessibility::XAccessible > xAccessible = + obj->mpContext->getAccessibleChild( i ); + + child = atk_object_wrapper_ref( xAccessible ); + } + catch(const uno::Exception&) { + TOOLS_WARN_EXCEPTION( "vcl", "getAccessibleChild"); + } + } + + return child; +} + +/*****************************************************************************/ + +static gint +wrapper_get_index_in_parent( AtkObject *atk_obj ) +{ + SolarMutexGuard aGuard; + + AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj); + + //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y + if (obj->mpOrig) + return atk_object_get_index_in_parent(obj->mpOrig); + + gint i = -1; + + if( obj->mpContext.is() ) + { + try { + sal_Int64 nIndex = obj->mpContext->getAccessibleIndexInParent(); + if (nIndex > std::numeric_limits<gint>::max()) + { + // use -2 when the child index is too large to fit into 32 bit to neither use the + // valid index of another child nor -1, which would e.g. make Orca interpret the + // object as being a zombie + SAL_WARN("vcl.gtk", "wrapper_get_index_in_parent: Child index exceeds maximum gint value, " + "returning -2."); + nIndex = -2; + } + i = nIndex; + } + catch(const uno::Exception&) { + g_warning( "Exception in getAccessibleIndexInParent()" ); + } + } + return i; +} + +/*****************************************************************************/ + +AtkRelation* +atk_object_wrapper_relation_new(const accessibility::AccessibleRelation& rRelation) +{ + sal_uInt32 nTargetCount = rRelation.TargetSet.getLength(); + + std::vector<AtkObject*> aTargets; + + for (const auto& rTarget : rRelation.TargetSet) + { + uno::Reference< accessibility::XAccessible > xAccessible( rTarget, uno::UNO_QUERY ); + aTargets.push_back(atk_object_wrapper_ref(xAccessible)); + } + + AtkRelation *pRel = + atk_relation_new( + aTargets.data(), nTargetCount, + mapRelationType( rRelation.RelationType ) + ); + + return pRel; +} + +static AtkRelationSet * +wrapper_ref_relation_set( AtkObject *atk_obj ) +{ + AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj); + + //if we're a native GtkDrawingArea with custom a11y, use the default toolkit relation set impl + if (obj->mpOrig) + return atk_object_ref_relation_set(obj->mpOrig); + + AtkRelationSet *pSet = atk_relation_set_new(); + + if( obj->mpContext.is() ) + { + try { + uno::Reference< accessibility::XAccessibleRelationSet > xRelationSet( + obj->mpContext->getAccessibleRelationSet() + ); + + sal_Int32 nRelations = xRelationSet.is() ? xRelationSet->getRelationCount() : 0; + for( sal_Int32 n = 0; n < nRelations; n++ ) + { + AtkRelation *pRel = atk_object_wrapper_relation_new(xRelationSet->getRelation(n)); + atk_relation_set_add(pSet, pRel); + g_object_unref(pRel); + } + } + catch(const uno::Exception &) { + g_object_unref( G_OBJECT( pSet ) ); + pSet = nullptr; + } + } + + return pSet; +} + +static AtkStateSet * +wrapper_ref_state_set( AtkObject *atk_obj ) +{ + AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj); + AtkStateSet *pSet = atk_state_set_new(); + + if( obj->mpContext.is() ) + { + try { + sal_Int64 nStateSet = obj->mpContext->getAccessibleStateSet(); + + if( nStateSet ) + { + for (int i=0; i<63; ++i) + { + // ATK_STATE_LAST_DEFINED is used to check if the state + // is unmapped, do not report it to Atk + sal_Int64 nState = sal_Int64(1) << i; + if ( (nStateSet & nState) && mapAtkState( nState ) != ATK_STATE_LAST_DEFINED ) + atk_state_set_add_state( pSet, mapAtkState( nState ) ); + } + + // We need to emulate FOCUS state for menus, menu-items etc. + if( atk_obj == atk_get_focus_object() ) + atk_state_set_add_state( pSet, ATK_STATE_FOCUSED ); +/* FIXME - should we do this ? + else + atk_state_set_remove_state( pSet, ATK_STATE_FOCUSED ); +*/ + } + } + + catch(const uno::Exception &) { + g_warning( "Exception in wrapper_ref_state_set" ); + atk_state_set_add_state( pSet, ATK_STATE_DEFUNCT ); + } + } + else + atk_state_set_add_state( pSet, ATK_STATE_DEFUNCT ); + + return pSet; +} + +/*****************************************************************************/ + +static void +atk_object_wrapper_finalize (GObject *obj) +{ + AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER (obj); + + if( pWrap->mpAccessible.is() ) + { + ooo_wrapper_registry_remove( pWrap->mpAccessible ); + SolarMutexGuard aGuard; + pWrap->mpAccessible.clear(); + } + + atk_object_wrapper_dispose( pWrap ); + + parent_class->finalize( obj ); +} + +static void +atk_object_wrapper_class_init (AtkObjectWrapperClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( klass ); + AtkObjectClass *atk_class = ATK_OBJECT_CLASS( klass ); + + parent_class = static_cast<GObjectClass *>(g_type_class_peek_parent (klass)); + + // GObject methods + gobject_class->finalize = atk_object_wrapper_finalize; + + // AtkObject methods + atk_class->get_name = wrapper_get_name; + atk_class->get_description = wrapper_get_description; + atk_class->get_attributes = wrapper_get_attributes; + atk_class->get_n_children = wrapper_get_n_children; + atk_class->ref_child = wrapper_ref_child; + atk_class->get_index_in_parent = wrapper_get_index_in_parent; + atk_class->ref_relation_set = wrapper_ref_relation_set; + atk_class->ref_state_set = wrapper_ref_state_set; + + AtkObjectClass* orig_atk_klass = static_cast<AtkObjectClass*>(g_type_class_ref(ATK_TYPE_OBJECT)); + // tdf#150496 we want to inherit from GtkAccessible because gtk assumes it can cast to GtkAccessible + // but we want the original behaviour we got from atk_object_real_get_parent when we inherited + // from AtkObject + atk_class->get_parent = orig_atk_klass->get_parent; + // and likewise for focus_event + atk_class->focus_event = orig_atk_klass->focus_event; + g_type_class_unref(orig_atk_klass); +} + +static void +atk_object_wrapper_init (AtkObjectWrapper *wrapper, + AtkObjectWrapperClass*) +{ + wrapper->mpAction = nullptr; + wrapper->mpComponent = nullptr; + wrapper->mpEditableText = nullptr; + wrapper->mpHypertext = nullptr; + wrapper->mpImage = nullptr; + wrapper->mpSelection = nullptr; + wrapper->mpTable = nullptr; + wrapper->mpTableSelection = nullptr; + wrapper->mpText = nullptr; + wrapper->mpValue = nullptr; +} + +} // extern "C" + +GType +atk_object_wrapper_get_type() +{ + static GType type = 0; + + if (!type) + { + static const GTypeInfo typeInfo = + { + sizeof (AtkObjectWrapperClass), + nullptr, + nullptr, + reinterpret_cast<GClassInitFunc>(atk_object_wrapper_class_init), + nullptr, + nullptr, + sizeof (AtkObjectWrapper), + 0, + reinterpret_cast<GInstanceInitFunc>(atk_object_wrapper_init), + nullptr + } ; + type = g_type_register_static (GTK_TYPE_WIDGET_ACCESSIBLE, + "OOoAtkObj", + &typeInfo, GTypeFlags(0)) ; + } + return type; +} + +static bool +isOfType( uno::XInterface *pInterface, const uno::Type & rType ) +{ + g_return_val_if_fail( pInterface != nullptr, false ); + + bool bIs = false; + try { + uno::Any aRet = pInterface->queryInterface( rType ); + + bIs = ( ( typelib_TypeClass_INTERFACE == aRet.pType->eTypeClass ) && + ( aRet.pReserved != nullptr ) ); + } catch( const uno::Exception &) { } + + return bIs; +} + +// Whether AtkTableCell can be supported for the interface. +// Returns true if the corresponding XAccessible has role TABLE_CELL +// and an XAccessibleTable as parent. +static bool isTableCell(uno::XInterface* pInterface) +{ + g_return_val_if_fail(pInterface != nullptr, false); + + try { + auto aType = cppu::UnoType<accessibility::XAccessible>::get().getTypeLibType(); + uno::Any aAcc = pInterface->queryInterface(aType); + + css::uno::Reference<css::accessibility::XAccessible> xAcc; + aAcc >>= xAcc; + if (!xAcc.is()) + return false; + + css::uno::Reference<css::accessibility::XAccessibleContext> xContext = xAcc->getAccessibleContext(); + if (!xContext.is() || !(xContext->getAccessibleRole() == accessibility::AccessibleRole::TABLE_CELL)) + return false; + + css::uno::Reference<css::accessibility::XAccessible> xParent = xContext->getAccessibleParent(); + if (!xParent.is()) + return false; + css::uno::Reference<css::accessibility::XAccessibleContext> xParentContext = xParent->getAccessibleContext(); + if (!xParentContext.is()) + return false; + + css::uno::Reference<css::accessibility::XAccessibleTable> xTable(xParentContext, uno::UNO_QUERY); + return xTable.is(); + } + catch(const uno::Exception &) + { + g_warning("Exception in isTableCell()"); + } + + return false; +} + +extern "C" { +typedef GType (* GetGIfaceType ) (); +} +const struct { + const char *name; + GInterfaceInitFunc const aInit; + GetGIfaceType const aGetGIfaceType; + const uno::Type & (*aGetUnoType) (); +} aTypeTable[] = { +// re-location heaven: + { + "Comp", reinterpret_cast<GInterfaceInitFunc>(componentIfaceInit), + atk_component_get_type, + cppu::UnoType<accessibility::XAccessibleComponent>::get + }, + { + "Act", reinterpret_cast<GInterfaceInitFunc>(actionIfaceInit), + atk_action_get_type, + cppu::UnoType<accessibility::XAccessibleAction>::get + }, + { + "Txt", reinterpret_cast<GInterfaceInitFunc>(textIfaceInit), + atk_text_get_type, + cppu::UnoType<accessibility::XAccessibleText>::get + }, + { + "Val", reinterpret_cast<GInterfaceInitFunc>(valueIfaceInit), + atk_value_get_type, + cppu::UnoType<accessibility::XAccessibleValue>::get + }, + { + "Tab", reinterpret_cast<GInterfaceInitFunc>(tableIfaceInit), + atk_table_get_type, + cppu::UnoType<accessibility::XAccessibleTable>::get + }, + { + "Cell", reinterpret_cast<GInterfaceInitFunc>(tablecellIfaceInit), + atk_table_cell_get_type, + // there is no UNO a11y interface for table cells, so this case is handled separately below + nullptr + }, + { + "Edt", reinterpret_cast<GInterfaceInitFunc>(editableTextIfaceInit), + atk_editable_text_get_type, + cppu::UnoType<accessibility::XAccessibleEditableText>::get + }, + { + "Img", reinterpret_cast<GInterfaceInitFunc>(imageIfaceInit), + atk_image_get_type, + cppu::UnoType<accessibility::XAccessibleImage>::get + }, + { + "Hyp", reinterpret_cast<GInterfaceInitFunc>(hypertextIfaceInit), + atk_hypertext_get_type, + cppu::UnoType<accessibility::XAccessibleHypertext>::get + }, + { + "Sel", reinterpret_cast<GInterfaceInitFunc>(selectionIfaceInit), + atk_selection_get_type, + cppu::UnoType<accessibility::XAccessibleSelection>::get + } + // AtkDocument is a nastily broken interface (so far) + // we could impl. get_document_type perhaps though. +}; + +const int aTypeTableSize = G_N_ELEMENTS( aTypeTable ); + +static GType +ensureTypeFor( uno::XInterface *pAccessible ) +{ + int i; + bool bTypes[ aTypeTableSize ] = { false, }; + OStringBuffer aTypeNameBuf( "OOoAtkObj" ); + + for( i = 0; i < aTypeTableSize; i++ ) + { + if(!g_strcmp0(aTypeTable[i].name, "Cell")) + { + // there is no UNO interface for table cells, but AtkTableCell can be supported + // for table cells via the methods of the parent that is a table + if (isTableCell(pAccessible)) + { + aTypeNameBuf.append(aTypeTable[i].name); + bTypes[i] = true; + } + } + else if (isOfType( pAccessible, aTypeTable[i].aGetUnoType() ) ) + { + aTypeNameBuf.append(aTypeTable[i].name); + bTypes[i] = true; + } + } + + OString aTypeName = aTypeNameBuf.makeStringAndClear(); + GType nType = g_type_from_name( aTypeName.getStr() ); + if( nType == G_TYPE_INVALID ) + { + GTypeInfo aTypeInfo = { + sizeof( AtkObjectWrapperClass ), + nullptr, nullptr, nullptr, nullptr, nullptr, + sizeof( AtkObjectWrapper ), + 0, nullptr, nullptr + } ; + nType = g_type_register_static( ATK_TYPE_OBJECT_WRAPPER, + aTypeName.getStr(), &aTypeInfo, + GTypeFlags(0) ) ; + + for( int j = 0; j < aTypeTableSize; j++ ) + if( bTypes[j] ) + { + GInterfaceInfo aIfaceInfo = { nullptr, nullptr, nullptr }; + aIfaceInfo.interface_init = aTypeTable[j].aInit; + g_type_add_interface_static (nType, aTypeTable[j].aGetGIfaceType(), + &aIfaceInfo); + } + } + return nType; +} + +AtkObject * +atk_object_wrapper_ref( const uno::Reference< accessibility::XAccessible > &rxAccessible, bool create ) +{ + g_return_val_if_fail( bool(rxAccessible), nullptr ); + + AtkObject *obj = ooo_wrapper_registry_get(rxAccessible); + if( obj ) + { + g_object_ref( obj ); + return obj; + } + + if( create ) + return atk_object_wrapper_new( rxAccessible ); + + return nullptr; +} + +AtkObject * +atk_object_wrapper_new( const css::uno::Reference< css::accessibility::XAccessible >& rxAccessible, + AtkObject* parent, AtkObject* orig ) +{ + g_return_val_if_fail( bool(rxAccessible), nullptr ); + + AtkObjectWrapper *pWrap = nullptr; + + try { + uno::Reference< accessibility::XAccessibleContext > xContext(rxAccessible->getAccessibleContext()); + + g_return_val_if_fail( bool(xContext), nullptr ); + + GType nType = ensureTypeFor( xContext.get() ); + gpointer obj = g_object_new( nType, nullptr); + + pWrap = ATK_OBJECT_WRAPPER( obj ); + pWrap->mpAccessible = rxAccessible; + + pWrap->index_of_child_about_to_be_removed = -1; + pWrap->child_about_to_be_removed = nullptr; + + pWrap->mpContext = xContext; + pWrap->mpOrig = orig; + + AtkObject* atk_obj = ATK_OBJECT(pWrap); + atk_obj->role = mapToAtkRole(xContext->getAccessibleRole(), xContext->getAccessibleStateSet()); + atk_obj->accessible_parent = parent; + + ooo_wrapper_registry_add( rxAccessible, atk_obj ); + + if( parent ) + g_object_ref( atk_obj->accessible_parent ); + else + { + /* gail_focus_tracker remembers the focused object at the first + * parent in the hierarchy that is a Gtk+ widget, but at the time the + * event gets processed (at idle), it may be too late to create the + * hierarchy, so doing it now .. + */ + uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() ); + + if( xParent.is() ) + atk_obj->accessible_parent = atk_object_wrapper_ref( xParent ); + } + + // Attach a listener to the UNO object if it's not TRANSIENT + sal_Int64 nStateSet( xContext->getAccessibleStateSet() ); + if( ! (nStateSet & accessibility::AccessibleStateType::TRANSIENT ) ) + { + uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY); + if( xBroadcaster.is() ) + { + uno::Reference<accessibility::XAccessibleEventListener> xListener(new AtkListener(pWrap)); + xBroadcaster->addAccessibleEventListener(xListener); + } + else + OSL_ASSERT( false ); + } + + static auto func = reinterpret_cast<void(*)(AtkObject*, const gchar*)>(dlsym(nullptr, "atk_object_set_accessible_id")); + if (func) + { + css::uno::Reference<css::accessibility::XAccessibleContext2> xContext2(xContext, css::uno::UNO_QUERY); + if( xContext2.is() ) + { + OString aId = OUStringToOString( xContext2->getAccessibleId(), RTL_TEXTENCODING_UTF8); + (*func)(atk_obj, aId.getStr()); + } + } + + // tdf#141197 if we have a sysobj child then include that in the hierarchy + if (UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper()) + { + css::uno::Reference<css::awt::XWindow> xAWTWindow(rxAccessible, css::uno::UNO_QUERY); + VclPtr<vcl::Window> xWindow = pWrapper->GetWindow(xAWTWindow); + if (xWindow && xWindow->GetType() == WindowType::SYSTEMCHILDWINDOW) + { + const SystemEnvData* pEnvData = static_cast<SystemChildWindow*>(xWindow.get())->GetSystemData(); + if (GtkWidget *pSysObj = pEnvData ? static_cast<GtkWidget*>(pEnvData->pWidget) : nullptr) + pWrap->mpSysObjChild = gtk_widget_get_accessible(pSysObj); + } + } + + return ATK_OBJECT( pWrap ); + } + catch (const uno::Exception &) + { + if( pWrap ) + g_object_unref( pWrap ); + + return nullptr; + } +} + +/*****************************************************************************/ + +void atk_object_wrapper_add_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index) +{ + AtkObject *atk_obj = ATK_OBJECT( wrapper ); + + atk_object_set_parent( child, atk_obj ); + g_signal_emit_by_name( atk_obj, "children_changed::add", index, child, nullptr ); +} + +/*****************************************************************************/ + +void atk_object_wrapper_remove_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index) +{ + /* + * the atk-bridge GTK+ module gets back to the event source to ref the child just + * vanishing, so we keep this reference because the semantic on OOo side is different. + */ + wrapper->child_about_to_be_removed = child; + wrapper->index_of_child_about_to_be_removed = index; + + g_signal_emit_by_name( ATK_OBJECT( wrapper ), "children_changed::remove", index, child, nullptr ); + + wrapper->index_of_child_about_to_be_removed = -1; + wrapper->child_about_to_be_removed = nullptr; +} + +/*****************************************************************************/ + +void atk_object_wrapper_set_role(AtkObjectWrapper* wrapper, sal_Int16 role, sal_Int64 nStates) +{ + AtkObject *atk_obj = ATK_OBJECT( wrapper ); + atk_object_set_role(atk_obj, mapToAtkRole(role, nStates)); +} + +/*****************************************************************************/ + +void atk_object_wrapper_dispose(AtkObjectWrapper* wrapper) +{ + wrapper->mpContext.clear(); + wrapper->mpAction.clear(); + wrapper->mpComponent.clear(); + wrapper->mpEditableText.clear(); + wrapper->mpHypertext.clear(); + wrapper->mpImage.clear(); + wrapper->mpSelection.clear(); + wrapper->mpMultiLineText.clear(); + wrapper->mpTable.clear(); + wrapper->mpTableSelection.clear(); + wrapper->mpText.clear(); + wrapper->mpTextMarkup.clear(); + wrapper->mpTextAttributes.clear(); + wrapper->mpValue.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk3/a11y/atkwrapper.hxx b/vcl/unx/gtk3/a11y/atkwrapper.hxx new file mode 100644 index 0000000000..8d1aeb359b --- /dev/null +++ b/vcl/unx/gtk3/a11y/atkwrapper.hxx @@ -0,0 +1,131 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <atk/atk.h> +#include <gtk/gtk.h> +#include <gtk/gtk-a11y.h> +#include <com/sun/star/accessibility/XAccessible.hpp> + +extern "C" { + +namespace com::sun::star::accessibility { + class XAccessibleAction; + class XAccessibleComponent; + class XAccessibleEditableText; + class XAccessibleHypertext; + class XAccessibleImage; + class XAccessibleMultiLineText; + class XAccessibleSelection; + class XAccessibleTable; + class XAccessibleTableSelection; + class XAccessibleText; + class XAccessibleTextMarkup; + class XAccessibleTextAttributes; + class XAccessibleValue; +} + +struct AtkObjectWrapper +{ + GtkWidgetAccessible aParent; + + AtkObject* mpOrig; //if we're a GtkDrawingArea acting as a custom LibreOffice widget, this is the toolkit default impl + AtkObject* mpSysObjChild; //if we're a container for a sysobj, then this is the sysobj native gtk AtkObject + + css::uno::Reference<css::accessibility::XAccessible> mpAccessible; + css::uno::Reference<css::accessibility::XAccessibleContext> mpContext; + css::uno::Reference<css::accessibility::XAccessibleAction> mpAction; + css::uno::Reference<css::accessibility::XAccessibleComponent> mpComponent; + css::uno::Reference<css::accessibility::XAccessibleEditableText> + mpEditableText; + css::uno::Reference<css::accessibility::XAccessibleHypertext> mpHypertext; + css::uno::Reference<css::accessibility::XAccessibleImage> mpImage; + css::uno::Reference<css::accessibility::XAccessibleMultiLineText> + mpMultiLineText; + css::uno::Reference<css::accessibility::XAccessibleSelection> mpSelection; + css::uno::Reference<css::accessibility::XAccessibleTable> mpTable; + css::uno::Reference<css::accessibility::XAccessibleTableSelection> mpTableSelection; + css::uno::Reference<css::accessibility::XAccessibleText> mpText; + css::uno::Reference<css::accessibility::XAccessibleTextMarkup> mpTextMarkup; + css::uno::Reference<css::accessibility::XAccessibleTextAttributes> + mpTextAttributes; + css::uno::Reference<css::accessibility::XAccessibleValue> mpValue; + + AtkObject *child_about_to_be_removed; + gint index_of_child_about_to_be_removed; +// OString * m_pKeyBindings +}; + +struct AtkObjectWrapperClass +{ + GtkWidgetAccessibleClass aParentClass; +}; + +GType atk_object_wrapper_get_type() G_GNUC_CONST; +AtkObject * atk_object_wrapper_ref( + const css::uno::Reference< css::accessibility::XAccessible >& rxAccessible, + bool create = true ); + +AtkObject * atk_object_wrapper_new( + const css::uno::Reference< css::accessibility::XAccessible >& rxAccessible, + AtkObject* parent = nullptr, AtkObject* orig = nullptr ); + +void atk_object_wrapper_add_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index); +void atk_object_wrapper_remove_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index); +void atk_object_wrapper_set_role(AtkObjectWrapper* wrapper, sal_Int16 role, sal_Int64 nStates); + +void atk_object_wrapper_dispose(AtkObjectWrapper* wrapper); + +AtkStateType mapAtkState( sal_Int64 nState ); + +AtkRelation* atk_object_wrapper_relation_new(const css::accessibility::AccessibleRelation& rRelation); + +void actionIfaceInit(AtkActionIface *iface); +void componentIfaceInit(AtkComponentIface *iface); +void editableTextIfaceInit(AtkEditableTextIface *iface); +void hypertextIfaceInit(AtkHypertextIface *iface); +void imageIfaceInit(AtkImageIface *iface); +void selectionIfaceInit(AtkSelectionIface *iface); +void tableIfaceInit(AtkTableIface *iface); +void tablecellIfaceInit(AtkTableCellIface *iface); +void textIfaceInit(AtkTextIface *iface); +void valueIfaceInit(AtkValueIface *iface); + +} // extern "C" + +#define ATK_TYPE_OBJECT_WRAPPER atk_object_wrapper_get_type() +#define ATK_OBJECT_WRAPPER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), ATK_TYPE_OBJECT_WRAPPER, AtkObjectWrapper)) +#define ATK_IS_OBJECT_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ATK_TYPE_OBJECT_WRAPPER)) + +static inline gchar * +OUStringToGChar(std::u16string_view rString ) +{ + OString aUtf8 = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 ); + return g_strdup( aUtf8.getStr() ); +} + +#define OUStringToConstGChar( string ) OUStringToOString( string, RTL_TEXTENCODING_UTF8 ).getStr() + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |