// Copyright (c) 2011 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/panel_manager.h"
#include <algorithm>
#include "base/logging.h"
#include "base/scoped_ptr.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/panels/panel.h"
#include "chrome/browser/ui/window_sizer.h"
namespace {
// Invalid panel index.
const size_t kInvalidPanelIndex = static_cast<size_t>(-1);
// Minimum width and height of a panel.
const int kPanelMinWidthPixels = 64;
const int kPanelMinHeightPixels = 24;
// Default width and height of a panel.
const int kPanelDefaultWidthPixels = 240;
const int kPanelDefaultHeightPixels = 290;
// Maxmium width and height of a panel based on the factor of the working
// area.
const double kPanelMaxWidthFactor = 1.0;
const double kPanelMaxHeightFactor = 0.5;
// Horizontal spacing between two panels.
const int kPanelsHorizontalSpacing = 4;
// Single instance of PanelManager.
scoped_ptr<PanelManager> panel_instance;
} // namespace
// static
PanelManager* PanelManager::GetInstance() {
if (!panel_instance.get()) {
panel_instance.reset(new PanelManager());
}
return panel_instance.get();
}
PanelManager::PanelManager()
: max_width_(0),
max_height_(0),
min_x_(0),
current_x_(0),
bottom_edge_y_(0),
dragging_panel_index_(kInvalidPanelIndex),
dragging_panel_original_x_(0) {
OnDisplayChanged();
}
PanelManager::~PanelManager() {
DCHECK(active_panels_.empty());
DCHECK(pending_panels_.empty());
DCHECK(panels_pending_to_remove_.empty());
}
void PanelManager::OnDisplayChanged() {
scoped_ptr<WindowSizer::MonitorInfoProvider> info_provider(
WindowSizer::CreateDefaultMonitorInfoProvider());
gfx::Rect work_area = info_provider->GetPrimaryMonitorWorkArea();
min_x_ = work_area.x();
current_x_ = work_area.right();
bottom_edge_y_ = work_area.bottom();
max_width_ = static_cast<int>(work_area.width() * kPanelMaxWidthFactor);
max_height_ = static_cast<int>(work_area.height() * kPanelMaxHeightFactor);
Rearrange(active_panels_.begin());
}
Panel* PanelManager::CreatePanel(Browser* browser) {
gfx::Rect bounds = browser->override_bounds();
bool is_within_bounds = ComputeBoundsForNextPanel(&bounds, true);
Panel* panel = new Panel(browser, bounds);
if (is_within_bounds)
active_panels_.push_back(panel);
else
pending_panels_.push_back(panel);
return panel;
}
void PanelManager::ProcessPending() {
while (!pending_panels_.empty()) {
Panel* panel = pending_panels_.front();
gfx::Rect bounds = panel->bounds();
if (ComputeBoundsForNextPanel(&bounds, true)) {
// TODO(jianli): More work to support displaying pending panels.
active_panels_.push_back(panel);
pending_panels_.pop_front();
}
}
}
void PanelManager::Remove(Panel* panel) {
// If we're in the process of dragging, delay the removal.
if (dragging_panel_index_ != kInvalidPanelIndex) {
panels_pending_to_remove_.push_back(panel);
return;
}
DoRemove(panel);
}
void PanelManager::DelayedRemove() {
for (size_t i = 0; i < panels_pending_to_remove_.size(); ++i)
DoRemove(panels_pending_to_remove_[i]);
panels_pending_to_remove_.clear();
}
void PanelManager::DoRemove(Panel* panel) {
// Checks the active panel list.
ActivePanels::iterator iter =
find(active_panels_.begin(), active_panels_.end(), panel);
if (iter == active_panels_.end()) {
// Checks the pending panel list.
PendingPanels::iterator iter2 =
find(pending_panels_.begin(), pending_panels_.end(), panel);
if (iter2 != pending_panels_.end())
pending_panels_.erase(iter2);
return;
}
current_x_ = (*iter)->bounds().x() + (*iter)->bounds().width();
Rearrange(active_panels_.erase(iter));
ProcessPending();
}
void PanelManager::StartDragging(Panel* panel) {
for (size_t i = 0; i < active_panels_.size(); ++i) {
if (active_panels_[i] == panel) {
dragging_panel_index_ = i;
dragging_panel_bounds_ = panel->bounds();
dragging_panel_original_x_ = dragging_panel_bounds_.x();
break;
}
}
}
void PanelManager::Drag(int delta_x) {
DCHECK(dragging_panel_index_ != kInvalidPanelIndex);
if (!delta_x)
return;
// Moves this panel to the dragging position.
gfx::Rect new_bounds(active_panels_[dragging_panel_index_]->bounds());
new_bounds.set_x(new_bounds.x() + delta_x);
active_panels_[dragging_panel_index_]->SetBounds(new_bounds);
// Checks and processes other affected panels.
if (delta_x > 0)
DragPositive(delta_x);
else
DragNegative(delta_x);
}
void PanelManager::DragNegative(int delta_x) {
DCHECK(delta_x < 0);
Panel* dragging_panel = active_panels_[dragging_panel_index_];
// This is the left corner of the dragging panel. We use it to check against
// all the panels on its left.
int dragging_panel_x = dragging_panel->bounds().x() + delta_x;
// This is the right corner which a panel will be moved to.
int right_x_to_move_to =
dragging_panel_bounds_.x() + dragging_panel_bounds_.width();
// Checks the panels to the left of the dragging panel.
size_t i = dragging_panel_index_;
size_t j = i + 1;
for (; j < active_panels_.size(); ++j, ++i) {
// Current panel will only be affected if the left corner of dragging
// panel goes beyond the middle position of the current panel.
if (dragging_panel_x > active_panels_[j]->bounds().x() +
active_panels_[j]->bounds().width() / 2)
break;
// Moves current panel to the new position.
gfx::Rect bounds(active_panels_[j]->bounds());
bounds.set_x(right_x_to_move_to - bounds.width());
right_x_to_move_to -= bounds.width() + kPanelsHorizontalSpacing;
active_panels_[j]->SetBounds(bounds);
// Adjusts the index of current panel.
active_panels_[i] = active_panels_[j];
}
// Adjusts the position and index of dragging panel as the result of moving
// other affected panels.
if (j != dragging_panel_index_ + 1) {
j--;
dragging_panel_bounds_.set_x(right_x_to_move_to -
dragging_panel_bounds_.width());
active_panels_[j] = dragging_panel;
dragging_panel_index_ = j;
}
}
void PanelManager::DragPositive(int delta_x) {
DCHECK(delta_x > 0);
Panel* dragging_panel = active_panels_[dragging_panel_index_];
// This is the right corner of the dragging panel. We use it to check against
// all the panels on its right.
int dragging_panel_x = dragging_panel->bounds().x() +
dragging_panel->bounds().width() - 1 + delta_x;
// This is the left corner which a panel will be moved to.
int left_x_to_move_to = dragging_panel_bounds_.x();
// Checks the panels to the right of the dragging panel.
int i = static_cast<int>(dragging_panel_index_);
int j = i - 1;
for (; j >= 0; --j, --i) {
// Current panel will only be affected if the right corner of dragging
// panel goes beyond the middle position of the current panel.
if (dragging_panel_x < active_panels_[j]->bounds().x() +
active_panels_[j]->bounds().width() / 2)
break;
// Moves current panel to the new position.
gfx::Rect bounds(active_panels_[j]->bounds());
bounds.set_x(left_x_to_move_to);
left_x_to_move_to += bounds.width() + kPanelsHorizontalSpacing;
active_panels_[j]->SetBounds(bounds);
// Adjusts the index of current panel.
active_panels_[i] = active_panels_[j];
}
// Adjusts the position and index of dragging panel as the result of moving
// other affected panels.
if (j != static_cast<int>(dragging_panel_index_) - 1) {
j++;
dragging_panel_bounds_.set_x(left_x_to_move_to);
active_panels_[j] = dragging_panel;
dragging_panel_index_ = j;
}
}
void PanelManager::EndDragging(bool cancelled) {
DCHECK(dragging_panel_index_ != kInvalidPanelIndex);
if (cancelled) {
Drag(dragging_panel_original_x_ -
active_panels_[dragging_panel_index_]->bounds().x());
} else {
active_panels_[dragging_panel_index_]->SetBounds(dragging_panel_bounds_);
}
dragging_panel_index_ = kInvalidPanelIndex;
DelayedRemove();
}
void PanelManager::Rearrange(ActivePanels::iterator iter_to_start) {
if (iter_to_start == active_panels_.end())
return;
for (ActivePanels::iterator iter = iter_to_start;
iter != active_panels_.end(); ++iter) {
gfx::Rect new_bounds((*iter)->bounds());
ComputeBoundsForNextPanel(&new_bounds, false);
if (new_bounds != (*iter)->bounds())
(*iter)->SetBounds(new_bounds);
}
}
bool PanelManager::ComputeBoundsForNextPanel(gfx::Rect* bounds,
bool allow_size_change) {
int width = bounds->width();
int height = bounds->height();
// Update the width and/or height to fit into our constraint.
if (allow_size_change) {
if (width == 0 && height == 0) {
width = kPanelDefaultWidthPixels;
height = kPanelDefaultHeightPixels;
}
if (width < kPanelMinWidthPixels)
width = kPanelMinWidthPixels;
else if (width > max_width_)
width = max_width_;
if (height < kPanelMinHeightPixels)
height = kPanelMinHeightPixels;
else if (height > max_height_)
height = max_height_;
}
int x = current_x_ - width;
int y = bottom_edge_y_ - height;
if (x < min_x_)
return false;
current_x_ -= width + kPanelsHorizontalSpacing;
bounds->SetRect(x, y, width, height);
return true;
}
void PanelManager::MinimizeAll() {
for (ActivePanels::const_iterator iter = active_panels_.begin();
iter != active_panels_.end(); ++iter) {
(*iter)->Minimize();
}
}
void PanelManager::RestoreAll() {
for (ActivePanels::const_iterator iter = active_panels_.begin();
iter != active_panels_.end(); ++iter) {
(*iter)->Restore();
}
}
void PanelManager::RemoveAllActive() {
// This should not be called when we're in the process of dragging.
DCHECK(dragging_panel_index_ == kInvalidPanelIndex);
// Start from the bottom to avoid reshuffling.
for (int i = static_cast<int>(active_panels_.size()) -1; i >= 0; --i)
active_panels_[i]->Close();
ProcessPending();
}
bool PanelManager::AreAllMinimized() const {
for (ActivePanels::const_iterator iter = active_panels_.begin();
iter != active_panels_.end(); ++iter) {
if (!(*iter)->minimized())
return false;
}
return true;
}