]> Pileus Git - ~andy/gtk/blob - gtk/a11y/gtklabelaccessible.c
Port GtkLabelAccessible from GailTextUtil to GtkPango api
[~andy/gtk] / gtk / a11y / gtklabelaccessible.c
1 /* GAIL - The GNOME Accessibility Enabling Library
2  * Copyright 2001, 2002, 2003 Sun Microsystems Inc.
3  *
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.
8  *
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.
13  *
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.
18  */
19
20 #include "config.h"
21
22 #include <string.h>
23
24 #include <gtk/gtk.h>
25 #include <gtk/gtkpango.h>
26 #include "gtklabelaccessible.h"
27 #include <libgail-util/gailmisc.h>
28
29
30 static void atk_text_interface_init (AtkTextIface *iface);
31
32 G_DEFINE_TYPE_WITH_CODE (GtkLabelAccessible, gtk_label_accessible, GAIL_TYPE_WIDGET,
33                          G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, atk_text_interface_init))
34
35 static void
36 gtk_label_accessible_init (GtkLabelAccessible *label)
37 {
38 }
39
40 static void
41 gtk_label_accessible_initialize (AtkObject *obj,
42                                  gpointer   data)
43 {
44   GtkWidget  *widget;
45   GtkLabelAccessible *accessible;
46
47   ATK_OBJECT_CLASS (gtk_label_accessible_parent_class)->initialize (obj, data);
48
49   accessible = GTK_LABEL_ACCESSIBLE (obj);
50
51   widget = GTK_WIDGET (data);
52
53   accessible->text = g_strdup (gtk_label_get_text (GTK_LABEL (widget)));
54
55   /*
56    * Check whether ancestor of GtkLabel is a GtkButton and if so
57    * set accessible parent for GtkLabelAccessible
58    */
59   while (widget != NULL)
60     {
61       widget = gtk_widget_get_parent (widget);
62       if (GTK_IS_BUTTON (widget))
63         {
64           atk_object_set_parent (obj, gtk_widget_get_accessible (widget));
65           break;
66         }
67     }
68
69   if (GTK_IS_ACCEL_LABEL (widget))
70     obj->role = ATK_ROLE_ACCEL_LABEL;
71   else
72     obj->role = ATK_ROLE_LABEL;
73 }
74
75 static void
76 gtk_label_accessible_notify_gtk (GObject    *obj,
77                                  GParamSpec *pspec)
78 {
79   GtkWidget *widget = GTK_WIDGET (obj);
80   AtkObject* atk_obj = gtk_widget_get_accessible (widget);
81   GtkLabelAccessible *accessible;
82   gint length;
83
84   accessible = GTK_LABEL_ACCESSIBLE (atk_obj);
85
86   if (strcmp (pspec->name, "label") == 0)
87     {
88       const gchar *text;
89
90       text = gtk_label_get_text (GTK_LABEL (widget));
91       if (strcmp (accessible->text, text) == 0)
92         return;
93
94       /* Create a delete text and an insert text signal */
95       length = g_utf8_strlen (accessible->text, -1);
96       if (length > 0)
97         g_signal_emit_by_name (atk_obj, "text_changed::delete", 0, length);
98
99       g_free (accessible->text);
100       accessible->text = g_strdup (text);
101
102       length = g_utf8_strlen (accessible->text, -1);
103       if (length > 0)
104         g_signal_emit_by_name (atk_obj, "text_changed::insert", 0, length);
105
106       if (atk_obj->name == NULL)
107         /* The label has changed so notify a change in accessible-name */
108         g_object_notify (G_OBJECT (atk_obj), "accessible-name");
109
110       g_signal_emit_by_name (atk_obj, "visible_data_changed");
111     }
112   else if (strcmp (pspec->name, "cursor-position") == 0)
113     {
114       g_signal_emit_by_name (atk_obj, "text_caret_moved",
115                              _gtk_label_get_cursor_position (GTK_LABEL (widget)));
116       g_signal_emit_by_name (atk_obj, "text_selection_changed");
117     }
118   else
119     GAIL_WIDGET_CLASS (gtk_label_accessible_parent_class)->notify_gtk (obj, pspec);
120 }
121
122 static void
123 gtk_label_accessible_finalize (GObject *object)
124 {
125   GtkLabelAccessible *accessible = GTK_LABEL_ACCESSIBLE (object);
126
127   g_free (accessible->text);
128
129   G_OBJECT_CLASS (gtk_label_accessible_parent_class)->finalize (object);
130 }
131
132
133 /* atkobject.h */
134
135 static AtkStateSet *
136 gtk_label_accessible_ref_state_set (AtkObject *accessible)
137 {
138   AtkStateSet *state_set;
139   GtkWidget *widget;
140
141   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
142   if (widget == NULL)
143     return NULL;
144
145   state_set = ATK_OBJECT_CLASS (gtk_label_accessible_parent_class)->ref_state_set (accessible);
146   atk_state_set_add_state (state_set, ATK_STATE_MULTI_LINE);
147
148   return state_set;
149 }
150
151 AtkRelationSet *
152 gtk_label_accessible_ref_relation_set (AtkObject *obj)
153 {
154   GtkWidget *widget;
155   AtkRelationSet *relation_set;
156
157   g_return_val_if_fail (GTK_IS_LABEL_ACCESSIBLE (obj), NULL);
158
159   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
160   if (widget == NULL)
161     return NULL;
162
163   relation_set = ATK_OBJECT_CLASS (gtk_label_accessible_parent_class)->ref_relation_set (obj);
164
165   if (!atk_relation_set_contains (relation_set, ATK_RELATION_LABEL_FOR))
166     {
167       /* Get the mnemonic widget.
168        * The relation set is not updated if the mnemonic widget is changed
169        */
170       GtkWidget *mnemonic_widget;
171
172       mnemonic_widget = gtk_label_get_mnemonic_widget (GTK_LABEL (widget));
173
174       if (mnemonic_widget)
175         {
176           AtkObject *accessible_array[1];
177           AtkRelation* relation;
178
179           if (!gtk_widget_get_can_focus (mnemonic_widget))
180             {
181             /*
182              * Handle the case where a GtkFileChooserButton is specified
183              * as the mnemonic widget. use the combobox which is a child of the
184              * GtkFileChooserButton as the mnemonic widget. See bug #359843.
185              */
186              if (GTK_IS_BOX (mnemonic_widget))
187                {
188                   GList *list, *tmpl;
189
190                   list = gtk_container_get_children (GTK_CONTAINER (mnemonic_widget));
191                   if (g_list_length (list) == 2)
192                     {
193                       tmpl = g_list_last (list);
194                       if (GTK_IS_COMBO_BOX(tmpl->data))
195                         {
196                           mnemonic_widget = GTK_WIDGET(tmpl->data);
197                         }
198                     }
199                   g_list_free (list);
200                 }
201             }
202           accessible_array[0] = gtk_widget_get_accessible (mnemonic_widget);
203           relation = atk_relation_new (accessible_array, 1,
204                                        ATK_RELATION_LABEL_FOR);
205           atk_relation_set_add (relation_set, relation);
206           /*
207            * Unref the relation so that it is not leaked.
208            */
209           g_object_unref (relation);
210         }
211     }
212   return relation_set;
213 }
214
215 static const gchar*
216 gtk_label_accessible_get_name (AtkObject *accessible)
217 {
218   const gchar *name;
219
220   g_return_val_if_fail (GTK_IS_LABEL_ACCESSIBLE (accessible), NULL);
221
222   name = ATK_OBJECT_CLASS (gtk_label_accessible_parent_class)->get_name (accessible);
223   if (name != NULL)
224     return name;
225   else
226     {
227       /*
228        * Get the text on the label
229        */
230       GtkWidget *widget;
231
232       widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
233       if (widget == NULL)
234         return NULL;
235
236       g_return_val_if_fail (GTK_IS_LABEL (widget), NULL);
237
238       return gtk_label_get_text (GTK_LABEL (widget));
239     }
240 }
241
242 static void
243 gtk_label_accessible_class_init (GtkLabelAccessibleClass *klass)
244 {
245   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
246   AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
247   GailWidgetClass *widget_class = (GailWidgetClass*)klass;
248
249   gobject_class->finalize = gtk_label_accessible_finalize;
250
251   widget_class->notify_gtk = gtk_label_accessible_notify_gtk;
252
253   class->get_name = gtk_label_accessible_get_name;
254   class->ref_state_set = gtk_label_accessible_ref_state_set;
255   class->ref_relation_set = gtk_label_accessible_ref_relation_set;
256   class->initialize = gtk_label_accessible_initialize;
257 }
258
259 /* atktext.h */
260
261 static gchar*
262 gtk_label_accessible_get_text (AtkText *atk_text,
263                                gint     start_pos,
264                                gint     end_pos)
265 {
266   GtkWidget *widget;
267   const gchar *text;
268
269   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_text));
270   if (widget == NULL)
271     return NULL;
272
273   text = gtk_label_get_text (GTK_LABEL (widget));
274
275   if (text)
276     return g_utf8_substring (text, start_pos, end_pos > -1 ? end_pos : g_utf8_strlen (text, -1));
277
278   return NULL;
279 }
280
281 static gchar *
282 gtk_label_accessible_get_text_before_offset (AtkText         *text,
283                                              gint             offset,
284                                              AtkTextBoundary  boundary_type,
285                                              gint            *start_offset,
286                                              gint            *end_offset)
287 {
288   GtkWidget *widget;
289
290   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
291   if (widget == NULL)
292     return NULL;
293
294   return _gtk_pango_get_text_before (gtk_label_get_layout (GTK_LABEL (widget)),
295                                      boundary_type, offset,
296                                      start_offset, end_offset);
297 }
298
299 static gchar*
300 gtk_label_accessible_get_text_at_offset (AtkText         *text,
301                                          gint             offset,
302                                          AtkTextBoundary  boundary_type,
303                                          gint            *start_offset,
304                                          gint            *end_offset)
305 {
306   GtkWidget *widget;
307
308   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
309   if (widget == NULL)
310     return NULL;
311
312   return _gtk_pango_get_text_at (gtk_label_get_layout (GTK_LABEL (widget)),
313                                  boundary_type, offset,
314                                  start_offset, end_offset);
315 }
316
317 static gchar*
318 gtk_label_accessible_get_text_after_offset (AtkText         *text,
319                                             gint             offset,
320                                             AtkTextBoundary  boundary_type,
321                                             gint            *start_offset,
322                                             gint            *end_offset)
323 {
324   GtkWidget *widget;
325
326   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
327   if (widget == NULL)
328     return NULL;
329
330   return _gtk_pango_get_text_after (gtk_label_get_layout (GTK_LABEL (widget)),
331                                     boundary_type, offset,
332                                     start_offset, end_offset);
333 }
334
335 static gint
336 gtk_label_accessible_get_character_count (AtkText *atk_text)
337 {
338   GtkWidget *widget;
339   const gchar *text;
340
341   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_text));
342   if (widget == NULL)
343     return 0;
344
345   text = gtk_label_get_text (GTK_LABEL (widget));
346
347   if (text)
348     return g_utf8_strlen (text, -1);
349
350   return 0;
351 }
352
353 static gint
354 gtk_label_accessible_get_caret_offset (AtkText *text)
355 {
356   GtkWidget *widget;
357
358   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
359   if (widget == NULL)
360     return 0;
361
362   return _gtk_label_get_cursor_position (GTK_LABEL (widget));
363 }
364
365 static gboolean
366 gtk_label_accessible_set_caret_offset (AtkText *text,
367                                        gint     offset)
368 {
369   GtkWidget *widget;
370   GtkLabel *label;
371
372   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
373   if (widget == NULL)
374     return FALSE;
375
376   label = GTK_LABEL (widget);
377
378   if (!gtk_label_get_selectable (label))
379     return FALSE;
380
381   gtk_label_select_region (label, offset, offset);
382
383   return TRUE;
384 }
385
386 static gint
387 gtk_label_accessible_get_n_selections (AtkText *text)
388 {
389   GtkWidget *widget;
390   gint start, end;
391
392   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
393   if (widget == NULL)
394     return 0;
395
396   if (gtk_label_get_selection_bounds (GTK_LABEL (widget), &start, &end))
397     return 1;
398
399   return 0;
400 }
401
402 static gchar *
403 gtk_label_accessible_get_selection (AtkText *text,
404                                     gint     selection_num,
405                                     gint    *start_pos,
406                                     gint    *end_pos)
407 {
408   GtkWidget *widget;
409   GtkLabel  *label;
410
411   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
412   if (widget == NULL)
413     return NULL;
414
415   label = GTK_LABEL (widget);
416
417   if (selection_num != 0)
418     return NULL;
419
420   if (gtk_label_get_selection_bounds (label, start_pos, end_pos))
421     {
422       const gchar *text;
423
424       text = gtk_label_get_text (label);
425
426       if (text)
427         return g_utf8_substring (text, *start_pos, *end_pos);
428     }
429
430   return NULL;
431 }
432
433 static gboolean
434 gtk_label_accessible_add_selection (AtkText *text,
435                                     gint     start_pos,
436                                     gint     end_pos)
437 {
438   GtkWidget *widget;
439   GtkLabel  *label;
440   gint start, end;
441
442   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
443   if (widget == NULL)
444     return FALSE;
445
446   label = GTK_LABEL (widget);
447
448   if (!gtk_label_get_selectable (label))
449     return FALSE;
450
451   if (!gtk_label_get_selection_bounds (label, &start, &end))
452     {
453       gtk_label_select_region (label, start_pos, end_pos);
454       return TRUE;
455     }
456   else
457     return FALSE;
458 }
459
460 static gboolean
461 gtk_label_accessible_remove_selection (AtkText *text,
462                                        gint     selection_num)
463 {
464   GtkWidget *widget;
465   GtkLabel  *label;
466   gint start, end;
467
468   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
469   if (widget == NULL)
470     return FALSE;
471
472   if (selection_num != 0)
473      return FALSE;
474
475   label = GTK_LABEL (widget);
476
477   if (!gtk_label_get_selectable (label))
478      return FALSE;
479
480   if (gtk_label_get_selection_bounds (label, &start, &end))
481     {
482       gtk_label_select_region (label, 0, 0);
483       return TRUE;
484     }
485   else
486     return FALSE;
487 }
488
489 static gboolean
490 gtk_label_accessible_set_selection (AtkText *text,
491                                     gint     selection_num,
492                                     gint     start_pos,
493                                     gint     end_pos)
494 {
495   GtkWidget *widget;
496   GtkLabel  *label;
497   gint start, end;
498
499   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
500   if (widget == NULL)
501     return FALSE;
502
503   if (selection_num != 0)
504     return FALSE;
505
506   label = GTK_LABEL (widget);
507
508   if (!gtk_label_get_selectable (label))
509     return FALSE;
510
511   if (gtk_label_get_selection_bounds (label, &start, &end))
512     {
513       gtk_label_select_region (label, start_pos, end_pos);
514       return TRUE;
515     }
516   else
517     return FALSE;
518 }
519
520 static void
521 gtk_label_accessible_get_character_extents (AtkText      *text,
522                                             gint          offset,
523                                             gint         *x,
524                                             gint         *y,
525                                             gint         *width,
526                                             gint         *height,
527                                             AtkCoordType  coords)
528 {
529   GtkWidget *widget;
530   GtkLabel *label;
531   PangoRectangle char_rect;
532   const gchar *label_text;
533   gint index, x_layout, y_layout;
534   GdkWindow *window;
535   gint x_window, y_window;
536
537   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
538   if (widget == NULL)
539     return;
540
541   label = GTK_LABEL (widget);
542
543   gtk_label_get_layout_offsets (label, &x_layout, &y_layout);
544   label_text = gtk_label_get_text (label);
545   index = g_utf8_offset_to_pointer (label_text, offset) - label_text;
546   pango_layout_index_to_pos (gtk_label_get_layout (label), index, &char_rect);
547   pango_extents_to_pixels (&char_rect, NULL);
548
549   window = gtk_widget_get_window (widget);
550   gdk_window_get_origin (window, &x_window, &y_window);
551
552   *x = x_window + x_layout + char_rect.x;
553   *y = x_window + y_layout + char_rect.y;
554   *width = char_rect.width;
555   *height = char_rect.height;
556
557   if (coords == ATK_XY_WINDOW)
558     {
559       window = gdk_window_get_toplevel (window);
560       gdk_window_get_origin (window, &x_window, &y_window);
561
562       *x -= x_window;
563       *y -= y_window;
564     }
565 }
566
567 static gint
568 gtk_label_accessible_get_offset_at_point (AtkText      *atk_text,
569                                           gint          x,
570                                           gint          y,
571                                           AtkCoordType  coords)
572 {
573   GtkWidget *widget;
574   GtkLabel *label;
575   const gchar *text;
576   gint index, x_layout, y_layout;
577   gint x_window, y_window;
578   gint x_local, y_local;
579   GdkWindow *window;
580
581   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_text));
582   if (widget == NULL)
583     return -1;
584
585   label = GTK_LABEL (widget);
586
587   gtk_label_get_layout_offsets (label, &x_layout, &y_layout);
588
589   window = gtk_widget_get_window (widget);
590   gdk_window_get_origin (window, &x_window, &y_window);
591
592   x_local = x - x_layout - x_window;
593   y_local = y - y_layout - y_window;
594
595   if (coords == ATK_XY_WINDOW)
596     {
597       window = gdk_window_get_toplevel (window);
598       gdk_window_get_origin (window, &x_window, &y_window);
599
600       x_local += x_window;
601       y_local += y_window;
602     }
603
604   if (!pango_layout_xy_to_index (gtk_label_get_layout (label),
605                                  x_local * PANGO_SCALE,
606                                  y_local * PANGO_SCALE,
607                                  &index, NULL))
608     {
609       if (x_local < 0 || y_local < 0)
610         index = 0;
611       else
612         index = -1;
613     }
614
615   if (index != -1)
616     {
617       text = gtk_label_get_text (label);
618       return g_utf8_pointer_to_offset (text, text + index);
619     }
620
621   return -1;
622 }
623
624 static AtkAttributeSet *
625 add_attribute (AtkAttributeSet  *attributes,
626                AtkTextAttribute  attr,
627                const gchar      *value)
628 {
629   AtkAttribute *at;
630
631   at = g_new (AtkAttribute, 1);
632   at->name = g_strdup (atk_text_attribute_get_name (attr));
633   at->value = g_strdup (value);
634
635   return g_slist_prepend (attributes, at);
636 }
637
638 static AtkAttributeSet*
639 gtk_label_accessible_get_run_attributes (AtkText *text,
640                                          gint     offset,
641                                          gint    *start_offset,
642                                          gint    *end_offset)
643 {
644   GtkWidget *widget;
645   AtkAttributeSet *attributes;
646
647   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
648   if (widget == NULL)
649     return NULL;
650
651   attributes = NULL;
652   attributes = add_attribute (attributes, ATK_TEXT_ATTR_DIRECTION,
653                    atk_text_attribute_get_value (ATK_TEXT_ATTR_DIRECTION,
654                                                  gtk_widget_get_direction (widget)));
655   attributes = _gtk_pango_get_run_attributes (attributes,
656                                               gtk_label_get_layout (GTK_LABEL (widget)),
657                                               offset,
658                                               start_offset,
659                                               end_offset);
660
661  return attributes;
662 }
663
664 static AtkAttributeSet *
665 gtk_label_accessible_get_default_attributes (AtkText *text)
666 {
667   GtkWidget *widget;
668   AtkAttributeSet *attributes;
669
670   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
671   if (widget == NULL)
672     return NULL;
673
674   attributes = NULL;
675   attributes = add_attribute (attributes, ATK_TEXT_ATTR_DIRECTION,
676                    atk_text_attribute_get_value (ATK_TEXT_ATTR_DIRECTION,
677                                                  gtk_widget_get_direction (widget)));
678   attributes = _gtk_pango_get_default_attributes (attributes,
679                                                   gtk_label_get_layout (GTK_LABEL (widget)));
680   attributes = _gtk_style_context_get_attributes (attributes,
681                                                   gtk_widget_get_style_context (widget),
682                                                   gtk_widget_get_state_flags (widget));
683
684   return attributes;
685 }
686
687 static gunichar
688 gtk_label_accessible_get_character_at_offset (AtkText *atk_text,
689                                               gint     offset)
690 {
691   GtkWidget *widget;
692   const gchar *text;
693   gchar *index;
694
695   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_text));
696   if (widget == NULL)
697     return '\0';
698
699   text = gtk_label_get_text (GTK_LABEL (widget));
700   if (offset >= g_utf8_strlen (text, -1))
701     return '\0';
702
703   index = g_utf8_offset_to_pointer (text, offset);
704
705   return g_utf8_get_char (index);
706 }
707
708 static void
709 atk_text_interface_init (AtkTextIface *iface)
710 {
711   iface->get_text = gtk_label_accessible_get_text;
712   iface->get_character_at_offset = gtk_label_accessible_get_character_at_offset;
713   iface->get_text_before_offset = gtk_label_accessible_get_text_before_offset;
714   iface->get_text_at_offset = gtk_label_accessible_get_text_at_offset;
715   iface->get_text_after_offset = gtk_label_accessible_get_text_after_offset;
716   iface->get_character_count = gtk_label_accessible_get_character_count;
717   iface->get_caret_offset = gtk_label_accessible_get_caret_offset;
718   iface->set_caret_offset = gtk_label_accessible_set_caret_offset;
719   iface->get_n_selections = gtk_label_accessible_get_n_selections;
720   iface->get_selection = gtk_label_accessible_get_selection;
721   iface->add_selection = gtk_label_accessible_add_selection;
722   iface->remove_selection = gtk_label_accessible_remove_selection;
723   iface->set_selection = gtk_label_accessible_set_selection;
724   iface->get_character_extents = gtk_label_accessible_get_character_extents;
725   iface->get_offset_at_point = gtk_label_accessible_get_offset_at_point;
726   iface->get_run_attributes = gtk_label_accessible_get_run_attributes;
727   iface->get_default_attributes = gtk_label_accessible_get_default_attributes;
728 }
729