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"
12 FocusSearch::FocusSearch(View
* root
, bool cycle
, bool accessibility_mode
)
15 accessibility_mode_(accessibility_mode
) {
18 View
* FocusSearch::FindNextFocusableView(View
* starting_view
,
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()) {
29 // Nothing to focus on here.
33 View
* initial_starting_view
= starting_view
;
34 int starting_view_group
= -1;
36 starting_view_group
= starting_view
->GetGroup();
39 // Default to the first/last child
40 starting_view
= reverse
? root_
->child_at(root_
->child_count() - 1) :
42 // If there was no starting view, then the one we select is a potential
44 check_starting_view
= true;
46 // The starting view should be a direct or indirect child of the root.
47 DCHECK(Contains(root_
, starting_view
));
52 v
= FindNextFocusableViewImpl(starting_view
, check_starting_view
,
57 focus_traversable_view
);
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
,
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
))
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
));
82 // Doing some sanity checks.
84 DCHECK(IsFocusable(v
));
87 if (*focus_traversable
) {
88 DCHECK(*focus_traversable_view
);
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.
112 View
* selected_view
= view
->GetSelectedViewForGroup(view
->GetGroup());
114 return selected_view
;
116 // No view selected for that group, default to the specified 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
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(
138 bool check_starting_view
,
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
153 *focus_traversable
= starting_view
->GetFocusTraversable();
154 if (*focus_traversable
) {
155 *focus_traversable_view
= starting_view
;
160 // First let's try the left child.
162 if (starting_view
->has_children()) {
163 View
* v
= FindNextFocusableViewImpl(starting_view
->child_at(0),
164 true, false, true, skip_group_id
,
166 focus_traversable_view
);
167 if (v
|| *focus_traversable
)
172 // Then try the right sibling.
173 View
* sibling
= starting_view
->GetNextFocusableView();
175 View
* v
= FindNextFocusableViewImpl(sibling
,
176 true, false, true, skip_group_id
,
178 focus_traversable_view
);
179 if (v
|| *focus_traversable
)
183 // Then go up to the parent sibling.
185 View
* parent
= GetParent(starting_view
);
186 while (parent
&& parent
!= root_
) {
187 sibling
= parent
->GetNextFocusableView();
189 return FindNextFocusableViewImpl(sibling
,
193 focus_traversable_view
);
195 parent
= GetParent(parent
);
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
209 View
* FocusSearch::FindPreviousFocusableViewImpl(
211 bool check_starting_view
,
215 FocusTraversable
** focus_traversable
,
216 View
** focus_traversable_view
) {
217 // Let's go down and right as much as we can.
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
;
227 if (starting_view
->has_children()) {
229 starting_view
->child_at(starting_view
->child_count() - 1);
230 View
* v
= FindPreviousFocusableViewImpl(view
, true, false, true,
233 focus_traversable_view
);
234 if (v
|| *focus_traversable
)
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
250 // Then try the left sibling.
251 View
* sibling
= starting_view
->GetPreviousFocusableView();
253 return FindPreviousFocusableViewImpl(sibling
,
254 true, can_go_up
, true,
257 focus_traversable_view
);
260 // Then go up the parent.
262 View
* parent
= GetParent(starting_view
);
264 return FindPreviousFocusableViewImpl(parent
,
268 focus_traversable_view
);