diff options
Diffstat (limited to 'chromium/ash/wm/overview/window_selector.cc')
-rw-r--r-- | chromium/ash/wm/overview/window_selector.cc | 521 |
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 |