• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "ui/views/focus/focus_manager.h"
7 #include "ui/views/focus/focus_search.h"
8 #include "ui/views/view.h"
9 
10 namespace views {
11 
FocusSearch(View * root,bool cycle,bool accessibility_mode)12 FocusSearch::FocusSearch(View* root, bool cycle, bool accessibility_mode)
13     : root_(root),
14       cycle_(cycle),
15       accessibility_mode_(accessibility_mode) {
16 }
17 
FindNextFocusableView(View * starting_view,bool reverse,Direction direction,bool check_starting_view,FocusTraversable ** focus_traversable,View ** focus_traversable_view)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;
26 
27   if (!root_->has_children()) {
28     NOTREACHED();
29     // Nothing to focus on here.
30     return NULL;
31   }
32 
33   View* initial_starting_view = starting_view;
34   int starting_view_group = -1;
35   if (starting_view)
36     starting_view_group = starting_view->GetGroup();
37 
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));
48   }
49 
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);
68   }
69 
70   // Don't set the focus to something outside of this view hierarchy.
71   if (v && v != root_ && !Contains(root_, v))
72     v = NULL;
73 
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;
80   }
81 
82   // Doing some sanity checks.
83   if (v) {
84     DCHECK(IsFocusable(v));
85     return v;
86   }
87   if (*focus_traversable) {
88     DCHECK(*focus_traversable_view);
89     return NULL;
90   }
91   // Nothing found.
92   return NULL;
93 }
94 
IsViewFocusableCandidate(View * v,int skip_group_id)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);
99 }
100 
IsFocusable(View * v)101 bool FocusSearch::IsFocusable(View* v) {
102   if (accessibility_mode_)
103     return v && v->IsAccessibilityFocusable();
104   return v && v->IsFocusable();
105 }
106 
FindSelectedViewForGroup(View * view)107 View* FocusSearch::FindSelectedViewForGroup(View* view) {
108   if (view->IsGroupFocusTraversable() ||
109       view->GetGroup() == -1)  // No group for that view.
110     return view;
111 
112   View* selected_view = view->GetSelectedViewForGroup(view->GetGroup());
113   if (selected_view)
114     return selected_view;
115 
116   // No view selected for that group, default to the specified view.
117   return view;
118 }
119 
GetParent(View * v)120 View* FocusSearch::GetParent(View* v) {
121   return Contains(root_, v) ? v->parent() : NULL;
122 }
123 
Contains(View * root,const View * v)124 bool FocusSearch::Contains(View* root, const View* v) {
125   return root->Contains(v);
126 }
127 
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.
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)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;
151     }
152 
153     *focus_traversable = starting_view->GetFocusTraversable();
154     if (*focus_traversable) {
155       *focus_traversable_view = starting_view;
156       return NULL;
157     }
158   }
159 
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;
169     }
170   }
171 
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;
181   }
182 
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);
194       }
195       parent = GetParent(parent);
196     }
197   }
198 
199   // We found nothing.
200   return NULL;
201 }
202 
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).
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)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;
225     }
226 
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;
236     }
237   }
238 
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;
248   }
249 
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);
258   }
259 
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);
269   }
270 
271   // We found nothing.
272   return NULL;
273 }
274 
275 }  // namespace views
276