1// Copyright (c) 2012 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/detached_panel_collection.h"
6
7#include <algorithm>
8#include "base/logging.h"
9#include "chrome/browser/ui/panels/display_settings_provider.h"
10#include "chrome/browser/ui/panels/panel_drag_controller.h"
11#include "chrome/browser/ui/panels/panel_manager.h"
12
13namespace {
14// How much horizontal and vertical offset there is between newly opened
15// detached panels.
16const int kPanelTilePixels = 10;
17
18// When the stacking mode is enabled, the detached panel will be positioned
19// near the top of the working area such that the subsequent panel could be
20// stacked to the bottom of the detached panel. This value is experimental
21// and subjective.
22const int kDetachedPanelStartingYPositionOnStackingEnabled = 20;
23}  // namespace
24
25DetachedPanelCollection::DetachedPanelCollection(PanelManager* panel_manager)
26    : PanelCollection(PanelCollection::DETACHED),
27      panel_manager_(panel_manager) {
28}
29
30DetachedPanelCollection::~DetachedPanelCollection() {
31  DCHECK(panels_.empty());
32}
33
34void DetachedPanelCollection::OnDisplayChanged() {
35  DisplaySettingsProvider* display_settings_provider =
36      panel_manager_->display_settings_provider();
37
38  for (Panels::const_iterator iter = panels_.begin();
39       iter != panels_.end(); ++iter) {
40    Panel* panel = *iter;
41    gfx::Rect work_area =
42        display_settings_provider->GetWorkAreaMatching(panel->GetBounds());
43
44    // Update size if needed.
45    panel->LimitSizeToWorkArea(work_area);
46
47    // Update bounds to make sure the panel falls completely within the work
48    // area. Note that the origin of the work area might also change.
49    gfx::Rect bounds = panel->GetBounds();
50    if (panel->full_size() != bounds.size()) {
51      bounds.set_size(panel->full_size());
52      if (bounds.right() > work_area.right())
53        bounds.set_x(work_area.right() - bounds.width());
54      if (bounds.bottom() > work_area.bottom())
55        bounds.set_y(work_area.bottom() - bounds.height());
56    }
57    if (bounds.x() < work_area.x())
58      bounds.set_x(work_area.x());
59    if (bounds.y() < work_area.y())
60      bounds.set_y(work_area.y());
61    panel->SetPanelBoundsInstantly(bounds);
62  }
63}
64
65void DetachedPanelCollection::RefreshLayout() {
66  // A detached panel would still maintain its minimized state when it was
67  // moved out the stack and the drag has not ended. When the drag ends, it
68  // needs to be expanded. This could occur in the following scenarios:
69  // 1) It was originally a minimized panel that was dragged out of a stack.
70  // 2) It was originally a minimized panel that was the top panel in a stack.
71  //    The panel below it was dragged out of the stack which also caused
72  //    the top panel became detached.
73  for (Panels::const_iterator iter = panels_.begin();
74       iter != panels_.end(); ++iter) {
75    Panel* panel = *iter;
76    if (!panel->in_preview_mode() &&
77        panel->expansion_state() != Panel::EXPANDED)
78      panel->SetExpansionState(Panel::EXPANDED);
79  }
80}
81
82void DetachedPanelCollection::AddPanel(Panel* panel,
83                                  PositioningMask positioning_mask) {
84  // positioning_mask is ignored since the detached panel is free-floating.
85  DCHECK_NE(this, panel->collection());
86  panel->set_collection(this);
87  panels_.push_back(panel);
88
89  // Offset the default position of the next detached panel if the current
90  // default position is used.
91  if (panel->GetBounds().origin() == default_panel_origin_)
92    ComputeNextDefaultPanelOrigin();
93}
94
95void DetachedPanelCollection::RemovePanel(Panel* panel, RemovalReason reason) {
96  DCHECK_EQ(this, panel->collection());
97  panel->set_collection(NULL);
98  panels_.remove(panel);
99}
100
101void DetachedPanelCollection::CloseAll() {
102  // Make a copy as closing panels can modify the iterator.
103  Panels panels_copy = panels_;
104
105  for (Panels::const_iterator iter = panels_copy.begin();
106       iter != panels_copy.end(); ++iter)
107    (*iter)->Close();
108}
109
110void DetachedPanelCollection::OnPanelAttentionStateChanged(Panel* panel) {
111  DCHECK_EQ(this, panel->collection());
112  // Nothing to do.
113}
114
115void DetachedPanelCollection::OnPanelTitlebarClicked(Panel* panel,
116                                                panel::ClickModifier modifier) {
117  DCHECK_EQ(this, panel->collection());
118  // Click on detached panel titlebars does not do anything.
119}
120
121void DetachedPanelCollection::ResizePanelWindow(
122    Panel* panel,
123    const gfx::Size& preferred_window_size) {
124  // We should get this call only of we have the panel.
125  DCHECK_EQ(this, panel->collection());
126
127  // Make sure the new size does not violate panel's size restrictions.
128  gfx::Size new_size(preferred_window_size.width(),
129                     preferred_window_size.height());
130  new_size = panel->ClampSize(new_size);
131
132  // Update restored size.
133  if (new_size != panel->full_size())
134    panel->set_full_size(new_size);
135
136  gfx::Rect bounds = panel->GetBounds();
137
138  // When we resize a detached panel, its origin does not move.
139  // So we set height and width only.
140  bounds.set_size(new_size);
141
142  if (bounds != panel->GetBounds())
143    panel->SetPanelBounds(bounds);
144}
145
146void DetachedPanelCollection::ActivatePanel(Panel* panel) {
147  DCHECK_EQ(this, panel->collection());
148  // No change in panel's appearance.
149}
150
151void DetachedPanelCollection::MinimizePanel(Panel* panel) {
152  DCHECK_EQ(this, panel->collection());
153  // Detached panels do not minimize. However, extensions may call this API
154  // regardless of which collection the panel is in. So we just quietly return.
155}
156
157void DetachedPanelCollection::RestorePanel(Panel* panel) {
158  DCHECK_EQ(this, panel->collection());
159  // Detached panels do not minimize. However, extensions may call this API
160  // regardless of which collection the panel is in. So we just quietly return.
161}
162
163void DetachedPanelCollection::OnMinimizeButtonClicked(
164    Panel* panel, panel::ClickModifier modifier) {
165  panel->MinimizeBySystem();
166}
167
168void DetachedPanelCollection::OnRestoreButtonClicked(
169    Panel* panel, panel::ClickModifier modifier) {
170  // No restore button is present.
171  NOTREACHED();
172}
173
174bool DetachedPanelCollection::CanShowMinimizeButton(const Panel* panel) const {
175  // We also show minimize button for detached panel when stacking mode is
176  // enabled.
177  return PanelManager::IsPanelStackingEnabled() &&
178         PanelManager::CanUseSystemMinimize();
179}
180
181bool DetachedPanelCollection::CanShowRestoreButton(const Panel* panel) const {
182  // The minimize button is used for system minimize and thus there is no
183  // restore button.
184  return false;
185}
186
187bool DetachedPanelCollection::IsPanelMinimized(const Panel* panel) const {
188  DCHECK_EQ(this, panel->collection());
189  // Detached panels do not minimize.
190  return false;
191}
192
193bool DetachedPanelCollection::UsesAlwaysOnTopPanels() const {
194  return false;
195}
196
197void DetachedPanelCollection::SavePanelPlacement(Panel* panel) {
198  DCHECK(!saved_panel_placement_.panel);
199  saved_panel_placement_.panel = panel;
200  saved_panel_placement_.position = panel->GetBounds().origin();
201}
202
203void DetachedPanelCollection::RestorePanelToSavedPlacement() {
204  DCHECK(saved_panel_placement_.panel);
205
206  gfx::Rect new_bounds(saved_panel_placement_.panel->GetBounds());
207  new_bounds.set_origin(saved_panel_placement_.position);
208  saved_panel_placement_.panel->SetPanelBounds(new_bounds);
209
210  DiscardSavedPanelPlacement();
211}
212
213void DetachedPanelCollection::DiscardSavedPanelPlacement() {
214  DCHECK(saved_panel_placement_.panel);
215  saved_panel_placement_.panel = NULL;
216}
217
218panel::Resizability DetachedPanelCollection::GetPanelResizability(
219    const Panel* panel) const {
220  return panel::RESIZABLE_ALL;
221}
222
223void DetachedPanelCollection::OnPanelResizedByMouse(
224    Panel* panel, const gfx::Rect& new_bounds) {
225  DCHECK_EQ(this, panel->collection());
226  panel->set_full_size(new_bounds.size());
227}
228
229bool DetachedPanelCollection::HasPanel(Panel* panel) const {
230  return std::find(panels_.begin(), panels_.end(), panel) != panels_.end();
231}
232
233void DetachedPanelCollection::SortPanels(PanelsComparer comparer) {
234  panels_.sort(comparer);
235}
236
237void DetachedPanelCollection::UpdatePanelOnCollectionChange(Panel* panel) {
238  panel->set_attention_mode(
239      static_cast<Panel::AttentionMode>(Panel::USE_PANEL_ATTENTION |
240                                        Panel::USE_SYSTEM_ATTENTION));
241  panel->ShowShadow(true);
242  panel->UpdateMinimizeRestoreButtonVisibility();
243  panel->SetWindowCornerStyle(panel::ALL_ROUNDED);
244}
245
246void DetachedPanelCollection::OnPanelExpansionStateChanged(Panel* panel) {
247  // This should only be reached when a minimized stacked panel is dragged out
248  // of the stack to become detached. For this case, the panel needs to be
249  // restored.
250  DCHECK_EQ(Panel::EXPANDED, panel->expansion_state());
251
252  gfx::Rect bounds = panel->GetBounds();
253  bounds.set_height(panel->full_size().height());
254  panel->SetPanelBounds(bounds);
255}
256
257void DetachedPanelCollection::OnPanelActiveStateChanged(Panel* panel) {
258}
259
260gfx::Rect DetachedPanelCollection::GetInitialPanelBounds(
261      const gfx::Rect& requested_bounds) const {
262  if (!PanelManager::IsPanelStackingEnabled())
263    return requested_bounds;
264
265  gfx::Rect work_area = panel_manager_->display_settings_provider()->
266      GetWorkAreaMatching(requested_bounds);
267  gfx::Rect initial_bounds = requested_bounds;
268  initial_bounds.set_y(
269      work_area.y() + kDetachedPanelStartingYPositionOnStackingEnabled);
270  return initial_bounds;
271}
272
273gfx::Point DetachedPanelCollection::GetDefaultPanelOrigin() {
274  if (!default_panel_origin_.x() && !default_panel_origin_.y()) {
275    gfx::Rect work_area =
276        panel_manager_->display_settings_provider()->GetPrimaryWorkArea();
277    default_panel_origin_.SetPoint(kPanelTilePixels + work_area.x(),
278                                   kPanelTilePixels + work_area.y());
279  }
280  return default_panel_origin_;
281}
282
283void DetachedPanelCollection::ComputeNextDefaultPanelOrigin() {
284  default_panel_origin_.Offset(kPanelTilePixels, kPanelTilePixels);
285  gfx::Rect work_area =
286      panel_manager_->display_settings_provider()->GetPrimaryWorkArea();
287  if (!work_area.Contains(default_panel_origin_)) {
288    default_panel_origin_.SetPoint(kPanelTilePixels + work_area.x(),
289                                   kPanelTilePixels + work_area.y());
290  }
291}
292