Index: chrome/browser/gtk/accessibility_event_router_gtk.cc |
=================================================================== |
--- chrome/browser/gtk/accessibility_event_router_gtk.cc (revision 43705) |
+++ chrome/browser/gtk/accessibility_event_router_gtk.cc (working copy) |
@@ -24,10 +24,10 @@ |
const GValue* param_values, |
gpointer user_data) { |
GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); |
- reinterpret_cast<AccessibilityEventRouter *>(user_data)-> |
+ reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> |
DispatchAccessibilityNotification( |
widget, NotificationType::ACCESSIBILITY_CONTROL_FOCUSED); |
- return true; |
+ return TRUE; |
} |
gboolean OnButtonClicked(GSignalInvocationHint *ihint, |
@@ -38,10 +38,10 @@ |
// Skip toggle buttons because we're also listening on "toggle" events. |
if (GTK_IS_TOGGLE_BUTTON(widget)) |
return true; |
- reinterpret_cast<AccessibilityEventRouter *>(user_data)-> |
+ reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> |
DispatchAccessibilityNotification( |
widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); |
- return true; |
+ return TRUE; |
} |
gboolean OnButtonToggled(GSignalInvocationHint *ihint, |
@@ -55,10 +55,10 @@ |
// a different radio button the group. |
if (GTK_IS_RADIO_BUTTON(widget) && !checked) |
return true; |
- reinterpret_cast<AccessibilityEventRouter *>(user_data)-> |
+ reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> |
DispatchAccessibilityNotification( |
widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); |
- return true; |
+ return TRUE; |
} |
gboolean OnPageSwitched(GSignalInvocationHint *ihint, |
@@ -68,10 +68,10 @@ |
GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); |
// The page hasn't switched yet, so defer calling |
// DispatchAccessibilityNotification. |
- reinterpret_cast<AccessibilityEventRouter *>(user_data)-> |
+ reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> |
PostDispatchAccessibilityNotification( |
widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); |
- return true; |
+ return TRUE; |
} |
gboolean OnComboBoxChanged(GSignalInvocationHint *ihint, |
@@ -81,10 +81,10 @@ |
GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); |
if (!GTK_IS_COMBO_BOX(widget)) |
return true; |
- reinterpret_cast<AccessibilityEventRouter *>(user_data)-> |
+ reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> |
DispatchAccessibilityNotification( |
widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); |
- return true; |
+ return TRUE; |
} |
gboolean OnTreeViewCursorChanged(GSignalInvocationHint *ihint, |
@@ -95,10 +95,10 @@ |
if (!GTK_IS_TREE_VIEW(widget)) { |
return true; |
} |
- reinterpret_cast<AccessibilityEventRouter *>(user_data)-> |
+ reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> |
DispatchAccessibilityNotification( |
widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); |
- return true; |
+ return TRUE; |
} |
gboolean OnEntryChanged(GSignalInvocationHint *ihint, |
@@ -107,46 +107,71 @@ |
gpointer user_data) { |
GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); |
if (!GTK_IS_ENTRY(widget)) { |
- return true; |
+ return TRUE; |
} |
// The text hasn't changed yet, so defer calling |
// DispatchAccessibilityNotification. |
- reinterpret_cast<AccessibilityEventRouter *>(user_data)-> |
+ reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> |
PostDispatchAccessibilityNotification( |
widget, NotificationType::ACCESSIBILITY_TEXT_CHANGED); |
- return true; |
+ 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 |
-AccessibilityEventRouter::AccessibilityEventRouter() |
- : method_factory_(this) { |
+AccessibilityEventRouterGtk::AccessibilityEventRouterGtk() |
+ : listening_(false), |
+ most_recent_profile_(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 *accessibility_event_router = |
+ ExtensionAccessibilityEventRouter *extension_event_router = |
ExtensionAccessibilityEventRouter::GetInstance(); |
- accessibility_event_router->AddOnEnabledListener( |
+ extension_event_router->AddOnEnabledListener( |
NewCallback(this, |
- &AccessibilityEventRouter::InstallEventListeners)); |
- accessibility_event_router->AddOnDisabledListener( |
+ &AccessibilityEventRouterGtk::InstallEventListeners)); |
+ extension_event_router->AddOnDisabledListener( |
NewCallback(this, |
- &AccessibilityEventRouter::RemoveEventListeners)); |
- if (accessibility_event_router->IsAccessibilityEnabled()) { |
+ &AccessibilityEventRouterGtk::RemoveEventListeners)); |
+ if (extension_event_router->IsAccessibilityEnabled()) { |
InstallEventListeners(); |
} |
} |
-AccessibilityEventRouter::~AccessibilityEventRouter() { |
+AccessibilityEventRouterGtk::~AccessibilityEventRouterGtk() { |
RemoveEventListeners(); |
} |
// static |
-AccessibilityEventRouter* AccessibilityEventRouter::GetInstance() { |
- return Singleton<AccessibilityEventRouter>::get(); |
+AccessibilityEventRouterGtk* AccessibilityEventRouterGtk::GetInstance() { |
+ return Singleton<AccessibilityEventRouterGtk>::get(); |
} |
-void AccessibilityEventRouter::InstallEventListener( |
+void AccessibilityEventRouterGtk::InstallEventListener( |
const char* signal_name, |
GType widget_type, |
GSignalEmissionHook hook_func) { |
@@ -156,7 +181,7 @@ |
installed_hooks_.push_back(InstalledHook(signal_id, hook_id)); |
} |
-void AccessibilityEventRouter::InstallEventListeners() { |
+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. |
@@ -178,11 +203,12 @@ |
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); |
listening_ = true; |
} |
-void AccessibilityEventRouter::RemoveEventListeners() { |
+void AccessibilityEventRouterGtk::RemoveEventListeners() { |
for (size_t i = 0; i < installed_hooks_.size(); i++) { |
g_signal_remove_emission_hook( |
installed_hooks_[i].signal_id, |
@@ -193,60 +219,60 @@ |
listening_ = false; |
} |
-void AccessibilityEventRouter::AddRootWidget( |
+void AccessibilityEventRouterGtk::AddRootWidget( |
GtkWidget* root_widget, Profile* profile) { |
root_widget_profile_map_[root_widget] = profile; |
} |
-void AccessibilityEventRouter::RemoveRootWidget(GtkWidget* root_widget) { |
+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 AccessibilityEventRouter::IgnoreWidget(GtkWidget* widget) { |
+void AccessibilityEventRouterGtk::IgnoreWidget(GtkWidget* widget) { |
widget_info_map_[widget].ignore = true; |
} |
-void AccessibilityEventRouter::SetWidgetName( |
+void AccessibilityEventRouterGtk::SetWidgetName( |
GtkWidget* widget, std::string name) { |
widget_info_map_[widget].name = name; |
} |
-void AccessibilityEventRouter::RemoveWidget(GtkWidget* widget) { |
+void AccessibilityEventRouterGtk::RemoveWidget(GtkWidget* widget) { |
DCHECK(widget_info_map_.find(widget) != widget_info_map_.end()); |
widget_info_map_.erase(widget); |
} |
-bool AccessibilityEventRouter::IsWidgetAccessible( |
- GtkWidget* widget, Profile** profile) { |
+void AccessibilityEventRouterGtk::FindWidget( |
+ GtkWidget* widget, Profile** profile, bool* is_accessible) { |
+ *is_accessible = false; |
+ |
// First see if it's a descendant of a root widget. |
- bool is_accessible = false; |
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; |
+ *is_accessible = true; |
if (profile) |
*profile = iter->second; |
break; |
} |
} |
- if (!is_accessible) |
- return false; |
+ 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; |
+ *is_accessible = false; |
+ return; |
} |
- |
- return is_accessible; |
} |
-std::string AccessibilityEventRouter::GetWidgetName(GtkWidget* widget) { |
+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()) { |
@@ -256,22 +282,37 @@ |
} |
} |
-void AccessibilityEventRouter::StartListening() { |
+void AccessibilityEventRouterGtk::StartListening() { |
listening_ = true; |
} |
-void AccessibilityEventRouter::StopListening() { |
+void AccessibilityEventRouterGtk::StopListening() { |
listening_ = false; |
} |
-void AccessibilityEventRouter::DispatchAccessibilityNotification( |
+void AccessibilityEventRouterGtk::DispatchAccessibilityNotification( |
GtkWidget* widget, NotificationType type) { |
if (!listening_) |
return; |
- Profile *profile; |
- if (!IsWidgetAccessible(widget, &profile)) |
+ |
+ Profile* profile = NULL; |
+ bool is_accessible; |
+ 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. |
@@ -309,19 +350,19 @@ |
StopListening(); |
MessageLoop::current()->PostTask( |
FROM_HERE, method_factory_.NewRunnableMethod( |
- &AccessibilityEventRouter::StartListening)); |
+ &AccessibilityEventRouterGtk::StartListening)); |
} |
-void AccessibilityEventRouter::PostDispatchAccessibilityNotification( |
+void AccessibilityEventRouterGtk::PostDispatchAccessibilityNotification( |
GtkWidget* widget, NotificationType type) { |
MessageLoop::current()->PostTask( |
FROM_HERE, method_factory_.NewRunnableMethod( |
- &AccessibilityEventRouter::DispatchAccessibilityNotification, |
+ &AccessibilityEventRouterGtk::DispatchAccessibilityNotification, |
widget, |
type)); |
} |
-void AccessibilityEventRouter::SendRadioButtonNotification( |
+void AccessibilityEventRouterGtk::SendRadioButtonNotification( |
GtkWidget* widget, NotificationType type, Profile* profile) { |
// Get the radio button name |
std::string button_name = GetWidgetName(widget); |
@@ -350,7 +391,7 @@ |
SendAccessibilityNotification(type, &info); |
} |
-void AccessibilityEventRouter::SendCheckboxNotification( |
+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))) |
@@ -360,7 +401,7 @@ |
SendAccessibilityNotification(type, &info); |
} |
-void AccessibilityEventRouter::SendButtonNotification( |
+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))) |
@@ -369,7 +410,7 @@ |
SendAccessibilityNotification(type, &info); |
} |
-void AccessibilityEventRouter::SendTextBoxNotification( |
+void AccessibilityEventRouterGtk::SendTextBoxNotification( |
GtkWidget* widget, NotificationType type, Profile* profile) { |
std::string name = GetWidgetName(widget); |
std::string value = gtk_entry_get_text(GTK_ENTRY(widget)); |
@@ -381,7 +422,7 @@ |
SendAccessibilityNotification(type, &info); |
} |
-void AccessibilityEventRouter::SendTabNotification( |
+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)); |
@@ -395,7 +436,7 @@ |
SendAccessibilityNotification(type, &info); |
} |
-void AccessibilityEventRouter::SendComboBoxNotification( |
+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. |
@@ -442,7 +483,7 @@ |
SendAccessibilityNotification(type, &info); |
} |
-void AccessibilityEventRouter::SendListBoxNotification( |
+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)); |
@@ -485,3 +526,43 @@ |
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); |
+} |