summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-18 13:53:37 +0000
committerdmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-18 13:53:37 +0000
commit83548a4b7d23ca252944fa1dabfbe85bf5742157 (patch)
tree1c76116c200885db472e61b0778a8e22e0ca052c
parent7869f47d58149dc27a2e42de61d32f459c04d241 (diff)
downloadchromium_src-83548a4b7d23ca252944fa1dabfbe85bf5742157.zip
chromium_src-83548a4b7d23ca252944fa1dabfbe85bf5742157.tar.gz
chromium_src-83548a4b7d23ca252944fa1dabfbe85bf5742157.tar.bz2
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
-rw-r--r--chrome/app/chrome_dll.rc4
-rw-r--r--chrome/app/chrome_dll_resource.h4
-rw-r--r--chrome/browser/browser.cc39
-rw-r--r--chrome/browser/browser.h4
-rw-r--r--chrome/browser/browser_window.h9
-rw-r--r--chrome/browser/chromeos/frame/browser_view.cc14
-rw-r--r--chrome/browser/chromeos/frame/browser_view.h8
-rw-r--r--chrome/browser/chromeos/status/status_area_view.h3
-rw-r--r--chrome/browser/cocoa/browser_window_cocoa.h3
-rw-r--r--chrome/browser/cocoa/browser_window_cocoa.mm14
-rw-r--r--chrome/browser/gtk/browser_window_gtk.cc12
-rw-r--r--chrome/browser/gtk/browser_window_gtk.h3
-rw-r--r--chrome/browser/views/accelerator_table_gtk.cc6
-rw-r--r--chrome/browser/views/accessible_toolbar_view.cc351
-rw-r--r--chrome/browser/views/accessible_toolbar_view.h112
-rw-r--r--chrome/browser/views/browser_actions_container.cc30
-rw-r--r--chrome/browser/views/browser_actions_container.h1
-rw-r--r--chrome/browser/views/frame/browser_view.cc96
-rw-r--r--chrome/browser/views/frame/browser_view.h31
-rw-r--r--chrome/browser/views/location_bar/location_bar_view.cc6
-rw-r--r--chrome/browser/views/location_bar/page_action_image_view.cc39
-rw-r--r--chrome/browser/views/location_bar/page_action_image_view.h3
-rw-r--r--chrome/browser/views/location_bar/page_action_with_badge_view.cc6
-rw-r--r--chrome/browser/views/location_bar/page_action_with_badge_view.h1
-rw-r--r--chrome/browser/views/location_bar/star_view.cc11
-rw-r--r--chrome/browser/views/location_bar/star_view.h2
-rw-r--r--chrome/browser/views/toolbar_view.cc160
-rw-r--r--chrome/browser/views/toolbar_view.h56
-rw-r--r--chrome/test/test_browser_window.h3
-rw-r--r--views/controls/button/button.cc1
-rw-r--r--views/controls/button/menu_button.cc20
-rw-r--r--views/controls/button/menu_button.h2
-rw-r--r--views/focus/accelerator_handler_gtk.cc1
-rw-r--r--views/focus/focus_manager.cc72
-rw-r--r--views/focus/focus_manager.h46
-rw-r--r--views/focus/focus_manager_unittest.cc233
-rw-r--r--views/focus/focus_search.cc278
-rw-r--r--views/focus/focus_search.h120
-rw-r--r--views/view.cc13
-rw-r--r--views/view.h22
-rw-r--r--views/views.gyp2
-rw-r--r--views/widget/root_view.cc228
-rw-r--r--views/widget/root_view.h15
-rw-r--r--views/widget/widget_gtk.cc12
-rw-r--r--views/widget/widget_gtk.h9
-rw-r--r--views/widget/widget_win.cc12
-rw-r--r--views/widget/widget_win.h10
47 files changed, 1275 insertions, 852 deletions
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<UIStyle>((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<AccessibleToolbarView*>* 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 <vector>
+
#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<AccessibleToolbarView*>* 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<views::MenuButton*>(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<BrowserView*>(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<BrowserView*>(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<LONG>(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<BrowserView*>(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<AccessibleToolbarView> 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<AccessibleToolbarView*> 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<int>(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<AccessibleToolbarView*>* 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 <map>
#include <set>
#include <string>
+#include <vector>
#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<AccessibleToolbarView*>* 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<BrowserExtender> browser_extender_;
- // Last focused view that issued a tab traversal.
- int last_focused_view_storage_id_;
-
UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
scoped_ptr<AccessibleViewHelper> 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<views::MenuButton*>(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<BrowserView*>(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<views::Menu2> page_menu_menu_;
scoped_ptr<views::Menu2> 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<views::MenuListener*> menu_listeners_;
@@ -239,10 +215,6 @@ class ToolbarView : public AccessibleToolbarView,
// once, to create a pulsating effect.
base::RepeatingTimer<ToolbarView> 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<ToolbarView> 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<int>(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();