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.
24 #include "gaillabel.h"
25 #include "gailwindow.h"
26 #include <libgail-util/gailmisc.h>
28 static void gail_label_class_init (GailLabelClass *klass);
29 static void gail_label_init (GailLabel *label);
30 static void gail_label_real_initialize (AtkObject *obj,
32 static void gail_label_real_notify_gtk (GObject *obj,
34 static void gail_label_map_gtk (GtkWidget *widget,
36 static void gail_label_init_text_util (GailLabel *gail_label,
38 static void gail_label_finalize (GObject *object);
40 static void atk_text_interface_init (AtkTextIface *iface);
44 static G_CONST_RETURN gchar* gail_label_get_name (AtkObject *accessible);
45 static AtkStateSet* gail_label_ref_state_set (AtkObject *accessible);
46 static AtkRelationSet* gail_label_ref_relation_set (AtkObject *accessible);
50 static gchar* gail_label_get_text (AtkText *text,
53 static gunichar gail_label_get_character_at_offset(AtkText *text,
55 static gchar* gail_label_get_text_before_offset(AtkText *text,
57 AtkTextBoundary boundary_type,
60 static gchar* gail_label_get_text_at_offset (AtkText *text,
62 AtkTextBoundary boundary_type,
65 static gchar* gail_label_get_text_after_offset (AtkText *text,
67 AtkTextBoundary boundary_type,
70 static gint gail_label_get_character_count (AtkText *text);
71 static gint gail_label_get_caret_offset (AtkText *text);
72 static gboolean gail_label_set_caret_offset (AtkText *text,
74 static gint gail_label_get_n_selections (AtkText *text);
75 static gchar* gail_label_get_selection (AtkText *text,
79 static gboolean gail_label_add_selection (AtkText *text,
82 static gboolean gail_label_remove_selection (AtkText *text,
84 static gboolean gail_label_set_selection (AtkText *text,
88 static void gail_label_get_character_extents (AtkText *text,
95 static gint gail_label_get_offset_at_point (AtkText *text,
99 static AtkAttributeSet* gail_label_get_run_attributes
104 static AtkAttributeSet* gail_label_get_default_attributes
107 G_DEFINE_TYPE_WITH_CODE (GailLabel, gail_label, GAIL_TYPE_WIDGET,
108 G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, atk_text_interface_init))
111 gail_label_class_init (GailLabelClass *klass)
113 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
114 AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
115 GailWidgetClass *widget_class;
117 gobject_class->finalize = gail_label_finalize;
119 widget_class = (GailWidgetClass*)klass;
120 widget_class->notify_gtk = gail_label_real_notify_gtk;
122 class->get_name = gail_label_get_name;
123 class->ref_state_set = gail_label_ref_state_set;
124 class->ref_relation_set = gail_label_ref_relation_set;
125 class->initialize = gail_label_real_initialize;
129 gail_label_init (GailLabel *label)
134 gail_label_real_initialize (AtkObject *obj,
138 GailLabel *gail_label;
140 ATK_OBJECT_CLASS (gail_label_parent_class)->initialize (obj, data);
142 gail_label = GAIL_LABEL (obj);
144 gail_label->window_create_handler = 0;
145 gail_label->has_top_level = FALSE;
146 gail_label->cursor_position = 0;
147 gail_label->selection_bound = 0;
148 gail_label->textutil = NULL;
149 gail_label->label_length = 0;
151 widget = GTK_WIDGET (data);
153 if (GTK_WIDGET_MAPPED (widget))
154 gail_label_init_text_util (gail_label, widget);
156 g_signal_connect (widget,
158 G_CALLBACK (gail_label_map_gtk),
162 * Check whether ancestor of GtkLabel is a GtkButton and if so
163 * set accessible parent for GailLabel
165 while (widget != NULL)
167 widget = gtk_widget_get_parent (widget);
168 if (GTK_IS_BUTTON (widget))
170 atk_object_set_parent (obj, gtk_widget_get_accessible (widget));
175 if (GTK_IS_ACCEL_LABEL (widget))
176 obj->role = ATK_ROLE_ACCEL_LABEL;
178 obj->role = ATK_ROLE_LABEL;
182 gail_label_map_gtk (GtkWidget *widget,
185 GailLabel *gail_label;
187 gail_label = GAIL_LABEL (data);
188 gail_label_init_text_util (gail_label, widget);
192 gail_label_init_text_util (GailLabel *gail_label,
196 const gchar *label_text;
198 if (gail_label->textutil == NULL)
199 gail_label->textutil = gail_text_util_new ();
201 label = GTK_LABEL (widget);
202 label_text = gtk_label_get_text (label);
203 gail_text_util_text_setup (gail_label->textutil, label_text);
205 if (label_text == NULL)
206 gail_label->label_length = 0;
208 gail_label->label_length = g_utf8_strlen (label_text, -1);
212 notify_name_change (AtkObject *atk_obj)
215 GailLabel *gail_label;
219 widget = GTK_ACCESSIBLE (atk_obj)->widget;
226 gail_obj = G_OBJECT (atk_obj);
227 label = GTK_LABEL (widget);
228 gail_label = GAIL_LABEL (atk_obj);
230 if (gail_label->textutil == NULL)
234 * Check whether the label has actually changed before emitting
237 if (gail_label->textutil->buffer)
239 GtkTextIter start, end;
240 const char *new_label;
244 gtk_text_buffer_get_start_iter (gail_label->textutil->buffer, &start);
245 gtk_text_buffer_get_end_iter (gail_label->textutil->buffer, &end);
246 old_label = gtk_text_buffer_get_text (gail_label->textutil->buffer, &start, &end, FALSE);
247 new_label = gtk_label_get_text (label);
248 same = strcmp (new_label, old_label);
254 /* Create a delete text and an insert text signal */
256 g_signal_emit_by_name (gail_obj, "text_changed::delete", 0,
257 gail_label->label_length);
259 gail_label_init_text_util (gail_label, widget);
261 g_signal_emit_by_name (gail_obj, "text_changed::insert", 0,
262 gail_label->label_length);
264 if (atk_obj->name == NULL)
266 * The label has changed so notify a change in accessible-name
268 g_object_notify (gail_obj, "accessible-name");
270 g_signal_emit_by_name (gail_obj, "visible_data_changed");
274 window_created (GObject *obj,
277 g_return_if_fail (GAIL_LABEL (data));
279 notify_name_change (ATK_OBJECT (data));
283 gail_label_real_notify_gtk (GObject *obj,
286 GtkWidget *widget = GTK_WIDGET (obj);
287 AtkObject* atk_obj = gtk_widget_get_accessible (widget);
289 GailLabel *gail_label;
291 AtkObject *top_level;
294 gail_label = GAIL_LABEL (atk_obj);
296 if (strcmp (pspec->name, "label") == 0)
299 * We may get a label change for a label which is not attached to an
300 * application. We wait until the toplevel window is created before
301 * emitting the notification.
303 * This happens when [Ctrl+]Alt+Tab is pressed in metacity
305 if (!gail_label->has_top_level)
311 top_level = temp_obj;
312 temp_obj = atk_object_get_parent (top_level);
314 if (atk_object_get_role (top_level) != ATK_ROLE_APPLICATION)
316 if (gail_label->window_create_handler == 0 &&
317 GAIL_IS_WINDOW (top_level))
318 gail_label->window_create_handler = g_signal_connect_after (top_level, "create", G_CALLBACK (window_created), atk_obj);
321 gail_label->has_top_level = TRUE;
323 if (gail_label->has_top_level)
324 notify_name_change (atk_obj);
326 else if (strcmp (pspec->name, "cursor-position") == 0)
328 gint start, end, tmp;
329 gboolean text_caret_moved = FALSE;
330 gboolean selection_changed = FALSE;
332 gail_obj = G_OBJECT (atk_obj);
333 label = GTK_LABEL (widget);
335 if (gail_label->selection_bound != -1 && gail_label->selection_bound < gail_label->cursor_position)
337 tmp = gail_label->selection_bound;
338 gail_label->selection_bound = gail_label->cursor_position;
339 gail_label->cursor_position = tmp;
342 if (gtk_label_get_selection_bounds (label, &start, &end))
344 if (start != gail_label->cursor_position ||
345 end != gail_label->selection_bound)
347 if (end != gail_label->selection_bound)
349 gail_label->selection_bound = start;
350 gail_label->cursor_position = end;
354 gail_label->selection_bound = end;
355 gail_label->cursor_position = start;
357 text_caret_moved = TRUE;
359 selection_changed = TRUE;
364 if (gail_label->cursor_position != gail_label->selection_bound)
365 selection_changed = TRUE;
366 if (gtk_label_get_selectable (label))
368 if (gail_label->cursor_position != -1 && start != gail_label->cursor_position)
369 text_caret_moved = TRUE;
370 if (gail_label->selection_bound != -1 && end != gail_label->selection_bound)
372 text_caret_moved = TRUE;
373 gail_label->cursor_position = end;
374 gail_label->selection_bound = start;
378 gail_label->cursor_position = start;
379 gail_label->selection_bound = end;
384 /* GtkLabel has become non selectable */
386 gail_label->cursor_position = 0;
387 gail_label->selection_bound = 0;
388 text_caret_moved = TRUE;
392 if (text_caret_moved)
393 g_signal_emit_by_name (gail_obj, "text_caret_moved",
394 gail_label->cursor_position);
395 if (selection_changed)
396 g_signal_emit_by_name (gail_obj, "text_selection_changed");
400 GAIL_WIDGET_CLASS (gail_label_parent_class)->notify_gtk (obj, pspec);
404 gail_label_finalize (GObject *object)
406 GailLabel *label = GAIL_LABEL (object);
409 g_object_unref (label->textutil);
410 G_OBJECT_CLASS (gail_label_parent_class)->finalize (object);
417 gail_label_ref_state_set (AtkObject *accessible)
419 AtkStateSet *state_set;
422 state_set = ATK_OBJECT_CLASS (gail_label_parent_class)->ref_state_set (accessible);
423 widget = GTK_ACCESSIBLE (accessible)->widget;
428 atk_state_set_add_state (state_set, ATK_STATE_MULTI_LINE);
434 gail_label_ref_relation_set (AtkObject *obj)
437 AtkRelationSet *relation_set;
439 g_return_val_if_fail (GAIL_IS_LABEL (obj), NULL);
441 widget = GTK_ACCESSIBLE (obj)->widget;
448 relation_set = ATK_OBJECT_CLASS (gail_label_parent_class)->ref_relation_set (obj);
450 if (!atk_relation_set_contains (relation_set, ATK_RELATION_LABEL_FOR))
453 * Get the mnemonic widget
455 * The relation set is not updated if the mnemonic widget is changed
457 GtkWidget *mnemonic_widget = GTK_LABEL (widget)->mnemonic_widget;
461 AtkObject *accessible_array[1];
462 AtkRelation* relation;
464 if (!GTK_WIDGET_CAN_FOCUS (mnemonic_widget))
467 * Handle the case where a GtkFileChooserButton is specified as the
468 * mnemonic widget. use the combobox which is a child of the
469 * GtkFileChooserButton as the mnemonic widget. See bug #359843.
471 if (GTK_IS_BOX (mnemonic_widget))
475 list = gtk_container_get_children (GTK_CONTAINER (mnemonic_widget));
476 if (g_list_length (list) == 2)
478 tmpl = g_list_last (list);
479 if (GTK_IS_COMBO_BOX(tmpl->data))
481 mnemonic_widget = GTK_WIDGET(tmpl->data);
487 * Handle the case where a GnomeIconEntry is specified as the
488 * mnemonic widget. use the button which is a grandchild of the
489 * GnomeIconEntry as the mnemonic widget. See bug #133967.
491 else if (GTK_IS_BOX (mnemonic_widget))
495 list = gtk_container_get_children (GTK_CONTAINER (mnemonic_widget));
496 if (g_list_length (list) == 1)
498 if (GTK_IS_ALIGNMENT (list->data))
500 GtkWidget *temp_widget;
502 temp_widget = GTK_BIN (list->data)->child;
503 if (GTK_IS_BUTTON (temp_widget))
504 mnemonic_widget = temp_widget;
506 else if (GTK_IS_HBOX (list->data))
508 GtkWidget *temp_widget;
510 temp_widget = GTK_WIDGET (list->data);
512 list = gtk_container_get_children (GTK_CONTAINER (temp_widget));
513 if (GTK_IS_COMBO (list->data))
515 mnemonic_widget = GTK_WIDGET (list->data);
522 accessible_array[0] = gtk_widget_get_accessible (mnemonic_widget);
523 relation = atk_relation_new (accessible_array, 1,
524 ATK_RELATION_LABEL_FOR);
525 atk_relation_set_add (relation_set, relation);
527 * Unref the relation so that it is not leaked.
529 g_object_unref (relation);
535 static G_CONST_RETURN gchar*
536 gail_label_get_name (AtkObject *accessible)
538 G_CONST_RETURN gchar *name;
540 g_return_val_if_fail (GAIL_IS_LABEL (accessible), NULL);
542 name = ATK_OBJECT_CLASS (gail_label_parent_class)->get_name (accessible);
548 * Get the text on the label
552 widget = GTK_ACCESSIBLE (accessible)->widget;
559 g_return_val_if_fail (GTK_IS_LABEL (widget), NULL);
561 return gtk_label_get_text (GTK_LABEL (widget));
568 atk_text_interface_init (AtkTextIface *iface)
570 iface->get_text = gail_label_get_text;
571 iface->get_character_at_offset = gail_label_get_character_at_offset;
572 iface->get_text_before_offset = gail_label_get_text_before_offset;
573 iface->get_text_at_offset = gail_label_get_text_at_offset;
574 iface->get_text_after_offset = gail_label_get_text_after_offset;
575 iface->get_character_count = gail_label_get_character_count;
576 iface->get_caret_offset = gail_label_get_caret_offset;
577 iface->set_caret_offset = gail_label_set_caret_offset;
578 iface->get_n_selections = gail_label_get_n_selections;
579 iface->get_selection = gail_label_get_selection;
580 iface->add_selection = gail_label_add_selection;
581 iface->remove_selection = gail_label_remove_selection;
582 iface->set_selection = gail_label_set_selection;
583 iface->get_character_extents = gail_label_get_character_extents;
584 iface->get_offset_at_point = gail_label_get_offset_at_point;
585 iface->get_run_attributes = gail_label_get_run_attributes;
586 iface->get_default_attributes = gail_label_get_default_attributes;
590 gail_label_get_text (AtkText *text,
597 const gchar *label_text;
599 widget = GTK_ACCESSIBLE (text)->widget;
601 /* State is defunct */
604 label = GTK_LABEL (widget);
606 label_text = gtk_label_get_text (label);
608 if (label_text == NULL)
612 if (GAIL_LABEL (text)->textutil == NULL)
613 gail_label_init_text_util (GAIL_LABEL (text), widget);
614 return gail_text_util_get_substring (GAIL_LABEL(text)->textutil,
620 gail_label_get_text_before_offset (AtkText *text,
622 AtkTextBoundary boundary_type,
629 widget = GTK_ACCESSIBLE (text)->widget;
632 /* State is defunct */
636 label = GTK_LABEL (widget);
638 return gail_text_util_get_text (GAIL_LABEL (text)->textutil,
639 gtk_label_get_layout (label), GAIL_BEFORE_OFFSET,
640 boundary_type, offset, start_offset, end_offset);
644 gail_label_get_text_at_offset (AtkText *text,
646 AtkTextBoundary boundary_type,
653 widget = GTK_ACCESSIBLE (text)->widget;
656 /* State is defunct */
660 label = GTK_LABEL (widget);
662 return gail_text_util_get_text (GAIL_LABEL (text)->textutil,
663 gtk_label_get_layout (label), GAIL_AT_OFFSET,
664 boundary_type, offset, start_offset, end_offset);
668 gail_label_get_text_after_offset (AtkText *text,
670 AtkTextBoundary boundary_type,
677 widget = GTK_ACCESSIBLE (text)->widget;
681 /* State is defunct */
686 label = GTK_LABEL (widget);
688 return gail_text_util_get_text (GAIL_LABEL (text)->textutil,
689 gtk_label_get_layout (label), GAIL_AFTER_OFFSET,
690 boundary_type, offset, start_offset, end_offset);
694 gail_label_get_character_count (AtkText *text)
699 widget = GTK_ACCESSIBLE (text)->widget;
701 /* State is defunct */
704 label = GTK_LABEL (widget);
705 return g_utf8_strlen (gtk_label_get_text (label), -1);
709 gail_label_get_caret_offset (AtkText *text)
711 return GAIL_LABEL (text)->cursor_position;
715 gail_label_set_caret_offset (AtkText *text,
721 widget = GTK_ACCESSIBLE (text)->widget;
723 /* State is defunct */
726 label = GTK_LABEL (widget);
728 if (gtk_label_get_selectable (label) &&
730 offset <= g_utf8_strlen (label->text, -1))
732 gtk_label_select_region (label, offset, offset);
740 gail_label_get_n_selections (AtkText *text)
746 widget = GTK_ACCESSIBLE (text)->widget;
748 /* State is defunct */
751 label = GTK_LABEL (widget);
753 if (!gtk_label_get_selectable (label))
756 if (gtk_label_get_selection_bounds (label, &start, &end))
763 gail_label_get_selection (AtkText *text,
771 widget = GTK_ACCESSIBLE (text)->widget;
773 /* State is defunct */
776 label = GTK_LABEL (widget);
778 /* Only let the user get the selection if one is set, and if the
779 * selection_num is 0.
781 if (!gtk_label_get_selectable( label) || selection_num != 0)
784 if (gtk_label_get_selection_bounds (label, start_pos, end_pos))
786 const gchar* label_text = gtk_label_get_text (label);
788 if (label_text == NULL)
791 return gail_text_util_get_substring (GAIL_LABEL (text)->textutil,
792 *start_pos, *end_pos);
799 gail_label_add_selection (AtkText *text,
807 widget = GTK_ACCESSIBLE (text)->widget;
809 /* State is defunct */
812 label = GTK_LABEL (widget);
814 if (!gtk_label_get_selectable (label))
817 if (! gtk_label_get_selection_bounds (label, &start, &end))
819 gtk_label_select_region (label, start_pos, end_pos);
827 gail_label_remove_selection (AtkText *text,
834 widget = GTK_ACCESSIBLE (text)->widget;
836 /* State is defunct */
839 if (selection_num != 0)
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, 0, 0);
857 gail_label_set_selection (AtkText *text,
866 widget = GTK_ACCESSIBLE (text)->widget;
868 /* State is defunct */
871 if (selection_num != 0)
874 label = GTK_LABEL (widget);
876 if (!gtk_label_get_selectable (label))
879 if (gtk_label_get_selection_bounds (label, &start, &end))
881 gtk_label_select_region (label, start_pos, end_pos);
889 gail_label_get_character_extents (AtkText *text,
899 PangoRectangle char_rect;
900 gint index, x_layout, y_layout;
902 widget = GTK_ACCESSIBLE (text)->widget;
905 /* State is defunct */
908 label = GTK_LABEL (widget);
910 gtk_label_get_layout_offsets (label, &x_layout, &y_layout);
911 index = g_utf8_offset_to_pointer (label->text, offset) - label->text;
912 pango_layout_index_to_pos (gtk_label_get_layout (label), index, &char_rect);
914 gail_misc_get_extents_from_pango_rectangle (widget, &char_rect,
915 x_layout, y_layout, x, y, width, height, coords);
919 gail_label_get_offset_at_point (AtkText *text,
926 gint index, x_layout, y_layout;
928 widget = GTK_ACCESSIBLE (text)->widget;
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);
941 if (coords == ATK_XY_WINDOW || coords == ATK_XY_SCREEN)
942 return g_utf8_strlen (label->text, -1);
947 return g_utf8_pointer_to_offset (label->text, label->text + index);
950 static AtkAttributeSet*
951 gail_label_get_run_attributes (AtkText *text,
958 AtkAttributeSet *at_set = NULL;
959 GtkJustification justify;
960 GtkTextDirection dir;
962 widget = GTK_ACCESSIBLE (text)->widget;
964 /* State is defunct */
967 label = GTK_LABEL (widget);
969 /* Get values set for entire label, if any */
970 justify = gtk_label_get_justify (label);
971 if (justify != GTK_JUSTIFY_CENTER)
973 at_set = gail_misc_add_attribute (at_set,
974 ATK_TEXT_ATTR_JUSTIFICATION,
975 g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_JUSTIFICATION, justify)));
977 dir = gtk_widget_get_direction (widget);
978 if (dir == GTK_TEXT_DIR_RTL)
980 at_set = gail_misc_add_attribute (at_set,
981 ATK_TEXT_ATTR_DIRECTION,
982 g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_DIRECTION, dir)));
985 at_set = gail_misc_layout_get_run_attributes (at_set,
986 gtk_label_get_layout (label),
994 static AtkAttributeSet*
995 gail_label_get_default_attributes (AtkText *text)
999 AtkAttributeSet *at_set = NULL;
1001 widget = GTK_ACCESSIBLE (text)->widget;
1003 /* State is defunct */
1006 label = GTK_LABEL (widget);
1008 at_set = gail_misc_get_default_attributes (at_set,
1009 gtk_label_get_layout (label),
1015 gail_label_get_character_at_offset (AtkText *text,
1020 const gchar *string;
1023 widget = GTK_ACCESSIBLE (text)->widget;
1025 /* State is defunct */
1028 label = GTK_LABEL (widget);
1029 string = gtk_label_get_text (label);
1030 if (offset >= g_utf8_strlen (string, -1))
1032 index = g_utf8_offset_to_pointer (string, offset);
1034 return g_utf8_get_char (index);