diff options
Diffstat (limited to 'vcl/unx/gtk4/a11y.cxx')
-rw-r--r-- | vcl/unx/gtk4/a11y.cxx | 851 |
1 files changed, 851 insertions, 0 deletions
diff --git a/vcl/unx/gtk4/a11y.cxx b/vcl/unx/gtk4/a11y.cxx new file mode 100644 index 0000000000..c8103471b0 --- /dev/null +++ b/vcl/unx/gtk4/a11y.cxx @@ -0,0 +1,851 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp> +#include <com/sun/star/accessibility/XAccessibleText.hpp> +#include <com/sun/star/accessibility/XAccessibleValue.hpp> +#include <unx/gtk/gtkframe.hxx> +#include <gtk/gtk.h> +#include <o3tl/string_view.hxx> + +#if GTK_CHECK_VERSION(4, 9, 0) + +#include "a11y.hxx" + +#define OOO_TYPE_FIXED (ooo_fixed_get_type()) +#define OOO_FIXED(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), OOO_TYPE_FIXED, OOoFixed)) +// #define OOO_IS_FIXED(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), OOO_TYPE_FIXED)) + +struct OOoFixed +{ + GtkFixed parent_instance; + GtkATContext* at_context; +}; + +struct OOoFixedClass +{ + GtkFixedClass parent_class; +}; + +static GtkAccessibleRole +map_accessible_role(const css::uno::Reference<css::accessibility::XAccessible>& rAccessible) +{ + GtkAccessibleRole eRole(GTK_ACCESSIBLE_ROLE_WIDGET); + + if (rAccessible.is()) + { + css::uno::Reference<css::accessibility::XAccessibleContext> xContext( + rAccessible->getAccessibleContext()); + // https://w3c.github.io/core-aam/#mapping_role + // https://hg.mozilla.org/mozilla-central/file/tip/accessible/base/RoleMap.h + // https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gtk/a11y/gtkatspiutils.c + switch (xContext->getAccessibleRole()) + { + case css::accessibility::AccessibleRole::ALERT: + case css::accessibility::AccessibleRole::NOTIFICATION: + eRole = GTK_ACCESSIBLE_ROLE_ALERT; + break; + case css::accessibility::AccessibleRole::BLOCK_QUOTE: +#if GTK_CHECK_VERSION(4, 13, 4) + eRole = GTK_ACCESSIBLE_ROLE_BLOCK_QUOTE; +#else + eRole = GTK_ACCESSIBLE_ROLE_GROUP; +#endif + break; + case css::accessibility::AccessibleRole::CAPTION: + eRole = GTK_ACCESSIBLE_ROLE_CAPTION; + break; + case css::accessibility::AccessibleRole::COLUMN_HEADER: + eRole = GTK_ACCESSIBLE_ROLE_COLUMN_HEADER; + break; + case css::accessibility::AccessibleRole::COMBO_BOX: + eRole = GTK_ACCESSIBLE_ROLE_COMBO_BOX; + break; + case css::accessibility::AccessibleRole::DIALOG: + case css::accessibility::AccessibleRole::FILE_CHOOSER: + eRole = GTK_ACCESSIBLE_ROLE_DIALOG; + break; + case css::accessibility::AccessibleRole::FOOTNOTE: + case css::accessibility::AccessibleRole::NOTE: + eRole = GTK_ACCESSIBLE_ROLE_NOTE; + break; + case css::accessibility::AccessibleRole::FORM: + eRole = GTK_ACCESSIBLE_ROLE_FORM; + break; + case css::accessibility::AccessibleRole::HEADING: + eRole = GTK_ACCESSIBLE_ROLE_HEADING; + break; + case css::accessibility::AccessibleRole::HYPER_LINK: + eRole = GTK_ACCESSIBLE_ROLE_LINK; + break; + case css::accessibility::AccessibleRole::PANEL: + eRole = GTK_ACCESSIBLE_ROLE_GROUP; + break; + case css::accessibility::AccessibleRole::ROOT_PANE: + eRole = GTK_ACCESSIBLE_ROLE_GROUP; + break; + case css::accessibility::AccessibleRole::MENU_BAR: + eRole = GTK_ACCESSIBLE_ROLE_MENU_BAR; + break; + case css::accessibility::AccessibleRole::STATUS_BAR: + eRole = GTK_ACCESSIBLE_ROLE_STATUS; + break; + case css::accessibility::AccessibleRole::MENU: + case css::accessibility::AccessibleRole::POPUP_MENU: + eRole = GTK_ACCESSIBLE_ROLE_MENU; + break; + case css::accessibility::AccessibleRole::SPLIT_PANE: + eRole = GTK_ACCESSIBLE_ROLE_GROUP; + break; + case css::accessibility::AccessibleRole::TOOL_BAR: + eRole = GTK_ACCESSIBLE_ROLE_TOOLBAR; + break; + case css::accessibility::AccessibleRole::LABEL: + case css::accessibility::AccessibleRole::STATIC: + eRole = GTK_ACCESSIBLE_ROLE_LABEL; + break; + case css::accessibility::AccessibleRole::LIST: + eRole = GTK_ACCESSIBLE_ROLE_LIST; + break; + case css::accessibility::AccessibleRole::LIST_ITEM: + eRole = GTK_ACCESSIBLE_ROLE_LIST_ITEM; + break; + case css::accessibility::AccessibleRole::MENU_ITEM: + eRole = GTK_ACCESSIBLE_ROLE_MENU_ITEM; + break; + case css::accessibility::AccessibleRole::SEPARATOR: + eRole = GTK_ACCESSIBLE_ROLE_SEPARATOR; + break; + case css::accessibility::AccessibleRole::CHECK_BOX: + eRole = GTK_ACCESSIBLE_ROLE_CHECKBOX; + break; + case css::accessibility::AccessibleRole::CHECK_MENU_ITEM: + eRole = GTK_ACCESSIBLE_ROLE_MENU_ITEM_CHECKBOX; + break; + case css::accessibility::AccessibleRole::RADIO_MENU_ITEM: + eRole = GTK_ACCESSIBLE_ROLE_MENU_ITEM_RADIO; + break; + case css::accessibility::AccessibleRole::DOCUMENT: + case css::accessibility::AccessibleRole::DOCUMENT_PRESENTATION: + case css::accessibility::AccessibleRole::DOCUMENT_SPREADSHEET: + case css::accessibility::AccessibleRole::DOCUMENT_TEXT: + eRole = GTK_ACCESSIBLE_ROLE_DOCUMENT; + break; + case css::accessibility::AccessibleRole::ROW_HEADER: + eRole = GTK_ACCESSIBLE_ROLE_ROW_HEADER; + break; + case css::accessibility::AccessibleRole::RULER: + eRole = GTK_ACCESSIBLE_ROLE_WIDGET; + break; + case css::accessibility::AccessibleRole::PARAGRAPH: +#if GTK_CHECK_VERSION(4, 13, 1) + eRole = GTK_ACCESSIBLE_ROLE_PARAGRAPH; +#else + eRole = GTK_ACCESSIBLE_ROLE_GROUP; +#endif + break; + case css::accessibility::AccessibleRole::FILLER: + eRole = GTK_ACCESSIBLE_ROLE_GENERIC; + break; + case css::accessibility::AccessibleRole::PUSH_BUTTON: + case css::accessibility::AccessibleRole::BUTTON_DROPDOWN: + case css::accessibility::AccessibleRole::BUTTON_MENU: + eRole = GTK_ACCESSIBLE_ROLE_BUTTON; + break; + case css::accessibility::AccessibleRole::TOGGLE_BUTTON: + eRole = GTK_ACCESSIBLE_ROLE_TOGGLE_BUTTON; + break; + case css::accessibility::AccessibleRole::TABLE: + eRole = GTK_ACCESSIBLE_ROLE_TABLE; + break; + case css::accessibility::AccessibleRole::TABLE_CELL: + eRole = GTK_ACCESSIBLE_ROLE_CELL; + break; + case css::accessibility::AccessibleRole::PAGE_TAB: + eRole = GTK_ACCESSIBLE_ROLE_TAB; + break; + case css::accessibility::AccessibleRole::PAGE_TAB_LIST: + eRole = GTK_ACCESSIBLE_ROLE_TAB_LIST; + break; + case css::accessibility::AccessibleRole::PROGRESS_BAR: + eRole = GTK_ACCESSIBLE_ROLE_PROGRESS_BAR; + break; + case css::accessibility::AccessibleRole::RADIO_BUTTON: + eRole = GTK_ACCESSIBLE_ROLE_RADIO; + break; + case css::accessibility::AccessibleRole::SCROLL_BAR: + eRole = GTK_ACCESSIBLE_ROLE_SCROLLBAR; + break; + case css::accessibility::AccessibleRole::SLIDER: + eRole = GTK_ACCESSIBLE_ROLE_SLIDER; + break; + case css::accessibility::AccessibleRole::SPIN_BOX: + eRole = GTK_ACCESSIBLE_ROLE_SPIN_BUTTON; + break; + case css::accessibility::AccessibleRole::TEXT: + case css::accessibility::AccessibleRole::PASSWORD_TEXT: + eRole = GTK_ACCESSIBLE_ROLE_TEXT_BOX; + break; + case css::accessibility::AccessibleRole::TOOL_TIP: + eRole = GTK_ACCESSIBLE_ROLE_TOOLTIP; + break; + case css::accessibility::AccessibleRole::TREE: + eRole = GTK_ACCESSIBLE_ROLE_TREE; + break; + case css::accessibility::AccessibleRole::TREE_ITEM: + eRole = GTK_ACCESSIBLE_ROLE_TREE_ITEM; + break; + case css::accessibility::AccessibleRole::TREE_TABLE: + eRole = GTK_ACCESSIBLE_ROLE_TREE_GRID; + break; + case css::accessibility::AccessibleRole::GRAPHIC: + case css::accessibility::AccessibleRole::ICON: + case css::accessibility::AccessibleRole::SHAPE: + eRole = GTK_ACCESSIBLE_ROLE_IMG; + break; + default: + SAL_WARN("vcl.gtk", + "unmapped GtkAccessibleRole: " << xContext->getAccessibleRole()); + break; + } + } + + return eRole; +} + +static css::uno::Reference<css::accessibility::XAccessible> get_uno_accessible(GtkWidget* pWidget) +{ + GtkWidget* pTopLevel = widget_get_toplevel(pWidget); + if (!pTopLevel) + return nullptr; + + GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel); + if (!pFrame) + return nullptr; + + vcl::Window* pFrameWindow = pFrame->GetWindow(); + if (!pFrameWindow) + return nullptr; + + vcl::Window* pWindow = pFrameWindow; + + // skip accessible objects already exposed by the frame objects + if (WindowType::BORDERWINDOW == pWindow->GetType()) + pWindow = pFrameWindow->GetAccessibleChildWindow(0); + + if (!pWindow) + return nullptr; + + return pWindow->GetAccessible(); +} + +/** + * Based on the states set in xContext, set the corresponding Gtk states/properties + * in pGtkAccessible. + */ +static void applyStates(GtkAccessible* pGtkAccessible, + css::uno::Reference<css::accessibility::XAccessibleContext>& xContext) +{ + assert(pGtkAccessible); + + if (!xContext.is()) + return; + + // Gtk differentiates between GtkAccessibleState and GtkAccessibleProperty + // (both handled here) and GtkAccessiblePlatformState (handled in + // 'lo_accessible_get_platform_state') + const sal_Int64 nStates = xContext->getAccessibleStateSet(); + gtk_accessible_update_property( + pGtkAccessible, GTK_ACCESSIBLE_PROPERTY_MODAL, + bool(nStates & com::sun::star::accessibility::AccessibleStateType::MODAL), + GTK_ACCESSIBLE_PROPERTY_MULTI_LINE, + bool(nStates & com::sun::star::accessibility::AccessibleStateType::MULTI_LINE), + GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, + bool(nStates & com::sun::star::accessibility::AccessibleStateType::MULTI_SELECTABLE), + GTK_ACCESSIBLE_PROPERTY_READ_ONLY, + bool(!(nStates & com::sun::star::accessibility::AccessibleStateType::EDITABLE)), -1); + if (nStates & com::sun::star::accessibility::AccessibleStateType::HORIZONTAL) + { + gtk_accessible_update_property(pGtkAccessible, GTK_ACCESSIBLE_PROPERTY_ORIENTATION, + GTK_ORIENTATION_HORIZONTAL, -1); + } + else if (nStates & com::sun::star::accessibility::AccessibleStateType::VERTICAL) + { + gtk_accessible_update_property(pGtkAccessible, GTK_ACCESSIBLE_PROPERTY_ORIENTATION, + GTK_ORIENTATION_VERTICAL, -1); + } + + gtk_accessible_update_state( + pGtkAccessible, GTK_ACCESSIBLE_STATE_BUSY, + bool(nStates & com::sun::star::accessibility::AccessibleStateType::BUSY), + GTK_ACCESSIBLE_STATE_DISABLED, + bool(!(nStates & com::sun::star::accessibility::AccessibleStateType::ENABLED)), + GTK_ACCESSIBLE_STATE_EXPANDED, + bool(nStates & com::sun::star::accessibility::AccessibleStateType::EXPANDED), + GTK_ACCESSIBLE_STATE_SELECTED, + bool(nStates & com::sun::star::accessibility::AccessibleStateType::SELECTED), -1); + + // when explicitly setting any value for GTK_ACCESSIBLE_STATE_CHECKED, + // Gtk will also report ATSPI_STATE_CHECKABLE on the AT-SPI layer + if (nStates & com::sun::star::accessibility::AccessibleStateType::CHECKABLE) + { + GtkAccessibleTristate eState = GTK_ACCESSIBLE_TRISTATE_FALSE; + if (nStates & com::sun::star::accessibility::AccessibleStateType::INDETERMINATE) + eState = GTK_ACCESSIBLE_TRISTATE_MIXED; + else if (nStates & com::sun::star::accessibility::AccessibleStateType::CHECKED) + eState = GTK_ACCESSIBLE_TRISTATE_TRUE; + gtk_accessible_update_state(pGtkAccessible, GTK_ACCESSIBLE_STATE_CHECKED, eState, -1); + } + + const sal_Int16 nRole = xContext->getAccessibleRole(); + if (nRole == com::sun::star::accessibility::AccessibleRole::TOGGLE_BUTTON) + { + GtkAccessibleTristate eState = GTK_ACCESSIBLE_TRISTATE_FALSE; + if (nStates & com::sun::star::accessibility::AccessibleStateType::INDETERMINATE) + eState = GTK_ACCESSIBLE_TRISTATE_MIXED; + else if (nStates & com::sun::star::accessibility::AccessibleStateType::PRESSED) + eState = GTK_ACCESSIBLE_TRISTATE_TRUE; + gtk_accessible_update_state(pGtkAccessible, GTK_ACCESSIBLE_STATE_PRESSED, eState, -1); + } +} + +static void applyObjectAttribute(GtkAccessible* pGtkAccessible, std::u16string_view rName, + const OUString& rValue) +{ + assert(pGtkAccessible); + + if (rName == u"colindextext") + { + gtk_accessible_update_relation(pGtkAccessible, GTK_ACCESSIBLE_RELATION_COL_INDEX_TEXT, + rValue.toUtf8().getStr(), -1); + } + else if (rName == u"level") + { + const int nLevel = o3tl::toInt32(rValue); + gtk_accessible_update_property(pGtkAccessible, GTK_ACCESSIBLE_PROPERTY_LEVEL, nLevel, -1); + } + else if (rName == u"rowindextext") + { + gtk_accessible_update_relation(pGtkAccessible, GTK_ACCESSIBLE_RELATION_ROW_INDEX_TEXT, + rValue.toUtf8().getStr(), -1); + } +} + +/** + * Based on the object attributes set for xContext, set the corresponding Gtk equivalents + * in pGtkAccessible, where applicable. + */ +static void +applyObjectAttributes(GtkAccessible* pGtkAccessible, + css::uno::Reference<css::accessibility::XAccessibleContext>& xContext) +{ + assert(pGtkAccessible); + + css::uno::Reference<css::accessibility::XAccessibleExtendedAttributes> xAttributes( + xContext, css::uno::UNO_QUERY); + if (!xAttributes.is()) + return; + + OUString sAttrs; + xAttributes->getExtendedAttributes() >>= sAttrs; + + sal_Int32 nIndex = 0; + do + { + const OUString sAttribute = sAttrs.getToken(0, ';', nIndex); + sal_Int32 nColonPos = 0; + const OUString sName = sAttribute.getToken(0, ':', nColonPos); + const OUString sValue = sAttribute.getToken(0, ':', nColonPos); + assert(nColonPos == -1 + && "Too many colons in attribute that should have \"name:value\" syntax"); + + applyObjectAttribute(pGtkAccessible, sName, sValue); + } while (nIndex >= 0); +} + +#define LO_TYPE_ACCESSIBLE (lo_accessible_get_type()) +#define LO_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), LO_TYPE_ACCESSIBLE, LoAccessible)) +// #define LO_IS_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LO_TYPE_ACCESSIBLE)) + +struct LoAccessible +{ + GObject parent_instance; + GdkDisplay* display; + GtkAccessible* parent; + GtkATContext* at_context; + css::uno::Reference<css::accessibility::XAccessible> uno_accessible; +}; + +struct LoAccessibleClass +{ + GObjectClass parent_class; +}; + +#if GTK_CHECK_VERSION(4, 10, 0) +static void lo_accessible_range_init(GtkAccessibleRangeInterface* iface); +static gboolean lo_accessible_range_set_current_value(GtkAccessibleRange* self, double fNewValue); +#endif + +extern "C" { +typedef GType (*GetGIfaceType)(); +} +const struct +{ + const char* name; + GInterfaceInitFunc const aInit; + GetGIfaceType const aGetGIfaceType; + const css::uno::Type& (*aGetUnoType)(); +} TYPE_TABLE[] = { +#if GTK_CHECK_VERSION(4, 10, 0) + { "Value", reinterpret_cast<GInterfaceInitFunc>(lo_accessible_range_init), + gtk_accessible_range_get_type, cppu::UnoType<css::accessibility::XAccessibleValue>::get } +#endif +}; + +static bool isOfType(css::uno::XInterface* xInterface, const css::uno::Type& rType) +{ + if (!xInterface) + return false; + + try + { + css::uno::Any aRet = xInterface->queryInterface(rType); + const bool bIs = (typelib_TypeClass_INTERFACE == aRet.pType->eTypeClass) + && (aRet.pReserved != nullptr); + return bIs; + } + catch (const css::uno::Exception&) + { + return false; + } +} + +static GType ensureTypeFor(css::uno::XInterface* xAccessible) +{ + OStringBuffer aTypeNameBuf("OOoGtkAccessibleObj"); + std::vector<bool> bTypes(std::size(TYPE_TABLE), false); + for (size_t i = 0; i < std::size(TYPE_TABLE); i++) + { + if (isOfType(xAccessible, TYPE_TABLE[i].aGetUnoType())) + { + aTypeNameBuf.append(TYPE_TABLE[i].name); + bTypes[i] = true; + } + } + + const OString aTypeName = aTypeNameBuf.makeStringAndClear(); + GType nType = g_type_from_name(aTypeName.getStr()); + if (nType != G_TYPE_INVALID) + return nType; + + GTypeInfo aTypeInfo = { sizeof(LoAccessibleClass), nullptr, nullptr, nullptr, nullptr, nullptr, + sizeof(LoAccessible), 0, nullptr, nullptr }; + nType + = g_type_register_static(LO_TYPE_ACCESSIBLE, aTypeName.getStr(), &aTypeInfo, GTypeFlags(0)); + + for (size_t i = 0; i < std::size(TYPE_TABLE); i++) + { + if (bTypes[i]) + { + GInterfaceInfo aIfaceInfo = { nullptr, nullptr, nullptr }; + aIfaceInfo.interface_init = TYPE_TABLE[i].aInit; + g_type_add_interface_static(nType, TYPE_TABLE[i].aGetGIfaceType(), &aIfaceInfo); + } + } + return nType; +} + +enum +{ + CHILD_PROP_0, + LAST_CHILD_PROP, + + PROP_ACCESSIBLE_ROLE +}; + +static void lo_accessible_get_property(GObject* object, guint property_id, GValue* value, + GParamSpec* pspec) +{ + LoAccessible* accessible = LO_ACCESSIBLE(object); + + switch (property_id) + { + case PROP_ACCESSIBLE_ROLE: + { + GtkAccessibleRole eRole(map_accessible_role(accessible->uno_accessible)); + g_value_set_enum(value, eRole); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void lo_accessible_set_property(GObject* object, guint property_id, const GValue* /*value*/, + GParamSpec* pspec) +{ + // LoAccessible* accessible = LO_ACCESSIBLE(object); + + switch (property_id) + { + case PROP_ACCESSIBLE_ROLE: + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static GtkAccessible* lo_accessible_get_accessible_parent(GtkAccessible* accessible) +{ + LoAccessible* lo_accessible = LO_ACCESSIBLE(accessible); + if (!lo_accessible->parent) + return nullptr; + return GTK_ACCESSIBLE(g_object_ref(lo_accessible->parent)); +} + +static GtkATContext* lo_accessible_get_at_context(GtkAccessible* self) +{ + LoAccessible* pAccessible = LO_ACCESSIBLE(self); + + GtkAccessibleRole eRole = map_accessible_role(pAccessible->uno_accessible); + + if (!pAccessible->at_context + || gtk_at_context_get_accessible_role(pAccessible->at_context) != eRole) + { + pAccessible->at_context = gtk_at_context_create(eRole, self, pAccessible->display); + if (!pAccessible->at_context) + return nullptr; + } + + return g_object_ref(pAccessible->at_context); +} + +static GtkAccessible* lo_accessible_get_first_accessible_child(GtkAccessible* self); + +static GtkAccessible* lo_accessible_get_next_accessible_sibling(GtkAccessible* self); + +static gboolean lo_accessible_get_platform_state(GtkAccessible* self, + GtkAccessiblePlatformState state); + +static gboolean lo_accessible_get_bounds(GtkAccessible* accessible, int* x, int* y, int* width, + int* height); + +static void lo_accessible_accessible_init(GtkAccessibleInterface* iface) +{ + iface->get_accessible_parent = lo_accessible_get_accessible_parent; + iface->get_at_context = lo_accessible_get_at_context; + iface->get_bounds = lo_accessible_get_bounds; + iface->get_first_accessible_child = lo_accessible_get_first_accessible_child; + iface->get_next_accessible_sibling = lo_accessible_get_next_accessible_sibling; + iface->get_platform_state = lo_accessible_get_platform_state; +} + +#if GTK_CHECK_VERSION(4, 10, 0) +static void lo_accessible_range_init(GtkAccessibleRangeInterface* iface) +{ + iface->set_current_value = lo_accessible_range_set_current_value; +} +#endif + +G_DEFINE_TYPE_WITH_CODE(LoAccessible, lo_accessible, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(GTK_TYPE_ACCESSIBLE, lo_accessible_accessible_init)) + +static void lo_accessible_class_init(LoAccessibleClass* klass) +{ + GObjectClass* object_class = G_OBJECT_CLASS(klass); + + // object_class->finalize = lo_accessible_finalize; + // object_class->dispose = lo_accessible_dispose; + object_class->get_property = lo_accessible_get_property; + object_class->set_property = lo_accessible_set_property; + // object_class->constructed = lo_accessible_constructed; + + // g_object_class_install_properties(object_class, LAST_CHILD_PROP, lo_accessible_props); + g_object_class_override_property(object_class, PROP_ACCESSIBLE_ROLE, "accessible-role"); +} + +static LoAccessible* +lo_accessible_new(GdkDisplay* pDisplay, GtkAccessible* pParent, + const css::uno::Reference<css::accessibility::XAccessible>& rAccessible) +{ + assert(rAccessible.is()); + + GType nType = ensureTypeFor(rAccessible.get()); + LoAccessible* ret = LO_ACCESSIBLE(g_object_new(nType, nullptr)); + ret->display = pDisplay; + ret->parent = pParent; + ret->uno_accessible = rAccessible; + + css::uno::Reference<css::accessibility::XAccessibleContext> xContext( + ret->uno_accessible->getAccessibleContext()); + assert(xContext.is() && "No accessible context"); + + GtkAccessible* pGtkAccessible = GTK_ACCESSIBLE(ret); + + // set accessible name and description + gtk_accessible_update_property(pGtkAccessible, GTK_ACCESSIBLE_PROPERTY_LABEL, + xContext->getAccessibleName().toUtf8().getStr(), + GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, + xContext->getAccessibleDescription().toUtf8().getStr(), -1); + + applyStates(pGtkAccessible, xContext); + + applyObjectAttributes(GTK_ACCESSIBLE(ret), xContext); + + // set values from XAccessibleValue interface if that's implemented + css::uno::Reference<css::accessibility::XAccessibleValue> xAccessibleValue(xContext, + css::uno::UNO_QUERY); + if (xAccessibleValue.is()) + { + double fCurrentValue = 0, fMinValue = 0, fMaxValue = 0; + xAccessibleValue->getCurrentValue() >>= fCurrentValue; + xAccessibleValue->getMinimumValue() >>= fMinValue; + xAccessibleValue->getMaximumValue() >>= fMaxValue; + gtk_accessible_update_property(GTK_ACCESSIBLE(ret), GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, + fCurrentValue, GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, fMinValue, + GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, fMaxValue, -1); + } + + return ret; +} + +static gboolean lo_accessible_get_bounds(GtkAccessible* self, int* x, int* y, int* width, + int* height) +{ + LoAccessible* pAccessible = LO_ACCESSIBLE(self); + + if (!pAccessible->uno_accessible) + return false; + + css::uno::Reference<css::accessibility::XAccessibleContext> xContext( + pAccessible->uno_accessible->getAccessibleContext()); + css::uno::Reference<css::accessibility::XAccessibleComponent> xAccessibleComponent( + xContext, css::uno::UNO_QUERY); + if (!xAccessibleComponent) + return false; + + css::awt::Rectangle aBounds = xAccessibleComponent->getBounds(); + *x = aBounds.X; + *y = aBounds.Y; + *width = aBounds.Width; + *height = aBounds.Height; + return true; +} + +static GtkAccessible* lo_accessible_get_first_accessible_child(GtkAccessible* self) +{ + LoAccessible* pAccessible = LO_ACCESSIBLE(self); + + if (!pAccessible->uno_accessible) + return nullptr; + + css::uno::Reference<css::accessibility::XAccessibleContext> xContext( + pAccessible->uno_accessible->getAccessibleContext()); + if (!xContext->getAccessibleChildCount()) + return nullptr; + css::uno::Reference<css::accessibility::XAccessible> xFirstChild( + xContext->getAccessibleChild(0)); + if (!xFirstChild) + return nullptr; + + LoAccessible* child_accessible = lo_accessible_new(pAccessible->display, self, xFirstChild); + return GTK_ACCESSIBLE(g_object_ref(child_accessible)); +} + +static GtkAccessible* lo_accessible_get_next_accessible_sibling(GtkAccessible* self) +{ + LoAccessible* pAccessible = LO_ACCESSIBLE(self); + + if (!pAccessible->uno_accessible) + return nullptr; + + css::uno::Reference<css::accessibility::XAccessibleContext> xContext( + pAccessible->uno_accessible->getAccessibleContext()); + sal_Int64 nThisChildIndex = xContext->getAccessibleIndexInParent(); + assert(nThisChildIndex != -1); + sal_Int64 nNextChildIndex = nThisChildIndex + 1; + + css::uno::Reference<css::accessibility::XAccessible> xParent = xContext->getAccessibleParent(); + css::uno::Reference<css::accessibility::XAccessibleContext> xParentContext( + xParent->getAccessibleContext()); + if (nNextChildIndex >= xParentContext->getAccessibleChildCount()) + return nullptr; + css::uno::Reference<css::accessibility::XAccessible> xNextChild( + xParentContext->getAccessibleChild(nNextChildIndex)); + if (!xNextChild) + return nullptr; + + LoAccessible* child_accessible + = lo_accessible_new(pAccessible->display, pAccessible->parent, xNextChild); + return GTK_ACCESSIBLE(g_object_ref(child_accessible)); +} + +static gboolean lo_accessible_get_platform_state(GtkAccessible* self, + GtkAccessiblePlatformState state) +{ + LoAccessible* pAccessible = LO_ACCESSIBLE(self); + + if (!pAccessible->uno_accessible) + return false; + + css::uno::Reference<css::accessibility::XAccessibleContext> xContext( + pAccessible->uno_accessible->getAccessibleContext()); + sal_Int64 nStateSet = xContext->getAccessibleStateSet(); + + switch (state) + { + case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE: + return (nStateSet & css::accessibility::AccessibleStateType::FOCUSABLE) != 0; + case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED: + return (nStateSet & css::accessibility::AccessibleStateType::FOCUSED) != 0; + case GTK_ACCESSIBLE_PLATFORM_STATE_ACTIVE: + return (nStateSet & css::accessibility::AccessibleStateType::ACTIVE) != 0; + } + + return false; +} + +#if GTK_CHECK_VERSION(4, 10, 0) +static gboolean lo_accessible_range_set_current_value(GtkAccessibleRange* self, double fNewValue) +{ + // return 'true' in any case, since otherwise no proper AT-SPI DBus reply gets sent + // and the app crashes, s. + // https://gitlab.gnome.org/GNOME/gtk/-/issues/6150 + // https://gitlab.gnome.org/GNOME/gtk/-/commit/0dbd2bd09eff8c9233e45338a05daf2a835529ab + + LoAccessible* pAccessible = LO_ACCESSIBLE(self); + if (!pAccessible->uno_accessible) + return true; + + css::uno::Reference<css::accessibility::XAccessibleContext> xContext( + pAccessible->uno_accessible->getAccessibleContext()); + + css::uno::Reference<css::accessibility::XAccessibleValue> xValue(xContext, css::uno::UNO_QUERY); + if (!xValue.is()) + return true; + + // 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 = xValue->getCurrentValue(); + if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_LONG) + { + const sal_Int32 nValue = std::round<sal_Int32>(fNewValue); + xValue->setCurrentValue(css::uno::Any(nValue)); + return true; + } + else if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_HYPER) + { + const sal_Int64 nValue = std::round<sal_Int64>(fNewValue); + xValue->setCurrentValue(css::uno::Any(nValue)); + return true; + } + + css::uno::Any aValue; + aValue <<= fNewValue; + xValue->setCurrentValue(aValue); + return true; +} +#endif + +static void lo_accessible_init(LoAccessible* /*iface*/) {} + +static GtkATContext* get_at_context(GtkAccessible* self) +{ + OOoFixed* pFixed = OOO_FIXED(self); + + css::uno::Reference<css::accessibility::XAccessible> xAccessible( + get_uno_accessible(GTK_WIDGET(pFixed))); + GtkAccessibleRole eRole = map_accessible_role(xAccessible); + + if (!pFixed->at_context || gtk_at_context_get_accessible_role(pFixed->at_context) != eRole) + { + // if (pFixed->at_context) + // g_clear_object(&pFixed->at_context); + + pFixed->at_context + = gtk_at_context_create(eRole, self, gtk_widget_get_display(GTK_WIDGET(pFixed))); + if (!pFixed->at_context) + return nullptr; + } + + return g_object_ref(pFixed->at_context); +} + +#if 0 +gboolean get_platform_state(GtkAccessible* self, GtkAccessiblePlatformState state) +{ + return false; +} +#endif + +static gboolean get_bounds(GtkAccessible* accessible, int* x, int* y, int* width, int* height) +{ + OOoFixed* pFixed = OOO_FIXED(accessible); + css::uno::Reference<css::accessibility::XAccessible> xAccessible( + get_uno_accessible(GTK_WIDGET(pFixed))); + css::uno::Reference<css::accessibility::XAccessibleContext> xContext( + xAccessible->getAccessibleContext()); + css::uno::Reference<css::accessibility::XAccessibleComponent> xAccessibleComponent( + xContext, css::uno::UNO_QUERY); + + css::awt::Rectangle aBounds = xAccessibleComponent->getBounds(); + *x = aBounds.X; + *y = aBounds.Y; + *width = aBounds.Width; + *height = aBounds.Height; + return true; +} + +static GtkAccessible* get_first_accessible_child(GtkAccessible* accessible) +{ + OOoFixed* pFixed = OOO_FIXED(accessible); + css::uno::Reference<css::accessibility::XAccessible> xAccessible( + get_uno_accessible(GTK_WIDGET(pFixed))); + if (!xAccessible) + return nullptr; + css::uno::Reference<css::accessibility::XAccessibleContext> xContext( + xAccessible->getAccessibleContext()); + if (!xContext->getAccessibleChildCount()) + return nullptr; + css::uno::Reference<css::accessibility::XAccessible> xFirstChild( + xContext->getAccessibleChild(0)); + LoAccessible* child_accessible + = lo_accessible_new(gtk_widget_get_display(GTK_WIDGET(pFixed)), accessible, xFirstChild); + return GTK_ACCESSIBLE(g_object_ref(child_accessible)); +} + +static void ooo_fixed_accessible_init(GtkAccessibleInterface* iface) +{ + GtkAccessibleInterface* parent_iface + = static_cast<GtkAccessibleInterface*>(g_type_interface_peek_parent(iface)); + iface->get_at_context = get_at_context; + iface->get_bounds = get_bounds; + iface->get_first_accessible_child = get_first_accessible_child; + iface->get_platform_state = parent_iface->get_platform_state; + // iface->get_platform_state = get_platform_state; +} + +G_DEFINE_TYPE_WITH_CODE(OOoFixed, ooo_fixed, GTK_TYPE_FIXED, + G_IMPLEMENT_INTERFACE(GTK_TYPE_ACCESSIBLE, ooo_fixed_accessible_init)) + +static void ooo_fixed_class_init(OOoFixedClass* /*klass*/) {} + +static void ooo_fixed_init(OOoFixed* /*area*/) {} + +GtkWidget* ooo_fixed_new() { return GTK_WIDGET(g_object_new(OOO_TYPE_FIXED, nullptr)); } + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ |