forked from AuroraMiddleware/gtk
995 lines
29 KiB
C
995 lines
29 KiB
C
/* GTK+ - accessibility implementations
|
|
* Copyright 2001 Sun Microsystems Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gtkaccessibility.h"
|
|
#include "gtkaccessibilityutil.h"
|
|
#include "gtkaccessibilitymisc.h"
|
|
|
|
#include "gtkwindowaccessible.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <gdk/gdk.h>
|
|
#include <gtk/gtkx.h>
|
|
#include <gtk/gtkentry.h>
|
|
#include <gtk/gtknotebook.h>
|
|
#include <gtk/gtkmenuitem.h>
|
|
#include <gtk/gtkmenu.h>
|
|
#include <gtk/gtkmenubar.h>
|
|
#include <gtk/gtktogglebutton.h>
|
|
#include <gtk/gtkcombobox.h>
|
|
#include <gtk/gtkaccessible.h>
|
|
|
|
#ifdef GDK_WINDOWING_X11
|
|
#include <atk-bridge.h>
|
|
#endif
|
|
|
|
static gboolean gail_focus_watcher (GSignalInvocationHint *ihint,
|
|
guint n_param_values,
|
|
const GValue *param_values,
|
|
gpointer data);
|
|
static gboolean gail_select_watcher (GSignalInvocationHint *ihint,
|
|
guint n_param_values,
|
|
const GValue *param_values,
|
|
gpointer data);
|
|
static gboolean gail_deselect_watcher (GSignalInvocationHint *ihint,
|
|
guint n_param_values,
|
|
const GValue *param_values,
|
|
gpointer data);
|
|
static gboolean gail_switch_page_watcher(GSignalInvocationHint *ihint,
|
|
guint n_param_values,
|
|
const GValue *param_values,
|
|
gpointer data);
|
|
static void gail_finish_select (GtkWidget *widget);
|
|
static void gail_map_cb (GtkWidget *widget);
|
|
static void gail_map_submenu_cb (GtkWidget *widget);
|
|
static gint gail_focus_idle_handler (gpointer data);
|
|
static void gail_focus_notify (GtkWidget *widget);
|
|
static void gail_focus_notify_when_idle (GtkWidget *widget);
|
|
|
|
static void gail_focus_tracker_init (void);
|
|
static void gail_focus_object_destroyed (gpointer data);
|
|
static void gail_focus_tracker (AtkObject *object);
|
|
static void gail_set_focus_widget (GtkWidget *focus_widget,
|
|
GtkWidget *widget);
|
|
static void gail_set_focus_object (AtkObject *focus_obj,
|
|
AtkObject *obj);
|
|
|
|
GtkWidget* _focus_widget = NULL;
|
|
static GtkWidget* next_focus_widget = NULL;
|
|
static gboolean was_deselect = FALSE;
|
|
static GtkWidget* subsequent_focus_widget = NULL;
|
|
static GtkWidget* focus_before_menu = NULL;
|
|
static guint focus_notify_handler = 0;
|
|
static guint focus_tracker_id = 0;
|
|
static GQuark quark_focus_object = 0;
|
|
static int initialized = FALSE;
|
|
|
|
static AtkObject*
|
|
get_accessible_for_widget (GtkWidget *widget,
|
|
gboolean *transient)
|
|
{
|
|
AtkObject *obj = NULL;
|
|
|
|
*transient = FALSE;
|
|
if (!widget)
|
|
return NULL;
|
|
|
|
if (GTK_IS_ENTRY (widget))
|
|
;
|
|
else if (GTK_IS_NOTEBOOK (widget))
|
|
{
|
|
GtkNotebook *notebook;
|
|
gint page_num = -1;
|
|
|
|
notebook = GTK_NOTEBOOK (widget);
|
|
page_num = gtk_notebook_get_current_page (notebook);
|
|
if (page_num != -1)
|
|
{
|
|
obj = gtk_widget_get_accessible (widget);
|
|
obj = atk_object_ref_accessible_child (obj, page_num);
|
|
g_object_unref (obj);
|
|
}
|
|
}
|
|
else if (GTK_IS_TOGGLE_BUTTON (widget))
|
|
{
|
|
GtkWidget *other_widget = gtk_widget_get_parent (widget);
|
|
if (GTK_IS_COMBO_BOX (other_widget))
|
|
{
|
|
gail_set_focus_widget (other_widget, widget);
|
|
widget = other_widget;
|
|
}
|
|
}
|
|
if (obj == NULL)
|
|
{
|
|
AtkObject *focus_object;
|
|
|
|
obj = gtk_widget_get_accessible (widget);
|
|
focus_object = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
|
|
/*
|
|
* We check whether the object for this focus_object has been deleted.
|
|
* This can happen when navigating to an empty directory in nautilus.
|
|
* See bug #141907.
|
|
*/
|
|
if (ATK_IS_GOBJECT_ACCESSIBLE (focus_object))
|
|
{
|
|
if (!atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (focus_object)))
|
|
focus_object = NULL;
|
|
}
|
|
if (focus_object)
|
|
obj = focus_object;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
static gboolean
|
|
gail_focus_watcher (GSignalInvocationHint *ihint,
|
|
guint n_param_values,
|
|
const GValue *param_values,
|
|
gpointer data)
|
|
{
|
|
GObject *object;
|
|
GtkWidget *widget;
|
|
GdkEvent *event;
|
|
|
|
object = g_value_get_object (param_values + 0);
|
|
g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
|
|
|
|
event = g_value_get_boxed (param_values + 1);
|
|
widget = GTK_WIDGET (object);
|
|
|
|
if (event->type == GDK_FOCUS_CHANGE)
|
|
{
|
|
if (event->focus_change.in)
|
|
{
|
|
if (GTK_IS_WINDOW (widget))
|
|
{
|
|
GtkWidget *focus_widget;
|
|
GtkWindow *window;
|
|
GtkWindowType type;
|
|
|
|
window = GTK_WINDOW (widget);
|
|
focus_widget = gtk_window_get_focus (window);
|
|
g_object_get (window, "type", &type, NULL);
|
|
|
|
if (focus_widget)
|
|
{
|
|
/*
|
|
* If we already have a potential focus widget set this
|
|
* windows's focus widget to focus_before_menu so that
|
|
* it will be reported when menu item is unset.
|
|
*/
|
|
if (next_focus_widget)
|
|
{
|
|
if (GTK_IS_MENU_ITEM (next_focus_widget) &&
|
|
!focus_before_menu)
|
|
{
|
|
void *vp_focus_before_menu = &focus_before_menu;
|
|
focus_before_menu = focus_widget;
|
|
g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
widget = focus_widget;
|
|
}
|
|
else if (type == GTK_WINDOW_POPUP)
|
|
{
|
|
if (GTK_IS_BIN (widget))
|
|
{
|
|
GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
|
|
|
|
if (GTK_IS_WIDGET (child) && gtk_widget_has_grab (child))
|
|
{
|
|
if (GTK_IS_MENU_SHELL (child))
|
|
{
|
|
if (gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (child)))
|
|
{
|
|
/*
|
|
* We have a menu which has a menu item selected
|
|
* so we do not report focus on the menu.
|
|
*/
|
|
return TRUE;
|
|
}
|
|
}
|
|
widget = child;
|
|
}
|
|
}
|
|
else /* popup window has no children; this edge case occurs in some custom code (OOo for instance) */
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
else /* Widget is a non-popup toplevel with no focus children;
|
|
don't emit for this case either, as it's useless */
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (next_focus_widget)
|
|
{
|
|
GtkWidget *toplevel;
|
|
|
|
toplevel = gtk_widget_get_toplevel (next_focus_widget);
|
|
if (toplevel == widget)
|
|
next_focus_widget = NULL;
|
|
}
|
|
/* focus out */
|
|
widget = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (event->type == GDK_MOTION_NOTIFY && gtk_widget_has_focus (widget))
|
|
{
|
|
if (widget == _focus_widget)
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
#ifdef GDK_WINDOWING_X11
|
|
/*
|
|
* If the focus widget is a GtkSocket without a plug
|
|
* then ignore the focus notification as the embedded
|
|
* plug will report a focus notification.
|
|
*/
|
|
if (GTK_IS_SOCKET (widget) &&
|
|
gtk_socket_get_plug_window (GTK_SOCKET (widget)) != NULL)
|
|
return TRUE;
|
|
#endif
|
|
|
|
/*
|
|
* The widget may not yet be visible on the screen so we wait until it is.
|
|
*/
|
|
gail_focus_notify_when_idle (widget);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gail_select_watcher (GSignalInvocationHint *ihint,
|
|
guint n_param_values,
|
|
const GValue *param_values,
|
|
gpointer data)
|
|
{
|
|
GObject *object;
|
|
GtkWidget *widget;
|
|
|
|
object = g_value_get_object (param_values + 0);
|
|
g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
|
|
|
|
widget = GTK_WIDGET (object);
|
|
|
|
if (!gtk_widget_get_mapped (widget))
|
|
{
|
|
g_signal_connect (widget, "map",
|
|
G_CALLBACK (gail_map_cb),
|
|
NULL);
|
|
}
|
|
else
|
|
gail_finish_select (widget);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gail_finish_select (GtkWidget *widget)
|
|
{
|
|
if (GTK_IS_MENU_ITEM (widget))
|
|
{
|
|
GtkMenuItem* menu_item;
|
|
GtkWidget *submenu;
|
|
|
|
menu_item = GTK_MENU_ITEM (widget);
|
|
submenu = gtk_menu_item_get_submenu (menu_item);
|
|
if (submenu &&
|
|
!gtk_widget_get_mapped (submenu))
|
|
{
|
|
/*
|
|
* If the submenu is not visble, wait until it is before
|
|
* reporting focus on the menu item.
|
|
*/
|
|
gulong handler_id;
|
|
|
|
handler_id = g_signal_handler_find (submenu,
|
|
G_SIGNAL_MATCH_FUNC,
|
|
g_signal_lookup ("map",
|
|
GTK_TYPE_WINDOW),
|
|
0,
|
|
NULL,
|
|
(gpointer) gail_map_submenu_cb,
|
|
NULL);
|
|
if (!handler_id)
|
|
g_signal_connect (submenu, "map",
|
|
G_CALLBACK (gail_map_submenu_cb),
|
|
NULL);
|
|
return;
|
|
}
|
|
/*
|
|
* If we are waiting to report focus on a menubar or a menu item
|
|
* because of a previous deselect, cancel it.
|
|
*/
|
|
if (was_deselect &&
|
|
focus_notify_handler &&
|
|
next_focus_widget &&
|
|
(GTK_IS_MENU_BAR (next_focus_widget) ||
|
|
GTK_IS_MENU_ITEM (next_focus_widget)))
|
|
{
|
|
void *vp_next_focus_widget = &next_focus_widget;
|
|
g_source_remove (focus_notify_handler);
|
|
g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
|
|
next_focus_widget = NULL;
|
|
focus_notify_handler = 0;
|
|
was_deselect = FALSE;
|
|
}
|
|
}
|
|
/*
|
|
* If previously focused widget is not a GtkMenuItem or a GtkMenu,
|
|
* keep track of it so we can return to it after menubar is deactivated
|
|
*/
|
|
if (_focus_widget &&
|
|
!GTK_IS_MENU_ITEM (_focus_widget) &&
|
|
!GTK_IS_MENU (_focus_widget))
|
|
{
|
|
void *vp_focus_before_menu = &focus_before_menu;
|
|
focus_before_menu = _focus_widget;
|
|
g_object_add_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
|
|
|
|
}
|
|
gail_focus_notify_when_idle (widget);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
gail_map_cb (GtkWidget *widget)
|
|
{
|
|
gail_finish_select (widget);
|
|
}
|
|
|
|
static void
|
|
gail_map_submenu_cb (GtkWidget *widget)
|
|
{
|
|
if (GTK_IS_MENU (widget))
|
|
{
|
|
GtkWidget *parent_menu_item;
|
|
|
|
parent_menu_item = gtk_menu_get_attach_widget (GTK_MENU (widget));
|
|
if (parent_menu_item)
|
|
gail_finish_select (parent_menu_item);
|
|
}
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gail_deselect_watcher (GSignalInvocationHint *ihint,
|
|
guint n_param_values,
|
|
const GValue *param_values,
|
|
gpointer data)
|
|
{
|
|
GObject *object;
|
|
GtkWidget *widget;
|
|
GtkWidget *menu_shell;
|
|
|
|
object = g_value_get_object (param_values + 0);
|
|
g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
|
|
|
|
widget = GTK_WIDGET (object);
|
|
|
|
if (!GTK_IS_MENU_ITEM (widget))
|
|
return TRUE;
|
|
|
|
if (subsequent_focus_widget == widget)
|
|
subsequent_focus_widget = NULL;
|
|
|
|
menu_shell = gtk_widget_get_parent (widget);
|
|
if (GTK_IS_MENU_SHELL (menu_shell))
|
|
{
|
|
GtkWidget *parent_menu_shell;
|
|
|
|
parent_menu_shell = gtk_menu_shell_get_parent_shell (GTK_MENU_SHELL (menu_shell));
|
|
if (parent_menu_shell)
|
|
{
|
|
GtkWidget *active_menu_item;
|
|
|
|
active_menu_item = gtk_menu_shell_get_selected_item (GTK_MENU_SHELL (parent_menu_shell));
|
|
if (active_menu_item)
|
|
{
|
|
gail_focus_notify_when_idle (active_menu_item);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!GTK_IS_MENU_BAR (menu_shell))
|
|
{
|
|
gail_focus_notify_when_idle (menu_shell);
|
|
}
|
|
}
|
|
}
|
|
was_deselect = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gail_switch_page_watcher (GSignalInvocationHint *ihint,
|
|
guint n_param_values,
|
|
const GValue *param_values,
|
|
gpointer data)
|
|
{
|
|
GObject *object;
|
|
GtkWidget *widget;
|
|
|
|
object = g_value_get_object (param_values + 0);
|
|
g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
|
|
|
|
widget = GTK_WIDGET (object);
|
|
|
|
if (!GTK_IS_NOTEBOOK (widget))
|
|
return TRUE;
|
|
|
|
if (gtk_notebook_get_current_page (GTK_NOTEBOOK (widget)) == -1)
|
|
return TRUE;
|
|
|
|
gail_focus_notify_when_idle (widget);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gail_focus_idle_handler (gpointer data)
|
|
{
|
|
focus_notify_handler = 0;
|
|
/*
|
|
* The widget which was to receive focus may have been removed
|
|
*/
|
|
if (!next_focus_widget)
|
|
{
|
|
if (next_focus_widget != data)
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
void *vp_next_focus_widget = &next_focus_widget;
|
|
g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
|
|
next_focus_widget = NULL;
|
|
}
|
|
|
|
gail_focus_notify (data);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gail_focus_notify (GtkWidget *widget)
|
|
{
|
|
AtkObject *atk_obj;
|
|
gboolean transient;
|
|
|
|
if (widget != _focus_widget)
|
|
{
|
|
if (_focus_widget)
|
|
{
|
|
void *vp_focus_widget = &_focus_widget;
|
|
g_object_remove_weak_pointer (G_OBJECT (_focus_widget), vp_focus_widget);
|
|
}
|
|
_focus_widget = widget;
|
|
if (_focus_widget)
|
|
{
|
|
void *vp_focus_widget = &_focus_widget;
|
|
g_object_add_weak_pointer (G_OBJECT (_focus_widget), vp_focus_widget);
|
|
/*
|
|
* The UI may not have been updated yet; e.g. in gtkhtml2
|
|
* html_view_layout() is called in a idle handler
|
|
*/
|
|
if (_focus_widget == focus_before_menu)
|
|
{
|
|
void *vp_focus_before_menu = &focus_before_menu;
|
|
g_object_remove_weak_pointer (G_OBJECT (focus_before_menu), vp_focus_before_menu);
|
|
focus_before_menu = NULL;
|
|
}
|
|
}
|
|
gail_focus_notify_when_idle (_focus_widget);
|
|
}
|
|
else
|
|
{
|
|
if (_focus_widget)
|
|
atk_obj = get_accessible_for_widget (_focus_widget, &transient);
|
|
else
|
|
atk_obj = NULL;
|
|
/*
|
|
* Do not report focus on redundant object
|
|
*/
|
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
|
if (atk_obj &&
|
|
(atk_object_get_role(atk_obj) != ATK_ROLE_REDUNDANT_OBJECT))
|
|
atk_focus_tracker_notify (atk_obj);
|
|
G_GNUC_END_IGNORE_DEPRECATIONS;
|
|
if (atk_obj && transient)
|
|
g_object_unref (atk_obj);
|
|
if (subsequent_focus_widget)
|
|
{
|
|
GtkWidget *tmp_widget = subsequent_focus_widget;
|
|
subsequent_focus_widget = NULL;
|
|
gail_focus_notify_when_idle (tmp_widget);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gail_focus_notify_when_idle (GtkWidget *widget)
|
|
{
|
|
if (focus_notify_handler)
|
|
{
|
|
if (widget)
|
|
{
|
|
/*
|
|
* Ignore focus request when menu item is going to be focused.
|
|
* See bug #124232.
|
|
*/
|
|
if (GTK_IS_MENU_ITEM (next_focus_widget) && !GTK_IS_MENU_ITEM (widget))
|
|
return;
|
|
|
|
if (next_focus_widget)
|
|
{
|
|
if (GTK_IS_MENU_ITEM (next_focus_widget) && GTK_IS_MENU_ITEM (widget))
|
|
{
|
|
if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (next_focus_widget)) == gtk_widget_get_parent (widget))
|
|
{
|
|
if (subsequent_focus_widget)
|
|
g_assert_not_reached ();
|
|
subsequent_focus_widget = widget;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
g_source_remove (focus_notify_handler);
|
|
if (next_focus_widget)
|
|
{
|
|
void *vp_next_focus_widget = &next_focus_widget;
|
|
g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
|
|
next_focus_widget = NULL;
|
|
}
|
|
}
|
|
else
|
|
/*
|
|
* Ignore if focus is being set to NULL and we are waiting to set focus
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (widget)
|
|
{
|
|
void *vp_next_focus_widget = &next_focus_widget;
|
|
next_focus_widget = widget;
|
|
g_object_add_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* We are about to report focus as NULL so remove the weak pointer
|
|
* for the widget we were waiting to report focus on.
|
|
*/
|
|
if (next_focus_widget)
|
|
{
|
|
void *vp_next_focus_widget = &next_focus_widget;
|
|
g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
|
|
next_focus_widget = NULL;
|
|
}
|
|
}
|
|
|
|
focus_notify_handler = gdk_threads_add_idle (gail_focus_idle_handler, widget);
|
|
}
|
|
|
|
static gboolean
|
|
gail_deactivate_watcher (GSignalInvocationHint *ihint,
|
|
guint n_param_values,
|
|
const GValue *param_values,
|
|
gpointer data)
|
|
{
|
|
GObject *object;
|
|
GtkWidget *widget;
|
|
GtkMenuShell *shell;
|
|
GtkWidget *focus = NULL;
|
|
|
|
object = g_value_get_object (param_values + 0);
|
|
g_return_val_if_fail (GTK_IS_WIDGET(object), FALSE);
|
|
widget = GTK_WIDGET (object);
|
|
|
|
g_return_val_if_fail (GTK_IS_MENU_SHELL(widget), TRUE);
|
|
shell = GTK_MENU_SHELL(widget);
|
|
if (! gtk_menu_shell_get_parent_shell (shell))
|
|
focus = focus_before_menu;
|
|
|
|
/*
|
|
* If we are waiting to report focus on a menubar or a menu item
|
|
* because of a previous deselect, cancel it.
|
|
*/
|
|
if (was_deselect &&
|
|
focus_notify_handler &&
|
|
next_focus_widget &&
|
|
(GTK_IS_MENU_BAR (next_focus_widget) ||
|
|
GTK_IS_MENU_ITEM (next_focus_widget)))
|
|
{
|
|
void *vp_next_focus_widget = &next_focus_widget;
|
|
g_source_remove (focus_notify_handler);
|
|
g_object_remove_weak_pointer (G_OBJECT (next_focus_widget), vp_next_focus_widget);
|
|
next_focus_widget = NULL;
|
|
focus_notify_handler = 0;
|
|
was_deselect = FALSE;
|
|
}
|
|
gail_focus_notify_when_idle (focus);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gail_focus_tracker_init (void)
|
|
{
|
|
static gboolean emission_hooks_added = FALSE;
|
|
|
|
if (!emission_hooks_added)
|
|
{
|
|
/*
|
|
* We cannot be sure that the classes exist so we make sure that they do.
|
|
*/
|
|
g_type_class_ref (GTK_TYPE_WIDGET);
|
|
g_type_class_ref (GTK_TYPE_MENU_ITEM);
|
|
g_type_class_ref (GTK_TYPE_MENU_SHELL);
|
|
g_type_class_ref (GTK_TYPE_NOTEBOOK);
|
|
|
|
/*
|
|
* We listen for event_after signal and then check that the
|
|
* event was a focus in event so we get called after the event.
|
|
*/
|
|
g_signal_add_emission_hook (
|
|
g_signal_lookup ("event-after", GTK_TYPE_WIDGET), 0,
|
|
gail_focus_watcher, NULL, (GDestroyNotify) NULL);
|
|
/*
|
|
* A "select" signal is emitted when arrow key is used to
|
|
* move to a list item in the popup window of a GtkCombo or
|
|
* a menu item in a menu.
|
|
*/
|
|
g_signal_add_emission_hook (
|
|
g_signal_lookup ("select", GTK_TYPE_MENU_ITEM), 0,
|
|
gail_select_watcher, NULL, (GDestroyNotify) NULL);
|
|
|
|
/*
|
|
* A "deselect" signal is emitted when arrow key is used to
|
|
* move from a menu item in a menu to the parent menu.
|
|
*/
|
|
g_signal_add_emission_hook (
|
|
g_signal_lookup ("deselect", GTK_TYPE_MENU_ITEM), 0,
|
|
gail_deselect_watcher, NULL, (GDestroyNotify) NULL);
|
|
|
|
/*
|
|
* We listen for deactivate signals on menushells to determine
|
|
* when the "focus" has left the menus.
|
|
*/
|
|
g_signal_add_emission_hook (
|
|
g_signal_lookup ("deactivate", GTK_TYPE_MENU_SHELL), 0,
|
|
gail_deactivate_watcher, NULL, (GDestroyNotify) NULL);
|
|
|
|
/*
|
|
* We listen for "switch-page" signal on a GtkNotebook to notify
|
|
* when page has changed because of clicking on a notebook tab.
|
|
*/
|
|
g_signal_add_emission_hook (
|
|
g_signal_lookup ("switch-page", GTK_TYPE_NOTEBOOK), 0,
|
|
gail_switch_page_watcher, NULL, (GDestroyNotify) NULL);
|
|
emission_hooks_added = TRUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gail_focus_object_destroyed (gpointer data)
|
|
{
|
|
GObject *obj;
|
|
|
|
obj = G_OBJECT (data);
|
|
g_object_set_qdata (obj, quark_focus_object, NULL);
|
|
g_object_unref (obj);
|
|
}
|
|
|
|
static void
|
|
gail_focus_tracker (AtkObject *focus_object)
|
|
{
|
|
/*
|
|
* Do not report focus on redundant object
|
|
*/
|
|
if (focus_object &&
|
|
(atk_object_get_role(focus_object) != ATK_ROLE_REDUNDANT_OBJECT))
|
|
{
|
|
AtkObject *old_focus_object;
|
|
|
|
if (!GTK_IS_ACCESSIBLE (focus_object))
|
|
{
|
|
AtkObject *parent;
|
|
|
|
parent = focus_object;
|
|
while (1)
|
|
{
|
|
parent = atk_object_get_parent (parent);
|
|
if (parent == NULL)
|
|
break;
|
|
if (GTK_IS_ACCESSIBLE (parent))
|
|
break;
|
|
}
|
|
|
|
if (parent)
|
|
{
|
|
gail_set_focus_object (focus_object, parent);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
old_focus_object = g_object_get_qdata (G_OBJECT (focus_object), quark_focus_object);
|
|
if (old_focus_object)
|
|
{
|
|
g_object_weak_unref (G_OBJECT (old_focus_object),
|
|
(GWeakNotify) gail_focus_object_destroyed,
|
|
focus_object);
|
|
g_object_set_qdata (G_OBJECT (focus_object), quark_focus_object, NULL);
|
|
g_object_unref (G_OBJECT (focus_object));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gail_set_focus_widget (GtkWidget *focus_widget,
|
|
GtkWidget *widget)
|
|
{
|
|
AtkObject *focus_obj;
|
|
AtkObject *obj;
|
|
|
|
focus_obj = gtk_widget_get_accessible (focus_widget);
|
|
obj = gtk_widget_get_accessible (widget);
|
|
gail_set_focus_object (focus_obj, obj);
|
|
}
|
|
|
|
static void
|
|
gail_set_focus_object (AtkObject *focus_obj,
|
|
AtkObject *obj)
|
|
{
|
|
AtkObject *old_focus_obj;
|
|
|
|
old_focus_obj = g_object_get_qdata (G_OBJECT (obj), quark_focus_object);
|
|
if (old_focus_obj != obj)
|
|
{
|
|
if (old_focus_obj)
|
|
g_object_weak_unref (G_OBJECT (old_focus_obj),
|
|
(GWeakNotify) gail_focus_object_destroyed,
|
|
obj);
|
|
else
|
|
/*
|
|
* We call g_object_ref as if obj is destroyed
|
|
* while the weak reference exists then destroying the
|
|
* focus_obj would cause gail_focus_object_destroyed to be
|
|
* called when obj is not a valid GObject.
|
|
*/
|
|
g_object_ref (obj);
|
|
|
|
g_object_weak_ref (G_OBJECT (focus_obj),
|
|
(GWeakNotify) gail_focus_object_destroyed,
|
|
obj);
|
|
g_object_set_qdata (G_OBJECT (obj), quark_focus_object, focus_obj);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
state_event_watcher (GSignalInvocationHint *hint,
|
|
guint n_param_values,
|
|
const GValue *param_values,
|
|
gpointer data)
|
|
{
|
|
GObject *object;
|
|
GtkWidget *widget;
|
|
AtkObject *atk_obj;
|
|
AtkObject *parent;
|
|
GdkEventWindowState *event;
|
|
gchar *signal_name;
|
|
|
|
object = g_value_get_object (param_values + 0);
|
|
if (!GTK_IS_WINDOW (object))
|
|
return FALSE;
|
|
|
|
event = g_value_get_boxed (param_values + 1);
|
|
if (event->type == GDK_WINDOW_STATE)
|
|
return FALSE;
|
|
widget = GTK_WIDGET (object);
|
|
|
|
if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
|
|
signal_name = "maximize";
|
|
else if (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED)
|
|
signal_name = "minimize";
|
|
else if (event->new_window_state == 0)
|
|
signal_name = "restore";
|
|
else
|
|
return TRUE;
|
|
|
|
atk_obj = gtk_widget_get_accessible (widget);
|
|
if (GTK_IS_WINDOW_ACCESSIBLE (atk_obj))
|
|
{
|
|
parent = atk_object_get_parent (atk_obj);
|
|
if (parent == atk_get_root ())
|
|
g_signal_emit_by_name (atk_obj, signal_name);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
configure_event_watcher (GSignalInvocationHint *hint,
|
|
guint n_param_values,
|
|
const GValue *param_values,
|
|
gpointer data)
|
|
{
|
|
GtkAllocation allocation;
|
|
GObject *object;
|
|
GtkWidget *widget;
|
|
AtkObject *atk_obj;
|
|
AtkObject *parent;
|
|
GdkEvent *event;
|
|
gchar *signal_name;
|
|
|
|
object = g_value_get_object (param_values + 0);
|
|
if (!GTK_IS_WINDOW (object))
|
|
return FALSE;
|
|
|
|
event = g_value_get_boxed (param_values + 1);
|
|
if (event->type != GDK_CONFIGURE)
|
|
return FALSE;
|
|
widget = GTK_WIDGET (object);
|
|
gtk_widget_get_allocation (widget, &allocation);
|
|
if (allocation.x == ((GdkEventConfigure *)event)->x &&
|
|
allocation.y == ((GdkEventConfigure *)event)->y &&
|
|
allocation.width == ((GdkEventConfigure *)event)->width &&
|
|
allocation.height == ((GdkEventConfigure *)event)->height)
|
|
return TRUE;
|
|
|
|
if (allocation.width != ((GdkEventConfigure *)event)->width ||
|
|
allocation.height != ((GdkEventConfigure *)event)->height)
|
|
signal_name = "resize";
|
|
else
|
|
signal_name = "move";
|
|
|
|
atk_obj = gtk_widget_get_accessible (widget);
|
|
if (GTK_IS_WINDOW_ACCESSIBLE (atk_obj))
|
|
{
|
|
parent = atk_object_get_parent (atk_obj);
|
|
if (parent == atk_get_root ())
|
|
g_signal_emit_by_name (atk_obj, signal_name);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
window_focus (GtkWidget *widget,
|
|
GdkEventFocus *event)
|
|
{
|
|
AtkObject *atk_obj;
|
|
|
|
g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
|
|
|
|
atk_obj = gtk_widget_get_accessible (widget);
|
|
g_signal_emit_by_name (atk_obj, event->in ? "activate" : "deactivate");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
window_added (AtkObject *atk_obj,
|
|
guint index,
|
|
AtkObject *child)
|
|
{
|
|
GtkWidget *widget;
|
|
|
|
if (!GTK_IS_WINDOW_ACCESSIBLE (child))
|
|
return;
|
|
|
|
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (child));
|
|
if (!widget)
|
|
return;
|
|
|
|
g_signal_connect (widget, "focus-in-event", (GCallback) window_focus, NULL);
|
|
g_signal_connect (widget, "focus-out-event", (GCallback) window_focus, NULL);
|
|
g_signal_emit_by_name (child, "create");
|
|
}
|
|
|
|
static void
|
|
window_removed (AtkObject *atk_obj,
|
|
guint index,
|
|
AtkObject *child)
|
|
{
|
|
GtkWidget *widget;
|
|
GtkWindow *window;
|
|
|
|
if (!GTK_IS_WINDOW_ACCESSIBLE (child))
|
|
return;
|
|
|
|
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (child));
|
|
if (!widget)
|
|
return;
|
|
|
|
window = GTK_WINDOW (widget);
|
|
/*
|
|
* Deactivate window if it is still focused and we are removing it. This
|
|
* can happen when a dialog displayed by gok is removed.
|
|
*/
|
|
if (gtk_window_is_active (window) && gtk_window_has_toplevel_focus (window))
|
|
g_signal_emit_by_name (child, "deactivate");
|
|
|
|
g_signal_handlers_disconnect_by_func (widget, (gpointer) window_focus, NULL);
|
|
g_signal_emit_by_name (child, "destroy");
|
|
}
|
|
|
|
static void
|
|
do_window_event_initialization (void)
|
|
{
|
|
AtkObject *root;
|
|
|
|
g_type_class_ref (GTK_TYPE_WINDOW_ACCESSIBLE);
|
|
g_signal_add_emission_hook (g_signal_lookup ("window-state-event", GTK_TYPE_WIDGET),
|
|
0, state_event_watcher, NULL, (GDestroyNotify) NULL);
|
|
g_signal_add_emission_hook (g_signal_lookup ("configure-event", GTK_TYPE_WIDGET),
|
|
0, configure_event_watcher, NULL, (GDestroyNotify) NULL);
|
|
|
|
root = atk_get_root ();
|
|
g_signal_connect (root, "children-changed::add", (GCallback) window_added, NULL);
|
|
g_signal_connect (root, "children-changed::remove", (GCallback) window_removed, NULL);
|
|
}
|
|
|
|
void
|
|
_gtk_accessibility_init (void)
|
|
{
|
|
if (initialized)
|
|
return;
|
|
|
|
initialized = TRUE;
|
|
quark_focus_object = g_quark_from_static_string ("gail-focus-object");
|
|
|
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
|
atk_focus_tracker_init (gail_focus_tracker_init);
|
|
focus_tracker_id = atk_add_focus_tracker (gail_focus_tracker);
|
|
G_GNUC_END_IGNORE_DEPRECATIONS;
|
|
|
|
_gtk_accessibility_override_atk_util ();
|
|
do_window_event_initialization ();
|
|
|
|
#ifdef GDK_WINDOWING_X11
|
|
atk_bridge_adaptor_init (NULL, NULL);
|
|
#endif
|
|
|
|
atk_misc_instance = g_object_new (GTK_TYPE_MISC_IMPL, NULL);
|
|
}
|