/* GAIL - The GNOME Accessibility Enabling Library * Copyright 2001, 2002, 2003 Sun Microsystems 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 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include "gtklabelaccessible.h" #include static void gtk_label_accessible_class_init (GtkLabelAccessibleClass *klass); static void gtk_label_accessible_init (GtkLabelAccessible *label); static void gtk_label_accessible_real_initialize (AtkObject *obj, gpointer data); static void gtk_label_accessible_real_notify_gtk (GObject *obj, GParamSpec *pspec); static void gtk_label_accessible_init_text_util (GtkLabelAccessible *gail_label, GtkWidget *widget); static void gtk_label_accessible_finalize (GObject *object); static void atk_text_interface_init (AtkTextIface *iface); /* atkobject.h */ static const gchar* gtk_label_accessible_get_name (AtkObject *accessible); static AtkStateSet* gtk_label_accessible_ref_state_set (AtkObject *accessible); static AtkRelationSet* gtk_label_accessible_ref_relation_set (AtkObject *accessible); /* atktext.h */ static gchar* gtk_label_accessible_get_text (AtkText *text, gint start_pos, gint end_pos); static gunichar gtk_label_accessible_get_character_at_offset(AtkText *text, gint offset); static gchar* gtk_label_accessible_get_text_before_offset(AtkText *text, gint offset, AtkTextBoundary boundary_type, gint *start_offset, gint *end_offset); static gchar* gtk_label_accessible_get_text_at_offset (AtkText *text, gint offset, AtkTextBoundary boundary_type, gint *start_offset, gint *end_offset); static gchar* gtk_label_accessible_get_text_after_offset (AtkText *text, gint offset, AtkTextBoundary boundary_type, gint *start_offset, gint *end_offset); static gint gtk_label_accessible_get_character_count (AtkText *text); static gint gtk_label_accessible_get_caret_offset (AtkText *text); static gboolean gtk_label_accessible_set_caret_offset (AtkText *text, gint offset); static gint gtk_label_accessible_get_n_selections (AtkText *text); static gchar* gtk_label_accessible_get_selection (AtkText *text, gint selection_num, gint *start_offset, gint *end_offset); static gboolean gtk_label_accessible_add_selection (AtkText *text, gint start_offset, gint end_offset); static gboolean gtk_label_accessible_remove_selection (AtkText *text, gint selection_num); static gboolean gtk_label_accessible_set_selection (AtkText *text, gint selection_num, gint start_offset, gint end_offset); static void gtk_label_accessible_get_character_extents (AtkText *text, gint offset, gint *x, gint *y, gint *width, gint *height, AtkCoordType coords); static gint gtk_label_accessible_get_offset_at_point (AtkText *text, gint x, gint y, AtkCoordType coords); static AtkAttributeSet* gtk_label_accessible_get_run_attributes (AtkText *text, gint offset, gint *start_offset, gint *end_offset); static AtkAttributeSet* gtk_label_accessible_get_default_attributes (AtkText *text); G_DEFINE_TYPE_WITH_CODE (GtkLabelAccessible, gtk_label_accessible, GAIL_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, atk_text_interface_init)) static void gtk_label_accessible_class_init (GtkLabelAccessibleClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); AtkObjectClass *class = ATK_OBJECT_CLASS (klass); GailWidgetClass *widget_class; gobject_class->finalize = gtk_label_accessible_finalize; widget_class = (GailWidgetClass*)klass; widget_class->notify_gtk = gtk_label_accessible_real_notify_gtk; class->get_name = gtk_label_accessible_get_name; class->ref_state_set = gtk_label_accessible_ref_state_set; class->ref_relation_set = gtk_label_accessible_ref_relation_set; class->initialize = gtk_label_accessible_real_initialize; } static void gtk_label_accessible_init (GtkLabelAccessible *label) { } static void gtk_label_accessible_real_initialize (AtkObject *obj, gpointer data) { GtkWidget *widget; GtkLabelAccessible *accessible; ATK_OBJECT_CLASS (gtk_label_accessible_parent_class)->initialize (obj, data); accessible = GTK_LABEL_ACCESSIBLE (obj); accessible->cursor_position = 0; accessible->selection_bound = 0; accessible->textutil = NULL; accessible->label_length = 0; widget = GTK_WIDGET (data); gtk_label_accessible_init_text_util (accessible, widget); /* * Check whether ancestor of GtkLabel is a GtkButton and if so * set accessible parent for GtkLabelAccessible */ while (widget != NULL) { widget = gtk_widget_get_parent (widget); if (GTK_IS_BUTTON (widget)) { atk_object_set_parent (obj, gtk_widget_get_accessible (widget)); break; } } if (GTK_IS_ACCEL_LABEL (widget)) obj->role = ATK_ROLE_ACCEL_LABEL; else obj->role = ATK_ROLE_LABEL; } static void gtk_label_accessible_init_text_util (GtkLabelAccessible *accessible, GtkWidget *widget) { GtkLabel *label; const gchar *label_text; if (accessible->textutil == NULL) accessible->textutil = gail_text_util_new (); label = GTK_LABEL (widget); label_text = gtk_label_get_text (label); gail_text_util_text_setup (accessible->textutil, label_text); if (label_text == NULL) accessible->label_length = 0; else accessible->label_length = g_utf8_strlen (label_text, -1); } static void notify_name_change (AtkObject *atk_obj) { GtkLabel *label; GtkLabelAccessible *accessible; GtkWidget *widget; GObject *gail_obj; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_obj)); if (widget == NULL) return; gail_obj = G_OBJECT (atk_obj); label = GTK_LABEL (widget); accessible = GTK_LABEL_ACCESSIBLE (atk_obj); /* * Check whether the label has actually changed before emitting * notification. */ if (accessible->textutil->buffer) { GtkTextIter start, end; const char *new_label; char *old_label; int same; gtk_text_buffer_get_start_iter (accessible->textutil->buffer, &start); gtk_text_buffer_get_end_iter (accessible->textutil->buffer, &end); old_label = gtk_text_buffer_get_text (accessible->textutil->buffer, &start, &end, FALSE); new_label = gtk_label_get_text (label); same = strcmp (new_label, old_label); g_free (old_label); if (same == 0) return; } /* Create a delete text and an insert text signal */ g_signal_emit_by_name (gail_obj, "text_changed::delete", 0, accessible->label_length); gtk_label_accessible_init_text_util (accessible, widget); g_signal_emit_by_name (gail_obj, "text_changed::insert", 0, accessible->label_length); if (atk_obj->name == NULL) /* * The label has changed so notify a change in accessible-name */ g_object_notify (gail_obj, "accessible-name"); g_signal_emit_by_name (gail_obj, "visible_data_changed"); } static void gtk_label_accessible_real_notify_gtk (GObject *obj, GParamSpec *pspec) { GtkWidget *widget = GTK_WIDGET (obj); AtkObject* atk_obj = gtk_widget_get_accessible (widget); GtkLabel *label; GtkLabelAccessible *accessible; GObject *gail_obj; accessible = GTK_LABEL_ACCESSIBLE (atk_obj); if (strcmp (pspec->name, "label") == 0 || strcmp (pspec->name, "use-underline") == 0 || strcmp (pspec->name, "use-markup") == 0) { notify_name_change (atk_obj); } else if (strcmp (pspec->name, "cursor-position") == 0) { gint start, end, tmp; gboolean text_caret_moved = FALSE; gboolean selection_changed = FALSE; gail_obj = G_OBJECT (atk_obj); label = GTK_LABEL (widget); if (accessible->selection_bound != -1 && accessible->selection_bound < accessible->cursor_position) { tmp = accessible->selection_bound; accessible->selection_bound = accessible->cursor_position; accessible->cursor_position = tmp; } if (gtk_label_get_selection_bounds (label, &start, &end)) { if (start != accessible->cursor_position || end != accessible->selection_bound) { if (end != accessible->selection_bound) { accessible->selection_bound = start; accessible->cursor_position = end; } else { accessible->selection_bound = end; accessible->cursor_position = start; } text_caret_moved = TRUE; if (start != end) selection_changed = TRUE; } } else { if (accessible->cursor_position != accessible->selection_bound) selection_changed = TRUE; if (gtk_label_get_selectable (label)) { if (accessible->cursor_position != -1 && start != accessible->cursor_position) text_caret_moved = TRUE; if (accessible->selection_bound != -1 && end != accessible ->selection_bound) { text_caret_moved = TRUE; accessible->cursor_position = end; accessible->selection_bound = start; } else { accessible->cursor_position = start; accessible->selection_bound = end; } } else { /* GtkLabel has become non selectable */ accessible->cursor_position = 0; accessible->selection_bound = 0; text_caret_moved = TRUE; } } if (text_caret_moved) g_signal_emit_by_name (gail_obj, "text_caret_moved", accessible->cursor_position); if (selection_changed) g_signal_emit_by_name (gail_obj, "text_selection_changed"); } else GAIL_WIDGET_CLASS (gtk_label_accessible_parent_class)->notify_gtk (obj, pspec); } static void gtk_label_accessible_finalize (GObject *object) { GtkLabelAccessible *accessible = GTK_LABEL_ACCESSIBLE (object); if (accessible->textutil) g_object_unref (accessible->textutil); G_OBJECT_CLASS (gtk_label_accessible_parent_class)->finalize (object); } /* atkobject.h */ static AtkStateSet* gtk_label_accessible_ref_state_set (AtkObject *accessible) { AtkStateSet *state_set; GtkWidget *widget; state_set = ATK_OBJECT_CLASS (gtk_label_accessible_parent_class)->ref_state_set (accessible); widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)); if (widget == NULL) return state_set; atk_state_set_add_state (state_set, ATK_STATE_MULTI_LINE); return state_set; } AtkRelationSet* gtk_label_accessible_ref_relation_set (AtkObject *obj) { GtkWidget *widget; AtkRelationSet *relation_set; g_return_val_if_fail (GTK_IS_LABEL_ACCESSIBLE (obj), NULL); widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj)); if (widget == NULL) return NULL; relation_set = ATK_OBJECT_CLASS (gtk_label_accessible_parent_class)->ref_relation_set (obj); if (!atk_relation_set_contains (relation_set, ATK_RELATION_LABEL_FOR)) { /* * Get the mnemonic widget * * The relation set is not updated if the mnemonic widget is changed */ GtkWidget *mnemonic_widget = gtk_label_get_mnemonic_widget (GTK_LABEL (widget)); if (mnemonic_widget) { AtkObject *accessible_array[1]; AtkRelation* relation; if (!gtk_widget_get_can_focus (mnemonic_widget)) { /* * Handle the case where a GtkFileChooserButton is specified as the * mnemonic widget. use the combobox which is a child of the * GtkFileChooserButton as the mnemonic widget. See bug #359843. */ if (GTK_IS_BOX (mnemonic_widget)) { GList *list, *tmpl; list = gtk_container_get_children (GTK_CONTAINER (mnemonic_widget)); if (g_list_length (list) == 2) { tmpl = g_list_last (list); if (GTK_IS_COMBO_BOX(tmpl->data)) { mnemonic_widget = GTK_WIDGET(tmpl->data); } } g_list_free (list); } } accessible_array[0] = gtk_widget_get_accessible (mnemonic_widget); relation = atk_relation_new (accessible_array, 1, ATK_RELATION_LABEL_FOR); atk_relation_set_add (relation_set, relation); /* * Unref the relation so that it is not leaked. */ g_object_unref (relation); } } return relation_set; } static const gchar* gtk_label_accessible_get_name (AtkObject *accessible) { const gchar *name; g_return_val_if_fail (GTK_IS_LABEL_ACCESSIBLE (accessible), NULL); name = ATK_OBJECT_CLASS (gtk_label_accessible_parent_class)->get_name (accessible); if (name != NULL) return name; else { /* * Get the text on the label */ GtkWidget *widget; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)); if (widget == NULL) return NULL; g_return_val_if_fail (GTK_IS_LABEL (widget), NULL); return gtk_label_get_text (GTK_LABEL (widget)); } } /* atktext.h */ static void atk_text_interface_init (AtkTextIface *iface) { iface->get_text = gtk_label_accessible_get_text; iface->get_character_at_offset = gtk_label_accessible_get_character_at_offset; iface->get_text_before_offset = gtk_label_accessible_get_text_before_offset; iface->get_text_at_offset = gtk_label_accessible_get_text_at_offset; iface->get_text_after_offset = gtk_label_accessible_get_text_after_offset; iface->get_character_count = gtk_label_accessible_get_character_count; iface->get_caret_offset = gtk_label_accessible_get_caret_offset; iface->set_caret_offset = gtk_label_accessible_set_caret_offset; iface->get_n_selections = gtk_label_accessible_get_n_selections; iface->get_selection = gtk_label_accessible_get_selection; iface->add_selection = gtk_label_accessible_add_selection; iface->remove_selection = gtk_label_accessible_remove_selection; iface->set_selection = gtk_label_accessible_set_selection; iface->get_character_extents = gtk_label_accessible_get_character_extents; iface->get_offset_at_point = gtk_label_accessible_get_offset_at_point; iface->get_run_attributes = gtk_label_accessible_get_run_attributes; iface->get_default_attributes = gtk_label_accessible_get_default_attributes; } static gchar* gtk_label_accessible_get_text (AtkText *text, gint start_pos, gint end_pos) { GtkWidget *widget; GtkLabel *label; const gchar *label_text; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return NULL; label = GTK_LABEL (widget); label_text = gtk_label_get_text (label); if (label_text == NULL) return NULL; else return gail_text_util_get_substring (GTK_LABEL_ACCESSIBLE (text)->textutil, start_pos, end_pos); } static gchar* gtk_label_accessible_get_text_before_offset (AtkText *text, gint offset, AtkTextBoundary boundary_type, gint *start_offset, gint *end_offset) { GtkWidget *widget; GtkLabel *label; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return NULL; /* Get label */ label = GTK_LABEL (widget); return gail_text_util_get_text (GTK_LABEL_ACCESSIBLE (text)->textutil, gtk_label_get_layout (label), GAIL_BEFORE_OFFSET, boundary_type, offset, start_offset, end_offset); } static gchar* gtk_label_accessible_get_text_at_offset (AtkText *text, gint offset, AtkTextBoundary boundary_type, gint *start_offset, gint *end_offset) { GtkWidget *widget; GtkLabel *label; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return NULL; /* Get label */ label = GTK_LABEL (widget); return gail_text_util_get_text (GTK_LABEL_ACCESSIBLE (text)->textutil, gtk_label_get_layout (label), GAIL_AT_OFFSET, boundary_type, offset, start_offset, end_offset); } static gchar* gtk_label_accessible_get_text_after_offset (AtkText *text, gint offset, AtkTextBoundary boundary_type, gint *start_offset, gint *end_offset) { GtkWidget *widget; GtkLabel *label; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return NULL; /* Get label */ label = GTK_LABEL (widget); return gail_text_util_get_text (GTK_LABEL_ACCESSIBLE (text)->textutil, gtk_label_get_layout (label), GAIL_AFTER_OFFSET, boundary_type, offset, start_offset, end_offset); } static gint gtk_label_accessible_get_character_count (AtkText *text) { GtkWidget *widget; GtkLabel *label; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return 0; label = GTK_LABEL (widget); return g_utf8_strlen (gtk_label_get_text (label), -1); } static gint gtk_label_accessible_get_caret_offset (AtkText *text) { return GTK_LABEL_ACCESSIBLE (text)->cursor_position; } static gboolean gtk_label_accessible_set_caret_offset (AtkText *text, gint offset) { GtkWidget *widget; GtkLabel *label; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return 0; label = GTK_LABEL (widget); if (gtk_label_get_selectable (label) && offset >= 0 && offset <= g_utf8_strlen (gtk_label_get_text (label), -1)) { gtk_label_select_region (label, offset, offset); return TRUE; } else return FALSE; } static gint gtk_label_accessible_get_n_selections (AtkText *text) { GtkWidget *widget; GtkLabel *label; gint start, end; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return 0; label = GTK_LABEL (widget); if (!gtk_label_get_selectable (label)) return 0; if (gtk_label_get_selection_bounds (label, &start, &end)) return 1; else return 0; } static gchar* gtk_label_accessible_get_selection (AtkText *text, gint selection_num, gint *start_pos, gint *end_pos) { GtkWidget *widget; GtkLabel *label; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return NULL; label = GTK_LABEL (widget); /* Only let the user get the selection if one is set, and if the * selection_num is 0. */ if (!gtk_label_get_selectable( label) || selection_num != 0) return NULL; if (gtk_label_get_selection_bounds (label, start_pos, end_pos)) { const gchar* label_text = gtk_label_get_text (label); if (label_text == NULL) return 0; else return gail_text_util_get_substring (GTK_LABEL_ACCESSIBLE (text)->textutil, *start_pos, *end_pos); } else return NULL; } static gboolean gtk_label_accessible_add_selection (AtkText *text, gint start_pos, gint end_pos) { GtkWidget *widget; GtkLabel *label; gint start, end; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return FALSE; label = GTK_LABEL (widget); if (!gtk_label_get_selectable (label)) return FALSE; if (! gtk_label_get_selection_bounds (label, &start, &end)) { gtk_label_select_region (label, start_pos, end_pos); return TRUE; } else return FALSE; } static gboolean gtk_label_accessible_remove_selection (AtkText *text, gint selection_num) { GtkWidget *widget; GtkLabel *label; gint start, end; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return FALSE; if (selection_num != 0) return FALSE; label = GTK_LABEL (widget); if (!gtk_label_get_selectable (label)) return FALSE; if (gtk_label_get_selection_bounds (label, &start, &end)) { gtk_label_select_region (label, 0, 0); return TRUE; } else return FALSE; } static gboolean gtk_label_accessible_set_selection (AtkText *text, gint selection_num, gint start_pos, gint end_pos) { GtkWidget *widget; GtkLabel *label; gint start, end; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return FALSE; if (selection_num != 0) return FALSE; label = GTK_LABEL (widget); if (!gtk_label_get_selectable (label)) return FALSE; if (gtk_label_get_selection_bounds (label, &start, &end)) { gtk_label_select_region (label, start_pos, end_pos); return TRUE; } else return FALSE; } static void gtk_label_accessible_get_character_extents (AtkText *text, gint offset, gint *x, gint *y, gint *width, gint *height, AtkCoordType coords) { GtkWidget *widget; GtkLabel *label; PangoRectangle char_rect; const gchar *label_text; gint index, x_layout, y_layout; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return; label = GTK_LABEL (widget); gtk_label_get_layout_offsets (label, &x_layout, &y_layout); label_text = gtk_label_get_text (label); index = g_utf8_offset_to_pointer (label_text, offset) - label_text; pango_layout_index_to_pos (gtk_label_get_layout (label), index, &char_rect); gail_misc_get_extents_from_pango_rectangle (widget, &char_rect, x_layout, y_layout, x, y, width, height, coords); } static gint gtk_label_accessible_get_offset_at_point (AtkText *text, gint x, gint y, AtkCoordType coords) { GtkWidget *widget; GtkLabel *label; const gchar *label_text; gint index, x_layout, y_layout; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return -1; label = GTK_LABEL (widget); gtk_label_get_layout_offsets (label, &x_layout, &y_layout); index = gail_misc_get_index_at_point_in_layout (widget, gtk_label_get_layout (label), x_layout, y_layout, x, y, coords); label_text = gtk_label_get_text (label); if (index == -1) { if (coords == ATK_XY_WINDOW || coords == ATK_XY_SCREEN) return g_utf8_strlen (label_text, -1); return index; } else return g_utf8_pointer_to_offset (label_text, label_text + index); } static AtkAttributeSet* gtk_label_accessible_get_run_attributes (AtkText *text, gint offset, gint *start_offset, gint *end_offset) { GtkWidget *widget; GtkLabel *label; AtkAttributeSet *at_set = NULL; GtkJustification justify; GtkTextDirection dir; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return NULL; label = GTK_LABEL (widget); /* Get values set for entire label, if any */ justify = gtk_label_get_justify (label); if (justify != GTK_JUSTIFY_CENTER) { at_set = gail_misc_add_attribute (at_set, ATK_TEXT_ATTR_JUSTIFICATION, g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_JUSTIFICATION, justify))); } dir = gtk_widget_get_direction (widget); if (dir == GTK_TEXT_DIR_RTL) { at_set = gail_misc_add_attribute (at_set, ATK_TEXT_ATTR_DIRECTION, g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_DIRECTION, dir))); } at_set = gail_misc_layout_get_run_attributes (at_set, gtk_label_get_layout (label), gtk_label_get_text (label), offset, start_offset, end_offset); return at_set; } static AtkAttributeSet* gtk_label_accessible_get_default_attributes (AtkText *text) { GtkWidget *widget; GtkLabel *label; AtkAttributeSet *at_set = NULL; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return NULL; label = GTK_LABEL (widget); at_set = gail_misc_get_default_attributes (at_set, gtk_label_get_layout (label), widget); return at_set; } static gunichar gtk_label_accessible_get_character_at_offset (AtkText *text, gint offset) { GtkWidget *widget; GtkLabel *label; const gchar *string; gchar *index; widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); if (widget == NULL) return '\0'; label = GTK_LABEL (widget); string = gtk_label_get_text (label); if (offset >= g_utf8_strlen (string, -1)) return '\0'; index = g_utf8_offset_to_pointer (string, offset); return g_utf8_get_char (index); }