1 /* GAIL - The GNOME Accessibility Enabling Library
2 * Copyright 2001, 2002, 2003 Sun Microsystems Inc.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
25 #include "gaillabel.h"
26 #include "gailwindow.h"
27 #include <libgail-util/gailmisc.h>
29 static void gail_label_class_init (GailLabelClass *klass);
30 static void gail_label_init (GailLabel *label);
31 static void gail_label_real_initialize (AtkObject *obj,
33 static void gail_label_real_notify_gtk (GObject *obj,
35 static void gail_label_map_gtk (GtkWidget *widget,
37 static void gail_label_init_text_util (GailLabel *gail_label,
39 static void gail_label_finalize (GObject *object);
41 static void atk_text_interface_init (AtkTextIface *iface);
45 static const gchar* gail_label_get_name (AtkObject *accessible);
46 static AtkStateSet* gail_label_ref_state_set (AtkObject *accessible);
47 static AtkRelationSet* gail_label_ref_relation_set (AtkObject *accessible);
51 static gchar* gail_label_get_text (AtkText *text,
54 static gunichar gail_label_get_character_at_offset(AtkText *text,
56 static gchar* gail_label_get_text_before_offset(AtkText *text,
58 AtkTextBoundary boundary_type,
61 static gchar* gail_label_get_text_at_offset (AtkText *text,
63 AtkTextBoundary boundary_type,
66 static gchar* gail_label_get_text_after_offset (AtkText *text,
68 AtkTextBoundary boundary_type,
71 static gint gail_label_get_character_count (AtkText *text);
72 static gint gail_label_get_caret_offset (AtkText *text);
73 static gboolean gail_label_set_caret_offset (AtkText *text,
75 static gint gail_label_get_n_selections (AtkText *text);
76 static gchar* gail_label_get_selection (AtkText *text,
80 static gboolean gail_label_add_selection (AtkText *text,
83 static gboolean gail_label_remove_selection (AtkText *text,
85 static gboolean gail_label_set_selection (AtkText *text,
89 static void gail_label_get_character_extents (AtkText *text,
96 static gint gail_label_get_offset_at_point (AtkText *text,
100 static AtkAttributeSet* gail_label_get_run_attributes
105 static AtkAttributeSet* gail_label_get_default_attributes
108 G_DEFINE_TYPE_WITH_CODE (GailLabel, gail_label, GAIL_TYPE_WIDGET,
109 G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, atk_text_interface_init))
112 gail_label_class_init (GailLabelClass *klass)
114 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
115 AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
116 GailWidgetClass *widget_class;
118 gobject_class->finalize = gail_label_finalize;
120 widget_class = (GailWidgetClass*)klass;
121 widget_class->notify_gtk = gail_label_real_notify_gtk;
123 class->get_name = gail_label_get_name;
124 class->ref_state_set = gail_label_ref_state_set;
125 class->ref_relation_set = gail_label_ref_relation_set;
126 class->initialize = gail_label_real_initialize;
130 gail_label_init (GailLabel *label)
135 gail_label_real_initialize (AtkObject *obj,
139 GailLabel *gail_label;
141 ATK_OBJECT_CLASS (gail_label_parent_class)->initialize (obj, data);
143 gail_label = GAIL_LABEL (obj);
145 gail_label->window_create_handler = 0;
146 gail_label->has_top_level = FALSE;
147 gail_label->cursor_position = 0;
148 gail_label->selection_bound = 0;
149 gail_label->textutil = NULL;
150 gail_label->label_length = 0;
152 widget = GTK_WIDGET (data);
154 if (gtk_widget_get_mapped (widget))
155 gail_label_init_text_util (gail_label, widget);
157 g_signal_connect (widget,
159 G_CALLBACK (gail_label_map_gtk),
163 * Check whether ancestor of GtkLabel is a GtkButton and if so
164 * set accessible parent for GailLabel
166 while (widget != NULL)
168 widget = gtk_widget_get_parent (widget);
169 if (GTK_IS_BUTTON (widget))
171 atk_object_set_parent (obj, gtk_widget_get_accessible (widget));
176 if (GTK_IS_ACCEL_LABEL (widget))
177 obj->role = ATK_ROLE_ACCEL_LABEL;
179 obj->role = ATK_ROLE_LABEL;
183 gail_label_map_gtk (GtkWidget *widget,
186 GailLabel *gail_label;
188 gail_label = GAIL_LABEL (data);
189 gail_label_init_text_util (gail_label, widget);
193 gail_label_init_text_util (GailLabel *gail_label,
197 const gchar *label_text;
199 if (gail_label->textutil == NULL)
200 gail_label->textutil = gail_text_util_new ();
202 label = GTK_LABEL (widget);
203 label_text = gtk_label_get_text (label);
204 gail_text_util_text_setup (gail_label->textutil, label_text);
206 if (label_text == NULL)
207 gail_label->label_length = 0;
209 gail_label->label_length = g_utf8_strlen (label_text, -1);
213 notify_name_change (AtkObject *atk_obj)
216 GailLabel *gail_label;
220 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_obj));
227 gail_obj = G_OBJECT (atk_obj);
228 label = GTK_LABEL (widget);
229 gail_label = GAIL_LABEL (atk_obj);
231 if (gail_label->textutil == NULL)
235 * Check whether the label has actually changed before emitting
238 if (gail_label->textutil->buffer)
240 GtkTextIter start, end;
241 const char *new_label;
245 gtk_text_buffer_get_start_iter (gail_label->textutil->buffer, &start);
246 gtk_text_buffer_get_end_iter (gail_label->textutil->buffer, &end);
247 old_label = gtk_text_buffer_get_text (gail_label->textutil->buffer, &start, &end, FALSE);
248 new_label = gtk_label_get_text (label);
249 same = strcmp (new_label, old_label);
255 /* Create a delete text and an insert text signal */
257 g_signal_emit_by_name (gail_obj, "text_changed::delete", 0,
258 gail_label->label_length);
260 gail_label_init_text_util (gail_label, widget);
262 g_signal_emit_by_name (gail_obj, "text_changed::insert", 0,
263 gail_label->label_length);
265 if (atk_obj->name == NULL)
267 * The label has changed so notify a change in accessible-name
269 g_object_notify (gail_obj, "accessible-name");
271 g_signal_emit_by_name (gail_obj, "visible_data_changed");
275 window_created (GObject *obj,
278 g_return_if_fail (GAIL_LABEL (data));
280 notify_name_change (ATK_OBJECT (data));
284 gail_label_real_notify_gtk (GObject *obj,
287 GtkWidget *widget = GTK_WIDGET (obj);
288 AtkObject* atk_obj = gtk_widget_get_accessible (widget);
290 GailLabel *gail_label;
292 AtkObject *top_level;
295 gail_label = GAIL_LABEL (atk_obj);
297 if (strcmp (pspec->name, "label") == 0)
300 * We may get a label change for a label which is not attached to an
301 * application. We wait until the toplevel window is created before
302 * emitting the notification.
304 * This happens when [Ctrl+]Alt+Tab is pressed in metacity
306 if (!gail_label->has_top_level)
312 top_level = temp_obj;
313 temp_obj = atk_object_get_parent (top_level);
315 if (atk_object_get_role (top_level) != ATK_ROLE_APPLICATION)
317 if (gail_label->window_create_handler == 0 &&
318 GAIL_IS_WINDOW (top_level))
319 gail_label->window_create_handler = g_signal_connect_after (top_level, "create", G_CALLBACK (window_created), atk_obj);
322 gail_label->has_top_level = TRUE;
324 if (gail_label->has_top_level)
325 notify_name_change (atk_obj);
327 else if (strcmp (pspec->name, "cursor-position") == 0)
329 gint start, end, tmp;
330 gboolean text_caret_moved = FALSE;
331 gboolean selection_changed = FALSE;
333 gail_obj = G_OBJECT (atk_obj);
334 label = GTK_LABEL (widget);
336 if (gail_label->selection_bound != -1 && gail_label->selection_bound < gail_label->cursor_position)
338 tmp = gail_label->selection_bound;
339 gail_label->selection_bound = gail_label->cursor_position;
340 gail_label->cursor_position = tmp;
343 if (gtk_label_get_selection_bounds (label, &start, &end))
345 if (start != gail_label->cursor_position ||
346 end != gail_label->selection_bound)
348 if (end != gail_label->selection_bound)
350 gail_label->selection_bound = start;
351 gail_label->cursor_position = end;
355 gail_label->selection_bound = end;
356 gail_label->cursor_position = start;
358 text_caret_moved = TRUE;
360 selection_changed = TRUE;
365 if (gail_label->cursor_position != gail_label->selection_bound)
366 selection_changed = TRUE;
367 if (gtk_label_get_selectable (label))
369 if (gail_label->cursor_position != -1 && start != gail_label->cursor_position)
370 text_caret_moved = TRUE;
371 if (gail_label->selection_bound != -1 && end != gail_label->selection_bound)
373 text_caret_moved = TRUE;
374 gail_label->cursor_position = end;
375 gail_label->selection_bound = start;
379 gail_label->cursor_position = start;
380 gail_label->selection_bound = end;
385 /* GtkLabel has become non selectable */
387 gail_label->cursor_position = 0;
388 gail_label->selection_bound = 0;
389 text_caret_moved = TRUE;
393 if (text_caret_moved)
394 g_signal_emit_by_name (gail_obj, "text_caret_moved",
395 gail_label->cursor_position);
396 if (selection_changed)
397 g_signal_emit_by_name (gail_obj, "text_selection_changed");
401 GAIL_WIDGET_CLASS (gail_label_parent_class)->notify_gtk (obj, pspec);
405 gail_label_finalize (GObject *object)
407 GailLabel *label = GAIL_LABEL (object);
410 g_object_unref (label->textutil);
411 G_OBJECT_CLASS (gail_label_parent_class)->finalize (object);
418 gail_label_ref_state_set (AtkObject *accessible)
420 AtkStateSet *state_set;
423 state_set = ATK_OBJECT_CLASS (gail_label_parent_class)->ref_state_set (accessible);
424 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
429 atk_state_set_add_state (state_set, ATK_STATE_MULTI_LINE);
435 gail_label_ref_relation_set (AtkObject *obj)
438 AtkRelationSet *relation_set;
440 g_return_val_if_fail (GAIL_IS_LABEL (obj), NULL);
442 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
449 relation_set = ATK_OBJECT_CLASS (gail_label_parent_class)->ref_relation_set (obj);
451 if (!atk_relation_set_contains (relation_set, ATK_RELATION_LABEL_FOR))
454 * Get the mnemonic widget
456 * The relation set is not updated if the mnemonic widget is changed
458 GtkWidget *mnemonic_widget = gtk_label_get_mnemonic_widget (GTK_LABEL (widget));
462 AtkObject *accessible_array[1];
463 AtkRelation* relation;
465 if (!gtk_widget_get_can_focus (mnemonic_widget))
468 * Handle the case where a GtkFileChooserButton is specified as the
469 * mnemonic widget. use the combobox which is a child of the
470 * GtkFileChooserButton as the mnemonic widget. See bug #359843.
472 if (GTK_IS_BOX (mnemonic_widget))
476 list = gtk_container_get_children (GTK_CONTAINER (mnemonic_widget));
477 if (g_list_length (list) == 2)
479 tmpl = g_list_last (list);
480 if (GTK_IS_COMBO_BOX(tmpl->data))
482 mnemonic_widget = GTK_WIDGET(tmpl->data);
488 * Handle the case where a GnomeIconEntry is specified as the
489 * mnemonic widget. use the button which is a grandchild of the
490 * GnomeIconEntry as the mnemonic widget. See bug #133967.
492 else if (GTK_IS_BOX (mnemonic_widget))
496 list = gtk_container_get_children (GTK_CONTAINER (mnemonic_widget));
497 if (g_list_length (list) == 1)
499 if (GTK_IS_ALIGNMENT (list->data))
501 GtkWidget *temp_widget;
503 temp_widget = gtk_bin_get_child (GTK_BIN (list->data));
504 if (GTK_IS_BUTTON (temp_widget))
505 mnemonic_widget = temp_widget;
507 else if (GTK_IS_HBOX (list->data))
509 GtkWidget *temp_widget;
511 temp_widget = GTK_WIDGET (list->data);
513 list = gtk_container_get_children (GTK_CONTAINER (temp_widget));
519 accessible_array[0] = gtk_widget_get_accessible (mnemonic_widget);
520 relation = atk_relation_new (accessible_array, 1,
521 ATK_RELATION_LABEL_FOR);
522 atk_relation_set_add (relation_set, relation);
524 * Unref the relation so that it is not leaked.
526 g_object_unref (relation);
533 gail_label_get_name (AtkObject *accessible)
537 g_return_val_if_fail (GAIL_IS_LABEL (accessible), NULL);
539 name = ATK_OBJECT_CLASS (gail_label_parent_class)->get_name (accessible);
545 * Get the text on the label
549 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
556 g_return_val_if_fail (GTK_IS_LABEL (widget), NULL);
558 return gtk_label_get_text (GTK_LABEL (widget));
565 atk_text_interface_init (AtkTextIface *iface)
567 iface->get_text = gail_label_get_text;
568 iface->get_character_at_offset = gail_label_get_character_at_offset;
569 iface->get_text_before_offset = gail_label_get_text_before_offset;
570 iface->get_text_at_offset = gail_label_get_text_at_offset;
571 iface->get_text_after_offset = gail_label_get_text_after_offset;
572 iface->get_character_count = gail_label_get_character_count;
573 iface->get_caret_offset = gail_label_get_caret_offset;
574 iface->set_caret_offset = gail_label_set_caret_offset;
575 iface->get_n_selections = gail_label_get_n_selections;
576 iface->get_selection = gail_label_get_selection;
577 iface->add_selection = gail_label_add_selection;
578 iface->remove_selection = gail_label_remove_selection;
579 iface->set_selection = gail_label_set_selection;
580 iface->get_character_extents = gail_label_get_character_extents;
581 iface->get_offset_at_point = gail_label_get_offset_at_point;
582 iface->get_run_attributes = gail_label_get_run_attributes;
583 iface->get_default_attributes = gail_label_get_default_attributes;
587 gail_label_get_text (AtkText *text,
594 const gchar *label_text;
596 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
598 /* State is defunct */
601 label = GTK_LABEL (widget);
603 label_text = gtk_label_get_text (label);
605 if (label_text == NULL)
609 if (GAIL_LABEL (text)->textutil == NULL)
610 gail_label_init_text_util (GAIL_LABEL (text), widget);
611 return gail_text_util_get_substring (GAIL_LABEL(text)->textutil,
617 gail_label_get_text_before_offset (AtkText *text,
619 AtkTextBoundary boundary_type,
626 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
629 /* State is defunct */
633 label = GTK_LABEL (widget);
635 return gail_text_util_get_text (GAIL_LABEL (text)->textutil,
636 gtk_label_get_layout (label), GAIL_BEFORE_OFFSET,
637 boundary_type, offset, start_offset, end_offset);
641 gail_label_get_text_at_offset (AtkText *text,
643 AtkTextBoundary boundary_type,
650 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
653 /* State is defunct */
657 label = GTK_LABEL (widget);
659 return gail_text_util_get_text (GAIL_LABEL (text)->textutil,
660 gtk_label_get_layout (label), GAIL_AT_OFFSET,
661 boundary_type, offset, start_offset, end_offset);
665 gail_label_get_text_after_offset (AtkText *text,
667 AtkTextBoundary boundary_type,
674 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
678 /* State is defunct */
683 label = GTK_LABEL (widget);
685 return gail_text_util_get_text (GAIL_LABEL (text)->textutil,
686 gtk_label_get_layout (label), GAIL_AFTER_OFFSET,
687 boundary_type, offset, start_offset, end_offset);
691 gail_label_get_character_count (AtkText *text)
696 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
698 /* State is defunct */
701 label = GTK_LABEL (widget);
702 return g_utf8_strlen (gtk_label_get_text (label), -1);
706 gail_label_get_caret_offset (AtkText *text)
708 return GAIL_LABEL (text)->cursor_position;
712 gail_label_set_caret_offset (AtkText *text,
718 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
720 /* State is defunct */
723 label = GTK_LABEL (widget);
725 if (gtk_label_get_selectable (label) &&
727 offset <= g_utf8_strlen (gtk_label_get_text (label), -1))
729 gtk_label_select_region (label, offset, offset);
737 gail_label_get_n_selections (AtkText *text)
743 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
745 /* State is defunct */
748 label = GTK_LABEL (widget);
750 if (!gtk_label_get_selectable (label))
753 if (gtk_label_get_selection_bounds (label, &start, &end))
760 gail_label_get_selection (AtkText *text,
768 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
770 /* State is defunct */
773 label = GTK_LABEL (widget);
775 /* Only let the user get the selection if one is set, and if the
776 * selection_num is 0.
778 if (!gtk_label_get_selectable( label) || selection_num != 0)
781 if (gtk_label_get_selection_bounds (label, start_pos, end_pos))
783 const gchar* label_text = gtk_label_get_text (label);
785 if (label_text == NULL)
788 return gail_text_util_get_substring (GAIL_LABEL (text)->textutil,
789 *start_pos, *end_pos);
796 gail_label_add_selection (AtkText *text,
804 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
806 /* State is defunct */
809 label = GTK_LABEL (widget);
811 if (!gtk_label_get_selectable (label))
814 if (! gtk_label_get_selection_bounds (label, &start, &end))
816 gtk_label_select_region (label, start_pos, end_pos);
824 gail_label_remove_selection (AtkText *text,
831 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
833 /* State is defunct */
836 if (selection_num != 0)
839 label = GTK_LABEL (widget);
841 if (!gtk_label_get_selectable (label))
844 if (gtk_label_get_selection_bounds (label, &start, &end))
846 gtk_label_select_region (label, 0, 0);
854 gail_label_set_selection (AtkText *text,
863 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
865 /* State is defunct */
868 if (selection_num != 0)
871 label = GTK_LABEL (widget);
873 if (!gtk_label_get_selectable (label))
876 if (gtk_label_get_selection_bounds (label, &start, &end))
878 gtk_label_select_region (label, start_pos, end_pos);
886 gail_label_get_character_extents (AtkText *text,
896 PangoRectangle char_rect;
897 const gchar *label_text;
898 gint index, x_layout, y_layout;
900 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
903 /* State is defunct */
906 label = GTK_LABEL (widget);
908 gtk_label_get_layout_offsets (label, &x_layout, &y_layout);
909 label_text = gtk_label_get_text (label);
910 index = g_utf8_offset_to_pointer (label_text, offset) - label_text;
911 pango_layout_index_to_pos (gtk_label_get_layout (label), index, &char_rect);
913 gail_misc_get_extents_from_pango_rectangle (widget, &char_rect,
914 x_layout, y_layout, x, y, width, height, coords);
918 gail_label_get_offset_at_point (AtkText *text,
925 const gchar *label_text;
926 gint index, x_layout, y_layout;
928 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
930 /* State is defunct */
932 label = GTK_LABEL (widget);
934 gtk_label_get_layout_offsets (label, &x_layout, &y_layout);
936 index = gail_misc_get_index_at_point_in_layout (widget,
937 gtk_label_get_layout (label),
938 x_layout, y_layout, x, y, coords);
939 label_text = gtk_label_get_text (label);
942 if (coords == ATK_XY_WINDOW || coords == ATK_XY_SCREEN)
943 return g_utf8_strlen (label_text, -1);
948 return g_utf8_pointer_to_offset (label_text, label_text + index);
951 static AtkAttributeSet*
952 gail_label_get_run_attributes (AtkText *text,
959 AtkAttributeSet *at_set = NULL;
960 GtkJustification justify;
961 GtkTextDirection dir;
963 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
965 /* State is defunct */
968 label = GTK_LABEL (widget);
970 /* Get values set for entire label, if any */
971 justify = gtk_label_get_justify (label);
972 if (justify != GTK_JUSTIFY_CENTER)
974 at_set = gail_misc_add_attribute (at_set,
975 ATK_TEXT_ATTR_JUSTIFICATION,
976 g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_JUSTIFICATION, justify)));
978 dir = gtk_widget_get_direction (widget);
979 if (dir == GTK_TEXT_DIR_RTL)
981 at_set = gail_misc_add_attribute (at_set,
982 ATK_TEXT_ATTR_DIRECTION,
983 g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_DIRECTION, dir)));
986 at_set = gail_misc_layout_get_run_attributes (at_set,
987 gtk_label_get_layout (label),
988 gtk_label_get_text (label),
995 static AtkAttributeSet*
996 gail_label_get_default_attributes (AtkText *text)
1000 AtkAttributeSet *at_set = NULL;
1002 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
1004 /* State is defunct */
1007 label = GTK_LABEL (widget);
1009 at_set = gail_misc_get_default_attributes (at_set,
1010 gtk_label_get_layout (label),
1016 gail_label_get_character_at_offset (AtkText *text,
1021 const gchar *string;
1024 widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
1026 /* State is defunct */
1029 label = GTK_LABEL (widget);
1030 string = gtk_label_get_text (label);
1031 if (offset >= g_utf8_strlen (string, -1))
1033 index = g_utf8_offset_to_pointer (string, offset);
1035 return g_utf8_get_char (index);