From 83548a4b7d23ca252944fa1dabfbe85bf5742157 Mon Sep 17 00:00:00 2001 From: "dmazzoni@chromium.org" Date: Fri, 18 Jun 2010 13:53:37 +0000 Subject: Improve toolbar keyboard accessibility. Design doc: https://docs.google.com/a/google.com/Doc?docid=0ATICCjR-gNReY2djdjkyNnNfNzl4ZnpiODQ2Mg&hl=en BUG=40745 BUG=36728 BUG=36222 TEST=New test added to focus_manager_unittest.cc Review URL: http://codereview.chromium.org/2737010 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@50234 0039d316-1c4b-4281-b951-d872f2087c98 --- chrome/app/chrome_dll.rc | 4 +- chrome/app/chrome_dll_resource.h | 4 + chrome/browser/browser.cc | 39 ++- chrome/browser/browser.h | 4 + chrome/browser/browser_window.h | 9 + chrome/browser/chromeos/frame/browser_view.cc | 14 + chrome/browser/chromeos/frame/browser_view.h | 8 + chrome/browser/chromeos/status/status_area_view.h | 3 +- chrome/browser/cocoa/browser_window_cocoa.h | 3 + chrome/browser/cocoa/browser_window_cocoa.mm | 14 +- chrome/browser/gtk/browser_window_gtk.cc | 12 + chrome/browser/gtk/browser_window_gtk.h | 3 + chrome/browser/views/accelerator_table_gtk.cc | 6 +- chrome/browser/views/accessible_toolbar_view.cc | 351 ++++++++++----------- chrome/browser/views/accessible_toolbar_view.h | 112 ++++--- chrome/browser/views/browser_actions_container.cc | 30 +- chrome/browser/views/browser_actions_container.h | 1 + chrome/browser/views/frame/browser_view.cc | 96 +++++- chrome/browser/views/frame/browser_view.h | 31 +- .../views/location_bar/location_bar_view.cc | 6 +- .../views/location_bar/page_action_image_view.cc | 39 ++- .../views/location_bar/page_action_image_view.h | 3 + .../location_bar/page_action_with_badge_view.cc | 6 + .../location_bar/page_action_with_badge_view.h | 1 + chrome/browser/views/location_bar/star_view.cc | 11 +- chrome/browser/views/location_bar/star_view.h | 2 + chrome/browser/views/toolbar_view.cc | 160 ++-------- chrome/browser/views/toolbar_view.h | 56 +--- chrome/test/test_browser_window.h | 3 + views/controls/button/button.cc | 1 + views/controls/button/menu_button.cc | 20 +- views/controls/button/menu_button.h | 2 +- views/focus/accelerator_handler_gtk.cc | 1 + views/focus/focus_manager.cc | 72 +++-- views/focus/focus_manager.h | 46 +-- views/focus/focus_manager_unittest.cc | 233 ++++++++++++-- views/focus/focus_search.cc | 278 ++++++++++++++++ views/focus/focus_search.h | 120 +++++++ views/view.cc | 13 +- views/view.h | 22 ++ views/views.gyp | 2 + views/widget/root_view.cc | 228 +------------ views/widget/root_view.h | 15 +- views/widget/widget_gtk.cc | 12 +- views/widget/widget_gtk.h | 9 +- views/widget/widget_win.cc | 12 +- views/widget/widget_win.h | 10 +- 47 files changed, 1275 insertions(+), 852 deletions(-) create mode 100644 views/focus/focus_search.cc create mode 100644 views/focus/focus_search.h diff --git a/chrome/app/chrome_dll.rc b/chrome/app/chrome_dll.rc index 03aa3b8..ab241c6 100644 --- a/chrome/app/chrome_dll.rc +++ b/chrome/app/chrome_dll.rc @@ -45,7 +45,8 @@ BEGIN VK_F3, IDC_FIND_NEXT, VIRTKEY "G", IDC_FIND_PREVIOUS, VIRTKEY, CONTROL, SHIFT VK_F3, IDC_FIND_PREVIOUS, VIRTKEY, SHIFT - VK_F6, IDC_FOCUS_LOCATION, VIRTKEY + VK_F6, IDC_FOCUS_NEXT_PANE, VIRTKEY + VK_F6, IDC_FOCUS_PREVIOUS_PANE, VIRTKEY, SHIFT "D", IDC_FOCUS_LOCATION, VIRTKEY, ALT "L", IDC_FOCUS_LOCATION, VIRTKEY, CONTROL VK_F10, IDC_FOCUS_MENU_BAR, VIRTKEY @@ -53,6 +54,7 @@ BEGIN "K", IDC_FOCUS_SEARCH, VIRTKEY, CONTROL "E", IDC_FOCUS_SEARCH, VIRTKEY, CONTROL "T", IDC_FOCUS_TOOLBAR, VIRTKEY, SHIFT, ALT + "B", IDC_FOCUS_BOOKMARKS, VIRTKEY, SHIFT, ALT VK_RIGHT, IDC_FORWARD, VIRTKEY, ALT VK_BACK, IDC_FORWARD, VIRTKEY, SHIFT VK_F11, IDC_FULLSCREEN, VIRTKEY diff --git a/chrome/app/chrome_dll_resource.h b/chrome/app/chrome_dll_resource.h index 8a02d90..ba0756a 100644 --- a/chrome/app/chrome_dll_resource.h +++ b/chrome/app/chrome_dll_resource.h @@ -179,6 +179,10 @@ #define IDC_FOCUS_LOCATION 39001 #define IDC_FOCUS_SEARCH 39002 #define IDC_FOCUS_MENU_BAR 39003 +#define IDC_FOCUS_NEXT_PANE 39004 +#define IDC_FOCUS_PREVIOUS_PANE 39005 +#define IDC_FOCUS_BOOKMARKS 39006 +#define IDC_FOCUS_CHROMEOS_STATUS 39007 // Show various bits of UI #define IDC_OPEN_FILE 40000 diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc index a85e72a..eea0ec5 100644 --- a/chrome/browser/browser.cc +++ b/chrome/browser/browser.cc @@ -1024,6 +1024,9 @@ void Browser::UpdateCommandsForFullscreenMode(bool is_fullscreen) { const bool show_main_ui = (type() == TYPE_NORMAL); #endif + bool main_not_fullscreen_or_popup = + show_main_ui && !is_fullscreen && (type() & TYPE_POPUP) == 0; + // Navigation commands command_updater_.UpdateCommandEnabled(IDC_OPEN_CURRENT_URL, show_main_ui); @@ -1036,8 +1039,15 @@ void Browser::UpdateCommandsForFullscreenMode(bool is_fullscreen) { command_updater_.UpdateCommandEnabled(IDC_FOCUS_LOCATION, show_main_ui); command_updater_.UpdateCommandEnabled(IDC_FOCUS_SEARCH, show_main_ui); command_updater_.UpdateCommandEnabled( - IDC_FOCUS_MENU_BAR, - show_main_ui && !is_fullscreen && (type() & TYPE_POPUP) == 0); + IDC_FOCUS_MENU_BAR, main_not_fullscreen_or_popup); + command_updater_.UpdateCommandEnabled( + IDC_FOCUS_NEXT_PANE, main_not_fullscreen_or_popup); + command_updater_.UpdateCommandEnabled( + IDC_FOCUS_PREVIOUS_PANE, main_not_fullscreen_or_popup); + command_updater_.UpdateCommandEnabled( + IDC_FOCUS_BOOKMARKS, main_not_fullscreen_or_popup); + command_updater_.UpdateCommandEnabled( + IDC_FOCUS_CHROMEOS_STATUS, main_not_fullscreen_or_popup); // Show various bits of UI command_updater_.UpdateCommandEnabled(IDC_DEVELOPER_MENU, show_main_ui); @@ -1547,6 +1557,27 @@ void Browser::FocusLocationBar() { window_->SetFocusToLocationBar(true); } +void Browser::FocusBookmarksToolbar() { + UserMetrics::RecordAction(UserMetricsAction("FocusBookmarksToolbar"), + profile_); + window_->FocusBookmarksToolbar(); +} + +void Browser::FocusChromeOSStatus() { + UserMetrics::RecordAction(UserMetricsAction("FocusChromeOSStatus"), profile_); + window_->FocusChromeOSStatus(); +} + +void Browser::FocusNextPane() { + UserMetrics::RecordAction(UserMetricsAction("FocusNextPane"), profile_); + window_->RotatePaneFocus(true); +} + +void Browser::FocusPreviousPane() { + UserMetrics::RecordAction(UserMetricsAction("FocusPreviousPane"), profile_); + window_->RotatePaneFocus(false); +} + void Browser::FocusSearch() { // TODO(beng): replace this with FocusLocationBar UserMetrics::RecordAction(UserMetricsAction("FocusSearch"), profile_); @@ -1993,6 +2024,10 @@ void Browser::ExecuteCommandWithDisposition( case IDC_FOCUS_LOCATION: FocusLocationBar(); break; case IDC_FOCUS_SEARCH: FocusSearch(); break; case IDC_FOCUS_MENU_BAR: FocusPageAndAppMenus(); break; + case IDC_FOCUS_BOOKMARKS: FocusBookmarksToolbar(); break; + case IDC_FOCUS_CHROMEOS_STATUS: FocusChromeOSStatus(); break; + case IDC_FOCUS_NEXT_PANE: FocusNextPane(); break; + case IDC_FOCUS_PREVIOUS_PANE: FocusPreviousPane(); break; // Show various bits of UI case IDC_OPEN_FILE: OpenFile(); break; diff --git a/chrome/browser/browser.h b/chrome/browser/browser.h index da30b6e..6b7671a 100644 --- a/chrome/browser/browser.h +++ b/chrome/browser/browser.h @@ -503,6 +503,10 @@ class Browser : public TabStripModelDelegate, void FocusLocationBar(); // Also selects any existing text. void FocusSearch(); void FocusPageAndAppMenus(); + void FocusBookmarksToolbar(); + void FocusChromeOSStatus(); + void FocusNextPane(); + void FocusPreviousPane(); // Show various bits of UI void OpenFile(); diff --git a/chrome/browser/browser_window.h b/chrome/browser/browser_window.h index bc2305a..d806e62 100644 --- a/chrome/browser/browser_window.h +++ b/chrome/browser/browser_window.h @@ -148,6 +148,15 @@ class BrowserWindow { // Not used on the Mac, which has a "normal" menu bar. virtual void FocusPageAndAppMenus() = 0; + // Focuses the bookmarks toolbar (for accessibility). + virtual void FocusBookmarksToolbar() = 0; + + // Focuses the Chrome OS status view (for accessibility). + virtual void FocusChromeOSStatus() = 0; + + // Moves keyboard focus to the next pane. + virtual void RotatePaneFocus(bool forwards) = 0; + // Returns whether the bookmark bar is visible or not. virtual bool IsBookmarkBarVisible() const = 0; diff --git a/chrome/browser/chromeos/frame/browser_view.cc b/chrome/browser/chromeos/frame/browser_view.cc index c03141a..124ecca 100644 --- a/chrome/browser/chromeos/frame/browser_view.cc +++ b/chrome/browser/chromeos/frame/browser_view.cc @@ -490,6 +490,11 @@ void BrowserView::SetFocusToLocationBar(bool select_all) { ::BrowserView::SetFocusToLocationBar(select_all); } +void BrowserView::FocusChromeOSStatus() { + SaveFocusedView(); + status_area_->SetToolbarFocus(last_focused_view_storage_id(), NULL); +} + void BrowserView::ToggleCompactNavigationBar() { UIStyle new_style = static_cast((ui_style_ + 1) % 2); if (new_style != StandardStyle && UseVerticalTabs()) @@ -595,6 +600,15 @@ void BrowserView::ShowCompactLocationBarUnderSelectedTab(bool select_all) { } //////////////////////////////////////////////////////////////////////////////// +// BrowserView protected: + +void BrowserView::GetAccessibleToolbars( + std::vector* toolbars) { + ::BrowserView::GetAccessibleToolbars(toolbars); + toolbars->push_back(status_area_); +} + +//////////////////////////////////////////////////////////////////////////////// // BrowserView private: void BrowserView::InitSystemMenu() { diff --git a/chrome/browser/chromeos/frame/browser_view.h b/chrome/browser/chromeos/frame/browser_view.h index 2bdb029..8a53c97 100644 --- a/chrome/browser/chromeos/frame/browser_view.h +++ b/chrome/browser/chromeos/frame/browser_view.h @@ -5,9 +5,12 @@ #ifndef CHROME_BROWSER_CHROMEOS_FRAME_BROWSER_VIEW_H_ #define CHROME_BROWSER_CHROMEOS_FRAME_BROWSER_VIEW_H_ +#include + #include "chrome/browser/chromeos/status/status_area_host.h" #include "chrome/browser/views/frame/browser_view.h" +class AccessibleToolbarView; class TabStripModel; namespace menus { @@ -59,6 +62,7 @@ class BrowserView : public ::BrowserView, virtual void Show(); virtual bool IsToolbarVisible() const; virtual void SetFocusToLocationBar(bool select_all); + virtual void FocusChromeOSStatus(); virtual void ToggleCompactNavigationBar(); virtual views::LayoutManager* CreateLayoutManager() const; virtual void InitTabStrip(TabStripModel* tab_strip_model); @@ -88,6 +92,10 @@ class BrowserView : public ::BrowserView, return ui_style_ == CompactStyle; } + protected: + virtual void GetAccessibleToolbars( + std::vector* toolbars); + private: friend class CompactLocationBarHostTest; diff --git a/chrome/browser/chromeos/status/status_area_view.h b/chrome/browser/chromeos/status/status_area_view.h index c760779..076ed34 100644 --- a/chrome/browser/chromeos/status/status_area_view.h +++ b/chrome/browser/chromeos/status/status_area_view.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_CHROMEOS_STATUS_STATUS_AREA_VIEW_H_ #include "base/basictypes.h" +#include "chrome/browser/views/accessible_toolbar_view.h" #include "views/view.h" namespace chromeos { @@ -19,7 +20,7 @@ class StatusAreaHost; // This class is used to wrap the small informative widgets in the upper-right // of the window title bar. It is used on ChromeOS only. -class StatusAreaView : public views::View { +class StatusAreaView : public AccessibleToolbarView { public: enum OpenTabsMode { OPEN_TABS_ON_LEFT = 1, diff --git a/chrome/browser/cocoa/browser_window_cocoa.h b/chrome/browser/cocoa/browser_window_cocoa.h index 4d8f017..ec1e0e5 100644 --- a/chrome/browser/cocoa/browser_window_cocoa.h +++ b/chrome/browser/cocoa/browser_window_cocoa.h @@ -58,6 +58,9 @@ class BrowserWindowCocoa : public BrowserWindow, bool should_restore_state); virtual void FocusToolbar(); virtual void FocusPageAndAppMenus(); + virtual void FocusBookmarksToolbar(); + virtual void FocusChromeOSStatus(); + virtual void RotatePaneFocus(bool forwards); virtual bool IsBookmarkBarVisible() const; virtual bool IsBookmarkBarAnimating() const; virtual bool IsToolbarVisible() const; diff --git a/chrome/browser/cocoa/browser_window_cocoa.mm b/chrome/browser/cocoa/browser_window_cocoa.mm index 0b8111e..9dbe789 100644 --- a/chrome/browser/cocoa/browser_window_cocoa.mm +++ b/chrome/browser/cocoa/browser_window_cocoa.mm @@ -217,13 +217,25 @@ void BrowserWindowCocoa::UpdateToolbar(TabContents* contents, } void BrowserWindowCocoa::FocusToolbar() { - NOTIMPLEMENTED(); + // Not needed on the Mac. } void BrowserWindowCocoa::FocusPageAndAppMenus() { // Chrome uses the standard Mac OS X menu bar, so this isn't needed. } +void BrowserWindowCocoa::RotatePaneFocus(bool forwards) { + // Not needed on the Mac. +} + +void BrowserWindowCocoa::FocusBookmarksToolbar() { + // Not needed on the Mac. +} + +void BrowserWindowCocoa::FocusChromeOSStatus() { + // Not needed on the Mac. +} + bool BrowserWindowCocoa::IsBookmarkBarVisible() const { return browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); } diff --git a/chrome/browser/gtk/browser_window_gtk.cc b/chrome/browser/gtk/browser_window_gtk.cc index eb708f5..c8a42ff 100644 --- a/chrome/browser/gtk/browser_window_gtk.cc +++ b/chrome/browser/gtk/browser_window_gtk.cc @@ -847,6 +847,18 @@ void BrowserWindowGtk::FocusPageAndAppMenus() { NOTIMPLEMENTED(); } +void BrowserWindowGtk::FocusBookmarksToolbar() { + NOTIMPLEMENTED(); +} + +void BrowserWindowGtk::FocusChromeOSStatus() { + NOTIMPLEMENTED(); +} + +void BrowserWindowGtk::RotatePaneFocus(bool forwards) { + NOTIMPLEMENTED(); +} + bool BrowserWindowGtk::IsBookmarkBarVisible() const { return browser_->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR) && bookmark_bar_.get() && diff --git a/chrome/browser/gtk/browser_window_gtk.h b/chrome/browser/gtk/browser_window_gtk.h index 3f86f43..b62c16e 100644 --- a/chrome/browser/gtk/browser_window_gtk.h +++ b/chrome/browser/gtk/browser_window_gtk.h @@ -76,6 +76,9 @@ class BrowserWindowGtk : public BrowserWindow, bool should_restore_state); virtual void FocusToolbar(); virtual void FocusPageAndAppMenus(); + virtual void FocusBookmarksToolbar(); + virtual void FocusChromeOSStatus(); + virtual void RotatePaneFocus(bool forwards); virtual bool IsBookmarkBarVisible() const; virtual bool IsBookmarkBarAnimating() const; virtual bool IsToolbarVisible() const; diff --git a/chrome/browser/views/accelerator_table_gtk.cc b/chrome/browser/views/accelerator_table_gtk.cc index f755de7..dfa5744 100644 --- a/chrome/browser/views/accelerator_table_gtk.cc +++ b/chrome/browser/views/accelerator_table_gtk.cc @@ -19,7 +19,11 @@ const AcceleratorMapping kAcceleratorMap[] = { { base::VKEY_BROWSER_SEARCH, false, false, false, IDC_FOCUS_SEARCH }, { base::VKEY_L, false, true, false, IDC_FOCUS_LOCATION }, { base::VKEY_D, false, false, true, IDC_FOCUS_LOCATION }, - { base::VKEY_F6, false, false, false, IDC_FOCUS_LOCATION }, + { base::VKEY_T, true, false, true, IDC_FOCUS_TOOLBAR }, + { base::VKEY_B, true, false, true, IDC_FOCUS_BOOKMARKS }, + { base::VKEY_S, true, false, true, IDC_FOCUS_CHROMEOS_STATUS }, + { base::VKEY_F6, false, false, false, IDC_FOCUS_NEXT_PANE }, + { base::VKEY_F6, true, false, false, IDC_FOCUS_PREVIOUS_PANE }, { base::VKEY_F10, false, false, false, IDC_FOCUS_MENU_BAR }, { base::VKEY_MENU, false, false, false, IDC_FOCUS_MENU_BAR }, diff --git a/chrome/browser/views/accessible_toolbar_view.cc b/chrome/browser/views/accessible_toolbar_view.cc index fead6c0..3edd8cf 100644 --- a/chrome/browser/views/accessible_toolbar_view.cc +++ b/chrome/browser/views/accessible_toolbar_view.cc @@ -7,201 +7,178 @@ #include "chrome/browser/views/frame/browser_view.h" #include "chrome/browser/views/accessible_toolbar_view.h" #include "views/controls/button/menu_button.h" +#include "views/controls/native/native_view_host.h" +#include "views/focus/focus_search.h" #include "views/focus/view_storage.h" #include "views/widget/tooltip_manager.h" #include "views/widget/widget.h" AccessibleToolbarView::AccessibleToolbarView() - : selected_focused_view_(NULL), + : toolbar_has_focus_(false), + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), + focus_manager_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(focus_search_(this, true, true)), + home_key_(base::VKEY_HOME, false, false, false), + end_key_(base::VKEY_END, false, false, false), + escape_key_(base::VKEY_ESCAPE, false, false, false), + left_key_(base::VKEY_LEFT, false, false, false), + right_key_(base::VKEY_RIGHT, false, false, false), last_focused_view_storage_id_(-1) { } AccessibleToolbarView::~AccessibleToolbarView() { + if (toolbar_has_focus_) { + focus_manager_->RemoveFocusChangeListener(this); + } } -void AccessibleToolbarView::InitiateTraversal(int view_storage_id) { - // We only traverse if accessibility is active. - if (selected_focused_view_) - return; +bool AccessibleToolbarView::SetToolbarFocus(int view_storage_id, + views::View* initial_focus) { + if (!IsVisible()) + return false; // Save the storage id to the last focused view. This would be used to request // focus to the view when the traversal is ended. last_focused_view_storage_id_ = view_storage_id; - // Request focus to the toolbar. - RequestFocus(); -} + if (!focus_manager_) + focus_manager_ = GetFocusManager(); -views::View* AccessibleToolbarView::GetNextAccessibleView(int view_index, - bool forward) { - int modifier = forward ? 1 : -1; - int current_view_index = view_index + modifier; - - while ((current_view_index >= 0) && - (current_view_index < GetChildViewCount())) { - // Try to find the next available view that can be interacted with. That - // view must be enabled, visible, and traversable. - views::View* current_view = GetChildViewAt(current_view_index); - if (current_view->IsEnabled() && current_view->IsVisible() && - IsAccessibleViewTraversable(current_view)) { - return current_view; - } - current_view_index += modifier; + // Use the provided initial focus if it's visible and enabled, otherwise + // use the first focusable child. + if (!initial_focus || + !IsParentOf(initial_focus) || + !initial_focus->IsVisible() || + !initial_focus->IsEnabled()) { + initial_focus = GetFirstFocusableChild(); } - // No button is available in the specified direction. - return NULL; -} + // Return false if there are no focusable children. + if (!initial_focus) + return false; -bool AccessibleToolbarView::IsAccessibleViewTraversable(views::View* view) { - return true; -} + // Set focus to the initial view + focus_manager_->SetFocusedView(initial_focus); -void AccessibleToolbarView::DidGainFocus() { - // Check to see if the accessible focus should be restored to previously - // focused button. The must be enabled and visible in the toolbar. - if (!selected_focused_view_ || - !selected_focused_view_->IsEnabled() || - !selected_focused_view_->IsVisible()) { - // Find first accessible child (-1 to start search at parent). - selected_focused_view_ = GetNextAccessibleView(-1, true); - - // No buttons enabled or visible. - if (!selected_focused_view_) - return; - } + // If we already have toolbar focus, we're done. + if (toolbar_has_focus_) + return true; - // Set the focus to the current accessible view. - SetFocusToAccessibleView(); -} + // Otherwise, set accelerators and start listening for focus change events. + toolbar_has_focus_ = true; + focus_manager_->RegisterAccelerator(home_key_, this); + focus_manager_->RegisterAccelerator(end_key_, this); + focus_manager_->RegisterAccelerator(escape_key_, this); + focus_manager_->RegisterAccelerator(left_key_, this); + focus_manager_->RegisterAccelerator(right_key_, this); + focus_manager_->AddFocusChangeListener(this); -void AccessibleToolbarView::WillLoseFocus() { - // Any tooltips that are active should be hidden when toolbar loses focus. - if (GetWidget() && GetWidget()->GetTooltipManager()) - GetWidget()->GetTooltipManager()->HideKeyboardTooltip(); - - // Removes the child accessibility view's focus when toolbar loses focus. It - // will not remove the view from the ViewStorage because it might be - // traversing to another toolbar hence the last focused view should not be - // removed. - if (selected_focused_view_) { - selected_focused_view_->SetHotTracked(false); - selected_focused_view_ = NULL; - } + return true; } -void AccessibleToolbarView::ShowContextMenu(const gfx::Point& p, - bool is_mouse_gesture) { - if (selected_focused_view_) - selected_focused_view_->ShowContextMenu(p, is_mouse_gesture); +bool AccessibleToolbarView::SetToolbarFocusAndFocusDefault( + int view_storage_id) { + return SetToolbarFocus(view_storage_id, GetDefaultFocusableChild()); } -void AccessibleToolbarView::RequestFocus() { - // When the toolbar needs to request focus, the default implementation of - // View::RequestFocus requires the View to be focusable. Since ToolbarView is - // not technically focused, we need to temporarily set and remove focus so - // that it can focus back to its focused state. |selected_focused_view_| is - // not necessarily set since it can be null if this view has already lost - // focus, such as traversing through the context menu. - SetFocusable(true); - View::RequestFocus(); - SetFocusable(false); +void AccessibleToolbarView::RemoveToolbarFocusIfNoChildHasFocus() { + views::View* focused_view = focus_manager_->GetFocusedView(); + if (toolbar_has_focus_ && (!focused_view || !IsParentOf(focused_view))) + RemoveToolbarFocus(); } -bool AccessibleToolbarView::OnKeyPressed(const views::KeyEvent& e) { - if (!HasFocus()) - return View::OnKeyPressed(e); +void AccessibleToolbarView::RemoveToolbarFocus() { + focus_manager_->RemoveFocusChangeListener(this); + toolbar_has_focus_ = false; - int focused_view = GetChildIndex(selected_focused_view_); - views::View* next_view = NULL; + focus_manager_->UnregisterAccelerator(home_key_, this); + focus_manager_->UnregisterAccelerator(end_key_, this); + focus_manager_->UnregisterAccelerator(escape_key_, this); + focus_manager_->UnregisterAccelerator(left_key_, this); + focus_manager_->UnregisterAccelerator(right_key_, this); +} - switch (e.GetKeyCode()) { - case base::VKEY_LEFT: - next_view = GetNextAccessibleView(focused_view, false); - break; - case base::VKEY_RIGHT: - next_view = GetNextAccessibleView(focused_view, true); - break; - case base::VKEY_DOWN: - case base::VKEY_RETURN: - if (selected_focused_view_ && selected_focused_view_->GetClassName() == - views::MenuButton::kViewClassName) { - // If a menu button is activated and its menu is displayed, then the - // active tooltip should be hidden. - if (GetWidget()->GetTooltipManager()) - GetWidget()->GetTooltipManager()->HideKeyboardTooltip(); - - // Safe to cast, given to above check. - static_cast(selected_focused_view_)->Activate(); - - // If activate did not trigger a focus change, the menu button should - // remain hot tracked since the view is still focused. - if (selected_focused_view_) - selected_focused_view_->SetHotTracked(true); - return true; - } - default: - // If key is not handled explicitly, pass it on to view. - if (selected_focused_view_) - return selected_focused_view_->OnKeyPressed(e); - else - return View::OnKeyPressed(e); +void AccessibleToolbarView::RestoreLastFocusedView() { + views::ViewStorage* view_storage = views::ViewStorage::GetSharedInstance(); + views::View* last_focused_view = + view_storage->RetrieveView(last_focused_view_storage_id_); + if (last_focused_view) { + focus_manager_->SetFocusedView(last_focused_view); + } else { + // Focus the location bar + views::View* view = GetAncestorWithClassName(BrowserView::kViewClassName); + if (view) { + BrowserView* browser_view = static_cast(view); + browser_view->SetFocusToLocationBar(false); + } } +} - // No buttons enabled, visible, or focus hasn't moved. - if (!next_view || !selected_focused_view_) - return false; - - // Remove hot-tracking from old focused button. - selected_focused_view_->SetHotTracked(false); - - // All is well, update the focused child member variable. - selected_focused_view_ = next_view; +views::View* AccessibleToolbarView::GetFirstFocusableChild() { + FocusTraversable* dummy_focus_traversable; + views::View* dummy_focus_traversable_view; + return focus_search_.FindNextFocusableView( + NULL, false, views::FocusSearch::DOWN, false, + &dummy_focus_traversable, &dummy_focus_traversable_view); +} - // Set the focus to the current accessible view. - SetFocusToAccessibleView(); - return true; +views::View* AccessibleToolbarView::GetLastFocusableChild() { + FocusTraversable* dummy_focus_traversable; + views::View* dummy_focus_traversable_view; + return focus_search_.FindNextFocusableView( + this, true, views::FocusSearch::DOWN, false, + &dummy_focus_traversable, &dummy_focus_traversable_view); } -bool AccessibleToolbarView::OnKeyReleased(const views::KeyEvent& e) { - if (!selected_focused_view_) - return false; +//////////////////////////////////////////////////////////////////////////////// +// View overrides: - // Have keys be handled by the views themselves. - return selected_focused_view_->OnKeyReleased(e); +views::FocusTraversable* AccessibleToolbarView::GetPaneFocusTraversable() { + if (toolbar_has_focus_) + return this; + else + return NULL; } -bool AccessibleToolbarView::SkipDefaultKeyEventProcessing( - const views::KeyEvent& e) { - // Accessibility focus must be present in order to handle ESC and TAB related - // key events. - if (!HasFocus()) - return false; - - // The ancestor *must* be a BrowserView. - views::View* view = GetAncestorWithClassName(BrowserView::kViewClassName); - if (!view) +bool AccessibleToolbarView::AcceleratorPressed( + const views::Accelerator& accelerator) { + // Special case: don't handle arrows for native views, like the + // location bar's edit text view, which needs them for text editing. + views::View* focused_view = focus_manager_->GetFocusedView(); + if (focused_view->GetClassName() == views::NativeViewHost::kViewClassName && + (accelerator.GetKeyCode() == base::VKEY_LEFT || + accelerator.GetKeyCode() == base::VKEY_RIGHT)) { return false; + } - // Given the check above, we can ensure its a BrowserView. - BrowserView* browser_view = static_cast(view); - - // Handle ESC and TAB events. - switch (e.GetKeyCode()) { - case base::VKEY_ESCAPE: { - SetFocusToLastFocusedView(); + switch (accelerator.GetKeyCode()) { + case base::VKEY_ESCAPE: + RemoveToolbarFocus(); + RestoreLastFocusedView(); return true; - } - case base::VKEY_TAB: { - if (e.IsShiftDown()) { - browser_view->TraverseNextAccessibleToolbar(false); - } else { - browser_view->TraverseNextAccessibleToolbar(true); - } + case base::VKEY_LEFT: + focus_manager_->AdvanceFocus(true); return true; - } - default: return false; + case base::VKEY_RIGHT: + focus_manager_->AdvanceFocus(false); + return true; + case base::VKEY_HOME: + focus_manager_->SetFocusedView(GetFirstFocusableChild()); + return true; + case base::VKEY_END: + focus_manager_->SetFocusedView(GetLastFocusableChild()); + return true; + default: + return false; + } +} + +void AccessibleToolbarView::SetVisible(bool flag) { + if (IsVisible() && !flag && toolbar_has_focus_) { + RemoveToolbarFocus(); + RestoreLastFocusedView(); } + View::SetVisible(flag); } bool AccessibleToolbarView::GetAccessibleRole(AccessibilityTypes::Role* role) { @@ -211,53 +188,41 @@ bool AccessibleToolbarView::GetAccessibleRole(AccessibilityTypes::Role* role) { return true; } -void AccessibleToolbarView::ViewHierarchyChanged(bool is_add, View* parent, - View* child) { - // When the toolbar is removed, traverse to the next accessible toolbar. - if (child == selected_focused_view_ && !is_add) { - selected_focused_view_->SetHotTracked(false); - selected_focused_view_ = NULL; - SetFocusToLastFocusedView(); +//////////////////////////////////////////////////////////////////////////////// +// FocusChangeListener overrides: + +void AccessibleToolbarView::FocusWillChange(views::View* focused_before, + views::View* focused_now) { + if (!focused_now || !IsParentOf(focused_now)) { + // The focus is no longer in the toolbar, so we should remove toolbar + // focus (i.e. make most of the controls not focusable again). + // Defer this rather than running it right away, for two reasons: + // 1. Sometimes the focus gets sets to NULL and then immediately back + // to something in this toolbar. We don't want to do anything in + // that case. + // 2. If we do want to remove toolbar focus, we can't remove this as + // a focus change listener while FocusManager is in the middle of + // iterating over the list of listeners. + MessageLoop::current()->PostTask( + FROM_HERE, method_factory_.NewRunnableMethod( + &AccessibleToolbarView::RemoveToolbarFocusIfNoChildHasFocus)); } } -void AccessibleToolbarView::SetFocusToAccessibleView() { - // Hot-track new focused button. - selected_focused_view_->SetHotTracked(true); - - // Show the tooltip for the view that got the focus. - if (GetWidget()->GetTooltipManager()) { - GetWidget()->GetTooltipManager()->ShowKeyboardTooltip( - selected_focused_view_); - } +//////////////////////////////////////////////////////////////////////////////// +// FocusTraversable overrides: -#if defined(OS_WIN) - // Retrieve information to generate an accessible focus event. - gfx::NativeView wnd = GetWidget()->GetNativeView(); - int view_id = selected_focused_view_->GetID(); - // Notify Access Technology that there was a change in keyboard focus. - ::NotifyWinEvent(EVENT_OBJECT_FOCUS, wnd, OBJID_CLIENT, - static_cast(view_id)); -#else - NOTIMPLEMENTED(); -#endif +views::FocusSearch* AccessibleToolbarView::GetFocusSearch() { + DCHECK(toolbar_has_focus_); + return &focus_search_; } -void AccessibleToolbarView::SetFocusToLastFocusedView() { - views::ViewStorage* view_storage = views::ViewStorage::GetSharedInstance(); - views::View* focused_view = - view_storage->RetrieveView(last_focused_view_storage_id_); - if (focused_view) { - view_storage->RemoveView(last_focused_view_storage_id_); - focused_view->RequestFocus(); - } else { - // The ancestor *must* be a BrowserView. The check below can ensure that. - views::View* view = GetAncestorWithClassName(BrowserView::kViewClassName); - if (!view) - return; - BrowserView* browser_view = static_cast(view); +views::FocusTraversable* AccessibleToolbarView::GetFocusTraversableParent() { + DCHECK(toolbar_has_focus_); + return NULL; +} - // Force the focus to be set on the location bar. - browser_view->SetFocusToLocationBar(false); - } +views::View* AccessibleToolbarView::GetFocusTraversableParentView() { + DCHECK(toolbar_has_focus_); + return NULL; } diff --git a/chrome/browser/views/accessible_toolbar_view.h b/chrome/browser/views/accessible_toolbar_view.h index 0af7b01..7ecb572 100644 --- a/chrome/browser/views/accessible_toolbar_view.h +++ b/chrome/browser/views/accessible_toolbar_view.h @@ -5,58 +5,90 @@ #ifndef CHROME_BROWSER_VIEWS_ACCESSIBLE_TOOLBAR_VIEW_H_ #define CHROME_BROWSER_VIEWS_ACCESSIBLE_TOOLBAR_VIEW_H_ +#include "base/hash_tables.h" +#include "base/task.h" +#include "chrome/browser/views/accessibility_event_router_views.h" +#include "views/focus/focus_manager.h" #include "views/view.h" -// This class provides keyboard access to any view that extends it by intiating -// ALT+SHIFT+T. And once you press TAB or SHIFT-TAB, it will traverse all the -// toolbars within Chrome. Child views are traversed in the order they were -// added. +namespace views { +class FocusSearch; +} -class AccessibleToolbarView : public views::View { +// This class provides keyboard access to any view that extends it, typically +// a toolbar. The user sets focus to a control in this view by pressing +// F6 to traverse all panes, or by pressing a shortcut that jumps directly +// to this toolbar. +class AccessibleToolbarView : public views::View, + public views::FocusChangeListener, + public views::FocusTraversable { public: AccessibleToolbarView(); virtual ~AccessibleToolbarView(); - // Initiate the traversal on the toolbar. The last focused view is stored in - // the ViewStorage with the corresponding |view_storage_id|. - void InitiateTraversal(int view_storage_id); + // Set focus to the toolbar with complete keyboard access. + // Focus will be restored to the ViewStorage with id |view_storage_id| + // if the user escapes. If |initial_focus| is not NULL, that control will get + // the initial focus, if it's enabled and focusable. Returns true if + // the toolbar was able to receive focus. + bool SetToolbarFocus(int view_storage_id, View* initial_focus); + + // Set focus to the toolbar with complete keyboard access, with the + // focus initially set to the default child. Focus will be restored + // to the ViewStorage with id |view_storage_id| if the user escapes. + // Returns true if the toolbar was able to receive focus. + bool SetToolbarFocusAndFocusDefault(int view_storage_id); // Overridden from views::View: - virtual void DidGainFocus(); - virtual void WillLoseFocus(); - virtual bool OnKeyPressed(const views::KeyEvent& e); - virtual bool OnKeyReleased(const views::KeyEvent& e); - virtual bool SkipDefaultKeyEventProcessing(const views::KeyEvent& e); - virtual void ShowContextMenu(const gfx::Point& p, bool is_mouse_gesture); - virtual void RequestFocus(); + virtual FocusTraversable* GetPaneFocusTraversable(); + virtual bool AcceleratorPressed(const views::Accelerator& accelerator); + virtual void SetVisible(bool flag); virtual bool GetAccessibleRole(AccessibilityTypes::Role* role); - virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child); - virtual View* GetAccFocusedChildView() { return selected_focused_view_; } + + // Overridden from views::FocusChangeListener: + virtual void FocusWillChange(View* focused_before, + View* focused_now); + + // Overridden from views::FocusTraversable: + virtual views::FocusSearch* GetFocusSearch(); + virtual FocusTraversable* GetFocusTraversableParent(); + virtual View* GetFocusTraversableParentView(); protected: - // Returns the next accessible view on the toolbar, starting from the given - // |view_index|. |forward| when true means it will navigate from left to right - // and vice versa when false. If |view_index| is -1 the first accessible child - // is returned. - views::View* GetNextAccessibleView(int view_index, bool forward); - - // Invoked from GetNextAccessibleViewIndex to determine if |view| can be - // traversed to. Default implementation returns true, override to return false - // for views you don't want reachable. - virtual bool IsAccessibleViewTraversable(views::View* view); - - private: - // Sets the focus to the currently |acc_focused_view_| view. - void SetFocusToAccessibleView(); - - // Retrieve the focused view from the storage so we can request focus back - // to it. If |focus_view| is null, we place focus on the default view. - // |selected_focused_view_| doesn't need to reset here since it will be - // dealt within the WillLoseFocus method. - void SetFocusToLastFocusedView(); - - // Selected child view currently having accessibility focus. - views::View* selected_focused_view_; + // A subclass can override this to provide a default focusable child + // other than the first focusable child. + virtual views::View* GetDefaultFocusableChild() { return NULL; } + + // Remove toolbar focus unless a child (including indirect children) + // still has the focus. + void RemoveToolbarFocusIfNoChildHasFocus(); + + void RemoveToolbarFocus(); + + void RestoreLastFocusedView(); + + View* GetFirstFocusableChild(); + View* GetLastFocusableChild(); + + bool toolbar_has_focus_; + + ScopedRunnableMethodFactory method_factory_; + + // Save the focus manager rather than calling GetFocusManager(), + // so that we can remove focus listeners in the destructor. + views::FocusManager* focus_manager_; + + // Our custom focus search implementation that traps focus in this + // toolbar and traverses all views that are focusable for accessibility, + // not just those that are normally focusable. + views::FocusSearch focus_search_; + + // Registered accelerators + views::Accelerator home_key_; + views::Accelerator end_key_; + views::Accelerator escape_key_; + views::Accelerator left_key_; + views::Accelerator right_key_; // Last focused view that issued this traversal. int last_focused_view_storage_id_; diff --git a/chrome/browser/views/browser_actions_container.cc b/chrome/browser/views/browser_actions_container.cc index 142cceb..18eef4e 100644 --- a/chrome/browser/views/browser_actions_container.cc +++ b/chrome/browser/views/browser_actions_container.cc @@ -227,10 +227,7 @@ bool BrowserActionButton::Activate() { } bool BrowserActionButton::OnMousePressed(const views::MouseEvent& e) { - showing_context_menu_ = e.IsRightMouseButton(); - if (showing_context_menu_) { - SetButtonPushed(); - + if (e.IsRightMouseButton()) { // Get the top left point of this button in screen coordinates. gfx::Point point = gfx::Point(0, 0); ConvertPointToScreen(this, &point); @@ -238,15 +235,7 @@ bool BrowserActionButton::OnMousePressed(const views::MouseEvent& e) { // Make the menu appear below the button. point.Offset(0, height()); - // Reconstructs the menu every time because the menu's contents are dynamic. - context_menu_contents_ = new ExtensionContextMenuModel( - extension(), panel_->browser(), panel_); - context_menu_menu_.reset(new views::Menu2(context_menu_contents_.get())); - context_menu_menu_->RunContextMenuAt(point); - - SetButtonNotPushed(); - showing_context_menu_ = false; - + ShowContextMenu(point, true); return false; } else if (IsPopup()) { return MenuButton::OnMousePressed(e); @@ -278,6 +267,21 @@ void BrowserActionButton::OnMouseExited(const views::MouseEvent& e) { TextButton::OnMouseExited(e); } +void BrowserActionButton::ShowContextMenu(const gfx::Point& p, + bool is_mouse_gesture) { + showing_context_menu_ = true; + SetButtonPushed(); + + // Reconstructs the menu every time because the menu's contents are dynamic. + context_menu_contents_ = new ExtensionContextMenuModel( + extension(), panel_->browser(), panel_); + context_menu_menu_.reset(new views::Menu2(context_menu_contents_.get())); + context_menu_menu_->RunContextMenuAt(p); + + SetButtonNotPushed(); + showing_context_menu_ = false; +} + void BrowserActionButton::SetButtonPushed() { SetState(views::CustomButton::BS_PUSHED); menu_visible_ = true; diff --git a/chrome/browser/views/browser_actions_container.h b/chrome/browser/views/browser_actions_container.h index 918a927..1b7dbed 100644 --- a/chrome/browser/views/browser_actions_container.h +++ b/chrome/browser/views/browser_actions_container.h @@ -89,6 +89,7 @@ class BrowserActionButton : public views::MenuButton, virtual void OnMouseReleased(const views::MouseEvent& e, bool canceled); virtual bool OnKeyReleased(const views::KeyEvent& e); virtual void OnMouseExited(const views::MouseEvent& event); + virtual void ShowContextMenu(const gfx::Point& p, bool is_mouse_gesture); // Does this button's action have a popup? virtual bool IsPopup(); diff --git a/chrome/browser/views/frame/browser_view.cc b/chrome/browser/views/frame/browser_view.cc index b6d5c94..a325e52 100644 --- a/chrome/browser/views/frame/browser_view.cc +++ b/chrome/browser/views/frame/browser_view.cc @@ -407,6 +407,8 @@ void BrowserView::SetShowState(int state) { BrowserView::BrowserView(Browser* browser) : views::ClientView(NULL, NULL), + last_focused_view_storage_id_( + views::ViewStorage::GetSharedInstance()->CreateStorageID()), frame_(NULL), browser_(browser), active_bookmark_bar_(NULL), @@ -425,8 +427,6 @@ BrowserView::BrowserView(Browser* browser) ticker_(0), #endif extension_shelf_(NULL), - last_focused_view_storage_id_( - views::ViewStorage::GetSharedInstance()->CreateStorageID()), extension_app_icon_loader_(this) { browser_->tabstrip_model()->AddObserver(this); } @@ -648,17 +648,6 @@ void BrowserView::PrepareToRunSystemMenu(HMENU menu) { } #endif -void BrowserView::TraverseNextAccessibleToolbar(bool forward) { - // TODO(mohamed) This needs to be smart, that applies to all toolbars. - // Currently it just traverses between bookmarks and toolbar. - if (!forward && toolbar_->IsVisible() && toolbar_->IsEnabled()) { - toolbar_->RequestFocus(); - } else if (forward && bookmark_bar_view_->IsVisible() && - bookmark_bar_view_->IsEnabled()) { - bookmark_bar_view_->RequestFocus(); - } -} - // static void BrowserView::RegisterBrowserViewPrefs(PrefService* prefs) { prefs->RegisterIntegerPref(prefs::kPluginMessageResponseTimeout, @@ -872,7 +861,14 @@ void BrowserView::FocusToolbar() { // Start the traversal within the main toolbar, passing it the storage id // of the view where focus should be returned if the user exits the toolbar. SaveFocusedView(); - toolbar_->InitiateTraversal(last_focused_view_storage_id_); + toolbar_->SetToolbarFocus(last_focused_view_storage_id_, NULL); +} + +void BrowserView::FocusBookmarksToolbar() { + if (active_bookmark_bar_ && bookmark_bar_view_->IsVisible()) { + SaveFocusedView(); + bookmark_bar_view_->SetToolbarFocus(last_focused_view_storage_id_, NULL); + } } void BrowserView::FocusPageAndAppMenus() { @@ -884,7 +880,65 @@ void BrowserView::FocusPageAndAppMenus() { // // Not used on the Mac, which has a normal menu bar. SaveFocusedView(); - toolbar_->EnterMenuBarEmulationMode(last_focused_view_storage_id_, NULL); + toolbar_->SetToolbarFocusAndFocusPageMenu(last_focused_view_storage_id_); +} + +void BrowserView::RotatePaneFocus(bool forwards) { + // This gets called when the user presses F6 (forwards) or Shift+F6 + // (backwards) to rotate to the next pane. Here, our "panes" are the + // tab contents and each of our accessible toolbars. When a toolbar has + // pane focus, all of its controls are accessible in the tab traversal, + // and the tab traversal is "trapped" within that pane. + + // Get a vector of all panes in the order we want them to be focused - + // each of the accessible toolbars, then NULL to represent the tab contents + // getting focus. If one of these is currently invisible or has no + // focusable children it will be automatically skipped. + std::vector accessible_toolbars; + GetAccessibleToolbars(&accessible_toolbars); + // Add NULL, which represents the tab contents getting focus + accessible_toolbars.push_back(NULL); + + // Figure out which toolbar (if any) currently has the focus. + AccessibleToolbarView* current_toolbar = NULL; + views::View* focused_view = GetRootView()->GetFocusedView(); + int index = -1; + int count = static_cast(accessible_toolbars.size()); + if (focused_view) { + for (int i = 0; i < count; i++) { + if (accessible_toolbars[i]->IsParentOf(focused_view)) { + current_toolbar = accessible_toolbars[i]; + index = i; + break; + } + } + } + + // If the focus isn't currently in a toolbar, save the focus so we + // can restore it if the user presses Escape. + if (focused_view && !current_toolbar) + SaveFocusedView(); + + // Try to focus the next pane; if SetToolbarFocusAndFocusDefault returns + // false it means the toolbar didn't have any focusable controls, so skip + // it and try the next one. + for (;;) { + if (forwards) + index = (index + 1) % count; + else + index = ((index - 1) + count + count) % count; + AccessibleToolbarView* next_toolbar = accessible_toolbars[index]; + + if (next_toolbar) { + if (next_toolbar->SetToolbarFocusAndFocusDefault( + last_focused_view_storage_id_)) { + break; + } + } else { + GetTabContentsContainerView()->RequestFocus(); + break; + } + } } void BrowserView::SaveFocusedView() { @@ -1603,6 +1657,18 @@ gfx::Size BrowserView::GetMinimumSize() { } /////////////////////////////////////////////////////////////////////////////// +// BrowserView, protected + +void BrowserView::GetAccessibleToolbars( + std::vector* toolbars) { + // This should be in the order of pane traversal of the toolbars using F6. + // If one of these is invisible or has no focusable children, it will be + // automatically skipped. + toolbars->push_back(toolbar_); + toolbars->push_back(bookmark_bar_view_.get()); +} + +/////////////////////////////////////////////////////////////////////////////// // BrowserView, views::View overrides: std::string BrowserView::GetClassName() const { diff --git a/chrome/browser/views/frame/browser_view.h b/chrome/browser/views/frame/browser_view.h index 6c6c4b4..96a6f86 100644 --- a/chrome/browser/views/frame/browser_view.h +++ b/chrome/browser/views/frame/browser_view.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "app/menus/simple_menu_model.h" #include "base/scoped_ptr.h" @@ -38,6 +39,7 @@ // NOTE: For more information about the objects and files in this directory, // view: http://dev.chromium.org/developers/design-documents/browser-window +class AccessibleToolbarView; class AccessibleViewHelper; class BookmarkBarView; class Browser; @@ -211,10 +213,6 @@ class BrowserView : public BrowserBubbleHost, void PrepareToRunSystemMenu(HMENU menu); #endif - // Traverses to the next toolbar. |forward| when true, will navigate from left - // to right and vice versa when false. - void TraverseNextAccessibleToolbar(bool forward); - // Returns true if the Browser object associated with this BrowserView is a // normal-type window (i.e. a browser window, not an app or popup). bool IsBrowserTypeNormal() const { @@ -287,6 +285,9 @@ class BrowserView : public BrowserBubbleHost, virtual void UpdateToolbar(TabContents* contents, bool should_restore_state); virtual void FocusToolbar(); virtual void FocusPageAndAppMenus(); + virtual void FocusBookmarksToolbar(); + virtual void FocusChromeOSStatus() {} + virtual void RotatePaneFocus(bool forwards); virtual void DestroyBrowser(); virtual bool IsBookmarkBarVisible() const; virtual bool IsBookmarkBarAnimating() const; @@ -397,6 +398,19 @@ class BrowserView : public BrowserBubbleHost, virtual void InfoBarSizeChanged(bool is_animating); protected: + // Appends to |toolbars| a pointer to each AccessibleToolbarView that + // can be traversed using F6, in the order they should be traversed. + // Abstracted here so that it can be extended for Chrome OS. + virtual void GetAccessibleToolbars( + std::vector* toolbars); + + // Save the current focused view to view storage + void SaveFocusedView(); + + int last_focused_view_storage_id() const { + return last_focused_view_storage_id_; + } + // Overridden from views::View: virtual std::string GetClassName() const; virtual void Layout(); @@ -487,12 +501,12 @@ class BrowserView : public BrowserBubbleHost, // Initialize the hung plugin detector. void InitHangMonitor(); - // Save the current focused view to view storage - void SaveFocusedView(); - // Initialize class statics. static void InitClass(); + // Last focused view that issued a tab traversal. + int last_focused_view_storage_id_; + // The BrowserFrame that hosts this view. BrowserFrame* frame_; @@ -592,9 +606,6 @@ class BrowserView : public BrowserBubbleHost, scoped_ptr browser_extender_; - // Last focused view that issued a tab traversal. - int last_focused_view_storage_id_; - UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_; scoped_ptr accessible_view_helper_; diff --git a/chrome/browser/views/location_bar/location_bar_view.cc b/chrome/browser/views/location_bar/location_bar_view.cc index 71c7d68..b387d4a 100644 --- a/chrome/browser/views/location_bar/location_bar_view.cc +++ b/chrome/browser/views/location_bar/location_bar_view.cc @@ -764,11 +764,13 @@ void LocationBarView::RefreshPageActionViews() { page_action_views_.resize(page_actions.size()); - for (size_t i = 0; i < page_actions.size(); ++i) { + // Add the page actions in reverse order, so that the child views are + // inserted in left-to-right order for accessibility. + for (int i = page_actions.size() - 1; i >= 0; --i) { page_action_views_[i] = new PageActionWithBadgeView( new PageActionImageView(this, profile_, page_actions[i])); page_action_views_[i]->SetVisible(false); - AddChildView(page_action_views_[i]); + AddChildView(GetChildIndex(star_view_), page_action_views_[i]); } } diff --git a/chrome/browser/views/location_bar/page_action_image_view.cc b/chrome/browser/views/location_bar/page_action_image_view.cc index 16623d4..e954da7 100644 --- a/chrome/browser/views/location_bar/page_action_image_view.cc +++ b/chrome/browser/views/location_bar/page_action_image_view.cc @@ -42,6 +42,8 @@ PageActionImageView::PageActionImageView(LocationBarView* owner, Extension::kPageActionIconMaxSize), ImageLoadingTracker::DONT_CACHE); } + + set_accessibility_focusable(true); } PageActionImageView::~PageActionImageView() { @@ -98,6 +100,11 @@ void PageActionImageView::ExecuteAction(int button, } } +bool PageActionImageView::GetAccessibleRole(AccessibilityTypes::Role* role) { + *role = AccessibilityTypes::ROLE_PUSHBUTTON; + return true; +} + bool PageActionImageView::OnMousePressed(const views::MouseEvent& event) { // We want to show the bubble on mouse release; that is the standard behavior // for buttons. (Also, triggering on mouse press causes bugs like @@ -119,24 +126,36 @@ void PageActionImageView::OnMouseReleased(const views::MouseEvent& event, // Get the top left point of this button in screen coordinates. gfx::Point menu_origin; ConvertPointToScreen(this, &menu_origin); - // Make the menu appear below the button. menu_origin.Offset(0, height()); - - Extension* extension = profile_->GetExtensionsService()->GetExtensionById( - page_action()->extension_id(), false); - Browser* browser = BrowserView::GetBrowserViewForNativeWindow( - platform_util::GetTopLevel(GetWidget()->GetNativeView()))->browser(); - context_menu_contents_ = - new ExtensionContextMenuModel(extension, browser, this); - context_menu_menu_.reset(new views::Menu2(context_menu_contents_.get())); - context_menu_menu_->RunContextMenuAt(menu_origin); + ShowContextMenu(menu_origin, true); return; } ExecuteAction(button, false); // inspect_with_devtools } +bool PageActionImageView::OnKeyPressed(const views::KeyEvent& e) { + if (e.GetKeyCode() == base::VKEY_SPACE || + e.GetKeyCode() == base::VKEY_RETURN) { + ExecuteAction(1, false); + return true; + } + return false; +} + +void PageActionImageView::ShowContextMenu(const gfx::Point& p, + bool is_mouse_gesture) { + Extension* extension = profile_->GetExtensionsService()->GetExtensionById( + page_action()->extension_id(), false); + Browser* browser = BrowserView::GetBrowserViewForNativeWindow( + platform_util::GetTopLevel(GetWidget()->GetNativeView()))->browser(); + context_menu_contents_ = + new ExtensionContextMenuModel(extension, browser, this); + context_menu_menu_.reset(new views::Menu2(context_menu_contents_.get())); + context_menu_menu_->RunContextMenuAt(p); +} + void PageActionImageView::OnImageLoaded( SkBitmap* image, ExtensionResource resource, int index) { // We loaded icons()->size() icons, plus one extra if the page action had diff --git a/chrome/browser/views/location_bar/page_action_image_view.h b/chrome/browser/views/location_bar/page_action_image_view.h index 64c537d..e61cb3e 100644 --- a/chrome/browser/views/location_bar/page_action_image_view.h +++ b/chrome/browser/views/location_bar/page_action_image_view.h @@ -40,8 +40,11 @@ class PageActionImageView : public views::ImageView, } // Overridden from view. + virtual bool GetAccessibleRole(AccessibilityTypes::Role* role); virtual bool OnMousePressed(const views::MouseEvent& event); virtual void OnMouseReleased(const views::MouseEvent& event, bool canceled); + virtual bool OnKeyPressed(const views::KeyEvent& e); + virtual void ShowContextMenu(const gfx::Point& p, bool is_mouse_gesture); // Overridden from ImageLoadingTracker. virtual void OnImageLoaded( diff --git a/chrome/browser/views/location_bar/page_action_with_badge_view.cc b/chrome/browser/views/location_bar/page_action_with_badge_view.cc index 6d890f9..603b4f9 100644 --- a/chrome/browser/views/location_bar/page_action_with_badge_view.cc +++ b/chrome/browser/views/location_bar/page_action_with_badge_view.cc @@ -13,6 +13,12 @@ PageActionWithBadgeView::PageActionWithBadgeView( AddChildView(image_view_); } +bool PageActionWithBadgeView::GetAccessibleRole( + AccessibilityTypes::Role* role) { + *role = AccessibilityTypes::ROLE_GROUPING; + return true; +} + gfx::Size PageActionWithBadgeView::GetPreferredSize() { return gfx::Size(Extension::kPageActionIconMaxSize, Extension::kPageActionIconMaxSize); diff --git a/chrome/browser/views/location_bar/page_action_with_badge_view.h b/chrome/browser/views/location_bar/page_action_with_badge_view.h index 2df76bb..c54573e 100644 --- a/chrome/browser/views/location_bar/page_action_with_badge_view.h +++ b/chrome/browser/views/location_bar/page_action_with_badge_view.h @@ -19,6 +19,7 @@ class PageActionWithBadgeView : public views::View { PageActionImageView* image_view() { return image_view_; } + virtual bool GetAccessibleRole(AccessibilityTypes::Role* role); virtual gfx::Size GetPreferredSize(); void UpdateVisibility(TabContents* contents, const GURL& url); diff --git a/chrome/browser/views/location_bar/star_view.cc b/chrome/browser/views/location_bar/star_view.cc index 30c7888..c559a27 100644 --- a/chrome/browser/views/location_bar/star_view.cc +++ b/chrome/browser/views/location_bar/star_view.cc @@ -16,6 +16,7 @@ StarView::StarView(CommandUpdater* command_updater) : command_updater_(command_updater) { SetID(VIEW_ID_STAR_BUTTON); SetToggled(false); + set_accessibility_focusable(true); } StarView::~StarView() { @@ -48,6 +49,15 @@ void StarView::OnMouseReleased(const views::MouseEvent& event, bool canceled) { command_updater_->ExecuteCommand(IDC_BOOKMARK_PAGE); } +bool StarView::OnKeyPressed(const views::KeyEvent& e) { + if (e.GetKeyCode() == base::VKEY_SPACE || + e.GetKeyCode() == base::VKEY_RETURN) { + command_updater_->ExecuteCommand(IDC_BOOKMARK_PAGE); + return true; + } + return false; +} + void StarView::InfoBubbleClosing(InfoBubble* info_bubble, bool closed_by_escape) { } @@ -55,4 +65,3 @@ void StarView::InfoBubbleClosing(InfoBubble* info_bubble, bool StarView::CloseOnEscape() { return true; } - diff --git a/chrome/browser/views/location_bar/star_view.h b/chrome/browser/views/location_bar/star_view.h index ce8aadc3..485cfb5 100644 --- a/chrome/browser/views/location_bar/star_view.h +++ b/chrome/browser/views/location_bar/star_view.h @@ -12,6 +12,7 @@ class CommandUpdater; class InfoBubble; namespace views { +class KeyEvent; class MouseEvent; } @@ -28,6 +29,7 @@ class StarView : public views::ImageView, public InfoBubbleDelegate { virtual bool GetAccessibleRole(AccessibilityTypes::Role* role); virtual bool OnMousePressed(const views::MouseEvent& event); virtual void OnMouseReleased(const views::MouseEvent& event, bool canceled); + virtual bool OnKeyPressed(const views::KeyEvent& e); // InfoBubbleDelegate overrides: virtual void InfoBubbleClosing(InfoBubble* info_bubble, diff --git a/chrome/browser/views/toolbar_view.cc b/chrome/browser/views/toolbar_view.cc index 2db68f8..1eee8f8 100644 --- a/chrome/browser/views/toolbar_view.cc +++ b/chrome/browser/views/toolbar_view.cc @@ -81,8 +81,6 @@ ToolbarView::ToolbarView(Browser* browser) profile_(NULL), browser_(browser), profiles_menu_contents_(NULL), - last_focused_view_storage_id_(-1), - menu_bar_emulation_mode_(false), ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), destroyed_flag_(NULL), collapsed_(false) { @@ -113,11 +111,6 @@ ToolbarView::~ToolbarView() { // NOTE: Don't remove the command observers here. This object gets destroyed // after the Browser (which owns the CommandUpdater), so the CommandUpdater is // already gone. - - if (menu_bar_emulation_mode_) { - focus_manager_->UnregisterAccelerators(this); - focus_manager_->RemoveFocusChangeListener(this); - } } void ToolbarView::Init(Profile* profile) { @@ -175,7 +168,6 @@ void ToolbarView::Init(Profile* profile) { page_menu_->SetAccessibleName(l10n_util::GetString(IDS_ACCNAME_PAGE)); page_menu_->SetTooltipText(l10n_util::GetString(IDS_PAGEMENU_TOOLTIP)); page_menu_->SetID(VIEW_ID_PAGE_MENU); - AddChildView(page_menu_); } app_menu_ = new views::MenuButton(NULL, std::wstring(), this, false); @@ -186,19 +178,23 @@ void ToolbarView::Init(Profile* profile) { if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kBookmarkMenu)) { bookmark_menu_ = new BookmarkMenuButton(browser_); - AddChildView(bookmark_menu_); } else { bookmark_menu_ = NULL; } LoadImages(); + // Always add children in order from left to right, for accessibility. AddChildView(back_); AddChildView(forward_); AddChildView(home_); AddChildView(reload_); AddChildView(location_bar_); AddChildView(browser_actions_); + if (bookmark_menu_) + AddChildView(bookmark_menu_); + if (page_menu_) + AddChildView(page_menu_); AddChildView(app_menu_); location_bar_->Init(); @@ -216,8 +212,6 @@ void ToolbarView::Init(Profile* profile) { SetAppMenuModel(new AppMenuModel(this, browser_)); } } - - focus_manager_ = GetFocusManager(); } void ToolbarView::SetProfile(Profile* profile) { @@ -240,44 +234,15 @@ void ToolbarView::SetAppMenuModel(menus::SimpleMenuModel* model) { app_menu_menu_.reset(new views::Menu2(app_menu_model_.get())); } -void ToolbarView::EnterMenuBarEmulationMode(int last_focused_view_storage_id, - views::MenuButton* menu_to_focus) { - last_focused_view_storage_id_ = last_focused_view_storage_id; - if (!menu_to_focus) - menu_to_focus = page_menu_; - - // If we're already in the menu bar emulation mode, just set the focus. - if (menu_bar_emulation_mode_) { - menu_to_focus->RequestFocus(); - return; - } +void ToolbarView::SetToolbarFocusAndFocusLocationBar(int view_storage_id) { + SetToolbarFocus(view_storage_id, location_bar_); +} - // Make the menus focusable and set focus to the initial menu. - menu_bar_emulation_mode_ = true; - page_menu_->SetFocusable(true); - app_menu_->SetFocusable(true); - menu_to_focus->RequestFocus(); - - // Listen so we know when focus has moved to something other than one - // of these menus. - focus_manager_->AddFocusChangeListener(this); - - // Add accelerators so that the usual keys used to interact with a - // menu bar work as expected. - views::Accelerator return_key(base::VKEY_RETURN, false, false, false); - focus_manager_->RegisterAccelerator(return_key, this); - views::Accelerator space(base::VKEY_SPACE, false, false, false); - focus_manager_->RegisterAccelerator(space, this); - views::Accelerator escape(base::VKEY_ESCAPE, false, false, false); - focus_manager_->RegisterAccelerator(escape, this); - views::Accelerator down(base::VKEY_DOWN, false, false, false); - focus_manager_->RegisterAccelerator(down, this); - views::Accelerator up(base::VKEY_UP, false, false, false); - focus_manager_->RegisterAccelerator(up, this); - views::Accelerator left(base::VKEY_LEFT, false, false, false); - focus_manager_->RegisterAccelerator(left, this); - views::Accelerator right(base::VKEY_RIGHT, false, false, false); - focus_manager_->RegisterAccelerator(right, this); +void ToolbarView::SetToolbarFocusAndFocusPageMenu(int view_storage_id) { + if (page_menu_) + SetToolbarFocus(view_storage_id, page_menu_); + else + SetToolbarFocus(view_storage_id, app_menu_); } void ToolbarView::AddMenuListener(views::MenuListener* listener) { @@ -311,32 +276,6 @@ void ToolbarView::SetCollapsed(bool val) { } //////////////////////////////////////////////////////////////////////////////// -// ToolbarView, FocusChangeListener overrides: - -void ToolbarView::FocusWillChange(views::View* focused_before, - views::View* focused_now) { - // If the focus is switching to something outside the menu bar, - // take it out of the focus traversal. - if ((focused_now != NULL) && (focused_now != page_menu_) && - (focused_now != app_menu_)) { - // Post ExitMenuBarEmulationMode to the queue rather than running it - // right away, because otherwise we'll remove ourselves from the - // list of listeners while FocusManager is in the middle of iterating - // over that list. - MessageLoop::current()->PostTask( - FROM_HERE, method_factory_.NewRunnableMethod( - &ToolbarView::ExitMenuBarEmulationMode)); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// ToolbarView, AccessibleToolbarView overrides: - -bool ToolbarView::IsAccessibleViewTraversable(views::View* view) { - return view != location_bar_; -} - -//////////////////////////////////////////////////////////////////////////////// // ToolbarView, Menu::BaseControllerDelegate overrides: bool ToolbarView::GetAcceleratorInfo(int id, menus::Accelerator* accel) { @@ -476,42 +415,6 @@ void ToolbarView::ExecuteCommand(int command_id) { //////////////////////////////////////////////////////////////////////////////// // ToolbarView, views::View overrides: -bool ToolbarView::AcceleratorPressed( - const views::Accelerator& accelerator) { - // The only accelerators we handle here are if the menus are focused. - views::View* focused_view = GetFocusManager()->GetFocusedView(); - if (focused_view != page_menu_ && focused_view != app_menu_) { - ExitMenuBarEmulationMode(); - return false; - } - - // Safe to cast, given the check above. - views::MenuButton* menu = static_cast(focused_view); - switch (accelerator.GetKeyCode()) { - case base::VKEY_ESCAPE: - RestoreLastFocusedView(); - return true; - case base::VKEY_LEFT: - case base::VKEY_RIGHT: - if (page_menu_) { - ((menu == app_menu_) ? page_menu_ : app_menu_)->RequestFocus(); - return true; - } - return false; - case base::VKEY_UP: - case base::VKEY_DOWN: - case base::VKEY_RETURN: - case base::VKEY_SPACE: - // Hide the tooltip before activating a menu button. - if (GetWidget()->GetTooltipManager()) - GetWidget()->GetTooltipManager()->HideKeyboardTooltip(); - ActivateMenuButton(menu); - return true; - default: - return false; - } -} - gfx::Size ToolbarView::GetPreferredSize() { if (IsDisplayModeNormal()) { int min_width = kControlIndent + back_->GetPreferredSize().width() + @@ -661,6 +564,13 @@ void ToolbarView::ThemeChanged() { } //////////////////////////////////////////////////////////////////////////////// +// ToolbarView, protected: + +views::View* ToolbarView::GetDefaultFocusableChild() { + return location_bar_; +} + +//////////////////////////////////////////////////////////////////////////////// // ToolbarView, private: int ToolbarView::PopupTopSpacing() const { @@ -875,34 +785,6 @@ void ToolbarView::ActivateMenuButton(views::MenuButton* menu_button) { menu_button->Activate(); #if defined(OS_WIN) - EnterMenuBarEmulationMode(last_focused_view_storage_id_, menu_button); + SetToolbarFocus(NULL, menu_button); #endif } - -void ToolbarView::ExitMenuBarEmulationMode() { - if ((page_menu_ && page_menu_->HasFocus()) || app_menu_->HasFocus()) - RestoreLastFocusedView(); - - focus_manager_->UnregisterAccelerators(this); - focus_manager_->RemoveFocusChangeListener(this); - if (page_menu_) - page_menu_->SetFocusable(false); - app_menu_->SetFocusable(false); - menu_bar_emulation_mode_ = false; -} - -void ToolbarView::RestoreLastFocusedView() { - views::ViewStorage* view_storage = views::ViewStorage::GetSharedInstance(); - views::View* last_focused_view = - view_storage->RetrieveView(last_focused_view_storage_id_); - if (last_focused_view) { - last_focused_view->RequestFocus(); - } else { - // Focus the location bar - views::View* view = GetAncestorWithClassName(BrowserView::kViewClassName); - if (view) { - BrowserView* browser_view = static_cast(view); - browser_view->SetFocusToLocationBar(false); - } - } -} diff --git a/chrome/browser/views/toolbar_view.h b/chrome/browser/views/toolbar_view.h index a99d658..d343546 100644 --- a/chrome/browser/views/toolbar_view.h +++ b/chrome/browser/views/toolbar_view.h @@ -22,7 +22,6 @@ #include "views/controls/menu/menu.h" #include "views/controls/menu/menu_wrapper.h" #include "views/controls/menu/view_menu_delegate.h" -#include "views/focus/focus_manager.h" #include "views/view.h" class BrowserActionsContainer; @@ -36,7 +35,6 @@ class Menu2; // The Browser Window's toolbar. class ToolbarView : public AccessibleToolbarView, public views::ViewMenuDelegate, - public views::FocusChangeListener, public menus::SimpleMenuModel::Delegate, public LocationBarView::Delegate, public AnimationDelegate, @@ -63,18 +61,15 @@ class ToolbarView : public AccessibleToolbarView, // Sets the app menu model. void SetAppMenuModel(menus::SimpleMenuModel* model); - // Focuses the page menu and enters a special mode where the page - // and app menus are focusable and allow for keyboard navigation just - // like a normal menu bar. As soon as focus leaves one of the menus, - // the special mode is exited. - // - // Pass it the storage id of the view where focus should be returned - // if the user escapes, and the menu button to focus initially. If - // |menu_to_focus| is NULL, it will focus the page menu by default. - // - // Not used on the Mac, which has a "normal" menu bar. - void EnterMenuBarEmulationMode(int last_focused_view_storage_id, - views::MenuButton* menu_to_focus); + // Set focus to the toolbar with complete keyboard access, with the + // focus initially set to the location bar. Focus will be restored + // to the ViewStorage with id |view_storage_id| if the user escapes. + void SetToolbarFocusAndFocusLocationBar(int view_storage_id); + + // Set focus to the toolbar with complete keyboard access, with the + // focus initially set to the page menu. Focus will be restored + // to the ViewStorage with id |view_storage_id| if the user escapes. + void SetToolbarFocusAndFocusPageMenu(int view_storage_id); // Add a listener to receive a callback when the menu opens. void AddMenuListener(views::MenuListener* listener); @@ -92,13 +87,6 @@ class ToolbarView : public AccessibleToolbarView, bool collapsed() const { return collapsed_; } void SetCollapsed(bool val); - // Overridden from views::FocusChangeListener: - virtual void FocusWillChange(views::View* focused_before, - views::View* focused_now); - - // Overridden from AccessibleToolbarView: - virtual bool IsAccessibleViewTraversable(views::View* view); - // Overridden from Menu::BaseControllerDelegate: virtual bool GetAcceleratorInfo(int id, menus::Accelerator* accel); @@ -131,12 +119,16 @@ class ToolbarView : public AccessibleToolbarView, virtual void ExecuteCommand(int command_id); // Overridden from views::View: - virtual bool AcceleratorPressed(const views::Accelerator& accelerator); virtual gfx::Size GetPreferredSize(); virtual void Layout(); virtual void Paint(gfx::Canvas* canvas); virtual void ThemeChanged(); + protected: + // Override this so that when the user presses F6 to rotate toolbar panes, + // the location bar gets focus, not the first control in the toolbar. + virtual views::View* GetDefaultFocusableChild(); + private: // Returns the number of pixels above the location bar in non-normal display. int PopupTopSpacing() const; @@ -165,14 +157,6 @@ class ToolbarView : public AccessibleToolbarView, return display_mode_ == DISPLAYMODE_NORMAL; } - // Take the menus out of the focus traversal, unregister accelerators, - // and stop listening to focus change events. - void ExitMenuBarEmulationMode(); - - // Restore the view that was focused before EnterMenuBarEmulationMode - // was called. - void RestoreLastFocusedView(); - // Starts the recurring timer that periodically asks the upgrade notifier // to pulsate. void ShowUpgradeReminder(); @@ -221,14 +205,6 @@ class ToolbarView : public AccessibleToolbarView, scoped_ptr page_menu_menu_; scoped_ptr app_menu_menu_; - // Save the focus manager rather than calling GetFocusManager(), - // so that we can remove focus listeners in the destructor. - views::FocusManager* focus_manager_; - - // Storage id for the last view that was focused before focus - // was given to one of the toolbar views. - int last_focused_view_storage_id_; - // Vector of listeners to receive callbacks when the menu opens. std::vector menu_listeners_; @@ -239,10 +215,6 @@ class ToolbarView : public AccessibleToolbarView, // once, to create a pulsating effect. base::RepeatingTimer upgrade_reminder_pulse_timer_; - // Are we in the menu bar emulation mode, where the app and page menu - // are temporarily focusable? - bool menu_bar_emulation_mode_; - // Used to post tasks to switch to the next/previous menu. ScopedRunnableMethodFactory method_factory_; diff --git a/chrome/test/test_browser_window.h b/chrome/test/test_browser_window.h index adf3277..1039a81 100644 --- a/chrome/test/test_browser_window.h +++ b/chrome/test/test_browser_window.h @@ -48,6 +48,9 @@ class TestBrowserWindow : public BrowserWindow { bool should_restore_state) {} virtual void FocusToolbar() {} virtual void FocusPageAndAppMenus() {} + virtual void FocusBookmarksToolbar() {} + virtual void FocusChromeOSStatus() {} + virtual void RotatePaneFocus(bool forwards) {} virtual void ShowPageMenu() {} virtual void ShowAppMenu() {} virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event, diff --git a/views/controls/button/button.cc b/views/controls/button/button.cc index 0305451..52d5747 100644 --- a/views/controls/button/button.cc +++ b/views/controls/button/button.cc @@ -52,6 +52,7 @@ Button::Button(ButtonListener* listener) : listener_(listener), tag_(-1), mouse_event_flags_(0) { + set_accessibility_focusable(true); } void Button::NotifyClick(const views::Event& event) { diff --git a/views/controls/button/menu_button.cc b/views/controls/button/menu_button.cc index fe86d7f..3af0326 100644 --- a/views/controls/button/menu_button.cc +++ b/views/controls/button/menu_button.cc @@ -213,17 +213,17 @@ void MenuButton::OnMouseReleased(const MouseEvent& e, } } -// When the space bar or the enter key is pressed we need to show the menu. -bool MenuButton::OnKeyReleased(const KeyEvent& e) { -#if defined(OS_WIN) - if ((e.GetKeyCode() == base::VKEY_SPACE) || - (e.GetKeyCode() == base::VKEY_RETURN)) { - return Activate(); +bool MenuButton::OnKeyPressed(const KeyEvent& e) { + if (e.GetKeyCode() == base::VKEY_SPACE || + e.GetKeyCode() == base::VKEY_RETURN || + e.GetKeyCode() == base::VKEY_UP || + e.GetKeyCode() == base::VKEY_DOWN) { + bool result = Activate(); + if (GetFocusManager()->GetFocusedView() == NULL) + RequestFocus(); + return result; } -#else - NOTIMPLEMENTED(); -#endif - return true; + return false; } // The reason we override View::OnMouseExited is because we get this event when diff --git a/views/controls/button/menu_button.h b/views/controls/button/menu_button.h index c4e95e4..1e20250 100644 --- a/views/controls/button/menu_button.h +++ b/views/controls/button/menu_button.h @@ -57,8 +57,8 @@ class MenuButton : public TextButton { // behavior virtual bool OnMousePressed(const MouseEvent& e); virtual void OnMouseReleased(const MouseEvent& e, bool canceled); - virtual bool OnKeyReleased(const KeyEvent& e); virtual void OnMouseExited(const MouseEvent& event); + virtual bool OnKeyPressed(const KeyEvent& e); // Accessibility accessors, overridden from View. virtual bool GetAccessibleDefaultAction(std::wstring* action); diff --git a/views/focus/accelerator_handler_gtk.cc b/views/focus/accelerator_handler_gtk.cc index fb04e89..ee91906 100644 --- a/views/focus/accelerator_handler_gtk.cc +++ b/views/focus/accelerator_handler_gtk.cc @@ -55,6 +55,7 @@ bool AcceleratorHandler::Dispatch(GdkEvent* event) { gtk_main_do_event(event); return true; } + DCHECK(ptr); // The top-level window or window widget is expected to always be associated diff --git a/views/focus/focus_manager.cc b/views/focus/focus_manager.cc index f637e09..9646bb8 100644 --- a/views/focus/focus_manager.cc +++ b/views/focus/focus_manager.cc @@ -15,6 +15,7 @@ #include "base/keyboard_codes.h" #include "base/logging.h" #include "views/accelerator.h" +#include "views/focus/focus_search.h" #include "views/focus/view_storage.h" #include "views/view.h" #include "views/widget/root_view.h" @@ -122,7 +123,7 @@ bool FocusManager::OnKeyEvent(const KeyEvent& event) { } else if (index >= static_cast(views.size())) { index = 0; } - views[index]->RequestFocus(); + SetFocusedView(views[index]); return false; } @@ -183,7 +184,7 @@ void FocusManager::AdvanceFocus(bool reverse) { // first element on the page. if (v) { v->AboutToRequestFocusFromTabTraversal(reverse); - v->RequestFocus(); + SetFocusedView(v); } } @@ -197,21 +198,36 @@ View* FocusManager::GetNextFocusableView(View* original_starting_view, View* starting_view = NULL; if (original_starting_view) { - if (!reverse) { - // If the starting view has a focus traversable, use it. - // This is the case with WidgetWins for example. - focus_traversable = original_starting_view->GetFocusTraversable(); + // Search up the containment hierarchy to see if a view is acting as + // a pane, and wants to implement its own focus traversable to keep + // the focus trapped within that pane. + View* pane_search = original_starting_view; + while (pane_search) { + focus_traversable = pane_search->GetPaneFocusTraversable(); + if (focus_traversable) { + starting_view = original_starting_view; + break; + } + pane_search = pane_search->GetParent(); + } - // Otherwise default to the root view. - if (!focus_traversable) { + if (!focus_traversable) { + if (!reverse) { + // If the starting view has a focus traversable, use it. + // This is the case with WidgetWins for example. + focus_traversable = original_starting_view->GetFocusTraversable(); + + // Otherwise default to the root view. + if (!focus_traversable) { + focus_traversable = original_starting_view->GetRootView(); + starting_view = original_starting_view; + } + } else { + // When you are going back, starting view's FocusTraversable + // should not be used. focus_traversable = original_starting_view->GetRootView(); starting_view = original_starting_view; } - } else { - // When you are going back, starting view's FocusTraversable should not be - // used. - focus_traversable = original_starting_view->GetRootView(); - starting_view = original_starting_view; } } else { focus_traversable = widget_->GetRootView(); @@ -231,8 +247,8 @@ View* FocusManager::GetNextFocusableView(View* original_starting_view, View* new_starting_view = NULL; // When we are going backward, the parent view might gain the next focus. bool check_starting_view = reverse; - v = parent_focus_traversable->FindNextFocusableView( - starting_view, reverse, FocusTraversable::UP, + v = parent_focus_traversable->GetFocusSearch()->FindNextFocusableView( + starting_view, reverse, FocusSearch::UP, check_starting_view, &new_focus_traversable, &new_starting_view); if (new_focus_traversable) { @@ -368,12 +384,13 @@ View* FocusManager::FindFocusableView(FocusTraversable* focus_traversable, bool reverse) { FocusTraversable* new_focus_traversable = NULL; View* new_starting_view = NULL; - View* v = focus_traversable->FindNextFocusableView(starting_view, - reverse, - FocusTraversable::DOWN, - false, - &new_focus_traversable, - &new_starting_view); + View* v = focus_traversable->GetFocusSearch()->FindNextFocusableView( + starting_view, + reverse, + FocusSearch::DOWN, + false, + &new_focus_traversable, + &new_starting_view); // Let's go down the FocusTraversable tree as much as we can. while (new_focus_traversable) { @@ -382,12 +399,13 @@ View* FocusManager::FindFocusableView(FocusTraversable* focus_traversable, starting_view = new_starting_view; new_focus_traversable = NULL; starting_view = NULL; - v = focus_traversable->FindNextFocusableView(starting_view, - reverse, - FocusTraversable::DOWN, - false, - &new_focus_traversable, - &new_starting_view); + v = focus_traversable->GetFocusSearch()->FindNextFocusableView( + starting_view, + reverse, + FocusSearch::DOWN, + false, + &new_focus_traversable, + &new_starting_view); } return v; } diff --git a/views/focus/focus_manager.h b/views/focus/focus_manager.h index 565184c..d789d12 100644 --- a/views/focus/focus_manager.h +++ b/views/focus/focus_manager.h @@ -48,7 +48,7 @@ // If you are embedding a native view containing a nested RootView (for example // by adding a NativeControl that contains a WidgetWin as its native // component), then you need to: -// - override the View::GetFocusTraversable() method in your outter component. +// - override the View::GetFocusTraversable() method in your outer component. // It should return the RootView of the inner component. This is used when // the focus traversal traverse down the focus hierarchy to enter the nested // RootView. In the example mentioned above, the NativeControl overrides @@ -66,11 +66,12 @@ // hwnd_view_container_->GetRootView()->SetFocusTraversableParent( // native_control); // -// Note that FocusTraversable do not have to be RootViews: TabContents is -// FocusTraversable. +// Note that FocusTraversable do not have to be RootViews: AccessibleToolbarView +// is FocusTraversable. namespace views { +class FocusSearch; class RootView; class View; class Widget; @@ -79,42 +80,9 @@ class Widget; // focus traversal events (due to Tab/Shift-Tab key events). class FocusTraversable { public: - // The direction in which the focus traversal is going. - // TODO (jcampan): add support for lateral (left, right) focus traversal. The - // goal is to switch to focusable views on the same level when using the arrow - // keys (ala Windows: in a dialog box, arrow keys typically move between the - // dialog OK, Cancel buttons). - enum Direction { - UP = 0, - DOWN - }; - - // Should find the next view that should be focused and return it. If a - // FocusTraversable is found while searching for the focusable view, NULL - // should be returned, focus_traversable should be set to the FocusTraversable - // and focus_traversable_view should be set to the view associated with the - // FocusTraversable. - // This call should return NULL if the end of the focus loop is reached. - // - |starting_view| is the view that should be used as the starting point - // when looking for the previous/next view. It may be NULL (in which case - // the first/last view should be used depending if normal/reverse). - // - |reverse| whether we should find the next (reverse is false) or the - // previous (reverse is true) view. - // - |direction| specifies whether we are traversing down (meaning we should - // look into child views) or traversing up (don't look at child views). - // - |check_starting_view| is true if starting_view may obtain the next focus. - // - |focus_traversable| is set to the focus traversable that should be - // traversed if one is found (in which case the call returns NULL). - // - |focus_traversable_view| is set to the view associated with the - // FocusTraversable set in the previous parameter (it is used as the - // starting view when looking for the next focusable view). - - virtual View* FindNextFocusableView(View* starting_view, - bool reverse, - Direction direction, - bool check_starting_view, - FocusTraversable** focus_traversable, - View** focus_traversable_view) = 0; + // Return a FocusSearch object that implements the algorithm to find + // the next or previous focusable view. + virtual FocusSearch* GetFocusSearch() = 0; // Should return the parent FocusTraversable. // The top RootView which is the top FocusTraversable returns NULL. diff --git a/views/focus/focus_manager_unittest.cc b/views/focus/focus_manager_unittest.cc index 80cbd5f4..ae585a7 100644 --- a/views/focus/focus_manager_unittest.cc +++ b/views/focus/focus_manager_unittest.cc @@ -2,10 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Disabled right now as this won't work on BuildBots right now as this test -// require the box it runs on to be unlocked (and no screen-savers). -// The test actually simulates mouse and key events, so if the screen is locked, -// the events don't go to the Chrome window. #include "testing/gtest/include/gtest/gtest.h" #include "app/combobox_model.h" @@ -331,6 +327,41 @@ class DummyComboboxModel : public ComboboxModel { } }; +// A View that can act as a pane. +class PaneView : public View, public FocusTraversable { + public: + PaneView() : focus_search_(NULL) {} + + // If this method is called, this view will use GetPaneFocusTraversable to + // have this provided FocusSearch used instead of the default one, allowing + // you to trap focus within the pane. + void EnablePaneFocus(FocusSearch* focus_search) { + focus_search_ = focus_search; + } + + // Overridden from views::View: + virtual FocusTraversable* GetPaneFocusTraversable() { + if (focus_search_) + return this; + else + return NULL; + } + + // Overridden from views::FocusTraversable: + virtual views::FocusSearch* GetFocusSearch() { + return focus_search_; + } + virtual FocusTraversable* GetFocusTraversableParent() { + return NULL; + } + virtual View* GetFocusTraversableParentView() { + return NULL; + } + + private: + FocusSearch* focus_search_; +}; + class FocusTraversalTest : public FocusManagerTest { public: ~FocusTraversalTest(); @@ -357,10 +388,12 @@ class FocusTraversalTest : public FocusManagerTest { return NULL; } - private: + protected: TabbedPane* style_tab_; BorderView* search_border_view_; DummyComboboxModel combobox_model_; + PaneView* left_container_; + PaneView* right_container_; DISALLOW_COPY_AND_ASSIGN(FocusTraversalTest); }; @@ -378,6 +411,64 @@ FocusTraversalTest::~FocusTraversalTest() { } void FocusTraversalTest::InitContentView() { + // Create a complicated view hierarchy with lots of control types for + // use by all of the focus traversal tests. + // + // Class name, ID, and asterisk next to focusable views: + // + // View + // Checkbox * kTopCheckBoxID + // PaneView kLeftContainerID + // Label kAppleLabelID + // Textfield * kAppleTextfieldID + // Label kOrangeLabelID + // Textfield * kOrangeTextfieldID + // Label kBananaLabelID + // Textfield * kBananaTextfieldID + // Label kKiwiLabelID + // Textfield * kKiwiTextfieldID + // NativeButton * kFruitButtonID + // Checkbox * kFruitCheckBoxID + // Combobox * kComboboxID + // PaneView kRightContainerID + // RadioButton * kAsparagusButtonID + // RadioButton * kBroccoliButtonID + // RadioButton * kCauliflowerButtonID + // View kInnerContainerID + // ScrollView kScrollViewID + // View + // Link * kRosettaLinkID + // Link * kStupeurEtTremblementLinkID + // Link * kDinerGameLinkID + // Link * kRidiculeLinkID + // Link * kClosetLinkID + // Link * kVisitingLinkID + // Link * kAmelieLinkID + // Link * kJoyeuxNoelLinkID + // Link * kCampingLinkID + // Link * kBriceDeNiceLinkID + // Link * kTaxiLinkID + // Link * kAsterixLinkID + // NativeButton * kOKButtonID + // NativeButton * kCancelButtonID + // NativeButton * kHelpButtonID + // TabbedPane * kStyleContainerID + // View + // Checkbox * kBoldCheckBoxID + // Checkbox * kItalicCheckBoxID + // Checkbox * kUnderlinedCheckBoxID + // Link * kStyleHelpLinkID + // Textfield * kStyleTextEditID + // Other + // BorderView kSearchContainerID + // View + // Textfield * kSearchTextfieldID + // NativeButton * kSearchButtonID + // Link * kHelpLinkID + // View * kThumbnailContainerID + // NativeButton * kThumbnailStarID + // NativeButton * kThumbnailSuperStarID + content_view_->set_background( Background::CreateSolidBackground(SK_ColorWHITE)); @@ -387,13 +478,13 @@ void FocusTraversalTest::InitContentView() { cb->SetBounds(10, 10, 200, 20); cb->SetID(kTopCheckBoxID); - View* left_container = new View(); - left_container->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK)); - left_container->set_background( + left_container_ = new PaneView(); + left_container_->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK)); + left_container_->set_background( Background::CreateSolidBackground(240, 240, 240)); - left_container->SetID(kLeftContainerID); - content_view_->AddChildView(left_container); - left_container->SetBounds(10, 35, 250, 200); + left_container_->SetID(kLeftContainerID); + content_view_->AddChildView(left_container_); + left_container_->SetBounds(10, 35, 250, 200); int label_x = 5; int label_width = 50; @@ -404,12 +495,12 @@ void FocusTraversalTest::InitContentView() { Label* label = new Label(L"Apple:"); label->SetID(kAppleLabelID); - left_container->AddChildView(label); + left_container_->AddChildView(label); label->SetBounds(label_x, y, label_width, label_height); Textfield* text_field = new Textfield(); text_field->SetID(kAppleTextfieldID); - left_container->AddChildView(text_field); + left_container_->AddChildView(text_field); text_field->SetBounds(label_x + label_width + 5, y, text_field_width, label_height); @@ -417,12 +508,12 @@ void FocusTraversalTest::InitContentView() { label = new Label(L"Orange:"); label->SetID(kOrangeLabelID); - left_container->AddChildView(label); + left_container_->AddChildView(label); label->SetBounds(label_x, y, label_width, label_height); text_field = new Textfield(); text_field->SetID(kOrangeTextfieldID); - left_container->AddChildView(text_field); + left_container_->AddChildView(text_field); text_field->SetBounds(label_x + label_width + 5, y, text_field_width, label_height); @@ -430,12 +521,12 @@ void FocusTraversalTest::InitContentView() { label = new Label(L"Banana:"); label->SetID(kBananaLabelID); - left_container->AddChildView(label); + left_container_->AddChildView(label); label->SetBounds(label_x, y, label_width, label_height); text_field = new Textfield(); text_field->SetID(kBananaTextfieldID); - left_container->AddChildView(text_field); + left_container_->AddChildView(text_field); text_field->SetBounds(label_x + label_width + 5, y, text_field_width, label_height); @@ -443,12 +534,12 @@ void FocusTraversalTest::InitContentView() { label = new Label(L"Kiwi:"); label->SetID(kKiwiLabelID); - left_container->AddChildView(label); + left_container_->AddChildView(label); label->SetBounds(label_x, y, label_width, label_height); text_field = new Textfield(); text_field->SetID(kKiwiTextfieldID); - left_container->AddChildView(text_field); + left_container_->AddChildView(text_field); text_field->SetBounds(label_x + label_width + 5, y, text_field_width, label_height); @@ -457,47 +548,47 @@ void FocusTraversalTest::InitContentView() { NativeButton* button = new NativeButton(NULL, L"Click me"); button->SetBounds(label_x, y + 10, 80, 30); button->SetID(kFruitButtonID); - left_container->AddChildView(button); + left_container_->AddChildView(button); y += 40; cb = new Checkbox(L"This is another check box"); cb->SetBounds(label_x + label_width + 5, y, 180, 20); cb->SetID(kFruitCheckBoxID); - left_container->AddChildView(cb); + left_container_->AddChildView(cb); y += 20; Combobox* combobox = new Combobox(&combobox_model_); combobox->SetBounds(label_x + label_width + 5, y, 150, 30); combobox->SetID(kComboboxID); - left_container->AddChildView(combobox); + left_container_->AddChildView(combobox); - View* right_container = new View(); - right_container->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK)); - right_container->set_background( + right_container_ = new PaneView(); + right_container_->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK)); + right_container_->set_background( Background::CreateSolidBackground(240, 240, 240)); - right_container->SetID(kRightContainerID); - content_view_->AddChildView(right_container); - right_container->SetBounds(270, 35, 300, 200); + right_container_->SetID(kRightContainerID); + content_view_->AddChildView(right_container_); + right_container_->SetBounds(270, 35, 300, 200); y = 10; int radio_button_height = 18; int gap_between_radio_buttons = 10; RadioButton* radio_button = new RadioButton(L"Asparagus", 1); radio_button->SetID(kAsparagusButtonID); - right_container->AddChildView(radio_button); + right_container_->AddChildView(radio_button); radio_button->SetBounds(5, y, 70, radio_button_height); radio_button->SetGroup(1); y += radio_button_height + gap_between_radio_buttons; radio_button = new RadioButton(L"Broccoli", 1); radio_button->SetID(kBroccoliButtonID); - right_container->AddChildView(radio_button); + right_container_->AddChildView(radio_button); radio_button->SetBounds(5, y, 70, radio_button_height); radio_button->SetGroup(1); RadioButton* radio_button_to_check = radio_button; y += radio_button_height + gap_between_radio_buttons; radio_button = new RadioButton(L"Cauliflower", 1); radio_button->SetID(kCauliflowerButtonID); - right_container->AddChildView(radio_button); + right_container_->AddChildView(radio_button); radio_button->SetBounds(5, y, 70, radio_button_height); radio_button->SetGroup(1); y += radio_button_height + gap_between_radio_buttons; @@ -507,7 +598,7 @@ void FocusTraversalTest::InitContentView() { inner_container->set_background( Background::CreateSolidBackground(230, 230, 230)); inner_container->SetID(kInnerContainerID); - right_container->AddChildView(inner_container); + right_container_->AddChildView(inner_container); inner_container->SetBounds(100, 10, 150, 180); ScrollView* scroll_view = new ScrollView(); @@ -1057,6 +1148,84 @@ TEST_F(FocusTraversalTest, TraversalWithNonEnabledViews) { } } +TEST_F(FocusTraversalTest, PaneTraversal) { + // Tests trapping the traversal within a pane - useful for full + // keyboard accessibility for toolbars. + + // First test the left container. + const int kLeftTraversalIDs[] = { + kAppleTextfieldID, + kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID, + kFruitButtonID, kFruitCheckBoxID, kComboboxID }; + + FocusSearch focus_search_left(left_container_, true, false); + left_container_->EnablePaneFocus(&focus_search_left); + FindViewByID(kComboboxID)->RequestFocus(); + + // Traverse the focus hierarchy within the pane several times. + for (int i = 0; i < 3; ++i) { + for (size_t j = 0; j < arraysize(kLeftTraversalIDs); j++) { + GetFocusManager()->AdvanceFocus(false); + View* focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kLeftTraversalIDs[j], focused_view->GetID()); + } + } + + // Traverse in reverse order. + FindViewByID(kAppleTextfieldID)->RequestFocus(); + for (int i = 0; i < 3; ++i) { + for (int j = arraysize(kLeftTraversalIDs) - 1; j >= 0; --j) { + GetFocusManager()->AdvanceFocus(true); + View* focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kLeftTraversalIDs[j], focused_view->GetID()); + } + } + + // Now test the right container, but this time with accessibility mode. + // Make some links not focusable, but mark one of them as + // "accessibility focusable", so it should show up in the traversal. + const int kRightTraversalIDs[] = { + kBroccoliButtonID, kDinerGameLinkID, kRidiculeLinkID, + kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, + kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID }; + + FocusSearch focus_search_right(right_container_, true, true); + right_container_->EnablePaneFocus(&focus_search_right); + FindViewByID(kRosettaLinkID)->SetFocusable(false); + FindViewByID(kStupeurEtTremblementLinkID)->SetFocusable(false); + FindViewByID(kDinerGameLinkID)->set_accessibility_focusable(true); + FindViewByID(kDinerGameLinkID)->SetFocusable(false); + FindViewByID(kAsterixLinkID)->RequestFocus(); + + // Traverse the focus hierarchy within the pane several times. + for (int i = 0; i < 3; ++i) { + for (size_t j = 0; j < arraysize(kRightTraversalIDs); j++) { + GetFocusManager()->AdvanceFocus(false); + View* focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kRightTraversalIDs[j], focused_view->GetID()); + } + } + + // Traverse in reverse order. + FindViewByID(kBroccoliButtonID)->RequestFocus(); + for (int i = 0; i < 3; ++i) { + for (int j = arraysize(kRightTraversalIDs) - 1; j >= 0; --j) { + GetFocusManager()->AdvanceFocus(true); + View* focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kRightTraversalIDs[j], focused_view->GetID()); + } + } + +} + // Counts accelerator calls. class TestAcceleratorTarget : public AcceleratorTarget { public: diff --git a/views/focus/focus_search.cc b/views/focus/focus_search.cc new file mode 100644 index 0000000..eaeb8ac --- /dev/null +++ b/views/focus/focus_search.cc @@ -0,0 +1,278 @@ +// 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 "base/logging.h" +#include "views/focus/focus_manager.h" +#include "views/focus/focus_search.h" +#include "views/view.h" + +namespace views { + +FocusSearch::FocusSearch(View* root, bool cycle, bool accessibility_mode) + : root_(root), + cycle_(cycle), + accessibility_mode_(accessibility_mode) { +} + +View* FocusSearch::FindNextFocusableView(View* starting_view, + bool reverse, + Direction direction, + bool check_starting_view, + FocusTraversable** focus_traversable, + View** focus_traversable_view) { + *focus_traversable = NULL; + *focus_traversable_view = NULL; + + if (root_->GetChildViewCount() == 0) { + NOTREACHED(); + // Nothing to focus on here. + return NULL; + } + + View* initial_starting_view = starting_view; + int starting_view_group = -1; + if (starting_view) + starting_view_group = starting_view->GetGroup(); + + if (!starting_view) { + // Default to the first/last child + starting_view = + reverse ? + root_->GetChildViewAt(root_->GetChildViewCount() - 1) : + root_->GetChildViewAt(0); + // If there was no starting view, then the one we select is a potential + // focus candidate. + check_starting_view = true; + } else { + // The starting view should be a direct or indirect child of the root. + DCHECK(root_->IsParentOf(starting_view)); + } + + View* v = NULL; + if (!reverse) { + v = FindNextFocusableViewImpl(starting_view, check_starting_view, + true, + (direction == DOWN) ? true : false, + starting_view_group, + focus_traversable, + focus_traversable_view); + } else { + // If the starting view is focusable, we don't want to go down, as we are + // traversing the view hierarchy tree bottom-up. + bool can_go_down = (direction == DOWN) && !IsFocusable(starting_view); + v = FindPreviousFocusableViewImpl(starting_view, check_starting_view, + true, + can_go_down, + starting_view_group, + focus_traversable, + focus_traversable_view); + } + + // Don't set the focus to something outside of this view hierarchy. + if (v && v != root_ && !root_->IsParentOf(v)) + v = NULL; + + // If |cycle_| is true, prefer to keep cycling rather than returning NULL. + if (cycle_ && !v && initial_starting_view) { + v = FindNextFocusableView(NULL, reverse, direction, check_starting_view, + focus_traversable, focus_traversable_view); + DCHECK(IsFocusable(v)); + return v; + } + + // Doing some sanity checks. + if (v) { + DCHECK(IsFocusable(v)); + return v; + } + if (*focus_traversable) { + DCHECK(*focus_traversable_view); + return NULL; + } + // Nothing found. + return NULL; +} + +bool FocusSearch::IsViewFocusableCandidate(View* v, int skip_group_id) { + return IsFocusable(v) && + (v->IsGroupFocusTraversable() || skip_group_id == -1 || + v->GetGroup() != skip_group_id); +} + +bool FocusSearch::IsFocusable(View* v) { + if (accessibility_mode_) + return v && v->IsAccessibilityFocusable(); + else + return v && v->IsFocusable(); +} + +View* FocusSearch::FindSelectedViewForGroup(View* view) { + if (view->IsGroupFocusTraversable() || + view->GetGroup() == -1) // No group for that view. + return view; + + View* selected_view = view->GetSelectedViewForGroup(view->GetGroup()); + if (selected_view) + return selected_view; + + // No view selected for that group, default to the specified view. + return view; +} + +View* FocusSearch::GetParent(View* v) { + if (root_->IsParentOf(v)) { + return v->GetParent(); + } else { + return NULL; + } +} + +// Strategy for finding the next focusable view: +// - keep going down the first child, stop when you find a focusable view or +// a focus traversable view (in that case return it) or when you reach a view +// with no children. +// - go to the right sibling and start the search from there (by invoking +// FindNextFocusableViewImpl on that view). +// - if the view has no right sibling, go up the parents until you find a parent +// with a right sibling and start the search from there. +View* FocusSearch::FindNextFocusableViewImpl( + View* starting_view, + bool check_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id, + FocusTraversable** focus_traversable, + View** focus_traversable_view) { + if (check_starting_view) { + if (IsViewFocusableCandidate(starting_view, skip_group_id)) { + View* v = FindSelectedViewForGroup(starting_view); + // The selected view might not be focusable (if it is disabled for + // example). + if (IsFocusable(v)) + return v; + } + + *focus_traversable = starting_view->GetFocusTraversable(); + if (*focus_traversable) { + *focus_traversable_view = starting_view; + return NULL; + } + } + + // First let's try the left child. + if (can_go_down) { + if (starting_view->GetChildViewCount() > 0) { + View* v = FindNextFocusableViewImpl(starting_view->GetChildViewAt(0), + true, false, true, skip_group_id, + focus_traversable, + focus_traversable_view); + if (v || *focus_traversable) + return v; + } + } + + // Then try the right sibling. + View* sibling = starting_view->GetNextFocusableView(); + if (sibling) { + View* v = FindNextFocusableViewImpl(sibling, + true, false, true, skip_group_id, + focus_traversable, + focus_traversable_view); + if (v || *focus_traversable) + return v; + } + + // Then go up to the parent sibling. + if (can_go_up) { + View* parent = GetParent(starting_view); + while (parent) { + sibling = parent->GetNextFocusableView(); + if (sibling) { + return FindNextFocusableViewImpl(sibling, + true, true, true, + skip_group_id, + focus_traversable, + focus_traversable_view); + } + parent = GetParent(parent); + } + } + + // We found nothing. + return NULL; +} + +// Strategy for finding the previous focusable view: +// - keep going down on the right until you reach a view with no children, if it +// it is a good candidate return it. +// - start the search on the left sibling. +// - if there are no left sibling, start the search on the parent (without going +// down). +View* FocusSearch::FindPreviousFocusableViewImpl( + View* starting_view, + bool check_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id, + FocusTraversable** focus_traversable, + View** focus_traversable_view) { + // Let's go down and right as much as we can. + if (can_go_down) { + // Before we go into the direct children, we have to check if this view has + // a FocusTraversable. + *focus_traversable = starting_view->GetFocusTraversable(); + if (*focus_traversable) { + *focus_traversable_view = starting_view; + return NULL; + } + + if (starting_view->GetChildViewCount() > 0) { + View* view = + starting_view->GetChildViewAt(starting_view->GetChildViewCount() - 1); + View* v = FindPreviousFocusableViewImpl(view, true, false, true, + skip_group_id, + focus_traversable, + focus_traversable_view); + if (v || *focus_traversable) + return v; + } + } + + // Then look at this view. Here, we do not need to see if the view has + // a FocusTraversable, since we do not want to go down any more. + if (check_starting_view && + IsViewFocusableCandidate(starting_view, skip_group_id)) { + View* v = FindSelectedViewForGroup(starting_view); + // The selected view might not be focusable (if it is disabled for + // example). + if (IsFocusable(v)) + return v; + } + + // Then try the left sibling. + View* sibling = starting_view->GetPreviousFocusableView(); + if (sibling) { + return FindPreviousFocusableViewImpl(sibling, + true, true, true, + skip_group_id, + focus_traversable, + focus_traversable_view); + } + + // Then go up the parent. + if (can_go_up) { + View* parent = GetParent(starting_view); + if (parent) + return FindPreviousFocusableViewImpl(parent, + true, true, false, + skip_group_id, + focus_traversable, + focus_traversable_view); + } + + // We found nothing. + return NULL; +} + +} // namespace views diff --git a/views/focus/focus_search.h b/views/focus/focus_search.h new file mode 100644 index 0000000..f24864b --- /dev/null +++ b/views/focus/focus_search.h @@ -0,0 +1,120 @@ +// 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. + +#ifndef VIEWS_WIDGET_FOCUS_SEARCH_H_ +#define VIEWS_WIDGET_FOCUS_SEARCH_H_ + +#include "views/view.h" + +namespace views { + +class FocusTraversable; + +// FocusSearch is an object that implements the algorithm to find the +// next view to focus. +class FocusSearch { + public: + // The direction in which the focus traversal is going. + // TODO (jcampan): add support for lateral (left, right) focus traversal. The + // goal is to switch to focusable views on the same level when using the arrow + // keys (ala Windows: in a dialog box, arrow keys typically move between the + // dialog OK, Cancel buttons). + enum Direction { + UP = 0, + DOWN + }; + + // Constructor. + // - |root| is the root of the view hierarchy to traverse. Focus will be + // trapped inside. + // - |cycle| should be true if you want FindNextFocusableView to cycle back + // to the first view within this root when the traversal reaches + // the end. If this is true, then if you pass a valid starting + // view to FindNextFocusableView you will always get a valid view + // out, even if it's the same view. + // - |accessibility_mode| should be true if full keyboard accessibility is + // needed and you want to check IsAccessibilityFocusable(), + // rather than IsFocusable(). + FocusSearch(View* root, bool cycle, bool accessibility_mode); + virtual ~FocusSearch() {} + + // Finds the next view that should be focused and returns it. If a + // FocusTraversable is found while searching for the focusable view, + // returns NULL and sets |focus_traversable| to the FocusTraversable + // and |focus_traversable_view| to the view associated with the + // FocusTraversable. + // + // Return NULL if the end of the focus loop is reached, unless this object + // was initialized with |cycle|=true, in which case it goes back to the + // beginning when it reaches the end of the traversal. + // - |starting_view| is the view that should be used as the starting point + // when looking for the previous/next view. It may be NULL (in which case + // the first/last view should be used depending if normal/reverse). + // - |reverse| whether we should find the next (reverse is false) or the + // previous (reverse is true) view. + // - |direction| specifies whether we are traversing down (meaning we should + // look into child views) or traversing up (don't look at child views). + // - |check_starting_view| is true if starting_view may obtain the next focus. + // - |focus_traversable| is set to the focus traversable that should be + // traversed if one is found (in which case the call returns NULL). + // - |focus_traversable_view| is set to the view associated with the + // FocusTraversable set in the previous parameter (it is used as the + // starting view when looking for the next focusable view). + virtual View* FindNextFocusableView(View* starting_view, + bool reverse, + Direction direction, + bool check_starting_view, + FocusTraversable** focus_traversable, + View** focus_traversable_view); + + private: + // Convenience method that returns true if a view is focusable and does not + // belong to the specified group. + bool IsViewFocusableCandidate(View* v, int skip_group_id); + + // Convenience method; returns true if a view is not NULL and is focusable + // (checking IsAccessibilityFocusable() if accessibility_mode_ is true). + bool IsFocusable(View* v); + + // Returns the view selected for the group of the selected view. If the view + // does not belong to a group or if no view is selected in the group, the + // specified view is returned. + View* FindSelectedViewForGroup(View* view); + + // Get the parent, but stay within the root. Returns NULL if asked for + // the parent of root_. + View* GetParent(View* view); + + // Returns the next focusable view or view containing a FocusTraversable + // (NULL if none was found), starting at the starting_view. + // |check_starting_view|, |can_go_up| and |can_go_down| controls the + // traversal of the views hierarchy. |skip_group_id| specifies a group_id, + // -1 means no group. All views from a group are traversed in one pass. + View* FindNextFocusableViewImpl(View* starting_view, + bool check_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id, + FocusTraversable** focus_traversable, + View** focus_traversable_view); + + // Same as FindNextFocusableViewImpl but returns the previous focusable view. + View* FindPreviousFocusableViewImpl(View* starting_view, + bool check_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id, + FocusTraversable** focus_traversable, + View** focus_traversable_view); + + View* root_; + bool cycle_; + bool accessibility_mode_; + + DISALLOW_COPY_AND_ASSIGN(FocusSearch); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_FOCUS_SEARCH_H_ diff --git a/views/view.cc b/views/view.cc index 988e76b..d8ba4c3 100644 --- a/views/view.cc +++ b/views/view.cc @@ -54,6 +54,7 @@ View::View() group_(-1), enabled_(true), focusable_(false), + accessibility_focusable_(false), bounds_(0, 0, 0, 0), parent_(NULL), is_visible_(true), @@ -281,13 +282,17 @@ void View::SetEnabled(bool state) { } bool View::IsFocusable() const { - return focusable_ && enabled_ && is_visible_; + return focusable_ && IsEnabled() && IsVisible(); } void View::SetFocusable(bool focusable) { focusable_ = focusable; } +bool View::IsAccessibilityFocusable() const { + return (focusable_ || accessibility_focusable_) && IsEnabled() && IsVisible(); +} + FocusManager* View::GetFocusManager() { Widget* widget = GetWidget(); return widget ? widget->GetFocusManager() : NULL; @@ -351,8 +356,10 @@ void View::PaintBorder(gfx::Canvas* canvas) { } void View::PaintFocusBorder(gfx::Canvas* canvas) { - if (HasFocus() && IsFocusable()) + if (HasFocus() && (IsFocusable() || + IsAccessibilityFocusable())) { canvas->DrawFocusRect(0, 0, width(), height()); + } } void View::PaintChildren(gfx::Canvas* canvas) { @@ -472,7 +479,7 @@ void View::ShowContextMenu(const gfx::Point& p, bool is_mouse_gesture) { ///////////////////////////////////////////////////////////////////////////// bool View::ProcessMousePressed(const MouseEvent& e, DragInfo* drag_info) { - const bool enabled = enabled_; + const bool enabled = IsEnabled(); int drag_operations = (enabled && e.IsOnlyLeftMouseButton() && HitTest(e.location())) ? GetDragOperations(e.location()) : 0; diff --git a/views/view.h b/views/view.h index a9012a5..f6d1efa 100644 --- a/views/view.h +++ b/views/view.h @@ -514,6 +514,17 @@ class View : public AcceleratorTarget { // not get the focus. virtual void SetFocusable(bool focusable); + // Return whether this view is focusable when the user requires + // full keyboard access, even though it may not be normally focusable. + virtual bool IsAccessibilityFocusable() const; + + // Set whether this view can be made focusable if the user requires + // full keyboard access, even though it's not normally focusable. + // Note that this is false by default. + virtual void set_accessibility_focusable(bool accessibility_focusable) { + accessibility_focusable_ = accessibility_focusable; + } + // Convenience method to retrieve the FocusManager associated with the // Widget that contains this view. This can return NULL if this view is not // part of a view hierarchy with a Widget. @@ -931,6 +942,13 @@ class View : public AcceleratorTarget { // FocusTraversable for the focus traversal to work properly. virtual FocusTraversable* GetFocusTraversable() { return NULL; } + // Subclasses that can act as a "pane" must implement their own + // FocusTraversable to keep the focus trapped within the pane. + // If this method returns an object, any view that's a direct or + // indirect child of this view will always use this FocusTraversable + // rather than the one from the widget. + virtual FocusTraversable* GetPaneFocusTraversable() { return NULL; } + #ifndef NDEBUG // Debug method that logs the view hierarchy to the output. void PrintViewHierarchy(); @@ -1101,6 +1119,10 @@ class View : public AcceleratorTarget { // Whether the view can be focused. bool focusable_; + // Whether this view is focusable if the user requires full keyboard access, + // even though it may not be normally focusable. + bool accessibility_focusable_; + private: friend class RootView; friend class FocusManager; diff --git a/views/views.gyp b/views/views.gyp index 0e58701..0b87955 100644 --- a/views/views.gyp +++ b/views/views.gyp @@ -233,6 +233,8 @@ 'focus/focus_manager_win.cc', 'focus/focus_manager.cc', 'focus/focus_manager.h', + 'focus/focus_search.cc', + 'focus/focus_search.h', 'focus/focus_util_win.cc', 'focus/focus_util_win.h', 'focus/view_storage.cc', diff --git a/views/widget/root_view.cc b/views/widget/root_view.cc index 12faf68..ff08d29 100644 --- a/views/widget/root_view.cc +++ b/views/widget/root_view.cc @@ -62,6 +62,7 @@ RootView::RootView(Widget* widget) mouse_move_handler_(NULL), last_click_handler_(NULL), widget_(widget), + ALLOW_THIS_IN_INITIALIZER_LIST(focus_search_(this, false, false)), invalid_rect_urgent_(false), pending_paint_task_(NULL), paint_task_needed_(false), @@ -575,210 +576,8 @@ View* RootView::GetFocusedView() { return NULL; } -View* RootView::FindNextFocusableView(View* starting_view, - bool reverse, - Direction direction, - bool check_starting_view, - FocusTraversable** focus_traversable, - View** focus_traversable_view) { - *focus_traversable = NULL; - *focus_traversable_view = NULL; - - if (GetChildViewCount() == 0) { - NOTREACHED(); - // Nothing to focus on here. - return NULL; - } - - if (!starting_view) { - // Default to the first/last child - starting_view = reverse ? GetChildViewAt(GetChildViewCount() - 1) : - GetChildViewAt(0); - // If there was no starting view, then the one we select is a potential - // focus candidate. - check_starting_view = true; - } else { - // The starting view should be part of this RootView. - DCHECK(IsParentOf(starting_view)); - } - - View* v = NULL; - if (!reverse) { - v = FindNextFocusableViewImpl(starting_view, check_starting_view, - true, - (direction == DOWN) ? true : false, - starting_view->GetGroup(), - focus_traversable, - focus_traversable_view); - } else { - // If the starting view is focusable, we don't want to go down, as we are - // traversing the view hierarchy tree bottom-up. - bool can_go_down = (direction == DOWN) && !starting_view->IsFocusable(); - v = FindPreviousFocusableViewImpl(starting_view, check_starting_view, - true, - can_go_down, - starting_view->GetGroup(), - focus_traversable, - focus_traversable_view); - } - - // Doing some sanity checks. - if (v) { - DCHECK(v->IsFocusable()); - return v; - } - if (*focus_traversable) { - DCHECK(*focus_traversable_view); - return NULL; - } - // Nothing found. - return NULL; -} - -// Strategy for finding the next focusable view: -// - keep going down the first child, stop when you find a focusable view or -// a focus traversable view (in that case return it) or when you reach a view -// with no children. -// - go to the right sibling and start the search from there (by invoking -// FindNextFocusableViewImpl on that view). -// - if the view has no right sibling, go up the parents until you find a parent -// with a right sibling and start the search from there. -View* RootView::FindNextFocusableViewImpl(View* starting_view, - bool check_starting_view, - bool can_go_up, - bool can_go_down, - int skip_group_id, - FocusTraversable** focus_traversable, - View** focus_traversable_view) { - if (check_starting_view) { - if (IsViewFocusableCandidate(starting_view, skip_group_id)) { - View* v = FindSelectedViewForGroup(starting_view); - // The selected view might not be focusable (if it is disabled for - // example). - if (v && v->IsFocusable()) - return v; - } - - *focus_traversable = starting_view->GetFocusTraversable(); - if (*focus_traversable) { - *focus_traversable_view = starting_view; - return NULL; - } - } - - // First let's try the left child. - if (can_go_down) { - if (starting_view->GetChildViewCount() > 0) { - View* v = FindNextFocusableViewImpl(starting_view->GetChildViewAt(0), - true, false, true, skip_group_id, - focus_traversable, - focus_traversable_view); - if (v || *focus_traversable) - return v; - } - } - - // Then try the right sibling. - View* sibling = starting_view->GetNextFocusableView(); - if (sibling) { - View* v = FindNextFocusableViewImpl(sibling, - true, false, true, skip_group_id, - focus_traversable, - focus_traversable_view); - if (v || *focus_traversable) - return v; - } - - // Then go up to the parent sibling. - if (can_go_up) { - View* parent = starting_view->GetParent(); - while (parent) { - sibling = parent->GetNextFocusableView(); - if (sibling) { - return FindNextFocusableViewImpl(sibling, - true, true, true, - skip_group_id, - focus_traversable, - focus_traversable_view); - } - parent = parent->GetParent(); - } - } - - // We found nothing. - return NULL; -} - -// Strategy for finding the previous focusable view: -// - keep going down on the right until you reach a view with no children, if it -// it is a good candidate return it. -// - start the search on the left sibling. -// - if there are no left sibling, start the search on the parent (without going -// down). -View* RootView::FindPreviousFocusableViewImpl( - View* starting_view, - bool check_starting_view, - bool can_go_up, - bool can_go_down, - int skip_group_id, - FocusTraversable** focus_traversable, - View** focus_traversable_view) { - // Let's go down and right as much as we can. - if (can_go_down) { - // Before we go into the direct children, we have to check if this view has - // a FocusTraversable. - *focus_traversable = starting_view->GetFocusTraversable(); - if (*focus_traversable) { - *focus_traversable_view = starting_view; - return NULL; - } - - if (starting_view->GetChildViewCount() > 0) { - View* view = - starting_view->GetChildViewAt(starting_view->GetChildViewCount() - 1); - View* v = FindPreviousFocusableViewImpl(view, true, false, true, - skip_group_id, - focus_traversable, - focus_traversable_view); - if (v || *focus_traversable) - return v; - } - } - - // Then look at this view. Here, we do not need to see if the view has - // a FocusTraversable, since we do not want to go down any more. - if (check_starting_view && - IsViewFocusableCandidate(starting_view, skip_group_id)) { - View* v = FindSelectedViewForGroup(starting_view); - // The selected view might not be focusable (if it is disabled for - // example). - if (v && v->IsFocusable()) - return v; - } - - // Then try the left sibling. - View* sibling = starting_view->GetPreviousFocusableView(); - if (sibling) { - return FindPreviousFocusableViewImpl(sibling, - true, true, true, - skip_group_id, - focus_traversable, - focus_traversable_view); - } - - // Then go up the parent. - if (can_go_up) { - View* parent = starting_view->GetParent(); - if (parent) - return FindPreviousFocusableViewImpl(parent, - true, true, false, - skip_group_id, - focus_traversable, - focus_traversable_view); - } - - // We found nothing. - return NULL; +FocusSearch* RootView::GetFocusSearch() { + return &focus_search_; } FocusTraversable* RootView::GetFocusTraversableParent() { @@ -803,27 +602,6 @@ void RootView::NotifyNativeViewHierarchyChanged(bool attached, PropagateNativeViewHierarchyChanged(attached, native_view, this); } -// static -View* RootView::FindSelectedViewForGroup(View* view) { - if (view->IsGroupFocusTraversable() || - view->GetGroup() == -1) // No group for that view. - return view; - - View* selected_view = view->GetSelectedViewForGroup(view->GetGroup()); - if (selected_view) - return selected_view; - - // No view selected for that group, default to the specified view. - return view; -} - -// static -bool RootView::IsViewFocusableCandidate(View* v, int skip_group_id) { - return v->IsFocusable() && - (v->IsGroupFocusTraversable() || skip_group_id == -1 || - v->GetGroup() != skip_group_id); -} - bool RootView::ProcessKeyEvent(const KeyEvent& event) { bool consumed = false; diff --git a/views/widget/root_view.h b/views/widget/root_view.h index ba1d5d22..280742f 100644 --- a/views/widget/root_view.h +++ b/views/widget/root_view.h @@ -10,6 +10,7 @@ #include "base/ref_counted.h" #include "views/focus/focus_manager.h" +#include "views/focus/focus_search.h" #include "views/view.h" #if defined(OS_LINUX) @@ -104,10 +105,6 @@ class RootView : public View, // Make the provided view focused. Also make sure that our Widget is focused. void FocusView(View* view); - // Check whether the provided view is in the focus path. The focus path is the - // path between the focused view (included) to the root view. - bool IsInFocusPath(View* view); - // Returns the View in this RootView hierarchy that has the focus, or NULL if // no View currently has the focus. View* GetFocusedView(); @@ -138,12 +135,7 @@ class RootView : public View, virtual bool IsVisibleInRootView() const; // FocusTraversable implementation. - virtual View* FindNextFocusableView(View* starting_view, - bool reverse, - Direction direction, - bool check_starting_view, - FocusTraversable** focus_traversable, - View** focus_traversable_view); + virtual FocusSearch* GetFocusSearch(); virtual FocusTraversable* GetFocusTraversableParent(); virtual View* GetFocusTraversableParentView(); @@ -285,6 +277,9 @@ class RootView : public View, // The host Widget Widget* widget_; + // The focus search algorithm. + FocusSearch focus_search_; + // The rectangle that should be painted gfx::Rect invalid_rect_; diff --git a/views/widget/widget_gtk.cc b/views/widget/widget_gtk.cc index f38267b..2f21358 100644 --- a/views/widget/widget_gtk.cc +++ b/views/widget/widget_gtk.cc @@ -831,16 +831,8 @@ bool WidgetGtk::ContainsNativeView(gfx::NativeView native_view) { //////////////////////////////////////////////////////////////////////////////// // WidgetGtk, FocusTraversable implementation: -View* WidgetGtk::FindNextFocusableView( - View* starting_view, bool reverse, Direction direction, - bool check_starting_view, FocusTraversable** focus_traversable, - View** focus_traversable_view) { - return root_view_->FindNextFocusableView(starting_view, - reverse, - direction, - check_starting_view, - focus_traversable, - focus_traversable_view); +FocusSearch* WidgetGtk::GetFocusSearch() { + return root_view_->GetFocusSearch(); } FocusTraversable* WidgetGtk::GetFocusTraversableParent() { diff --git a/views/widget/widget_gtk.h b/views/widget/widget_gtk.h index 8f7ca17..3b95140 100644 --- a/views/widget/widget_gtk.h +++ b/views/widget/widget_gtk.h @@ -25,6 +25,7 @@ namespace views { class DefaultThemeProvider; class DropTargetGtk; +class FocusSearch; class TooltipManagerGtk; class View; class WindowGtk; @@ -188,14 +189,8 @@ class WidgetGtk View *child); virtual bool ContainsNativeView(gfx::NativeView native_view); - // Overridden from FocusTraversable: - virtual View* FindNextFocusableView(View* starting_view, - bool reverse, - Direction direction, - bool check_starting_view, - FocusTraversable** focus_traversable, - View** focus_traversable_view); + virtual FocusSearch* GetFocusSearch(); virtual FocusTraversable* GetFocusTraversableParent(); virtual View* GetFocusTraversableParentView(); diff --git a/views/widget/widget_win.cc b/views/widget/widget_win.cc index ef6612a..90cffe5 100644 --- a/views/widget/widget_win.cc +++ b/views/widget/widget_win.cc @@ -467,16 +467,8 @@ void WidgetWin::DidProcessMessage(const MSG& msg) { //////////////////////////////////////////////////////////////////////////////// // FocusTraversable -View* WidgetWin::FindNextFocusableView( - View* starting_view, bool reverse, Direction direction, - bool check_starting_view, FocusTraversable** focus_traversable, - View** focus_traversable_view) { - return root_view_->FindNextFocusableView(starting_view, - reverse, - direction, - check_starting_view, - focus_traversable, - focus_traversable_view); +FocusSearch* WidgetWin::GetFocusSearch() { + return root_view_->GetFocusSearch(); } FocusTraversable* WidgetWin::GetFocusTraversableParent() { diff --git a/views/widget/widget_win.h b/views/widget/widget_win.h index e0cdc44d..699d60e3 100644 --- a/views/widget/widget_win.h +++ b/views/widget/widget_win.h @@ -24,10 +24,11 @@ class Rect; namespace views { +class DefaultThemeProvider; class DropTargetWin; +class FocusSearch; class RootView; class TooltipManagerWin; -class DefaultThemeProvider; class Window; bool SetRootViewForHWND(HWND hwnd, RootView* root_view); @@ -222,12 +223,7 @@ class WidgetWin : public app::WindowImpl, virtual void DidProcessMessage(const MSG& msg); // Overridden from FocusTraversable: - virtual View* FindNextFocusableView(View* starting_view, - bool reverse, - Direction direction, - bool check_starting_view, - FocusTraversable** focus_traversable, - View** focus_traversable_view); + virtual FocusSearch* GetFocusSearch(); virtual FocusTraversable* GetFocusTraversableParent(); virtual View* GetFocusTraversableParentView(); -- cgit v1.1