1 /* GTK+ - accessibility implementations
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, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /* Preamble {{{1 */
19 
20 #include "config.h"
21 
22 #include <gtk/gtk.h>
23 #include <gtk/gtkpango.h>
24 #include "gtkwidgetprivate.h"
25 #include "gtklabelprivate.h"
26 #include "gtklabelaccessible.h"
27 #include "gtklabelaccessibleprivate.h"
28 #include "gtkstylecontextprivate.h"
29 
30 struct _GtkLabelAccessiblePrivate
31 {
32   gint cursor_position;
33   gint selection_bound;
34 
35   GList *links;
36 };
37 
38 typedef struct _GtkLabelAccessibleLink      GtkLabelAccessibleLink;
39 typedef struct _GtkLabelAccessibleLinkClass GtkLabelAccessibleLinkClass;
40 
41 struct _GtkLabelAccessibleLink
42 {
43   AtkHyperlink parent;
44 
45   GtkLabelAccessible *label;
46   gint index;
47   gboolean focused;
48 };
49 
50 struct _GtkLabelAccessibleLinkClass
51 {
52   AtkHyperlinkClass parent_class;
53 };
54 
55 static GtkLabelAccessibleLink *gtk_label_accessible_link_new (GtkLabelAccessible *label,
56                                                               gint                idx);
57 
58 typedef struct _GtkLabelAccessibleLinkImpl      GtkLabelAccessibleLinkImpl;
59 typedef struct _GtkLabelAccessibleLinkImplClass GtkLabelAccessibleLinkImplClass;
60 
61 struct _GtkLabelAccessibleLinkImpl
62 {
63   AtkObject parent;
64 
65   GtkLabelAccessibleLink *link;
66 };
67 
68 struct _GtkLabelAccessibleLinkImplClass
69 {
70   AtkObjectClass parent_class;
71 };
72 
73 /* GtkLabelAccessibleLinkImpl {{{1 */
74 
75 GType _gtk_label_accessible_link_impl_get_type (void);
76 
77 static void atk_hyperlink_impl_interface_init (AtkHyperlinkImplIface *iface);
78 
G_DEFINE_TYPE_WITH_CODE(GtkLabelAccessibleLinkImpl,_gtk_label_accessible_link_impl,ATK_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (ATK_TYPE_HYPERLINK_IMPL,atk_hyperlink_impl_interface_init))79 G_DEFINE_TYPE_WITH_CODE (GtkLabelAccessibleLinkImpl, _gtk_label_accessible_link_impl, ATK_TYPE_OBJECT,
80                          G_IMPLEMENT_INTERFACE (ATK_TYPE_HYPERLINK_IMPL, atk_hyperlink_impl_interface_init))
81 
82 static AtkHyperlink *
83 gtk_label_accessible_link_impl_get_hyperlink (AtkHyperlinkImpl *atk_impl)
84 {
85   GtkLabelAccessibleLinkImpl *impl = (GtkLabelAccessibleLinkImpl *)atk_impl;
86 
87   return ATK_HYPERLINK (g_object_ref (impl->link));
88 }
89 
90 static void
atk_hyperlink_impl_interface_init(AtkHyperlinkImplIface * iface)91 atk_hyperlink_impl_interface_init (AtkHyperlinkImplIface *iface)
92 {
93   iface->get_hyperlink = gtk_label_accessible_link_impl_get_hyperlink;
94 }
95 
96 static AtkStateSet *
gtk_label_accessible_link_impl_ref_state_set(AtkObject * obj)97 gtk_label_accessible_link_impl_ref_state_set (AtkObject *obj)
98 {
99   AtkStateSet *state_set;
100   GtkLabelAccessibleLink *link;
101   GtkWidget *widget;
102 
103   link = ((GtkLabelAccessibleLinkImpl *)obj)->link;
104 
105   state_set = atk_object_ref_state_set (atk_object_get_parent (obj));
106 
107   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_object_get_parent (obj)));
108   if (widget)
109     {
110       if (gtk_widget_get_can_focus (widget))
111         {
112           atk_state_set_add_state (state_set, ATK_STATE_FOCUSABLE);
113           if (_gtk_label_get_link_focused (GTK_LABEL (widget), link->index))
114             atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
115           else
116             atk_state_set_remove_state (state_set, ATK_STATE_FOCUSED);
117         }
118 
119       if (_gtk_label_get_link_visited (GTK_LABEL (widget), link->index))
120         atk_state_set_add_state (state_set, ATK_STATE_VISITED);
121     }
122 
123   return state_set;
124 }
125 
126 static void
_gtk_label_accessible_link_impl_init(GtkLabelAccessibleLinkImpl * impl)127 _gtk_label_accessible_link_impl_init (GtkLabelAccessibleLinkImpl *impl)
128 {
129   atk_object_set_role (ATK_OBJECT (impl), ATK_ROLE_LINK);
130 }
131 
132 static void
_gtk_label_accessible_link_impl_finalize(GObject * obj)133 _gtk_label_accessible_link_impl_finalize (GObject *obj)
134 {
135   GtkLabelAccessibleLinkImpl *impl = (GtkLabelAccessibleLinkImpl *)obj;
136 
137   g_object_unref (impl->link);
138 
139   G_OBJECT_CLASS (_gtk_label_accessible_link_impl_parent_class)->finalize (obj);
140 }
141 
142 static void
_gtk_label_accessible_link_impl_class_init(GtkLabelAccessibleLinkImplClass * class)143 _gtk_label_accessible_link_impl_class_init (GtkLabelAccessibleLinkImplClass *class)
144 {
145   GObjectClass *object_class = G_OBJECT_CLASS (class);
146   AtkObjectClass *atk_obj_class = ATK_OBJECT_CLASS (class);
147 
148   object_class->finalize = _gtk_label_accessible_link_impl_finalize;
149   atk_obj_class->ref_state_set = gtk_label_accessible_link_impl_ref_state_set;
150 }
151 
152 /* 'Public' API {{{2 */
153 
154 static GtkLabelAccessibleLinkImpl *
gtk_label_accessible_link_impl_new(GtkLabelAccessible * label,gint idx)155 gtk_label_accessible_link_impl_new (GtkLabelAccessible *label,
156                                     gint                idx)
157 {
158   GtkLabelAccessibleLinkImpl *impl;
159 
160   impl = g_object_new (_gtk_label_accessible_link_impl_get_type (), NULL);
161   impl->link = gtk_label_accessible_link_new (label, idx);
162   atk_object_set_parent (ATK_OBJECT (impl), ATK_OBJECT (label));
163 
164   return impl;
165 }
166 
167 /* GtkLabelAccessibleLink {{{1 */
168 
169 GType _gtk_label_accessible_link_get_type (void);
170 
171 static void atk_action_interface_init (AtkActionIface *iface);
172 
G_DEFINE_TYPE_WITH_CODE(GtkLabelAccessibleLink,_gtk_label_accessible_link,ATK_TYPE_HYPERLINK,G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION,atk_action_interface_init))173 G_DEFINE_TYPE_WITH_CODE (GtkLabelAccessibleLink, _gtk_label_accessible_link, ATK_TYPE_HYPERLINK,
174                          G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, atk_action_interface_init))
175 
176 static gchar *
177 gtk_label_accessible_link_get_uri (AtkHyperlink *atk_link,
178                                    gint          i)
179 {
180   GtkLabelAccessibleLink *link = (GtkLabelAccessibleLink *)atk_link;
181   GtkWidget *widget;
182   const gchar *uri;
183 
184   g_return_val_if_fail (i == 0, NULL);
185 
186   if (link->label == NULL)
187     return NULL;
188 
189   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (link->label));
190   uri = _gtk_label_get_link_uri (GTK_LABEL (widget), link->index);
191 
192   return g_strdup (uri);
193 }
194 
195 static gint
gtk_label_accessible_link_get_n_anchors(AtkHyperlink * atk_link)196 gtk_label_accessible_link_get_n_anchors (AtkHyperlink *atk_link)
197 {
198   return 1;
199 }
200 
201 static gboolean
gtk_label_accessible_link_is_valid(AtkHyperlink * atk_link)202 gtk_label_accessible_link_is_valid (AtkHyperlink *atk_link)
203 {
204   return TRUE;
205 }
206 
207 static AtkObject *
gtk_label_accessible_link_get_object(AtkHyperlink * atk_link,gint i)208 gtk_label_accessible_link_get_object (AtkHyperlink *atk_link,
209                                       gint          i)
210 {
211   GtkLabelAccessibleLink *link = (GtkLabelAccessibleLink *)atk_link;
212 
213   g_return_val_if_fail (i == 0, NULL);
214 
215   return ATK_OBJECT (link->label);
216 }
217 
218 static gint
gtk_label_accessible_link_get_start_index(AtkHyperlink * atk_link)219 gtk_label_accessible_link_get_start_index (AtkHyperlink *atk_link)
220 {
221   GtkLabelAccessibleLink *link = (GtkLabelAccessibleLink *)atk_link;
222   GtkWidget *widget;
223   gint start, end;
224 
225   if (link->label == NULL)
226     return 0;
227 
228   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (link->label));
229   _gtk_label_get_link_extent (GTK_LABEL (widget), link->index, &start, &end);
230 
231   return start;
232 }
233 
234 static gint
gtk_label_accessible_link_get_end_index(AtkHyperlink * atk_link)235 gtk_label_accessible_link_get_end_index (AtkHyperlink *atk_link)
236 {
237   GtkLabelAccessibleLink *link = (GtkLabelAccessibleLink *)atk_link;
238   GtkWidget *widget;
239   gint start, end;
240 
241   if (link->label == NULL)
242     return 0;
243 
244   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (link->label));
245   _gtk_label_get_link_extent (GTK_LABEL (widget), link->index, &start, &end);
246 
247   return end;
248 }
249 
250 static void
_gtk_label_accessible_link_init(GtkLabelAccessibleLink * link)251 _gtk_label_accessible_link_init (GtkLabelAccessibleLink *link)
252 {
253 }
254 
255 static void
_gtk_label_accessible_link_class_init(GtkLabelAccessibleLinkClass * class)256 _gtk_label_accessible_link_class_init (GtkLabelAccessibleLinkClass *class)
257 {
258   AtkHyperlinkClass *atk_link_class = ATK_HYPERLINK_CLASS (class);
259 
260   atk_link_class->get_uri = gtk_label_accessible_link_get_uri;
261   atk_link_class->get_n_anchors = gtk_label_accessible_link_get_n_anchors;
262   atk_link_class->is_valid = gtk_label_accessible_link_is_valid;
263   atk_link_class->get_object = gtk_label_accessible_link_get_object;
264   atk_link_class->get_start_index = gtk_label_accessible_link_get_start_index;
265   atk_link_class->get_end_index = gtk_label_accessible_link_get_end_index;
266 }
267 
268 /* 'Public' API {{{2 */
269 
270 static GtkLabelAccessibleLink *
gtk_label_accessible_link_new(GtkLabelAccessible * label,gint idx)271 gtk_label_accessible_link_new (GtkLabelAccessible *label,
272                                gint                idx)
273 {
274   GtkLabelAccessibleLink *link;
275 
276   link = g_object_new (_gtk_label_accessible_link_get_type (), NULL);
277   link->label = label;
278   link->index = idx;
279 
280   return link;
281 }
282 
283 /* AtkAction implementation {{{2 */
284 
285 static gboolean
gtk_label_accessible_link_do_action(AtkAction * action,gint i)286 gtk_label_accessible_link_do_action (AtkAction *action,
287                                      gint       i)
288 {
289   GtkLabelAccessibleLink *link = (GtkLabelAccessibleLink *)action;
290   GtkWidget *widget;
291 
292   if (i != 0)
293     return FALSE;
294 
295   if (link->label == NULL)
296     return FALSE;
297 
298   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (link->label));
299   if (widget == NULL)
300     return FALSE;
301 
302   if (!gtk_widget_is_sensitive (widget) || !gtk_widget_get_visible (widget))
303     return FALSE;
304 
305   _gtk_label_activate_link (GTK_LABEL (widget), link->index);
306 
307   return TRUE;
308 }
309 
310 static gint
gtk_label_accessible_link_get_n_actions(AtkAction * action)311 gtk_label_accessible_link_get_n_actions (AtkAction *action)
312 {
313   return 1;
314 }
315 
316 static const gchar *
gtk_label_accessible_link_get_name(AtkAction * action,gint i)317 gtk_label_accessible_link_get_name (AtkAction *action,
318                                     gint       i)
319 {
320   if (i != 0)
321     return NULL;
322 
323   return "activate";
324 }
325 
326 static void
atk_action_interface_init(AtkActionIface * iface)327 atk_action_interface_init (AtkActionIface *iface)
328 {
329   iface->do_action = gtk_label_accessible_link_do_action;
330   iface->get_n_actions = gtk_label_accessible_link_get_n_actions;
331   iface->get_name = gtk_label_accessible_link_get_name;
332 }
333 
334 /* GtkLabelAccessible {{{1 */
335 
336 static void atk_text_interface_init       (AtkTextIface      *iface);
337 static void atk_hypertext_interface_init (AtkHypertextIface *iface);
338 
G_DEFINE_TYPE_WITH_CODE(GtkLabelAccessible,gtk_label_accessible,GTK_TYPE_WIDGET_ACCESSIBLE,G_ADD_PRIVATE (GtkLabelAccessible)G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT,atk_text_interface_init)G_IMPLEMENT_INTERFACE (ATK_TYPE_HYPERTEXT,atk_hypertext_interface_init))339 G_DEFINE_TYPE_WITH_CODE (GtkLabelAccessible, gtk_label_accessible, GTK_TYPE_WIDGET_ACCESSIBLE,
340                          G_ADD_PRIVATE (GtkLabelAccessible)
341                          G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, atk_text_interface_init)
342                          G_IMPLEMENT_INTERFACE (ATK_TYPE_HYPERTEXT, atk_hypertext_interface_init))
343 
344 static void
345 gtk_label_accessible_init (GtkLabelAccessible *label)
346 {
347   label->priv = gtk_label_accessible_get_instance_private (label);
348 }
349 
350 static void
gtk_label_accessible_initialize(AtkObject * obj,gpointer data)351 gtk_label_accessible_initialize (AtkObject *obj,
352                                  gpointer   data)
353 {
354   GtkWidget  *widget;
355 
356   ATK_OBJECT_CLASS (gtk_label_accessible_parent_class)->initialize (obj, data);
357 
358   widget = GTK_WIDGET (data);
359 
360   _gtk_label_accessible_update_links (GTK_LABEL (widget));
361 
362   /* Check whether ancestor of GtkLabel is a GtkButton
363    * and if so set accessible parent for GtkLabelAccessible
364    */
365   while (widget != NULL)
366     {
367       widget = gtk_widget_get_parent (widget);
368       if (GTK_IS_BUTTON (widget))
369         {
370           atk_object_set_parent (obj, gtk_widget_get_accessible (widget));
371           break;
372         }
373     }
374 
375   obj->role = ATK_ROLE_LABEL;
376 }
377 
378 static gboolean
check_for_selection_change(GtkLabelAccessible * accessible,GtkLabel * label)379 check_for_selection_change (GtkLabelAccessible *accessible,
380                             GtkLabel           *label)
381 {
382   gboolean ret_val = FALSE;
383   gint start, end;
384 
385   if (gtk_label_get_selection_bounds (label, &start, &end))
386     {
387       if (end != accessible->priv->cursor_position ||
388           start != accessible->priv->selection_bound)
389         ret_val = TRUE;
390     }
391   else
392     {
393       ret_val = (accessible->priv->cursor_position != accessible->priv->selection_bound);
394     }
395 
396   accessible->priv->cursor_position = end;
397   accessible->priv->selection_bound = start;
398 
399   return ret_val;
400 }
401 
402 static void
gtk_label_accessible_notify_gtk(GObject * obj,GParamSpec * pspec)403 gtk_label_accessible_notify_gtk (GObject    *obj,
404                                  GParamSpec *pspec)
405 {
406   GtkWidget *widget = GTK_WIDGET (obj);
407   AtkObject* atk_obj = gtk_widget_get_accessible (widget);
408   GtkLabelAccessible *accessible;
409 
410   accessible = GTK_LABEL_ACCESSIBLE (atk_obj);
411 
412   if (g_strcmp0 (pspec->name, "cursor-position") == 0)
413     {
414       g_signal_emit_by_name (atk_obj, "text-caret-moved",
415                              _gtk_label_get_cursor_position (GTK_LABEL (widget)));
416       if (check_for_selection_change (accessible, GTK_LABEL (widget)))
417         g_signal_emit_by_name (atk_obj, "text-selection-changed");
418     }
419   else if (g_strcmp0 (pspec->name, "selection-bound") == 0)
420     {
421       if (check_for_selection_change (accessible, GTK_LABEL (widget)))
422         g_signal_emit_by_name (atk_obj, "text-selection-changed");
423     }
424   else
425     GTK_WIDGET_ACCESSIBLE_CLASS (gtk_label_accessible_parent_class)->notify_gtk (obj, pspec);
426 }
427 
428 /* atkobject.h */
429 
430 static AtkStateSet *
gtk_label_accessible_ref_state_set(AtkObject * accessible)431 gtk_label_accessible_ref_state_set (AtkObject *accessible)
432 {
433   AtkStateSet *state_set;
434   GtkWidget *widget;
435 
436   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
437   if (widget == NULL)
438     return NULL;
439 
440   state_set = ATK_OBJECT_CLASS (gtk_label_accessible_parent_class)->ref_state_set (accessible);
441   atk_state_set_add_state (state_set, ATK_STATE_MULTI_LINE);
442 
443   return state_set;
444 }
445 
446 static AtkRelationSet *
gtk_label_accessible_ref_relation_set(AtkObject * obj)447 gtk_label_accessible_ref_relation_set (AtkObject *obj)
448 {
449   GtkWidget *widget;
450   AtkRelationSet *relation_set;
451 
452   g_return_val_if_fail (GTK_IS_LABEL_ACCESSIBLE (obj), NULL);
453 
454   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
455   if (widget == NULL)
456     return NULL;
457 
458   relation_set = ATK_OBJECT_CLASS (gtk_label_accessible_parent_class)->ref_relation_set (obj);
459 
460   if (!atk_relation_set_contains (relation_set, ATK_RELATION_LABEL_FOR))
461     {
462       /* Get the mnemonic widget.
463        * The relation set is not updated if the mnemonic widget is changed
464        */
465       GtkWidget *mnemonic_widget;
466 
467       mnemonic_widget = gtk_label_get_mnemonic_widget (GTK_LABEL (widget));
468 
469       if (mnemonic_widget)
470         {
471           AtkObject *accessible_array[1];
472           AtkRelation* relation;
473 
474           if (!gtk_widget_get_can_focus (mnemonic_widget))
475             {
476             /*
477              * Handle the case where a GtkFileChooserButton is specified
478              * as the mnemonic widget. use the combobox which is a child of the
479              * GtkFileChooserButton as the mnemonic widget. See bug #359843.
480              */
481              if (GTK_IS_BOX (mnemonic_widget))
482                {
483                   GList *list, *tmpl;
484 
485                   list = gtk_container_get_children (GTK_CONTAINER (mnemonic_widget));
486                   if (g_list_length (list) == 2)
487                     {
488                       tmpl = g_list_last (list);
489                       if (GTK_IS_COMBO_BOX(tmpl->data))
490                         {
491                           mnemonic_widget = GTK_WIDGET(tmpl->data);
492                         }
493                     }
494                   g_list_free (list);
495                 }
496             }
497           accessible_array[0] = gtk_widget_get_accessible (mnemonic_widget);
498           relation = atk_relation_new (accessible_array, 1,
499                                        ATK_RELATION_LABEL_FOR);
500           atk_relation_set_add (relation_set, relation);
501           /*
502            * Unref the relation so that it is not leaked.
503            */
504           g_object_unref (relation);
505         }
506     }
507   return relation_set;
508 }
509 
510 static const gchar*
gtk_label_accessible_get_name(AtkObject * accessible)511 gtk_label_accessible_get_name (AtkObject *accessible)
512 {
513   const gchar *name;
514 
515   g_return_val_if_fail (GTK_IS_LABEL_ACCESSIBLE (accessible), NULL);
516 
517   name = ATK_OBJECT_CLASS (gtk_label_accessible_parent_class)->get_name (accessible);
518   if (name != NULL)
519     return name;
520   else
521     {
522       /*
523        * Get the text on the label
524        */
525       GtkWidget *widget;
526 
527       widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
528       if (widget == NULL)
529         return NULL;
530 
531       g_return_val_if_fail (GTK_IS_LABEL (widget), NULL);
532 
533       return gtk_label_get_text (GTK_LABEL (widget));
534     }
535 }
536 
537 static gint
gtk_label_accessible_get_n_children(AtkObject * obj)538 gtk_label_accessible_get_n_children (AtkObject *obj)
539 {
540   GtkLabelAccessible *accessible = GTK_LABEL_ACCESSIBLE (obj);
541 
542   return g_list_length (accessible->priv->links);
543 }
544 
545 static AtkObject *
gtk_label_accessible_ref_child(AtkObject * obj,gint idx)546 gtk_label_accessible_ref_child (AtkObject *obj,
547                                 gint       idx)
548 {
549   GtkLabelAccessible *accessible = GTK_LABEL_ACCESSIBLE (obj);
550   AtkObject *child;
551 
552   child = g_list_nth_data (accessible->priv->links, idx);
553 
554   if (child)
555     g_object_ref (child);
556 
557   return child;
558 }
559 
560 static void clear_links (GtkLabelAccessible *accessible);
561 
562 static void
gtk_label_accessible_finalize(GObject * obj)563 gtk_label_accessible_finalize (GObject *obj)
564 {
565   GtkLabelAccessible *accessible = GTK_LABEL_ACCESSIBLE (obj);
566 
567   clear_links (accessible);
568 
569   G_OBJECT_CLASS (gtk_label_accessible_parent_class)->finalize (obj);
570 }
571 
572 static void
gtk_label_accessible_class_init(GtkLabelAccessibleClass * klass)573 gtk_label_accessible_class_init (GtkLabelAccessibleClass *klass)
574 {
575   GObjectClass *object_class = G_OBJECT_CLASS (klass);
576   AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
577   GtkWidgetAccessibleClass *widget_class = GTK_WIDGET_ACCESSIBLE_CLASS (klass);
578 
579   object_class->finalize = gtk_label_accessible_finalize;
580 
581   class->get_name = gtk_label_accessible_get_name;
582   class->ref_state_set = gtk_label_accessible_ref_state_set;
583   class->ref_relation_set = gtk_label_accessible_ref_relation_set;
584   class->initialize = gtk_label_accessible_initialize;
585 
586   class->get_n_children = gtk_label_accessible_get_n_children;
587   class->ref_child = gtk_label_accessible_ref_child;
588 
589   widget_class->notify_gtk = gtk_label_accessible_notify_gtk;
590 }
591 
592 /* 'Public' API {{{2 */
593 
594 void
_gtk_label_accessible_text_deleted(GtkLabel * label)595 _gtk_label_accessible_text_deleted (GtkLabel *label)
596 {
597   AtkObject *obj;
598   const char *text;
599   guint length;
600 
601   obj = _gtk_widget_peek_accessible (GTK_WIDGET (label));
602   if (obj == NULL)
603     return;
604 
605   text = gtk_label_get_text (label);
606   length = g_utf8_strlen (text, -1);
607   if (length > 0)
608     g_signal_emit_by_name (obj, "text-changed::delete", 0, length);
609 }
610 
611 void
_gtk_label_accessible_text_inserted(GtkLabel * label)612 _gtk_label_accessible_text_inserted (GtkLabel *label)
613 {
614   AtkObject *obj;
615   const char *text;
616   guint length;
617 
618   obj = _gtk_widget_peek_accessible (GTK_WIDGET (label));
619   if (obj == NULL)
620     return;
621 
622   text = gtk_label_get_text (label);
623   length = g_utf8_strlen (text, -1);
624   if (length > 0)
625     g_signal_emit_by_name (obj, "text-changed::insert", 0, length);
626 
627   if (obj->name == NULL)
628     /* The label has changed so notify a change in accessible-name */
629     g_object_notify (G_OBJECT (obj), "accessible-name");
630 
631   g_signal_emit_by_name (obj, "visible-data-changed");
632 }
633 
634 static void
clear_links(GtkLabelAccessible * accessible)635 clear_links (GtkLabelAccessible *accessible)
636 {
637   GList *l;
638   gint i;
639   GtkLabelAccessibleLinkImpl *impl;
640 
641   for (l = accessible->priv->links, i = 0; l; l = l->next, i++)
642     {
643       impl = l->data;
644       g_signal_emit_by_name (accessible, "children-changed::remove", i, impl, NULL);
645       atk_object_set_parent (ATK_OBJECT (impl), NULL);
646       impl->link->label = NULL;
647     }
648   g_list_free_full (accessible->priv->links, g_object_unref);
649   accessible->priv->links = NULL;
650 }
651 
652 static void
create_links(GtkLabelAccessible * accessible)653 create_links (GtkLabelAccessible *accessible)
654 {
655   GtkWidget *widget;
656   gint n, i;
657   GtkLabelAccessibleLinkImpl *impl;
658 
659   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
660 
661   n = _gtk_label_get_n_links (GTK_LABEL (widget));
662   for (i = 0; i < n; i++)
663     {
664       impl = gtk_label_accessible_link_impl_new (accessible, i);
665       accessible->priv->links = g_list_append (accessible->priv->links, impl);
666       g_signal_emit_by_name (accessible, "children-changed::add", i, impl, NULL);
667     }
668 }
669 
670 void
_gtk_label_accessible_update_links(GtkLabel * label)671 _gtk_label_accessible_update_links (GtkLabel *label)
672 {
673   AtkObject *obj;
674 
675   obj = _gtk_widget_peek_accessible (GTK_WIDGET (label));
676   if (obj == NULL)
677     return;
678 
679   clear_links (GTK_LABEL_ACCESSIBLE (obj));
680   create_links (GTK_LABEL_ACCESSIBLE (obj));
681 }
682 
683 void
_gtk_label_accessible_focus_link_changed(GtkLabel * label)684 _gtk_label_accessible_focus_link_changed (GtkLabel *label)
685 {
686   AtkObject *obj;
687   GtkLabelAccessible *accessible;
688   GList *l;
689   GtkLabelAccessibleLinkImpl *impl;
690   gboolean focused;
691 
692   obj = _gtk_widget_peek_accessible (GTK_WIDGET (label));
693   if (obj == NULL)
694     return;
695 
696   accessible = GTK_LABEL_ACCESSIBLE (obj);
697 
698   for (l = accessible->priv->links; l; l = l->next)
699     {
700       impl = l->data;
701       focused = _gtk_label_get_link_focused (label, impl->link->index);
702       if (impl->link->focused != focused)
703         {
704           impl->link->focused = focused;
705           atk_object_notify_state_change (ATK_OBJECT (impl), ATK_STATE_FOCUSED, focused);
706         }
707     }
708 }
709 
710 /* AtkText implementation {{{2 */
711 
712 static gchar*
gtk_label_accessible_get_text(AtkText * atk_text,gint start_pos,gint end_pos)713 gtk_label_accessible_get_text (AtkText *atk_text,
714                                gint     start_pos,
715                                gint     end_pos)
716 {
717   GtkWidget *widget;
718   const gchar *text;
719 
720   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_text));
721   if (widget == NULL)
722     return NULL;
723 
724   text = gtk_label_get_text (GTK_LABEL (widget));
725 
726   if (text)
727     {
728       guint length;
729       const gchar *start, *end;
730 
731       length = g_utf8_strlen (text, -1);
732       if (end_pos < 0 || end_pos > length)
733         end_pos = length;
734       if (start_pos > length)
735         start_pos = length;
736       if (end_pos <= start_pos)
737         return g_strdup ("");
738       start = g_utf8_offset_to_pointer (text, start_pos);
739       end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
740       return g_strndup (start, end - start);
741     }
742 
743   return NULL;
744 }
745 
746 static gchar *
gtk_label_accessible_get_text_before_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)747 gtk_label_accessible_get_text_before_offset (AtkText         *text,
748                                              gint             offset,
749                                              AtkTextBoundary  boundary_type,
750                                              gint            *start_offset,
751                                              gint            *end_offset)
752 {
753   GtkWidget *widget;
754 
755   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
756   if (widget == NULL)
757     return NULL;
758 
759   return _gtk_pango_get_text_before (gtk_label_get_layout (GTK_LABEL (widget)),
760                                      boundary_type, offset,
761                                      start_offset, end_offset);
762 }
763 
764 static gchar*
gtk_label_accessible_get_text_at_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)765 gtk_label_accessible_get_text_at_offset (AtkText         *text,
766                                          gint             offset,
767                                          AtkTextBoundary  boundary_type,
768                                          gint            *start_offset,
769                                          gint            *end_offset)
770 {
771   GtkWidget *widget;
772 
773   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
774   if (widget == NULL)
775     return NULL;
776 
777   return _gtk_pango_get_text_at (gtk_label_get_layout (GTK_LABEL (widget)),
778                                  boundary_type, offset,
779                                  start_offset, end_offset);
780 }
781 
782 static gchar*
gtk_label_accessible_get_text_after_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)783 gtk_label_accessible_get_text_after_offset (AtkText         *text,
784                                             gint             offset,
785                                             AtkTextBoundary  boundary_type,
786                                             gint            *start_offset,
787                                             gint            *end_offset)
788 {
789   GtkWidget *widget;
790 
791   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
792   if (widget == NULL)
793     return NULL;
794 
795   return _gtk_pango_get_text_after (gtk_label_get_layout (GTK_LABEL (widget)),
796                                     boundary_type, offset,
797                                     start_offset, end_offset);
798 }
799 
800 static gint
gtk_label_accessible_get_character_count(AtkText * atk_text)801 gtk_label_accessible_get_character_count (AtkText *atk_text)
802 {
803   GtkWidget *widget;
804   const gchar *text;
805 
806   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_text));
807   if (widget == NULL)
808     return 0;
809 
810   text = gtk_label_get_text (GTK_LABEL (widget));
811 
812   if (text)
813     return g_utf8_strlen (text, -1);
814 
815   return 0;
816 }
817 
818 static gint
gtk_label_accessible_get_caret_offset(AtkText * text)819 gtk_label_accessible_get_caret_offset (AtkText *text)
820 {
821   GtkWidget *widget;
822 
823   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
824   if (widget == NULL)
825     return 0;
826 
827   return _gtk_label_get_cursor_position (GTK_LABEL (widget));
828 }
829 
830 static gboolean
gtk_label_accessible_set_caret_offset(AtkText * text,gint offset)831 gtk_label_accessible_set_caret_offset (AtkText *text,
832                                        gint     offset)
833 {
834   GtkWidget *widget;
835   GtkLabel *label;
836 
837   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
838   if (widget == NULL)
839     return FALSE;
840 
841   label = GTK_LABEL (widget);
842 
843   if (!gtk_label_get_selectable (label))
844     return FALSE;
845 
846   gtk_label_select_region (label, offset, offset);
847 
848   return TRUE;
849 }
850 
851 static gint
gtk_label_accessible_get_n_selections(AtkText * text)852 gtk_label_accessible_get_n_selections (AtkText *text)
853 {
854   GtkWidget *widget;
855 
856   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
857   if (widget == NULL)
858     return 0;
859 
860   if (gtk_label_get_selection_bounds (GTK_LABEL (widget), NULL, NULL))
861     return 1;
862 
863   return 0;
864 }
865 
866 static gchar *
gtk_label_accessible_get_selection(AtkText * atk_text,gint selection_num,gint * start_pos,gint * end_pos)867 gtk_label_accessible_get_selection (AtkText *atk_text,
868                                     gint     selection_num,
869                                     gint    *start_pos,
870                                     gint    *end_pos)
871 {
872   GtkWidget *widget;
873   GtkLabel  *label;
874 
875   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_text));
876   if (widget == NULL)
877     return NULL;
878 
879   if (selection_num != 0)
880     return NULL;
881 
882   label = GTK_LABEL (widget);
883 
884   if (gtk_label_get_selection_bounds (label, start_pos, end_pos))
885     {
886       const gchar *text;
887 
888       text = gtk_label_get_text (label);
889 
890       if (text)
891         return g_utf8_substring (text, *start_pos, *end_pos);
892     }
893 
894   return NULL;
895 }
896 
897 static gboolean
gtk_label_accessible_add_selection(AtkText * text,gint start_pos,gint end_pos)898 gtk_label_accessible_add_selection (AtkText *text,
899                                     gint     start_pos,
900                                     gint     end_pos)
901 {
902   GtkWidget *widget;
903   GtkLabel  *label;
904   gint start, end;
905 
906   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
907   if (widget == NULL)
908     return FALSE;
909 
910   label = GTK_LABEL (widget);
911 
912   if (!gtk_label_get_selectable (label))
913     return FALSE;
914 
915   if (!gtk_label_get_selection_bounds (label, &start, &end))
916     {
917       gtk_label_select_region (label, start_pos, end_pos);
918       return TRUE;
919     }
920   else
921     return FALSE;
922 }
923 
924 static gboolean
gtk_label_accessible_remove_selection(AtkText * text,gint selection_num)925 gtk_label_accessible_remove_selection (AtkText *text,
926                                        gint     selection_num)
927 {
928   GtkWidget *widget;
929   GtkLabel  *label;
930   gint start, end;
931 
932   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
933   if (widget == NULL)
934     return FALSE;
935 
936   if (selection_num != 0)
937      return FALSE;
938 
939   label = GTK_LABEL (widget);
940 
941   if (!gtk_label_get_selectable (label))
942      return FALSE;
943 
944   if (gtk_label_get_selection_bounds (label, &start, &end))
945     {
946       gtk_label_select_region (label, end, end);
947       return TRUE;
948     }
949   else
950     return FALSE;
951 }
952 
953 static gboolean
gtk_label_accessible_set_selection(AtkText * text,gint selection_num,gint start_pos,gint end_pos)954 gtk_label_accessible_set_selection (AtkText *text,
955                                     gint     selection_num,
956                                     gint     start_pos,
957                                     gint     end_pos)
958 {
959   GtkWidget *widget;
960   GtkLabel  *label;
961   gint start, end;
962 
963   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
964   if (widget == NULL)
965     return FALSE;
966 
967   if (selection_num != 0)
968     return FALSE;
969 
970   label = GTK_LABEL (widget);
971 
972   if (!gtk_label_get_selectable (label))
973     return FALSE;
974 
975   if (gtk_label_get_selection_bounds (label, &start, &end))
976     {
977       gtk_label_select_region (label, start_pos, end_pos);
978       return TRUE;
979     }
980   else
981     return FALSE;
982 }
983 
984 static void
gtk_label_accessible_get_character_extents(AtkText * text,gint offset,gint * x,gint * y,gint * width,gint * height,AtkCoordType coords)985 gtk_label_accessible_get_character_extents (AtkText      *text,
986                                             gint          offset,
987                                             gint         *x,
988                                             gint         *y,
989                                             gint         *width,
990                                             gint         *height,
991                                             AtkCoordType  coords)
992 {
993   GtkWidget *widget;
994   GtkLabel *label;
995   PangoRectangle char_rect;
996   const gchar *label_text;
997   gint index, x_layout, y_layout;
998   GdkWindow *window;
999   gint x_window, y_window;
1000 
1001   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
1002   if (widget == NULL)
1003     return;
1004 
1005   label = GTK_LABEL (widget);
1006 
1007   gtk_label_get_layout_offsets (label, &x_layout, &y_layout);
1008   label_text = gtk_label_get_text (label);
1009   index = g_utf8_offset_to_pointer (label_text, offset) - label_text;
1010   pango_layout_index_to_pos (gtk_label_get_layout (label), index, &char_rect);
1011   pango_extents_to_pixels (&char_rect, NULL);
1012 
1013   window = gtk_widget_get_window (widget);
1014   gdk_window_get_origin (window, &x_window, &y_window);
1015 
1016   *x = x_window + x_layout + char_rect.x;
1017   *y = y_window + y_layout + char_rect.y;
1018   *width = char_rect.width;
1019   *height = char_rect.height;
1020 
1021   if (coords == ATK_XY_WINDOW)
1022     {
1023       window = gdk_window_get_toplevel (window);
1024       gdk_window_get_origin (window, &x_window, &y_window);
1025 
1026       *x -= x_window;
1027       *y -= y_window;
1028     }
1029 }
1030 
1031 static gint
gtk_label_accessible_get_offset_at_point(AtkText * atk_text,gint x,gint y,AtkCoordType coords)1032 gtk_label_accessible_get_offset_at_point (AtkText      *atk_text,
1033                                           gint          x,
1034                                           gint          y,
1035                                           AtkCoordType  coords)
1036 {
1037   GtkWidget *widget;
1038   GtkLabel *label;
1039   const gchar *text;
1040   gint index, x_layout, y_layout;
1041   gint x_window, y_window;
1042   gint x_local, y_local;
1043   GdkWindow *window;
1044 
1045   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_text));
1046   if (widget == NULL)
1047     return -1;
1048 
1049   label = GTK_LABEL (widget);
1050 
1051   gtk_label_get_layout_offsets (label, &x_layout, &y_layout);
1052 
1053   window = gtk_widget_get_window (widget);
1054   gdk_window_get_origin (window, &x_window, &y_window);
1055 
1056   x_local = x - x_layout - x_window;
1057   y_local = y - y_layout - y_window;
1058 
1059   if (coords == ATK_XY_WINDOW)
1060     {
1061       window = gdk_window_get_toplevel (window);
1062       gdk_window_get_origin (window, &x_window, &y_window);
1063 
1064       x_local += x_window;
1065       y_local += y_window;
1066     }
1067 
1068   if (!pango_layout_xy_to_index (gtk_label_get_layout (label),
1069                                  x_local * PANGO_SCALE,
1070                                  y_local * PANGO_SCALE,
1071                                  &index, NULL))
1072     {
1073       if (x_local < 0 || y_local < 0)
1074         index = 0;
1075       else
1076         index = -1;
1077     }
1078 
1079   if (index != -1)
1080     {
1081       text = gtk_label_get_text (label);
1082       return g_utf8_pointer_to_offset (text, text + index);
1083     }
1084 
1085   return -1;
1086 }
1087 
1088 static AtkAttributeSet *
add_attribute(AtkAttributeSet * attributes,AtkTextAttribute attr,const gchar * value)1089 add_attribute (AtkAttributeSet  *attributes,
1090                AtkTextAttribute  attr,
1091                const gchar      *value)
1092 {
1093   AtkAttribute *at;
1094 
1095   at = g_new (AtkAttribute, 1);
1096   at->name = g_strdup (atk_text_attribute_get_name (attr));
1097   at->value = g_strdup (value);
1098 
1099   return g_slist_prepend (attributes, at);
1100 }
1101 
1102 static AtkAttributeSet*
gtk_label_accessible_get_run_attributes(AtkText * text,gint offset,gint * start_offset,gint * end_offset)1103 gtk_label_accessible_get_run_attributes (AtkText *text,
1104                                          gint     offset,
1105                                          gint    *start_offset,
1106                                          gint    *end_offset)
1107 {
1108   GtkWidget *widget;
1109   AtkAttributeSet *attributes;
1110 
1111   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
1112   if (widget == NULL)
1113     return NULL;
1114 
1115   attributes = NULL;
1116   attributes = add_attribute (attributes, ATK_TEXT_ATTR_DIRECTION,
1117                    atk_text_attribute_get_value (ATK_TEXT_ATTR_DIRECTION,
1118                                                  gtk_widget_get_direction (widget)));
1119   attributes = _gtk_pango_get_run_attributes (attributes,
1120                                               gtk_label_get_layout (GTK_LABEL (widget)),
1121                                               offset,
1122                                               start_offset,
1123                                               end_offset);
1124 
1125   return attributes;
1126 }
1127 
1128 static AtkAttributeSet *
gtk_label_accessible_get_default_attributes(AtkText * text)1129 gtk_label_accessible_get_default_attributes (AtkText *text)
1130 {
1131   GtkWidget *widget;
1132   AtkAttributeSet *attributes;
1133 
1134   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
1135   if (widget == NULL)
1136     return NULL;
1137 
1138   attributes = NULL;
1139   attributes = add_attribute (attributes, ATK_TEXT_ATTR_DIRECTION,
1140                    atk_text_attribute_get_value (ATK_TEXT_ATTR_DIRECTION,
1141                                                  gtk_widget_get_direction (widget)));
1142   attributes = _gtk_pango_get_default_attributes (attributes,
1143                                                   gtk_label_get_layout (GTK_LABEL (widget)));
1144   attributes = _gtk_style_context_get_attributes (attributes,
1145                                                   gtk_widget_get_style_context (widget),
1146                                                   gtk_widget_get_state_flags (widget));
1147 
1148   return attributes;
1149 }
1150 
1151 static gunichar
gtk_label_accessible_get_character_at_offset(AtkText * atk_text,gint offset)1152 gtk_label_accessible_get_character_at_offset (AtkText *atk_text,
1153                                               gint     offset)
1154 {
1155   GtkWidget *widget;
1156   const gchar *text;
1157   gchar *index;
1158 
1159   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_text));
1160   if (widget == NULL)
1161     return '\0';
1162 
1163   text = gtk_label_get_text (GTK_LABEL (widget));
1164   if (offset >= g_utf8_strlen (text, -1))
1165     return '\0';
1166 
1167   index = g_utf8_offset_to_pointer (text, offset);
1168 
1169   return g_utf8_get_char (index);
1170 }
1171 
1172 static void
atk_text_interface_init(AtkTextIface * iface)1173 atk_text_interface_init (AtkTextIface *iface)
1174 {
1175   iface->get_text = gtk_label_accessible_get_text;
1176   iface->get_character_at_offset = gtk_label_accessible_get_character_at_offset;
1177   iface->get_text_before_offset = gtk_label_accessible_get_text_before_offset;
1178   iface->get_text_at_offset = gtk_label_accessible_get_text_at_offset;
1179   iface->get_text_after_offset = gtk_label_accessible_get_text_after_offset;
1180   iface->get_character_count = gtk_label_accessible_get_character_count;
1181   iface->get_caret_offset = gtk_label_accessible_get_caret_offset;
1182   iface->set_caret_offset = gtk_label_accessible_set_caret_offset;
1183   iface->get_n_selections = gtk_label_accessible_get_n_selections;
1184   iface->get_selection = gtk_label_accessible_get_selection;
1185   iface->add_selection = gtk_label_accessible_add_selection;
1186   iface->remove_selection = gtk_label_accessible_remove_selection;
1187   iface->set_selection = gtk_label_accessible_set_selection;
1188   iface->get_character_extents = gtk_label_accessible_get_character_extents;
1189   iface->get_offset_at_point = gtk_label_accessible_get_offset_at_point;
1190   iface->get_run_attributes = gtk_label_accessible_get_run_attributes;
1191   iface->get_default_attributes = gtk_label_accessible_get_default_attributes;
1192 }
1193 
1194 /* AtkHypertext implementation {{{2 */
1195 
1196 static AtkHyperlink *
gtk_label_accessible_get_link(AtkHypertext * hypertext,gint idx)1197 gtk_label_accessible_get_link (AtkHypertext *hypertext,
1198                                gint          idx)
1199 {
1200   GtkLabelAccessible *label = GTK_LABEL_ACCESSIBLE (hypertext);
1201   GtkLabelAccessibleLinkImpl *impl;
1202 
1203   impl = (GtkLabelAccessibleLinkImpl *)g_list_nth_data (label->priv->links, idx);
1204 
1205   if (impl)
1206     return ATK_HYPERLINK (impl->link);
1207 
1208   return NULL;
1209 }
1210 
1211 static gint
gtk_label_accessible_get_n_links(AtkHypertext * hypertext)1212 gtk_label_accessible_get_n_links (AtkHypertext *hypertext)
1213 {
1214   GtkWidget *widget;
1215 
1216   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (hypertext));
1217 
1218   return _gtk_label_get_n_links (GTK_LABEL (widget));
1219 }
1220 
1221 static gint
gtk_label_accessible_get_link_index(AtkHypertext * hypertext,gint char_index)1222 gtk_label_accessible_get_link_index (AtkHypertext *hypertext,
1223                                      gint          char_index)
1224 {
1225   GtkWidget *widget;
1226 
1227   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (hypertext));
1228 
1229   return _gtk_label_get_link_at (GTK_LABEL (widget), char_index);
1230 }
1231 
1232 static void
atk_hypertext_interface_init(AtkHypertextIface * iface)1233 atk_hypertext_interface_init (AtkHypertextIface *iface)
1234 {
1235   iface->get_link = gtk_label_accessible_get_link;
1236   iface->get_n_links = gtk_label_accessible_get_n_links;
1237   iface->get_link_index = gtk_label_accessible_get_link_index;
1238 }
1239 
1240 /* vim:set foldmethod=marker expandtab: */
1241