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 13 namespace { 14 // How much horizontal and vertical offset there is between newly opened 15 // detached panels. 16 const 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. 22 const int kDetachedPanelStartingYPositionOnStackingEnabled = 20; 23 } // namespace 24 25 DetachedPanelCollection::DetachedPanelCollection(PanelManager* panel_manager) 26 : PanelCollection(PanelCollection::DETACHED), 27 panel_manager_(panel_manager) { 28 } 29 30 DetachedPanelCollection::~DetachedPanelCollection() { 31 DCHECK(panels_.empty()); 32 } 33 34 void 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 65 void 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 82 void 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 95 void DetachedPanelCollection::RemovePanel(Panel* panel, RemovalReason reason) { 96 DCHECK_EQ(this, panel->collection()); 97 panel->set_collection(NULL); 98 panels_.remove(panel); 99 } 100 101 void 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 110 void DetachedPanelCollection::OnPanelAttentionStateChanged(Panel* panel) { 111 DCHECK_EQ(this, panel->collection()); 112 // Nothing to do. 113 } 114 115 void 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 121 void 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 146 void DetachedPanelCollection::ActivatePanel(Panel* panel) { 147 DCHECK_EQ(this, panel->collection()); 148 // No change in panel's appearance. 149 } 150 151 void 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 157 void 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 163 void DetachedPanelCollection::OnMinimizeButtonClicked( 164 Panel* panel, panel::ClickModifier modifier) { 165 panel->MinimizeBySystem(); 166 } 167 168 void DetachedPanelCollection::OnRestoreButtonClicked( 169 Panel* panel, panel::ClickModifier modifier) { 170 // No restore button is present. 171 NOTREACHED(); 172 } 173 174 bool 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 181 bool 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 187 bool DetachedPanelCollection::IsPanelMinimized(const Panel* panel) const { 188 DCHECK_EQ(this, panel->collection()); 189 // Detached panels do not minimize. 190 return false; 191 } 192 193 bool DetachedPanelCollection::UsesAlwaysOnTopPanels() const { 194 return false; 195 } 196 197 void 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 203 void 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 213 void DetachedPanelCollection::DiscardSavedPanelPlacement() { 214 DCHECK(saved_panel_placement_.panel); 215 saved_panel_placement_.panel = NULL; 216 } 217 218 panel::Resizability DetachedPanelCollection::GetPanelResizability( 219 const Panel* panel) const { 220 return panel::RESIZABLE_ALL; 221 } 222 223 void 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 229 bool DetachedPanelCollection::HasPanel(Panel* panel) const { 230 return std::find(panels_.begin(), panels_.end(), panel) != panels_.end(); 231 } 232 233 void DetachedPanelCollection::SortPanels(PanelsComparer comparer) { 234 panels_.sort(comparer); 235 } 236 237 void 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 246 void 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 257 void DetachedPanelCollection::OnPanelActiveStateChanged(Panel* panel) { 258 } 259 260 gfx::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 273 gfx::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 283 void 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