Home | History | Annotate | Download | only in focus
      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 
     12 FocusSearch::FocusSearch(View* root, bool cycle, bool accessibility_mode)
     13     : root_(root),
     14       cycle_(cycle),
     15       accessibility_mode_(accessibility_mode) {
     16 }
     17 
     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 
     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 
    101 bool FocusSearch::IsFocusable(View* v) {
    102   if (accessibility_mode_)
    103     return v && v->IsAccessibilityFocusable();
    104   return v && v->IsFocusable();
    105 }
    106 
    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 
    120 View* FocusSearch::GetParent(View* v) {
    121   return Contains(root_, v) ? v->parent() : NULL;
    122 }
    123 
    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.
    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).
    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