diff options
author | Ben Murdoch <benm@google.com> | 2010-07-29 17:14:53 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2010-08-04 14:29:45 +0100 |
commit | c407dc5cd9bdc5668497f21b26b09d988ab439de (patch) | |
tree | 7eaf8707c0309516bdb042ad976feedaf72b0bb1 /chrome/browser/gtk/accessibility_event_router_gtk.cc | |
parent | 0998b1cdac5733f299c12d88bc31ef9c8035b8fa (diff) | |
download | external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.zip external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.gz external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.bz2 |
Merge Chromium src@r53293
Change-Id: Ia79acf8670f385cee48c45b0a75371d8e950af34
Diffstat (limited to 'chrome/browser/gtk/accessibility_event_router_gtk.cc')
-rw-r--r-- | chrome/browser/gtk/accessibility_event_router_gtk.cc | 636 |
1 files changed, 636 insertions, 0 deletions
diff --git a/chrome/browser/gtk/accessibility_event_router_gtk.cc b/chrome/browser/gtk/accessibility_event_router_gtk.cc new file mode 100644 index 0000000..606fd40 --- /dev/null +++ b/chrome/browser/gtk/accessibility_event_router_gtk.cc @@ -0,0 +1,636 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/gtk/accessibility_event_router_gtk.h" + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/message_loop.h" +#include "base/stl_util-inl.h" +#include "chrome/browser/extensions/extension_accessibility_api.h" +#include "chrome/browser/gtk/gtk_chrome_link_button.h" +#include "chrome/browser/profile.h" +#include "chrome/common/notification_type.h" + +namespace { + +// +// Callbacks triggered by signals on gtk widgets. +// + +gboolean OnWidgetFocused(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> + DispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_CONTROL_FOCUSED); + return TRUE; +} + +gboolean OnButtonClicked(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + // Skip toggle buttons because we're also listening on "toggle" events. + if (GTK_IS_TOGGLE_BUTTON(widget)) + return true; + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> + DispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); + return TRUE; +} + +gboolean OnButtonToggled(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + bool checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); + // Skip propagating an "uncheck" event for a radio button because it's + // redundant; there will always be a corresponding "check" event for + // a different radio button the group. + if (GTK_IS_RADIO_BUTTON(widget) && !checked) + return true; + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> + DispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); + return TRUE; +} + +gboolean OnPageSwitched(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + // The page hasn't switched yet, so defer calling + // DispatchAccessibilityNotification. + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> + PostDispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); + return TRUE; +} + +gboolean OnComboBoxChanged(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + if (!GTK_IS_COMBO_BOX(widget)) + return true; + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> + DispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); + return TRUE; +} + +gboolean OnTreeViewCursorChanged(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + if (!GTK_IS_TREE_VIEW(widget)) { + return true; + } + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> + DispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); + return TRUE; +} + +gboolean OnEntryChanged(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + if (!GTK_IS_ENTRY(widget)) { + return TRUE; + } + // The text hasn't changed yet, so defer calling + // DispatchAccessibilityNotification. + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> + PostDispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_TEXT_CHANGED); + return TRUE; +} + +gboolean OnTextBufferChanged(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + // The text hasn't changed yet, so defer calling + // DispatchAccessibilityNotification. + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> + PostDispatchAccessibilityNotification( + NULL, NotificationType::ACCESSIBILITY_TEXT_CHANGED); + return TRUE; +} + +gboolean OnTextViewChanged(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + if (!GTK_IS_TEXT_VIEW(widget)) { + return TRUE; + } + // The text hasn't changed yet, so defer calling + // DispatchAccessibilityNotification. + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> + PostDispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_TEXT_CHANGED); + return TRUE; +} + +gboolean OnMenuMoveCurrent(GSignalInvocationHint *ihint, + guint n_param_values, + const GValue* param_values, + gpointer user_data) { + // Get the widget (the GtkMenu). + GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); + + // Moving may move us into or out of a submenu, so after the menu + // item moves, |widget| may not be valid anymore. To be safe, then, + // find the topmost ancestor of this menu and post the notification + // dispatch on that menu. Then the dispatcher will recurse into submenus + // as necessary to figure out which item is focused. + while (GTK_MENU_SHELL(widget)->parent_menu_shell) + widget = GTK_MENU_SHELL(widget)->parent_menu_shell; + + // The menu item hasn't moved yet, so we want to defer calling + // DispatchAccessibilityNotification until after it does. + reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> + PostDispatchAccessibilityNotification( + widget, NotificationType::ACCESSIBILITY_CONTROL_FOCUSED); + return TRUE; +} + +} // anonymous namespace + +AccessibilityEventRouterGtk::AccessibilityEventRouterGtk() + : listening_(false), + most_recent_profile_(NULL), + most_recent_widget_(NULL), + method_factory_(this) { + // We don't want our event listeners to be installed if accessibility is + // disabled. Install listeners so we can install and uninstall them as + // needed, then install them now if it's currently enabled. + ExtensionAccessibilityEventRouter *extension_event_router = + ExtensionAccessibilityEventRouter::GetInstance(); + extension_event_router->AddOnEnabledListener( + NewCallback(this, + &AccessibilityEventRouterGtk::InstallEventListeners)); + extension_event_router->AddOnDisabledListener( + NewCallback(this, + &AccessibilityEventRouterGtk::RemoveEventListeners)); + if (extension_event_router->IsAccessibilityEnabled()) { + InstallEventListeners(); + } +} + +AccessibilityEventRouterGtk::~AccessibilityEventRouterGtk() { + RemoveEventListeners(); +} + +// static +AccessibilityEventRouterGtk* AccessibilityEventRouterGtk::GetInstance() { + return Singleton<AccessibilityEventRouterGtk>::get(); +} + +void AccessibilityEventRouterGtk::InstallEventListener( + const char* signal_name, + GType widget_type, + GSignalEmissionHook hook_func) { + guint signal_id = g_signal_lookup(signal_name, widget_type); + gulong hook_id = g_signal_add_emission_hook( + signal_id, 0, hook_func, reinterpret_cast<gpointer>(this), NULL); + installed_hooks_.push_back(InstalledHook(signal_id, hook_id)); +} + +void AccessibilityEventRouterGtk::InstallEventListeners() { + // Create and destroy each type of widget we need signals for, + // to ensure their modules are loaded, otherwise g_signal_lookup + // might fail. + g_object_unref(g_object_ref_sink(gtk_combo_box_new())); + g_object_unref(g_object_ref_sink(gtk_entry_new())); + g_object_unref(g_object_ref_sink(gtk_notebook_new())); + g_object_unref(g_object_ref_sink(gtk_toggle_button_new())); + g_object_unref(g_object_ref_sink(gtk_tree_view_new())); + g_object_unref(g_object_ref_sink(gtk_text_view_new())); + g_object_unref(g_object_ref_sink(gtk_text_buffer_new(NULL))); + + // Add signal emission hooks for the events we're interested in. + InstallEventListener("clicked", GTK_TYPE_BUTTON, OnButtonClicked); + InstallEventListener("changed", GTK_TYPE_COMBO_BOX, OnComboBoxChanged); + InstallEventListener("cursor-changed", GTK_TYPE_TREE_VIEW, + OnTreeViewCursorChanged); + InstallEventListener("changed", GTK_TYPE_ENTRY, OnEntryChanged); + InstallEventListener("insert-text", GTK_TYPE_ENTRY, OnEntryChanged); + InstallEventListener("delete-text", GTK_TYPE_ENTRY, OnEntryChanged); + InstallEventListener("move-cursor", GTK_TYPE_ENTRY, OnEntryChanged); + InstallEventListener("focus-in-event", GTK_TYPE_WIDGET, OnWidgetFocused); + InstallEventListener("switch-page", GTK_TYPE_NOTEBOOK, OnPageSwitched); + InstallEventListener("toggled", GTK_TYPE_TOGGLE_BUTTON, OnButtonToggled); + InstallEventListener("move-current", GTK_TYPE_MENU, OnMenuMoveCurrent); + InstallEventListener("changed", GTK_TYPE_TEXT_BUFFER, OnTextBufferChanged); + InstallEventListener("move-cursor", GTK_TYPE_TEXT_VIEW, OnTextViewChanged); + + listening_ = true; +} + +void AccessibilityEventRouterGtk::RemoveEventListeners() { + for (size_t i = 0; i < installed_hooks_.size(); i++) { + g_signal_remove_emission_hook( + installed_hooks_[i].signal_id, + installed_hooks_[i].hook_id); + } + installed_hooks_.clear(); + + listening_ = false; +} + +void AccessibilityEventRouterGtk::AddRootWidget( + GtkWidget* root_widget, Profile* profile) { + root_widget_profile_map_[root_widget] = profile; +} + +void AccessibilityEventRouterGtk::RemoveRootWidget(GtkWidget* root_widget) { + DCHECK(root_widget_profile_map_.find(root_widget) != + root_widget_profile_map_.end()); + root_widget_profile_map_.erase(root_widget); +} + +void AccessibilityEventRouterGtk::IgnoreWidget(GtkWidget* widget) { + widget_info_map_[widget].ignore = true; +} + +void AccessibilityEventRouterGtk::SetWidgetName( + GtkWidget* widget, std::string name) { + widget_info_map_[widget].name = name; +} + +void AccessibilityEventRouterGtk::RemoveWidget(GtkWidget* widget) { + DCHECK(widget_info_map_.find(widget) != widget_info_map_.end()); + widget_info_map_.erase(widget); +} + +void AccessibilityEventRouterGtk::FindWidget( + GtkWidget* widget, Profile** profile, bool* is_accessible) { + *is_accessible = false; + + // First see if it's a descendant of a root widget. + for (base::hash_map<GtkWidget*, Profile*>::const_iterator iter = + root_widget_profile_map_.begin(); + iter != root_widget_profile_map_.end(); + ++iter) { + if (gtk_widget_is_ancestor(widget, iter->first)) { + *is_accessible = true; + if (profile) + *profile = iter->second; + break; + } + } + if (!*is_accessible) + return; + + // Now make sure it's not marked as a widget to be ignored. + base::hash_map<GtkWidget*, WidgetInfo>::const_iterator iter = + widget_info_map_.find(widget); + if (iter != widget_info_map_.end() && iter->second.ignore) { + *is_accessible = false; + return; + } +} + +std::string AccessibilityEventRouterGtk::GetWidgetName(GtkWidget* widget) { + base::hash_map<GtkWidget*, WidgetInfo>::const_iterator iter = + widget_info_map_.find(widget); + if (iter != widget_info_map_.end()) { + return iter->second.name; + } else { + return ""; + } +} + +void AccessibilityEventRouterGtk::StartListening() { + listening_ = true; +} + +void AccessibilityEventRouterGtk::StopListening() { + listening_ = false; +} + +void AccessibilityEventRouterGtk::DispatchAccessibilityNotification( + GtkWidget* widget, NotificationType type) { + if (!listening_) + return; + + Profile* profile = NULL; + bool is_accessible; + + // Special case: when we get ACCESSIBILITY_TEXT_CHANGED, we don't get + // a pointer to the widget, so we try to retrieve it from the most recent + // widget. + if (widget == NULL && + type == NotificationType::ACCESSIBILITY_TEXT_CHANGED && + most_recent_widget_ && + GTK_IS_TEXT_VIEW(most_recent_widget_)) { + widget = most_recent_widget_; + } + + if (!widget) + return; + + most_recent_widget_ = widget; + FindWidget(widget, &profile, &is_accessible); + if (profile) + most_recent_profile_ = profile; + + // Special case: a GtkMenu isn't associated with any particular + // toplevel window, so menu events get routed to the profile of + // the most recent event that was associated with a window. + if (GTK_IS_MENU_SHELL(widget) && most_recent_profile_) { + SendMenuItemNotification(widget, type, most_recent_profile_); + return; + } + + // In all other cases, return if this widget wasn't marked as accessible. + if (!is_accessible) + return; + + // The order of these checks matters, because, for example, a radio button + // is a subclass of button, and a combo box is a composite control where + // the focus event goes to the button that's a child of the combo box. + GtkWidget* parent = gtk_widget_get_parent(widget); + if (parent && GTK_IS_BUTTON(widget) && GTK_IS_TREE_VIEW(parent)) { + // This is a list box column header. Currently not supported. + return; + } else if (GTK_IS_COMBO_BOX(widget)) { + SendComboBoxNotification(widget, type, profile); + } else if (parent && GTK_IS_COMBO_BOX(parent)) { + SendComboBoxNotification(parent, type, profile); + } else if (GTK_IS_RADIO_BUTTON(widget)) { + SendRadioButtonNotification(widget, type, profile); + } else if (GTK_IS_TOGGLE_BUTTON(widget)) { + SendCheckboxNotification(widget, type, profile); + } else if (GTK_IS_BUTTON(widget)) { + SendButtonNotification(widget, type, profile); + } else if (GTK_IS_ENTRY(widget)) { + SendEntryNotification(widget, type, profile); + } else if (GTK_IS_TEXT_VIEW(widget)) { + SendTextViewNotification(widget, type, profile); + } else if (GTK_IS_NOTEBOOK(widget)) { + SendTabNotification(widget, type, profile); + } else if (GTK_IS_TREE_VIEW(widget)) { + SendListBoxNotification(widget, type, profile); + } else { + // If we have no idea what this control is, return and skip the + // temporary pause in event listening. + return; + } + + // After this method returns, additional signal handlers will run, + // which will sometimes generate additional signals. To avoid + // generating redundant accessibility notifications for the same + // initial event, stop listening to all signals generated from now + // until this posted task runs. + StopListening(); + MessageLoop::current()->PostTask( + FROM_HERE, method_factory_.NewRunnableMethod( + &AccessibilityEventRouterGtk::StartListening)); +} + +void AccessibilityEventRouterGtk::PostDispatchAccessibilityNotification( + GtkWidget* widget, NotificationType type) { + MessageLoop::current()->PostTask( + FROM_HERE, method_factory_.NewRunnableMethod( + &AccessibilityEventRouterGtk::DispatchAccessibilityNotification, + widget, + type)); +} + +void AccessibilityEventRouterGtk::SendRadioButtonNotification( + GtkWidget* widget, NotificationType type, Profile* profile) { + // Get the radio button name + std::string button_name = GetWidgetName(widget); + if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) + button_name = gtk_button_get_label(GTK_BUTTON(widget)); + + // Get its state + bool checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); + + // Get the index of this radio button and the total number of + // radio buttons in the group. + int item_count = 0; + int item_index = -1; + for (GSList* group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(widget)); + group; + group = group->next) { + if (group->data == widget) { + item_index = item_count; + } + item_count++; + } + item_index = item_count - 1 - item_index; + + AccessibilityRadioButtonInfo info( + profile, button_name, checked, item_index, item_count); + SendAccessibilityNotification(type, &info); +} + +void AccessibilityEventRouterGtk::SendCheckboxNotification( + GtkWidget* widget, NotificationType type, Profile* profile) { + std::string button_name = GetWidgetName(widget); + if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) + button_name = gtk_button_get_label(GTK_BUTTON(widget)); + bool checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); + AccessibilityCheckboxInfo info(profile, button_name, checked); + SendAccessibilityNotification(type, &info); +} + +void AccessibilityEventRouterGtk::SendButtonNotification( + GtkWidget* widget, NotificationType type, Profile* profile) { + std::string button_name = GetWidgetName(widget); + if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) + button_name = gtk_button_get_label(GTK_BUTTON(widget)); + AccessibilityButtonInfo info(profile, button_name); + SendAccessibilityNotification(type, &info); +} + +void AccessibilityEventRouterGtk::SendEntryNotification( + GtkWidget* widget, NotificationType type, Profile* profile) { + std::string name = GetWidgetName(widget); + std::string value = gtk_entry_get_text(GTK_ENTRY(widget)); + gint start_pos; + gint end_pos; + gtk_editable_get_selection_bounds(GTK_EDITABLE(widget), &start_pos, &end_pos); + AccessibilityTextBoxInfo info(profile, name, false); + info.SetValue(value, start_pos, end_pos); + SendAccessibilityNotification(type, &info); +} + +void AccessibilityEventRouterGtk::SendTextViewNotification( + GtkWidget* widget, NotificationType type, Profile* profile) { + std::string name = GetWidgetName(widget); + GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget)); + GtkTextIter start, end; + gtk_text_buffer_get_bounds(buffer, &start, &end); + gchar* text = gtk_text_buffer_get_text(buffer, &start, &end, false); + std::string value = text; + g_free(text); + GtkTextIter sel_start, sel_end; + gtk_text_buffer_get_selection_bounds(buffer, &sel_start, &sel_end); + int start_pos = gtk_text_iter_get_offset(&sel_start); + int end_pos = gtk_text_iter_get_offset(&sel_end); + AccessibilityTextBoxInfo info(profile, name, false); + info.SetValue(value, start_pos, end_pos); + SendAccessibilityNotification(type, &info); +} + +void AccessibilityEventRouterGtk::SendTabNotification( + GtkWidget* widget, NotificationType type, Profile* profile) { + int index = gtk_notebook_get_current_page(GTK_NOTEBOOK(widget)); + int page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(widget)); + GtkWidget* page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(widget), index); + GtkWidget* label = gtk_notebook_get_tab_label(GTK_NOTEBOOK(widget), page); + std::string name = GetWidgetName(widget); + if (name.empty() && gtk_label_get_text(GTK_LABEL(label))) { + name = gtk_label_get_text(GTK_LABEL(label)); + } + AccessibilityTabInfo info(profile, name, index, page_count); + SendAccessibilityNotification(type, &info); +} + +void AccessibilityEventRouterGtk::SendComboBoxNotification( + GtkWidget* widget, NotificationType type, Profile* profile) { + // Get the index of the selected item. Will return -1 if no item is + // active, which matches the semantics of the extension API. + int index = gtk_combo_box_get_active(GTK_COMBO_BOX(widget)); + + // Get the number of items. + GtkTreeModel* model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget)); + int count = gtk_tree_model_iter_n_children(model, NULL); + + // Get the value of the current item, if possible. Note that the + // model behind the combo box could be arbitrarily complex in theory, + // but this code just handles flat lists where the first string column + // contains the display value. + std::string value; + int string_column_index = -1; + for (int i = 0; i < gtk_tree_model_get_n_columns(model); i++) { + if (gtk_tree_model_get_column_type(model, i) == G_TYPE_STRING) { + string_column_index = i; + break; + } + } + if (string_column_index) { + GtkTreeIter iter; + if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter)) { + GValue gvalue = { 0 }; + gtk_tree_model_get_value(model, &iter, string_column_index, &gvalue); + const char* string_value = g_value_get_string(&gvalue); + if (string_value) { + value = string_value; + } + g_value_unset(&gvalue); + } + } else { + // Otherwise this must be a gtk_combo_box_text, in which case this + // function will return the value of the current item, instead. + value = gtk_combo_box_get_active_text(GTK_COMBO_BOX(widget)); + } + + // Get the name of this combo box. + std::string name = GetWidgetName(widget); + + // Send the notification. + AccessibilityComboBoxInfo info(profile, name, value, index, count); + SendAccessibilityNotification(type, &info); +} + +void AccessibilityEventRouterGtk::SendListBoxNotification( + GtkWidget* widget, NotificationType type, Profile* profile) { + // Get the number of items. + GtkTreeModel* model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget)); + int count = gtk_tree_model_iter_n_children(model, NULL); + + // Get the current selected index and its value. + int index = -1; + std::string value; + GtkTreePath* path; + gtk_tree_view_get_cursor(GTK_TREE_VIEW(widget), &path, NULL); + if (path != NULL) { + gint* indices = gtk_tree_path_get_indices(path); + if (indices) + index = indices[0]; + + GtkTreeIter iter; + if (gtk_tree_model_get_iter(model, &iter, path)) { + for (int i = 0; i < gtk_tree_model_get_n_columns(model); i++) { + if (gtk_tree_model_get_column_type(model, i) == G_TYPE_STRING) { + GValue gvalue = { 0 }; + gtk_tree_model_get_value(model, &iter, i, &gvalue); + const char* string_value = g_value_get_string(&gvalue); + if (string_value) { + if (!value.empty()) + value += " "; + value += string_value; + } + g_value_unset(&gvalue); + } + } + } + + gtk_tree_path_free(path); + } + + // Get the name of this control. + std::string name = GetWidgetName(widget); + + // Send the notification. + AccessibilityListBoxInfo info(profile, name, value, index, count); + SendAccessibilityNotification(type, &info); +} + +void AccessibilityEventRouterGtk::SendMenuItemNotification( + GtkWidget* menu, NotificationType type, Profile* profile) { + // Find the focused menu item, recursing into submenus as needed. + GtkWidget* menu_item = GTK_MENU_SHELL(menu)->active_menu_item; + if (!menu_item) + return; + GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); + while (submenu && GTK_MENU_SHELL(submenu)->active_menu_item) { + menu = submenu; + menu_item = GTK_MENU_SHELL(menu)->active_menu_item; + if (!menu_item) + return; + submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); + } + + // Figure out the item index and total number of items. + GList* items = gtk_container_get_children(GTK_CONTAINER(menu)); + guint count = g_list_length(items); + int index = g_list_index(items, static_cast<gconstpointer>(menu_item)); + + // Get the menu item's label. + std::string name; +#if GTK_CHECK_VERSION(2, 16, 0) + name = gtk_menu_item_get_label(GTK_MENU_ITEM(menu_item)); +#else + GList* children = gtk_container_get_children(GTK_CONTAINER(menu_item)); + for (GList* l = g_list_first(children); l != NULL; l = g_list_next(l)) { + GtkWidget* child = static_cast<GtkWidget*>(l->data); + if (GTK_IS_LABEL(child)) { + name = gtk_label_get_label(GTK_LABEL(child)); + break; + } + } +#endif + + // Send the event. + AccessibilityMenuItemInfo info(profile, name, submenu != NULL, index, count); + SendAccessibilityNotification(type, &info); +} |