/* -*- 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 #include #include #include #include #include #include #include #include #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& rAccessible) { GtkAccessibleRole eRole(GTK_ACCESSIBLE_ROLE_WIDGET); if (rAccessible.is()) { css::uno::Reference 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 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& 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& xContext) { assert(pGtkAccessible); css::uno::Reference 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 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(lo_accessible_range_init), gtk_accessible_range_get_type, cppu::UnoType::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 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& 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 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 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 xContext( pAccessible->uno_accessible->getAccessibleContext()); css::uno::Reference 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 xContext( pAccessible->uno_accessible->getAccessibleContext()); if (!xContext->getAccessibleChildCount()) return nullptr; css::uno::Reference 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 xContext( pAccessible->uno_accessible->getAccessibleContext()); sal_Int64 nThisChildIndex = xContext->getAccessibleIndexInParent(); assert(nThisChildIndex != -1); sal_Int64 nNextChildIndex = nThisChildIndex + 1; css::uno::Reference xParent = xContext->getAccessibleParent(); css::uno::Reference xParentContext( xParent->getAccessibleContext()); if (nNextChildIndex >= xParentContext->getAccessibleChildCount()) return nullptr; css::uno::Reference 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 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 xContext( pAccessible->uno_accessible->getAccessibleContext()); css::uno::Reference 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(fNewValue); xValue->setCurrentValue(css::uno::Any(nValue)); return true; } else if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_HYPER) { const sal_Int64 nValue = std::round(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 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 xAccessible( get_uno_accessible(GTK_WIDGET(pFixed))); css::uno::Reference xContext( xAccessible->getAccessibleContext()); css::uno::Reference 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 xAccessible( get_uno_accessible(GTK_WIDGET(pFixed))); if (!xAccessible) return nullptr; css::uno::Reference xContext( xAccessible->getAccessibleContext()); if (!xContext->getAccessibleChildCount()) return nullptr; css::uno::Reference 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(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: */