blob: fade7ebe36da4bb18711f2b3077d9672bbd09f8c [file] [log] [blame]
// 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"
#include "views/controls/textfield/native_textfield_gtk.h"
#if defined(TOOLKIT_VIEWS)
#include "views/controls/textfield/gtk_views_textview.h"
#include "views/controls/textfield/gtk_views_entry.h"
#endif
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));
}
bool AccessibilityEventRouterGtk::IsPassword(GtkWidget* widget) {
bool is_password = false;
#if defined (TOOLKIT_VIEWS)
is_password = (GTK_IS_VIEWS_ENTRY(widget) &&
GTK_VIEWS_ENTRY(widget)->host != NULL &&
GTK_VIEWS_ENTRY(widget)->host->IsPassword()) ||
(GTK_IS_VIEWS_TEXTVIEW(widget) &&
GTK_VIEWS_TEXTVIEW(widget)->host != NULL &&
GTK_VIEWS_TEXTVIEW(widget)->host->IsPassword());
#endif
return is_password;
}
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_info_map_[root_widget].refcount++;
root_widget_info_map_[root_widget].profile = profile;
}
void AccessibilityEventRouterGtk::RemoveRootWidget(GtkWidget* root_widget) {
DCHECK(root_widget_info_map_.find(root_widget) !=
root_widget_info_map_.end());
root_widget_info_map_[root_widget].refcount--;
if (root_widget_info_map_[root_widget].refcount == 0) {
root_widget_info_map_.erase(root_widget);
}
}
void AccessibilityEventRouterGtk::AddWidgetNameOverride(
GtkWidget* widget, std::string name) {
widget_info_map_[widget].name = name;
widget_info_map_[widget].refcount++;
}
void AccessibilityEventRouterGtk::RemoveWidgetNameOverride(GtkWidget* widget) {
DCHECK(widget_info_map_.find(widget) != widget_info_map_.end());
widget_info_map_[widget].refcount--;
if (widget_info_map_[widget].refcount == 0) {
widget_info_map_.erase(widget);
}
}
void AccessibilityEventRouterGtk::FindWidget(
GtkWidget* widget, Profile** profile, bool* is_accessible) {
*is_accessible = false;
for (base::hash_map<GtkWidget*, RootWidgetInfo>::const_iterator iter =
root_widget_info_map_.begin();
iter != root_widget_info_map_.end();
++iter) {
if (widget == iter->first || gtk_widget_is_ancestor(widget, iter->first)) {
*is_accessible = true;
if (profile)
*profile = iter->second.profile;
break;
}
}
}
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 there's no message loop, we must be about to shutdown or we're
// running inside a test; either way, there's no reason to do any
// further processing.
if (!MessageLoop::current())
return;
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) {
if (!MessageLoop::current())
return;
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, IsPassword(widget));
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, IsPassword(widget));
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);
}