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