[gtk/a11y/atspi: 3/3] atspi: Make text change notification work
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/a11y/atspi: 3/3] atspi: Make text change notification work
- Date: Tue, 13 Oct 2020 13:45:23 +0000 (UTC)
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]