Home | History | Annotate | Download | only in overview
      1 // Copyright 2013 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 "ash/wm/overview/window_selector.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "ash/accessibility_delegate.h"
     10 #include "ash/ash_switches.h"
     11 #include "ash/metrics/user_metrics_recorder.h"
     12 #include "ash/root_window_controller.h"
     13 #include "ash/shell.h"
     14 #include "ash/shell_window_ids.h"
     15 #include "ash/switchable_windows.h"
     16 #include "ash/wm/overview/scoped_transform_overview_window.h"
     17 #include "ash/wm/overview/window_grid.h"
     18 #include "ash/wm/overview/window_selector_delegate.h"
     19 #include "ash/wm/overview/window_selector_item.h"
     20 #include "ash/wm/window_state.h"
     21 #include "base/auto_reset.h"
     22 #include "base/command_line.h"
     23 #include "base/metrics/histogram.h"
     24 #include "third_party/skia/include/core/SkPaint.h"
     25 #include "third_party/skia/include/core/SkPath.h"
     26 #include "ui/aura/client/focus_client.h"
     27 #include "ui/aura/window.h"
     28 #include "ui/aura/window_event_dispatcher.h"
     29 #include "ui/aura/window_observer.h"
     30 #include "ui/base/resource/resource_bundle.h"
     31 #include "ui/compositor/scoped_layer_animation_settings.h"
     32 #include "ui/events/event.h"
     33 #include "ui/gfx/canvas.h"
     34 #include "ui/gfx/screen.h"
     35 #include "ui/gfx/skia_util.h"
     36 #include "ui/views/border.h"
     37 #include "ui/views/controls/textfield/textfield.h"
     38 #include "ui/views/layout/box_layout.h"
     39 #include "ui/wm/core/window_util.h"
     40 #include "ui/wm/public/activation_client.h"
     41 
     42 namespace ash {
     43 
     44 namespace {
     45 
     46 // The proportion of screen width that the text filter takes.
     47 const float kTextFilterScreenProportion = 0.25;
     48 
     49 // The amount of padding surrounding the text in the text filtering textbox.
     50 const int kTextFilterHorizontalPadding = 8;
     51 
     52 // The distance between the top of the screen and the top edge of the
     53 // text filtering textbox.
     54 const int kTextFilterDistanceFromTop = 32;
     55 
     56 // The height of the text filtering textbox.
     57 const int kTextFilterHeight = 32;
     58 
     59 // The font style used for text filtering.
     60 static const ::ui::ResourceBundle::FontStyle kTextFilterFontStyle =
     61     ::ui::ResourceBundle::FontStyle::MediumFont;
     62 
     63 // The alpha value for the background of the text filtering textbox.
     64 const unsigned char kTextFilterOpacity = 180;
     65 
     66 // The radius used for the rounded corners on the text filtering textbox.
     67 const int kTextFilterCornerRadius = 1;
     68 
     69 // A comparator for locating a grid with a given root window.
     70 struct RootWindowGridComparator
     71     : public std::unary_function<WindowGrid*, bool> {
     72   explicit RootWindowGridComparator(const aura::Window* root_window)
     73       : root_window_(root_window) {
     74   }
     75 
     76   bool operator()(WindowGrid* grid) const {
     77     return (grid->root_window() == root_window_);
     78   }
     79 
     80   const aura::Window* root_window_;
     81 };
     82 
     83 // A comparator for locating a selectable window given a targeted window.
     84 struct WindowSelectorItemTargetComparator
     85     : public std::unary_function<WindowSelectorItem*, bool> {
     86   explicit WindowSelectorItemTargetComparator(const aura::Window* target_window)
     87       : target(target_window) {
     88   }
     89 
     90   bool operator()(WindowSelectorItem* window) const {
     91     return window->Contains(target);
     92   }
     93 
     94   const aura::Window* target;
     95 };
     96 
     97 // A comparator for locating a selector item for a given root.
     98 struct WindowSelectorItemForRoot
     99     : public std::unary_function<WindowSelectorItem*, bool> {
    100   explicit WindowSelectorItemForRoot(const aura::Window* root)
    101       : root_window(root) {
    102   }
    103 
    104   bool operator()(WindowSelectorItem* item) const {
    105     return item->GetRootWindow() == root_window;
    106   }
    107 
    108   const aura::Window* root_window;
    109 };
    110 
    111 // A View having rounded corners and a specified background color which is
    112 // only painted within the bounds defined by the rounded corners.
    113 // TODO(tdanderson): This duplicates code from RoundedImageView. Refactor these
    114 //                   classes and move into ui/views.
    115 class RoundedContainerView : public views::View {
    116  public:
    117   RoundedContainerView(int corner_radius, SkColor background)
    118       : corner_radius_(corner_radius),
    119         background_(background) {
    120   }
    121 
    122   virtual ~RoundedContainerView() {}
    123 
    124   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
    125     views::View::OnPaint(canvas);
    126 
    127     SkScalar radius = SkIntToScalar(corner_radius_);
    128     const SkScalar kRadius[8] = {radius, radius, radius, radius,
    129                                  radius, radius, radius, radius};
    130     SkPath path;
    131     gfx::Rect bounds(size());
    132     path.addRoundRect(gfx::RectToSkRect(bounds), kRadius);
    133 
    134     SkPaint paint;
    135     paint.setAntiAlias(true);
    136     canvas->ClipPath(path, true);
    137     canvas->DrawColor(background_);
    138   }
    139 
    140  private:
    141   int corner_radius_;
    142   SkColor background_;
    143 
    144   DISALLOW_COPY_AND_ASSIGN(RoundedContainerView);
    145 };
    146 
    147 // Triggers a shelf visibility update on all root window controllers.
    148 void UpdateShelfVisibility() {
    149   Shell::RootWindowControllerList root_window_controllers =
    150       Shell::GetInstance()->GetAllRootWindowControllers();
    151   for (Shell::RootWindowControllerList::iterator iter =
    152           root_window_controllers.begin();
    153        iter != root_window_controllers.end(); ++iter) {
    154     (*iter)->UpdateShelfVisibility();
    155   }
    156 }
    157 
    158 // Initializes the text filter on the top of the main root window and requests
    159 // focus on its textfield.
    160 views::Widget* CreateTextFilter(views::TextfieldController* controller,
    161                                 aura::Window* root_window) {
    162   views::Widget* widget = new views::Widget;
    163   views::Widget::InitParams params;
    164   params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
    165   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    166   params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
    167   params.parent =
    168       Shell::GetContainer(root_window, ash::kShellWindowId_OverlayContainer);
    169   params.accept_events = true;
    170   params.bounds = gfx::Rect(
    171       root_window->bounds().width() / 2 * (1 - kTextFilterScreenProportion),
    172       kTextFilterDistanceFromTop,
    173       root_window->bounds().width() * kTextFilterScreenProportion,
    174       kTextFilterHeight);
    175   widget->Init(params);
    176 
    177   // Use |container| to specify the padding surrounding the text and to give
    178   // the textfield rounded corners.
    179   views::View* container = new RoundedContainerView(
    180       kTextFilterCornerRadius, SkColorSetARGB(kTextFilterOpacity, 0, 0, 0));
    181   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
    182   int text_height = bundle.GetFontList(kTextFilterFontStyle).GetHeight();
    183   DCHECK(text_height);
    184   int vertical_padding = (kTextFilterHeight - text_height) / 2;
    185   container->SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical,
    186                                                    kTextFilterHorizontalPadding,
    187                                                    vertical_padding,
    188                                                    0));
    189 
    190   views::Textfield* textfield = new views::Textfield;
    191   textfield->set_controller(controller);
    192   textfield->SetBackgroundColor(SK_ColorTRANSPARENT);
    193   textfield->SetBorder(views::Border::NullBorder());
    194   textfield->SetTextColor(SK_ColorWHITE);
    195   textfield->SetFontList(bundle.GetFontList(kTextFilterFontStyle));
    196 
    197   container->AddChildView(textfield);
    198   widget->SetContentsView(container);
    199 
    200   // The textfield initially contains no text, so shift its position to be
    201   // outside the visible bounds of the screen.
    202   gfx::Transform transform;
    203   transform.Translate(0, -WindowSelector::kTextFilterBottomEdge);
    204   widget->GetNativeWindow()->SetTransform(transform);
    205   widget->Show();
    206   textfield->RequestFocus();
    207 
    208   return widget;
    209 }
    210 
    211 }  // namespace
    212 
    213 const int WindowSelector::kTextFilterBottomEdge =
    214     kTextFilterDistanceFromTop + kTextFilterHeight;
    215 
    216 WindowSelector::WindowSelector(const WindowList& windows,
    217                                WindowSelectorDelegate* delegate)
    218     : delegate_(delegate),
    219       restore_focus_window_(aura::client::GetFocusClient(
    220           Shell::GetPrimaryRootWindow())->GetFocusedWindow()),
    221       ignore_activations_(false),
    222       selected_grid_index_(0),
    223       overview_start_time_(base::Time::Now()),
    224       num_key_presses_(0),
    225       num_items_(0),
    226       showing_selection_widget_(false),
    227       text_filter_string_length_(0),
    228       num_times_textfield_cleared_(0) {
    229   DCHECK(delegate_);
    230   Shell* shell = Shell::GetInstance();
    231   shell->OnOverviewModeStarting();
    232 
    233   if (restore_focus_window_)
    234     restore_focus_window_->AddObserver(this);
    235 
    236   const aura::Window::Windows root_windows = Shell::GetAllRootWindows();
    237   for (aura::Window::Windows::const_iterator iter = root_windows.begin();
    238        iter != root_windows.end(); iter++) {
    239     // Observed switchable containers for newly created windows on all root
    240     // windows.
    241     for (size_t i = 0; i < kSwitchableWindowContainerIdsLength; ++i) {
    242       aura::Window* container = Shell::GetContainer(*iter,
    243           kSwitchableWindowContainerIds[i]);
    244       container->AddObserver(this);
    245       observed_windows_.insert(container);
    246     }
    247     scoped_ptr<WindowGrid> grid(new WindowGrid(*iter, windows, this));
    248     if (grid->empty())
    249       continue;
    250     num_items_ += grid->size();
    251     grid_list_.push_back(grid.release());
    252   }
    253 
    254   // Do not call PrepareForOverview until all items are added to window_list_ as
    255   // we don't want to cause any window updates until all windows in overview
    256   // are observed. See http://crbug.com/384495.
    257   for (ScopedVector<WindowGrid>::iterator iter = grid_list_.begin();
    258        iter != grid_list_.end(); ++iter) {
    259     (*iter)->PrepareForOverview();
    260     (*iter)->PositionWindows(true);
    261   }
    262 
    263   DCHECK(!grid_list_.empty());
    264   UMA_HISTOGRAM_COUNTS_100("Ash.WindowSelector.Items", num_items_);
    265 
    266   text_filter_widget_.reset(
    267       CreateTextFilter(this, Shell::GetPrimaryRootWindow()));
    268 
    269   shell->activation_client()->AddObserver(this);
    270 
    271   shell->GetScreen()->AddObserver(this);
    272   shell->metrics()->RecordUserMetricsAction(UMA_WINDOW_OVERVIEW);
    273   HideAndTrackNonOverviewWindows();
    274   // Send an a11y alert.
    275   shell->accessibility_delegate()->TriggerAccessibilityAlert(
    276       A11Y_ALERT_WINDOW_OVERVIEW_MODE_ENTERED);
    277 
    278   UpdateShelfVisibility();
    279 }
    280 
    281 WindowSelector::~WindowSelector() {
    282   ash::Shell* shell = ash::Shell::GetInstance();
    283 
    284   ResetFocusRestoreWindow(true);
    285   for (std::set<aura::Window*>::iterator iter = observed_windows_.begin();
    286        iter != observed_windows_.end(); ++iter) {
    287     (*iter)->RemoveObserver(this);
    288   }
    289   shell->activation_client()->RemoveObserver(this);
    290   aura::Window::Windows root_windows = Shell::GetAllRootWindows();
    291 
    292   const aura::WindowTracker::Windows hidden_windows(hidden_windows_.windows());
    293   for (aura::WindowTracker::Windows::const_iterator iter =
    294        hidden_windows.begin(); iter != hidden_windows.end(); ++iter) {
    295     ui::ScopedLayerAnimationSettings settings(
    296         (*iter)->layer()->GetAnimator());
    297     settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
    298         ScopedTransformOverviewWindow::kTransitionMilliseconds));
    299     settings.SetPreemptionStrategy(
    300         ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
    301     (*iter)->layer()->SetOpacity(1);
    302     (*iter)->Show();
    303   }
    304 
    305   shell->GetScreen()->RemoveObserver(this);
    306 
    307   size_t remaining_items = 0;
    308   for (ScopedVector<WindowGrid>::iterator iter = grid_list_.begin();
    309       iter != grid_list_.end(); iter++) {
    310     remaining_items += (*iter)->size();
    311   }
    312 
    313   DCHECK(num_items_ >= remaining_items);
    314   UMA_HISTOGRAM_COUNTS_100("Ash.WindowSelector.OverviewClosedItems",
    315                            num_items_ - remaining_items);
    316   UMA_HISTOGRAM_MEDIUM_TIMES("Ash.WindowSelector.TimeInOverview",
    317                              base::Time::Now() - overview_start_time_);
    318 
    319   // Record metrics related to text filtering.
    320   UMA_HISTOGRAM_COUNTS_100("Ash.WindowSelector.TextFilteringStringLength",
    321                            text_filter_string_length_);
    322   UMA_HISTOGRAM_COUNTS_100("Ash.WindowSelector.TextFilteringTextfieldCleared",
    323                            num_times_textfield_cleared_);
    324   if (text_filter_string_length_) {
    325     UMA_HISTOGRAM_MEDIUM_TIMES(
    326         "Ash.WindowSelector.TimeInOverviewWithTextFiltering",
    327         base::Time::Now() - overview_start_time_);
    328     UMA_HISTOGRAM_COUNTS_100(
    329         "Ash.WindowSelector.ItemsWhenTextFilteringUsed",
    330         remaining_items);
    331   }
    332 
    333   // TODO(flackr): Change this to OnOverviewModeEnded and move it to when
    334   // everything is done.
    335   shell->OnOverviewModeEnding();
    336 
    337   // Clearing the window list resets the ignored_by_shelf flag on the windows.
    338   grid_list_.clear();
    339   UpdateShelfVisibility();
    340 }
    341 
    342 void WindowSelector::CancelSelection() {
    343   delegate_->OnSelectionEnded();
    344 }
    345 
    346 void WindowSelector::OnGridEmpty(WindowGrid* grid) {
    347   ScopedVector<WindowGrid>::iterator iter =
    348       std::find(grid_list_.begin(), grid_list_.end(), grid);
    349   DCHECK(iter != grid_list_.end());
    350   grid_list_.erase(iter);
    351   // TODO(flackr): Use the previous index for more than two displays.
    352   selected_grid_index_ = 0;
    353   if (grid_list_.empty())
    354     CancelSelection();
    355 }
    356 
    357 bool WindowSelector::HandleKeyEvent(views::Textfield* sender,
    358                                     const ui::KeyEvent& key_event) {
    359   if (key_event.type() != ui::ET_KEY_PRESSED)
    360     return false;
    361 
    362   switch (key_event.key_code()) {
    363     case ui::VKEY_ESCAPE:
    364       CancelSelection();
    365       break;
    366     case ui::VKEY_UP:
    367       num_key_presses_++;
    368       Move(WindowSelector::UP, true);
    369       break;
    370     case ui::VKEY_DOWN:
    371       num_key_presses_++;
    372       Move(WindowSelector::DOWN, true);
    373       break;
    374     case ui::VKEY_RIGHT:
    375     case ui::VKEY_TAB:
    376       num_key_presses_++;
    377       Move(WindowSelector::RIGHT, true);
    378       break;
    379     case ui::VKEY_LEFT:
    380       num_key_presses_++;
    381       Move(WindowSelector::LEFT, true);
    382       break;
    383     case ui::VKEY_RETURN:
    384       // Ignore if no item is selected.
    385       if (!grid_list_[selected_grid_index_]->is_selecting())
    386         return false;
    387       UMA_HISTOGRAM_COUNTS_100("Ash.WindowSelector.ArrowKeyPresses",
    388                                num_key_presses_);
    389       UMA_HISTOGRAM_CUSTOM_COUNTS(
    390           "Ash.WindowSelector.KeyPressesOverItemsRatio",
    391           (num_key_presses_ * 100) / num_items_, 1, 300, 30);
    392       Shell::GetInstance()->metrics()->RecordUserMetricsAction(
    393           UMA_WINDOW_OVERVIEW_ENTER_KEY);
    394       wm::GetWindowState(grid_list_[selected_grid_index_]->
    395                          SelectedWindow()->SelectionWindow())->Activate();
    396       break;
    397     default:
    398       // Not a key we are interested in, allow the textfield to handle it.
    399       return false;
    400   }
    401   return true;
    402 }
    403 
    404 void WindowSelector::OnDisplayAdded(const gfx::Display& display) {
    405 }
    406 
    407 void WindowSelector::OnDisplayRemoved(const gfx::Display& display) {
    408   // TODO(flackr): Keep window selection active on remaining displays.
    409   CancelSelection();
    410 }
    411 
    412 void WindowSelector::OnDisplayMetricsChanged(const gfx::Display& display,
    413                                              uint32_t metrics) {
    414   PositionWindows(/* animate */ false);
    415 }
    416 
    417 void WindowSelector::OnWindowAdded(aura::Window* new_window) {
    418   if (new_window->type() != ui::wm::WINDOW_TYPE_NORMAL &&
    419       new_window->type() != ui::wm::WINDOW_TYPE_PANEL) {
    420     return;
    421   }
    422 
    423   for (size_t i = 0; i < kSwitchableWindowContainerIdsLength; ++i) {
    424     if (new_window->parent()->id() == kSwitchableWindowContainerIds[i] &&
    425         !::wm::GetTransientParent(new_window)) {
    426       // The new window is in one of the switchable containers, abort overview.
    427       CancelSelection();
    428       return;
    429     }
    430   }
    431 }
    432 
    433 void WindowSelector::OnWindowDestroying(aura::Window* window) {
    434   window->RemoveObserver(this);
    435   observed_windows_.erase(window);
    436   if (window == restore_focus_window_)
    437     restore_focus_window_ = NULL;
    438 }
    439 
    440 void WindowSelector::OnWindowActivated(aura::Window* gained_active,
    441                                        aura::Window* lost_active) {
    442   if (ignore_activations_ ||
    443       !gained_active ||
    444       gained_active == text_filter_widget_->GetNativeWindow()) {
    445     return;
    446   }
    447 
    448   ScopedVector<WindowGrid>::iterator grid =
    449       std::find_if(grid_list_.begin(), grid_list_.end(),
    450                    RootWindowGridComparator(gained_active->GetRootWindow()));
    451   if (grid == grid_list_.end())
    452     return;
    453   const std::vector<WindowSelectorItem*> windows = (*grid)->window_list();
    454 
    455   ScopedVector<WindowSelectorItem>::const_iterator iter = std::find_if(
    456       windows.begin(), windows.end(),
    457       WindowSelectorItemTargetComparator(gained_active));
    458 
    459   if (iter != windows.end())
    460     (*iter)->RestoreWindowOnExit(gained_active);
    461 
    462   // Don't restore focus on exit if a window was just activated.
    463   ResetFocusRestoreWindow(false);
    464   CancelSelection();
    465 }
    466 
    467 void WindowSelector::OnAttemptToReactivateWindow(aura::Window* request_active,
    468                                                  aura::Window* actual_active) {
    469   OnWindowActivated(request_active, actual_active);
    470 }
    471 
    472 void WindowSelector::ContentsChanged(views::Textfield* sender,
    473                                      const base::string16& new_contents) {
    474   if (CommandLine::ForCurrentProcess()->HasSwitch(
    475       switches::kAshDisableTextFilteringInOverviewMode)) {
    476     return;
    477   }
    478 
    479   text_filter_string_length_ = new_contents.length();
    480   if (!text_filter_string_length_)
    481     num_times_textfield_cleared_++;
    482 
    483   bool should_show_selection_widget = !new_contents.empty();
    484   if (showing_selection_widget_ != should_show_selection_widget) {
    485     ui::ScopedLayerAnimationSettings animation_settings(
    486         text_filter_widget_->GetNativeWindow()->layer()->GetAnimator());
    487     animation_settings.SetPreemptionStrategy(
    488         ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
    489     animation_settings.SetTweenType(showing_selection_widget_ ?
    490         gfx::Tween::FAST_OUT_LINEAR_IN : gfx::Tween::LINEAR_OUT_SLOW_IN);
    491 
    492     gfx::Transform transform;
    493     if (should_show_selection_widget) {
    494       transform.Translate(0, 0);
    495       text_filter_widget_->GetNativeWindow()->layer()->SetOpacity(1);
    496     } else {
    497       transform.Translate(0, -kTextFilterBottomEdge);
    498       text_filter_widget_->GetNativeWindow()->layer()->SetOpacity(0);
    499     }
    500 
    501     text_filter_widget_->GetNativeWindow()->SetTransform(transform);
    502     showing_selection_widget_ = should_show_selection_widget;
    503   }
    504   for (ScopedVector<WindowGrid>::iterator iter = grid_list_.begin();
    505        iter != grid_list_.end(); iter++) {
    506     (*iter)->FilterItems(new_contents);
    507   }
    508 
    509   // If the selection widget is not active, execute a Move() command so that it
    510   // shows up on the first undimmed item.
    511   if (grid_list_[selected_grid_index_]->is_selecting())
    512     return;
    513   Move(WindowSelector::RIGHT, false);
    514 }
    515 
    516 void WindowSelector::PositionWindows(bool animate) {
    517   for (ScopedVector<WindowGrid>::iterator iter = grid_list_.begin();
    518       iter != grid_list_.end(); iter++) {
    519     (*iter)->PositionWindows(animate);
    520   }
    521 }
    522 
    523 void WindowSelector::HideAndTrackNonOverviewWindows() {
    524   // Add the windows to hidden_windows first so that if any are destroyed
    525   // while hiding them they are tracked.
    526   for (ScopedVector<WindowGrid>::iterator grid_iter = grid_list_.begin();
    527       grid_iter != grid_list_.end(); ++grid_iter) {
    528     for (size_t i = 0; i < kSwitchableWindowContainerIdsLength; ++i) {
    529       const aura::Window* container =
    530           Shell::GetContainer((*grid_iter)->root_window(),
    531                               kSwitchableWindowContainerIds[i]);
    532       for (aura::Window::Windows::const_iterator iter =
    533            container->children().begin(); iter != container->children().end();
    534            ++iter) {
    535         if (!(*iter)->IsVisible() || (*grid_iter)->Contains(*iter))
    536           continue;
    537         hidden_windows_.Add(*iter);
    538       }
    539     }
    540   }
    541 
    542   // Copy the window list as it can change during iteration.
    543   const aura::WindowTracker::Windows hidden_windows(hidden_windows_.windows());
    544   for (aura::WindowTracker::Windows::const_iterator iter =
    545        hidden_windows.begin(); iter != hidden_windows.end(); ++iter) {
    546     if (!hidden_windows_.Contains(*iter))
    547       continue;
    548     ui::ScopedLayerAnimationSettings settings(
    549         (*iter)->layer()->GetAnimator());
    550     settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
    551         ScopedTransformOverviewWindow::kTransitionMilliseconds));
    552     settings.SetPreemptionStrategy(
    553         ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
    554     (*iter)->Hide();
    555     // Hiding the window can result in it being destroyed.
    556     if (!hidden_windows_.Contains(*iter))
    557       continue;
    558     (*iter)->layer()->SetOpacity(0);
    559   }
    560 }
    561 
    562 void WindowSelector::ResetFocusRestoreWindow(bool focus) {
    563   if (!restore_focus_window_)
    564     return;
    565   if (focus) {
    566     base::AutoReset<bool> restoring_focus(&ignore_activations_, true);
    567     restore_focus_window_->Focus();
    568   }
    569   // If the window is in the observed_windows_ list it needs to continue to be
    570   // observed.
    571   if (observed_windows_.find(restore_focus_window_) ==
    572           observed_windows_.end()) {
    573     restore_focus_window_->RemoveObserver(this);
    574   }
    575   restore_focus_window_ = NULL;
    576 }
    577 
    578 void WindowSelector::Move(Direction direction, bool animate) {
    579   // Keep calling Move() on the grids until one of them reports no overflow or
    580   // we made a full cycle on all the grids.
    581   for (size_t i = 0;
    582       i <= grid_list_.size() &&
    583       grid_list_[selected_grid_index_]->Move(direction, animate); i++) {
    584     // TODO(flackr): If there are more than two monitors, move between grids
    585     // in the requested direction.
    586     selected_grid_index_ = (selected_grid_index_ + 1) % grid_list_.size();
    587   }
    588 }
    589 
    590 }  // namespace ash
    591