blob: 79d428f2040cc170a41cd8b9b93f2f734b1d9ea0 [file] [log] [blame]
// Copyright 2013 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 "ash/wm/overview/window_selector.h"
#include <algorithm>
#include "ash/ash_switches.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/window_overview.h"
#include "ash/wm/overview/window_selector_delegate.h"
#include "ash/wm/overview/window_selector_panels.h"
#include "ash/wm/overview/window_selector_window.h"
#include "ash/wm/window_state.h"
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/timer/timer.h"
#include "ui/aura/client/activation_client.h"
#include "ui/aura/client/focus_client.h"
#include "ui/aura/root_window.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/events/event.h"
#include "ui/events/event_handler.h"
namespace ash {
namespace {
// The time from when the user pressed alt+tab while still holding alt before
// overview is engaged.
const int kOverviewDelayOnCycleMilliseconds = 10000;
// If the delay before overview is less than or equal to this threshold the
// initial monitor is used for multi-display overview, otherwise the monitor
// of the currently selected window is used.
const int kOverviewDelayInitialMonitorThreshold = 100;
// The maximum amount of time allowed for the delay before overview on cycling.
// If the specified time exceeds this the timer will not be started.
const int kMaxOverviewDelayOnCycleMilliseconds = 10000;
int GetOverviewDelayOnCycleMilliseconds() {
static int value = -1;
if (value == -1) {
value = kOverviewDelayOnCycleMilliseconds;
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAshOverviewDelayOnAltTab)) {
if (!base::StringToInt(CommandLine::ForCurrentProcess()->
GetSwitchValueASCII(switches::kAshOverviewDelayOnAltTab), &value)) {
LOG(ERROR) << "Expected int value for "
<< switches::kAshOverviewDelayOnAltTab;
}
}
}
return value;
}
// A comparator for locating a given target window.
struct WindowSelectorItemComparator
: public std::unary_function<WindowSelectorItem*, bool> {
explicit WindowSelectorItemComparator(const aura::Window* target_window)
: target(target_window) {
}
bool operator()(WindowSelectorItem* window) const {
return window->TargetedWindow(target) != NULL;
}
const aura::Window* target;
};
// A comparator for locating a selector item for a given root.
struct WindowSelectorItemForRoot
: public std::unary_function<WindowSelectorItem*, bool> {
explicit WindowSelectorItemForRoot(const aura::Window* root)
: root_window(root) {
}
bool operator()(WindowSelectorItem* item) const {
return item->GetRootWindow() == root_window;
}
const aura::Window* root_window;
};
// Filter to watch for the termination of a keyboard gesture to cycle through
// multiple windows.
class WindowSelectorEventFilter : public ui::EventHandler {
public:
WindowSelectorEventFilter(WindowSelector* selector);
virtual ~WindowSelectorEventFilter();
// Overridden from ui::EventHandler:
virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
private:
// A weak pointer to the WindowSelector which owns this instance.
WindowSelector* selector_;
DISALLOW_COPY_AND_ASSIGN(WindowSelectorEventFilter);
};
// Watch for all keyboard events by filtering the root window.
WindowSelectorEventFilter::WindowSelectorEventFilter(WindowSelector* selector)
: selector_(selector) {
Shell::GetInstance()->AddPreTargetHandler(this);
}
WindowSelectorEventFilter::~WindowSelectorEventFilter() {
Shell::GetInstance()->RemovePreTargetHandler(this);
}
void WindowSelectorEventFilter::OnKeyEvent(ui::KeyEvent* event) {
// Views uses VKEY_MENU for both left and right Alt keys.
if (event->key_code() == ui::VKEY_MENU &&
event->type() == ui::ET_KEY_RELEASED) {
selector_->SelectWindow();
// Warning: |this| will be deleted from here on.
}
}
// Triggers a shelf visibility update on all root window controllers.
void UpdateShelfVisibility() {
Shell::RootWindowControllerList root_window_controllers =
Shell::GetInstance()->GetAllRootWindowControllers();
for (Shell::RootWindowControllerList::iterator iter =
root_window_controllers.begin();
iter != root_window_controllers.end(); ++iter) {
(*iter)->UpdateShelfVisibility();
}
}
// Returns the window immediately below |window| in the current container.
aura::Window* GetWindowBelow(aura::Window* window) {
aura::Window* parent = window->parent();
if (!parent)
return NULL;
aura::Window* below = NULL;
for (aura::Window::Windows::const_iterator iter = parent->children().begin();
iter != parent->children().end(); ++iter) {
if (*iter == window)
return below;
below = *iter;
}
NOTREACHED();
return NULL;
}
} // namespace
// This class restores and moves a window to the front of the stacking order for
// the duration of the class's scope.
class ScopedShowWindow : public aura::WindowObserver {
public:
ScopedShowWindow();
virtual ~ScopedShowWindow();
// Show |window| at the top of the stacking order.
void Show(aura::Window* window);
// Cancel restoring the window on going out of scope.
void CancelRestore();
aura::Window* window() { return window_; }
// aura::WindowObserver:
virtual void OnWillRemoveWindow(aura::Window* window) OVERRIDE;
private:
// The window being shown.
aura::Window* window_;
// The window immediately below where window_ belongs.
aura::Window* stack_window_above_;
// If true, minimize window_ on going out of scope.
bool minimized_;
DISALLOW_COPY_AND_ASSIGN(ScopedShowWindow);
};
ScopedShowWindow::ScopedShowWindow()
: window_(NULL),
stack_window_above_(NULL),
minimized_(false) {
}
void ScopedShowWindow::Show(aura::Window* window) {
DCHECK(!window_);
window_ = window;
stack_window_above_ = GetWindowBelow(window);
minimized_ = wm::GetWindowState(window)->IsMinimized();
window_->parent()->AddObserver(this);
window_->Show();
wm::GetWindowState(window_)->Activate();
}
ScopedShowWindow::~ScopedShowWindow() {
if (window_) {
window_->parent()->RemoveObserver(this);
// Restore window's stacking position.
if (stack_window_above_)
window_->parent()->StackChildAbove(window_, stack_window_above_);
else
window_->parent()->StackChildAtBottom(window_);
// Restore minimized state.
if (minimized_)
wm::GetWindowState(window_)->Minimize();
}
}
void ScopedShowWindow::CancelRestore() {
if (!window_)
return;
window_->parent()->RemoveObserver(this);
window_ = stack_window_above_ = NULL;
}
void ScopedShowWindow::OnWillRemoveWindow(aura::Window* window) {
if (window == window_) {
CancelRestore();
} else if (window == stack_window_above_) {
// If the window this window was above is removed, use the next window down
// as the restore marker.
stack_window_above_ = GetWindowBelow(stack_window_above_);
}
}
WindowSelector::WindowSelector(const WindowList& windows,
WindowSelector::Mode mode,
WindowSelectorDelegate* delegate)
: mode_(mode),
timer_enabled_(GetOverviewDelayOnCycleMilliseconds() <
kMaxOverviewDelayOnCycleMilliseconds),
start_overview_timer_(FROM_HERE,
base::TimeDelta::FromMilliseconds(
GetOverviewDelayOnCycleMilliseconds()),
this, &WindowSelector::StartOverview),
delegate_(delegate),
selected_window_(0),
restore_focus_window_(aura::client::GetFocusClient(
Shell::GetPrimaryRootWindow())->GetFocusedWindow()),
ignore_activations_(false) {
DCHECK(delegate_);
if (restore_focus_window_)
restore_focus_window_->AddObserver(this);
std::vector<WindowSelectorPanels*> panels_items;
for (size_t i = 0; i < windows.size(); ++i) {
WindowSelectorItem* item = NULL;
if (windows[i] != restore_focus_window_)
windows[i]->AddObserver(this);
observed_windows_.insert(windows[i]);
if (windows[i]->type() == aura::client::WINDOW_TYPE_PANEL &&
wm::GetWindowState(windows[i])->panel_attached()) {
// Attached panel windows are grouped into a single overview item per
// root window (display).
std::vector<WindowSelectorPanels*>::iterator iter =
std::find_if(panels_items.begin(), panels_items.end(),
WindowSelectorItemForRoot(windows[i]->GetRootWindow()));
WindowSelectorPanels* panels_item = NULL;
if (iter == panels_items.end()) {
panels_item = new WindowSelectorPanels();
panels_items.push_back(panels_item);
windows_.push_back(panels_item);
} else {
panels_item = *iter;
}
panels_item->AddWindow(windows[i]);
item = panels_item;
} else {
item = new WindowSelectorWindow(windows[i]);
windows_.push_back(item);
}
// Verify that the window has been added to an item in overview.
CHECK(item->TargetedWindow(windows[i]));
}
UMA_HISTOGRAM_COUNTS_100("Ash.WindowSelector.Items", windows_.size());
// Observe window activations and switchable containers on all root windows
// for newly created windows during overview.
Shell::GetInstance()->activation_client()->AddObserver(this);
aura::Window::Windows root_windows = Shell::GetAllRootWindows();
for (aura::Window::Windows::const_iterator iter = root_windows.begin();
iter != root_windows.end(); ++iter) {
for (size_t i = 0; i < kSwitchableWindowContainerIdsLength; ++i) {
Shell::GetContainer(*iter,
kSwitchableWindowContainerIds[i])->AddObserver(this);
}
}
if (mode == WindowSelector::CYCLE) {
event_handler_.reset(new WindowSelectorEventFilter(this));
if (timer_enabled_)
start_overview_timer_.Reset();
} else {
StartOverview();
}
}
WindowSelector::~WindowSelector() {
ResetFocusRestoreWindow(true);
for (std::set<aura::Window*>::iterator iter = observed_windows_.begin();
iter != observed_windows_.end(); ++iter) {
(*iter)->RemoveObserver(this);
}
Shell::GetInstance()->activation_client()->RemoveObserver(this);
aura::Window::Windows root_windows = Shell::GetAllRootWindows();
for (aura::Window::Windows::const_iterator iter = root_windows.begin();
iter != root_windows.end(); ++iter) {
for (size_t i = 0; i < kSwitchableWindowContainerIdsLength; ++i) {
Shell::GetContainer(*iter,
kSwitchableWindowContainerIds[i])->RemoveObserver(this);
}
}
window_overview_.reset();
// Clearing the window list resets the ignored_by_shelf flag on the windows.
windows_.clear();
UpdateShelfVisibility();
}
void WindowSelector::Step(WindowSelector::Direction direction) {
DCHECK(!windows_.empty());
// Upgrade to CYCLE mode if currently in OVERVIEW mode.
if (mode_ != CYCLE) {
event_handler_.reset(new WindowSelectorEventFilter(this));
DCHECK(window_overview_);
// Set the initial selection window to animate to the new selection.
window_overview_->SetSelection(selected_window_);
window_overview_->MoveToSingleRootWindow(
windows_[selected_window_]->GetRootWindow());
mode_ = CYCLE;
}
selected_window_ = (selected_window_ + windows_.size() +
(direction == WindowSelector::FORWARD ? 1 : -1)) % windows_.size();
if (window_overview_) {
window_overview_->SetSelection(selected_window_);
} else {
base::AutoReset<bool> restoring_focus(&ignore_activations_, true);
showing_window_.reset(new ScopedShowWindow);
showing_window_->Show(windows_[selected_window_]->SelectionWindow());
if (timer_enabled_)
start_overview_timer_.Reset();
}
}
void WindowSelector::SelectWindow() {
SelectWindow(windows_[selected_window_]->SelectionWindow());
}
void WindowSelector::SelectWindow(aura::Window* window) {
ResetFocusRestoreWindow(false);
if (showing_window_ && showing_window_->window() == window)
showing_window_->CancelRestore();
ScopedVector<WindowSelectorItem>::iterator iter =
std::find_if(windows_.begin(), windows_.end(),
WindowSelectorItemComparator(window));
DCHECK(iter != windows_.end());
// The selected window should not be minimized when window selection is
// ended.
(*iter)->RestoreWindowOnExit(window);
delegate_->OnWindowSelected(window);
}
void WindowSelector::CancelSelection() {
delegate_->OnSelectionCanceled();
}
void WindowSelector::OnWindowAdded(aura::Window* new_window) {
if (new_window->type() != aura::client::WINDOW_TYPE_NORMAL &&
new_window->type() != aura::client::WINDOW_TYPE_PANEL) {
return;
}
for (size_t i = 0; i < kSwitchableWindowContainerIdsLength; ++i) {
if (new_window->parent()->id() == kSwitchableWindowContainerIds[i] &&
!new_window->transient_parent()) {
// The new window is in one of the switchable containers, abort overview.
CancelSelection();
return;
}
}
}
void WindowSelector::OnWindowDestroying(aura::Window* window) {
ScopedVector<WindowSelectorItem>::iterator iter =
std::find_if(windows_.begin(), windows_.end(),
WindowSelectorItemComparator(window));
DCHECK(window == restore_focus_window_ || iter != windows_.end());
window->RemoveObserver(this);
if (window == restore_focus_window_)
restore_focus_window_ = NULL;
if (iter == windows_.end()) {
CHECK(std::find(observed_windows_.begin(),
observed_windows_.end(), window) == observed_windows_.end());
return;
}
observed_windows_.erase(window);
(*iter)->RemoveWindow(window);
// If there are still windows in this selector entry then the overview is
// still active and the active selection remains the same.
if (!(*iter)->empty())
return;
size_t deleted_index = iter - windows_.begin();
windows_.erase(iter);
if (windows_.empty()) {
CancelSelection();
return;
}
if (window_overview_)
window_overview_->OnWindowsChanged();
if (mode_ == CYCLE && selected_window_ >= deleted_index) {
if (selected_window_ > deleted_index)
selected_window_--;
selected_window_ = selected_window_ % windows_.size();
if (window_overview_)
window_overview_->SetSelection(selected_window_);
}
}
void WindowSelector::OnWindowBoundsChanged(aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds) {
if (!window_overview_)
return;
ScopedVector<WindowSelectorItem>::iterator iter =
std::find_if(windows_.begin(), windows_.end(),
WindowSelectorItemComparator(window));
DCHECK(window == restore_focus_window_ || iter != windows_.end());
if (iter == windows_.end())
return;
// Immediately finish any active bounds animation.
window->layer()->GetAnimator()->StopAnimatingProperty(
ui::LayerAnimationElement::BOUNDS);
// Recompute the transform for the window.
(*iter)->RecomputeWindowTransforms();
}
void WindowSelector::OnWindowActivated(aura::Window* gained_active,
aura::Window* lost_active) {
if (ignore_activations_ || !gained_active)
return;
// Don't restore focus on exit if a window was just activated.
ResetFocusRestoreWindow(false);
CancelSelection();
}
void WindowSelector::OnAttemptToReactivateWindow(aura::Window* request_active,
aura::Window* actual_active) {
if (ignore_activations_)
return;
// Don't restore focus on exit if a window was just activated.
ResetFocusRestoreWindow(false);
CancelSelection();
}
void WindowSelector::StartOverview() {
DCHECK(!window_overview_);
// Remove focus from active window before entering overview.
aura::client::GetFocusClient(
Shell::GetPrimaryRootWindow())->FocusWindow(NULL);
aura::Window* overview_root = NULL;
if (mode_ == CYCLE) {
overview_root = GetOverviewDelayOnCycleMilliseconds() <=
kOverviewDelayInitialMonitorThreshold ?
windows_.front()->GetRootWindow() :
windows_[selected_window_]->GetRootWindow();
}
window_overview_.reset(new WindowOverview(this, &windows_, overview_root));
if (mode_ == CYCLE)
window_overview_->SetSelection(selected_window_);
UpdateShelfVisibility();
}
void WindowSelector::ResetFocusRestoreWindow(bool focus) {
if (!restore_focus_window_)
return;
if (focus) {
base::AutoReset<bool> restoring_focus(&ignore_activations_, true);
restore_focus_window_->Focus();
}
// If the window is in the observed_windows_ list it needs to continue to be
// observed.
if (observed_windows_.find(restore_focus_window_) ==
observed_windows_.end()) {
restore_focus_window_->RemoveObserver(this);
}
restore_focus_window_ = NULL;
}
} // namespace ash