Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / panels / panel_manager.cc
blob1846ea65c5300d6f455697a41313f7db3e910375
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.
5 #include "chrome/browser/ui/panels/panel_manager.h"
7 #include "base/bind.h"
8 #include "base/command_line.h"
9 #include "base/logging.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/message_loop/message_loop.h"
12 #include "chrome/browser/chrome_notification_types.h"
13 #include "chrome/browser/ui/panels/detached_panel_collection.h"
14 #include "chrome/browser/ui/panels/docked_panel_collection.h"
15 #include "chrome/browser/ui/panels/panel_drag_controller.h"
16 #include "chrome/browser/ui/panels/panel_mouse_watcher.h"
17 #include "chrome/browser/ui/panels/panel_resize_controller.h"
18 #include "chrome/browser/ui/panels/stacked_panel_collection.h"
19 #include "chrome/common/channel_info.h"
20 #include "chrome/common/chrome_switches.h"
21 #include "components/version_info/version_info.h"
22 #include "content/public/browser/notification_service.h"
23 #include "content/public/browser/notification_source.h"
24 #include "extensions/common/constants.h"
25 #include "ui/base/hit_test.h"
27 #if defined(USE_X11) && !defined(OS_CHROMEOS)
28 #include "base/environment.h"
29 #include "base/nix/xdg_util.h"
30 #include "ui/base/x/x11_util.h"
31 #endif
33 namespace {
34 // Maxmium width of a panel is based on a factor of the working area.
35 #if defined(OS_CHROMEOS)
36 // ChromeOS device screens are relatively small and limiting the width
37 // interferes with some apps (e.g. http://crbug.com/111121).
38 const double kPanelMaxWidthFactor = 0.80;
39 #else
40 const double kPanelMaxWidthFactor = 0.35;
41 #endif
43 // Maxmium height of a panel is based on a factor of the working area.
44 const double kPanelMaxHeightFactor = 0.5;
46 // Width to height ratio is used to compute the default width or height
47 // when only one value is provided.
48 const double kPanelDefaultWidthToHeightRatio = 1.62; // golden ratio
50 // The test code could call PanelManager::SetDisplaySettingsProviderForTesting
51 // to set this for testing purpose.
52 DisplaySettingsProvider* display_settings_provider_for_testing;
54 // The following comparers are used by std::list<>::sort to determine which
55 // stack or panel we want to seacrh first for adding new panel.
56 bool ComparePanelsByPosition(Panel* panel1, Panel* panel2) {
57 gfx::Rect bounds1 = panel1->GetBounds();
58 gfx::Rect bounds2 = panel2->GetBounds();
60 // When there're ties, the right-most stack will appear first.
61 if (bounds1.x() > bounds2.x())
62 return true;
63 if (bounds1.x() < bounds2.x())
64 return false;
66 // In the event of another draw, the top-most stack will appear first.
67 return bounds1.y() < bounds2.y();
70 bool ComparerNumberOfPanelsInStack(StackedPanelCollection* stack1,
71 StackedPanelCollection* stack2) {
72 // The stack with more panels will appear first.
73 int num_panels_in_stack1 = stack1->num_panels();
74 int num_panels_in_stack2 = stack2->num_panels();
75 if (num_panels_in_stack1 > num_panels_in_stack2)
76 return true;
77 if (num_panels_in_stack1 < num_panels_in_stack2)
78 return false;
80 DCHECK(num_panels_in_stack1);
82 return ComparePanelsByPosition(stack1->top_panel(), stack2->top_panel());
85 bool CompareDetachedPanels(Panel* panel1, Panel* panel2) {
86 return ComparePanelsByPosition(panel1, panel2);
89 } // namespace
91 // static
92 bool PanelManager::shorten_time_intervals_ = false;
94 // static
95 PanelManager* PanelManager::GetInstance() {
96 static base::LazyInstance<PanelManager> instance = LAZY_INSTANCE_INITIALIZER;
97 return instance.Pointer();
100 // static
101 void PanelManager::SetDisplaySettingsProviderForTesting(
102 DisplaySettingsProvider* provider) {
103 display_settings_provider_for_testing = provider;
106 // static
107 bool PanelManager::ShouldUsePanels(const std::string& extension_id) {
108 // If --enable-panels is on, always use panels.
109 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
110 switches::kEnablePanels))
111 return true;
113 #if defined(USE_X11) && !defined(OS_CHROMEOS)
114 // On Linux, panels are only supported on tested window managers.
115 ui::WindowManagerName wm_type = ui::GuessWindowManager();
116 if (wm_type != ui::WM_COMPIZ &&
117 wm_type != ui::WM_ICE_WM &&
118 wm_type != ui::WM_KWIN &&
119 wm_type != ui::WM_METACITY &&
120 wm_type != ui::WM_MUFFIN &&
121 wm_type != ui::WM_MUTTER &&
122 wm_type != ui::WM_XFWM4) {
123 return false;
125 #endif // USE_X11 && !OS_CHROMEOS
127 // Without --enable-panels, only support Hangouts.
128 for (const char* id : extension_misc::kHangoutsExtensionIds) {
129 if (extension_id == id)
130 return true;
133 return false;
136 // static
137 bool PanelManager::IsPanelStackingEnabled() {
138 // Stacked panel mode is not supported in linux-aura.
139 #if defined(OS_LINUX)
140 return false;
141 #else
142 return true;
143 #endif
146 // static
147 bool PanelManager::CanUseSystemMinimize() {
148 #if defined(USE_X11) && !defined(OS_CHROMEOS)
149 static base::nix::DesktopEnvironment desktop_env =
150 base::nix::DESKTOP_ENVIRONMENT_OTHER;
151 if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_OTHER) {
152 scoped_ptr<base::Environment> env(base::Environment::Create());
153 desktop_env = base::nix::GetDesktopEnvironment(env.get());
155 return desktop_env != base::nix::DESKTOP_ENVIRONMENT_UNITY;
156 #else
157 return true;
158 #endif
161 PanelManager::PanelManager()
162 : panel_mouse_watcher_(PanelMouseWatcher::Create()),
163 auto_sizing_enabled_(true) {
164 // DisplaySettingsProvider should be created before the creation of
165 // collections since some collection might depend on it.
166 if (display_settings_provider_for_testing)
167 display_settings_provider_.reset(display_settings_provider_for_testing);
168 else
169 display_settings_provider_.reset(DisplaySettingsProvider::Create());
170 display_settings_provider_->AddDisplayObserver(this);
172 detached_collection_.reset(new DetachedPanelCollection(this));
173 docked_collection_.reset(new DockedPanelCollection(this));
174 drag_controller_.reset(new PanelDragController(this));
175 resize_controller_.reset(new PanelResizeController(this));
178 PanelManager::~PanelManager() {
179 display_settings_provider_->RemoveDisplayObserver(this);
181 // Docked collection should be disposed explicitly before
182 // DisplaySettingsProvider is gone since docked collection needs to remove
183 // the observer from DisplaySettingsProvider.
184 docked_collection_.reset();
187 gfx::Point PanelManager::GetDefaultDetachedPanelOrigin() {
188 return detached_collection_->GetDefaultPanelOrigin();
191 void PanelManager::OnDisplayChanged() {
192 docked_collection_->OnDisplayChanged();
193 detached_collection_->OnDisplayChanged();
194 for (Stacks::const_iterator iter = stacks_.begin();
195 iter != stacks_.end(); iter++)
196 (*iter)->OnDisplayChanged();
199 void PanelManager::OnFullScreenModeChanged(bool is_full_screen) {
200 std::vector<Panel*> all_panels = panels();
201 for (std::vector<Panel*>::const_iterator iter = all_panels.begin();
202 iter != all_panels.end(); ++iter) {
203 (*iter)->FullScreenModeChanged(is_full_screen);
207 int PanelManager::GetMaxPanelWidth(const gfx::Rect& work_area) const {
208 return static_cast<int>(work_area.width() * kPanelMaxWidthFactor);
211 int PanelManager::GetMaxPanelHeight(const gfx::Rect& work_area) const {
212 return static_cast<int>(work_area.height() * kPanelMaxHeightFactor);
215 Panel* PanelManager::CreatePanel(const std::string& app_name,
216 Profile* profile,
217 const GURL& url,
218 const gfx::Rect& requested_bounds,
219 CreateMode mode) {
220 // Need to sync the display area if no panel is present. This is because:
221 // 1) Display area is not initialized until first panel is created.
222 // 2) On windows, display settings notification is tied to a window. When
223 // display settings are changed at the time that no panel exists, we do
224 // not receive any notification.
225 if (num_panels() == 0) {
226 display_settings_provider_->OnDisplaySettingsChanged();
227 display_settings_provider_->AddFullScreenObserver(this);
230 // Compute initial bounds for the panel.
231 int width = requested_bounds.width();
232 int height = requested_bounds.height();
233 if (width == 0)
234 width = height * kPanelDefaultWidthToHeightRatio;
235 else if (height == 0)
236 height = width / kPanelDefaultWidthToHeightRatio;
238 gfx::Rect work_area =
239 display_settings_provider_->GetWorkAreaMatching(requested_bounds);
240 gfx::Size min_size(panel::kPanelMinWidth, panel::kPanelMinHeight);
241 gfx::Size max_size(GetMaxPanelWidth(work_area), GetMaxPanelHeight(work_area));
242 if (width < min_size.width())
243 width = min_size.width();
244 else if (width > max_size.width())
245 width = max_size.width();
247 if (height < min_size.height())
248 height = min_size.height();
249 else if (height > max_size.height())
250 height = max_size.height();
252 // Create the panel.
253 Panel* panel = new Panel(profile, app_name, min_size, max_size);
255 // Find the appropriate panel collection to hold the new panel.
256 gfx::Rect adjusted_requested_bounds(
257 requested_bounds.x(), requested_bounds.y(), width, height);
258 PanelCollection::PositioningMask positioning_mask;
259 PanelCollection* collection = GetCollectionForNewPanel(
260 panel, adjusted_requested_bounds, mode, &positioning_mask);
262 // Let the panel collection decide the initial bounds.
263 gfx::Rect bounds = collection->GetInitialPanelBounds(
264 adjusted_requested_bounds);
265 bounds.AdjustToFit(work_area);
267 panel->Initialize(url, bounds, collection->UsesAlwaysOnTopPanels());
269 // Auto resizable feature is enabled only if no initial size is requested.
270 if (auto_sizing_enabled() && requested_bounds.width() == 0 &&
271 requested_bounds.height() == 0) {
272 panel->SetAutoResizable(true);
275 // Add the panel to the panel collection.
276 collection->AddPanel(panel, positioning_mask);
277 collection->UpdatePanelOnCollectionChange(panel);
279 return panel;
282 PanelCollection* PanelManager::GetCollectionForNewPanel(
283 Panel* new_panel,
284 const gfx::Rect& bounds,
285 CreateMode mode,
286 PanelCollection::PositioningMask* positioning_mask) {
287 if (mode == CREATE_AS_DOCKED) {
288 // Delay layout refreshes in case multiple panels are created within
289 // a short time of one another or the focus changes shortly after panel
290 // is created to avoid excessive screen redraws.
291 *positioning_mask = PanelCollection::DELAY_LAYOUT_REFRESH;
292 return docked_collection_.get();
295 DCHECK_EQ(CREATE_AS_DETACHED, mode);
296 *positioning_mask = PanelCollection::DEFAULT_POSITION;
298 // If the stacking support is not enabled, new panel will still be created as
299 // detached.
300 if (!IsPanelStackingEnabled())
301 return detached_collection_.get();
303 // If there're stacks, try to find a stack that can fit new panel.
304 if (!stacks_.empty()) {
305 // Perform the search as:
306 // 1) Search from the stack with more panels to the stack with least panels.
307 // 2) Amongs the stacks with same number of panels, search from the right-
308 // most stack to the left-most stack.
309 // 3) Among the stack with same number of panels and same x position,
310 // search from the top-most stack to the bottom-most stack.
311 // 4) If there is not enough space to fit new panel even with all inactive
312 // panels being collapsed, move to next stack.
313 stacks_.sort(ComparerNumberOfPanelsInStack);
314 for (Stacks::const_iterator iter = stacks_.begin();
315 iter != stacks_.end(); iter++) {
316 StackedPanelCollection* stack = *iter;
318 // Do not add to other stack that is from differnt extension or profile.
319 // Note that the check is based on bottom panel.
320 Panel* panel = stack->bottom_panel();
321 if (panel->profile() != new_panel->profile() ||
322 panel->extension_id() != new_panel->extension_id())
323 continue;
325 // Do not add to the stack that is minimized by the system.
326 if (stack->IsMinimized())
327 continue;
329 // Do not stack with the panel that is not shown in current virtual
330 // desktop.
331 if (!panel->IsShownOnActiveDesktop())
332 continue;
334 if (bounds.height() <= stack->GetMaximiumAvailableBottomSpace()) {
335 *positioning_mask = static_cast<PanelCollection::PositioningMask>(
336 *positioning_mask | PanelCollection::COLLAPSE_TO_FIT);
337 return stack;
342 // Then try to find a detached panel to which new panel can stack.
343 if (detached_collection_->num_panels()) {
344 // Perform the search as:
345 // 1) Search from the right-most detached panel to the left-most detached
346 // panel.
347 // 2) Among the detached panels with same x position, search from the
348 // top-most detached panel to the bottom-most deatched panel.
349 // 3) If there is not enough space beneath the detached panel, even by
350 // collapsing it if it is inactive, to fit new panel, move to next
351 // detached panel.
352 detached_collection_->SortPanels(CompareDetachedPanels);
354 for (DetachedPanelCollection::Panels::const_iterator iter =
355 detached_collection_->panels().begin();
356 iter != detached_collection_->panels().end(); ++iter) {
357 Panel* panel = *iter;
359 // Do not stack with other panel that is from differnt extension or
360 // profile.
361 if (panel->profile() != new_panel->profile() ||
362 panel->extension_id() != new_panel->extension_id())
363 continue;
365 // Do not stack with the panel that is minimized by the system.
366 if (panel->IsMinimizedBySystem())
367 continue;
369 // Do not stack with the panel that is not shown in the active desktop.
370 if (!panel->IsShownOnActiveDesktop())
371 continue;
373 gfx::Rect work_area =
374 display_settings_provider_->GetWorkAreaMatching(panel->GetBounds());
375 int max_available_space =
376 work_area.bottom() - panel->GetBounds().y() -
377 (panel->IsActive() ? panel->GetBounds().height()
378 : panel::kTitlebarHeight);
379 if (bounds.height() <= max_available_space) {
380 StackedPanelCollection* new_stack = CreateStack();
381 MovePanelToCollection(panel,
382 new_stack,
383 PanelCollection::DEFAULT_POSITION);
384 *positioning_mask = static_cast<PanelCollection::PositioningMask>(
385 *positioning_mask | PanelCollection::COLLAPSE_TO_FIT);
386 return new_stack;
391 return detached_collection_.get();
394 void PanelManager::OnPanelClosed(Panel* panel) {
395 if (num_panels() == 1) {
396 display_settings_provider_->RemoveFullScreenObserver(this);
399 drag_controller_->OnPanelClosed(panel);
400 resize_controller_->OnPanelClosed(panel);
402 // Note that we need to keep track of panel's collection since it will be
403 // gone once RemovePanel is called.
404 PanelCollection* collection = panel->collection();
405 collection->RemovePanel(panel, PanelCollection::PANEL_CLOSED);
407 // If only one panel is left in the stack, move it out of the stack.
408 // Also make sure that this detached panel will be expanded if not yet.
409 if (collection->type() == PanelCollection::STACKED) {
410 StackedPanelCollection* stack =
411 static_cast<StackedPanelCollection*>(collection);
412 DCHECK_GE(stack->num_panels(), 1);
413 if (stack->num_panels() == 1) {
414 Panel* top_panel = stack->top_panel();
415 MovePanelToCollection(top_panel,
416 detached_collection(),
417 PanelCollection::DEFAULT_POSITION);
418 if (top_panel->expansion_state() != Panel::EXPANDED)
419 top_panel->SetExpansionState(Panel::EXPANDED);
420 RemoveStack(stack);
424 content::NotificationService::current()->Notify(
425 chrome::NOTIFICATION_PANEL_CLOSED,
426 content::Source<Panel>(panel),
427 content::NotificationService::NoDetails());
430 StackedPanelCollection* PanelManager::CreateStack() {
431 StackedPanelCollection* stack = new StackedPanelCollection(this);
432 stacks_.push_back(stack);
433 return stack;
436 void PanelManager::RemoveStack(StackedPanelCollection* stack) {
437 DCHECK_EQ(0, stack->num_panels());
438 stacks_.remove(stack);
439 stack->CloseAll();
440 delete stack;
443 void PanelManager::StartDragging(Panel* panel,
444 const gfx::Point& mouse_location) {
445 drag_controller_->StartDragging(panel, mouse_location);
448 void PanelManager::Drag(const gfx::Point& mouse_location) {
449 drag_controller_->Drag(mouse_location);
452 void PanelManager::EndDragging(bool cancelled) {
453 drag_controller_->EndDragging(cancelled);
456 void PanelManager::StartResizingByMouse(Panel* panel,
457 const gfx::Point& mouse_location,
458 int component) {
459 if (panel->CanResizeByMouse() != panel::NOT_RESIZABLE &&
460 component != HTNOWHERE) {
461 resize_controller_->StartResizing(panel, mouse_location, component);
465 void PanelManager::ResizeByMouse(const gfx::Point& mouse_location) {
466 if (resize_controller_->IsResizing())
467 resize_controller_->Resize(mouse_location);
470 void PanelManager::EndResizingByMouse(bool cancelled) {
471 if (resize_controller_->IsResizing()) {
472 Panel* resized_panel = resize_controller_->EndResizing(cancelled);
473 if (!cancelled && resized_panel->collection())
474 resized_panel->collection()->RefreshLayout();
478 void PanelManager::OnPanelExpansionStateChanged(Panel* panel) {
479 panel->collection()->OnPanelExpansionStateChanged(panel);
482 void PanelManager::MovePanelToCollection(
483 Panel* panel,
484 PanelCollection* target_collection,
485 PanelCollection::PositioningMask positioning_mask) {
486 DCHECK(panel);
487 PanelCollection* current_collection = panel->collection();
488 DCHECK(current_collection);
489 DCHECK_NE(current_collection, target_collection);
490 current_collection->RemovePanel(panel,
491 PanelCollection::PANEL_CHANGED_COLLECTION);
493 target_collection->AddPanel(panel, positioning_mask);
494 target_collection->UpdatePanelOnCollectionChange(panel);
495 panel->SetAlwaysOnTop(target_collection->UsesAlwaysOnTopPanels());
498 bool PanelManager::ShouldBringUpTitlebars(int mouse_x, int mouse_y) const {
499 return docked_collection_->ShouldBringUpTitlebars(mouse_x, mouse_y);
502 void PanelManager::BringUpOrDownTitlebars(bool bring_up) {
503 docked_collection_->BringUpOrDownTitlebars(bring_up);
506 void PanelManager::CloseAll() {
507 DCHECK(!drag_controller_->is_dragging());
509 detached_collection_->CloseAll();
510 docked_collection_->CloseAll();
513 int PanelManager::num_panels() const {
514 int count = detached_collection_->num_panels() +
515 docked_collection_->num_panels();
516 for (Stacks::const_iterator iter = stacks_.begin();
517 iter != stacks_.end(); iter++)
518 count += (*iter)->num_panels();
519 return count;
522 std::vector<Panel*> PanelManager::panels() const {
523 std::vector<Panel*> panels;
524 for (DetachedPanelCollection::Panels::const_iterator iter =
525 detached_collection_->panels().begin();
526 iter != detached_collection_->panels().end(); ++iter)
527 panels.push_back(*iter);
528 for (DockedPanelCollection::Panels::const_iterator iter =
529 docked_collection_->panels().begin();
530 iter != docked_collection_->panels().end(); ++iter)
531 panels.push_back(*iter);
532 for (Stacks::const_iterator stack_iter = stacks_.begin();
533 stack_iter != stacks_.end(); stack_iter++) {
534 for (StackedPanelCollection::Panels::const_iterator iter =
535 (*stack_iter)->panels().begin();
536 iter != (*stack_iter)->panels().end(); ++iter) {
537 panels.push_back(*iter);
540 return panels;
543 std::vector<Panel*> PanelManager::GetDetachedAndStackedPanels() const {
544 std::vector<Panel*> panels;
545 for (DetachedPanelCollection::Panels::const_iterator iter =
546 detached_collection_->panels().begin();
547 iter != detached_collection_->panels().end(); ++iter)
548 panels.push_back(*iter);
549 for (Stacks::const_iterator stack_iter = stacks_.begin();
550 stack_iter != stacks_.end(); stack_iter++) {
551 for (StackedPanelCollection::Panels::const_iterator iter =
552 (*stack_iter)->panels().begin();
553 iter != (*stack_iter)->panels().end(); ++iter) {
554 panels.push_back(*iter);
557 return panels;
560 void PanelManager::SetMouseWatcher(PanelMouseWatcher* watcher) {
561 panel_mouse_watcher_.reset(watcher);
564 void PanelManager::OnPanelAnimationEnded(Panel* panel) {
565 content::NotificationService::current()->Notify(
566 chrome::NOTIFICATION_PANEL_BOUNDS_ANIMATIONS_FINISHED,
567 content::Source<Panel>(panel),
568 content::NotificationService::NoDetails());