Index: ash/wm/workspace/workspace_manager2.cc |
diff --git a/ash/wm/workspace/workspace_manager2.cc b/ash/wm/workspace/workspace_manager2.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..385e346901004b7d8f6857681a84022587ca09f7 |
--- /dev/null |
+++ b/ash/wm/workspace/workspace_manager2.cc |
@@ -0,0 +1,522 @@ |
+// Copyright (c) 2012 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/workspace/workspace_manager2.h" |
+ |
+#include <algorithm> |
+#include <functional> |
+ |
+#include "ash/screen_ash.h" |
+#include "ash/shell.h" |
+#include "ash/shell_window_ids.h" |
+#include "ash/wm/always_on_top_controller.h" |
+#include "ash/wm/base_layout_manager.h" |
+#include "ash/wm/property_util.h" |
+#include "ash/wm/shelf_layout_manager.h" |
+#include "ash/wm/window_animations.h" |
+#include "ash/wm/window_properties.h" |
+#include "ash/wm/window_util.h" |
+#include "ash/wm/workspace/workspace2.h" |
+#include "base/auto_reset.h" |
+#include "base/logging.h" |
+#include "base/stl_util.h" |
+#include "base/stringprintf.h" |
+#include "ui/aura/client/aura_constants.h" |
+#include "ui/aura/env.h" |
+#include "ui/aura/root_window.h" |
+#include "ui/aura/window.h" |
+#include "ui/aura/window_property.h" |
+#include "ui/base/ui_base_types.h" |
+#include "ui/compositor/layer.h" |
+#include "ui/compositor/layer_animator.h" |
+#include "ui/compositor/scoped_layer_animation_settings.h" |
+#include "ui/gfx/screen.h" |
+#include "ui/gfx/transform.h" |
+ |
+DECLARE_WINDOW_PROPERTY_TYPE(ash::internal::Workspace2*); |
+ |
+using aura::Window; |
+ |
+namespace ash { |
+namespace internal { |
+ |
+DEFINE_WINDOW_PROPERTY_KEY(Workspace2*, kWorkspaceKey, NULL); |
+ |
+namespace { |
+ |
+// Changes the parent of |window| and all its transient children to |
+// |new_parent|. If |stack_beneach| is non-NULL all the windows are stacked |
+// beneath it. |
+void ReparentWindow(Window* window, |
+ Window* new_parent, |
+ Window* stack_beneath) { |
+ window->SetParent(new_parent); |
+ if (stack_beneath) |
+ new_parent->StackChildBelow(window, stack_beneath); |
+ for (size_t i = 0; i < window->transient_children().size(); ++i) |
+ ReparentWindow(window->transient_children()[i], new_parent, stack_beneath); |
+} |
+ |
+// Workspace ------------------------------------------------------------------- |
+ |
+// LayoutManager installed on the parent window of all the Workspace windows (eg |
+// |WorkspaceManager2::contents_view_|). |
+class WorkspaceManagerLayoutManager2 : public BaseLayoutManager { |
+ public: |
+ WorkspaceManagerLayoutManager2(Window* window) |
+ : BaseLayoutManager(window->GetRootWindow()), |
+ window_(window) { |
+ } |
+ virtual ~WorkspaceManagerLayoutManager2() {} |
+ |
+ // Overridden from BaseWorkspaceLayoutManager: |
+ virtual void OnWindowResized() OVERRIDE { |
+ for (size_t i = 0; i < window_->children().size(); ++i) |
+ window_->children()[i]->SetBounds(gfx::Rect(window_->bounds().size())); |
+ } |
+ virtual void OnWindowAddedToLayout(Window* child) OVERRIDE { |
+ // Only workspaces should be added as children. |
+ DCHECK_EQ(kShellWindowId_WorkspaceContainer, child->id()); |
+ child->SetBounds(gfx::Rect(window_->bounds().size())); |
+ } |
+ |
+ private: |
+ Window* window_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(WorkspaceManagerLayoutManager2); |
+}; |
+ |
+} // namespace |
+ |
+// WorkspaceLayoutManager ------------------------------------------------------ |
+ |
+// LayoutManager installed on the window each workspace contains. |
+class WorkspaceManager2::WorkspaceLayoutManager : public BaseLayoutManager { |
+ public: |
+ WorkspaceLayoutManager(aura::RootWindow* root_window, Workspace2* workspace) |
+ : BaseLayoutManager(root_window), |
+ workspace_(workspace) { |
+ } |
+ virtual ~WorkspaceLayoutManager() { |
+ } |
+ |
+ // Overridden from BaseWorkspaceLayoutManager: |
+ virtual void OnWindowAddedToLayout(Window* child) OVERRIDE { |
+ BaseLayoutManager::OnWindowAddedToLayout(child); |
+ child->SetProperty(kWorkspaceKey, workspace_); |
+ workspace_manager()->OnWindowAddedToWorkspace(workspace_, child); |
+ } |
+ |
+ virtual void OnWillRemoveWindowFromLayout(Window* child) OVERRIDE { |
+ BaseLayoutManager::OnWillRemoveWindowFromLayout(child); |
+ child->ClearProperty(kWorkspaceKey); |
+ } |
+ |
+ virtual void OnWindowRemovedFromLayout(Window* child) OVERRIDE { |
+ BaseLayoutManager::OnWindowRemovedFromLayout(child); |
+ workspace_manager()->OnWindowRemovedFromWorkspace(workspace_, child); |
+ } |
+ |
+ virtual void OnChildWindowVisibilityChanged(Window* child, |
+ bool visibile) OVERRIDE { |
+ BaseLayoutManager::OnChildWindowVisibilityChanged(child, visibile); |
+ workspace_manager()->OnWorkspaceChildWindowVisibilityChanged(workspace_, |
+ child); |
+ } |
+ |
+ virtual void SetChildBounds(Window* child, |
+ const gfx::Rect& requested_bounds) OVERRIDE { |
+ BaseLayoutManager::SetChildBounds(child, requested_bounds); |
+ workspace_manager()->OnWorkspaceWindowChildBoundsChanged(workspace_, child); |
+ } |
+ |
+ |
+ // Overriden from WindowObserver: |
+ virtual void OnWindowPropertyChanged(Window* window, |
+ const void* key, |
+ intptr_t old) { |
+ BaseLayoutManager::OnWindowPropertyChanged(window, key, old); |
+ |
+ if (key == aura::client::kAlwaysOnTopKey && |
+ window->GetProperty(aura::client::kAlwaysOnTopKey)) { |
+ internal::AlwaysOnTopController* controller = |
+ window->GetRootWindow()->GetProperty( |
+ internal::kAlwaysOnTopControllerKey); |
+ controller->GetContainer(window)->AddChild(window); |
+ } |
+ } |
+ |
+ protected: |
+ // Overriden from WindowObserver: |
+ virtual void ShowStateChanged(Window* window, |
+ ui::WindowShowState last_show_state) OVERRIDE { |
+ // NOTE: we can't use BaseLayoutManager::ShowStateChanged() as we need to |
+ // forward to WorkspaceManager before the window is hidden. |
+ if (wm::IsWindowMinimized(window)) { |
+ // Save the previous show state so that we can correctly restore it. |
+ window->SetProperty(internal::kRestoreShowStateKey, last_show_state); |
+ SetWindowVisibilityAnimationType( |
+ window, WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE); |
+ |
+ workspace_manager()->OnWorkspaceWindowShowStateChanged( |
+ workspace_, window, last_show_state); |
+ |
+ // Hide the window. |
+ window->Hide(); |
+ |
+ // Activate another window. |
+ if (wm::IsActiveWindow(window)) |
+ wm::DeactivateWindow(window); |
+ } else { |
+ if ((window->TargetVisibility() || |
+ last_show_state == ui::SHOW_STATE_MINIMIZED) && |
+ !window->layer()->visible()) { |
+ // The layer may be hidden if the window was previously minimized. Make |
+ // sure it's visible. |
+ window->Show(); |
+ } |
+ workspace_manager()->OnWorkspaceWindowShowStateChanged( |
+ workspace_, window, last_show_state); |
+ } |
+ } |
+ |
+ private: |
+ WorkspaceManager2* workspace_manager() { |
+ return workspace_->workspace_manager(); |
+ } |
+ |
+ Workspace2* workspace_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(WorkspaceLayoutManager); |
+}; |
+ |
+// WorkspaceManager2 ----------------------------------------------------------- |
+ |
+WorkspaceManager2::WorkspaceManager2(Window* contents_view) |
+ : contents_view_(contents_view), |
+ active_workspace_(NULL), |
+ grid_size_(0), |
+ shelf_(NULL), |
+ in_move_(false) { |
+ // Clobber any existing event filter. |
+ contents_view->SetEventFilter(NULL); |
+ // |contents_view| takes ownership of WorkspaceManagerLayoutManager2. |
+ contents_view->SetLayoutManager( |
+ new WorkspaceManagerLayoutManager2(contents_view)); |
+ active_workspace_ = CreateWorkspace(false); |
+ workspaces_.push_back(active_workspace_); |
+} |
+ |
+WorkspaceManager2::~WorkspaceManager2() { |
+ // Release the windows, they'll be destroyed when |contents_view_| is |
+ // destroyed. |
+ std::for_each(workspaces_.begin(), workspaces_.end(), |
+ std::mem_fun(&Workspace2::ReleaseWindow)); |
+ std::for_each(pending_workspaces_.begin(), pending_workspaces_.end(), |
+ std::mem_fun(&Workspace2::ReleaseWindow)); |
+ STLDeleteElements(&workspaces_); |
+ STLDeleteElements(&pending_workspaces_); |
+} |
+ |
+// static |
+bool WorkspaceManager2::IsMaximized(Window* window) { |
+ return wm::IsWindowFullscreen(window) || wm::IsWindowMaximized(window); |
+} |
+ |
+// static |
+bool WorkspaceManager2::WillRestoreMaximized(aura::Window* window) { |
+ if (!wm::IsWindowMinimized(window)) |
+ return false; |
+ |
+ ui::WindowShowState restore_state = |
+ window->GetProperty(internal::kRestoreShowStateKey); |
+ return restore_state == ui::SHOW_STATE_MAXIMIZED || |
+ restore_state == ui::SHOW_STATE_FULLSCREEN; |
+ |
+} |
+ |
+bool WorkspaceManager2::IsInMaximizedMode() const { |
+ return active_workspace_ && active_workspace_->is_maximized(); |
+} |
+ |
+void WorkspaceManager2::SetGridSize(int size) { |
+ grid_size_ = size; |
+ std::for_each(workspaces_.begin(), workspaces_.end(), |
+ std::bind2nd(std::mem_fun(&Workspace2::SetGridSize), |
+ grid_size_)); |
+ std::for_each(pending_workspaces_.begin(), pending_workspaces_.end(), |
+ std::bind2nd(std::mem_fun(&Workspace2::SetGridSize), |
+ grid_size_)); |
+} |
+ |
+int WorkspaceManager2::GetGridSize() const { |
+ return grid_size_; |
+} |
+ |
+WorkspaceWindowState WorkspaceManager2::GetWindowState() const { |
+ if (!shelf_) |
+ return WORKSPACE_WINDOW_STATE_DEFAULT; |
+ |
+ gfx::Rect shelf_bounds(shelf_->GetIdealBounds()); |
+ const Window::Windows& windows(active_workspace_->window()->children()); |
+ bool window_overlaps_launcher = false; |
+ bool has_maximized_window = false; |
+ for (Window::Windows::const_iterator i = windows.begin(); |
+ i != windows.end(); ++i) { |
+ ui::Layer* layer = (*i)->layer(); |
+ if (!layer->GetTargetVisibility() || layer->GetTargetOpacity() == 0.0f) |
+ continue; |
+ if (wm::IsWindowMaximized(*i)) { |
+ // An untracked window may still be fullscreen so we keep iterating when |
+ // we hit a maximized window. |
+ has_maximized_window = true; |
+ } else if (wm::IsWindowFullscreen(*i)) { |
+ return WORKSPACE_WINDOW_STATE_FULL_SCREEN; |
+ } |
+ if (!window_overlaps_launcher && (*i)->bounds().Intersects(shelf_bounds)) |
+ window_overlaps_launcher = true; |
+ } |
+ if (has_maximized_window) |
+ return WORKSPACE_WINDOW_STATE_MAXIMIZED; |
+ |
+ return window_overlaps_launcher ? |
+ WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF : |
+ WORKSPACE_WINDOW_STATE_DEFAULT; |
+} |
+ |
+void WorkspaceManager2::SetShelf(ShelfLayoutManager* shelf) { |
+ shelf_ = shelf; |
+} |
+ |
+void WorkspaceManager2::SetActiveWorkspaceByWindow(Window* window) { |
+ Workspace2* workspace = FindBy(window); |
+ if (!workspace) |
+ return; |
+ |
+ if (workspace != active_workspace_) { |
+ // If the window persists across all workspaces, move it to the current |
+ // workspace. |
+ if (GetPersistsAcrossAllWorkspaces(window) && !IsMaximized(window)) |
+ ReparentWindow(window, active_workspace_->window(), NULL); |
+ else |
+ SetActiveWorkspace(workspace); |
+ } |
+ UpdateShelfVisibility(); |
+} |
+ |
+Window* WorkspaceManager2::GetParentForNewWindow(Window* window) { |
+ if (window->transient_parent()) { |
+ DCHECK(contents_view_->Contains(window->transient_parent())); |
+ DCHECK(!IsMaximized(window)); |
+ return window->transient_parent()->parent(); |
+ } |
+ |
+ if (IsMaximized(window)) { |
+ // Wait for the window to be made active before showing the workspace. |
+ Workspace2* workspace = CreateWorkspace(false); |
+ pending_workspaces_.insert(workspace); |
+ return workspace->window(); |
+ } |
+ |
+ if (!GetTrackedByWorkspace(window) || GetPersistsAcrossAllWorkspaces(window)) |
+ return active_workspace_->window(); |
+ |
+ return desktop_workspace()->window(); |
+} |
+ |
+void WorkspaceManager2::UpdateShelfVisibility() { |
+ if (shelf_) |
+ shelf_->UpdateVisibilityState(); |
+} |
+ |
+Workspace2* WorkspaceManager2::FindBy(Window* window) const { |
+ while (window) { |
+ Workspace2* workspace = window->GetProperty(kWorkspaceKey); |
+ if (workspace) |
+ return workspace; |
+ window = window->transient_parent(); |
+ } |
+ return NULL; |
+} |
+ |
+void WorkspaceManager2::SetActiveWorkspace(Workspace2* workspace) { |
+ DCHECK(workspace); |
+ if (active_workspace_ == workspace) |
+ return; |
+ |
+ // TODO: sort out animations. |
+ |
+ pending_workspaces_.erase(workspace); |
+ |
+ // Adjust the z-order. No need to adjust the z-order for the desktop since |
+ // it always stays at the bottom. |
+ if (workspace != desktop_workspace()) { |
+ if (FindWorkspace(workspace) == workspaces_.end()) { |
+ contents_view_->StackChildAbove(workspace->window(), |
+ workspaces_.back()->window()); |
+ workspaces_.push_back(workspace); |
+ } |
+ } |
+ |
+ active_workspace_ = workspace; |
+ |
+ for (size_t i = 0; i < workspaces_.size(); ++i) { |
+ if (workspaces_[i] == active_workspace_) |
+ workspaces_[i]->window()->Show(); |
+ else |
+ workspaces_[i]->window()->Hide(); |
+ } |
+} |
+ |
+WorkspaceManager2::Workspaces::iterator |
+WorkspaceManager2::FindWorkspace(Workspace2* workspace) { |
+ return std::find(workspaces_.begin(), workspaces_.end(), workspace); |
+} |
+ |
+Workspace2* WorkspaceManager2::CreateWorkspace(bool maximized) { |
+ Workspace2* workspace = new Workspace2(this, contents_view_, maximized); |
+ workspace->SetGridSize(grid_size_); |
+ workspace->window()->SetLayoutManager( |
+ new WorkspaceLayoutManager(contents_view_->GetRootWindow(), workspace)); |
+ return workspace; |
+} |
+ |
+void WorkspaceManager2::MoveWorkspaceToPendingOrDelete( |
+ Workspace2* workspace, |
+ Window* stack_beneath) { |
+ // We're all ready moving windows. |
+ if (in_move_) |
+ return; |
+ |
+ DCHECK_NE(desktop_workspace(), workspace); |
+ |
+ if (workspace == active_workspace_) |
+ SelectNextWorkspace(); |
+ |
+ AutoReset<bool> setter(&in_move_, true); |
+ |
+ // Move all non-maximized/fullscreen windows to the desktop. |
+ { |
+ // Build the list of windows to move. Exclude maximized/fullscreen and |
+ // windows with transient parents. |
+ Window::Windows to_move; |
+ Window* w_window = workspace->window(); |
+ for (size_t i = 0; i < w_window->children().size(); ++i) { |
+ Window* child = w_window->children()[i]; |
+ if (!child->transient_parent() && !IsMaximized(child) && |
+ !WillRestoreMaximized(child)) { |
+ to_move.push_back(child); |
+ } |
+ } |
+ // Move the windows, but make sure the window is still a child of |w_window| |
+ // before moving (moving may cascade and cause other windows to move). |
+ for (size_t i = 0; i < to_move.size(); ++i) { |
+ if (std::find(w_window->children().begin(), w_window->children().end(), |
+ to_move[i]) != w_window->children().end()) { |
+ ReparentWindow(to_move[i], desktop_workspace()->window(), |
+ stack_beneath); |
+ } |
+ } |
+ } |
+ |
+ { |
+ Workspaces::iterator workspace_i(FindWorkspace(workspace)); |
+ if (workspace_i != workspaces_.end()) |
+ workspaces_.erase(workspace_i); |
+ } |
+ |
+ if (workspace->window()->children().empty()) { |
+ pending_workspaces_.erase(workspace); |
+ delete workspace->ReleaseWindow(); |
+ delete workspace; |
+ } else { |
+ pending_workspaces_.insert(workspace); |
+ } |
+} |
+ |
+void WorkspaceManager2::SelectNextWorkspace() { |
+ DCHECK_NE(active_workspace_, desktop_workspace()); |
+ |
+ Workspaces::const_iterator workspace_i(FindWorkspace(active_workspace_)); |
+ Workspaces::const_iterator next_workspace_i(workspace_i + 1); |
+ if (next_workspace_i != workspaces_.end()) |
+ SetActiveWorkspace(*next_workspace_i); |
+ else |
+ SetActiveWorkspace(*(workspace_i - 1)); |
+ UpdateShelfVisibility(); |
+} |
+ |
+void WorkspaceManager2::OnWindowAddedToWorkspace(Workspace2* workspace, |
+ Window* child) { |
+ // Do nothing (other than updating shelf visibility) as the right parent was |
+ // chosen by way of GetParentForNewWindow() or we explicitly moved the window |
+ // to the workspace. |
+ if (workspace == active_workspace_) |
+ UpdateShelfVisibility(); |
+} |
+ |
+void WorkspaceManager2::OnWindowRemovedFromWorkspace(Workspace2* workspace, |
+ Window* child) { |
+ if (workspace->ShouldMoveToPending()) |
+ MoveWorkspaceToPendingOrDelete(workspace, NULL); |
+} |
+ |
+void WorkspaceManager2::OnWorkspaceChildWindowVisibilityChanged( |
+ Workspace2* workspace, |
+ Window* child) { |
+ if (workspace->ShouldMoveToPending()) |
+ MoveWorkspaceToPendingOrDelete(workspace, NULL); |
+} |
+ |
+void WorkspaceManager2::OnWorkspaceWindowChildBoundsChanged( |
+ Workspace2* workspace, |
+ Window* child) { |
+} |
+ |
+void WorkspaceManager2::OnWorkspaceWindowShowStateChanged( |
+ Workspace2* workspace, |
+ Window* child, |
+ ui::WindowShowState last_show_state) { |
+ if (wm::IsWindowMinimized(child)) { |
+ if (workspace->ShouldMoveToPending()) |
+ MoveWorkspaceToPendingOrDelete(workspace, NULL); |
+ } else { |
+ // Here's the cases that need to be handled: |
+ // . More than one maximized window: move newly maximized window into |
+ // own workspace. |
+ // . One maximized window and not in a maximized workspace: move window |
+ // into own workspace. |
+ // . No maximized window and not in desktop: move to desktop and further |
+ // any existing windows are stacked beneath |child|. |
+ const bool is_active = wm::IsActiveWindow(child); |
+ Workspace2* new_workspace = NULL; |
+ const int max_count = workspace->GetNumMaximizedWindows(); |
+ if (max_count == 0) { |
+ if (workspace != desktop_workspace()) { |
+ { |
+ AutoReset<bool> setter(&in_move_, true); |
+ ReparentWindow(child, desktop_workspace()->window(), NULL); |
+ } |
+ MoveWorkspaceToPendingOrDelete(workspace, child); |
+ new_workspace = desktop_workspace(); |
+ } |
+ } else if (max_count == 1) { |
+ if (workspace == desktop_workspace()) { |
+ new_workspace = CreateWorkspace(true); |
+ pending_workspaces_.insert(new_workspace); |
+ ReparentWindow(child, new_workspace->window(), NULL); |
+ } |
+ } else { |
+ new_workspace = CreateWorkspace(true); |
+ pending_workspaces_.insert(new_workspace); |
+ ReparentWindow(child, new_workspace->window(), NULL); |
+ } |
+ if (is_active && new_workspace) |
+ SetActiveWorkspace(new_workspace); |
+ } |
+ UpdateShelfVisibility(); |
+} |
+ |
+} // namespace internal |
+} // namespace ash |