diff options
Diffstat (limited to 'modules/other/gail/gailmenuitem.c')
-rw-r--r-- | modules/other/gail/gailmenuitem.c | 682 |
1 files changed, 682 insertions, 0 deletions
diff --git a/modules/other/gail/gailmenuitem.c b/modules/other/gail/gailmenuitem.c new file mode 100644 index 000000000..0425cae42 --- /dev/null +++ b/modules/other/gail/gailmenuitem.c @@ -0,0 +1,682 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001, 2002, 2003 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include "gailmenuitem.h" +#include "gailsubmenuitem.h" + +#define KEYBINDING_SEPARATOR ";" + +static void gail_menu_item_class_init (GailMenuItemClass *klass); +static void gail_menu_item_object_init (GailMenuItem *menu_item); + +static void gail_menu_item_real_initialize + (AtkObject *obj, + gpointer data); +static gint gail_menu_item_get_n_children (AtkObject *obj); +static AtkObject* gail_menu_item_ref_child (AtkObject *obj, + gint i); +static void gail_menu_item_finalize (GObject *object); + +static void atk_action_interface_init (AtkActionIface *iface); +static gboolean gail_menu_item_do_action (AtkAction *action, + gint i); +static gboolean idle_do_action (gpointer data); +static gint gail_menu_item_get_n_actions (AtkAction *action); +static G_CONST_RETURN gchar* gail_menu_item_get_description(AtkAction *action, + gint i); +static G_CONST_RETURN gchar* gail_menu_item_get_name (AtkAction *action, + gint i); +static G_CONST_RETURN gchar* gail_menu_item_get_keybinding (AtkAction *action, + gint i); +static gboolean gail_menu_item_set_description(AtkAction *action, + gint i, + const gchar *desc); +static void menu_item_select (GtkItem *item); +static void menu_item_deselect (GtkItem *item); +static void menu_item_selection (GtkItem *item, + gboolean selected); +static gboolean find_accel (GtkAccelKey *key, + GClosure *closure, + gpointer data); +static gboolean find_accel_new (GtkAccelKey *key, + GClosure *closure, + gpointer data); + +static gpointer parent_class = NULL; + +GType +gail_menu_item_get_type (void) +{ + static GType type = 0; + + if (!type) + { + static const GTypeInfo tinfo = + { + sizeof (GailMenuItemClass), + (GBaseInitFunc) NULL, /* base init */ + (GBaseFinalizeFunc) NULL, /* base finalize */ + (GClassInitFunc) gail_menu_item_class_init, /* class init */ + (GClassFinalizeFunc) NULL, /* class finalize */ + NULL, /* class data */ + sizeof (GailMenuItem), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) gail_menu_item_object_init, /* instance init */ + NULL /* value table */ + }; + + static const GInterfaceInfo atk_action_info = + { + (GInterfaceInitFunc) atk_action_interface_init, + (GInterfaceFinalizeFunc) NULL, + NULL + }; + + type = g_type_register_static (GAIL_TYPE_ITEM, + "GailMenuItem", &tinfo, 0); + g_type_add_interface_static (type, ATK_TYPE_ACTION, + &atk_action_info); + } + return type; +} + +static void +gail_menu_item_class_init (GailMenuItemClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + AtkObjectClass *class = ATK_OBJECT_CLASS (klass); + + gobject_class->finalize = gail_menu_item_finalize; + + class->get_n_children = gail_menu_item_get_n_children; + class->ref_child = gail_menu_item_ref_child; + class->initialize = gail_menu_item_real_initialize; + + parent_class = g_type_class_peek_parent (klass); +} + +static void +gail_menu_item_real_initialize (AtkObject *obj, + gpointer data) +{ + GtkWidget *widget; + GtkWidget *parent; + + ATK_OBJECT_CLASS (parent_class)->initialize (obj, data); + + g_signal_connect (data, + "select", + G_CALLBACK (menu_item_select), + NULL); + g_signal_connect (data, + "deselect", + G_CALLBACK (menu_item_deselect), + NULL); + widget = GTK_WIDGET (data); + parent = gtk_widget_get_parent (widget); + if (GTK_IS_MENU (parent)) + { + GtkWidget *parent_widget; + + parent_widget = gtk_menu_get_attach_widget (GTK_MENU (parent)); + + if (!GTK_IS_MENU_ITEM (parent_widget)) + parent_widget = gtk_widget_get_parent (widget); + if (parent_widget) + { + atk_object_set_parent (obj, gtk_widget_get_accessible (parent_widget)); + } + } + g_object_set_data (G_OBJECT (obj), "atk-component-layer", + GINT_TO_POINTER (ATK_LAYER_POPUP)); + + if (GTK_IS_TEAROFF_MENU_ITEM (data)) + obj->role = ATK_ROLE_TEAR_OFF_MENU_ITEM; + else if (GTK_IS_SEPARATOR_MENU_ITEM (data)) + obj->role = ATK_ROLE_SEPARATOR; + else + obj->role = ATK_ROLE_MENU_ITEM; +} + +static void +gail_menu_item_object_init (GailMenuItem *menu_item) +{ + menu_item->click_keybinding = NULL; + menu_item->click_description = NULL; +} + +AtkObject* +gail_menu_item_new (GtkWidget *widget) +{ + GObject *object; + AtkObject *accessible; + + g_return_val_if_fail (GTK_IS_MENU_ITEM (widget), NULL); + + if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget))) + return gail_sub_menu_item_new (widget); + + object = g_object_new (GAIL_TYPE_MENU_ITEM, NULL); + + accessible = ATK_OBJECT (object); + atk_object_initialize (accessible, widget); + + return accessible; +} + +GList * +get_children (GtkWidget *submenu) +{ + GList *children; + + children = gtk_container_get_children (GTK_CONTAINER (submenu)); + if (g_list_length (children) == 0) + { + /* + * If menu is empty it may be because the menu items are created only + * on demand. For example, in gnome-panel the menu items are created + * only when "show" signal is emitted on the menu. + * + * The following hack forces the menu items to be created. + */ + if (!GTK_WIDGET_VISIBLE (submenu)) + { + GTK_WIDGET_SET_FLAGS (submenu, GTK_VISIBLE); + g_signal_emit_by_name (submenu, "show"); + GTK_WIDGET_UNSET_FLAGS (submenu, GTK_VISIBLE); + } + g_list_free (children); + children = gtk_container_get_children (GTK_CONTAINER (submenu)); + } + return children; +} + +/* + * If a menu item has a submenu return the items of the submenu as the + * accessible children; otherwise expose no accessible children. + */ +static gint +gail_menu_item_get_n_children (AtkObject* obj) +{ + GtkWidget *widget; + GtkWidget *submenu; + gint count = 0; + + g_return_val_if_fail (GAIL_IS_MENU_ITEM (obj), count); + + widget = GTK_ACCESSIBLE (obj)->widget; + if (widget == NULL) + return count; + + submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)); + if (submenu) + { + GList *children; + + children = get_children (submenu); + count = g_list_length (children); + g_list_free (children); + } + return count; +} + +static AtkObject* +gail_menu_item_ref_child (AtkObject *obj, + gint i) +{ + AtkObject *accessible; + GtkWidget *widget; + GtkWidget *submenu; + + g_return_val_if_fail (GAIL_IS_MENU_ITEM (obj), NULL); + g_return_val_if_fail ((i >= 0), NULL); + + widget = GTK_ACCESSIBLE (obj)->widget; + if (widget == NULL) + return NULL; + + submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)); + if (submenu) + { + GList *children; + GList *tmp_list; + + children = get_children (submenu); + tmp_list = g_list_nth (children, i); + if (!tmp_list) + { + g_list_free (children); + return NULL; + } + accessible = gtk_widget_get_accessible (GTK_WIDGET (tmp_list->data)); + g_list_free (children); + g_object_ref (accessible); + } + else + accessible = NULL; + + return accessible; +} + +static void +atk_action_interface_init (AtkActionIface *iface) +{ + g_return_if_fail (iface != NULL); + + iface->do_action = gail_menu_item_do_action; + iface->get_n_actions = gail_menu_item_get_n_actions; + iface->get_description = gail_menu_item_get_description; + iface->get_name = gail_menu_item_get_name; + iface->get_keybinding = gail_menu_item_get_keybinding; + iface->set_description = gail_menu_item_set_description; +} + +static gboolean +gail_menu_item_do_action (AtkAction *action, + gint i) +{ + if (i == 0) + { + GtkWidget *item; + GailMenuItem *gail_menu_item; + + item = GTK_ACCESSIBLE (action)->widget; + if (item == NULL) + /* State is defunct */ + return FALSE; + + if (!GTK_WIDGET_SENSITIVE (item) || !GTK_WIDGET_VISIBLE (item)) + return FALSE; + + gail_menu_item = GAIL_MENU_ITEM (action); + if (gail_menu_item->action_idle_handler) + return FALSE; + else + { + g_object_ref (gail_menu_item); + gail_menu_item->action_idle_handler = g_idle_add (idle_do_action, gail_menu_item); + } + return TRUE; + } + else + return FALSE; +} + +static void +ensure_menus_unposted (GailMenuItem *menu_item) +{ + AtkObject *parent; + GtkWidget *widget; + + parent = atk_object_get_parent (ATK_OBJECT (menu_item)); + while (parent) + { + if (GTK_IS_ACCESSIBLE (parent)) + { + widget = GTK_ACCESSIBLE (parent)->widget; + if (GTK_IS_MENU (widget)) + { + if (GTK_WIDGET_MAPPED (widget)) + gtk_menu_shell_cancel (GTK_MENU_SHELL (widget)); + + return; + } + } + parent = atk_object_get_parent (parent); + } +} + +static gboolean +idle_do_action (gpointer data) +{ + GtkWidget *item; + GtkWidget *item_parent; + GailMenuItem *menu_item; + gboolean item_mapped; + + GDK_THREADS_ENTER (); + + menu_item = GAIL_MENU_ITEM (data); + menu_item->action_idle_handler = 0; + item = GTK_ACCESSIBLE (menu_item)->widget; + if (item == NULL /* State is defunct */ || + !GTK_WIDGET_SENSITIVE (item) || !GTK_WIDGET_VISIBLE (item)) + { + g_object_unref (menu_item); + GDK_THREADS_LEAVE (); + return FALSE; + } + + item_parent = gtk_widget_get_parent (item); + gtk_menu_shell_select_item (GTK_MENU_SHELL (item_parent), item); + item_mapped = GTK_WIDGET_MAPPED (item); + /* + * This is what is called when <Return> is pressed for a menu item + */ + g_signal_emit_by_name (item_parent, "activate_current", + /*force_hide*/ 1); + if (!item_mapped) + ensure_menus_unposted (menu_item); + + g_object_unref (menu_item); + GDK_THREADS_LEAVE (); + + return FALSE; +} + +static gint +gail_menu_item_get_n_actions (AtkAction *action) +{ + /* + * Menu item has 1 action + */ + return 1; +} + +static G_CONST_RETURN gchar* +gail_menu_item_get_description (AtkAction *action, + gint i) +{ + if (i == 0) + { + GailMenuItem *item; + + item = GAIL_MENU_ITEM (action); + return item->click_description; + } + else + return NULL; +} + +static G_CONST_RETURN gchar* +gail_menu_item_get_name (AtkAction *action, + gint i) +{ + if (i == 0) + return "click"; + else + return NULL; +} + +static G_CONST_RETURN gchar* +gail_menu_item_get_keybinding (AtkAction *action, + gint i) +{ + /* + * This function returns a string of the form A;B;C where + * A is the keybinding for the widget; B is the keybinding to traverse + * from the menubar and C is the accelerator. + * The items in the keybinding to traverse from the menubar are separated + * by ":". + */ + GailMenuItem *gail_menu_item; + gchar *keybinding = NULL; + gchar *item_keybinding = NULL; + gchar *full_keybinding = NULL; + gchar *accelerator = NULL; + + gail_menu_item = GAIL_MENU_ITEM (action); + if (i == 0) + { + GtkWidget *item; + GtkWidget *temp_item; + GtkWidget *child; + GtkWidget *parent; + + item = GTK_ACCESSIBLE (action)->widget; + if (item == NULL) + /* State is defunct */ + return NULL; + + temp_item = item; + while (TRUE) + { + GdkModifierType mnemonic_modifier = 0; + guint key_val; + gchar *key, *temp_keybinding; + + child = gtk_bin_get_child (GTK_BIN (temp_item)); + if (child == NULL) + { + /* Possibly a tear off menu item; it could also be a menu + * separator generated by gtk_item_factory_create_items() + */ + return NULL; + } + parent = gtk_widget_get_parent (temp_item); + if (!parent) + { + /* + * parent can be NULL when activating a window from the panel + */ + return NULL; + } + g_return_val_if_fail (GTK_IS_MENU_SHELL (parent), NULL); + if (GTK_IS_MENU_BAR (parent)) + { + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (parent); + if (toplevel && GTK_IS_WINDOW (toplevel)) + mnemonic_modifier = gtk_window_get_mnemonic_modifier ( + GTK_WINDOW (toplevel)); + } + if (GTK_IS_LABEL (child)) + { + key_val = gtk_label_get_mnemonic_keyval (GTK_LABEL (child)); + if (key_val != GDK_VoidSymbol) + { + key = gtk_accelerator_name (key_val, mnemonic_modifier); + if (full_keybinding) + temp_keybinding = g_strconcat (key, ":", full_keybinding, NULL); + else + temp_keybinding = g_strconcat (key, NULL); + if (temp_item == item) + { + item_keybinding = g_strdup (key); + } + g_free (key); + g_free (full_keybinding); + full_keybinding = temp_keybinding; + } + else + { + /* No keybinding */ + g_free (full_keybinding); + full_keybinding = NULL; + break; + } + } + if (GTK_IS_MENU_BAR (parent)) + /* We have reached the menu bar so we are finished */ + break; + g_return_val_if_fail (GTK_IS_MENU (parent), NULL); + temp_item = gtk_menu_get_attach_widget (GTK_MENU (parent)); + if (!GTK_IS_MENU_ITEM (temp_item)) + { + /* + * Menu is attached to something other than a menu item; + * probably an option menu + */ + g_free (full_keybinding); + full_keybinding = NULL; + break; + } + } + + parent = gtk_widget_get_parent (item); + if (GTK_IS_MENU (parent)) + { + GtkAccelGroup *group; + GtkAccelKey *key; + + group = gtk_menu_get_accel_group (GTK_MENU (parent)); + + if (group) + { + key = gtk_accel_group_find (group, find_accel, item); + } + else + { + /* + * If the menu item is created using GtkAction and GtkUIManager + * we get here. + */ + key = NULL; + child = GTK_BIN (item)->child; + if (GTK_IS_ACCEL_LABEL (child)) + { + GtkAccelLabel *accel_label; + + accel_label = GTK_ACCEL_LABEL (child); + if (accel_label->accel_closure) + { + key = gtk_accel_group_find (accel_label->accel_group, + find_accel_new, + accel_label->accel_closure); + } + + } + } + + if (key) + { + accelerator = gtk_accelerator_name (key->accel_key, + key->accel_mods); + } + } + } + /* + * Concatenate the bindings + */ + if (item_keybinding || full_keybinding || accelerator) + { + gchar *temp; + if (item_keybinding) + { + keybinding = g_strconcat (item_keybinding, KEYBINDING_SEPARATOR, NULL); + g_free (item_keybinding); + } + else + keybinding = g_strconcat (KEYBINDING_SEPARATOR, NULL); + + if (full_keybinding) + { + temp = g_strconcat (keybinding, full_keybinding, + KEYBINDING_SEPARATOR, NULL); + g_free (full_keybinding); + } + else + temp = g_strconcat (keybinding, KEYBINDING_SEPARATOR, NULL); + + g_free (keybinding); + keybinding = temp; + if (accelerator) + { + temp = g_strconcat (keybinding, accelerator, NULL); + g_free (accelerator); + g_free (keybinding); + keybinding = temp; + } + } + g_free (gail_menu_item->click_keybinding); + gail_menu_item->click_keybinding = keybinding; + return keybinding; +} + +static gboolean +gail_menu_item_set_description (AtkAction *action, + gint i, + const gchar *desc) +{ + if (i == 0) + { + GailMenuItem *item; + + item = GAIL_MENU_ITEM (action); + g_free (item->click_description); + item->click_description = g_strdup (desc); + return TRUE; + } + else + return FALSE; +} + +static void +gail_menu_item_finalize (GObject *object) +{ + GailMenuItem *menu_item = GAIL_MENU_ITEM (object); + + g_free (menu_item->click_keybinding); + g_free (menu_item->click_description); + if (menu_item->action_idle_handler) + { + g_source_remove (menu_item->action_idle_handler); + menu_item->action_idle_handler = 0; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +menu_item_select (GtkItem *item) +{ + menu_item_selection (item, TRUE); +} + +static void +menu_item_deselect (GtkItem *item) +{ + menu_item_selection (item, FALSE); +} + +static void +menu_item_selection (GtkItem *item, + gboolean selected) +{ + AtkObject *obj, *parent; + + obj = gtk_widget_get_accessible (GTK_WIDGET (item)); + atk_object_notify_state_change (obj, ATK_STATE_SELECTED, selected); + + parent = atk_object_get_parent (obj); + g_signal_emit_by_name (parent, "selection_changed"); +} + +static gboolean +find_accel (GtkAccelKey *key, + GClosure *closure, + gpointer data) +{ + /* + * We assume that closure->data points to the widget + * pending gtk_widget_get_accel_closures being made public + */ + return data == (gpointer) closure->data; +} + +static gboolean +find_accel_new (GtkAccelKey *key, + GClosure *closure, + gpointer data) +{ + return data == (gpointer) closure; +} |