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: */

Reply via email to