| // Copyright 2019 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/desks/desk.h" |
| |
| #include <utility> |
| |
| #include "ash/shell.h" |
| #include "ash/wm/mru_window_tracker.h" |
| #include "ash/wm/window_transient_descendant_iterator.h" |
| #include "ash/wm/window_util.h" |
| |
| namespace ash { |
| |
| class DeskContainerObserver : public aura::WindowObserver { |
| public: |
| DeskContainerObserver(Desk* owner, aura::Window* container) |
| : owner_(owner), container_(container) { |
| DCHECK_EQ(container_->id(), owner_->container_id()); |
| container->AddObserver(this); |
| } |
| |
| ~DeskContainerObserver() override { container_->RemoveObserver(this); } |
| |
| // aura::WindowObserver: |
| void OnWindowAdded(aura::Window* new_window) override { |
| // TODO(afakhry): Overview mode creates a new widget for each window under |
| // the same parent for the CaptionContainerView. We will be notified with |
| // this window addition here. Ignore this window. |
| owner_->AddWindowToDesk(new_window); |
| } |
| |
| void OnWindowDestroyed(aura::Window* window) override { |
| // We should never get here. We should be notified in |
| // `OnRootWindowClosing()` before the child containers of the root window |
| // are destroyed, and this object should have already been destroyed. |
| NOTREACHED(); |
| } |
| |
| private: |
| Desk* const owner_; |
| aura::Window* const container_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DeskContainerObserver); |
| }; |
| |
| // ----------------------------------------------------------------------------- |
| // Desk: |
| |
| Desk::Desk(int associated_container_id) |
| : container_id_(associated_container_id) { |
| // For the very first default desk added during initialization, there won't be |
| // any root windows yet. That's OK, OnRootWindowAdded() will be called |
| // explicitly by the RootWindowController when they're initialized. |
| for (aura::Window* root : Shell::GetAllRootWindows()) |
| OnRootWindowAdded(root); |
| } |
| |
| Desk::~Desk() { |
| DCHECK(windows_.empty()) << "DesksController should remove my windows first."; |
| } |
| |
| void Desk::OnRootWindowAdded(aura::Window* root) { |
| DCHECK(!roots_to_containers_observers_.count(root)); |
| |
| aura::Window* desk_container = root->GetChildById(container_id_); |
| DCHECK(desk_container); |
| |
| auto container_observer = |
| std::make_unique<DeskContainerObserver>(this, desk_container); |
| roots_to_containers_observers_.emplace(root, std::move(container_observer)); |
| } |
| |
| void Desk::OnRootWindowClosing(aura::Window* root) { |
| const size_t count = roots_to_containers_observers_.erase(root); |
| DCHECK(count); |
| } |
| |
| void Desk::AddWindowToDesk(aura::Window* window) { |
| if (windows_.count(window)) |
| return; |
| |
| for (auto* transient_window : wm::GetTransientTreeIterator(window)) { |
| const auto result = windows_.emplace(transient_window); |
| DCHECK(result.second); |
| transient_window->AddObserver(this); |
| } |
| } |
| |
| void Desk::Activate() { |
| is_active_ = true; |
| |
| // Show the associated containers on all roots. |
| for (aura::Window* root : Shell::GetAllRootWindows()) |
| root->GetChildById(container_id_)->Show(); |
| |
| if (windows_.empty()) |
| return; |
| |
| // Activate the window on this desk that was most recently used right before |
| // the user switched to another desk, so as not to break the user's workflow. |
| for (auto* window : |
| Shell::Get()->mru_window_tracker()->BuildMruWindowList()) { |
| if (windows_.contains(window)) { |
| wm::ActivateWindow(window); |
| return; |
| } |
| } |
| } |
| |
| void Desk::Deactivate() { |
| auto* active_window = wm::GetActiveWindow(); |
| |
| // Hide the associated containers on all roots. |
| for (aura::Window* root : Shell::GetAllRootWindows()) |
| root->GetChildById(container_id_)->Hide(); |
| |
| // Deactivate the active window (if any) after this desk's associated |
| // containers have been hidden. This is to prevent the focus controller from |
| // activating another window on the same desk when the active window loses |
| // focus. |
| if (active_window) { |
| DCHECK(windows_.contains(active_window)); |
| wm::DeactivateWindow(active_window); |
| } |
| |
| is_active_ = false; |
| } |
| |
| void Desk::MoveWindowsToDesk(Desk* target_desk) { |
| DCHECK(target_desk); |
| |
| for (auto* window : windows_) { |
| window->RemoveObserver(this); |
| |
| // Add the window to the target desk before reparenting such that when the |
| // target desk's DeskContainerObserver notices this reparenting and calls |
| // AddWindowToDesk(), the window had already been added and there's no need |
| // to iterate over its transient hierarchy. |
| target_desk->windows_.emplace(window); |
| window->AddObserver(target_desk); |
| |
| // Reparent windows to the target desk's container in the same root window. |
| // Note that `windows_` may contain transient children, which may not have |
| // the same parent as the source desk's container. So, only reparent the |
| // windows which are direct children of the source desks' container. |
| // TODO(afakhry): Check if this is necessary. |
| aura::Window* root = window->GetRootWindow(); |
| aura::Window* source_container = GetDeskContainerForRoot(root); |
| aura::Window* target_container = target_desk->GetDeskContainerForRoot(root); |
| if (window->parent() == source_container) |
| target_container->AddChild(window); |
| } |
| |
| windows_.clear(); |
| } |
| |
| aura::Window* Desk::GetDeskContainerForRoot(aura::Window* root) const { |
| DCHECK(root); |
| |
| return root->GetChildById(container_id_); |
| } |
| |
| void Desk::OnWindowDestroyed(aura::Window* window) { |
| const size_t count = windows_.erase(window); |
| DCHECK(count); |
| } |
| |
| } // namespace ash |