summaryrefslogtreecommitdiff
path: root/chromium/ash/wm/overview/window_selector.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ash/wm/overview/window_selector.cc')
-rw-r--r--chromium/ash/wm/overview/window_selector.cc521
1 files changed, 521 insertions, 0 deletions
diff --git a/chromium/ash/wm/overview/window_selector.cc b/chromium/ash/wm/overview/window_selector.cc
new file mode 100644
index 00000000000..f4d8bd08f69
--- /dev/null
+++ b/chromium/ash/wm/overview/window_selector.cc
@@ -0,0 +1,521 @@
+// 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 selectable window.
+struct WindowSelectorItemComparator
+ : public std::unary_function<WindowSelectorItem*, bool> {
+ explicit WindowSelectorItemComparator(const aura::Window* window)
+ : window_(window) {
+ }
+
+ bool operator()(WindowSelectorItem* window) const {
+ return window->HasSelectableWindow(window_);
+ }
+
+ const aura::Window* window_;
+};
+
+// A comparator for locating a selectable window given a targeted window.
+struct WindowSelectorItemTargetComparator
+ : public std::unary_function<WindowSelectorItem*, bool> {
+ explicit WindowSelectorItemTargetComparator(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) {
+ aura::Window* container = Shell::GetContainer(*iter,
+ kSwitchableWindowContainerIds[i]);
+ container->AddObserver(this);
+ observed_windows_.insert(container);
+ }
+ }
+
+ if (mode == WindowSelector::CYCLE) {
+ cycle_start_time_ = base::Time::Now();
+ 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();
+ window_overview_.reset();
+ // Clearing the window list resets the ignored_by_shelf flag on the windows.
+ windows_.clear();
+ UpdateShelfVisibility();
+
+ if (!cycle_start_time_.is_null()) {
+ UMA_HISTOGRAM_MEDIUM_TIMES("Ash.WindowSelector.CycleTime",
+ base::Time::Now() - cycle_start_time_);
+ }
+}
+
+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(),
+ WindowSelectorItemTargetComparator(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) {
+ // window is one of a container, the restore_focus_window and/or
+ // one of the selectable windows in overview.
+ ScopedVector<WindowSelectorItem>::iterator iter =
+ std::find_if(windows_.begin(), windows_.end(),
+ WindowSelectorItemComparator(window));
+ window->RemoveObserver(this);
+ observed_windows_.erase(window);
+ if (window == restore_focus_window_)
+ restore_focus_window_ = NULL;
+ if (iter == windows_.end())
+ return;
+
+ (*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(),
+ WindowSelectorItemTargetComparator(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