Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / ui / views / focus / focus_search.cc
blob2507b3460ef8fd2ed9006f3e5226e28d5bb09fea
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.
5 #include "base/logging.h"
6 #include "ui/views/focus/focus_manager.h"
7 #include "ui/views/focus/focus_search.h"
8 #include "ui/views/view.h"
10 namespace views {
12 FocusSearch::FocusSearch(View* root, bool cycle, bool accessibility_mode)
13 : root_(root),
14 cycle_(cycle),
15 accessibility_mode_(accessibility_mode) {
18 View* FocusSearch::FindNextFocusableView(View* starting_view,
19 bool reverse,
20 Direction direction,
21 bool check_starting_view,
22 FocusTraversable** focus_traversable,
23 View** focus_traversable_view) {
24 *focus_traversable = NULL;
25 *focus_traversable_view = NULL;
27 if (!root_->has_children()) {
28 NOTREACHED();
29 // Nothing to focus on here.
30 return NULL;
33 View* initial_starting_view = starting_view;
34 int starting_view_group = -1;
35 if (starting_view)
36 starting_view_group = starting_view->GetGroup();
38 if (!starting_view) {
39 // Default to the first/last child
40 starting_view = reverse ? root_->child_at(root_->child_count() - 1) :
41 root_->child_at(0);
42 // If there was no starting view, then the one we select is a potential
43 // focus candidate.
44 check_starting_view = true;
45 } else {
46 // The starting view should be a direct or indirect child of the root.
47 DCHECK(Contains(root_, starting_view));
50 View* v = NULL;
51 if (!reverse) {
52 v = FindNextFocusableViewImpl(starting_view, check_starting_view,
53 true,
54 (direction == DOWN),
55 starting_view_group,
56 focus_traversable,
57 focus_traversable_view);
58 } else {
59 // If the starting view is focusable, we don't want to go down, as we are
60 // traversing the view hierarchy tree bottom-up.
61 bool can_go_down = (direction == DOWN) && !IsFocusable(starting_view);
62 v = FindPreviousFocusableViewImpl(starting_view, check_starting_view,
63 true,
64 can_go_down,
65 starting_view_group,
66 focus_traversable,
67 focus_traversable_view);
70 // Don't set the focus to something outside of this view hierarchy.
71 if (v && v != root_ && !Contains(root_, v))
72 v = NULL;
74 // If |cycle_| is true, prefer to keep cycling rather than returning NULL.
75 if (cycle_ && !v && initial_starting_view) {
76 v = FindNextFocusableView(NULL, reverse, direction, check_starting_view,
77 focus_traversable, focus_traversable_view);
78 DCHECK(IsFocusable(v));
79 return v;
82 // Doing some sanity checks.
83 if (v) {
84 DCHECK(IsFocusable(v));
85 return v;
87 if (*focus_traversable) {
88 DCHECK(*focus_traversable_view);
89 return NULL;
91 // Nothing found.
92 return NULL;
95 bool FocusSearch::IsViewFocusableCandidate(View* v, int skip_group_id) {
96 return IsFocusable(v) &&
97 (v->IsGroupFocusTraversable() || skip_group_id == -1 ||
98 v->GetGroup() != skip_group_id);
101 bool FocusSearch::IsFocusable(View* v) {
102 if (accessibility_mode_)
103 return v && v->IsAccessibilityFocusable();
104 return v && v->IsFocusable();
107 View* FocusSearch::FindSelectedViewForGroup(View* view) {
108 if (view->IsGroupFocusTraversable() ||
109 view->GetGroup() == -1) // No group for that view.
110 return view;
112 View* selected_view = view->GetSelectedViewForGroup(view->GetGroup());
113 if (selected_view)
114 return selected_view;
116 // No view selected for that group, default to the specified view.
117 return view;
120 View* FocusSearch::GetParent(View* v) {
121 return Contains(root_, v) ? v->parent() : NULL;
124 bool FocusSearch::Contains(View* root, const View* v) {
125 return root->Contains(v);
128 // Strategy for finding the next focusable view:
129 // - keep going down the first child, stop when you find a focusable view or
130 // a focus traversable view (in that case return it) or when you reach a view
131 // with no children.
132 // - go to the right sibling and start the search from there (by invoking
133 // FindNextFocusableViewImpl on that view).
134 // - if the view has no right sibling, go up the parents until you find a parent
135 // with a right sibling and start the search from there.
136 View* FocusSearch::FindNextFocusableViewImpl(
137 View* starting_view,
138 bool check_starting_view,
139 bool can_go_up,
140 bool can_go_down,
141 int skip_group_id,
142 FocusTraversable** focus_traversable,
143 View** focus_traversable_view) {
144 if (check_starting_view) {
145 if (IsViewFocusableCandidate(starting_view, skip_group_id)) {
146 View* v = FindSelectedViewForGroup(starting_view);
147 // The selected view might not be focusable (if it is disabled for
148 // example).
149 if (IsFocusable(v))
150 return v;
153 *focus_traversable = starting_view->GetFocusTraversable();
154 if (*focus_traversable) {
155 *focus_traversable_view = starting_view;
156 return NULL;
160 // First let's try the left child.
161 if (can_go_down) {
162 if (starting_view->has_children()) {
163 View* v = FindNextFocusableViewImpl(starting_view->child_at(0),
164 true, false, true, skip_group_id,
165 focus_traversable,
166 focus_traversable_view);
167 if (v || *focus_traversable)
168 return v;
172 // Then try the right sibling.
173 View* sibling = starting_view->GetNextFocusableView();
174 if (sibling) {
175 View* v = FindNextFocusableViewImpl(sibling,
176 true, false, true, skip_group_id,
177 focus_traversable,
178 focus_traversable_view);
179 if (v || *focus_traversable)
180 return v;
183 // Then go up to the parent sibling.
184 if (can_go_up) {
185 View* parent = GetParent(starting_view);
186 while (parent && parent != root_) {
187 sibling = parent->GetNextFocusableView();
188 if (sibling) {
189 return FindNextFocusableViewImpl(sibling,
190 true, true, true,
191 skip_group_id,
192 focus_traversable,
193 focus_traversable_view);
195 parent = GetParent(parent);
199 // We found nothing.
200 return NULL;
203 // Strategy for finding the previous focusable view:
204 // - keep going down on the right until you reach a view with no children, if it
205 // it is a good candidate return it.
206 // - start the search on the left sibling.
207 // - if there are no left sibling, start the search on the parent (without going
208 // down).
209 View* FocusSearch::FindPreviousFocusableViewImpl(
210 View* starting_view,
211 bool check_starting_view,
212 bool can_go_up,
213 bool can_go_down,
214 int skip_group_id,
215 FocusTraversable** focus_traversable,
216 View** focus_traversable_view) {
217 // Let's go down and right as much as we can.
218 if (can_go_down) {
219 // Before we go into the direct children, we have to check if this view has
220 // a FocusTraversable.
221 *focus_traversable = starting_view->GetFocusTraversable();
222 if (*focus_traversable) {
223 *focus_traversable_view = starting_view;
224 return NULL;
227 if (starting_view->has_children()) {
228 View* view =
229 starting_view->child_at(starting_view->child_count() - 1);
230 View* v = FindPreviousFocusableViewImpl(view, true, false, true,
231 skip_group_id,
232 focus_traversable,
233 focus_traversable_view);
234 if (v || *focus_traversable)
235 return v;
239 // Then look at this view. Here, we do not need to see if the view has
240 // a FocusTraversable, since we do not want to go down any more.
241 if (check_starting_view &&
242 IsViewFocusableCandidate(starting_view, skip_group_id)) {
243 View* v = FindSelectedViewForGroup(starting_view);
244 // The selected view might not be focusable (if it is disabled for
245 // example).
246 if (IsFocusable(v))
247 return v;
250 // Then try the left sibling.
251 View* sibling = starting_view->GetPreviousFocusableView();
252 if (sibling) {
253 return FindPreviousFocusableViewImpl(sibling,
254 true, true, true,
255 skip_group_id,
256 focus_traversable,
257 focus_traversable_view);
260 // Then go up the parent.
261 if (can_go_up) {
262 View* parent = GetParent(starting_view);
263 if (parent)
264 return FindPreviousFocusableViewImpl(parent,
265 true, true, false,
266 skip_group_id,
267 focus_traversable,
268 focus_traversable_view);
271 // We found nothing.
272 return NULL;
275 } // namespace views