// 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 #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(-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 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 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(work_area.width() * kPanelMaxWidthFactor); max_height_ = static_cast(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(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(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(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; }