// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/logging.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/focus/focus_search.h"
#include "ui/views/view.h"
namespace views {
FocusSearch::FocusSearch(View* root, bool cycle, bool accessibility_mode)
: root_(root),
cycle_(cycle),
accessibility_mode_(accessibility_mode) {
}
View* FocusSearch::FindNextFocusableView(View* starting_view,
bool reverse,
Direction direction,
bool check_starting_view,
FocusTraversable** focus_traversable,
View** focus_traversable_view) {
*focus_traversable = NULL;
*focus_traversable_view = NULL;
if (!root_->has_children()) {
NOTREACHED();
// Nothing to focus on here.
return NULL;
}
View* initial_starting_view = starting_view;
int starting_view_group = -1;
if (starting_view)
starting_view_group = starting_view->GetGroup();
if (!starting_view) {
// Default to the first/last child
starting_view = reverse ? root_->child_at(root_->child_count() - 1) :
root_->child_at(0);
// If there was no starting view, then the one we select is a potential
// focus candidate.
check_starting_view = true;
} else {
// The starting view should be a direct or indirect child of the root.
DCHECK(Contains(root_, starting_view));
}
View* v = NULL;
if (!reverse) {
v = FindNextFocusableViewImpl(starting_view, check_starting_view,
true,
(direction == DOWN),
starting_view_group,
focus_traversable,
focus_traversable_view);
} else {
// If the starting view is focusable, we don't want to go down, as we are
// traversing the view hierarchy tree bottom-up.
bool can_go_down = (direction == DOWN) && !IsFocusable(starting_view);
v = FindPreviousFocusableViewImpl(starting_view, check_starting_view,
true,
can_go_down,
starting_view_group,
focus_traversable,
focus_traversable_view);
}
// Don't set the focus to something outside of this view hierarchy.
if (v && v != root_ && !Contains(root_, v))
v = NULL;
// If |cycle_| is true, prefer to keep cycling rather than returning NULL.
if (cycle_ && !v && initial_starting_view) {
v = FindNextFocusableView(NULL, reverse, direction, check_starting_view,
focus_traversable, focus_traversable_view);
DCHECK(IsFocusable(v));
return v;
}
// Doing some sanity checks.
if (v) {
DCHECK(IsFocusable(v));
return v;
}
if (*focus_traversable) {
DCHECK(*focus_traversable_view);
return NULL;
}
// Nothing found.
return NULL;
}
bool FocusSearch::IsViewFocusableCandidate(View* v, int skip_group_id) {
return IsFocusable(v) &&
(v->IsGroupFocusTraversable() || skip_group_id == -1 ||
v->GetGroup() != skip_group_id);
}
bool FocusSearch::IsFocusable(View* v) {
if (accessibility_mode_)
return v && v->IsAccessibilityFocusable();
return v && v->IsFocusable();
}
View* FocusSearch::FindSelectedViewForGroup(View* view) {
if (view->IsGroupFocusTraversable() ||
view->GetGroup() == -1) // No group for that view.
return view;
View* selected_view = view->GetSelectedViewForGroup(view->GetGroup());
if (selected_view)
return selected_view;
// No view selected for that group, default to the specified view.
return view;
}
View* FocusSearch::GetParent(View* v) {
return Contains(root_, v) ? v->parent() : NULL;
}
bool FocusSearch::Contains(View* root, const View* v) {
return root->Contains(v);
}
// Strategy for finding the next focusable view:
// - keep going down the first child, stop when you find a focusable view or
// a focus traversable view (in that case return it) or when you reach a view
// with no children.
// - go to the right sibling and start the search from there (by invoking
// FindNextFocusableViewImpl on that view).
// - if the view has no right sibling, go up the parents until you find a parent
// with a right sibling and start the search from there.
View* FocusSearch::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) {
if (check_starting_view) {
if (IsViewFocusableCandidate(starting_view, skip_group_id)) {
View* v = FindSelectedViewForGroup(starting_view);
// The selected view might not be focusable (if it is disabled for
// example).
if (IsFocusable(v))
return v;
}
*focus_traversable = starting_view->GetFocusTraversable();
if (*focus_traversable) {
*focus_traversable_view = starting_view;
return NULL;
}
}
// First let's try the left child.
if (can_go_down) {
if (starting_view->has_children()) {
View* v = FindNextFocusableViewImpl(starting_view->child_at(0),
true, false, true, skip_group_id,
focus_traversable,
focus_traversable_view);
if (v || *focus_traversable)
return v;
}
}
// Then try the right sibling.
View* sibling = starting_view->GetNextFocusableView();
if (sibling) {
View* v = FindNextFocusableViewImpl(sibling,
true, false, true, skip_group_id,
focus_traversable,
focus_traversable_view);
if (v || *focus_traversable)
return v;
}
// Then go up to the parent sibling.
if (can_go_up) {
View* parent = GetParent(starting_view);
while (parent && parent != root_) {
sibling = parent->GetNextFocusableView();
if (sibling) {
return FindNextFocusableViewImpl(sibling,
true, true, true,
skip_group_id,
focus_traversable,
focus_traversable_view);
}
parent = GetParent(parent);
}
}
// We found nothing.
return NULL;
}
// Strategy for finding the previous focusable view:
// - keep going down on the right until you reach a view with no children, if it
// it is a good candidate return it.
// - start the search on the left sibling.
// - if there are no left sibling, start the search on the parent (without going
// down).
View* FocusSearch::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) {
// Let's go down and right as much as we can.
if (can_go_down) {
// Before we go into the direct children, we have to check if this view has
// a FocusTraversable.
*focus_traversable = starting_view->GetFocusTraversable();
if (*focus_traversable) {
*focus_traversable_view = starting_view;
return NULL;
}
if (starting_view->has_children()) {
View* view =
starting_view->child_at(starting_view->child_count() - 1);
View* v = FindPreviousFocusableViewImpl(view, true, false, true,
skip_group_id,
focus_traversable,
focus_traversable_view);
if (v || *focus_traversable)
return v;
}
}
// Then look at this view. Here, we do not need to see if the view has
// a FocusTraversable, since we do not want to go down any more.
if (check_starting_view &&
IsViewFocusableCandidate(starting_view, skip_group_id)) {
View* v = FindSelectedViewForGroup(starting_view);
// The selected view might not be focusable (if it is disabled for
// example).
if (IsFocusable(v))
return v;
}
// Then try the left sibling.
View* sibling = starting_view->GetPreviousFocusableView();
if (sibling) {
return FindPreviousFocusableViewImpl(sibling,
true, true, true,
skip_group_id,
focus_traversable,
focus_traversable_view);
}
// Then go up the parent.
if (can_go_up) {
View* parent = GetParent(starting_view);
if (parent)
return FindPreviousFocusableViewImpl(parent,
true, true, false,
skip_group_id,
focus_traversable,
focus_traversable_view);
}
// We found nothing.
return NULL;
}
} // namespace views