blob: d3e614cde5424a4523eb332d12eaa9a2a32cbe9c [file] [log] [blame]
// 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 "chrome/browser/ui/panels/detached_panel_collection.h"
#include <algorithm>
#include "base/logging.h"
#include "chrome/browser/ui/panels/display_settings_provider.h"
#include "chrome/browser/ui/panels/panel_drag_controller.h"
#include "chrome/browser/ui/panels/panel_manager.h"
namespace {
// How much horizontal and vertical offset there is between newly opened
// detached panels.
const int kPanelTilePixels = 10;
// When the stacking mode is enabled, the detached panel will be positioned
// near the top of the working area such that the subsequent panel could be
// stacked to the bottom of the detached panel. This value is experimental
// and subjective.
const int kDetachedPanelStartingYPositionOnStackingEnabled = 20;
} // namespace
DetachedPanelCollection::DetachedPanelCollection(PanelManager* panel_manager)
: PanelCollection(PanelCollection::DETACHED),
panel_manager_(panel_manager) {
}
DetachedPanelCollection::~DetachedPanelCollection() {
DCHECK(panels_.empty());
}
void DetachedPanelCollection::OnDisplayChanged() {
DisplaySettingsProvider* display_settings_provider =
panel_manager_->display_settings_provider();
for (Panels::const_iterator iter = panels_.begin();
iter != panels_.end(); ++iter) {
Panel* panel = *iter;
gfx::Rect work_area =
display_settings_provider->GetWorkAreaMatching(panel->GetBounds());
// Update size if needed.
panel->LimitSizeToWorkArea(work_area);
// Update bounds to make sure the panel falls completely within the work
// area. Note that the origin of the work area might also change.
gfx::Rect bounds = panel->GetBounds();
if (panel->full_size() != bounds.size()) {
bounds.set_size(panel->full_size());
if (bounds.right() > work_area.right())
bounds.set_x(work_area.right() - bounds.width());
if (bounds.bottom() > work_area.bottom())
bounds.set_y(work_area.bottom() - bounds.height());
}
if (bounds.x() < work_area.x())
bounds.set_x(work_area.x());
if (bounds.y() < work_area.y())
bounds.set_y(work_area.y());
panel->SetPanelBoundsInstantly(bounds);
}
}
void DetachedPanelCollection::RefreshLayout() {
// A detached panel would still maintain its minimized state when it was
// moved out the stack and the drag has not ended. When the drag ends, it
// needs to be expanded. This could occur in the following scenarios:
// 1) It was originally a minimized panel that was dragged out of a stack.
// 2) It was originally a minimized panel that was the top panel in a stack.
// The panel below it was dragged out of the stack which also caused
// the top panel became detached.
for (Panels::const_iterator iter = panels_.begin();
iter != panels_.end(); ++iter) {
Panel* panel = *iter;
if (!panel->in_preview_mode() &&
panel->expansion_state() != Panel::EXPANDED)
panel->SetExpansionState(Panel::EXPANDED);
}
}
void DetachedPanelCollection::AddPanel(Panel* panel,
PositioningMask positioning_mask) {
// positioning_mask is ignored since the detached panel is free-floating.
DCHECK_NE(this, panel->collection());
panel->set_collection(this);
panels_.push_back(panel);
// Offset the default position of the next detached panel if the current
// default position is used.
if (panel->GetBounds().origin() == default_panel_origin_)
ComputeNextDefaultPanelOrigin();
}
void DetachedPanelCollection::RemovePanel(Panel* panel, RemovalReason reason) {
DCHECK_EQ(this, panel->collection());
panel->set_collection(NULL);
panels_.remove(panel);
}
void DetachedPanelCollection::CloseAll() {
// Make a copy as closing panels can modify the iterator.
Panels panels_copy = panels_;
for (Panels::const_iterator iter = panels_copy.begin();
iter != panels_copy.end(); ++iter)
(*iter)->Close();
}
void DetachedPanelCollection::OnPanelAttentionStateChanged(Panel* panel) {
DCHECK_EQ(this, panel->collection());
// Nothing to do.
}
void DetachedPanelCollection::OnPanelTitlebarClicked(Panel* panel,
panel::ClickModifier modifier) {
DCHECK_EQ(this, panel->collection());
// Click on detached panel titlebars does not do anything.
}
void DetachedPanelCollection::ResizePanelWindow(
Panel* panel,
const gfx::Size& preferred_window_size) {
// We should get this call only of we have the panel.
DCHECK_EQ(this, panel->collection());
// Make sure the new size does not violate panel's size restrictions.
gfx::Size new_size(preferred_window_size.width(),
preferred_window_size.height());
new_size = panel->ClampSize(new_size);
// Update restored size.
if (new_size != panel->full_size())
panel->set_full_size(new_size);
gfx::Rect bounds = panel->GetBounds();
// When we resize a detached panel, its origin does not move.
// So we set height and width only.
bounds.set_size(new_size);
if (bounds != panel->GetBounds())
panel->SetPanelBounds(bounds);
}
void DetachedPanelCollection::ActivatePanel(Panel* panel) {
DCHECK_EQ(this, panel->collection());
// No change in panel's appearance.
}
void DetachedPanelCollection::MinimizePanel(Panel* panel) {
DCHECK_EQ(this, panel->collection());
// Detached panels do not minimize. However, extensions may call this API
// regardless of which collection the panel is in. So we just quietly return.
}
void DetachedPanelCollection::RestorePanel(Panel* panel) {
DCHECK_EQ(this, panel->collection());
// Detached panels do not minimize. However, extensions may call this API
// regardless of which collection the panel is in. So we just quietly return.
}
void DetachedPanelCollection::OnMinimizeButtonClicked(
Panel* panel, panel::ClickModifier modifier) {
panel->MinimizeBySystem();
}
void DetachedPanelCollection::OnRestoreButtonClicked(
Panel* panel, panel::ClickModifier modifier) {
// No restore button is present.
NOTREACHED();
}
bool DetachedPanelCollection::CanShowMinimizeButton(const Panel* panel) const {
// We also show minimize button for detached panel when stacking mode is
// enabled.
return PanelManager::IsPanelStackingEnabled() &&
PanelManager::CanUseSystemMinimize();
}
bool DetachedPanelCollection::CanShowRestoreButton(const Panel* panel) const {
// The minimize button is used for system minimize and thus there is no
// restore button.
return false;
}
bool DetachedPanelCollection::IsPanelMinimized(const Panel* panel) const {
DCHECK_EQ(this, panel->collection());
// Detached panels do not minimize.
return false;
}
bool DetachedPanelCollection::UsesAlwaysOnTopPanels() const {
return false;
}
void DetachedPanelCollection::SavePanelPlacement(Panel* panel) {
DCHECK(!saved_panel_placement_.panel);
saved_panel_placement_.panel = panel;
saved_panel_placement_.position = panel->GetBounds().origin();
}
void DetachedPanelCollection::RestorePanelToSavedPlacement() {
DCHECK(saved_panel_placement_.panel);
gfx::Rect new_bounds(saved_panel_placement_.panel->GetBounds());
new_bounds.set_origin(saved_panel_placement_.position);
saved_panel_placement_.panel->SetPanelBounds(new_bounds);
DiscardSavedPanelPlacement();
}
void DetachedPanelCollection::DiscardSavedPanelPlacement() {
DCHECK(saved_panel_placement_.panel);
saved_panel_placement_.panel = NULL;
}
panel::Resizability DetachedPanelCollection::GetPanelResizability(
const Panel* panel) const {
return panel::RESIZABLE_ALL;
}
void DetachedPanelCollection::OnPanelResizedByMouse(
Panel* panel, const gfx::Rect& new_bounds) {
DCHECK_EQ(this, panel->collection());
panel->set_full_size(new_bounds.size());
}
bool DetachedPanelCollection::HasPanel(Panel* panel) const {
return std::find(panels_.begin(), panels_.end(), panel) != panels_.end();
}
void DetachedPanelCollection::SortPanels(PanelsComparer comparer) {
panels_.sort(comparer);
}
void DetachedPanelCollection::UpdatePanelOnCollectionChange(Panel* panel) {
panel->set_attention_mode(
static_cast<Panel::AttentionMode>(Panel::USE_PANEL_ATTENTION |
Panel::USE_SYSTEM_ATTENTION));
panel->ShowShadow(true);
panel->UpdateMinimizeRestoreButtonVisibility();
panel->SetWindowCornerStyle(panel::ALL_ROUNDED);
}
void DetachedPanelCollection::OnPanelExpansionStateChanged(Panel* panel) {
// This should only be reached when a minimized stacked panel is dragged out
// of the stack to become detached. For this case, the panel needs to be
// restored.
DCHECK_EQ(Panel::EXPANDED, panel->expansion_state());
gfx::Rect bounds = panel->GetBounds();
bounds.set_height(panel->full_size().height());
panel->SetPanelBounds(bounds);
}
void DetachedPanelCollection::OnPanelActiveStateChanged(Panel* panel) {
}
gfx::Rect DetachedPanelCollection::GetInitialPanelBounds(
const gfx::Rect& requested_bounds) const {
if (!PanelManager::IsPanelStackingEnabled())
return requested_bounds;
gfx::Rect work_area = panel_manager_->display_settings_provider()->
GetWorkAreaMatching(requested_bounds);
gfx::Rect initial_bounds = requested_bounds;
initial_bounds.set_y(
work_area.y() + kDetachedPanelStartingYPositionOnStackingEnabled);
return initial_bounds;
}
gfx::Point DetachedPanelCollection::GetDefaultPanelOrigin() {
if (!default_panel_origin_.x() && !default_panel_origin_.y()) {
gfx::Rect work_area =
panel_manager_->display_settings_provider()->GetPrimaryWorkArea();
default_panel_origin_.SetPoint(kPanelTilePixels + work_area.x(),
kPanelTilePixels + work_area.y());
}
return default_panel_origin_;
}
void DetachedPanelCollection::ComputeNextDefaultPanelOrigin() {
default_panel_origin_.Offset(kPanelTilePixels, kPanelTilePixels);
gfx::Rect work_area =
panel_manager_->display_settings_provider()->GetPrimaryWorkArea();
if (!work_area.Contains(default_panel_origin_)) {
default_panel_origin_.SetPoint(kPanelTilePixels + work_area.x(),
kPanelTilePixels + work_area.y());
}
}