]> Pileus Git - ~andy/gtk/blob - modules/other/gail/gailexpander.c
Use gdk_threads_add_idle. Bug #504571.
[~andy/gtk] / modules / other / gail / gailexpander.c
1 /* GAIL - The GNOME Accessibility Implementation Library
2  * Copyright 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 <string.h>
21 #include <gtk/gtk.h>
22 #include <gdk/gdkkeysyms.h>
23 #include "gailexpander.h"
24 #include <libgail-util/gailmisc.h>
25
26 static void                  gail_expander_class_init       (GailExpanderClass *klass);
27 static void                  gail_expander_object_init      (GailExpander      *expander);
28
29 static G_CONST_RETURN gchar* gail_expander_get_name         (AtkObject         *obj);
30 static gint                  gail_expander_get_n_children   (AtkObject         *obj)
31 ;
32 static AtkObject*            gail_expander_ref_child        (AtkObject         *obj,
33                                                              gint              i);
34
35 static AtkStateSet*          gail_expander_ref_state_set    (AtkObject         *obj);
36 static void                  gail_expander_real_notify_gtk  (GObject           *obj,
37                                                              GParamSpec        *pspec);
38 static void                  gail_expander_map_gtk          (GtkWidget         *widget,
39                                                              gpointer          data);
40
41 static void                  gail_expander_real_initialize  (AtkObject         *obj,
42                                                              gpointer          data);
43 static void                  gail_expander_finalize         (GObject           *object);
44 static void                  gail_expander_init_textutil    (GailExpander      *expander,
45                                                              GtkExpander       *widget);
46
47 static void                  atk_action_interface_init  (AtkActionIface *iface);
48 static gboolean              gail_expander_do_action    (AtkAction      *action,
49                                                          gint           i);
50 static gboolean              idle_do_action             (gpointer       data);
51 static gint                  gail_expander_get_n_actions(AtkAction      *action);
52 static G_CONST_RETURN gchar* gail_expander_get_description
53                                                         (AtkAction      *action,
54                                                          gint           i);
55 static G_CONST_RETURN gchar* gail_expander_get_keybinding 
56                                                         (AtkAction      *action,
57                                                          gint           i);
58 static G_CONST_RETURN gchar* gail_expander_action_get_name
59                                                         (AtkAction      *action,
60                                                          gint           i);
61 static gboolean              gail_expander_set_description
62                                                         (AtkAction      *action,
63                                                          gint           i,
64                                                          const gchar    *desc);
65
66 /* atktext.h */ 
67 static void       atk_text_interface_init          (AtkTextIface        *iface);
68
69 static gchar*     gail_expander_get_text           (AtkText           *text,
70                                                     gint              start_pos,
71                                                     gint              end_pos);
72 static gunichar   gail_expander_get_character_at_offset
73                                                    (AtkText           *text,
74                                                     gint              offset);
75 static gchar*     gail_expander_get_text_before_offset
76                                                    (AtkText           *text,
77                                                     gint              offset,
78                                                     AtkTextBoundary   boundary_type,
79                                                     gint              *start_offset,
80                                                     gint              *end_offset);
81 static gchar*     gail_expander_get_text_at_offset (AtkText           *text,
82                                                     gint              offset,
83                                                     AtkTextBoundary   boundary_type,
84                                                     gint              *start_offset,
85                                                     gint              *end_offset);
86 static gchar*     gail_expander_get_text_after_offset
87                                                    (AtkText           *text,
88                                                     gint              offset,
89                                                     AtkTextBoundary   boundary_type,
90                                                     gint              *start_offset,
91                                                     gint              *end_offset);
92 static gint       gail_expander_get_character_count(AtkText           *text);
93 static void gail_expander_get_character_extents    (AtkText           *text,
94                                                     gint              offset,
95                                                     gint              *x,
96                                                     gint              *y,
97                                                     gint              *width,
98                                                     gint              *height,
99                                                     AtkCoordType      coords);
100 static gint gail_expander_get_offset_at_point      (AtkText           *text,
101                                                     gint              x,
102                                                     gint              y,
103                                                     AtkCoordType      coords);
104 static AtkAttributeSet* gail_expander_get_run_attributes 
105                                                    (AtkText           *text,
106                                                     gint              offset,
107                                                     gint              *start_offset,
108                                                     gint              *end_offset);
109 static AtkAttributeSet* gail_expander_get_default_attributes
110                                                    (AtkText           *text);
111
112 static GailContainer* parent_class = NULL;
113
114 GType
115 gail_expander_get_type (void)
116 {
117   static GType type = 0;
118
119   if (!type)
120     {
121       static const GTypeInfo tinfo =
122       {
123         sizeof (GailExpanderClass),
124         (GBaseInitFunc) NULL, /* base init */
125         (GBaseFinalizeFunc) NULL, /* base finalize */
126         (GClassInitFunc) gail_expander_class_init, /* class init */
127         (GClassFinalizeFunc) NULL, /* class finalize */
128         NULL, /* class data */
129         sizeof (GailExpander), /* instance size */
130         0, /* nb preallocs */
131         (GInstanceInitFunc) gail_expander_object_init, /* instance init */
132         NULL /* value table */
133       };
134
135       static const GInterfaceInfo atk_action_info =
136       {
137         (GInterfaceInitFunc) atk_action_interface_init,
138         (GInterfaceFinalizeFunc) NULL,
139         NULL
140       };
141
142       static const GInterfaceInfo atk_text_info =
143       {
144         (GInterfaceInitFunc) atk_text_interface_init,
145         (GInterfaceFinalizeFunc) NULL,
146         NULL
147       };
148
149       type = g_type_register_static (GAIL_TYPE_CONTAINER,
150                                      "GailExpander", &tinfo, 0);
151
152       g_type_add_interface_static (type, ATK_TYPE_ACTION,
153                                    &atk_action_info);
154       g_type_add_interface_static (type, ATK_TYPE_TEXT,
155                                    &atk_text_info);
156
157     }
158
159   return type;
160 }
161
162 static void
163 gail_expander_class_init (GailExpanderClass *klass)
164 {
165   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
166   AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
167   GailWidgetClass *widget_class;
168
169   widget_class = (GailWidgetClass*)klass;
170   widget_class->notify_gtk = gail_expander_real_notify_gtk;
171
172   gobject_class->finalize = gail_expander_finalize;
173
174   parent_class = g_type_class_peek_parent (klass);
175
176   class->get_name = gail_expander_get_name;
177   class->get_n_children = gail_expander_get_n_children;
178   class->ref_child = gail_expander_ref_child;
179   class->ref_state_set = gail_expander_ref_state_set;
180
181   class->initialize = gail_expander_real_initialize;
182 }
183
184 static void
185 gail_expander_object_init (GailExpander *expander)
186 {
187   expander->activate_description = NULL;
188   expander->activate_keybinding = NULL;
189   expander->action_idle_handler = 0;
190   expander->textutil = NULL;
191 }
192
193 AtkObject* 
194 gail_expander_new (GtkWidget *widget)
195 {
196   GObject *object;
197   AtkObject *accessible;
198
199   g_return_val_if_fail (GTK_IS_EXPANDER (widget), NULL);
200
201   object = g_object_new (GAIL_TYPE_EXPANDER, NULL);
202
203   accessible = ATK_OBJECT (object);
204   atk_object_initialize (accessible, widget);
205
206   return accessible;
207 }
208
209 static G_CONST_RETURN gchar*
210 gail_expander_get_name (AtkObject *obj)
211 {
212   G_CONST_RETURN gchar *name;
213   g_return_val_if_fail (GAIL_IS_EXPANDER (obj), NULL);
214
215   name = ATK_OBJECT_CLASS (parent_class)->get_name (obj);
216   if (name != NULL)
217     return name;
218   else
219     {
220       /*
221        * Get the text on the label
222        */
223       GtkWidget *widget;
224
225       widget = GTK_ACCESSIBLE (obj)->widget;
226       if (widget == NULL)
227         /*
228          * State is defunct
229          */
230         return NULL;
231
232       g_return_val_if_fail (GTK_IS_EXPANDER (widget), NULL);
233
234       return gtk_expander_get_label (GTK_EXPANDER (widget)); 
235     }
236 }
237
238 static gint
239 gail_expander_get_n_children (AtkObject* obj)
240 {
241   GtkWidget *widget;
242   GList *children;
243   gint count = 0;
244
245   g_return_val_if_fail (GAIL_IS_CONTAINER (obj), count);
246
247   widget = GTK_ACCESSIBLE (obj)->widget;
248   if (widget == NULL)
249     return 0;
250
251   children = gtk_container_get_children (GTK_CONTAINER(widget));
252   count = g_list_length (children);
253   g_list_free (children);
254
255   /* See if there is a label - if there is, reduce our count by 1
256    * since we don't want the label included with the children.
257    */
258   if (gtk_expander_get_label_widget (widget))
259     count -= 1;
260
261   return count; 
262 }
263
264 static AtkObject*
265 gail_expander_ref_child (AtkObject *obj,
266                          gint      i)
267 {
268   GList *children, *tmp_list;
269   AtkObject *accessible;
270   GtkWidget *widget;
271   GtkWidget *label;
272   gint index;
273   gint count;
274
275   g_return_val_if_fail (GAIL_IS_CONTAINER (obj), NULL);
276   g_return_val_if_fail ((i >= 0), NULL);
277   widget = GTK_ACCESSIBLE (obj)->widget;
278   if (widget == NULL)
279     return NULL;
280
281   children = gtk_container_get_children (GTK_CONTAINER (widget));
282
283   /* See if there is a label - if there is, we need to skip it
284    * since we don't want the label included with the children.
285    */
286   label = gtk_expander_get_label_widget (widget);
287   if (label) {
288     count = g_list_length (children);
289     for (index = 0; index <= i; index++) {
290       tmp_list = g_list_nth (children, index);
291       if (label == GTK_WIDGET (tmp_list->data)) {
292         i += 1;
293         break;
294       }
295     }
296   }
297
298   tmp_list = g_list_nth (children, i);
299   if (!tmp_list)
300     {
301       g_list_free (children);
302       return NULL;
303     }  
304   accessible = gtk_widget_get_accessible (GTK_WIDGET (tmp_list->data));
305
306   g_list_free (children);
307   g_object_ref (accessible);
308   return accessible; 
309 }
310
311 static void
312 gail_expander_real_initialize (AtkObject *obj,
313                                gpointer   data)
314 {
315   GailExpander *gail_expander = GAIL_EXPANDER (obj);
316   GtkWidget  *expander;
317
318   ATK_OBJECT_CLASS (parent_class)->initialize (obj, data);
319
320   expander = GTK_WIDGET (data);
321   if (GTK_WIDGET_MAPPED (expander))
322     gail_expander_init_textutil (gail_expander, GTK_EXPANDER (expander));
323   else 
324     g_signal_connect (expander,
325                       "map",
326                       G_CALLBACK (gail_expander_map_gtk),
327                       gail_expander);
328  
329   obj->role = ATK_ROLE_TOGGLE_BUTTON;
330 }
331
332 static void
333 gail_expander_map_gtk (GtkWidget *widget,
334                        gpointer data)
335 {
336   GailExpander *expander; 
337
338   expander = GAIL_EXPANDER (data);
339   gail_expander_init_textutil (expander, GTK_EXPANDER (widget));
340 }
341
342 static void
343 gail_expander_real_notify_gtk (GObject    *obj,
344                                GParamSpec *pspec)
345 {
346   AtkObject* atk_obj;
347   GtkExpander *expander;
348   GailExpander *gail_expander;
349
350   expander = GTK_EXPANDER (obj);
351   atk_obj = gtk_widget_get_accessible (GTK_WIDGET (expander));
352 ;
353   if (strcmp (pspec->name, "label") == 0)
354     {
355       const gchar* label_text;
356
357
358       label_text = gtk_expander_get_label (expander);
359
360       gail_expander = GAIL_EXPANDER (atk_obj);
361       if (gail_expander->textutil)
362         gail_text_util_text_setup (gail_expander->textutil, label_text);
363
364       if (atk_obj->name == NULL)
365       {
366         /*
367          * The label has changed so notify a change in accessible-name
368          */
369         g_object_notify (G_OBJECT (atk_obj), "accessible-name");
370       }
371       /*
372        * The label is the only property which can be changed
373        */
374       g_signal_emit_by_name (atk_obj, "visible_data_changed");
375     }
376   else if (strcmp (pspec->name, "expanded") == 0)
377     {
378       atk_object_notify_state_change (atk_obj, ATK_STATE_CHECKED, 
379                                       gtk_expander_get_expanded (expander));
380       atk_object_notify_state_change (atk_obj, ATK_STATE_EXPANDED, 
381                                       gtk_expander_get_expanded (expander));
382       g_signal_emit_by_name (atk_obj, "visible_data_changed");
383     }
384   else
385     GAIL_WIDGET_CLASS (parent_class)->notify_gtk (obj, pspec);
386 }
387
388 static void
389 gail_expander_init_textutil (GailExpander *expander,
390                              GtkExpander  *widget)
391 {
392   const gchar *label_text;
393
394   expander->textutil = gail_text_util_new ();
395   label_text = gtk_expander_get_label (widget);
396   gail_text_util_text_setup (expander->textutil, label_text);
397 }
398
399 static void
400 atk_action_interface_init (AtkActionIface *iface)
401 {
402   g_return_if_fail (iface != NULL);
403
404   iface->do_action = gail_expander_do_action;
405   iface->get_n_actions = gail_expander_get_n_actions;
406   iface->get_description = gail_expander_get_description;
407   iface->get_keybinding = gail_expander_get_keybinding;
408   iface->get_name = gail_expander_action_get_name;
409   iface->set_description = gail_expander_set_description;
410 }
411
412 static gboolean
413 gail_expander_do_action (AtkAction *action,
414                          gint      i)
415 {
416   GtkWidget *widget;
417   GailExpander *expander;
418   gboolean return_value = TRUE;
419
420   widget = GTK_ACCESSIBLE (action)->widget;
421   if (widget == NULL)
422     /*
423      * State is defunct
424      */
425     return FALSE;
426
427   if (!GTK_WIDGET_IS_SENSITIVE (widget) || !GTK_WIDGET_VISIBLE (widget))
428     return FALSE;
429
430   expander = GAIL_EXPANDER (action);
431   switch (i)
432     {
433     case 0:
434       if (expander->action_idle_handler)
435         return_value = FALSE;
436       else
437         expander->action_idle_handler = gdk_threads_add_idle (idle_do_action, expander);
438       break;
439     default:
440       return_value = FALSE;
441       break;
442     }
443   return return_value; 
444 }
445
446 static gboolean
447 idle_do_action (gpointer data)
448 {
449   GtkWidget *widget;
450   GailExpander *gail_expander;
451
452   gail_expander = GAIL_EXPANDER (data);
453   gail_expander->action_idle_handler = 0;
454
455   widget = GTK_ACCESSIBLE (gail_expander)->widget;
456   if (widget == NULL /* State is defunct */ ||
457       !GTK_WIDGET_IS_SENSITIVE (widget) || !GTK_WIDGET_VISIBLE (widget))
458     return FALSE;
459
460   gtk_widget_activate (widget);
461
462   return FALSE;
463 }
464
465 static gint
466 gail_expander_get_n_actions (AtkAction *action)
467 {
468   return 1;
469 }
470
471 static G_CONST_RETURN gchar*
472 gail_expander_get_description (AtkAction *action,
473                                gint      i)
474 {
475   GailExpander *expander;
476   G_CONST_RETURN gchar *return_value;
477
478   expander = GAIL_EXPANDER (action);
479
480   switch (i)
481     {
482     case 0:
483       return_value = expander->activate_description;
484       break;
485     default:
486       return_value = NULL;
487       break;
488     }
489   return return_value; 
490 }
491
492 static G_CONST_RETURN gchar*
493 gail_expander_get_keybinding (AtkAction *action,
494                               gint      i)
495 {
496   GailExpander *expander;
497   gchar *return_value = NULL;
498
499   switch (i)
500     {
501     case 0:
502       {
503         /*
504          * We look for a mnemonic on the label
505          */
506         GtkWidget *widget;
507         GtkWidget *label;
508
509         expander = GAIL_EXPANDER (action);
510         widget = GTK_ACCESSIBLE (expander)->widget;
511         if (widget == NULL)
512           /*
513            * State is defunct
514            */
515           return NULL;
516
517         g_return_val_if_fail (GTK_IS_EXPANDER (widget), NULL);
518
519         label = gtk_expander_get_label_widget (GTK_EXPANDER (widget));
520         if (GTK_IS_LABEL (label))
521           {
522             guint key_val; 
523
524             key_val = gtk_label_get_mnemonic_keyval (GTK_LABEL (label)); 
525             if (key_val != GDK_VoidSymbol)
526               return_value = gtk_accelerator_name (key_val, GDK_MOD1_MASK);
527             g_free (expander->activate_keybinding);
528             expander->activate_keybinding = return_value;
529           }
530         break;
531       }
532     default:
533       break;
534     }
535   return return_value; 
536 }
537
538 static G_CONST_RETURN gchar*
539 gail_expander_action_get_name (AtkAction *action,
540                                gint      i)
541 {
542   G_CONST_RETURN gchar *return_value;
543
544   switch (i)
545     {
546     case 0:
547       return_value = "activate";
548       break;
549     default:
550       return_value = NULL;
551       break;
552     }
553   return return_value; 
554 }
555
556 static gboolean
557 gail_expander_set_description (AtkAction      *action,
558                                gint           i,
559                                const gchar    *desc)
560 {
561   GailExpander *expander;
562   gchar **value;
563
564   expander = GAIL_EXPANDER (action);
565
566   switch (i)
567     {
568     case 0:
569       value = &expander->activate_description;
570       break;
571     default:
572       value = NULL;
573       break;
574     }
575   if (value)
576     {
577       g_free (*value);
578       *value = g_strdup (desc);
579       return TRUE;
580     }
581   else
582     return FALSE;
583 }
584
585 static AtkStateSet*
586 gail_expander_ref_state_set (AtkObject *obj)
587 {
588   AtkStateSet *state_set;
589   GtkWidget *widget;
590   GtkExpander *expander;
591
592   state_set = ATK_OBJECT_CLASS (parent_class)->ref_state_set (obj);
593   widget = GTK_ACCESSIBLE (obj)->widget;
594
595   if (widget == NULL)
596     return state_set;
597
598   expander = GTK_EXPANDER (widget);
599
600   atk_state_set_add_state (state_set, ATK_STATE_EXPANDABLE);
601
602   if (gtk_expander_get_expanded (expander)) {
603     atk_state_set_add_state (state_set, ATK_STATE_CHECKED);
604     atk_state_set_add_state (state_set, ATK_STATE_EXPANDED);
605   }
606
607   return state_set;
608 }
609
610 /* atktext.h */
611
612 static void
613 atk_text_interface_init (AtkTextIface *iface)
614 {
615   g_return_if_fail (iface != NULL);
616   iface->get_text = gail_expander_get_text;
617   iface->get_character_at_offset = gail_expander_get_character_at_offset;
618   iface->get_text_before_offset = gail_expander_get_text_before_offset;
619   iface->get_text_at_offset = gail_expander_get_text_at_offset;
620   iface->get_text_after_offset = gail_expander_get_text_after_offset;
621   iface->get_character_count = gail_expander_get_character_count;
622   iface->get_character_extents = gail_expander_get_character_extents;
623   iface->get_offset_at_point = gail_expander_get_offset_at_point;
624   iface->get_run_attributes = gail_expander_get_run_attributes;
625   iface->get_default_attributes = gail_expander_get_default_attributes;
626 }
627
628 static gchar*
629 gail_expander_get_text (AtkText *text,
630                         gint    start_pos,
631                         gint    end_pos)
632 {
633   GtkWidget *widget;
634   GailExpander *expander;
635   const gchar *label_text;
636
637   widget = GTK_ACCESSIBLE (text)->widget;
638   if (widget == NULL)
639     /* State is defunct */
640     return NULL;
641
642   expander = GAIL_EXPANDER (text);
643   if (!expander->textutil) 
644     gail_expander_init_textutil (expander, GTK_EXPANDER (widget));
645
646   label_text = gtk_expander_get_label (GTK_EXPANDER (widget));
647
648   if (label_text == NULL)
649     return NULL;
650   else
651     return gail_text_util_get_substring (expander->textutil, 
652                                          start_pos, end_pos);
653 }
654
655 static gchar*
656 gail_expander_get_text_before_offset (AtkText         *text,
657                                       gint            offset,
658                                       AtkTextBoundary boundary_type,
659                                       gint            *start_offset,
660                                       gint            *end_offset)
661 {
662   GtkWidget *widget;
663   GailExpander *expander;
664   GtkWidget *label;
665
666   widget = GTK_ACCESSIBLE (text)->widget;
667   if (widget == NULL)
668     /* State is defunct */
669     return NULL;
670
671   expander = GAIL_EXPANDER (text);
672   if (!expander->textutil) 
673     gail_expander_init_textutil (expander, GTK_EXPANDER (widget));
674
675   label = gtk_expander_get_label_widget (GTK_EXPANDER (widget));
676   if (!GTK_IS_LABEL(label))
677     return NULL;
678   return gail_text_util_get_text (expander->textutil,
679                            gtk_label_get_layout (GTK_LABEL (label)),
680                            GAIL_BEFORE_OFFSET, 
681                            boundary_type, offset, start_offset, end_offset); 
682 }
683
684 static gchar*
685 gail_expander_get_text_at_offset (AtkText         *text,
686                                   gint            offset,
687                                   AtkTextBoundary boundary_type,
688                                   gint            *start_offset,
689                                   gint            *end_offset)
690 {
691   GtkWidget *widget;
692   GailExpander *expander;
693   GtkWidget *label;
694
695   widget = GTK_ACCESSIBLE (text)->widget;
696   if (widget == NULL)
697     /* State is defunct */
698     return NULL;
699
700   expander = GAIL_EXPANDER (text);
701   if (!expander->textutil) 
702     gail_expander_init_textutil (expander, GTK_EXPANDER (widget));
703
704   label = gtk_expander_get_label_widget (GTK_EXPANDER (widget));
705   if (!GTK_IS_LABEL(label))
706     return NULL;
707   return gail_text_util_get_text (expander->textutil,
708                            gtk_label_get_layout (GTK_LABEL (label)),
709                            GAIL_AT_OFFSET, 
710                            boundary_type, offset, start_offset, end_offset);
711 }
712
713 static gchar*
714 gail_expander_get_text_after_offset (AtkText         *text,
715                                      gint            offset,
716                                      AtkTextBoundary boundary_type,
717                                      gint            *start_offset,
718                                      gint            *end_offset)
719 {
720   GtkWidget *widget;
721   GailExpander *expander;
722   GtkWidget *label;
723
724   widget = GTK_ACCESSIBLE (text)->widget;
725   if (widget == NULL)
726     /* State is defunct */
727     return NULL;
728
729   expander = GAIL_EXPANDER (text);
730   if (!expander->textutil) 
731     gail_expander_init_textutil (expander, GTK_EXPANDER (widget));
732
733   label = gtk_expander_get_label_widget (GTK_EXPANDER (widget));
734   if (!GTK_IS_LABEL(label))
735     return NULL;
736   return gail_text_util_get_text (expander->textutil,
737                            gtk_label_get_layout (GTK_LABEL (label)),
738                            GAIL_AFTER_OFFSET, 
739                            boundary_type, offset, start_offset, end_offset);
740 }
741
742 static gint
743 gail_expander_get_character_count (AtkText *text)
744 {
745   GtkWidget *widget;
746   GtkWidget *label;
747
748   widget = GTK_ACCESSIBLE (text)->widget;
749   if (widget == NULL)
750     /* State is defunct */
751     return 0;
752
753   label = gtk_expander_get_label_widget (GTK_EXPANDER (widget));
754   if (!GTK_IS_LABEL(label))
755     return 0;
756
757   return g_utf8_strlen (gtk_label_get_text (GTK_LABEL (label)), -1);
758 }
759
760 static void
761 gail_expander_get_character_extents (AtkText      *text,
762                                      gint         offset,
763                                      gint         *x,
764                                      gint       *y,
765                                      gint       *width,
766                                      gint       *height,
767                                      AtkCoordType coords)
768 {
769   GtkWidget *widget;
770   GtkWidget *label;
771   PangoRectangle char_rect;
772   gint index, x_layout, y_layout;
773   const gchar *label_text;
774  
775   widget = GTK_ACCESSIBLE (text)->widget;
776
777   if (widget == NULL)
778     /* State is defunct */
779     return;
780
781   label = gtk_expander_get_label_widget (GTK_EXPANDER (widget));
782   if (!GTK_IS_LABEL(label))
783     return;
784   
785   gtk_label_get_layout_offsets (GTK_LABEL (label), &x_layout, &y_layout);
786   label_text = gtk_label_get_text (GTK_LABEL (label));
787   index = g_utf8_offset_to_pointer (label_text, offset) - label_text;
788   pango_layout_index_to_pos (gtk_label_get_layout (GTK_LABEL (label)), index, &char_rect);
789   
790   gail_misc_get_extents_from_pango_rectangle (label, &char_rect, 
791                     x_layout, y_layout, x, y, width, height, coords);
792
793
794 static gint 
795 gail_expander_get_offset_at_point (AtkText      *text,
796                                    gint         x,
797                                    gint         y,
798                                    AtkCoordType coords)
799
800   GtkWidget *widget;
801   GtkWidget *label;
802   gint index, x_layout, y_layout;
803   const gchar *label_text;
804
805   widget = GTK_ACCESSIBLE (text)->widget;
806   if (widget == NULL)
807     /* State is defunct */
808     return -1;
809   label = gtk_expander_get_label_widget (GTK_EXPANDER (widget));
810
811   if (!GTK_IS_LABEL(label))
812     return -1;
813   
814   gtk_label_get_layout_offsets (GTK_LABEL (label), &x_layout, &y_layout);
815   
816   index = gail_misc_get_index_at_point_in_layout (label, 
817                                               gtk_label_get_layout (GTK_LABEL (label)), 
818                                               x_layout, y_layout, x, y, coords);
819   label_text = gtk_label_get_text (GTK_LABEL (label));
820   if (index == -1)
821     {
822       if (coords == ATK_XY_WINDOW || coords == ATK_XY_SCREEN)
823         return g_utf8_strlen (label_text, -1);
824
825       return index;  
826     }
827   else
828     return g_utf8_pointer_to_offset (label_text, label_text + index);  
829 }
830
831 static AtkAttributeSet*
832 gail_expander_get_run_attributes (AtkText *text,
833                                   gint    offset,
834                                   gint    *start_offset,
835                                   gint    *end_offset)
836 {
837   GtkWidget *widget;
838   GtkWidget *label;
839   AtkAttributeSet *at_set = NULL;
840   GtkJustification justify;
841   GtkTextDirection dir;
842
843   widget = GTK_ACCESSIBLE (text)->widget;
844   if (widget == NULL)
845     /* State is defunct */
846     return NULL;
847
848   label = gtk_expander_get_label_widget (GTK_EXPANDER (widget));
849
850   if (!GTK_IS_LABEL(label))
851     return NULL;
852   
853   /* Get values set for entire label, if any */
854   justify = gtk_label_get_justify (GTK_LABEL (label));
855   if (justify != GTK_JUSTIFY_CENTER)
856     {
857       at_set = gail_misc_add_attribute (at_set, 
858                                         ATK_TEXT_ATTR_JUSTIFICATION,
859      g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_JUSTIFICATION, justify)));
860     }
861   dir = gtk_widget_get_direction (label);
862   if (dir == GTK_TEXT_DIR_RTL)
863     {
864       at_set = gail_misc_add_attribute (at_set, 
865                                         ATK_TEXT_ATTR_DIRECTION,
866      g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_DIRECTION, dir)));
867     }
868
869   at_set = gail_misc_layout_get_run_attributes (at_set,
870                                                 gtk_label_get_layout (GTK_LABEL (label)),
871                                                 (gchar *) gtk_label_get_text (GTK_LABEL (label)),
872                                                 offset,
873                                                 start_offset,
874                                                 end_offset);
875   return at_set;
876 }
877
878 static AtkAttributeSet*
879 gail_expander_get_default_attributes (AtkText *text)
880 {
881   GtkWidget *widget;
882   GtkWidget *label;
883   AtkAttributeSet *at_set = NULL;
884
885   widget = GTK_ACCESSIBLE (text)->widget;
886   if (widget == NULL)
887     /* State is defunct */
888     return NULL;
889
890   label = gtk_expander_get_label_widget (GTK_EXPANDER (widget));
891
892   if (!GTK_IS_LABEL(label))
893     return NULL;
894
895   at_set = gail_misc_get_default_attributes (at_set,
896                                              gtk_label_get_layout (GTK_LABEL (label)),
897                                              widget);
898   return at_set;
899 }
900
901 static gunichar 
902 gail_expander_get_character_at_offset (AtkText *text,
903                                        gint    offset)
904 {
905   GtkWidget *widget;
906   GtkWidget *label;
907   const gchar *string;
908   gchar *index;
909
910   widget = GTK_ACCESSIBLE (text)->widget;
911   if (widget == NULL)
912     /* State is defunct */
913     return '\0';
914
915   label = gtk_expander_get_label_widget (GTK_EXPANDER (widget));
916
917   if (!GTK_IS_LABEL(label))
918     return '\0';
919   string = gtk_label_get_text (GTK_LABEL (label));
920   if (offset >= g_utf8_strlen (string, -1))
921     return '\0';
922   index = g_utf8_offset_to_pointer (string, offset);
923
924   return g_utf8_get_char (index);
925 }
926
927 static void
928 gail_expander_finalize (GObject *object)
929 {
930   GailExpander *expander = GAIL_EXPANDER (object);
931
932   g_free (expander->activate_description);
933   g_free (expander->activate_keybinding);
934   if (expander->action_idle_handler)
935     {
936       g_source_remove (expander->action_idle_handler);
937       expander->action_idle_handler = 0;
938     }
939   if (expander->textutil)
940     g_object_unref (expander->textutil);
941
942   G_OBJECT_CLASS (parent_class)->finalize (object);
943 }