Index: chrome/browser/gtk/accessibility_event_router_gtk.cc |
=================================================================== |
--- chrome/browser/gtk/accessibility_event_router_gtk.cc (revision 0) |
+++ chrome/browser/gtk/accessibility_event_router_gtk.cc (revision 0) |
@@ -0,0 +1,286 @@ |
+// 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/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<AccessibilityEventRouter *>(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<AccessibilityEventRouter *>(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<AccessibilityEventRouter *>(user_data) |
+ ->DispatchAccessibilityNotification( |
+ widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); |
+ return true; |
+} |
+ |
+gboolean OnSwitchPage(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<AccessibilityEventRouter *>(user_data) |
+ ->DispatchAccessibilityNotification( |
+ widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); |
+ return true; |
+} |
+ |
+} // anonymous namespace |
+ |
+AccessibilityEventRouter::AccessibilityEventRouter() { |
+ // 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::GetInstance(); |
+ accessibility_event_router->AddOnEnabledListener( |
+ NewCallback(this, |
+ &AccessibilityEventRouter::InstallEventListeners)); |
+ accessibility_event_router->AddOnDisabledListener( |
+ NewCallback(this, |
+ &AccessibilityEventRouter::RemoveEventListeners)); |
+ if (accessibility_event_router->IsAccessibilityEnabled()) { |
+ InstallEventListeners(); |
+ } |
+} |
+ |
+// static |
+AccessibilityEventRouter* AccessibilityEventRouter::GetInstance() { |
+ return Singleton<AccessibilityEventRouter>::get(); |
+} |
+ |
+void AccessibilityEventRouter::InstallEventListeners() { |
+ // Create and destroy a GtkNotebook to ensure this module is loaded, |
+ // otherwise we can't lookup its signals. All of the other modules we |
+ // need will already be loaded by the time we get here. |
+ g_object_unref(g_object_ref_sink(gtk_notebook_new())); |
+ |
+ // Add signal emission hooks for the events we're interested in. |
+ focus_hook_ = g_signal_add_emission_hook( |
+ g_signal_lookup("focus-in-event", GTK_TYPE_WIDGET), |
+ 0, OnWidgetFocused, (gpointer)this, NULL); |
+ click_hook_ = g_signal_add_emission_hook( |
+ g_signal_lookup("clicked", GTK_TYPE_BUTTON), |
+ 0, OnButtonClicked, (gpointer)this, NULL); |
+ toggle_hook_ = g_signal_add_emission_hook( |
+ g_signal_lookup("toggled", GTK_TYPE_TOGGLE_BUTTON), |
+ 0, OnButtonToggled, (gpointer)this, NULL); |
+ switch_page_hook_ = g_signal_add_emission_hook( |
+ g_signal_lookup("switch-page", GTK_TYPE_NOTEBOOK), |
+ 0, OnSwitchPage, (gpointer)this, NULL); |
+} |
+ |
+void AccessibilityEventRouter::RemoveEventListeners() { |
+ g_signal_remove_emission_hook( |
+ g_signal_lookup("focus-in-event", GTK_TYPE_WIDGET), focus_hook_); |
+ g_signal_remove_emission_hook( |
+ g_signal_lookup("clicked", GTK_TYPE_BUTTON), click_hook_); |
+ g_signal_remove_emission_hook( |
+ g_signal_lookup("toggled", GTK_TYPE_TOGGLE_BUTTON), toggle_hook_); |
+ g_signal_remove_emission_hook( |
+ g_signal_lookup("switch-page", GTK_TYPE_NOTEBOOK), switch_page_hook_); |
+} |
+ |
+void AccessibilityEventRouter::AddRootWidget( |
+ GtkWidget* root_widget, Profile* profile) { |
+ root_widget_profile_map_[root_widget] = profile; |
+} |
+ |
+void AccessibilityEventRouter::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) { |
+ widget_info_map_[widget].ignore = true; |
+} |
+ |
+void AccessibilityEventRouter::SetWidgetName( |
+ GtkWidget* widget, std::string name) { |
+ widget_info_map_[widget].name = name; |
+} |
+ |
+void AccessibilityEventRouter::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) { |
+ // 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; |
+ if (profile) |
+ *profile = iter->second; |
+ break; |
+ } |
+ } |
+ if (!is_accessible) |
+ return false; |
+ |
+ // 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 is_accessible; |
+} |
+ |
+std::string AccessibilityEventRouter::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 AccessibilityEventRouter::DispatchAccessibilityNotification( |
+ GtkWidget* widget, NotificationType type) { |
+ Profile *profile; |
+ if (!IsWidgetAccessible(widget, &profile)) |
+ return; |
+ |
+ // The order of these checks matters, because, for example, a radio button |
+ // is a subclass of button. We need to catch the most specific type that |
+ // we can handle for each object. |
+ 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)) { |
+ SendTextBoxNotification(widget, type, profile); |
+ } else if (GTK_IS_NOTEBOOK(widget)) { |
+ SendTabNotification(widget, type, profile); |
+ } |
+} |
+ |
+void AccessibilityEventRouter::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 AccessibilityEventRouter::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 AccessibilityEventRouter::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 AccessibilityEventRouter::SendTextBoxNotification( |
+ 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 AccessibilityEventRouter::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); |
+} |
Property changes on: chrome/browser/gtk/accessibility_event_router_gtk.cc |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |