]> Pileus Git - ~andy/gtk/blob - gtk/a11y/gailnotebook.c
gail: Move from modules/other/gail to gtk/a11y
[~andy/gtk] / gtk / a11y / gailnotebook.c
1 /* GAIL - The GNOME Accessibility Implementation 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 #include <gtk/gtk.h>
24 #include "gailnotebook.h"
25 #include "gailnotebookpage.h"
26 #include "gail-private-macros.h"
27
28 static void         gail_notebook_class_init          (GailNotebookClass *klass);
29 static void         gail_notebook_init                (GailNotebook      *notebook);
30 static void         gail_notebook_finalize            (GObject           *object);
31 static void         gail_notebook_real_initialize     (AtkObject         *obj,
32                                                        gpointer          data);
33
34 static void         gail_notebook_real_notify_gtk     (GObject           *obj,
35                                                        GParamSpec        *pspec);
36
37 static AtkObject*   gail_notebook_ref_child           (AtkObject      *obj,
38                                                        gint           i);
39 static gint         gail_notebook_real_remove_gtk     (GtkContainer   *container,
40                                                        GtkWidget      *widget,
41                                                        gpointer       data);    
42 static void         atk_selection_interface_init      (AtkSelectionIface *iface);
43 static gboolean     gail_notebook_add_selection       (AtkSelection   *selection,
44                                                        gint           i);
45 static AtkObject*   gail_notebook_ref_selection       (AtkSelection   *selection,
46                                                        gint           i);
47 static gint         gail_notebook_get_selection_count (AtkSelection   *selection);
48 static gboolean     gail_notebook_is_child_selected   (AtkSelection   *selection,
49                                                        gint           i);
50 static AtkObject*   find_child_in_list                (GList          *list,
51                                                        gint           index);
52 static void         check_cache                       (GailNotebook   *gail_notebook,
53                                                        GtkNotebook    *notebook);
54 static void         reset_cache                       (GailNotebook   *gail_notebook,
55                                                        gint           index);
56 static void         create_notebook_page_accessible   (GailNotebook   *gail_notebook,
57                                                        GtkNotebook    *notebook,
58                                                        gint           index,
59                                                        gboolean       insert_before,
60                                                        GList          *list);
61 static void         gail_notebook_child_parent_set    (GtkWidget      *widget,
62                                                        GtkWidget      *old_parent,
63                                                        gpointer       data);
64 static gboolean     gail_notebook_focus_cb            (GtkWidget      *widget,
65                                                        GtkDirectionType type);
66 static gboolean     gail_notebook_check_focus_tab     (gpointer       data);
67 static void         gail_notebook_destroyed           (gpointer       data);
68
69
70 G_DEFINE_TYPE_WITH_CODE (GailNotebook, gail_notebook, GAIL_TYPE_CONTAINER,
71                          G_IMPLEMENT_INTERFACE (ATK_TYPE_SELECTION, atk_selection_interface_init))
72
73 static void
74 gail_notebook_class_init (GailNotebookClass *klass)
75 {
76   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
77   AtkObjectClass  *class = ATK_OBJECT_CLASS (klass);
78   GailWidgetClass *widget_class;
79   GailContainerClass *container_class;
80
81   widget_class = (GailWidgetClass*)klass;
82   container_class = (GailContainerClass*)klass;
83
84   gobject_class->finalize = gail_notebook_finalize;
85
86   widget_class->notify_gtk = gail_notebook_real_notify_gtk;
87
88   class->ref_child = gail_notebook_ref_child;
89   class->initialize = gail_notebook_real_initialize;
90   /*
91    * We do not provide an implementation of get_n_children
92    * as the implementation in GailContainer returns the correct
93    * number of children.
94    */
95   container_class->remove_gtk = gail_notebook_real_remove_gtk;
96 }
97
98 static void
99 gail_notebook_init (GailNotebook      *notebook)
100 {
101   notebook->page_cache = NULL;
102   notebook->selected_page = -1;
103   notebook->focus_tab_page = -1;
104   notebook->remove_index = -1;
105   notebook->idle_focus_id = 0;
106 }
107
108 static AtkObject*
109 gail_notebook_ref_child (AtkObject      *obj,
110                          gint           i)
111 {
112   AtkObject *accessible = NULL;
113   GailNotebook *gail_notebook;
114   GtkNotebook *gtk_notebook;
115   GtkWidget *widget;
116  
117   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (obj));
118   if (widget == NULL)
119     /*
120      * State is defunct
121      */
122     return NULL;
123
124   gail_notebook = GAIL_NOTEBOOK (obj);
125   
126   gtk_notebook = GTK_NOTEBOOK (widget);
127
128   if (gail_notebook->page_count < gtk_notebook_get_n_pages (gtk_notebook))
129     check_cache (gail_notebook, gtk_notebook);
130
131   accessible = find_child_in_list (gail_notebook->page_cache, i);
132
133   if (accessible != NULL)
134     g_object_ref (accessible);
135
136   return accessible;
137 }
138
139 static void
140 gail_notebook_page_added (GtkNotebook *gtk_notebook,
141                           GtkWidget   *child,
142                           guint        page_num,
143                           gpointer     data)
144 {
145   AtkObject *atk_obj;
146   GailNotebook *notebook;
147
148   atk_obj = gtk_widget_get_accessible (GTK_WIDGET (gtk_notebook));
149   notebook = GAIL_NOTEBOOK (atk_obj);
150   create_notebook_page_accessible (notebook, gtk_notebook, page_num, FALSE, NULL);
151   notebook->page_count++;
152 }
153
154 static void
155 gail_notebook_real_initialize (AtkObject *obj,
156                                gpointer  data)
157 {
158   GailNotebook *notebook;
159   GtkNotebook *gtk_notebook;
160   gint i;
161
162   ATK_OBJECT_CLASS (gail_notebook_parent_class)->initialize (obj, data);
163
164   notebook = GAIL_NOTEBOOK (obj);
165   gtk_notebook = GTK_NOTEBOOK (data);
166   for (i = 0; i < gtk_notebook_get_n_pages (gtk_notebook); i++)
167     {
168       create_notebook_page_accessible (notebook, gtk_notebook, i, FALSE, NULL);
169     }
170   notebook->page_count = i;
171   notebook->selected_page = gtk_notebook_get_current_page (gtk_notebook);
172
173   g_signal_connect (gtk_notebook,
174                     "focus",
175                     G_CALLBACK (gail_notebook_focus_cb),
176                     NULL);
177   g_signal_connect (gtk_notebook,
178                     "page-added",
179                     G_CALLBACK (gail_notebook_page_added),
180                     NULL);
181   g_object_weak_ref (G_OBJECT(gtk_notebook),
182                      (GWeakNotify) gail_notebook_destroyed,
183                      obj);                     
184
185   obj->role = ATK_ROLE_PAGE_TAB_LIST;
186 }
187
188 static void
189 gail_notebook_real_notify_gtk (GObject           *obj,
190                                GParamSpec        *pspec)
191 {
192   GtkWidget *widget;
193   AtkObject* atk_obj;
194
195   widget = GTK_WIDGET (obj);
196   atk_obj = gtk_widget_get_accessible (widget);
197
198   if (strcmp (pspec->name, "page") == 0)
199     {
200       gint page_num, old_page_num;
201       gint focus_page_num = 0;
202       gint old_focus_page_num;
203       GailNotebook *gail_notebook;
204       GtkNotebook *gtk_notebook;
205      
206       gail_notebook = GAIL_NOTEBOOK (atk_obj);
207       gtk_notebook = GTK_NOTEBOOK (widget);
208      
209       if (gail_notebook->page_count < gtk_notebook_get_n_pages (gtk_notebook))
210        check_cache (gail_notebook, gtk_notebook);
211       /*
212        * Notify SELECTED state change for old and new page
213        */
214       old_page_num = gail_notebook->selected_page;
215       page_num = gtk_notebook_get_current_page (gtk_notebook);
216       gail_notebook->selected_page = page_num;
217       gail_notebook->focus_tab_page = page_num;
218       old_focus_page_num = gail_notebook->focus_tab_page;
219
220       if (page_num != old_page_num)
221         {
222           AtkObject *obj;
223
224           if (old_page_num != -1)
225             {
226               obj = gail_notebook_ref_child (atk_obj, old_page_num);
227               if (obj)
228                 {
229                   atk_object_notify_state_change (obj,
230                                                   ATK_STATE_SELECTED,
231                                                   FALSE);
232                   g_object_unref (obj);
233                 }
234             }
235           obj = gail_notebook_ref_child (atk_obj, page_num);
236           if (obj)
237             {
238               atk_object_notify_state_change (obj,
239                                               ATK_STATE_SELECTED,
240                                               TRUE);
241               g_object_unref (obj);
242               /*
243                * The page which is being displayed has changed but there is
244                * no need to tell the focus tracker as the focus page will also 
245                * change or a widget in the page will receive focus if the
246                * Notebook does not have tabs.
247                */
248             }
249           g_signal_emit_by_name (atk_obj, "selection_changed");
250           g_signal_emit_by_name (atk_obj, "visible_data_changed");
251         }
252       if (gtk_notebook_get_show_tabs (gtk_notebook) &&
253          (focus_page_num != old_focus_page_num))
254         {
255           if (gail_notebook->idle_focus_id)
256             g_source_remove (gail_notebook->idle_focus_id);
257           gail_notebook->idle_focus_id = gdk_threads_add_idle (gail_notebook_check_focus_tab, atk_obj);
258         }
259     }
260   else
261     GAIL_WIDGET_CLASS (gail_notebook_parent_class)->notify_gtk (obj, pspec);
262 }
263
264 static void
265 gail_notebook_finalize (GObject            *object)
266 {
267   GailNotebook *notebook = GAIL_NOTEBOOK (object);
268   GList *list;
269
270   /*
271    * Get rid of the GailNotebookPage objects which we have cached.
272    */
273   list = notebook->page_cache;
274   if (list != NULL)
275     {
276       while (list)
277         {
278           g_object_unref (list->data);
279           list = list->next;
280         }
281     }
282
283   g_list_free (notebook->page_cache);
284
285   if (notebook->idle_focus_id)
286     g_source_remove (notebook->idle_focus_id);
287
288   G_OBJECT_CLASS (gail_notebook_parent_class)->finalize (object);
289 }
290
291 static void
292 atk_selection_interface_init (AtkSelectionIface *iface)
293 {
294   iface->add_selection = gail_notebook_add_selection;
295   iface->ref_selection = gail_notebook_ref_selection;
296   iface->get_selection_count = gail_notebook_get_selection_count;
297   iface->is_child_selected = gail_notebook_is_child_selected;
298   /*
299    * The following don't make any sense for GtkNotebook widgets.
300    * Unsupported AtkSelection interfaces:
301    * clear_selection();
302    * remove_selection();
303    * select_all_selection();
304    */
305 }
306
307 /*
308  * GtkNotebook only supports the selection of one page at a time. 
309  * Selecting a page unselects any previous selection, so this 
310  * changes the current selection instead of adding to it.
311  */
312 static gboolean
313 gail_notebook_add_selection (AtkSelection *selection,
314                              gint         i)
315 {
316   GtkNotebook *notebook;
317   GtkWidget *widget;
318   
319   widget =  gtk_accessible_get_widget (GTK_ACCESSIBLE (selection));
320   if (widget == NULL)
321     /*
322      * State is defunct
323      */
324     return FALSE;
325   
326   notebook = GTK_NOTEBOOK (widget);
327   gtk_notebook_set_current_page (notebook, i);
328   return TRUE;
329 }
330
331 static AtkObject*
332 gail_notebook_ref_selection (AtkSelection *selection,
333                              gint i)
334 {
335   AtkObject *accessible;
336   GtkWidget *widget;
337   GtkNotebook *notebook;
338   gint pagenum;
339   
340   /*
341    * A note book can have only one selection.
342    */
343   gail_return_val_if_fail (i == 0, NULL);
344   g_return_val_if_fail (GAIL_IS_NOTEBOOK (selection), NULL);
345   
346   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection));
347   if (widget == NULL)
348     /* State is defunct */
349         return NULL;
350   
351   notebook = GTK_NOTEBOOK (widget);
352   pagenum = gtk_notebook_get_current_page (notebook);
353   gail_return_val_if_fail (pagenum != -1, NULL);
354   accessible = gail_notebook_ref_child (ATK_OBJECT (selection), pagenum);
355
356   return accessible;
357 }
358
359 /*
360  * Always return 1 because there can only be one page
361  * selected at any time
362  */
363 static gint
364 gail_notebook_get_selection_count (AtkSelection *selection)
365 {
366   GtkWidget *widget;
367   GtkNotebook *notebook;
368   
369   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection));
370   if (widget == NULL)
371     /*
372      * State is defunct
373      */
374     return 0;
375
376   notebook = GTK_NOTEBOOK (widget);
377   if (notebook == NULL || gtk_notebook_get_current_page (notebook) == -1)
378     return 0;
379   else
380     return 1;
381 }
382
383 static gboolean
384 gail_notebook_is_child_selected (AtkSelection *selection,
385                                  gint i)
386 {
387   GtkWidget *widget;
388   GtkNotebook *notebook;
389   gint pagenumber;
390
391   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (selection));
392   if (widget == NULL)
393     /* 
394      * State is defunct
395      */
396     return FALSE;
397
398   
399   notebook = GTK_NOTEBOOK (widget);
400   pagenumber = gtk_notebook_get_current_page(notebook);
401
402   if (pagenumber == i)
403     return TRUE;
404   else
405     return FALSE; 
406 }
407
408 static AtkObject*
409 find_child_in_list (GList *list,
410                     gint  index)
411 {
412   AtkObject *obj = NULL;
413
414   while (list)
415     {
416       if (GAIL_NOTEBOOK_PAGE (list->data)->index == index)
417         {
418           obj = ATK_OBJECT (list->data);
419           break;
420         }
421       list = list->next;
422     }
423   return obj;
424 }
425
426 static void
427 check_cache (GailNotebook *gail_notebook,
428              GtkNotebook  *notebook)
429 {
430   GList *gtk_list;
431   GList *gail_list;
432   gint i;
433
434   gtk_list = gtk_container_get_children (GTK_CONTAINER (notebook));
435   gail_list = gail_notebook->page_cache;
436
437   i = 0;
438   while (gtk_list)
439     {
440       if (!gail_list)
441         {
442           create_notebook_page_accessible (gail_notebook, notebook, i, FALSE, NULL);
443         }
444       else if (GAIL_NOTEBOOK_PAGE (gail_list->data)->page != gtk_list->data)
445         {
446           create_notebook_page_accessible (gail_notebook, notebook, i, TRUE, gail_list);
447         }
448       else
449         {
450           gail_list = gail_list->next;
451         }
452       i++;
453       gtk_list = gtk_list->next;
454     }
455   g_list_free (gtk_list);
456
457   gail_notebook->page_count = i;
458 }
459
460 static void
461 reset_cache (GailNotebook *gail_notebook,
462              gint         index)
463 {
464   GList *l;
465
466   for (l = gail_notebook->page_cache; l; l = l->next)
467     {
468       if (GAIL_NOTEBOOK_PAGE (l->data)->index > index)
469         GAIL_NOTEBOOK_PAGE (l->data)->index -= 1;
470     }
471 }
472
473 static void
474 create_notebook_page_accessible (GailNotebook *gail_notebook,
475                                  GtkNotebook  *notebook,
476                                  gint         index,
477                                  gboolean     insert_before,
478                                  GList        *list)
479 {
480   AtkObject *obj;
481
482   obj = gail_notebook_page_new (notebook, index);
483   g_object_ref (obj);
484   if (insert_before)
485     gail_notebook->page_cache = g_list_insert_before (gail_notebook->page_cache, list, obj);
486   else
487     gail_notebook->page_cache = g_list_append (gail_notebook->page_cache, obj);
488   g_signal_connect (gtk_notebook_get_nth_page (notebook, index), 
489                     "parent_set",
490                     G_CALLBACK (gail_notebook_child_parent_set),
491                     obj);
492 }
493
494 static void
495 gail_notebook_child_parent_set (GtkWidget *widget,
496                                 GtkWidget *old_parent,
497                                 gpointer   data)
498 {
499   GailNotebook *gail_notebook;
500
501   gail_return_if_fail (old_parent != NULL);
502   gail_notebook = GAIL_NOTEBOOK (gtk_widget_get_accessible (old_parent));
503   gail_notebook->remove_index = GAIL_NOTEBOOK_PAGE (data)->index;
504 }
505
506 static gint
507 gail_notebook_real_remove_gtk (GtkContainer *container,
508                                GtkWidget    *widget,
509                                gpointer      data)    
510 {
511   GailNotebook *gail_notebook;
512   AtkObject *obj;
513   gint index;
514
515   g_return_val_if_fail (container != NULL, 1);
516   gail_notebook = GAIL_NOTEBOOK (gtk_widget_get_accessible (GTK_WIDGET (container)));
517   index = gail_notebook->remove_index;
518   gail_notebook->remove_index = -1;
519
520   obj = find_child_in_list (gail_notebook->page_cache, index);
521   g_return_val_if_fail (obj, 1);
522   gail_notebook->page_cache = g_list_remove (gail_notebook->page_cache, obj);
523   gail_notebook->page_count -= 1;
524   reset_cache (gail_notebook, index);
525   g_signal_emit_by_name (gail_notebook,
526                          "children_changed::remove",
527                           GAIL_NOTEBOOK_PAGE (obj)->index, 
528                           obj, NULL);
529   g_object_unref (obj);
530   return 1;
531 }
532
533 static gboolean
534 gail_notebook_focus_cb (GtkWidget      *widget,
535                         GtkDirectionType type)
536 {
537   AtkObject *atk_obj = gtk_widget_get_accessible (widget);
538   GailNotebook *gail_notebook = GAIL_NOTEBOOK (atk_obj);
539
540   switch (type)
541     {
542     case GTK_DIR_LEFT:
543     case GTK_DIR_RIGHT:
544       if (gail_notebook->idle_focus_id == 0)
545         gail_notebook->idle_focus_id = gdk_threads_add_idle (gail_notebook_check_focus_tab, atk_obj);
546       break;
547     default:
548       break;
549     }
550   return FALSE;
551 }
552
553 static gboolean
554 gail_notebook_check_focus_tab (gpointer data)
555 {
556   GtkWidget *widget;
557   AtkObject *atk_obj;
558   gint focus_page_num, old_focus_page_num;
559   GailNotebook *gail_notebook;
560   GtkNotebook *gtk_notebook;
561
562   atk_obj = ATK_OBJECT (data);
563   gail_notebook = GAIL_NOTEBOOK (atk_obj);
564   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_obj));
565
566   gtk_notebook = GTK_NOTEBOOK (widget);
567
568   gail_notebook->idle_focus_id = 0;
569
570   focus_page_num = gtk_notebook_get_current_page (gtk_notebook);
571   if (focus_page_num == -1)
572     return FALSE;
573
574   old_focus_page_num = gail_notebook->focus_tab_page;
575   gail_notebook->focus_tab_page = focus_page_num;
576   if (old_focus_page_num != focus_page_num)
577     {
578       AtkObject *obj;
579
580       obj = atk_object_ref_accessible_child (atk_obj, focus_page_num);
581       atk_focus_tracker_notify (obj);
582       g_object_unref (obj);
583     }
584
585   return FALSE;
586 }
587
588 static void
589 gail_notebook_destroyed (gpointer data)
590 {
591   GailNotebook *gail_notebook = GAIL_NOTEBOOK (data);
592
593   if (gail_notebook->idle_focus_id)
594     {
595       g_source_remove (gail_notebook->idle_focus_id);
596       gail_notebook->idle_focus_id = 0;
597     }
598 }