// 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/shelf/shelf_window_watcher.h"
#include "ash/display/display_controller.h"
#include "ash/shelf/shelf_constants.h"
#include "ash/shelf/shelf_item_delegate_manager.h"
#include "ash/shelf/shelf_model.h"
#include "ash/shelf/shelf_util.h"
#include "ash/shelf/shelf_window_watcher_item_delegate.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/memory/scoped_ptr.h"
#include "ui/aura/window.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/screen.h"
#include "ui/wm/public/activation_client.h"
namespace {
// Sets ShelfItem property by using the value of |details|.
void SetShelfItemDetailsForShelfItem(ash::ShelfItem* item,
const ash::ShelfItemDetails& details) {
item->type = details.type;
if (details.image_resource_id != ash::kInvalidImageResourceID) {
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
item->image = *rb.GetImageSkiaNamed(details.image_resource_id);
}
}
// Returns true if |window| has a ShelfItem added by ShelfWindowWatcher.
bool HasShelfItemForWindow(aura::Window* window) {
if (ash::GetShelfItemDetailsForWindow(window) != NULL &&
ash::GetShelfIDForWindow(window) != ash::kInvalidShelfID)
return true;
return false;
}
// Returns true if |window| is in the process of being dragged.
bool IsDragging(aura::Window* window) {
return ash::wm::GetWindowState(window)->is_dragged();
}
} // namespace
namespace ash {
ShelfWindowWatcher::RootWindowObserver::RootWindowObserver(
ShelfWindowWatcher* window_watcher)
: window_watcher_(window_watcher) {
}
ShelfWindowWatcher::RootWindowObserver::~RootWindowObserver() {
}
void ShelfWindowWatcher::RootWindowObserver::OnWindowDestroying(
aura::Window* window) {
window_watcher_->OnRootWindowRemoved(window);
}
ShelfWindowWatcher::RemovedWindowObserver::RemovedWindowObserver(
ShelfWindowWatcher* window_watcher)
: window_watcher_(window_watcher) {
}
ShelfWindowWatcher::RemovedWindowObserver::~RemovedWindowObserver() {
}
void ShelfWindowWatcher::RemovedWindowObserver::OnWindowParentChanged(
aura::Window* window,
aura::Window* parent) {
// When |parent| is NULL, this |window| will be destroyed. In that case, its
// item will be removed at OnWindowDestroyed().
if (!parent)
return;
// When |parent| is changed from default container to docked container
// during the dragging, |window|'s item should not be removed because it will
// be re-parented to default container again after finishing the dragging.
// We don't need to check |parent| is default container because this observer
// is already removed from |window| when |window| is re-parented to default
// container.
if (IsDragging(window) && parent->id() == kShellWindowId_DockedContainer)
return;
// When |window| is re-parented to other containers or |window| is re-parented
// not to |docked_container| during the dragging, its item should be removed
// and stop observing this |window|.
window_watcher_->FinishObservingRemovedWindow(window);
}
void ShelfWindowWatcher::RemovedWindowObserver::OnWindowDestroyed(
aura::Window* window) {
DCHECK(HasShelfItemForWindow(window));
window_watcher_->FinishObservingRemovedWindow(window);
}
ShelfWindowWatcher::ShelfWindowWatcher(
ShelfModel* model,
ShelfItemDelegateManager* item_delegate_manager)
: model_(model),
item_delegate_manager_(item_delegate_manager),
root_window_observer_(this),
removed_window_observer_(this),
observed_windows_(this),
observed_root_windows_(&root_window_observer_),
observed_removed_windows_(&removed_window_observer_),
observed_activation_clients_(this) {
// We can't assume all RootWindows have the same ActivationClient.
// Add a RootWindow and its ActivationClient to the observed list.
aura::Window::Windows root_windows = Shell::GetAllRootWindows();
for (aura::Window::Windows::const_iterator it = root_windows.begin();
it != root_windows.end(); ++it)
OnRootWindowAdded(*it);
Shell::GetScreen()->AddObserver(this);
}
ShelfWindowWatcher::~ShelfWindowWatcher() {
Shell::GetScreen()->RemoveObserver(this);
}
void ShelfWindowWatcher::AddShelfItem(aura::Window* window) {
const ShelfItemDetails* item_details =
GetShelfItemDetailsForWindow(window);
ShelfItem item;
ShelfID id = model_->next_id();
item.status = wm::IsActiveWindow(window) ? STATUS_ACTIVE: STATUS_RUNNING;
SetShelfItemDetailsForShelfItem(&item, *item_details);
SetShelfIDForWindow(id, window);
scoped_ptr<ShelfItemDelegate> item_delegate(
new ShelfWindowWatcherItemDelegate(window, model_));
// |item_delegate| is owned by |item_delegate_manager_|.
item_delegate_manager_->SetShelfItemDelegate(id, item_delegate.Pass());
model_->Add(item);
}
void ShelfWindowWatcher::RemoveShelfItem(aura::Window* window) {
model_->RemoveItemAt(model_->ItemIndexByID(GetShelfIDForWindow(window)));
SetShelfIDForWindow(kInvalidShelfID, window);
}
void ShelfWindowWatcher::OnRootWindowAdded(aura::Window* root_window) {
// |observed_activation_clients_| can have the same ActivationClient multiple
// times - which would be handled by the |observed_activation_clients_|.
observed_activation_clients_.Add(
aura::client::GetActivationClient(root_window));
observed_root_windows_.Add(root_window);
aura::Window* default_container = Shell::GetContainer(
root_window,
kShellWindowId_DefaultContainer);
observed_windows_.Add(default_container);
for (size_t i = 0; i < default_container->children().size(); ++i)
observed_windows_.Add(default_container->children()[i]);
}
void ShelfWindowWatcher::OnRootWindowRemoved(aura::Window* root_window) {
observed_root_windows_.Remove(root_window);
observed_activation_clients_.Remove(
aura::client::GetActivationClient(root_window));
}
void ShelfWindowWatcher::UpdateShelfItemStatus(aura::Window* window,
bool is_active) {
int index = GetShelfItemIndexForWindow(window);
DCHECK_GE(index, 0);
ShelfItem item = model_->items()[index];
item.status = is_active ? STATUS_ACTIVE : STATUS_RUNNING;
model_->Set(index, item);
}
int ShelfWindowWatcher::GetShelfItemIndexForWindow(
aura::Window* window) const {
return model_->ItemIndexByID(GetShelfIDForWindow(window));
}
void ShelfWindowWatcher::StartObservingRemovedWindow(aura::Window* window) {
observed_removed_windows_.Add(window);
}
void ShelfWindowWatcher::FinishObservingRemovedWindow(aura::Window* window) {
observed_removed_windows_.Remove(window);
RemoveShelfItem(window);
}
void ShelfWindowWatcher::OnWindowActivated(aura::Window* gained_active,
aura::Window* lost_active) {
if (gained_active && HasShelfItemForWindow(gained_active))
UpdateShelfItemStatus(gained_active, true);
if (lost_active && HasShelfItemForWindow(lost_active))
UpdateShelfItemStatus(lost_active, false);
}
void ShelfWindowWatcher::OnWindowAdded(aura::Window* window) {
observed_windows_.Add(window);
if (observed_removed_windows_.IsObserving(window)) {
// When |window| is added and it is already observed by
// |dragged_window_observer_|, |window| already has its item.
DCHECK(HasShelfItemForWindow(window));
observed_removed_windows_.Remove(window);
return;
}
// Add ShelfItem if |window| already has a ShelfItemDetails when it is
// created. Don't make a new ShelfItem for the re-parented |window| that
// already has a ShelfItem.
if (GetShelfIDForWindow(window) == kInvalidShelfID &&
GetShelfItemDetailsForWindow(window))
AddShelfItem(window);
}
void ShelfWindowWatcher::OnWillRemoveWindow(aura::Window* window) {
// Remove a child window of default container.
if (observed_windows_.IsObserving(window))
observed_windows_.Remove(window);
// Don't remove |window| item immediately. Instead, defer handling of removing
// |window|'s item to RemovedWindowObserver because |window| could be added
// again to default container.
if (HasShelfItemForWindow(window))
StartObservingRemovedWindow(window);
}
void ShelfWindowWatcher::OnWindowDestroying(aura::Window* window) {
// Remove the default container.
if (observed_windows_.IsObserving(window))
observed_windows_.Remove(window);
}
void ShelfWindowWatcher::OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) {
if (key != kShelfItemDetailsKey)
return;
if (GetShelfItemDetailsForWindow(window) == NULL) {
// Removes ShelfItem for |window| when it has a ShelfItem.
if (reinterpret_cast<ShelfItemDetails*>(old) != NULL)
RemoveShelfItem(window);
return;
}
// When ShelfItemDetails is changed, update ShelfItem.
if (HasShelfItemForWindow(window)) {
int index = GetShelfItemIndexForWindow(window);
DCHECK_GE(index, 0);
ShelfItem item = model_->items()[index];
const ShelfItemDetails* details =
GetShelfItemDetailsForWindow(window);
SetShelfItemDetailsForShelfItem(&item, *details);
model_->Set(index, item);
return;
}
// Creates a new ShelfItem for |window|.
AddShelfItem(window);
}
void ShelfWindowWatcher::OnDisplayAdded(const gfx::Display& new_display) {
// Add a new RootWindow and its ActivationClient to observed list.
aura::Window* root_window = Shell::GetInstance()->display_controller()->
GetRootWindowForDisplayId(new_display.id());
// When the primary root window's display get removed, the existing root
// window is taken over by the new display and the observer is already set.
if (!observed_root_windows_.IsObserving(root_window))
OnRootWindowAdded(root_window);
}
void ShelfWindowWatcher::OnDisplayRemoved(const gfx::Display& old_display) {
// When this is called, RootWindow of |old_display| is already removed.
// Instead, we remove an observer from RootWindow and ActivationClient in the
// OnRootWindowDestroyed().
// Do nothing here.
}
void ShelfWindowWatcher::OnDisplayMetricsChanged(const gfx::Display&,
uint32_t) {
}
} // namespace ash