/* * Copyright (C) 2002,2003 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* * SECTION: vte-access * @short_description: Accessibility peer of #VteTerminal * * The accessibility peer of a #VteTerminal, implementing GNOME's accessibility * framework. */ #include "config.h" #include #include #include #include #include "debug.h" #include #include "vteaccess.h" #include "vteint.h" #include "vte-private.h" #ifdef HAVE_LOCALE_H #include #endif #include enum { ACTION_MENU, LAST_ACTION }; typedef struct _VteTerminalAccessiblePrivate { gboolean snapshot_contents_invalid; /* This data is stale. */ gboolean snapshot_caret_invalid; /* This data is stale. */ GString *snapshot_text; /* Pointer to UTF-8 text. */ GArray *snapshot_characters; /* Offsets to character begin points. */ GArray *snapshot_attributes; /* Attributes, per byte. */ GArray *snapshot_linebreaks; /* Offsets to line breaks. */ gint snapshot_caret; /* Location of the cursor (in characters). */ gboolean text_caret_moved_pending; char *action_descriptions[LAST_ACTION]; } VteTerminalAccessiblePrivate; enum direction { direction_previous = -1, direction_current = 0, direction_next = 1 }; static gunichar vte_terminal_accessible_get_character_at_offset(AtkText *text, gint offset); static const char *vte_terminal_accessible_action_names[] = { "menu", NULL }; static const char *vte_terminal_accessible_action_descriptions[] = { "Popup context menu", NULL }; static void vte_terminal_accessible_text_iface_init(AtkTextIface *iface); static void vte_terminal_accessible_component_iface_init(AtkComponentIface *component); static void vte_terminal_accessible_action_iface_init(AtkActionIface *action); G_DEFINE_TYPE_WITH_CODE (VteTerminalAccessible, _vte_terminal_accessible, GTK_TYPE_WIDGET_ACCESSIBLE, G_ADD_PRIVATE (VteTerminalAccessible) G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, vte_terminal_accessible_text_iface_init) G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, vte_terminal_accessible_component_iface_init) G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, vte_terminal_accessible_action_iface_init)) static gint offset_from_xy (VteTerminalAccessiblePrivate *priv, gint x, gint y) { gint offset; gint linebreak; gint next_linebreak; if (y >= (gint) priv->snapshot_linebreaks->len) y = priv->snapshot_linebreaks->len -1; linebreak = g_array_index (priv->snapshot_linebreaks, int, y); if (y + 1 == (gint) priv->snapshot_linebreaks->len) next_linebreak = priv->snapshot_characters->len; else next_linebreak = g_array_index (priv->snapshot_linebreaks, int, y + 1); offset = linebreak + x; if (offset >= next_linebreak) offset = next_linebreak -1; return offset; } static void xy_from_offset (VteTerminalAccessiblePrivate *priv, guint offset, gint *x, gint *y) { guint i, linebreak; gint cur_x, cur_y; gint cur_offset = 0; cur_x = -1; cur_y = -1; for (i = 0; i < priv->snapshot_linebreaks->len; i++) { linebreak = g_array_index (priv->snapshot_linebreaks, int, i); if (offset < linebreak) { cur_x = offset - cur_offset; cur_y = i - 1; break; } else { cur_offset = linebreak; } } if (i == priv->snapshot_linebreaks->len) { if (offset <= priv->snapshot_characters->len) { cur_x = offset - cur_offset; cur_y = i - 1; } } *x = cur_x; *y = cur_y; } /* "Oh yeah, that's selected. Sure." callback. */ static gboolean all_selected(VteTerminal *terminal, glong column, glong row, gpointer data) { return TRUE; } static void emit_text_caret_moved(GObject *object, glong caret) { _vte_debug_print(VTE_DEBUG_SIGNALS|VTE_DEBUG_ALLY, "Accessibility peer emitting " "`text-caret-moved'.\n"); g_signal_emit_by_name(object, "text-caret-moved", caret); } static void emit_text_changed_insert(GObject *object, const char *text, glong offset, glong len) { glong start, count; if (len == 0) { return; } /* Convert the byte offsets to character offsets. */ start = g_utf8_pointer_to_offset (text, text + offset); count = g_utf8_pointer_to_offset (text + offset, text + offset + len); _vte_debug_print(VTE_DEBUG_SIGNALS|VTE_DEBUG_ALLY, "Accessibility peer emitting " "`text-changed::insert' (%ld, %ld) (%ld, %ld).\n" "Inserted text was `%.*s'.\n", offset, len, start, count, (int) len, text + offset); g_signal_emit_by_name(object, "text-changed::insert", start, count); } static void emit_text_changed_delete(GObject *object, const char *text, glong offset, glong len) { glong start, count; if (len == 0) { return; } /* Convert the byte offsets to characters. */ start = g_utf8_pointer_to_offset (text, text + offset); count = g_utf8_pointer_to_offset (text + offset, text + offset + len); _vte_debug_print(VTE_DEBUG_SIGNALS|VTE_DEBUG_ALLY, "Accessibility peer emitting " "`text-changed::delete' (%ld, %ld) (%ld, %ld).\n" "Deleted text was `%.*s'.\n", offset, len, start, count, (int) len, text + offset); g_signal_emit_by_name(object, "text-changed::delete", start, count); } static void vte_terminal_accessible_update_private_data_if_needed(VteTerminalAccessible *accessible, GString **old_text, GArray **old_characters) { VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); VteTerminal *terminal; struct _VteCharAttributes attrs; char *next, *tmp; long row, offset, caret; long ccol, crow; guint i; /* If nothing's changed, just return immediately. */ if ((priv->snapshot_contents_invalid == FALSE) && (priv->snapshot_caret_invalid == FALSE)) { if (old_text) { if (priv->snapshot_text) { *old_text = g_string_new_len(priv->snapshot_text->str, priv->snapshot_text->len); } else { *old_text = g_string_new(""); } } if (old_characters) { if (priv->snapshot_characters) { *old_characters = g_array_sized_new(FALSE, FALSE, sizeof(int), priv->snapshot_characters->len); g_array_append_vals(*old_characters, priv->snapshot_characters->data, priv->snapshot_characters->len); } else { *old_characters = g_array_new(FALSE, FALSE, sizeof(int)); } } return; } /* Re-read the contents of the widget if the contents have changed. */ terminal = VTE_TERMINAL(gtk_accessible_get_widget(GTK_ACCESSIBLE(accessible))); if (priv->snapshot_contents_invalid) { /* Free the outdated snapshot data, unless the caller * wants it. */ if (old_text) { if (priv->snapshot_text != NULL) { *old_text = priv->snapshot_text; } else { *old_text = g_string_new(""); } } else { if (priv->snapshot_text != NULL) { g_string_free(priv->snapshot_text, TRUE); } } priv->snapshot_text = NULL; /* Free the character offsets unless the caller wants it, * and allocate a new array to hold them. */ if (old_characters) { if (priv->snapshot_characters != NULL) { *old_characters = priv->snapshot_characters; } else { *old_characters = g_array_new(FALSE, FALSE, sizeof(int)); } } else { if (priv->snapshot_characters != NULL) { g_array_free(priv->snapshot_characters, TRUE); } } priv->snapshot_characters = g_array_new(FALSE, FALSE, sizeof(int)); /* Free the attribute lists and allocate a new array to hold * them. */ if (priv->snapshot_attributes != NULL) { g_array_free(priv->snapshot_attributes, TRUE); } priv->snapshot_attributes = g_array_new(FALSE, FALSE, sizeof(struct _VteCharAttributes)); /* Free the linebreak offsets and allocate a new array to hold * them. */ if (priv->snapshot_linebreaks != NULL) { g_array_free(priv->snapshot_linebreaks, TRUE); } priv->snapshot_linebreaks = g_array_new(FALSE, FALSE, sizeof(int)); /* Get a new view of the uber-label. */ tmp = vte_terminal_get_text_include_trailing_spaces(terminal, all_selected, NULL, priv->snapshot_attributes); if (tmp == NULL) { /* Aaargh! We're screwed. */ return; } priv->snapshot_text = g_string_new_len(tmp, priv->snapshot_attributes->len); g_free(tmp); /* Get the offsets to the beginnings of each character. */ i = 0; next = priv->snapshot_text->str; while (i < priv->snapshot_attributes->len) { g_array_append_val(priv->snapshot_characters, i); next = g_utf8_next_char(next); if (next == NULL) { break; } else { i = next - priv->snapshot_text->str; } } /* Find offsets for the beginning of lines. */ for (i = 0, row = 0; i < priv->snapshot_characters->len; i++) { /* Get the attributes for the current cell. */ offset = g_array_index(priv->snapshot_characters, int, i); attrs = g_array_index(priv->snapshot_attributes, struct _VteCharAttributes, offset); /* If this character is on a row different from the row * the character we looked at previously was on, then * it's a new line and we need to keep track of where * it is. */ if ((i == 0) || (attrs.row != row)) { _vte_debug_print(VTE_DEBUG_ALLY, "Row %d/%ld begins at %u.\n", priv->snapshot_linebreaks->len, attrs.row, i); g_array_append_val(priv->snapshot_linebreaks, i); } row = attrs.row; } /* Add the final line break. */ g_array_append_val(priv->snapshot_linebreaks, i); /* We're finished updating this. */ priv->snapshot_contents_invalid = FALSE; } /* Update the caret position. */ vte_terminal_get_cursor_position(terminal, &ccol, &crow); _vte_debug_print(VTE_DEBUG_ALLY, "Cursor at (%ld, " "%ld).\n", ccol, crow); /* Get the offsets to the beginnings of each line. */ caret = 0; for (i = 0; i < priv->snapshot_characters->len; i++) { /* Get the attributes for the current cell. */ offset = g_array_index(priv->snapshot_characters, int, i); attrs = g_array_index(priv->snapshot_attributes, struct _VteCharAttributes, offset); /* If this cell is "before" the cursor, move the * caret to be "here". */ if ((attrs.row < crow) || ((attrs.row == crow) && (attrs.column < ccol))) { caret = i + 1; } } /* Make a note that we'll need to notify observers if the caret moved. * But only notify them after sending text-changed. */ if (caret != priv->snapshot_caret) { priv->snapshot_caret = caret; priv->text_caret_moved_pending = TRUE; } /* Done updating the caret position, whether we needed to or not. */ priv->snapshot_caret_invalid = FALSE; _vte_debug_print(VTE_DEBUG_ALLY, "Refreshed accessibility snapshot, " "%ld cells, %ld characters.\n", (long)priv->snapshot_attributes->len, (long)priv->snapshot_characters->len); } static void vte_terminal_accessible_maybe_emit_text_caret_moved(VteTerminalAccessible *accessible) { VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); if (priv->text_caret_moved_pending) { emit_text_caret_moved(G_OBJECT(accessible), priv->snapshot_caret); priv->text_caret_moved_pending = FALSE; } } /* A signal handler to catch "text-inserted/deleted/modified" signals. */ static void vte_terminal_accessible_text_modified(VteTerminal *terminal, gpointer data) { VteTerminalAccessible *accessible = (VteTerminalAccessible *)data; VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); GString *old_text; GArray *old_characters; char *old, *current; glong offset, caret_offset, olen, clen; gint old_snapshot_caret; old_snapshot_caret = priv->snapshot_caret; priv->snapshot_contents_invalid = TRUE; vte_terminal_accessible_update_private_data_if_needed(accessible, &old_text, &old_characters); g_assert(old_text != NULL); g_assert(old_characters != NULL); current = priv->snapshot_text->str; clen = priv->snapshot_text->len; old = old_text->str; olen = old_text->len; if ((guint) priv->snapshot_caret < priv->snapshot_characters->len) { caret_offset = g_array_index(priv->snapshot_characters, int, priv->snapshot_caret); } else { /* caret was not in the line */ caret_offset = clen; } /* Find the offset where they don't match. */ offset = 0; while ((offset < olen) && (offset < clen)) { if (old[offset] != current[offset]) { break; } offset++; } /* Check if we just backspaced over a space. */ if ((olen == offset) && (caret_offset < olen && old[caret_offset] == ' ') && (old_snapshot_caret == priv->snapshot_caret + 1)) { GString *saved_text = priv->snapshot_text; GArray *saved_characters = priv->snapshot_characters; priv->snapshot_text = old_text; priv->snapshot_characters = old_characters; emit_text_changed_delete(G_OBJECT(accessible), old, caret_offset, 1); priv->snapshot_text = saved_text; priv->snapshot_characters = saved_characters; } /* At least one of them had better have more data, right? */ if ((offset < olen) || (offset < clen)) { /* Back up from both end points until we find the *last* point * where they differed. */ gchar *op = old + olen; gchar *cp = current + clen; while (op > old + offset && cp > current + offset) { gchar *opp = g_utf8_prev_char (op); gchar *cpp = g_utf8_prev_char (cp); if (g_utf8_get_char (opp) != g_utf8_get_char (cpp)) { break; } op = opp; cp = cpp; } /* recompute the respective lengths */ olen = op - old; clen = cp - current; /* At least one of them has to have text the other * doesn't. */ g_assert((clen > offset) || (olen > offset)); g_assert((clen >= 0) && (olen >= 0)); /* Now emit a deleted signal for text that was in the old * string but isn't in the new one... */ if (olen > offset) { GString *saved_text = priv->snapshot_text; GArray *saved_characters = priv->snapshot_characters; priv->snapshot_text = old_text; priv->snapshot_characters = old_characters; emit_text_changed_delete(G_OBJECT(accessible), old, offset, olen - offset); priv->snapshot_text = saved_text; priv->snapshot_characters = saved_characters; } /* .. and an inserted signal for text that wasn't in the old * string but is in the new one. */ if (clen > offset) { emit_text_changed_insert(G_OBJECT(accessible), current, offset, clen - offset); } } vte_terminal_accessible_maybe_emit_text_caret_moved(accessible); g_string_free(old_text, TRUE); g_array_free(old_characters, TRUE); } /* A signal handler to catch "text-scrolled" signals. */ static void vte_terminal_accessible_text_scrolled(VteTerminal *terminal, gint howmuch, gpointer data) { VteTerminalAccessible *accessible = (VteTerminalAccessible *)data; VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); struct _VteCharAttributes attr; long delta, row_count; guint i, len; g_assert(howmuch != 0); row_count = vte_terminal_get_row_count(terminal); if (((howmuch < 0) && (howmuch <= -row_count)) || ((howmuch > 0) && (howmuch >= row_count))) { /* All of the text was removed. */ if (priv->snapshot_text != NULL) { if (priv->snapshot_text->str != NULL) { emit_text_changed_delete(G_OBJECT(accessible), priv->snapshot_text->str, 0, priv->snapshot_text->len); } } priv->snapshot_contents_invalid = TRUE; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); /* All of the present text was added. */ if (priv->snapshot_text != NULL) { if (priv->snapshot_text->str != NULL) { emit_text_changed_insert(G_OBJECT(accessible), priv->snapshot_text->str, 0, priv->snapshot_text->len); } } vte_terminal_accessible_maybe_emit_text_caret_moved(accessible); return; } /* Find the start point. */ delta = 0; if (priv->snapshot_attributes != NULL) { if (priv->snapshot_attributes->len > 0) { attr = g_array_index(priv->snapshot_attributes, struct _VteCharAttributes, 0); delta = attr.row; } } /* We scrolled up, so text was added at the top and removed * from the bottom. */ if ((howmuch < 0) && (howmuch > -row_count)) { gboolean inserted = FALSE; howmuch = -howmuch; if (priv->snapshot_attributes != NULL && priv->snapshot_text != NULL) { /* Find the first byte that scrolled off. */ for (i = 0; i < priv->snapshot_attributes->len; i++) { attr = g_array_index(priv->snapshot_attributes, struct _VteCharAttributes, i); if (attr.row >= delta + row_count - howmuch) { break; } } if (i < priv->snapshot_attributes->len) { /* The rest of the string was deleted -- make a note. */ emit_text_changed_delete(G_OBJECT(accessible), priv->snapshot_text->str, i, priv->snapshot_attributes->len - i); } inserted = TRUE; } /* Refresh. Note that i is now the length of the data which * we expect to have left over. */ priv->snapshot_contents_invalid = TRUE; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); /* If we now have more text than before, the initial portion * was added. */ if (inserted) { len = priv->snapshot_text->len; if (len > i) { emit_text_changed_insert(G_OBJECT(accessible), priv->snapshot_text->str, 0, len - i); } } vte_terminal_accessible_maybe_emit_text_caret_moved(accessible); return; } /* We scrolled down, so text was added at the bottom and removed * from the top. */ if ((howmuch > 0) && (howmuch < row_count)) { gboolean inserted = FALSE; if (priv->snapshot_attributes != NULL && priv->snapshot_text != NULL) { /* Find the first byte that wasn't scrolled off the top. */ for (i = 0; i < priv->snapshot_attributes->len; i++) { attr = g_array_index(priv->snapshot_attributes, struct _VteCharAttributes, i); if (attr.row >= delta + howmuch) { break; } } /* That many bytes disappeared -- make a note. */ emit_text_changed_delete(G_OBJECT(accessible), priv->snapshot_text->str, 0, i); /* Figure out how much text was left, and refresh. */ i = strlen(priv->snapshot_text->str + i); inserted = TRUE; } priv->snapshot_contents_invalid = TRUE; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); /* Any newly-added string data is new, so note that it was * inserted. */ if (inserted) { len = priv->snapshot_text->len; if (len > i) { /* snapshot_text always contains a trailing '\n', * insertion happens in front of it: bug 657960 */ g_assert(i >= 1); emit_text_changed_insert(G_OBJECT(accessible), priv->snapshot_text->str, i - 1, len - i); } } vte_terminal_accessible_maybe_emit_text_caret_moved(accessible); return; } g_assert_not_reached(); } /* A signal handler to catch "cursor-moved" signals. */ static void vte_terminal_accessible_invalidate_cursor(VteTerminal *terminal, gpointer data) { VteTerminalAccessible *accessible = (VteTerminalAccessible *)data; VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); _vte_debug_print(VTE_DEBUG_ALLY, "Invalidating accessibility cursor.\n"); priv->snapshot_caret_invalid = TRUE; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); vte_terminal_accessible_maybe_emit_text_caret_moved(accessible); } /* Handle title changes by resetting the description. */ static void vte_terminal_accessible_title_changed(VteTerminal *terminal, gpointer data) { VteTerminalAccessible *accessible = (VteTerminalAccessible *)data; atk_object_set_description(ATK_OBJECT(accessible), vte_terminal_get_window_title(terminal)); } /* Reflect visibility-notify events. */ static gboolean vte_terminal_accessible_visibility_notify(VteTerminal *terminal, GdkEventVisibility *event, gpointer data) { VteTerminalAccessible *accessible = (VteTerminalAccessible *)data; GtkWidget *widget; gboolean visible; visible = event->state != GDK_VISIBILITY_FULLY_OBSCURED; /* The VISIBLE state indicates that this widget is "visible". */ atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_VISIBLE, visible); widget = &terminal->widget; while (visible) { if (gtk_widget_get_toplevel(widget) == widget) { break; } if (widget == NULL) { break; } visible = visible && (gtk_widget_get_visible(widget)); widget = gtk_widget_get_parent(widget); } /* The SHOWING state indicates that this widget, and all of its * parents up to the toplevel, are "visible". */ atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_SHOWING, visible); return FALSE; } static void vte_terminal_accessible_selection_changed (VteTerminal *terminal, gpointer data) { VteTerminalAccessible *accessible = (VteTerminalAccessible *)data; g_signal_emit_by_name (accessible, "text_selection_changed"); } static void vte_terminal_accessible_initialize (AtkObject *obj, gpointer data) { VteTerminal *terminal = VTE_TERMINAL (data); const char *window_title; ATK_OBJECT_CLASS (_vte_terminal_accessible_parent_class)->initialize (obj, data); _vte_terminal_accessible_ref(terminal); g_signal_connect(terminal, "text-inserted", G_CALLBACK(vte_terminal_accessible_text_modified), obj); g_signal_connect(terminal, "text-deleted", G_CALLBACK(vte_terminal_accessible_text_modified), obj); g_signal_connect(terminal, "text-modified", G_CALLBACK(vte_terminal_accessible_text_modified), obj); g_signal_connect(terminal, "text-scrolled", G_CALLBACK(vte_terminal_accessible_text_scrolled), obj); g_signal_connect(terminal, "cursor-moved", G_CALLBACK(vte_terminal_accessible_invalidate_cursor), obj); g_signal_connect(terminal, "window-title-changed", G_CALLBACK(vte_terminal_accessible_title_changed), obj); g_signal_connect(terminal, "visibility-notify-event", G_CALLBACK(vte_terminal_accessible_visibility_notify), obj); g_signal_connect(terminal, "selection-changed", G_CALLBACK(vte_terminal_accessible_selection_changed), obj); atk_object_set_name(obj, "Terminal"); window_title = vte_terminal_get_window_title(terminal); atk_object_set_description(obj, window_title ? window_title : ""); atk_object_notify_state_change(obj, ATK_STATE_FOCUSABLE, TRUE); atk_object_notify_state_change(obj, ATK_STATE_EXPANDABLE, FALSE); atk_object_notify_state_change(obj, ATK_STATE_RESIZABLE, TRUE); atk_object_set_role(obj, ATK_ROLE_TERMINAL); } static void _vte_terminal_accessible_init (VteTerminalAccessible *accessible) { VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private (accessible); _vte_debug_print(VTE_DEBUG_ALLY, "Initialising accessible peer.\n"); priv->snapshot_text = NULL; priv->snapshot_characters = NULL; priv->snapshot_attributes = NULL; priv->snapshot_linebreaks = NULL; priv->snapshot_caret = -1; priv->snapshot_contents_invalid = TRUE; priv->snapshot_caret_invalid = TRUE; priv->text_caret_moved_pending = FALSE; } static void vte_terminal_accessible_finalize(GObject *object) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(object); VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); GtkWidget *widget; gint i; _vte_debug_print(VTE_DEBUG_ALLY, "Finalizing accessible peer.\n"); widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(accessible)); if (widget != NULL) { g_signal_handlers_disconnect_matched(widget, (GSignalMatchType)(G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, NULL, (void *)vte_terminal_accessible_text_modified, object); g_signal_handlers_disconnect_matched(widget, (GSignalMatchType)(G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, NULL, (void *)vte_terminal_accessible_text_scrolled, object); g_signal_handlers_disconnect_matched(widget, (GSignalMatchType)(G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, NULL, (void *)vte_terminal_accessible_invalidate_cursor, object); g_signal_handlers_disconnect_matched(widget, (GSignalMatchType)(G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, NULL, (void *)vte_terminal_accessible_title_changed, object); g_signal_handlers_disconnect_matched(widget, (GSignalMatchType)(G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, NULL, (void *)vte_terminal_accessible_visibility_notify, object); } if (priv->snapshot_text != NULL) { g_string_free(priv->snapshot_text, TRUE); } if (priv->snapshot_characters != NULL) { g_array_free(priv->snapshot_characters, TRUE); } if (priv->snapshot_attributes != NULL) { g_array_free(priv->snapshot_attributes, TRUE); } if (priv->snapshot_linebreaks != NULL) { g_array_free(priv->snapshot_linebreaks, TRUE); } for (i = 0; i < LAST_ACTION; i++) { g_free (priv->action_descriptions[i]); } G_OBJECT_CLASS(_vte_terminal_accessible_parent_class)->finalize(object); } static gchar * vte_terminal_accessible_get_text(AtkText *text, gint start_offset, gint end_offset) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); int start, end; gchar *ret; g_assert(VTE_IS_TERMINAL_ACCESSIBLE(accessible)); /* Swap around if start is greater than end */ if (start_offset > end_offset && end_offset != -1) { gint tmp; tmp = start_offset; start_offset = end_offset; end_offset = tmp; } g_assert((start_offset >= 0) && (end_offset >= -1)); vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); _vte_debug_print(VTE_DEBUG_ALLY, "Getting text from %d to %d of %d.\n", start_offset, end_offset, priv->snapshot_characters->len); /* If the requested area is after all of the text, just return an * empty string. */ if (start_offset >= (int) priv->snapshot_characters->len) { return g_strdup(""); } /* Map the offsets to, er, offsets. */ start = g_array_index(priv->snapshot_characters, int, start_offset); if ((end_offset == -1) || (end_offset >= (int) priv->snapshot_characters->len) ) { /* Get everything up to the end of the buffer. */ end = priv->snapshot_text->len; } else { /* Map the stopping point. */ end = g_array_index(priv->snapshot_characters, int, end_offset); } if (end <= start) { ret = g_strdup (""); } else { ret = (char *)g_malloc(end - start + 1); memcpy(ret, priv->snapshot_text->str + start, end - start); ret[end - start] = '\0'; } return ret; } /* Map a subsection of the text with before/at/after char/word/line specs * into a run of Unicode characters. (The interface is specifying characters, * not bytes, plus that saves us from having to deal with parts of multibyte * characters, which are icky.) */ static gchar * vte_terminal_accessible_get_text_somewhere(AtkText *text, gint offset, AtkTextBoundary boundary_type, enum direction direction, gint *start_offset, gint *end_offset) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); VteTerminal *terminal; gunichar current, prev, next; guint start, end, line; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); terminal = VTE_TERMINAL(gtk_accessible_get_widget (GTK_ACCESSIBLE(text))); _vte_debug_print(VTE_DEBUG_ALLY, "Getting %s %s at %d of %d.\n", (direction == direction_current) ? "this" : ((direction == direction_next) ? "next" : "previous"), (boundary_type == ATK_TEXT_BOUNDARY_CHAR) ? "char" : ((boundary_type == ATK_TEXT_BOUNDARY_LINE_START) ? "line (start)" : ((boundary_type == ATK_TEXT_BOUNDARY_LINE_END) ? "line (end)" : ((boundary_type == ATK_TEXT_BOUNDARY_WORD_START) ? "word (start)" : ((boundary_type == ATK_TEXT_BOUNDARY_WORD_END) ? "word (end)" : ((boundary_type == ATK_TEXT_BOUNDARY_SENTENCE_START) ? "sentence (start)" : ((boundary_type == ATK_TEXT_BOUNDARY_SENTENCE_END) ? "sentence (end)" : "unknown")))))), offset, priv->snapshot_attributes->len); g_assert(priv->snapshot_text != NULL); g_assert(priv->snapshot_characters != NULL); if (offset >= (int) priv->snapshot_characters->len) { return g_strdup(""); } g_assert(offset < (int) priv->snapshot_characters->len); g_assert(offset >= 0); switch (boundary_type) { case ATK_TEXT_BOUNDARY_CHAR: /* We're either looking at the character at this * position, the one before it, or the one after it. */ offset += direction; start = MAX(offset, 0); end = MIN(offset + 1, (int) priv->snapshot_attributes->len); break; case ATK_TEXT_BOUNDARY_WORD_START: /* Back up to the previous non-word-word transition. */ while (offset > 0) { prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1); if (_vte_terminal_is_word_char(terminal, prev)) { offset--; } else { break; } } start = offset; /* If we started in a word and we're looking for the * word before this one, keep searching by backing up * to the previous non-word character and then searching * for the word-start before that. */ if (direction == direction_previous) { while (offset > 0) { prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1); if (!_vte_terminal_is_word_char(terminal, prev)) { offset--; } else { break; } } while (offset > 0) { prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1); if (_vte_terminal_is_word_char(terminal, prev)) { offset--; } else { break; } } start = offset; } /* If we're looking for the word after this one, * search forward by scanning forward for the next * non-word character, then the next word character * after that. */ if (direction == direction_next) { while (offset < (int) priv->snapshot_characters->len) { next = vte_terminal_accessible_get_character_at_offset(text, offset); if (_vte_terminal_is_word_char(terminal, next)) { offset++; } else { break; } } while (offset < (int) priv->snapshot_characters->len) { next = vte_terminal_accessible_get_character_at_offset(text, offset); if (!_vte_terminal_is_word_char(terminal, next)) { offset++; } else { break; } } start = offset; } /* Now find the end of this word. */ while (offset < (int) priv->snapshot_characters->len) { current = vte_terminal_accessible_get_character_at_offset(text, offset); if (_vte_terminal_is_word_char(terminal, current)) { offset++; } else { break; } } /* Now find the next non-word-word transition */ while (offset < (int) priv->snapshot_characters->len) { next = vte_terminal_accessible_get_character_at_offset(text, offset); if (!_vte_terminal_is_word_char(terminal, next)) { offset++; } else { break; } } end = offset; break; case ATK_TEXT_BOUNDARY_WORD_END: /* Back up to the previous word-non-word transition. */ current = vte_terminal_accessible_get_character_at_offset(text, offset); while (offset > 0) { prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1); if (_vte_terminal_is_word_char(terminal, prev) && !_vte_terminal_is_word_char(terminal, current)) { break; } else { offset--; current = prev; } } start = offset; /* If we're looking for the word end before this one, * keep searching by backing up to the previous word * character and then searching for the word-end * before that. */ if (direction == direction_previous) { while (offset > 0) { prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1); if (_vte_terminal_is_word_char(terminal, prev)) { offset--; } else { break; } } current = vte_terminal_accessible_get_character_at_offset(text, offset); while (offset > 0) { prev = vte_terminal_accessible_get_character_at_offset(text, offset - 1); if (_vte_terminal_is_word_char(terminal, prev) && !_vte_terminal_is_word_char(terminal, current)) { break; } else { offset--; current = prev; } } start = offset; } /* If we're looking for the word end after this one, * search forward by scanning forward for the next * word character, then the next non-word character * after that. */ if (direction == direction_next) { while (offset < (int) priv->snapshot_characters->len) { current = vte_terminal_accessible_get_character_at_offset(text, offset); if (!_vte_terminal_is_word_char(terminal, current)) { offset++; } else { break; } } while (offset < (int) priv->snapshot_characters->len) { current = vte_terminal_accessible_get_character_at_offset(text, offset); if (_vte_terminal_is_word_char(terminal, current)) { offset++; } else { break; } } start = offset; } /* Now find the next word end. */ while (offset < (int) priv->snapshot_characters->len) { current = vte_terminal_accessible_get_character_at_offset(text, offset); if (!_vte_terminal_is_word_char(terminal, current)) { offset++; } else { break; } } while (offset < (int) priv->snapshot_characters->len) { current = vte_terminal_accessible_get_character_at_offset(text, offset); if (_vte_terminal_is_word_char(terminal, current)) { offset++; } else { break; } } end = offset; break; case ATK_TEXT_BOUNDARY_LINE_START: case ATK_TEXT_BOUNDARY_LINE_END: /* Figure out which line we're on. If the start of the * i'th line is before the offset, then i could be the * line we're looking for. */ line = 0; for (line = 0; line < priv->snapshot_linebreaks->len; line++) { if (g_array_index(priv->snapshot_linebreaks, int, line) > offset) { line--; break; } } _vte_debug_print(VTE_DEBUG_ALLY, "Character %d is on line %d.\n", offset, line); /* Perturb the line number to handle before/at/after. */ line += direction; line = MIN(line, priv->snapshot_linebreaks->len - 1); /* Read the offsets for this line. */ start = g_array_index(priv->snapshot_linebreaks, int, line); line++; line = MIN(line, priv->snapshot_linebreaks->len - 1); end = g_array_index(priv->snapshot_linebreaks, int, line); _vte_debug_print(VTE_DEBUG_ALLY, "Line runs from %d to %d.\n", start, end); break; case ATK_TEXT_BOUNDARY_SENTENCE_START: case ATK_TEXT_BOUNDARY_SENTENCE_END: /* This doesn't make sense. Fall through. */ default: start = end = 0; break; } *start_offset = start = MIN(start, priv->snapshot_characters->len - 1); *end_offset = end = CLAMP(end, start, priv->snapshot_characters->len); return vte_terminal_accessible_get_text(text, start, end); } static gchar * vte_terminal_accessible_get_text_before_offset(AtkText *text, gint offset, AtkTextBoundary boundary_type, gint *start_offset, gint *end_offset) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); return vte_terminal_accessible_get_text_somewhere(text, offset, boundary_type, direction_previous, start_offset, end_offset); } static gchar * vte_terminal_accessible_get_text_after_offset(AtkText *text, gint offset, AtkTextBoundary boundary_type, gint *start_offset, gint *end_offset) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); return vte_terminal_accessible_get_text_somewhere(text, offset, boundary_type, direction_next, start_offset, end_offset); } static gchar * vte_terminal_accessible_get_text_at_offset(AtkText *text, gint offset, AtkTextBoundary boundary_type, gint *start_offset, gint *end_offset) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); return vte_terminal_accessible_get_text_somewhere(text, offset, boundary_type, direction_current, start_offset, end_offset); } static gunichar vte_terminal_accessible_get_character_at_offset(AtkText *text, gint offset) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); char *unichar; gunichar ret; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); g_assert(offset < (int) priv->snapshot_characters->len); unichar = vte_terminal_accessible_get_text(text, offset, offset + 1); ret = g_utf8_get_char(unichar); g_free(unichar); return ret; } static gint vte_terminal_accessible_get_caret_offset(AtkText *text) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); return priv->snapshot_caret; } static AtkAttributeSet * get_attribute_set (struct _VteCharAttributes attr) { AtkAttributeSet *set = NULL; AtkAttribute *at; if (attr.underline) { at = g_new (AtkAttribute, 1); at->name = g_strdup ("underline"); at->value = g_strdup ("true"); set = g_slist_append (set, at); } if (attr.strikethrough) { at = g_new (AtkAttribute, 1); at->name = g_strdup ("strikethrough"); at->value = g_strdup ("true"); set = g_slist_append (set, at); } at = g_new (AtkAttribute, 1); at->name = g_strdup ("fg-color"); at->value = g_strdup_printf ("%u,%u,%u", attr.fore.red, attr.fore.green, attr.fore.blue); set = g_slist_append (set, at); at = g_new (AtkAttribute, 1); at->name = g_strdup ("bg-color"); at->value = g_strdup_printf ("%u,%u,%u", attr.back.red, attr.back.green, attr.back.blue); set = g_slist_append (set, at); return set; } static gboolean _pango_color_equal(const PangoColor *a, const PangoColor *b) { return a->red == b->red && a->green == b->green && a->blue == b->blue; } static AtkAttributeSet * vte_terminal_accessible_get_run_attributes(AtkText *text, gint offset, gint *start_offset, gint *end_offset) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); guint i; struct _VteCharAttributes cur_attr; struct _VteCharAttributes attr; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); attr = g_array_index (priv->snapshot_attributes, struct _VteCharAttributes, offset); *start_offset = 0; for (i = offset; i--;) { cur_attr = g_array_index (priv->snapshot_attributes, struct _VteCharAttributes, i); if (!_pango_color_equal (&cur_attr.fore, &attr.fore) || !_pango_color_equal (&cur_attr.back, &attr.back) || cur_attr.underline != attr.underline || cur_attr.strikethrough != attr.strikethrough) { *start_offset = i + 1; break; } } *end_offset = priv->snapshot_attributes->len - 1; for (i = offset + 1; i < priv->snapshot_attributes->len; i++) { cur_attr = g_array_index (priv->snapshot_attributes, struct _VteCharAttributes, i); if (!_pango_color_equal (&cur_attr.fore, &attr.fore) || !_pango_color_equal (&cur_attr.back, &attr.back) || cur_attr.underline != attr.underline || cur_attr.strikethrough != attr.strikethrough) { *end_offset = i - 1; break; } } return get_attribute_set (attr); } static AtkAttributeSet * vte_terminal_accessible_get_default_attributes(AtkText *text) { return NULL; } static void vte_terminal_accessible_get_character_extents(AtkText *text, gint offset, gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); VteTerminal *terminal; glong char_width, char_height; gint base_x, base_y, w, h; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); terminal = VTE_TERMINAL (gtk_accessible_get_widget (GTK_ACCESSIBLE (text))); atk_component_get_extents (ATK_COMPONENT (text), &base_x, &base_y, &w, &h, coords); xy_from_offset (priv, offset, x, y); char_width = vte_terminal_get_char_width (terminal); char_height = vte_terminal_get_char_height (terminal); *x *= char_width; *y *= char_height; *width = char_width; *height = char_height; *x += base_x; *y += base_y; } static gint vte_terminal_accessible_get_character_count(AtkText *text) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); return priv->snapshot_attributes->len; } static gint vte_terminal_accessible_get_offset_at_point(AtkText *text, gint x, gint y, AtkCoordType coords) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); VteTerminal *terminal; glong char_width, char_height; gint base_x, base_y, w, h; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); terminal = VTE_TERMINAL (gtk_accessible_get_widget (GTK_ACCESSIBLE (text))); atk_component_get_extents (ATK_COMPONENT (text), &base_x, &base_y, &w, &h, coords); /* FIXME: use _vte_terminal_xy_to_grid */ char_width = vte_terminal_get_char_width (terminal); char_height = vte_terminal_get_char_height (terminal); x -= base_x; y -= base_y; x /= char_width; y /= char_height; return offset_from_xy (priv, x, y); } static gint vte_terminal_accessible_get_n_selections(AtkText *text) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); GtkWidget *widget; VteTerminal *terminal; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text)); if (widget == NULL) { /* State is defunct */ return -1; } terminal = VTE_TERMINAL (widget); return (vte_terminal_get_has_selection (terminal)) ? 1 : 0; } static gchar * vte_terminal_accessible_get_selection(AtkText *text, gint selection_number, gint *start_offset, gint *end_offset) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); GtkWidget *widget; VteTerminal *terminal; long start_x, start_y, end_x, end_y; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text)); if (widget == NULL) { /* State is defunct */ return NULL; } terminal = VTE_TERMINAL (widget); if (!vte_terminal_get_has_selection (terminal)) { return NULL; } if (selection_number != 0) { return NULL; } _vte_terminal_get_start_selection (terminal, &start_x, &start_y); *start_offset = offset_from_xy (priv, start_x, start_y); _vte_terminal_get_end_selection (terminal, &end_x, &end_y); *end_offset = offset_from_xy (priv, end_x, end_y); return _vte_terminal_get_selection (terminal); } static gboolean vte_terminal_accessible_add_selection(AtkText *text, gint start_offset, gint end_offset) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); GtkWidget *widget; VteTerminal *terminal; gint start_x, start_y, end_x, end_y; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text)); if (widget == NULL) { /* State is defunct */ return FALSE; } terminal = VTE_TERMINAL (widget); g_assert(!vte_terminal_get_has_selection (terminal)); xy_from_offset (priv, start_offset, &start_x, &start_y); xy_from_offset (priv, end_offset, &end_x, &end_y); _vte_terminal_select_text (terminal, start_x, start_y, end_x, end_y, start_offset, end_offset); return TRUE; } static gboolean vte_terminal_accessible_remove_selection(AtkText *text, gint selection_number) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); GtkWidget *widget; VteTerminal *terminal; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text)); if (widget == NULL) { /* State is defunct */ return FALSE; } terminal = VTE_TERMINAL (widget); if (selection_number == 0 && vte_terminal_get_has_selection (terminal)) { _vte_terminal_remove_selection (terminal); return TRUE; } else { return FALSE; } } static gboolean vte_terminal_accessible_set_selection(AtkText *text, gint selection_number, gint start_offset, gint end_offset) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); GtkWidget *widget; VteTerminal *terminal; vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(text)); if (widget == NULL) { /* State is defunct */ return FALSE; } terminal = VTE_TERMINAL (widget); if (selection_number != 0) { return FALSE; } if (vte_terminal_get_has_selection (terminal)) { _vte_terminal_remove_selection (terminal); } return vte_terminal_accessible_add_selection (text, start_offset, end_offset); } static gboolean vte_terminal_accessible_set_caret_offset(AtkText *text, gint offset) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(text); vte_terminal_accessible_update_private_data_if_needed(accessible, NULL, NULL); /* Whoa, very not allowed. */ return FALSE; } static void vte_terminal_accessible_text_iface_init(AtkTextIface *text) { text->get_text = vte_terminal_accessible_get_text; text->get_text_after_offset = vte_terminal_accessible_get_text_after_offset; text->get_text_at_offset = vte_terminal_accessible_get_text_at_offset; text->get_character_at_offset = vte_terminal_accessible_get_character_at_offset; text->get_text_before_offset = vte_terminal_accessible_get_text_before_offset; text->get_caret_offset = vte_terminal_accessible_get_caret_offset; text->get_run_attributes = vte_terminal_accessible_get_run_attributes; text->get_default_attributes = vte_terminal_accessible_get_default_attributes; text->get_character_extents = vte_terminal_accessible_get_character_extents; text->get_character_count = vte_terminal_accessible_get_character_count; text->get_offset_at_point = vte_terminal_accessible_get_offset_at_point; text->get_n_selections = vte_terminal_accessible_get_n_selections; text->get_selection = vte_terminal_accessible_get_selection; text->add_selection = vte_terminal_accessible_add_selection; text->remove_selection = vte_terminal_accessible_remove_selection; text->set_selection = vte_terminal_accessible_set_selection; text->set_caret_offset = vte_terminal_accessible_set_caret_offset; } static gboolean vte_terminal_accessible_set_extents(AtkComponent *component, gint x, gint y, gint width, gint height, AtkCoordType coord_type) { /* FIXME? We can change the size, but our position is controlled * by the parent container. */ return FALSE; } static gboolean vte_terminal_accessible_set_position(AtkComponent *component, gint x, gint y, AtkCoordType coord_type) { /* Controlled by the parent container, if there is one. */ return FALSE; } static gboolean vte_terminal_accessible_set_size(AtkComponent *component, gint width, gint height) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(component); VteTerminal *terminal; long columns, rows; GtkWidget *widget; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE(accessible)); if (widget == NULL) { return FALSE; } terminal = VTE_TERMINAL(widget); /* If the size is an exact multiple of the cell size, use that, * otherwise round down. */ (void) _vte_terminal_size_to_grid_size(terminal, width, height, &columns, &rows); vte_terminal_set_size(terminal, columns, rows); return (vte_terminal_get_row_count (terminal) == rows) && (vte_terminal_get_column_count (terminal) == columns); } static AtkObject * vte_terminal_accessible_ref_accessible_at_point(AtkComponent *component, gint x, gint y, AtkCoordType coord_type) { /* There are no children. */ return NULL; } static void vte_terminal_accessible_component_iface_init(AtkComponentIface *component) { component->ref_accessible_at_point = vte_terminal_accessible_ref_accessible_at_point; component->set_extents = vte_terminal_accessible_set_extents; component->set_position = vte_terminal_accessible_set_position; component->set_size = vte_terminal_accessible_set_size; } /* AtkAction interface */ static gboolean vte_terminal_accessible_do_action (AtkAction *accessible, int i) { GtkWidget *widget; gboolean retval = FALSE; g_return_val_if_fail (i < LAST_ACTION, FALSE); widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)); if (!widget) { return FALSE; } switch (i) { case ACTION_MENU : g_signal_emit_by_name (widget, "popup_menu", &retval); break; default : g_warning ("Invalid action passed to VteTerminalAccessible::do_action"); return FALSE; } return retval; } static int vte_terminal_accessible_get_n_actions (AtkAction *accessible) { return LAST_ACTION; } static const char * vte_terminal_accessible_action_get_description (AtkAction *action, int i) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(action); VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); g_return_val_if_fail (i < LAST_ACTION, NULL); if (priv->action_descriptions[i]) { return priv->action_descriptions[i]; } else { return vte_terminal_accessible_action_descriptions[i]; } } static const char * vte_terminal_accessible_action_get_name (AtkAction *accessible, int i) { g_return_val_if_fail (i < LAST_ACTION, NULL); return vte_terminal_accessible_action_names[i]; } static const char * vte_terminal_accessible_action_get_keybinding (AtkAction *accessible, int i) { g_return_val_if_fail (i < LAST_ACTION, NULL); return NULL; } static gboolean vte_terminal_accessible_action_set_description (AtkAction *action, int i, const char *description) { VteTerminalAccessible *accessible = VTE_TERMINAL_ACCESSIBLE(action); VteTerminalAccessiblePrivate *priv = (VteTerminalAccessiblePrivate *)_vte_terminal_accessible_get_instance_private(accessible); g_return_val_if_fail (i < LAST_ACTION, FALSE); if (priv->action_descriptions[i]) { g_free (priv->action_descriptions[i]); } priv->action_descriptions[i] = g_strdup (description); return TRUE; } static void vte_terminal_accessible_action_iface_init(AtkActionIface *action) { action->do_action = vte_terminal_accessible_do_action; action->get_n_actions = vte_terminal_accessible_get_n_actions; action->get_description = vte_terminal_accessible_action_get_description; action->get_name = vte_terminal_accessible_action_get_name; action->get_keybinding = vte_terminal_accessible_action_get_keybinding; action->set_description = vte_terminal_accessible_action_set_description; } static void _vte_terminal_accessible_class_init(VteTerminalAccessibleClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); AtkObjectClass *atk_object_class = ATK_OBJECT_CLASS (klass); gobject_class->finalize = vte_terminal_accessible_finalize; atk_object_class->initialize = vte_terminal_accessible_initialize; }