window_cycle_controller.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2012 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/window_cycle_controller.h"
6
7#include <algorithm>
8
9#include "ash/session_state_delegate.h"
10#include "ash/shell.h"
11#include "ash/shell_window_ids.h"
12#include "ash/wm/activation_controller.h"
13#include "ash/wm/window_cycle_list.h"
14#include "ash/wm/window_util.h"
15#include "ash/wm/workspace_controller.h"
16#include "ui/aura/root_window.h"
17#include "ui/base/events/event.h"
18#include "ui/base/events/event_handler.h"
19
20namespace ash {
21
22namespace {
23
24// List of containers whose children we will cycle through.
25const int kContainerIds[] = {
26  internal::kShellWindowId_DefaultContainer,
27  internal::kShellWindowId_AlwaysOnTopContainer
28};
29
30// Filter to watch for the termination of a keyboard gesture to cycle through
31// multiple windows.
32class WindowCycleEventFilter : public ui::EventHandler {
33 public:
34  WindowCycleEventFilter();
35  virtual ~WindowCycleEventFilter();
36
37  // Overridden from ui::EventHandler:
38  virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
39 private:
40  DISALLOW_COPY_AND_ASSIGN(WindowCycleEventFilter);
41};
42
43// Watch for all keyboard events by filtering the root window.
44WindowCycleEventFilter::WindowCycleEventFilter() {
45}
46
47WindowCycleEventFilter::~WindowCycleEventFilter() {
48}
49
50void WindowCycleEventFilter::OnKeyEvent(ui::KeyEvent* event) {
51  // Views uses VKEY_MENU for both left and right Alt keys.
52  if (event->key_code() == ui::VKEY_MENU &&
53      event->type() == ui::ET_KEY_RELEASED) {
54    Shell::GetInstance()->window_cycle_controller()->AltKeyReleased();
55    // Warning: |this| will be deleted from here on.
56  }
57}
58
59// Adds all the children of |window| to |windows|.
60void AddAllChildren(aura::Window* window,
61                    WindowCycleList::WindowList* windows) {
62  const WindowCycleList::WindowList& children(window->children());
63  windows->insert(windows->end(), children.begin(), children.end());
64}
65
66// Adds all the children of all of |window|s children to |windows|.
67void AddWorkspaceChildren(aura::Window* window,
68                          WindowCycleList::WindowList* windows) {
69  for (size_t i = 0; i < window->children().size(); ++i)
70    AddAllChildren(window->children()[i], windows);
71}
72
73// Adds the windows that can be cycled through for the specified window id to
74// |windows|.
75void AddCycleWindows(aura::RootWindow* root,
76                     int container_id,
77                     WindowCycleList::WindowList* windows) {
78  aura::Window* container = Shell::GetContainer(root, container_id);
79  if (container_id == internal::kShellWindowId_DefaultContainer)
80    AddWorkspaceChildren(container, windows);
81  else
82    AddAllChildren(container, windows);
83}
84
85}  // namespace
86
87//////////////////////////////////////////////////////////////////////////////
88// WindowCycleController, public:
89
90WindowCycleController::WindowCycleController(
91    aura::client::ActivationClient* activation_client)
92    : activation_client_(activation_client) {
93  activation_client_->AddObserver(this);
94}
95
96WindowCycleController::~WindowCycleController() {
97  Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
98  for (Shell::RootWindowList::const_iterator iter = root_windows.begin();
99       iter != root_windows.end(); ++iter) {
100    for (size_t i = 0; i < arraysize(kContainerIds); ++i) {
101      aura::Window* container = Shell::GetContainer(*iter, kContainerIds[i]);
102      if (container)
103        container->RemoveObserver(this);
104    }
105    aura::Window* default_container =
106        Shell::GetContainer(*iter, internal::kShellWindowId_DefaultContainer);
107    if (default_container) {
108      for (size_t i = 0; i < default_container->children().size(); ++i) {
109        aura::Window* workspace_window = default_container->children()[i];
110        DCHECK_EQ(internal::kShellWindowId_WorkspaceContainer,
111                  workspace_window->id());
112        workspace_window->RemoveObserver(this);
113      }
114    }
115  }
116
117  activation_client_->RemoveObserver(this);
118  StopCycling();
119}
120
121// static
122bool WindowCycleController::CanCycle() {
123  // Don't allow window cycling if the screen is locked or a modal dialog is
124  // open.
125  return !Shell::GetInstance()->session_state_delegate()->IsScreenLocked() &&
126         !Shell::GetInstance()->IsSystemModalWindowOpen();
127}
128
129void WindowCycleController::HandleCycleWindow(Direction direction,
130                                              bool is_alt_down) {
131  if (!CanCycle())
132    return;
133
134  if (is_alt_down) {
135    if (!IsCycling()) {
136      // This is the start of an alt-tab cycle through multiple windows, so
137      // listen for the alt key being released to stop cycling.
138      StartCycling();
139      Step(direction);
140      InstallEventFilter();
141    } else {
142      // We're in the middle of an alt-tab cycle, just step forward.
143      Step(direction);
144    }
145  } else {
146    // This is a simple, single-step window cycle.
147    StartCycling();
148    Step(direction);
149    StopCycling();
150  }
151}
152
153void WindowCycleController::HandleLinearCycleWindow() {
154  if (!CanCycle() || IsCycling())
155    return;
156
157  // Use the reversed list of windows to prevent a 2-cycle of the most recent
158  // windows occurring.
159  WindowCycleList cycle_list(BuildWindowList(NULL,true));
160  cycle_list.Step(WindowCycleList::FORWARD);
161}
162
163void WindowCycleController::AltKeyReleased() {
164  StopCycling();
165}
166
167// static
168std::vector<aura::Window*> WindowCycleController::BuildWindowList(
169    const std::list<aura::Window*>* mru_windows,
170    bool top_most_at_end) {
171  WindowCycleList::WindowList windows;
172  Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
173
174  aura::RootWindow* active_root = Shell::GetActiveRootWindow();
175  for (Shell::RootWindowList::const_iterator iter = root_windows.begin();
176       iter != root_windows.end(); ++iter) {
177    if (*iter == active_root)
178      continue;
179    for (size_t i = 0; i < arraysize(kContainerIds); ++i)
180      AddCycleWindows(*iter, kContainerIds[i], &windows);
181  }
182
183  // Add windows in the active root windows last so that the topmost window
184  // in the active root window becomes the front of the list.
185  for (size_t i = 0; i < arraysize(kContainerIds); ++i)
186    AddCycleWindows(active_root, kContainerIds[i], &windows);
187
188  // Removes unfocusable windows.
189  WindowCycleList::WindowList::iterator last =
190      std::remove_if(
191          windows.begin(),
192          windows.end(),
193          std::not1(std::ptr_fun(ash::wm::CanActivateWindow)));
194  windows.erase(last, windows.end());
195
196  // Put the windows in the mru_windows list at the head, if it's available.
197  if (mru_windows) {
198    // Iterate through the list backwards, so that we can move each window to
199    // the front of the windows list as we find them.
200    for (std::list<aura::Window*>::const_reverse_iterator ix =
201         mru_windows->rbegin();
202         ix != mru_windows->rend(); ++ix) {
203      WindowCycleList::WindowList::iterator window =
204          std::find(windows.begin(), windows.end(), *ix);
205      if (window != windows.end()) {
206        windows.erase(window);
207        windows.push_back(*ix);
208      }
209    }
210  }
211
212  // Window cycling expects the topmost window at the front of the list.
213  if (!top_most_at_end)
214    std::reverse(windows.begin(), windows.end());
215
216  return windows;
217}
218
219void WindowCycleController::OnRootWindowAdded(aura::RootWindow* root_window) {
220  for (size_t i = 0; i < arraysize(kContainerIds); ++i) {
221    aura::Window* container =
222        Shell::GetContainer(root_window, kContainerIds[i]);
223    container->AddObserver(this);
224  }
225
226  aura::Window* default_container =
227      Shell::GetContainer(root_window,
228                          internal::kShellWindowId_DefaultContainer);
229  for (size_t i = 0; i < default_container->children().size(); ++i) {
230    aura::Window* workspace_window = default_container->children()[i];
231    DCHECK_EQ(internal::kShellWindowId_WorkspaceContainer,
232              workspace_window->id());
233    workspace_window->AddObserver(this);
234  }
235}
236
237//////////////////////////////////////////////////////////////////////////////
238// WindowCycleController, private:
239
240void WindowCycleController::StartCycling() {
241  windows_.reset(new WindowCycleList(BuildWindowList(&mru_windows_, false)));
242}
243
244void WindowCycleController::Step(Direction direction) {
245  DCHECK(windows_.get());
246  windows_->Step(direction == FORWARD ? WindowCycleList::FORWARD :
247                 WindowCycleList::BACKWARD);
248}
249
250void WindowCycleController::StopCycling() {
251  windows_.reset();
252  // Remove our key event filter.
253  if (event_handler_) {
254    Shell::GetInstance()->RemovePreTargetHandler(event_handler_.get());
255    event_handler_.reset();
256  }
257
258  // Add the currently focused window to the MRU list
259  aura::Window* active_window = wm::GetActiveWindow();
260  mru_windows_.remove(active_window);
261  mru_windows_.push_front(active_window);
262}
263
264// static
265bool WindowCycleController::IsTrackedContainer(aura::Window* window) {
266  if (!window)
267    return false;
268  for (size_t i = 0; i < arraysize(kContainerIds); ++i) {
269    if (window->id() == kContainerIds[i]) {
270      return true;
271    }
272  }
273  return window->id() == internal::kShellWindowId_WorkspaceContainer;
274}
275
276void WindowCycleController::InstallEventFilter() {
277  event_handler_.reset(new WindowCycleEventFilter());
278  Shell::GetInstance()->AddPreTargetHandler(event_handler_.get());
279}
280
281void WindowCycleController::OnWindowActivated(aura::Window* gained_active,
282                                              aura::Window* lost_active) {
283  if (gained_active && !IsCycling() &&
284      IsTrackedContainer(gained_active->parent())) {
285    mru_windows_.remove(gained_active);
286    mru_windows_.push_front(gained_active);
287  }
288}
289
290void WindowCycleController::OnWindowAdded(aura::Window* window) {
291  if (window->id() == internal::kShellWindowId_WorkspaceContainer)
292    window->AddObserver(this);
293}
294
295void WindowCycleController::OnWillRemoveWindow(aura::Window* window) {
296  mru_windows_.remove(window);
297  if (window->id() == internal::kShellWindowId_WorkspaceContainer)
298    window->RemoveObserver(this);
299}
300
301void WindowCycleController::OnWindowDestroying(aura::Window* window) {
302  window->RemoveObserver(this);
303}
304
305}  // namespace ash
306