OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/browser/gtk/accessibility_event_router_gtk.h" |
| 6 |
| 7 #include "base/basictypes.h" |
| 8 #include "base/stl_util-inl.h" |
| 9 #include "chrome/browser/extensions/extension_accessibility_api.h" |
| 10 #include "chrome/browser/gtk/gtk_chrome_link_button.h" |
| 11 #include "chrome/browser/profile.h" |
| 12 #include "chrome/common/notification_type.h" |
| 13 |
| 14 namespace { |
| 15 |
| 16 // |
| 17 // Callbacks triggered by signals on gtk widgets. |
| 18 // |
| 19 |
| 20 gboolean OnWidgetFocused(GSignalInvocationHint *ihint, |
| 21 guint n_param_values, |
| 22 const GValue* param_values, |
| 23 gpointer user_data) { |
| 24 GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); |
| 25 reinterpret_cast<AccessibilityEventRouter *>(user_data) |
| 26 ->DispatchAccessibilityNotification( |
| 27 widget, NotificationType::ACCESSIBILITY_CONTROL_FOCUSED); |
| 28 return true; |
| 29 } |
| 30 |
| 31 gboolean OnButtonClicked(GSignalInvocationHint *ihint, |
| 32 guint n_param_values, |
| 33 const GValue* param_values, |
| 34 gpointer user_data) { |
| 35 GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); |
| 36 // Skip toggle buttons because we're also listening on "toggle" events. |
| 37 if (GTK_IS_TOGGLE_BUTTON(widget)) |
| 38 return true; |
| 39 reinterpret_cast<AccessibilityEventRouter *>(user_data) |
| 40 ->DispatchAccessibilityNotification( |
| 41 widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); |
| 42 return true; |
| 43 } |
| 44 |
| 45 gboolean OnButtonToggled(GSignalInvocationHint *ihint, |
| 46 guint n_param_values, |
| 47 const GValue* param_values, |
| 48 gpointer user_data) { |
| 49 GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); |
| 50 bool checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); |
| 51 // Skip propagating an "uncheck" event for a radio button because it's |
| 52 // redundant; there will always be a corresponding "check" event for |
| 53 // a different radio button the group. |
| 54 if (GTK_IS_RADIO_BUTTON(widget) && !checked) |
| 55 return true; |
| 56 reinterpret_cast<AccessibilityEventRouter *>(user_data) |
| 57 ->DispatchAccessibilityNotification( |
| 58 widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); |
| 59 return true; |
| 60 } |
| 61 |
| 62 gboolean OnSwitchPage(GSignalInvocationHint *ihint, |
| 63 guint n_param_values, |
| 64 const GValue* param_values, |
| 65 gpointer user_data) { |
| 66 GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); |
| 67 reinterpret_cast<AccessibilityEventRouter *>(user_data) |
| 68 ->DispatchAccessibilityNotification( |
| 69 widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); |
| 70 return true; |
| 71 } |
| 72 |
| 73 } // anonymous namespace |
| 74 |
| 75 AccessibilityEventRouter::AccessibilityEventRouter() { |
| 76 // We don't want our event listeners to be installed if accessibility is |
| 77 // disabled. Install listeners so we can install and uninstall them as |
| 78 // needed, then install them now if it's currently enabled. |
| 79 ExtensionAccessibilityEventRouter *accessibility_event_router = |
| 80 ExtensionAccessibilityEventRouter::GetInstance(); |
| 81 accessibility_event_router->AddOnEnabledListener( |
| 82 NewCallback(this, |
| 83 &AccessibilityEventRouter::InstallEventListeners)); |
| 84 accessibility_event_router->AddOnDisabledListener( |
| 85 NewCallback(this, |
| 86 &AccessibilityEventRouter::RemoveEventListeners)); |
| 87 if (accessibility_event_router->IsAccessibilityEnabled()) { |
| 88 InstallEventListeners(); |
| 89 } |
| 90 } |
| 91 |
| 92 // static |
| 93 AccessibilityEventRouter* AccessibilityEventRouter::GetInstance() { |
| 94 return Singleton<AccessibilityEventRouter>::get(); |
| 95 } |
| 96 |
| 97 void AccessibilityEventRouter::InstallEventListeners() { |
| 98 // Create and destroy a GtkNotebook to ensure this module is loaded, |
| 99 // otherwise we can't lookup its signals. All of the other modules we |
| 100 // need will already be loaded by the time we get here. |
| 101 g_object_unref(g_object_ref_sink(gtk_notebook_new())); |
| 102 |
| 103 // Add signal emission hooks for the events we're interested in. |
| 104 focus_hook_ = g_signal_add_emission_hook( |
| 105 g_signal_lookup("focus-in-event", GTK_TYPE_WIDGET), |
| 106 0, OnWidgetFocused, (gpointer)this, NULL); |
| 107 click_hook_ = g_signal_add_emission_hook( |
| 108 g_signal_lookup("clicked", GTK_TYPE_BUTTON), |
| 109 0, OnButtonClicked, (gpointer)this, NULL); |
| 110 toggle_hook_ = g_signal_add_emission_hook( |
| 111 g_signal_lookup("toggled", GTK_TYPE_TOGGLE_BUTTON), |
| 112 0, OnButtonToggled, (gpointer)this, NULL); |
| 113 switch_page_hook_ = g_signal_add_emission_hook( |
| 114 g_signal_lookup("switch-page", GTK_TYPE_NOTEBOOK), |
| 115 0, OnSwitchPage, (gpointer)this, NULL); |
| 116 } |
| 117 |
| 118 void AccessibilityEventRouter::RemoveEventListeners() { |
| 119 g_signal_remove_emission_hook( |
| 120 g_signal_lookup("focus-in-event", GTK_TYPE_WIDGET), focus_hook_); |
| 121 g_signal_remove_emission_hook( |
| 122 g_signal_lookup("clicked", GTK_TYPE_BUTTON), click_hook_); |
| 123 g_signal_remove_emission_hook( |
| 124 g_signal_lookup("toggled", GTK_TYPE_TOGGLE_BUTTON), toggle_hook_); |
| 125 g_signal_remove_emission_hook( |
| 126 g_signal_lookup("switch-page", GTK_TYPE_NOTEBOOK), switch_page_hook_); |
| 127 } |
| 128 |
| 129 void AccessibilityEventRouter::AddRootWidget( |
| 130 GtkWidget* root_widget, Profile* profile) { |
| 131 root_widget_profile_map_[root_widget] = profile; |
| 132 } |
| 133 |
| 134 void AccessibilityEventRouter::RemoveRootWidget(GtkWidget* root_widget) { |
| 135 DCHECK(root_widget_profile_map_.find(root_widget) != |
| 136 root_widget_profile_map_.end()); |
| 137 root_widget_profile_map_.erase(root_widget); |
| 138 } |
| 139 |
| 140 void AccessibilityEventRouter::IgnoreWidget(GtkWidget* widget) { |
| 141 widget_info_map_[widget].ignore = true; |
| 142 } |
| 143 |
| 144 void AccessibilityEventRouter::SetWidgetName( |
| 145 GtkWidget* widget, std::string name) { |
| 146 widget_info_map_[widget].name = name; |
| 147 } |
| 148 |
| 149 void AccessibilityEventRouter::RemoveWidget(GtkWidget* widget) { |
| 150 DCHECK(widget_info_map_.find(widget) != widget_info_map_.end()); |
| 151 widget_info_map_.erase(widget); |
| 152 } |
| 153 |
| 154 bool AccessibilityEventRouter::IsWidgetAccessible( |
| 155 GtkWidget* widget, Profile** profile) { |
| 156 // First see if it's a descendant of a root widget. |
| 157 bool is_accessible = false; |
| 158 for (base::hash_map<GtkWidget*, Profile*>::const_iterator iter = |
| 159 root_widget_profile_map_.begin(); |
| 160 iter != root_widget_profile_map_.end(); |
| 161 ++iter) { |
| 162 if (gtk_widget_is_ancestor(widget, iter->first)) { |
| 163 is_accessible = true; |
| 164 if (profile) |
| 165 *profile = iter->second; |
| 166 break; |
| 167 } |
| 168 } |
| 169 if (!is_accessible) |
| 170 return false; |
| 171 |
| 172 // Now make sure it's not marked as a widget to be ignored. |
| 173 base::hash_map<GtkWidget*, WidgetInfo>::const_iterator iter = |
| 174 widget_info_map_.find(widget); |
| 175 if (iter != widget_info_map_.end() && iter->second.ignore) { |
| 176 is_accessible = false; |
| 177 } |
| 178 |
| 179 return is_accessible; |
| 180 } |
| 181 |
| 182 std::string AccessibilityEventRouter::GetWidgetName(GtkWidget* widget) { |
| 183 base::hash_map<GtkWidget*, WidgetInfo>::const_iterator iter = |
| 184 widget_info_map_.find(widget); |
| 185 if (iter != widget_info_map_.end()) { |
| 186 return iter->second.name; |
| 187 } else { |
| 188 return ""; |
| 189 } |
| 190 } |
| 191 |
| 192 void AccessibilityEventRouter::DispatchAccessibilityNotification( |
| 193 GtkWidget* widget, NotificationType type) { |
| 194 Profile *profile; |
| 195 if (!IsWidgetAccessible(widget, &profile)) |
| 196 return; |
| 197 |
| 198 // The order of these checks matters, because, for example, a radio button |
| 199 // is a subclass of button. We need to catch the most specific type that |
| 200 // we can handle for each object. |
| 201 if (GTK_IS_RADIO_BUTTON(widget)) { |
| 202 SendRadioButtonNotification(widget, type, profile); |
| 203 } else if (GTK_IS_TOGGLE_BUTTON(widget)) { |
| 204 SendCheckboxNotification(widget, type, profile); |
| 205 } else if (GTK_IS_BUTTON(widget)) { |
| 206 SendButtonNotification(widget, type, profile); |
| 207 } else if (GTK_IS_ENTRY(widget)) { |
| 208 SendTextBoxNotification(widget, type, profile); |
| 209 } else if (GTK_IS_NOTEBOOK(widget)) { |
| 210 SendTabNotification(widget, type, profile); |
| 211 } |
| 212 } |
| 213 |
| 214 void AccessibilityEventRouter::SendRadioButtonNotification( |
| 215 GtkWidget* widget, NotificationType type, Profile* profile) { |
| 216 // Get the radio button name |
| 217 std::string button_name = GetWidgetName(widget); |
| 218 if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) |
| 219 button_name = gtk_button_get_label(GTK_BUTTON(widget)); |
| 220 |
| 221 // Get its state |
| 222 bool checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); |
| 223 |
| 224 // Get the index of this radio button and the total number of |
| 225 // radio buttons in the group. |
| 226 int item_count = 0; |
| 227 int item_index = -1; |
| 228 for (GSList* group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(widget)); |
| 229 group; |
| 230 group = group->next) { |
| 231 if (group->data == widget) { |
| 232 item_index = item_count; |
| 233 } |
| 234 item_count++; |
| 235 } |
| 236 item_index = item_count - 1 - item_index; |
| 237 |
| 238 AccessibilityRadioButtonInfo info( |
| 239 profile, button_name, checked, item_index, item_count); |
| 240 SendAccessibilityNotification(type, &info); |
| 241 } |
| 242 |
| 243 void AccessibilityEventRouter::SendCheckboxNotification( |
| 244 GtkWidget* widget, NotificationType type, Profile* profile) { |
| 245 std::string button_name = GetWidgetName(widget); |
| 246 if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) |
| 247 button_name = gtk_button_get_label(GTK_BUTTON(widget)); |
| 248 bool checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); |
| 249 AccessibilityCheckboxInfo info(profile, button_name, checked); |
| 250 SendAccessibilityNotification(type, &info); |
| 251 } |
| 252 |
| 253 void AccessibilityEventRouter::SendButtonNotification( |
| 254 GtkWidget* widget, NotificationType type, Profile* profile) { |
| 255 std::string button_name = GetWidgetName(widget); |
| 256 if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) |
| 257 button_name = gtk_button_get_label(GTK_BUTTON(widget)); |
| 258 AccessibilityButtonInfo info(profile, button_name); |
| 259 SendAccessibilityNotification(type, &info); |
| 260 } |
| 261 |
| 262 void AccessibilityEventRouter::SendTextBoxNotification( |
| 263 GtkWidget* widget, NotificationType type, Profile* profile) { |
| 264 std::string name = GetWidgetName(widget); |
| 265 std::string value = gtk_entry_get_text(GTK_ENTRY(widget)); |
| 266 gint start_pos; |
| 267 gint end_pos; |
| 268 gtk_editable_get_selection_bounds(GTK_EDITABLE(widget), &start_pos, &end_pos); |
| 269 AccessibilityTextBoxInfo info(profile, name, false); |
| 270 info.SetValue(value, start_pos, end_pos); |
| 271 SendAccessibilityNotification(type, &info); |
| 272 } |
| 273 |
| 274 void AccessibilityEventRouter::SendTabNotification( |
| 275 GtkWidget* widget, NotificationType type, Profile* profile) { |
| 276 int index = gtk_notebook_get_current_page(GTK_NOTEBOOK(widget)); |
| 277 int page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(widget)); |
| 278 GtkWidget* page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(widget), index); |
| 279 GtkWidget* label = gtk_notebook_get_tab_label(GTK_NOTEBOOK(widget), page); |
| 280 std::string name = GetWidgetName(widget); |
| 281 if (name.empty() && gtk_label_get_text(GTK_LABEL(label))) { |
| 282 name = gtk_label_get_text(GTK_LABEL(label)); |
| 283 } |
| 284 AccessibilityTabInfo info(profile, name, index, page_count); |
| 285 SendAccessibilityNotification(type, &info); |
| 286 } |
OLD | NEW |