vcl/Library_vclplug_gtk4.mk | 1 vcl/unx/gtk4/a11y.cxx | 11 +- vcl/unx/gtk4/a11y.hxx | 4 vcl/unx/gtk4/gtkaccessibleeventlistener.cxx | 40 +++++++ vcl/unx/gtk4/gtkaccessibletext.cxx | 145 ++++++++++++++++++++++++++++ vcl/unx/gtk4/gtkaccessibletext.hxx | 20 +++ 6 files changed, 216 insertions(+), 5 deletions(-)
New commits: commit 255853c7979657c38339c42f42e245f712cb9e65 Author: Michael Weghorn <[email protected]> AuthorDate: Thu Feb 22 13:55:01 2024 +0100 Commit: Michael Weghorn <[email protected]> CommitDate: Thu Feb 22 15:20:42 2024 +0100 gtk4 a11y: Forward text change events Bridge text-related a11y change events for the gtk4 VCL plugin by using the new functions to notify about text, text caret and text selection changes recently introduced in Gtk in commit [1] commit 0ca8d74842837b1ad5dc42c1fcff8b1270e5750b Author: Matthias Clasen <[email protected]> Date: Tue Feb 20 12:18:27 2024 -0500 a11y: Add GtkAccessibleText interface With this in place, having a Writer paragraph selected in Accerciser's treeview, the text in the "Text" section of the "Interface Viewer" tab in Accerciser now updates when typing in LibreOffice (i.e. text changes are propagated there right away as expected). [1] https://gitlab.gnome.org/GNOME/gtk/-/commit/0ca8d74842837b1ad5dc42c1fcff8b1270e5750b Change-Id: I33c93613ddde3650d81ce11d605dc70b0569080a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/163746 Tested-by: Jenkins Reviewed-by: Michael Weghorn <[email protected]> diff --git a/vcl/unx/gtk4/gtkaccessibleeventlistener.cxx b/vcl/unx/gtk4/gtkaccessibleeventlistener.cxx index 4211340d7ee3..6e50b854d16e 100644 --- a/vcl/unx/gtk4/gtkaccessibleeventlistener.cxx +++ b/vcl/unx/gtk4/gtkaccessibleeventlistener.cxx @@ -9,6 +9,7 @@ #include <com/sun/star/accessibility/AccessibleEventId.hpp> #include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/TextSegment.hpp> #include <sal/log.hxx> #include "gtkaccessibleeventlistener.hxx" @@ -40,6 +41,14 @@ void GtkAccessibleEventListener::notifyEvent( { switch (rEvent.EventId) { +#if GTK_CHECK_VERSION(4, 13, 8) + case css::accessibility::AccessibleEventId::CARET_CHANGED: + { + if (GTK_IS_ACCESSIBLE_TEXT(m_pLoAccessible)) + gtk_accessible_text_update_caret_position(GTK_ACCESSIBLE_TEXT(m_pLoAccessible)); + break; + } +#endif case css::accessibility::AccessibleEventId::STATE_CHANGED: { sal_Int64 nState; @@ -62,6 +71,37 @@ void GtkAccessibleEventListener::notifyEvent( } break; } +#if GTK_CHECK_VERSION(4, 13, 8) + case css::accessibility::AccessibleEventId::TEXT_CHANGED: + { + if (!GTK_IS_ACCESSIBLE_TEXT(m_pLoAccessible)) + break; + + GtkAccessibleText* pText = GTK_ACCESSIBLE_TEXT(m_pLoAccessible); + + css::accessibility::TextSegment aDeletedText; + css::accessibility::TextSegment aInsertedText; + if (rEvent.OldValue >>= aDeletedText) + { + gtk_accessible_text_update_contents( + pText, GTK_ACCESSIBLE_TEXT_CONTENT_CHANGE_REMOVE, aDeletedText.SegmentStart, + aDeletedText.SegmentEnd); + } + if (rEvent.NewValue >>= aInsertedText) + { + gtk_accessible_text_update_contents( + pText, GTK_ACCESSIBLE_TEXT_CONTENT_CHANGE_INSERT, aInsertedText.SegmentStart, + aInsertedText.SegmentEnd); + } + return; + } + case css::accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED: + { + if (GTK_IS_ACCESSIBLE_TEXT(m_pLoAccessible)) + gtk_accessible_text_update_selection_bound(GTK_ACCESSIBLE_TEXT(m_pLoAccessible)); + break; + } +#endif default: break; } commit e268efd612d12ae9a459d6b9d0cb23220f025163 Author: Michael Weghorn <[email protected]> AuthorDate: Thu Feb 22 11:58:08 2024 +0100 Commit: Michael Weghorn <[email protected]> CommitDate: Thu Feb 22 15:20:36 2024 +0100 gtk4 a11y: Implement new GtkAccessibleTextInterface Implement most of the methods of the `GtkAccessibleInterface` newly added to Gtk 4 in Gtk commit [1] commit 0ca8d74842837b1ad5dc42c1fcff8b1270e5750b Author: Matthias Clasen <[email protected]> Date: Tue Feb 20 12:18:27 2024 -0500 a11y: Add GtkAccessibleText interface The AccessibleText interface is meant to be implemented by widgets and other accessible objects that expose selectable, navigatable, or rich text to assistive technologies. This kind of text is not covered by the plain accessible name and description, as it contains things like a caret, or text attributes. This commit adds a stub GtkAccessibleText with its basic virtual functions; the interface will be implemented by widgets like GtkLabel, GtkInscription, GtkText, and GtkTextView. A further commit will ensure that the AT-SPI implementation will convert from GTK to AT-SPI through a generic (internal API); and, finally, we'll remove the widget type checks in the AT-SPI implementation of GtkATContext, and only check for GtkAccessibleText. Fixes: #5912 and follow-up commits. The `css::accessibility::XAccessibleText` interface provides the required functionality. With a Writer paragraph consisting of the text "Hello world. And another sentence." and the word "world" selected, using some of the AT-SPI Text interface methods via Accerciser's IPython console behaves as expected now when the paragraph's a11y object is selected in Accerciser's treeview: In [9]: text = acc.queryText() In [10]: text.get_caretOffset() Out[10]: 11 In [11]: text.getText(0, -1) Out[11]: 'Hello world. And another sentence.' In [12]: text.getText(2,5) Out[12]: 'llo' In [13]: text.getStringAtOffset(10, pyatspi.TEXT_GRANULARITY_CHAR) Out[13]: ('d', 10, 11) In [14]: text.getStringAtOffset(10, pyatspi.TEXT_GRANULARITY_WORD) Out[14]: ('world', 6, 11) In [15]: text.getStringAtOffset(10, pyatspi.TEXT_GRANULARITY_SENTENCE) Out[15]: ('Hello world. ', 0, 13) In [16]: text.getStringAtOffset(10, pyatspi.TEXT_GRANULARITY_PARAGRAPH) Out[16]: ('Hello world. And another sentence.', 0, 34) In [17]: text.getNSelections() Out[17]: 1 In [18]: text.getSelection(0) Out[18]: (6, 11) Actual handling of text attributes is left for later (s. TODO comment in the newly added `lo_accessible_text_get_attributes`). [1] https://gitlab.gnome.org/GNOME/gtk/-/commit/0ca8d74842837b1ad5dc42c1fcff8b1270e5750b Change-Id: Icad236cd87285d9a336883e67b191f633e9e4413 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/163733 Tested-by: Jenkins Reviewed-by: Michael Weghorn <[email protected]> diff --git a/vcl/Library_vclplug_gtk4.mk b/vcl/Library_vclplug_gtk4.mk index 72ffeb08e267..a383414d17ed 100644 --- a/vcl/Library_vclplug_gtk4.mk +++ b/vcl/Library_vclplug_gtk4.mk @@ -90,6 +90,7 @@ $(eval $(call gb_Library_add_exception_objects,vclplug_gtk4,\ vcl/unx/gtk4/customcellrenderer \ vcl/unx/gtk4/gtkaccessibleeventlistener \ vcl/unx/gtk4/gtkaccessibleregistry \ + vcl/unx/gtk4/gtkaccessibletext \ vcl/unx/gtk4/gtkdata \ vcl/unx/gtk4/gtkinst \ vcl/unx/gtk4/gtksys \ diff --git a/vcl/unx/gtk4/a11y.cxx b/vcl/unx/gtk4/a11y.cxx index 19cb941b9158..41e49bf2845b 100644 --- a/vcl/unx/gtk4/a11y.cxx +++ b/vcl/unx/gtk4/a11y.cxx @@ -23,6 +23,7 @@ #include "a11y.hxx" #include "gtkaccessibleeventlistener.hxx" #include "gtkaccessibleregistry.hxx" +#include "gtkaccessibletext.hxx" #define OOO_TYPE_FIXED (ooo_fixed_get_type()) #define OOO_FIXED(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), OOO_TYPE_FIXED, OOoFixed)) @@ -410,9 +411,13 @@ const struct GetGIfaceType const aGetGIfaceType; const css::uno::Type& (*aGetUnoType)(); } TYPE_TABLE[] = { +#if GTK_CHECK_VERSION(4, 13, 8) + { "Text", reinterpret_cast<GInterfaceInitFunc>(lo_accessible_text_init), + gtk_accessible_text_get_type, cppu::UnoType<css::accessibility::XAccessibleText>::get }, +#endif #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 } + gtk_accessible_range_get_type, cppu::UnoType<css::accessibility::XAccessibleValue>::get }, #endif }; diff --git a/vcl/unx/gtk4/gtkaccessibletext.cxx b/vcl/unx/gtk4/gtkaccessibletext.cxx new file mode 100644 index 000000000000..32e1448a5f65 --- /dev/null +++ b/vcl/unx/gtk4/gtkaccessibletext.cxx @@ -0,0 +1,145 @@ +/* -*- 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 <com/sun/star/accessibility/AccessibleTextType.hpp> +#include <com/sun/star/accessibility/TextSegment.hpp> +#include <com/sun/star/accessibility/XAccessibleText.hpp> +#include <sal/log.hxx> + +#include "a11y.hxx" +#include "gtkaccessibletext.hxx" + +#if GTK_CHECK_VERSION(4, 13, 8) + +namespace +{ +sal_Int16 lcl_GtkTextGranularityToUNOBoundaryType(GtkAccessibleTextGranularity eGranularity) +{ + switch (eGranularity) + { + case GTK_ACCESSIBLE_TEXT_GRANULARITY_CHARACTER: + return com::sun::star::accessibility::AccessibleTextType::CHARACTER; + case GTK_ACCESSIBLE_TEXT_GRANULARITY_WORD: + return com::sun::star::accessibility::AccessibleTextType::WORD; + case GTK_ACCESSIBLE_TEXT_GRANULARITY_SENTENCE: + return com::sun::star::accessibility::AccessibleTextType::SENTENCE; + case GTK_ACCESSIBLE_TEXT_GRANULARITY_LINE: + return com::sun::star::accessibility::AccessibleTextType::LINE; + case GTK_ACCESSIBLE_TEXT_GRANULARITY_PARAGRAPH: + return com::sun::star::accessibility::AccessibleTextType::PARAGRAPH; + default: + assert(false && "Unhandled GtkAccessibleTextGranularity."); + return GTK_ACCESSIBLE_TEXT_GRANULARITY_CHARACTER; + } +} + +css::uno::Reference<css::accessibility::XAccessibleText> getXText(GtkAccessibleText* pGtkText) +{ + LoAccessible* pAccessible = LO_ACCESSIBLE(pGtkText); + if (!pAccessible->uno_accessible) + return nullptr; + + css::uno::Reference<css::accessibility::XAccessibleContext> xContext( + pAccessible->uno_accessible->getAccessibleContext()); + + css::uno::Reference<css::accessibility::XAccessibleText> xText(xContext, css::uno::UNO_QUERY); + return xText; +} +} + +static GBytes* lo_accessible_text_get_contents(GtkAccessibleText* self, unsigned int start, + unsigned int end) +{ + css::uno::Reference<css::accessibility::XAccessibleText> xText = getXText(self); + if (!xText.is()) + return nullptr; + + // G_MAXUINT has special meaning: end of the text + const sal_Int32 nEndIndex = (end == G_MAXUINT) ? xText->getCharacterCount() : end; + + const OString sText + = rtl::OUStringToOString(xText->getTextRange(start, nEndIndex), RTL_TEXTENCODING_UTF8); + return g_bytes_new(sText.getStr(), sText.getLength()); +} + +static GBytes* lo_accessible_text_get_contents_at(GtkAccessibleText* self, unsigned int offset, + GtkAccessibleTextGranularity eGranularity, + unsigned int* start, unsigned int* end) +{ + css::uno::Reference<css::accessibility::XAccessibleText> xText = getXText(self); + if (!xText.is()) + return nullptr; + + if (offset < 0 || offset > o3tl::make_unsigned(xText->getCharacterCount())) + { + SAL_WARN("vcl.gtk", + "lo_accessible_text_get_contents_at called with invalid offset: " << offset); + return nullptr; + } + + const sal_Int16 nUnoBoundaryType = lcl_GtkTextGranularityToUNOBoundaryType(eGranularity); + const css::accessibility::TextSegment aSegment + = xText->getTextAtIndex(offset, nUnoBoundaryType); + *start = o3tl::make_unsigned(aSegment.SegmentStart); + *end = o3tl::make_unsigned(aSegment.SegmentEnd); + const OString sText = rtl::OUStringToOString(aSegment.SegmentText, RTL_TEXTENCODING_UTF8); + return g_bytes_new(sText.getStr(), sText.getLength()); +} + +static unsigned int lo_accessible_text_get_caret_position(GtkAccessibleText* self) +{ + css::uno::Reference<css::accessibility::XAccessibleText> xText = getXText(self); + if (!xText.is()) + return 0; + + return std::max(0, xText->getCaretPosition()); +} + +static gboolean lo_accessible_text_get_selection(GtkAccessibleText* self, gsize* n_ranges, + GtkAccessibleTextRange** ranges) +{ + css::uno::Reference<css::accessibility::XAccessibleText> xText = getXText(self); + if (!xText.is()) + return 0; + + if (xText->getSelectedText().isEmpty()) + return false; + + const sal_Int32 nSelectionStart = xText->getSelectionStart(); + const sal_Int32 nSelectionEnd = xText->getSelectionEnd(); + + *n_ranges = 1; + *ranges = g_new(GtkAccessibleTextRange, 1); + (*ranges)[0].start = std::min(nSelectionStart, nSelectionEnd); + (*ranges)[0].length = std::abs(nSelectionEnd - nSelectionStart); + return true; +} + +static gboolean lo_accessible_text_get_attributes(GtkAccessibleText* /* self */, + unsigned int /* offset */, gsize* /* n_ranges */, + GtkAccessibleTextRange** /* ranges */, + char*** /* attribute_names */, + char*** /* attribute_values */) +{ + // TODO: implement + return false; +} + +void lo_accessible_text_init(GtkAccessibleTextInterface* iface) +{ + iface->get_contents = lo_accessible_text_get_contents; + iface->get_contents_at = lo_accessible_text_get_contents_at; + iface->get_caret_position = lo_accessible_text_get_caret_position; + iface->get_selection = lo_accessible_text_get_selection; + iface->get_attributes = lo_accessible_text_get_attributes; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/gtk4/gtkaccessibletext.hxx b/vcl/unx/gtk4/gtkaccessibletext.hxx new file mode 100644 index 000000000000..3e8a08db0d73 --- /dev/null +++ b/vcl/unx/gtk4/gtkaccessibletext.hxx @@ -0,0 +1,20 @@ +/* -*- 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/. + */ + +#pragma once + +#include <gtk/gtk.h> + +#if GTK_CHECK_VERSION(4, 13, 8) + +void lo_accessible_text_init(GtkAccessibleTextInterface* iface); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ commit 56e5c2c35d8febb17decaae1d526d3c98ae08f09 Author: Michael Weghorn <[email protected]> AuthorDate: Thu Feb 22 11:23:30 2024 +0100 Commit: Michael Weghorn <[email protected]> CommitDate: Thu Feb 22 15:20:28 2024 +0100 gtk4 a11y: Move LO_ACCESSIBLE define to header ... and the LO_TYPE_ACCESSIBLE one as well, for use elsewhere in an upcoming commit. Change-Id: Ib7294ce0aa0c275a405c2ff87c8de8493c36a61b Reviewed-on: https://gerrit.libreoffice.org/c/core/+/163732 Tested-by: Jenkins Reviewed-by: Michael Weghorn <[email protected]> diff --git a/vcl/unx/gtk4/a11y.cxx b/vcl/unx/gtk4/a11y.cxx index 6bc89f15c401..19cb941b9158 100644 --- a/vcl/unx/gtk4/a11y.cxx +++ b/vcl/unx/gtk4/a11y.cxx @@ -390,10 +390,6 @@ applyObjectAttributes(GtkAccessible* pGtkAccessible, } 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 LoAccessibleClass { GObjectClass parent_class; diff --git a/vcl/unx/gtk4/a11y.hxx b/vcl/unx/gtk4/a11y.hxx index 7fc0b2acdab9..0dd9d507ba6a 100644 --- a/vcl/unx/gtk4/a11y.hxx +++ b/vcl/unx/gtk4/a11y.hxx @@ -32,4 +32,8 @@ LoAccessible* lo_accessible_new(GdkDisplay* pDisplay, GtkAccessible* pParent, const css::uno::Reference<css::accessibility::XAccessible>& rAccessible); +#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)) + /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
