[gtk/a11y/atspi: 3/3] atspi: Make text change notification work




commit bf0f3a82cfd497ef1185b1c62472b2155e46105e
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Oct 13 00:50:19 2020 -0400

    atspi: Make text change notification work
    
    Make text change notification work for editables, by connecting
    to the ::insert-text and ::delete-text signals on the wrapped
    GtkText widget, and for GtkTextView by connecting to the
    corresponding GtkTextBuffer signals.
    
    This code is more or less directly copied from GtkTextViewAccessible
    and GtkEntryAccessible in GTK 3.

 gtk/a11y/gtkatspicontext.c     |  71 ++++++++++
 gtk/a11y/gtkatspitext.c        | 303 +++++++++++++++++++++++++++++++++++++++--
 gtk/a11y/gtkatspitextprivate.h |  15 ++
 3 files changed, 379 insertions(+), 10 deletions(-)
---
diff --git a/gtk/a11y/gtkatspicontext.c b/gtk/a11y/gtkatspicontext.c
index 92562e4a04..9a5f58fd47 100644
--- a/gtk/a11y/gtkatspicontext.c
+++ b/gtk/a11y/gtkatspicontext.c
@@ -39,6 +39,7 @@
 
 #include "gtkdebug.h"
 #include "gtkeditable.h"
+#include "gtkentryprivate.h"
 #include "gtkroot.h"
 #include "gtktextview.h"
 #include "gtkwindow.h"
@@ -670,6 +671,38 @@ gtk_at_spi_context_unregister_object (GtkAtSpiContext *self)
     }
 }
 
+static void
+emit_text_changed (GtkAtSpiContext *self,
+                   const char      *kind,
+                   int              start,
+                   int              end,
+                   const char      *text)
+{
+  g_dbus_connection_emit_signal (self->connection,
+                                 NULL,
+                                 self->context_path,
+                                 "org.a11y.atspi.Event.Object",
+                                 "TextChanged",
+                                 g_variant_new ("(siiva{sv})",
+                                                kind, start, end, g_variant_new_string (text), NULL),
+                                 NULL);
+}
+
+static void
+emit_selection_changed (GtkAtSpiContext *self,
+                        const char      *kind,
+                        int              cursor_position)
+{
+  g_dbus_connection_emit_signal (self->connection,
+                                 NULL,
+                                 self->context_path,
+                                 "org.a11y.atspi.Event.Object",
+                                 "TextChanged",
+                                 g_variant_new ("(siiva{sv})",
+                                                kind, cursor_position, 0, g_variant_new_string (""), NULL),
+                                 NULL);
+}
+
 static void
 emit_state_changed (GtkAtSpiContext *self,
                     const char      *name,
@@ -874,12 +907,45 @@ gtk_at_spi_context_state_change (GtkATContext                *ctx,
     }
 }
 
+static void
+insert_text_cb (GtkEditable     *editable,
+                char            *new_text,
+                int              new_text_length,
+                int             *position,
+                GtkAtSpiContext *self)
+{
+  int length;
+
+  if (new_text_length == 0)
+    return;
+
+  length = g_utf8_strlen (new_text, new_text_length);
+  emit_text_changed (self, "insert", *position - length, length, new_text);
+}
+
+static void
+delete_text_cb (GtkEditable     *editable,
+                int              start,
+                int              end,
+                GtkAtSpiContext *self)
+{
+  char *text;
+
+  if (start == end)
+    return;
+
+  text = gtk_editable_get_chars (editable, start, end);
+  emit_text_changed (self, "delete", start, end - start, text);
+}
+
 static void
 gtk_at_spi_context_dispose (GObject *gobject)
 {
   GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (gobject);
+  GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
 
   gtk_at_spi_context_unregister_object (self);
+  gtk_atspi_disconnect_text_signals (GTK_WIDGET (accessible));
 
   G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->dispose (gobject);
 }
@@ -1002,6 +1068,11 @@ gtk_at_spi_context_constructed (GObject *gobject)
   g_free (base_path);
   g_free (uuid);
 
+  GtkAccessible *accessible = gtk_at_context_get_accessible (GTK_AT_CONTEXT (self));
+  gtk_atspi_connect_text_signals (GTK_WIDGET (accessible),
+                                  emit_text_changed,
+                                  emit_selection_changed,
+                                  self);
   gtk_at_spi_context_register_object (self);
 
   G_OBJECT_CLASS (gtk_at_spi_context_parent_class)->constructed (gobject);
diff --git a/gtk/a11y/gtkatspitext.c b/gtk/a11y/gtkatspitext.c
index bf8bdb79c4..9a7b37b5a3 100644
--- a/gtk/a11y/gtkatspitext.c
+++ b/gtk/a11y/gtkatspitext.c
@@ -404,6 +404,20 @@ static const GDBusInterfaceVTable label_vtable = {
   NULL,
 };
 
+static GtkText *
+gtk_editable_get_text_widget (GtkWidget *widget)
+{
+  if (GTK_IS_ENTRY (widget))
+    return gtk_entry_get_text_widget (GTK_ENTRY (widget));
+  else if (GTK_IS_SEARCH_ENTRY (widget))
+    return gtk_search_entry_get_text_widget (GTK_SEARCH_ENTRY (widget));
+  else if (GTK_IS_PASSWORD_ENTRY (widget))
+    return gtk_password_entry_get_text_widget (GTK_PASSWORD_ENTRY (widget));
+  else if (GTK_IS_SPIN_BUTTON (widget))
+    return gtk_spin_button_get_text_widget (GTK_SPIN_BUTTON (widget));
+
+  return NULL;
+}
 
 static void
 entry_handle_method (GDBusConnection       *connection,
@@ -418,16 +432,7 @@ entry_handle_method (GDBusConnection       *connection,
   GtkATContext *self = user_data;
   GtkAccessible *accessible = gtk_at_context_get_accessible (self);
   GtkWidget *widget = GTK_WIDGET (accessible);
-  GtkText *text_widget;
-
-  if (GTK_IS_ENTRY (widget))
-    text_widget = gtk_entry_get_text_widget (GTK_ENTRY (widget));
-  else if (GTK_IS_SEARCH_ENTRY (widget))
-    text_widget = gtk_search_entry_get_text_widget (GTK_SEARCH_ENTRY (widget));
-  else if (GTK_IS_PASSWORD_ENTRY (widget))
-    text_widget = gtk_password_entry_get_text_widget (GTK_PASSWORD_ENTRY (widget));
-  else if (GTK_IS_SPIN_BUTTON (widget))
-    text_widget = gtk_spin_button_get_text_widget (GTK_SPIN_BUTTON (widget));
+  GtkText *text_widget = gtk_editable_get_text_widget (widget);
 
   if (g_strcmp0 (method_name, "GetCaretOffset") == 0)
     {
@@ -1165,3 +1170,281 @@ gtk_atspi_get_text_vtable (GtkWidget *widget)
 
   return NULL;
 }
+
+typedef struct {
+  void (* text_changed)      (gpointer    data,
+                              const char *kind,
+                              int         start,
+                              int         end,
+                              const char *text);
+  void (* selection_changed) (gpointer    data,
+                              const char *kind,
+                              int         cursor_position);
+
+  gpointer data;
+  GtkTextBuffer *buffer;
+  int cursor_position;
+  int selection_bound;
+} TextChanged;
+
+static void
+insert_text_cb (GtkEditable     *editable,
+                char            *new_text,
+                int              new_text_length,
+                int             *position,
+                TextChanged     *changed)
+{
+  int length;
+
+  if (new_text_length == 0)
+    return;
+
+  length = g_utf8_strlen (new_text, new_text_length);
+  changed->text_changed (changed->data, "insert", *position - length, length, new_text);
+}
+
+static void
+delete_text_cb (GtkEditable     *editable,
+                int              start,
+                int              end,
+                TextChanged     *changed)
+{
+  char *text;
+
+  if (start == end)
+    return;
+
+  text = gtk_editable_get_chars (editable, start, end);
+  changed->text_changed (changed->data, "delete", start, end - start, text);
+  g_free (text);
+}
+
+static void
+update_selection (TextChanged *changed,
+                  int          cursor_position,
+                  int          selection_bound)
+{
+  gboolean caret_moved, bound_moved;
+
+  caret_moved = cursor_position != changed->cursor_position;
+  bound_moved = selection_bound != changed->selection_bound;
+
+  if (!caret_moved && !bound_moved)
+    return;
+
+  changed->cursor_position = cursor_position;
+  changed->selection_bound = selection_bound;
+
+  if (caret_moved)
+    changed->selection_changed (changed->data, "text-caret-moved", changed->cursor_position);
+
+  if (caret_moved || bound_moved)
+    changed->selection_changed (changed->data, "text-selection-changed", 0);
+}
+
+static void
+notify_cb (GObject     *object,
+           GParamSpec  *pspec,
+           TextChanged *changed)
+{
+  if (g_strcmp0 (pspec->name, "cursor-position") == 0 ||
+      g_strcmp0 (pspec->name, "selection-bound") == 0)
+    {
+      int cursor_position, selection_bound;
+
+      gtk_editable_get_selection_bounds (GTK_EDITABLE (object), &cursor_position, &selection_bound);
+      update_selection (changed, cursor_position, selection_bound);
+    }
+}
+
+static void
+update_cursor (GtkTextBuffer *buffer,
+               TextChanged   *changed)
+{
+  GtkTextIter iter;
+  int cursor_position, selection_bound;
+
+  gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer));
+  cursor_position = gtk_text_iter_get_offset (&iter);
+
+  gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_selection_bound (buffer));
+
+  selection_bound = gtk_text_iter_get_offset (&iter);
+
+  update_selection (changed, cursor_position, selection_bound);
+}
+
+static void
+insert_range_cb (GtkTextBuffer *buffer,
+                 GtkTextIter   *iter,
+                 char          *text,
+                 int            len,
+                 TextChanged   *changed)
+{
+  int position;
+  int length;
+
+  position = gtk_text_iter_get_offset (iter);
+  length = g_utf8_strlen (text, len);
+
+  changed->text_changed (changed->data, "insert", position - length, length, text);
+
+  update_cursor (buffer, changed);
+}
+
+static void
+delete_range_cb (GtkTextBuffer *buffer,
+                 GtkTextIter   *start,
+                 GtkTextIter   *end,
+                 TextChanged   *changed)
+{
+  int offset, length;
+  char *text;
+
+  text = gtk_text_buffer_get_slice (buffer, start, end, FALSE);
+
+  offset = gtk_text_iter_get_offset (start);
+  length = gtk_text_iter_get_offset (end) - offset;
+
+  changed->text_changed (changed->data, "delete", offset, length, text);
+
+  g_free (text);
+}
+
+static void
+delete_range_after_cb (GtkTextBuffer *buffer,
+                       GtkTextIter   *start,
+                       GtkTextIter   *end,
+                       TextChanged   *changed)
+{
+  update_cursor (buffer, changed);
+}
+
+static void
+mark_set_cb (GtkTextBuffer *buffer,
+             GtkTextIter   *location,
+             GtkTextMark   *mark,
+             TextChanged   *changed)
+{
+  if (mark == gtk_text_buffer_get_insert (buffer) ||
+      mark == gtk_text_buffer_get_selection_bound (buffer))
+    update_cursor (buffer, changed);
+}
+
+static void
+buffer_changed (GtkWidget   *widget,
+                GParamSpec  *pspec,
+                TextChanged *changed)
+{
+  GtkTextBuffer *buffer;
+  GtkTextIter start, end;
+  char *text;
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+
+  if (changed->buffer)
+    {
+      g_signal_handlers_disconnect_by_func (changed->buffer, insert_range_cb, changed);
+      g_signal_handlers_disconnect_by_func (changed->buffer, delete_range_cb, changed);
+      g_signal_handlers_disconnect_by_func (changed->buffer, delete_range_after_cb, changed);
+      g_signal_handlers_disconnect_by_func (changed->buffer, mark_set_cb, changed);
+
+      gtk_text_buffer_get_bounds (changed->buffer, &start, &end);
+      text = gtk_text_buffer_get_slice (changed->buffer, &start, &end, FALSE);
+      changed->text_changed (changed->data, "delete", 0, gtk_text_buffer_get_char_count (changed->buffer), 
text);
+      g_free (text);
+
+      update_selection (changed, 0, 0);
+
+      g_clear_object (&changed->buffer);
+    }
+
+  changed->buffer = buffer;
+
+  if (changed->buffer)
+    {
+      g_object_ref (changed->buffer);
+      g_signal_connect (changed->buffer, "insert-text", G_CALLBACK (insert_range_cb), changed);
+      g_signal_connect (changed->buffer, "delete-range", G_CALLBACK (delete_range_cb), changed);
+      g_signal_connect_after (changed->buffer, "delete-range", G_CALLBACK (delete_range_after_cb), changed);
+      g_signal_connect_after (changed->buffer, "mark-set", G_CALLBACK (mark_set_cb), changed);
+
+      gtk_text_buffer_get_bounds (changed->buffer, &start, &end);
+      text = gtk_text_buffer_get_slice (changed->buffer, &start, &end, FALSE);
+      changed->text_changed (changed->data, "insert", 0, gtk_text_buffer_get_char_count (changed->buffer), 
text);
+      g_free (text);
+
+      update_cursor (changed->buffer, changed);
+    }
+}
+
+void
+gtk_atspi_connect_text_signals (GtkWidget *widget,
+                                GtkAtspiTextChangedCallback text_changed,
+                                GtkAtspiSelectionChangedCallback selection_changed,
+                                gpointer   data)
+{
+  TextChanged *changed;
+
+  changed = g_new0 (TextChanged, 1);
+  changed->text_changed = text_changed;
+  changed->selection_changed = selection_changed;
+  changed->data = data;
+
+  g_object_set_data_full (G_OBJECT (widget), "accessible-text-data", changed, g_free);
+
+  if (GTK_IS_EDITABLE (widget))
+    {
+      GtkText *text = gtk_editable_get_text_widget (widget);
+
+      if (text)
+        {
+          g_signal_connect_after (text, "insert-text", G_CALLBACK (insert_text_cb), changed);
+          g_signal_connect (text, "delete-text", G_CALLBACK (delete_text_cb), changed);
+          g_signal_connect (text, "notify", G_CALLBACK (notify_cb), changed);
+
+          gtk_editable_get_selection_bounds (GTK_EDITABLE (text), &changed->cursor_position, 
&changed->selection_bound);
+        }
+    }
+  else if (GTK_IS_TEXT_VIEW (widget))
+    {
+      g_signal_connect (widget, "notify::buffer", G_CALLBACK (buffer_changed), changed);
+      buffer_changed (widget, NULL, changed);
+    }
+}
+
+void
+gtk_atspi_disconnect_text_signals (GtkWidget *widget)
+{
+  TextChanged *changed;
+
+  changed = g_object_get_data (G_OBJECT (widget), "accessible-text-data");
+
+  g_assert (changed != NULL);
+
+  if (GTK_IS_EDITABLE (widget))
+    {
+      GtkText *text = gtk_editable_get_text_widget (widget);
+
+      if (text)
+        {
+          g_signal_handlers_disconnect_by_func (text, insert_text_cb, changed);
+          g_signal_handlers_disconnect_by_func (text, delete_text_cb, changed);
+          g_signal_handlers_disconnect_by_func (text, notify_cb, changed);
+        }
+    }
+  else if (GTK_IS_TEXT_VIEW (widget))
+    {
+      g_signal_handlers_disconnect_by_func (widget, buffer_changed, changed);
+      if (changed->buffer)
+        {
+          g_signal_handlers_disconnect_by_func (changed->buffer, insert_range_cb, changed);
+          g_signal_handlers_disconnect_by_func (changed->buffer, delete_range_cb, changed);
+          g_signal_handlers_disconnect_by_func (changed->buffer, delete_range_after_cb, changed);
+          g_signal_handlers_disconnect_by_func (changed->buffer, mark_set_cb, changed);
+        }
+      g_clear_object (&changed->buffer);
+    }
+
+  g_object_set_data (G_OBJECT (widget), "accessible-text-data", NULL);
+}
diff --git a/gtk/a11y/gtkatspitextprivate.h b/gtk/a11y/gtkatspitextprivate.h
index 2ee9b2d85f..14ebf8c2b4 100644
--- a/gtk/a11y/gtkatspitextprivate.h
+++ b/gtk/a11y/gtkatspitextprivate.h
@@ -27,4 +27,19 @@ G_BEGIN_DECLS
 
 const GDBusInterfaceVTable *gtk_atspi_get_text_vtable (GtkWidget *widget);
 
+typedef void (GtkAtspiTextChangedCallback) (gpointer    data,
+                                            const char *kind,
+                                            int         start,
+                                            int         end,
+                                            const char *text);
+typedef void (GtkAtspiSelectionChangedCallback) (gpointer    data,
+                                                 const char *kind,
+                                                 int         position);
+
+void gtk_atspi_connect_text_signals    (GtkWidget *widget,
+                                        GtkAtspiTextChangedCallback text_changed,
+                                        GtkAtspiSelectionChangedCallback selection_changed,
+                                        gpointer   data);
+void gtk_atspi_disconnect_text_signals (GtkWidget *widget);
+
 G_END_DECLS


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]