| 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/callback.h" | |
| 9 #include "base/message_loop.h" | |
| 10 #include "base/stl_util-inl.h" | |
| 11 #include "chrome/browser/extensions/extension_accessibility_api.h" | |
| 12 #include "chrome/browser/gtk/gtk_chrome_link_button.h" | |
| 13 #include "chrome/browser/profiles/profile.h" | |
| 14 #include "chrome/common/notification_type.h" | |
| 15 | |
| 16 #if defined(TOOLKIT_VIEWS) | |
| 17 #include "views/controls/textfield/gtk_views_textview.h" | |
| 18 #include "views/controls/textfield/gtk_views_entry.h" | |
| 19 #include "views/controls/textfield/native_textfield_gtk.h" | |
| 20 #endif | |
| 21 | |
| 22 namespace { | |
| 23 | |
| 24 // | |
| 25 // Callbacks triggered by signals on gtk widgets. | |
| 26 // | |
| 27 | |
| 28 gboolean OnWidgetFocused(GSignalInvocationHint *ihint, | |
| 29 guint n_param_values, | |
| 30 const GValue* param_values, | |
| 31 gpointer user_data) { | |
| 32 GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); | |
| 33 reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> | |
| 34 DispatchAccessibilityNotification( | |
| 35 widget, NotificationType::ACCESSIBILITY_CONTROL_FOCUSED); | |
| 36 return TRUE; | |
| 37 } | |
| 38 | |
| 39 gboolean OnButtonClicked(GSignalInvocationHint *ihint, | |
| 40 guint n_param_values, | |
| 41 const GValue* param_values, | |
| 42 gpointer user_data) { | |
| 43 GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); | |
| 44 // Skip toggle buttons because we're also listening on "toggle" events. | |
| 45 if (GTK_IS_TOGGLE_BUTTON(widget)) | |
| 46 return true; | |
| 47 reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> | |
| 48 DispatchAccessibilityNotification( | |
| 49 widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); | |
| 50 return TRUE; | |
| 51 } | |
| 52 | |
| 53 gboolean OnButtonToggled(GSignalInvocationHint *ihint, | |
| 54 guint n_param_values, | |
| 55 const GValue* param_values, | |
| 56 gpointer user_data) { | |
| 57 GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); | |
| 58 bool checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); | |
| 59 // Skip propagating an "uncheck" event for a radio button because it's | |
| 60 // redundant; there will always be a corresponding "check" event for | |
| 61 // a different radio button the group. | |
| 62 if (GTK_IS_RADIO_BUTTON(widget) && !checked) | |
| 63 return true; | |
| 64 reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> | |
| 65 DispatchAccessibilityNotification( | |
| 66 widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); | |
| 67 return TRUE; | |
| 68 } | |
| 69 | |
| 70 gboolean OnPageSwitched(GSignalInvocationHint *ihint, | |
| 71 guint n_param_values, | |
| 72 const GValue* param_values, | |
| 73 gpointer user_data) { | |
| 74 GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); | |
| 75 // The page hasn't switched yet, so defer calling | |
| 76 // DispatchAccessibilityNotification. | |
| 77 reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> | |
| 78 PostDispatchAccessibilityNotification( | |
| 79 widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); | |
| 80 return TRUE; | |
| 81 } | |
| 82 | |
| 83 gboolean OnComboBoxChanged(GSignalInvocationHint *ihint, | |
| 84 guint n_param_values, | |
| 85 const GValue* param_values, | |
| 86 gpointer user_data) { | |
| 87 GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); | |
| 88 if (!GTK_IS_COMBO_BOX(widget)) | |
| 89 return true; | |
| 90 reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> | |
| 91 DispatchAccessibilityNotification( | |
| 92 widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); | |
| 93 return TRUE; | |
| 94 } | |
| 95 | |
| 96 gboolean OnTreeViewCursorChanged(GSignalInvocationHint *ihint, | |
| 97 guint n_param_values, | |
| 98 const GValue* param_values, | |
| 99 gpointer user_data) { | |
| 100 GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); | |
| 101 if (!GTK_IS_TREE_VIEW(widget)) { | |
| 102 return true; | |
| 103 } | |
| 104 reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> | |
| 105 DispatchAccessibilityNotification( | |
| 106 widget, NotificationType::ACCESSIBILITY_CONTROL_ACTION); | |
| 107 return TRUE; | |
| 108 } | |
| 109 | |
| 110 gboolean OnEntryChanged(GSignalInvocationHint *ihint, | |
| 111 guint n_param_values, | |
| 112 const GValue* param_values, | |
| 113 gpointer user_data) { | |
| 114 GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); | |
| 115 if (!GTK_IS_ENTRY(widget)) { | |
| 116 return TRUE; | |
| 117 } | |
| 118 // The text hasn't changed yet, so defer calling | |
| 119 // DispatchAccessibilityNotification. | |
| 120 reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> | |
| 121 PostDispatchAccessibilityNotification( | |
| 122 widget, NotificationType::ACCESSIBILITY_TEXT_CHANGED); | |
| 123 return TRUE; | |
| 124 } | |
| 125 | |
| 126 gboolean OnTextBufferChanged(GSignalInvocationHint *ihint, | |
| 127 guint n_param_values, | |
| 128 const GValue* param_values, | |
| 129 gpointer user_data) { | |
| 130 // The text hasn't changed yet, so defer calling | |
| 131 // DispatchAccessibilityNotification. | |
| 132 reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> | |
| 133 PostDispatchAccessibilityNotification( | |
| 134 NULL, NotificationType::ACCESSIBILITY_TEXT_CHANGED); | |
| 135 return TRUE; | |
| 136 } | |
| 137 | |
| 138 gboolean OnTextViewChanged(GSignalInvocationHint *ihint, | |
| 139 guint n_param_values, | |
| 140 const GValue* param_values, | |
| 141 gpointer user_data) { | |
| 142 GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); | |
| 143 if (!GTK_IS_TEXT_VIEW(widget)) { | |
| 144 return TRUE; | |
| 145 } | |
| 146 // The text hasn't changed yet, so defer calling | |
| 147 // DispatchAccessibilityNotification. | |
| 148 reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> | |
| 149 PostDispatchAccessibilityNotification( | |
| 150 widget, NotificationType::ACCESSIBILITY_TEXT_CHANGED); | |
| 151 return TRUE; | |
| 152 } | |
| 153 | |
| 154 gboolean OnMenuMoveCurrent(GSignalInvocationHint *ihint, | |
| 155 guint n_param_values, | |
| 156 const GValue* param_values, | |
| 157 gpointer user_data) { | |
| 158 // Get the widget (the GtkMenu). | |
| 159 GtkWidget* widget = GTK_WIDGET(g_value_get_object(param_values)); | |
| 160 | |
| 161 // Moving may move us into or out of a submenu, so after the menu | |
| 162 // item moves, |widget| may not be valid anymore. To be safe, then, | |
| 163 // find the topmost ancestor of this menu and post the notification | |
| 164 // dispatch on that menu. Then the dispatcher will recurse into submenus | |
| 165 // as necessary to figure out which item is focused. | |
| 166 while (GTK_MENU_SHELL(widget)->parent_menu_shell) | |
| 167 widget = GTK_MENU_SHELL(widget)->parent_menu_shell; | |
| 168 | |
| 169 // The menu item hasn't moved yet, so we want to defer calling | |
| 170 // DispatchAccessibilityNotification until after it does. | |
| 171 reinterpret_cast<AccessibilityEventRouterGtk*>(user_data)-> | |
| 172 PostDispatchAccessibilityNotification( | |
| 173 widget, NotificationType::ACCESSIBILITY_CONTROL_FOCUSED); | |
| 174 return TRUE; | |
| 175 } | |
| 176 | |
| 177 } // anonymous namespace | |
| 178 | |
| 179 AccessibilityEventRouterGtk::AccessibilityEventRouterGtk() | |
| 180 : listening_(false), | |
| 181 most_recent_profile_(NULL), | |
| 182 most_recent_widget_(NULL), | |
| 183 method_factory_(this) { | |
| 184 // We don't want our event listeners to be installed if accessibility is | |
| 185 // disabled. Install listeners so we can install and uninstall them as | |
| 186 // needed, then install them now if it's currently enabled. | |
| 187 ExtensionAccessibilityEventRouter *extension_event_router = | |
| 188 ExtensionAccessibilityEventRouter::GetInstance(); | |
| 189 extension_event_router->AddOnEnabledListener( | |
| 190 NewCallback(this, | |
| 191 &AccessibilityEventRouterGtk::InstallEventListeners)); | |
| 192 extension_event_router->AddOnDisabledListener( | |
| 193 NewCallback(this, | |
| 194 &AccessibilityEventRouterGtk::RemoveEventListeners)); | |
| 195 if (extension_event_router->IsAccessibilityEnabled()) { | |
| 196 InstallEventListeners(); | |
| 197 } | |
| 198 } | |
| 199 | |
| 200 AccessibilityEventRouterGtk::~AccessibilityEventRouterGtk() { | |
| 201 RemoveEventListeners(); | |
| 202 } | |
| 203 | |
| 204 // static | |
| 205 AccessibilityEventRouterGtk* AccessibilityEventRouterGtk::GetInstance() { | |
| 206 return Singleton<AccessibilityEventRouterGtk>::get(); | |
| 207 } | |
| 208 | |
| 209 void AccessibilityEventRouterGtk::InstallEventListener( | |
| 210 const char* signal_name, | |
| 211 GType widget_type, | |
| 212 GSignalEmissionHook hook_func) { | |
| 213 guint signal_id = g_signal_lookup(signal_name, widget_type); | |
| 214 gulong hook_id = g_signal_add_emission_hook( | |
| 215 signal_id, 0, hook_func, reinterpret_cast<gpointer>(this), NULL); | |
| 216 installed_hooks_.push_back(InstalledHook(signal_id, hook_id)); | |
| 217 } | |
| 218 | |
| 219 bool AccessibilityEventRouterGtk::IsPassword(GtkWidget* widget) { | |
| 220 bool is_password = false; | |
| 221 #if defined (TOOLKIT_VIEWS) | |
| 222 is_password = (GTK_IS_VIEWS_ENTRY(widget) && | |
| 223 GTK_VIEWS_ENTRY(widget)->host != NULL && | |
| 224 GTK_VIEWS_ENTRY(widget)->host->IsPassword()) || | |
| 225 (GTK_IS_VIEWS_TEXTVIEW(widget) && | |
| 226 GTK_VIEWS_TEXTVIEW(widget)->host != NULL && | |
| 227 GTK_VIEWS_TEXTVIEW(widget)->host->IsPassword()); | |
| 228 #endif | |
| 229 return is_password; | |
| 230 } | |
| 231 | |
| 232 | |
| 233 void AccessibilityEventRouterGtk::InstallEventListeners() { | |
| 234 // Create and destroy each type of widget we need signals for, | |
| 235 // to ensure their modules are loaded, otherwise g_signal_lookup | |
| 236 // might fail. | |
| 237 g_object_unref(g_object_ref_sink(gtk_combo_box_new())); | |
| 238 g_object_unref(g_object_ref_sink(gtk_entry_new())); | |
| 239 g_object_unref(g_object_ref_sink(gtk_notebook_new())); | |
| 240 g_object_unref(g_object_ref_sink(gtk_toggle_button_new())); | |
| 241 g_object_unref(g_object_ref_sink(gtk_tree_view_new())); | |
| 242 g_object_unref(g_object_ref_sink(gtk_text_view_new())); | |
| 243 g_object_unref(g_object_ref_sink(gtk_text_buffer_new(NULL))); | |
| 244 | |
| 245 // Add signal emission hooks for the events we're interested in. | |
| 246 InstallEventListener("clicked", GTK_TYPE_BUTTON, OnButtonClicked); | |
| 247 InstallEventListener("changed", GTK_TYPE_COMBO_BOX, OnComboBoxChanged); | |
| 248 InstallEventListener("cursor-changed", GTK_TYPE_TREE_VIEW, | |
| 249 OnTreeViewCursorChanged); | |
| 250 InstallEventListener("changed", GTK_TYPE_ENTRY, OnEntryChanged); | |
| 251 InstallEventListener("insert-text", GTK_TYPE_ENTRY, OnEntryChanged); | |
| 252 InstallEventListener("delete-text", GTK_TYPE_ENTRY, OnEntryChanged); | |
| 253 InstallEventListener("move-cursor", GTK_TYPE_ENTRY, OnEntryChanged); | |
| 254 InstallEventListener("focus-in-event", GTK_TYPE_WIDGET, OnWidgetFocused); | |
| 255 InstallEventListener("switch-page", GTK_TYPE_NOTEBOOK, OnPageSwitched); | |
| 256 InstallEventListener("toggled", GTK_TYPE_TOGGLE_BUTTON, OnButtonToggled); | |
| 257 InstallEventListener("move-current", GTK_TYPE_MENU, OnMenuMoveCurrent); | |
| 258 InstallEventListener("changed", GTK_TYPE_TEXT_BUFFER, OnTextBufferChanged); | |
| 259 InstallEventListener("move-cursor", GTK_TYPE_TEXT_VIEW, OnTextViewChanged); | |
| 260 | |
| 261 listening_ = true; | |
| 262 } | |
| 263 | |
| 264 void AccessibilityEventRouterGtk::RemoveEventListeners() { | |
| 265 for (size_t i = 0; i < installed_hooks_.size(); i++) { | |
| 266 g_signal_remove_emission_hook( | |
| 267 installed_hooks_[i].signal_id, | |
| 268 installed_hooks_[i].hook_id); | |
| 269 } | |
| 270 installed_hooks_.clear(); | |
| 271 | |
| 272 listening_ = false; | |
| 273 } | |
| 274 | |
| 275 void AccessibilityEventRouterGtk::AddRootWidget( | |
| 276 GtkWidget* root_widget, Profile* profile) { | |
| 277 root_widget_info_map_[root_widget].refcount++; | |
| 278 root_widget_info_map_[root_widget].profile = profile; | |
| 279 } | |
| 280 | |
| 281 void AccessibilityEventRouterGtk::RemoveRootWidget(GtkWidget* root_widget) { | |
| 282 DCHECK(root_widget_info_map_.find(root_widget) != | |
| 283 root_widget_info_map_.end()); | |
| 284 root_widget_info_map_[root_widget].refcount--; | |
| 285 if (root_widget_info_map_[root_widget].refcount == 0) { | |
| 286 root_widget_info_map_.erase(root_widget); | |
| 287 } | |
| 288 } | |
| 289 | |
| 290 void AccessibilityEventRouterGtk::AddWidgetNameOverride( | |
| 291 GtkWidget* widget, std::string name) { | |
| 292 widget_info_map_[widget].name = name; | |
| 293 widget_info_map_[widget].refcount++; | |
| 294 } | |
| 295 | |
| 296 void AccessibilityEventRouterGtk::RemoveWidgetNameOverride(GtkWidget* widget) { | |
| 297 DCHECK(widget_info_map_.find(widget) != widget_info_map_.end()); | |
| 298 widget_info_map_[widget].refcount--; | |
| 299 if (widget_info_map_[widget].refcount == 0) { | |
| 300 widget_info_map_.erase(widget); | |
| 301 } | |
| 302 } | |
| 303 | |
| 304 void AccessibilityEventRouterGtk::FindWidget( | |
| 305 GtkWidget* widget, Profile** profile, bool* is_accessible) { | |
| 306 *is_accessible = false; | |
| 307 | |
| 308 for (base::hash_map<GtkWidget*, RootWidgetInfo>::const_iterator iter = | |
| 309 root_widget_info_map_.begin(); | |
| 310 iter != root_widget_info_map_.end(); | |
| 311 ++iter) { | |
| 312 if (widget == iter->first || gtk_widget_is_ancestor(widget, iter->first)) { | |
| 313 *is_accessible = true; | |
| 314 if (profile) | |
| 315 *profile = iter->second.profile; | |
| 316 break; | |
| 317 } | |
| 318 } | |
| 319 } | |
| 320 | |
| 321 std::string AccessibilityEventRouterGtk::GetWidgetName(GtkWidget* widget) { | |
| 322 base::hash_map<GtkWidget*, WidgetInfo>::const_iterator iter = | |
| 323 widget_info_map_.find(widget); | |
| 324 if (iter != widget_info_map_.end()) { | |
| 325 return iter->second.name; | |
| 326 } else { | |
| 327 return ""; | |
| 328 } | |
| 329 } | |
| 330 | |
| 331 void AccessibilityEventRouterGtk::StartListening() { | |
| 332 listening_ = true; | |
| 333 } | |
| 334 | |
| 335 void AccessibilityEventRouterGtk::StopListening() { | |
| 336 listening_ = false; | |
| 337 } | |
| 338 | |
| 339 void AccessibilityEventRouterGtk::DispatchAccessibilityNotification( | |
| 340 GtkWidget* widget, NotificationType type) { | |
| 341 // If there's no message loop, we must be about to shutdown or we're | |
| 342 // running inside a test; either way, there's no reason to do any | |
| 343 // further processing. | |
| 344 if (!MessageLoop::current()) | |
| 345 return; | |
| 346 | |
| 347 if (!listening_) | |
| 348 return; | |
| 349 | |
| 350 Profile* profile = NULL; | |
| 351 bool is_accessible; | |
| 352 | |
| 353 // Special case: when we get ACCESSIBILITY_TEXT_CHANGED, we don't get | |
| 354 // a pointer to the widget, so we try to retrieve it from the most recent | |
| 355 // widget. | |
| 356 if (widget == NULL && | |
| 357 type == NotificationType::ACCESSIBILITY_TEXT_CHANGED && | |
| 358 most_recent_widget_ && | |
| 359 GTK_IS_TEXT_VIEW(most_recent_widget_)) { | |
| 360 widget = most_recent_widget_; | |
| 361 } | |
| 362 | |
| 363 if (!widget) | |
| 364 return; | |
| 365 | |
| 366 most_recent_widget_ = widget; | |
| 367 FindWidget(widget, &profile, &is_accessible); | |
| 368 if (profile) | |
| 369 most_recent_profile_ = profile; | |
| 370 | |
| 371 // Special case: a GtkMenu isn't associated with any particular | |
| 372 // toplevel window, so menu events get routed to the profile of | |
| 373 // the most recent event that was associated with a window. | |
| 374 if (GTK_IS_MENU_SHELL(widget) && most_recent_profile_) { | |
| 375 SendMenuItemNotification(widget, type, most_recent_profile_); | |
| 376 return; | |
| 377 } | |
| 378 | |
| 379 // In all other cases, return if this widget wasn't marked as accessible. | |
| 380 if (!is_accessible) | |
| 381 return; | |
| 382 | |
| 383 // The order of these checks matters, because, for example, a radio button | |
| 384 // is a subclass of button, and a combo box is a composite control where | |
| 385 // the focus event goes to the button that's a child of the combo box. | |
| 386 GtkWidget* parent = gtk_widget_get_parent(widget); | |
| 387 if (parent && GTK_IS_BUTTON(widget) && GTK_IS_TREE_VIEW(parent)) { | |
| 388 // This is a list box column header. Currently not supported. | |
| 389 return; | |
| 390 } else if (GTK_IS_COMBO_BOX(widget)) { | |
| 391 SendComboBoxNotification(widget, type, profile); | |
| 392 } else if (parent && GTK_IS_COMBO_BOX(parent)) { | |
| 393 SendComboBoxNotification(parent, type, profile); | |
| 394 } else if (GTK_IS_RADIO_BUTTON(widget)) { | |
| 395 SendRadioButtonNotification(widget, type, profile); | |
| 396 } else if (GTK_IS_TOGGLE_BUTTON(widget)) { | |
| 397 SendCheckboxNotification(widget, type, profile); | |
| 398 } else if (GTK_IS_BUTTON(widget)) { | |
| 399 SendButtonNotification(widget, type, profile); | |
| 400 } else if (GTK_IS_ENTRY(widget)) { | |
| 401 SendEntryNotification(widget, type, profile); | |
| 402 } else if (GTK_IS_TEXT_VIEW(widget)) { | |
| 403 SendTextViewNotification(widget, type, profile); | |
| 404 } else if (GTK_IS_NOTEBOOK(widget)) { | |
| 405 SendTabNotification(widget, type, profile); | |
| 406 } else if (GTK_IS_TREE_VIEW(widget)) { | |
| 407 SendListBoxNotification(widget, type, profile); | |
| 408 } else { | |
| 409 // If we have no idea what this control is, return and skip the | |
| 410 // temporary pause in event listening. | |
| 411 return; | |
| 412 } | |
| 413 | |
| 414 // After this method returns, additional signal handlers will run, | |
| 415 // which will sometimes generate additional signals. To avoid | |
| 416 // generating redundant accessibility notifications for the same | |
| 417 // initial event, stop listening to all signals generated from now | |
| 418 // until this posted task runs. | |
| 419 StopListening(); | |
| 420 MessageLoop::current()->PostTask( | |
| 421 FROM_HERE, method_factory_.NewRunnableMethod( | |
| 422 &AccessibilityEventRouterGtk::StartListening)); | |
| 423 } | |
| 424 | |
| 425 void AccessibilityEventRouterGtk::PostDispatchAccessibilityNotification( | |
| 426 GtkWidget* widget, NotificationType type) { | |
| 427 if (!MessageLoop::current()) | |
| 428 return; | |
| 429 | |
| 430 MessageLoop::current()->PostTask( | |
| 431 FROM_HERE, method_factory_.NewRunnableMethod( | |
| 432 &AccessibilityEventRouterGtk::DispatchAccessibilityNotification, | |
| 433 widget, | |
| 434 type)); | |
| 435 } | |
| 436 | |
| 437 void AccessibilityEventRouterGtk::SendRadioButtonNotification( | |
| 438 GtkWidget* widget, NotificationType type, Profile* profile) { | |
| 439 // Get the radio button name | |
| 440 std::string button_name = GetWidgetName(widget); | |
| 441 if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) | |
| 442 button_name = gtk_button_get_label(GTK_BUTTON(widget)); | |
| 443 | |
| 444 // Get its state | |
| 445 bool checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); | |
| 446 | |
| 447 // Get the index of this radio button and the total number of | |
| 448 // radio buttons in the group. | |
| 449 int item_count = 0; | |
| 450 int item_index = -1; | |
| 451 for (GSList* group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(widget)); | |
| 452 group; | |
| 453 group = group->next) { | |
| 454 if (group->data == widget) { | |
| 455 item_index = item_count; | |
| 456 } | |
| 457 item_count++; | |
| 458 } | |
| 459 item_index = item_count - 1 - item_index; | |
| 460 | |
| 461 AccessibilityRadioButtonInfo info( | |
| 462 profile, button_name, checked, item_index, item_count); | |
| 463 SendAccessibilityNotification(type, &info); | |
| 464 } | |
| 465 | |
| 466 void AccessibilityEventRouterGtk::SendCheckboxNotification( | |
| 467 GtkWidget* widget, NotificationType type, Profile* profile) { | |
| 468 std::string button_name = GetWidgetName(widget); | |
| 469 if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) | |
| 470 button_name = gtk_button_get_label(GTK_BUTTON(widget)); | |
| 471 bool checked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); | |
| 472 AccessibilityCheckboxInfo info(profile, button_name, checked); | |
| 473 SendAccessibilityNotification(type, &info); | |
| 474 } | |
| 475 | |
| 476 void AccessibilityEventRouterGtk::SendButtonNotification( | |
| 477 GtkWidget* widget, NotificationType type, Profile* profile) { | |
| 478 std::string button_name = GetWidgetName(widget); | |
| 479 if (button_name.empty() && gtk_button_get_label(GTK_BUTTON(widget))) | |
| 480 button_name = gtk_button_get_label(GTK_BUTTON(widget)); | |
| 481 AccessibilityButtonInfo info(profile, button_name); | |
| 482 SendAccessibilityNotification(type, &info); | |
| 483 } | |
| 484 | |
| 485 void AccessibilityEventRouterGtk::SendEntryNotification( | |
| 486 GtkWidget* widget, NotificationType type, Profile* profile) { | |
| 487 std::string name = GetWidgetName(widget); | |
| 488 std::string value = gtk_entry_get_text(GTK_ENTRY(widget)); | |
| 489 gint start_pos; | |
| 490 gint end_pos; | |
| 491 gtk_editable_get_selection_bounds(GTK_EDITABLE(widget), &start_pos, &end_pos); | |
| 492 AccessibilityTextBoxInfo info(profile, name, IsPassword(widget)); | |
| 493 info.SetValue(value, start_pos, end_pos); | |
| 494 SendAccessibilityNotification(type, &info); | |
| 495 } | |
| 496 | |
| 497 void AccessibilityEventRouterGtk::SendTextViewNotification( | |
| 498 GtkWidget* widget, NotificationType type, Profile* profile) { | |
| 499 std::string name = GetWidgetName(widget); | |
| 500 GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget)); | |
| 501 GtkTextIter start, end; | |
| 502 gtk_text_buffer_get_bounds(buffer, &start, &end); | |
| 503 gchar* text = gtk_text_buffer_get_text(buffer, &start, &end, false); | |
| 504 std::string value = text; | |
| 505 g_free(text); | |
| 506 GtkTextIter sel_start, sel_end; | |
| 507 gtk_text_buffer_get_selection_bounds(buffer, &sel_start, &sel_end); | |
| 508 int start_pos = gtk_text_iter_get_offset(&sel_start); | |
| 509 int end_pos = gtk_text_iter_get_offset(&sel_end); | |
| 510 AccessibilityTextBoxInfo info(profile, name, IsPassword(widget)); | |
| 511 info.SetValue(value, start_pos, end_pos); | |
| 512 SendAccessibilityNotification(type, &info); | |
| 513 } | |
| 514 | |
| 515 void AccessibilityEventRouterGtk::SendTabNotification( | |
| 516 GtkWidget* widget, NotificationType type, Profile* profile) { | |
| 517 int index = gtk_notebook_get_current_page(GTK_NOTEBOOK(widget)); | |
| 518 int page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(widget)); | |
| 519 GtkWidget* page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(widget), index); | |
| 520 GtkWidget* label = gtk_notebook_get_tab_label(GTK_NOTEBOOK(widget), page); | |
| 521 std::string name = GetWidgetName(widget); | |
| 522 if (name.empty() && gtk_label_get_text(GTK_LABEL(label))) { | |
| 523 name = gtk_label_get_text(GTK_LABEL(label)); | |
| 524 } | |
| 525 AccessibilityTabInfo info(profile, name, index, page_count); | |
| 526 SendAccessibilityNotification(type, &info); | |
| 527 } | |
| 528 | |
| 529 void AccessibilityEventRouterGtk::SendComboBoxNotification( | |
| 530 GtkWidget* widget, NotificationType type, Profile* profile) { | |
| 531 // Get the index of the selected item. Will return -1 if no item is | |
| 532 // active, which matches the semantics of the extension API. | |
| 533 int index = gtk_combo_box_get_active(GTK_COMBO_BOX(widget)); | |
| 534 | |
| 535 // Get the number of items. | |
| 536 GtkTreeModel* model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget)); | |
| 537 int count = gtk_tree_model_iter_n_children(model, NULL); | |
| 538 | |
| 539 // Get the value of the current item, if possible. Note that the | |
| 540 // model behind the combo box could be arbitrarily complex in theory, | |
| 541 // but this code just handles flat lists where the first string column | |
| 542 // contains the display value. | |
| 543 std::string value; | |
| 544 int string_column_index = -1; | |
| 545 for (int i = 0; i < gtk_tree_model_get_n_columns(model); i++) { | |
| 546 if (gtk_tree_model_get_column_type(model, i) == G_TYPE_STRING) { | |
| 547 string_column_index = i; | |
| 548 break; | |
| 549 } | |
| 550 } | |
| 551 if (string_column_index) { | |
| 552 GtkTreeIter iter; | |
| 553 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter)) { | |
| 554 GValue gvalue = { 0 }; | |
| 555 gtk_tree_model_get_value(model, &iter, string_column_index, &gvalue); | |
| 556 const char* string_value = g_value_get_string(&gvalue); | |
| 557 if (string_value) { | |
| 558 value = string_value; | |
| 559 } | |
| 560 g_value_unset(&gvalue); | |
| 561 } | |
| 562 } else { | |
| 563 // Otherwise this must be a gtk_combo_box_text, in which case this | |
| 564 // function will return the value of the current item, instead. | |
| 565 value = gtk_combo_box_get_active_text(GTK_COMBO_BOX(widget)); | |
| 566 } | |
| 567 | |
| 568 // Get the name of this combo box. | |
| 569 std::string name = GetWidgetName(widget); | |
| 570 | |
| 571 // Send the notification. | |
| 572 AccessibilityComboBoxInfo info(profile, name, value, index, count); | |
| 573 SendAccessibilityNotification(type, &info); | |
| 574 } | |
| 575 | |
| 576 void AccessibilityEventRouterGtk::SendListBoxNotification( | |
| 577 GtkWidget* widget, NotificationType type, Profile* profile) { | |
| 578 // Get the number of items. | |
| 579 GtkTreeModel* model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget)); | |
| 580 int count = gtk_tree_model_iter_n_children(model, NULL); | |
| 581 | |
| 582 // Get the current selected index and its value. | |
| 583 int index = -1; | |
| 584 std::string value; | |
| 585 GtkTreePath* path; | |
| 586 gtk_tree_view_get_cursor(GTK_TREE_VIEW(widget), &path, NULL); | |
| 587 if (path != NULL) { | |
| 588 gint* indices = gtk_tree_path_get_indices(path); | |
| 589 if (indices) | |
| 590 index = indices[0]; | |
| 591 | |
| 592 GtkTreeIter iter; | |
| 593 if (gtk_tree_model_get_iter(model, &iter, path)) { | |
| 594 for (int i = 0; i < gtk_tree_model_get_n_columns(model); i++) { | |
| 595 if (gtk_tree_model_get_column_type(model, i) == G_TYPE_STRING) { | |
| 596 GValue gvalue = { 0 }; | |
| 597 gtk_tree_model_get_value(model, &iter, i, &gvalue); | |
| 598 const char* string_value = g_value_get_string(&gvalue); | |
| 599 if (string_value) { | |
| 600 if (!value.empty()) | |
| 601 value += " "; | |
| 602 value += string_value; | |
| 603 } | |
| 604 g_value_unset(&gvalue); | |
| 605 } | |
| 606 } | |
| 607 } | |
| 608 | |
| 609 gtk_tree_path_free(path); | |
| 610 } | |
| 611 | |
| 612 // Get the name of this control. | |
| 613 std::string name = GetWidgetName(widget); | |
| 614 | |
| 615 // Send the notification. | |
| 616 AccessibilityListBoxInfo info(profile, name, value, index, count); | |
| 617 SendAccessibilityNotification(type, &info); | |
| 618 } | |
| 619 | |
| 620 void AccessibilityEventRouterGtk::SendMenuItemNotification( | |
| 621 GtkWidget* menu, NotificationType type, Profile* profile) { | |
| 622 // Find the focused menu item, recursing into submenus as needed. | |
| 623 GtkWidget* menu_item = GTK_MENU_SHELL(menu)->active_menu_item; | |
| 624 if (!menu_item) | |
| 625 return; | |
| 626 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); | |
| 627 while (submenu && GTK_MENU_SHELL(submenu)->active_menu_item) { | |
| 628 menu = submenu; | |
| 629 menu_item = GTK_MENU_SHELL(menu)->active_menu_item; | |
| 630 if (!menu_item) | |
| 631 return; | |
| 632 submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); | |
| 633 } | |
| 634 | |
| 635 // Figure out the item index and total number of items. | |
| 636 GList* items = gtk_container_get_children(GTK_CONTAINER(menu)); | |
| 637 guint count = g_list_length(items); | |
| 638 int index = g_list_index(items, static_cast<gconstpointer>(menu_item)); | |
| 639 | |
| 640 // Get the menu item's label. | |
| 641 std::string name; | |
| 642 #if GTK_CHECK_VERSION(2, 16, 0) | |
| 643 name = gtk_menu_item_get_label(GTK_MENU_ITEM(menu_item)); | |
| 644 #else | |
| 645 GList* children = gtk_container_get_children(GTK_CONTAINER(menu_item)); | |
| 646 for (GList* l = g_list_first(children); l != NULL; l = g_list_next(l)) { | |
| 647 GtkWidget* child = static_cast<GtkWidget*>(l->data); | |
| 648 if (GTK_IS_LABEL(child)) { | |
| 649 name = gtk_label_get_label(GTK_LABEL(child)); | |
| 650 break; | |
| 651 } | |
| 652 } | |
| 653 #endif | |
| 654 | |
| 655 // Send the event. | |
| 656 AccessibilityMenuItemInfo info(profile, name, submenu != NULL, index, count); | |
| 657 SendAccessibilityNotification(type, &info); | |
| 658 } | |
| OLD | NEW |