1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/logging.h"
6 #include "chrome/browser/ui/view_ids.h"
7 #include "chrome/browser/ui/views/accessible_pane_view.h"
8 #include "chrome/browser/ui/views/frame/browser_view.h"
9 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
10 #include "ui/base/accessibility/accessible_view_state.h"
11 #include "views/controls/button/menu_button.h"
12 #include "views/controls/native/native_view_host.h"
13 #include "views/focus/focus_search.h"
14 #include "views/focus/view_storage.h"
15 #include "views/widget/tooltip_manager.h"
16 #include "views/widget/widget.h"
17
AccessiblePaneView()18 AccessiblePaneView::AccessiblePaneView()
19 : pane_has_focus_(false),
20 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
21 focus_manager_(NULL),
22 home_key_(ui::VKEY_HOME, false, false, false),
23 end_key_(ui::VKEY_END, false, false, false),
24 escape_key_(ui::VKEY_ESCAPE, false, false, false),
25 left_key_(ui::VKEY_LEFT, false, false, false),
26 right_key_(ui::VKEY_RIGHT, false, false, false),
27 last_focused_view_storage_id_(-1) {
28 focus_search_.reset(new views::FocusSearch(this, true, true));
29 }
30
~AccessiblePaneView()31 AccessiblePaneView::~AccessiblePaneView() {
32 if (pane_has_focus_) {
33 focus_manager_->RemoveFocusChangeListener(this);
34 }
35 }
36
SetPaneFocus(int view_storage_id,views::View * initial_focus)37 bool AccessiblePaneView::SetPaneFocus(int view_storage_id,
38 views::View* initial_focus) {
39 if (!IsVisible())
40 return false;
41
42 // Save the storage id to the last focused view. This would be used to request
43 // focus to the view when the traversal is ended.
44 last_focused_view_storage_id_ = view_storage_id;
45
46 if (!focus_manager_)
47 focus_manager_ = GetFocusManager();
48
49 // Use the provided initial focus if it's visible and enabled, otherwise
50 // use the first focusable child.
51 if (!initial_focus ||
52 !Contains(initial_focus) ||
53 !initial_focus->IsVisible() ||
54 !initial_focus->IsEnabled()) {
55 initial_focus = GetFirstFocusableChild();
56 }
57
58 // Return false if there are no focusable children.
59 if (!initial_focus)
60 return false;
61
62 // Set focus to the initial view. If it's a location bar, use a special
63 // method that tells it to select all, also.
64 if (initial_focus->GetClassName() == LocationBarView::kViewClassName) {
65 static_cast<LocationBarView*>(initial_focus)->FocusLocation(true);
66 } else {
67 focus_manager_->SetFocusedView(initial_focus);
68 }
69
70 // If we already have pane focus, we're done.
71 if (pane_has_focus_)
72 return true;
73
74 // Otherwise, set accelerators and start listening for focus change events.
75 pane_has_focus_ = true;
76 focus_manager_->RegisterAccelerator(home_key_, this);
77 focus_manager_->RegisterAccelerator(end_key_, this);
78 focus_manager_->RegisterAccelerator(escape_key_, this);
79 focus_manager_->RegisterAccelerator(left_key_, this);
80 focus_manager_->RegisterAccelerator(right_key_, this);
81 focus_manager_->AddFocusChangeListener(this);
82
83 return true;
84 }
85
SetPaneFocusAndFocusDefault(int view_storage_id)86 bool AccessiblePaneView::SetPaneFocusAndFocusDefault(
87 int view_storage_id) {
88 return SetPaneFocus(view_storage_id, GetDefaultFocusableChild());
89 }
90
GetDefaultFocusableChild()91 views::View* AccessiblePaneView::GetDefaultFocusableChild() {
92 return NULL;
93 }
94
RemovePaneFocus()95 void AccessiblePaneView::RemovePaneFocus() {
96 focus_manager_->RemoveFocusChangeListener(this);
97 pane_has_focus_ = false;
98
99 focus_manager_->UnregisterAccelerator(home_key_, this);
100 focus_manager_->UnregisterAccelerator(end_key_, this);
101 focus_manager_->UnregisterAccelerator(escape_key_, this);
102 focus_manager_->UnregisterAccelerator(left_key_, this);
103 focus_manager_->UnregisterAccelerator(right_key_, this);
104 }
105
LocationBarSelectAll()106 void AccessiblePaneView::LocationBarSelectAll() {
107 views::View* focused_view = GetFocusManager()->GetFocusedView();
108 if (focused_view &&
109 focused_view->GetClassName() == LocationBarView::kViewClassName) {
110 static_cast<LocationBarView*>(focused_view)->SelectAll();
111 }
112 }
113
RestoreLastFocusedView()114 void AccessiblePaneView::RestoreLastFocusedView() {
115 views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
116 views::View* last_focused_view =
117 view_storage->RetrieveView(last_focused_view_storage_id_);
118 if (last_focused_view) {
119 focus_manager_->SetFocusedViewWithReason(
120 last_focused_view, views::FocusManager::kReasonFocusRestore);
121 } else {
122 // Focus the location bar
123 views::View* view = GetAncestorWithClassName(BrowserView::kViewClassName);
124 if (view) {
125 BrowserView* browser_view = static_cast<BrowserView*>(view);
126 browser_view->SetFocusToLocationBar(false);
127 }
128 }
129 }
130
GetFirstFocusableChild()131 views::View* AccessiblePaneView::GetFirstFocusableChild() {
132 FocusTraversable* dummy_focus_traversable;
133 views::View* dummy_focus_traversable_view;
134 return focus_search_->FindNextFocusableView(
135 NULL, false, views::FocusSearch::DOWN, false,
136 &dummy_focus_traversable, &dummy_focus_traversable_view);
137 }
138
GetLastFocusableChild()139 views::View* AccessiblePaneView::GetLastFocusableChild() {
140 FocusTraversable* dummy_focus_traversable;
141 views::View* dummy_focus_traversable_view;
142 return focus_search_->FindNextFocusableView(
143 this, true, views::FocusSearch::DOWN, false,
144 &dummy_focus_traversable, &dummy_focus_traversable_view);
145 }
146
147 ////////////////////////////////////////////////////////////////////////////////
148 // View overrides:
149
GetPaneFocusTraversable()150 views::FocusTraversable* AccessiblePaneView::GetPaneFocusTraversable() {
151 if (pane_has_focus_)
152 return this;
153 else
154 return NULL;
155 }
156
AcceleratorPressed(const views::Accelerator & accelerator)157 bool AccessiblePaneView::AcceleratorPressed(
158 const views::Accelerator& accelerator) {
159 // Special case: don't handle any accelerators for the location bar,
160 // so that it behaves exactly the same whether you focus it with Ctrl+L
161 // or F6 or Alt+D or Alt+Shift+T.
162 views::View* focused_view = focus_manager_->GetFocusedView();
163 if ((focused_view->GetClassName() == LocationBarView::kViewClassName ||
164 focused_view->GetClassName() == views::NativeViewHost::kViewClassName)) {
165 return false;
166 }
167
168 switch (accelerator.GetKeyCode()) {
169 case ui::VKEY_ESCAPE:
170 RemovePaneFocus();
171 RestoreLastFocusedView();
172 return true;
173 case ui::VKEY_LEFT:
174 focus_manager_->AdvanceFocus(true);
175 return true;
176 case ui::VKEY_RIGHT:
177 focus_manager_->AdvanceFocus(false);
178 return true;
179 case ui::VKEY_HOME:
180 focus_manager_->SetFocusedViewWithReason(
181 GetFirstFocusableChild(), views::FocusManager::kReasonFocusTraversal);
182 return true;
183 case ui::VKEY_END:
184 focus_manager_->SetFocusedViewWithReason(
185 GetLastFocusableChild(), views::FocusManager::kReasonFocusTraversal);
186 return true;
187 default:
188 return false;
189 }
190 }
191
SetVisible(bool flag)192 void AccessiblePaneView::SetVisible(bool flag) {
193 if (IsVisible() && !flag && pane_has_focus_) {
194 RemovePaneFocus();
195 RestoreLastFocusedView();
196 }
197 View::SetVisible(flag);
198 }
199
GetAccessibleState(ui::AccessibleViewState * state)200 void AccessiblePaneView::GetAccessibleState(ui::AccessibleViewState* state) {
201 state->role = ui::AccessibilityTypes::ROLE_PANE;
202 }
203
204 ////////////////////////////////////////////////////////////////////////////////
205 // FocusChangeListener overrides:
206
FocusWillChange(views::View * focused_before,views::View * focused_now)207 void AccessiblePaneView::FocusWillChange(views::View* focused_before,
208 views::View* focused_now) {
209 if (!focused_now)
210 return;
211
212 views::FocusManager::FocusChangeReason reason =
213 focus_manager_->focus_change_reason();
214
215 if (focused_now->GetClassName() == LocationBarView::kViewClassName &&
216 reason == views::FocusManager::kReasonFocusTraversal) {
217 // Tabbing to the location bar should select all. Defer so that it happens
218 // after the focus.
219 MessageLoop::current()->PostTask(
220 FROM_HERE, method_factory_.NewRunnableMethod(
221 &AccessiblePaneView::LocationBarSelectAll));
222 }
223
224 if (!Contains(focused_now) ||
225 reason == views::FocusManager::kReasonDirectFocusChange) {
226 // We should remove pane focus (i.e. make most of the controls
227 // not focusable again) either because the focus is leaving the pane,
228 // or because the focus changed within the pane due to the user
229 // directly focusing to a specific view (e.g., clicking on it).
230 //
231 // Defer rather than calling RemovePaneFocus right away, because we can't
232 // remove |this| as a focus change listener while FocusManager is in the
233 // middle of iterating over the list of listeners.
234 MessageLoop::current()->PostTask(
235 FROM_HERE, method_factory_.NewRunnableMethod(
236 &AccessiblePaneView::RemovePaneFocus));
237 }
238 }
239
240 ////////////////////////////////////////////////////////////////////////////////
241 // FocusTraversable overrides:
242
GetFocusSearch()243 views::FocusSearch* AccessiblePaneView::GetFocusSearch() {
244 DCHECK(pane_has_focus_);
245 return focus_search_.get();
246 }
247
GetFocusTraversableParent()248 views::FocusTraversable* AccessiblePaneView::GetFocusTraversableParent() {
249 DCHECK(pane_has_focus_);
250 return NULL;
251 }
252
GetFocusTraversableParentView()253 views::View* AccessiblePaneView::GetFocusTraversableParentView() {
254 DCHECK(pane_has_focus_);
255 return NULL;
256 }
257