1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/panels/panel_manager.h"
6
7#include <algorithm>
8#include "base/logging.h"
9#include "base/scoped_ptr.h"
10#include "chrome/browser/ui/browser.h"
11#include "chrome/browser/ui/panels/panel.h"
12#include "chrome/browser/ui/window_sizer.h"
13
14namespace {
15// Invalid panel index.
16const size_t kInvalidPanelIndex = static_cast<size_t>(-1);
17
18// Minimum width and height of a panel.
19const int kPanelMinWidthPixels = 64;
20const int kPanelMinHeightPixels = 24;
21
22// Default width and height of a panel.
23const int kPanelDefaultWidthPixels = 240;
24const int kPanelDefaultHeightPixels = 290;
25
26// Maxmium width and height of a panel based on the factor of the working
27// area.
28const double kPanelMaxWidthFactor = 1.0;
29const double kPanelMaxHeightFactor = 0.5;
30
31// Horizontal spacing between two panels.
32const int kPanelsHorizontalSpacing = 4;
33
34// Single instance of PanelManager.
35scoped_ptr<PanelManager> panel_instance;
36} // namespace
37
38// static
39PanelManager* PanelManager::GetInstance() {
40  if (!panel_instance.get()) {
41    panel_instance.reset(new PanelManager());
42  }
43  return panel_instance.get();
44}
45
46PanelManager::PanelManager()
47    : max_width_(0),
48      max_height_(0),
49      min_x_(0),
50      current_x_(0),
51      bottom_edge_y_(0),
52      dragging_panel_index_(kInvalidPanelIndex),
53      dragging_panel_original_x_(0) {
54  OnDisplayChanged();
55}
56
57PanelManager::~PanelManager() {
58  DCHECK(active_panels_.empty());
59  DCHECK(pending_panels_.empty());
60  DCHECK(panels_pending_to_remove_.empty());
61}
62
63void PanelManager::OnDisplayChanged() {
64  scoped_ptr<WindowSizer::MonitorInfoProvider> info_provider(
65      WindowSizer::CreateDefaultMonitorInfoProvider());
66  gfx::Rect work_area = info_provider->GetPrimaryMonitorWorkArea();
67
68  min_x_ = work_area.x();
69  current_x_ = work_area.right();
70  bottom_edge_y_ = work_area.bottom();
71  max_width_ = static_cast<int>(work_area.width() * kPanelMaxWidthFactor);
72  max_height_ = static_cast<int>(work_area.height() * kPanelMaxHeightFactor);
73
74  Rearrange(active_panels_.begin());
75}
76
77Panel* PanelManager::CreatePanel(Browser* browser) {
78  gfx::Rect bounds = browser->override_bounds();
79  bool is_within_bounds = ComputeBoundsForNextPanel(&bounds, true);
80
81  Panel* panel = new Panel(browser, bounds);
82  if (is_within_bounds)
83    active_panels_.push_back(panel);
84  else
85    pending_panels_.push_back(panel);
86
87  return panel;
88}
89
90void PanelManager::ProcessPending() {
91  while (!pending_panels_.empty()) {
92    Panel* panel = pending_panels_.front();
93    gfx::Rect bounds = panel->bounds();
94    if (ComputeBoundsForNextPanel(&bounds, true)) {
95      // TODO(jianli): More work to support displaying pending panels.
96      active_panels_.push_back(panel);
97      pending_panels_.pop_front();
98    }
99  }
100}
101
102void PanelManager::Remove(Panel* panel) {
103  // If we're in the process of dragging, delay the removal.
104  if (dragging_panel_index_ != kInvalidPanelIndex) {
105    panels_pending_to_remove_.push_back(panel);
106    return;
107  }
108
109  DoRemove(panel);
110}
111
112void PanelManager::DelayedRemove() {
113  for (size_t i = 0; i < panels_pending_to_remove_.size(); ++i)
114    DoRemove(panels_pending_to_remove_[i]);
115  panels_pending_to_remove_.clear();
116}
117
118void PanelManager::DoRemove(Panel* panel) {
119  // Checks the active panel list.
120  ActivePanels::iterator iter =
121      find(active_panels_.begin(), active_panels_.end(), panel);
122  if (iter == active_panels_.end()) {
123    // Checks the pending panel list.
124    PendingPanels::iterator iter2 =
125        find(pending_panels_.begin(), pending_panels_.end(), panel);
126    if (iter2 != pending_panels_.end())
127      pending_panels_.erase(iter2);
128    return;
129  }
130
131  current_x_ = (*iter)->bounds().x() + (*iter)->bounds().width();
132  Rearrange(active_panels_.erase(iter));
133
134  ProcessPending();
135}
136
137void PanelManager::StartDragging(Panel* panel) {
138  for (size_t i = 0; i < active_panels_.size(); ++i) {
139    if (active_panels_[i] == panel) {
140      dragging_panel_index_ = i;
141      dragging_panel_bounds_ = panel->bounds();
142      dragging_panel_original_x_ = dragging_panel_bounds_.x();
143      break;
144    }
145  }
146}
147
148void PanelManager::Drag(int delta_x) {
149  DCHECK(dragging_panel_index_ != kInvalidPanelIndex);
150
151  if (!delta_x)
152    return;
153
154  // Moves this panel to the dragging position.
155  gfx::Rect new_bounds(active_panels_[dragging_panel_index_]->bounds());
156  new_bounds.set_x(new_bounds.x() + delta_x);
157  active_panels_[dragging_panel_index_]->SetBounds(new_bounds);
158
159  // Checks and processes other affected panels.
160  if (delta_x > 0)
161    DragPositive(delta_x);
162  else
163    DragNegative(delta_x);
164}
165
166void PanelManager::DragNegative(int delta_x) {
167  DCHECK(delta_x < 0);
168
169  Panel* dragging_panel = active_panels_[dragging_panel_index_];
170
171  // This is the left corner of the dragging panel. We use it to check against
172  // all the panels on its left.
173  int dragging_panel_x = dragging_panel->bounds().x() + delta_x;
174
175  // This is the right corner which a panel will be moved to.
176  int right_x_to_move_to =
177      dragging_panel_bounds_.x() + dragging_panel_bounds_.width();
178
179  // Checks the panels to the left of the dragging panel.
180  size_t i = dragging_panel_index_;
181  size_t j = i + 1;
182  for (; j < active_panels_.size(); ++j, ++i) {
183    // Current panel will only be affected if the left corner of dragging
184    // panel goes beyond the middle position of the current panel.
185    if (dragging_panel_x > active_panels_[j]->bounds().x() +
186                           active_panels_[j]->bounds().width() / 2)
187      break;
188
189    // Moves current panel to the new position.
190    gfx::Rect bounds(active_panels_[j]->bounds());
191    bounds.set_x(right_x_to_move_to - bounds.width());
192    right_x_to_move_to -= bounds.width() + kPanelsHorizontalSpacing;
193    active_panels_[j]->SetBounds(bounds);
194
195    // Adjusts the index of current panel.
196    active_panels_[i] = active_panels_[j];
197  }
198
199  // Adjusts the position and index of dragging panel as the result of moving
200  // other affected panels.
201  if (j != dragging_panel_index_ + 1) {
202    j--;
203    dragging_panel_bounds_.set_x(right_x_to_move_to -
204                                 dragging_panel_bounds_.width());
205    active_panels_[j] = dragging_panel;
206    dragging_panel_index_ = j;
207  }
208}
209
210void PanelManager::DragPositive(int delta_x) {
211  DCHECK(delta_x > 0);
212
213  Panel* dragging_panel = active_panels_[dragging_panel_index_];
214
215  // This is the right corner of the dragging panel. We use it to check against
216  // all the panels on its right.
217  int dragging_panel_x = dragging_panel->bounds().x() +
218      dragging_panel->bounds().width() - 1 + delta_x;
219
220  // This is the left corner which a panel will be moved to.
221  int left_x_to_move_to = dragging_panel_bounds_.x();
222
223  // Checks the panels to the right of the dragging panel.
224  int i = static_cast<int>(dragging_panel_index_);
225  int j = i - 1;
226  for (; j >= 0; --j, --i) {
227    // Current panel will only be affected if the right corner of dragging
228    // panel goes beyond the middle position of the current panel.
229    if (dragging_panel_x < active_panels_[j]->bounds().x() +
230                           active_panels_[j]->bounds().width() / 2)
231      break;
232
233    // Moves current panel to the new position.
234    gfx::Rect bounds(active_panels_[j]->bounds());
235    bounds.set_x(left_x_to_move_to);
236    left_x_to_move_to += bounds.width() + kPanelsHorizontalSpacing;
237    active_panels_[j]->SetBounds(bounds);
238
239    // Adjusts the index of current panel.
240    active_panels_[i] = active_panels_[j];
241  }
242
243  // Adjusts the position and index of dragging panel as the result of moving
244  // other affected panels.
245  if (j != static_cast<int>(dragging_panel_index_) - 1) {
246    j++;
247    dragging_panel_bounds_.set_x(left_x_to_move_to);
248    active_panels_[j] = dragging_panel;
249    dragging_panel_index_ = j;
250  }
251}
252
253void PanelManager::EndDragging(bool cancelled) {
254  DCHECK(dragging_panel_index_ != kInvalidPanelIndex);
255
256  if (cancelled) {
257    Drag(dragging_panel_original_x_ -
258         active_panels_[dragging_panel_index_]->bounds().x());
259  } else {
260    active_panels_[dragging_panel_index_]->SetBounds(dragging_panel_bounds_);
261  }
262
263  dragging_panel_index_ = kInvalidPanelIndex;
264
265  DelayedRemove();
266}
267
268void PanelManager::Rearrange(ActivePanels::iterator iter_to_start) {
269  if (iter_to_start == active_panels_.end())
270    return;
271
272  for (ActivePanels::iterator iter = iter_to_start;
273       iter != active_panels_.end(); ++iter) {
274    gfx::Rect new_bounds((*iter)->bounds());
275    ComputeBoundsForNextPanel(&new_bounds, false);
276    if (new_bounds != (*iter)->bounds())
277      (*iter)->SetBounds(new_bounds);
278  }
279}
280
281bool PanelManager::ComputeBoundsForNextPanel(gfx::Rect* bounds,
282                                             bool allow_size_change) {
283  int width = bounds->width();
284  int height = bounds->height();
285
286  // Update the width and/or height to fit into our constraint.
287  if (allow_size_change) {
288    if (width == 0 && height == 0) {
289      width = kPanelDefaultWidthPixels;
290      height = kPanelDefaultHeightPixels;
291    }
292
293    if (width < kPanelMinWidthPixels)
294      width = kPanelMinWidthPixels;
295    else if (width > max_width_)
296      width = max_width_;
297
298    if (height < kPanelMinHeightPixels)
299      height = kPanelMinHeightPixels;
300    else if (height > max_height_)
301      height = max_height_;
302  }
303
304  int x = current_x_ - width;
305  int y = bottom_edge_y_ - height;
306
307  if (x < min_x_)
308    return false;
309
310  current_x_ -= width + kPanelsHorizontalSpacing;
311
312  bounds->SetRect(x, y, width, height);
313  return true;
314}
315
316void PanelManager::MinimizeAll() {
317  for (ActivePanels::const_iterator iter = active_panels_.begin();
318       iter != active_panels_.end(); ++iter) {
319    (*iter)->Minimize();
320  }
321}
322
323void PanelManager::RestoreAll() {
324  for (ActivePanels::const_iterator iter = active_panels_.begin();
325       iter != active_panels_.end(); ++iter) {
326    (*iter)->Restore();
327  }
328}
329
330void PanelManager::RemoveAllActive() {
331  // This should not be called when we're in the process of dragging.
332  DCHECK(dragging_panel_index_ == kInvalidPanelIndex);
333
334  // Start from the bottom to avoid reshuffling.
335  for (int i = static_cast<int>(active_panels_.size()) -1; i >= 0; --i)
336    active_panels_[i]->Close();
337
338  ProcessPending();
339}
340
341bool PanelManager::AreAllMinimized() const {
342  for (ActivePanels::const_iterator iter = active_panels_.begin();
343       iter != active_panels_.end(); ++iter) {
344    if (!(*iter)->minimized())
345      return false;
346  }
347  return true;
348}
349