1a98a28534e48e59d5f7d69adae121589e298dd43Douglas Gregor// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2a98a28534e48e59d5f7d69adae121589e298dd43Douglas Gregor// Use of this source code is governed by a BSD-style license that can be
3a98a28534e48e59d5f7d69adae121589e298dd43Douglas Gregor// found in the LICENSE file.
4a98a28534e48e59d5f7d69adae121589e298dd43Douglas Gregor
5a98a28534e48e59d5f7d69adae121589e298dd43Douglas Gregor#include "base/logging.h"
693ab6bf534fb6c26563c00f28a8fc5581bb71dfdStephen Lin#include "ui/views/focus/focus_manager.h"
7a98a28534e48e59d5f7d69adae121589e298dd43Douglas Gregor#include "ui/views/focus/focus_search.h"
8a98a28534e48e59d5f7d69adae121589e298dd43Douglas Gregor#include "ui/views/view.h"
9a98a28534e48e59d5f7d69adae121589e298dd43Douglas Gregor
10a98a28534e48e59d5f7d69adae121589e298dd43Douglas Gregornamespace views {
11a98a28534e48e59d5f7d69adae121589e298dd43Douglas Gregor
12FocusSearch::FocusSearch(View* root, bool cycle, bool accessibility_mode)
13    : root_(root),
14      cycle_(cycle),
15      accessibility_mode_(accessibility_mode) {
16}
17
18View* 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
95bool 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
101bool FocusSearch::IsFocusable(View* v) {
102  if (accessibility_mode_)
103    return v && v->IsAccessibilityFocusable();
104  return v && v->IsFocusable();
105}
106
107View* 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
120View* FocusSearch::GetParent(View* v) {
121  return Contains(root_, v) ? v->parent() : NULL;
122}
123
124bool 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.
136View* 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).
209View* 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