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.
22 #include "gaillabel.h"
23 #include "gailwindow.h"
24 #include <libgail-util/gailmisc.h>
26 static void gail_label_class_init (GailLabelClass *klass);
27 static void gail_label_real_initialize (AtkObject *obj,
29 static void gail_label_real_notify_gtk (GObject *obj,
31 static void gail_label_map_gtk (GtkWidget *widget,
33 static void gail_label_init_text_util (GailLabel *gail_label,
35 static void gail_label_finalize (GObject *object);
37 static void atk_text_interface_init (AtkTextIface *iface);
41 static G_CONST_RETURN gchar* gail_label_get_name (AtkObject *accessible);
42 static AtkStateSet* gail_label_ref_state_set (AtkObject *accessible);
43 static AtkRelationSet* gail_label_ref_relation_set (AtkObject *accessible);
47 static gchar* gail_label_get_text (AtkText *text,
50 static gunichar gail_label_get_character_at_offset(AtkText *text,
52 static gchar* gail_label_get_text_before_offset(AtkText *text,
54 AtkTextBoundary boundary_type,
57 static gchar* gail_label_get_text_at_offset (AtkText *text,
59 AtkTextBoundary boundary_type,
62 static gchar* gail_label_get_text_after_offset (AtkText *text,
64 AtkTextBoundary boundary_type,
67 static gint gail_label_get_character_count (AtkText *text);
68 static gint gail_label_get_caret_offset (AtkText *text);
69 static gboolean gail_label_set_caret_offset (AtkText *text,
71 static gint gail_label_get_n_selections (AtkText *text);
72 static gchar* gail_label_get_selection (AtkText *text,
76 static gboolean gail_label_add_selection (AtkText *text,
79 static gboolean gail_label_remove_selection (AtkText *text,
81 static gboolean gail_label_set_selection (AtkText *text,
85 static void gail_label_get_character_extents (AtkText *text,
92 static gint gail_label_get_offset_at_point (AtkText *text,
96 static AtkAttributeSet* gail_label_get_run_attributes
101 static AtkAttributeSet* gail_label_get_default_attributes
104 static GailWidgetClass *parent_class = NULL;
107 gail_label_get_type (void)
109 static GType type = 0;
113 static const GTypeInfo tinfo =
115 sizeof (GailLabelClass),
116 (GBaseInitFunc) NULL, /* base init */
117 (GBaseFinalizeFunc) NULL, /* base finalize */
118 (GClassInitFunc) gail_label_class_init, /* class init */
119 (GClassFinalizeFunc) NULL, /* class finalize */
120 NULL, /* class data */
121 sizeof (GailLabel), /* instance size */
122 0, /* nb preallocs */
123 (GInstanceInitFunc) NULL, /* instance init */
124 NULL /* value table */
127 static const GInterfaceInfo atk_text_info =
129 (GInterfaceInitFunc) atk_text_interface_init,
130 (GInterfaceFinalizeFunc) NULL,
134 type = g_type_register_static (GAIL_TYPE_WIDGET,
135 "GailLabel", &tinfo, 0);
136 g_type_add_interface_static (type, ATK_TYPE_TEXT,
143 gail_label_class_init (GailLabelClass *klass)
145 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
146 AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
147 GailWidgetClass *widget_class;
149 gobject_class->finalize = gail_label_finalize;
151 widget_class = (GailWidgetClass*)klass;
152 widget_class->notify_gtk = gail_label_real_notify_gtk;
154 parent_class = g_type_class_peek_parent (klass);
156 class->get_name = gail_label_get_name;
157 class->ref_state_set = gail_label_ref_state_set;
158 class->ref_relation_set = gail_label_ref_relation_set;
159 class->initialize = gail_label_real_initialize;
163 gail_label_real_initialize (AtkObject *obj,
167 GailLabel *gail_label;
169 ATK_OBJECT_CLASS (parent_class)->initialize (obj, data);
171 gail_label = GAIL_LABEL (obj);
173 gail_label->window_create_handler = 0;
174 gail_label->has_top_level = FALSE;
175 gail_label->cursor_position = 0;
176 gail_label->selection_bound = 0;
177 gail_label->textutil = NULL;
178 gail_label->label_length = 0;
180 widget = GTK_WIDGET (data);
182 if (GTK_WIDGET_MAPPED (widget))
183 gail_label_init_text_util (gail_label, widget);
185 g_signal_connect (widget,
187 G_CALLBACK (gail_label_map_gtk),
191 * Check whether ancestor of GtkLabel is a GtkButton and if so
192 * set accessible parent for GailLabel
194 while (widget != NULL)
196 widget = gtk_widget_get_parent (widget);
197 if (GTK_IS_BUTTON (widget))
199 atk_object_set_parent (obj, gtk_widget_get_accessible (widget));
204 if (GTK_IS_ACCEL_LABEL (widget))
205 obj->role = ATK_ROLE_ACCEL_LABEL;
207 obj->role = ATK_ROLE_LABEL;
211 gail_label_map_gtk (GtkWidget *widget,
214 GailLabel *gail_label;
216 gail_label = GAIL_LABEL (data);
217 gail_label_init_text_util (gail_label, widget);
221 gail_label_init_text_util (GailLabel *gail_label,
225 const gchar *label_text;
227 if (gail_label->textutil == NULL)
228 gail_label->textutil = gail_text_util_new ();
230 label = GTK_LABEL (widget);
231 label_text = gtk_label_get_text (label);
232 gail_text_util_text_setup (gail_label->textutil, label_text);
234 if (label_text == NULL)
235 gail_label->label_length = 0;
237 gail_label->label_length = g_utf8_strlen (label_text, -1);
241 gail_label_new (GtkWidget *widget)
244 AtkObject *accessible;
246 g_return_val_if_fail (GTK_IS_LABEL (widget), NULL);
248 object = g_object_new (GAIL_TYPE_LABEL, NULL);
250 accessible = ATK_OBJECT (object);
251 atk_object_initialize (accessible, widget);
257 notify_name_change (AtkObject *atk_obj)
260 GailLabel *gail_label;
264 widget = GTK_ACCESSIBLE (atk_obj)->widget;
271 gail_obj = G_OBJECT (atk_obj);
272 label = GTK_LABEL (widget);
273 gail_label = GAIL_LABEL (atk_obj);
275 if (gail_label->textutil == NULL)
279 * Check whether the label has actually changed before emitting
282 if (gail_label->textutil->buffer)
284 GtkTextIter start, end;
285 const char *new_label;
289 gtk_text_buffer_get_start_iter (gail_label->textutil->buffer, &start);
290 gtk_text_buffer_get_end_iter (gail_label->textutil->buffer, &end);
291 old_label = gtk_text_buffer_get_text (gail_label->textutil->buffer, &start, &end, FALSE);
292 new_label = gtk_label_get_text (label);
293 same = strcmp (new_label, old_label);
299 /* Create a delete text and an insert text signal */
301 g_signal_emit_by_name (gail_obj, "text_changed::delete", 0,
302 gail_label->label_length);
304 gail_label_init_text_util (gail_label, widget);
306 g_signal_emit_by_name (gail_obj, "text_changed::insert", 0,
307 gail_label->label_length);
309 if (atk_obj->name == NULL)
311 * The label has changed so notify a change in accessible-name
313 g_object_notify (gail_obj, "accessible-name");
315 g_signal_emit_by_name (gail_obj, "visible_data_changed");
319 window_created (GObject *obj,
322 g_return_if_fail (GAIL_LABEL (data));
324 notify_name_change (ATK_OBJECT (data));
328 gail_label_real_notify_gtk (GObject *obj,
331 GtkWidget *widget = GTK_WIDGET (obj);
332 AtkObject* atk_obj = gtk_widget_get_accessible (widget);
334 GailLabel *gail_label;
336 AtkObject *top_level;
339 gail_label = GAIL_LABEL (atk_obj);
341 if (strcmp (pspec->name, "label") == 0)
344 * We may get a label change for a label which is not attached to an
345 * application. We wait until the toplevel window is created before
346 * emitting the notification.
348 * This happens when [Ctrl+]Alt+Tab is pressed in metacity
350 if (!gail_label->has_top_level)
356 top_level = temp_obj;
357 temp_obj = atk_object_get_parent (top_level);
359 if (atk_object_get_role (top_level) != ATK_ROLE_APPLICATION)
361 if (gail_label->window_create_handler == 0 &&
362 GAIL_IS_WINDOW (top_level))
363 gail_label->window_create_handler = g_signal_connect_after (top_level, "create", G_CALLBACK (window_created), atk_obj);
366 gail_label->has_top_level = TRUE;
368 if (gail_label->has_top_level)
369 notify_name_change (atk_obj);
371 else if (strcmp (pspec->name, "cursor-position") == 0)
374 gboolean text_caret_moved = FALSE;
375 gboolean selection_changed = FALSE;
376 gboolean is_start = TRUE;
378 gail_obj = G_OBJECT (atk_obj);
379 label = GTK_LABEL (widget);
381 if (gtk_label_get_selection_bounds (label, &start, &end))
383 if (start != gail_label->cursor_position ||
384 end != gail_label->selection_bound)
386 if (end != gail_label->selection_bound)
388 gail_label->selection_bound = end;
389 gail_label->cursor_position = start;
390 text_caret_moved = TRUE;
392 selection_changed = TRUE;
397 if (gail_label->cursor_position != gail_label->selection_bound)
398 selection_changed = TRUE;
399 if (gtk_label_get_selectable (label))
401 if (gail_label->cursor_position != -1 && start != gail_label->cursor_position)
402 text_caret_moved = TRUE;
403 if (gail_label->selection_bound != -1 && end != gail_label->selection_bound)
405 text_caret_moved = TRUE;
408 gail_label->cursor_position = start;
409 gail_label->selection_bound = end;
413 /* GtkLabel has become non selectable */
415 gail_label->cursor_position = 0;
416 gail_label->selection_bound = 0;
417 text_caret_moved = TRUE;
421 if (text_caret_moved)
422 g_signal_emit_by_name (gail_obj, "text_caret_moved",
423 is_start ? gail_label->cursor_position : gail_label->selection_bound);
424 if (selection_changed)
425 g_signal_emit_by_name (gail_obj, "text_selection_changed");
429 parent_class->notify_gtk (obj, pspec);
433 gail_label_finalize (GObject *object)
435 GailLabel *label = GAIL_LABEL (object);
438 g_object_unref (label->textutil);
439 G_OBJECT_CLASS (parent_class)->finalize (object);
446 gail_label_ref_state_set (AtkObject *accessible)
448 AtkStateSet *state_set;
451 state_set = ATK_OBJECT_CLASS (parent_class)->ref_state_set (accessible);
452 widget = GTK_ACCESSIBLE (accessible)->widget;
457 atk_state_set_add_state (state_set, ATK_STATE_MULTI_LINE);
463 gail_label_ref_relation_set (AtkObject *obj)
466 AtkRelationSet *relation_set;
468 g_return_val_if_fail (GAIL_IS_LABEL (obj), NULL);
470 widget = GTK_ACCESSIBLE (obj)->widget;
477 relation_set = ATK_OBJECT_CLASS (parent_class)->ref_relation_set (obj);
479 if (!atk_relation_set_contains (relation_set, ATK_RELATION_LABEL_FOR))
482 * Get the mnemonic widget
484 * The relation set is not updated if the mnemonic widget is changed
486 GtkWidget *mnemonic_widget = GTK_LABEL (widget)->mnemonic_widget;
490 AtkObject *accessible_array[1];
491 AtkRelation* relation;
493 if (!GTK_WIDGET_CAN_FOCUS (mnemonic_widget))
496 * Handle the case where a GtkFileChooserButton is specified as the
497 * mnemonic widget. use the combobox which is a child of the
498 * GtkFileChooserButton as the mnemonic widget. See bug #359843.
500 if (GTK_IS_BOX (mnemonic_widget))
504 list = gtk_container_get_children (GTK_CONTAINER (mnemonic_widget));
505 if (g_list_length (list) == 2)
507 tmpl = g_list_last (list);
508 if (GTK_IS_COMBO_BOX(tmpl->data))
510 mnemonic_widget = GTK_WIDGET(tmpl->data);
516 * Handle the case where a GnomeIconEntry is specified as the
517 * mnemonic widget. use the button which is a grandchild of the
518 * GnomeIconEntry as the mnemonic widget. See bug #133967.
520 else if (GTK_IS_BOX (mnemonic_widget))
524 list = gtk_container_get_children (GTK_CONTAINER (mnemonic_widget));
525 if (g_list_length (list) == 1)
527 if (GTK_IS_ALIGNMENT (list->data))
529 GtkWidget *temp_widget;
531 temp_widget = GTK_BIN (list->data)->child;
532 if (GTK_IS_BUTTON (temp_widget))
533 mnemonic_widget = temp_widget;
535 else if (GTK_IS_HBOX (list->data))
537 GtkWidget *temp_widget;
539 temp_widget = GTK_WIDGET (list->data);
541 list = gtk_container_get_children (GTK_CONTAINER (temp_widget));
542 if (GTK_IS_COMBO (list->data))
544 mnemonic_widget = GTK_WIDGET (list->data);
551 accessible_array[0] = gtk_widget_get_accessible (mnemonic_widget);
552 relation = atk_relation_new (accessible_array, 1,
553 ATK_RELATION_LABEL_FOR);
554 atk_relation_set_add (relation_set, relation);
556 * Unref the relation so that it is not leaked.
558 g_object_unref (relation);
564 static G_CONST_RETURN gchar*
565 gail_label_get_name (AtkObject *accessible)
567 G_CONST_RETURN gchar *name;
569 g_return_val_if_fail (GAIL_IS_LABEL (accessible), NULL);
571 name = ATK_OBJECT_CLASS (parent_class)->get_name (accessible);
577 * Get the text on the label
581 widget = GTK_ACCESSIBLE (accessible)->widget;
588 g_return_val_if_fail (GTK_IS_LABEL (widget), NULL);
590 return gtk_label_get_text (GTK_LABEL (widget));
597 atk_text_interface_init (AtkTextIface *iface)
599 g_return_if_fail (iface != NULL);
600 iface->get_text = gail_label_get_text;
601 iface->get_character_at_offset = gail_label_get_character_at_offset;
602 iface->get_text_before_offset = gail_label_get_text_before_offset;
603 iface->get_text_at_offset = gail_label_get_text_at_offset;
604 iface->get_text_after_offset = gail_label_get_text_after_offset;
605 iface->get_character_count = gail_label_get_character_count;
606 iface->get_caret_offset = gail_label_get_caret_offset;
607 iface->set_caret_offset = gail_label_set_caret_offset;
608 iface->get_n_selections = gail_label_get_n_selections;
609 iface->get_selection = gail_label_get_selection;
610 iface->add_selection = gail_label_add_selection;
611 iface->remove_selection = gail_label_remove_selection;
612 iface->set_selection = gail_label_set_selection;
613 iface->get_character_extents = gail_label_get_character_extents;
614 iface->get_offset_at_point = gail_label_get_offset_at_point;
615 iface->get_run_attributes = gail_label_get_run_attributes;
616 iface->get_default_attributes = gail_label_get_default_attributes;
620 gail_label_get_text (AtkText *text,
627 const gchar *label_text;
629 widget = GTK_ACCESSIBLE (text)->widget;
631 /* State is defunct */
634 label = GTK_LABEL (widget);
636 label_text = gtk_label_get_text (label);
638 if (label_text == NULL)
642 if (GAIL_LABEL (text)->textutil == NULL)
643 gail_label_init_text_util (GAIL_LABEL (text), widget);
644 return gail_text_util_get_substring (GAIL_LABEL(text)->textutil,
650 gail_label_get_text_before_offset (AtkText *text,
652 AtkTextBoundary boundary_type,
659 widget = GTK_ACCESSIBLE (text)->widget;
662 /* State is defunct */
666 label = GTK_LABEL (widget);
668 return gail_text_util_get_text (GAIL_LABEL (text)->textutil,
669 gtk_label_get_layout (label), GAIL_BEFORE_OFFSET,
670 boundary_type, offset, start_offset, end_offset);
674 gail_label_get_text_at_offset (AtkText *text,
676 AtkTextBoundary boundary_type,
683 widget = GTK_ACCESSIBLE (text)->widget;
686 /* State is defunct */
690 label = GTK_LABEL (widget);
692 return gail_text_util_get_text (GAIL_LABEL (text)->textutil,
693 gtk_label_get_layout (label), GAIL_AT_OFFSET,
694 boundary_type, offset, start_offset, end_offset);
698 gail_label_get_text_after_offset (AtkText *text,
700 AtkTextBoundary boundary_type,
707 widget = GTK_ACCESSIBLE (text)->widget;
711 /* State is defunct */
716 label = GTK_LABEL (widget);
718 return gail_text_util_get_text (GAIL_LABEL (text)->textutil,
719 gtk_label_get_layout (label), GAIL_AFTER_OFFSET,
720 boundary_type, offset, start_offset, end_offset);
724 gail_label_get_character_count (AtkText *text)
729 widget = GTK_ACCESSIBLE (text)->widget;
731 /* State is defunct */
734 label = GTK_LABEL (widget);
735 return g_utf8_strlen (gtk_label_get_text (label), -1);
739 gail_label_get_caret_offset (AtkText *text)
741 return GAIL_LABEL (text)->cursor_position;
745 gail_label_set_caret_offset (AtkText *text,
751 widget = GTK_ACCESSIBLE (text)->widget;
753 /* State is defunct */
756 label = GTK_LABEL (widget);
758 if (gtk_label_get_selectable (label) &&
760 offset <= g_utf8_strlen (label->text, -1))
762 gtk_label_select_region (label, offset, offset);
770 gail_label_get_n_selections (AtkText *text)
776 widget = GTK_ACCESSIBLE (text)->widget;
778 /* State is defunct */
781 label = GTK_LABEL (widget);
783 if (!gtk_label_get_selectable (label))
786 if (gtk_label_get_selection_bounds (label, &start, &end))
793 gail_label_get_selection (AtkText *text,
801 widget = GTK_ACCESSIBLE (text)->widget;
803 /* State is defunct */
806 label = GTK_LABEL (widget);
808 /* Only let the user get the selection if one is set, and if the
809 * selection_num is 0.
811 if (!gtk_label_get_selectable( label) || selection_num != 0)
814 if (gtk_label_get_selection_bounds (label, start_pos, end_pos))
816 const gchar* label_text = gtk_label_get_text (label);
818 if (label_text == NULL)
821 return gail_text_util_get_substring (GAIL_LABEL (text)->textutil,
822 *start_pos, *end_pos);
829 gail_label_add_selection (AtkText *text,
837 widget = GTK_ACCESSIBLE (text)->widget;
839 /* State is defunct */
842 label = GTK_LABEL (widget);
844 if (!gtk_label_get_selectable (label))
847 if (! gtk_label_get_selection_bounds (label, &start, &end))
849 gtk_label_select_region (label, start_pos, end_pos);
857 gail_label_remove_selection (AtkText *text,
864 widget = GTK_ACCESSIBLE (text)->widget;
866 /* State is defunct */
869 if (selection_num != 0)
872 label = GTK_LABEL (widget);
874 if (!gtk_label_get_selectable (label))
877 if (gtk_label_get_selection_bounds (label, &start, &end))
879 gtk_label_select_region (label, 0, 0);
887 gail_label_set_selection (AtkText *text,
896 widget = GTK_ACCESSIBLE (text)->widget;
898 /* State is defunct */
901 if (selection_num != 0)
904 label = GTK_LABEL (widget);
906 if (!gtk_label_get_selectable (label))
909 if (gtk_label_get_selection_bounds (label, &start, &end))
911 gtk_label_select_region (label, start_pos, end_pos);
919 gail_label_get_character_extents (AtkText *text,
929 PangoRectangle char_rect;
930 gint index, x_layout, y_layout;
932 widget = GTK_ACCESSIBLE (text)->widget;
935 /* State is defunct */
938 label = GTK_LABEL (widget);
940 gtk_label_get_layout_offsets (label, &x_layout, &y_layout);
941 index = g_utf8_offset_to_pointer (label->text, offset) - label->text;
942 pango_layout_index_to_pos (gtk_label_get_layout (label), index, &char_rect);
944 gail_misc_get_extents_from_pango_rectangle (widget, &char_rect,
945 x_layout, y_layout, x, y, width, height, coords);
949 gail_label_get_offset_at_point (AtkText *text,
956 gint index, x_layout, y_layout;
958 widget = GTK_ACCESSIBLE (text)->widget;
960 /* State is defunct */
962 label = GTK_LABEL (widget);
964 gtk_label_get_layout_offsets (label, &x_layout, &y_layout);
966 index = gail_misc_get_index_at_point_in_layout (widget,
967 gtk_label_get_layout (label),
968 x_layout, y_layout, x, y, coords);
971 if (coords == ATK_XY_WINDOW || coords == ATK_XY_SCREEN)
972 return g_utf8_strlen (label->text, -1);
977 return g_utf8_pointer_to_offset (label->text, label->text + index);
980 static AtkAttributeSet*
981 gail_label_get_run_attributes (AtkText *text,
988 AtkAttributeSet *at_set = NULL;
989 GtkJustification justify;
990 GtkTextDirection dir;
992 widget = GTK_ACCESSIBLE (text)->widget;
994 /* State is defunct */
997 label = GTK_LABEL (widget);
999 /* Get values set for entire label, if any */
1000 justify = gtk_label_get_justify (label);
1001 if (justify != GTK_JUSTIFY_CENTER)
1003 at_set = gail_misc_add_attribute (at_set,
1004 ATK_TEXT_ATTR_JUSTIFICATION,
1005 g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_JUSTIFICATION, justify)));
1007 dir = gtk_widget_get_direction (widget);
1008 if (dir == GTK_TEXT_DIR_RTL)
1010 at_set = gail_misc_add_attribute (at_set,
1011 ATK_TEXT_ATTR_DIRECTION,
1012 g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_DIRECTION, dir)));
1015 at_set = gail_misc_layout_get_run_attributes (at_set,
1016 gtk_label_get_layout (label),
1024 static AtkAttributeSet*
1025 gail_label_get_default_attributes (AtkText *text)
1029 AtkAttributeSet *at_set = NULL;
1031 widget = GTK_ACCESSIBLE (text)->widget;
1033 /* State is defunct */
1036 label = GTK_LABEL (widget);
1038 at_set = gail_misc_get_default_attributes (at_set,
1039 gtk_label_get_layout (label),
1045 gail_label_get_character_at_offset (AtkText *text,
1050 const gchar *string;
1053 widget = GTK_ACCESSIBLE (text)->widget;
1055 /* State is defunct */
1058 label = GTK_LABEL (widget);
1059 string = gtk_label_get_text (label);
1060 if (offset >= g_utf8_strlen (string, -1))
1062 index = g_utf8_offset_to_pointer (string, offset);
1064 return g_utf8_get_char (index);