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"
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/chrome_switches.h"
20 #include "chrome/common/chrome_version_info.h"
21 #include "content/public/browser/notification_service.h"
22 #include "content/public/browser/notification_source.h"
24 #if defined(USE_X11) && !defined(OS_CHROMEOS)
25 #include "base/environment.h"
26 #include "base/nix/xdg_util.h"
27 #include "ui/base/x/x11_util.h"
31 // Maxmium width of a panel is based on a factor of the working area.
32 #if defined(OS_CHROMEOS)
33 // ChromeOS device screens are relatively small and limiting the width
34 // interferes with some apps (e.g. http://crbug.com/111121).
35 const double kPanelMaxWidthFactor
= 0.80;
37 const double kPanelMaxWidthFactor
= 0.35;
40 // Maxmium height of a panel is based on a factor of the working area.
41 const double kPanelMaxHeightFactor
= 0.5;
43 // Width to height ratio is used to compute the default width or height
44 // when only one value is provided.
45 const double kPanelDefaultWidthToHeightRatio
= 1.62; // golden ratio
47 // The test code could call PanelManager::SetDisplaySettingsProviderForTesting
48 // to set this for testing purpose.
49 DisplaySettingsProvider
* display_settings_provider_for_testing
;
51 // The following comparers are used by std::list<>::sort to determine which
52 // stack or panel we want to seacrh first for adding new panel.
53 bool ComparePanelsByPosition(Panel
* panel1
, Panel
* panel2
) {
54 gfx::Rect bounds1
= panel1
->GetBounds();
55 gfx::Rect bounds2
= panel2
->GetBounds();
57 // When there're ties, the right-most stack will appear first.
58 if (bounds1
.x() > bounds2
.x())
60 if (bounds1
.x() < bounds2
.x())
63 // In the event of another draw, the top-most stack will appear first.
64 return bounds1
.y() < bounds2
.y();
67 bool ComparerNumberOfPanelsInStack(StackedPanelCollection
* stack1
,
68 StackedPanelCollection
* stack2
) {
69 // The stack with more panels will appear first.
70 int num_panels_in_stack1
= stack1
->num_panels();
71 int num_panels_in_stack2
= stack2
->num_panels();
72 if (num_panels_in_stack1
> num_panels_in_stack2
)
74 if (num_panels_in_stack1
< num_panels_in_stack2
)
77 DCHECK(num_panels_in_stack1
);
79 return ComparePanelsByPosition(stack1
->top_panel(), stack2
->top_panel());
82 bool CompareDetachedPanels(Panel
* panel1
, Panel
* panel2
) {
83 return ComparePanelsByPosition(panel1
, panel2
);
89 bool PanelManager::shorten_time_intervals_
= false;
92 PanelManager
* PanelManager::GetInstance() {
93 static base::LazyInstance
<PanelManager
> instance
= LAZY_INSTANCE_INITIALIZER
;
94 return instance
.Pointer();
98 void PanelManager::SetDisplaySettingsProviderForTesting(
99 DisplaySettingsProvider
* provider
) {
100 display_settings_provider_for_testing
= provider
;
104 bool PanelManager::ShouldUsePanels(const std::string
& extension_id
) {
105 #if defined(USE_X11) && !defined(OS_CHROMEOS)
106 // If --enable-panels is on, always use panels on Linux.
107 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnablePanels
))
110 // Otherwise, panels are only supported on tested window managers.
111 ui::WindowManagerName wm_type
= ui::GuessWindowManager();
112 if (wm_type
!= ui::WM_COMPIZ
&&
113 wm_type
!= ui::WM_ICE_WM
&&
114 wm_type
!= ui::WM_KWIN
&&
115 wm_type
!= ui::WM_METACITY
&&
116 wm_type
!= ui::WM_MUFFIN
&&
117 wm_type
!= ui::WM_MUTTER
&&
118 wm_type
!= ui::WM_XFWM4
) {
121 #endif // USE_X11 && !OS_CHROMEOS
123 chrome::VersionInfo::Channel channel
= chrome::VersionInfo::GetChannel();
124 if (channel
== chrome::VersionInfo::CHANNEL_STABLE
||
125 channel
== chrome::VersionInfo::CHANNEL_BETA
) {
126 return CommandLine::ForCurrentProcess()->HasSwitch(
127 switches::kEnablePanels
) ||
128 extension_id
== std::string("nckgahadagoaajjgafhacjanaoiihapd") ||
129 extension_id
== std::string("ljclpkphhpbpinifbeabbhlfddcpfdde") ||
130 extension_id
== std::string("ppleadejekpmccmnpjdimmlfljlkdfej") ||
131 extension_id
== std::string("eggnbpckecmjlblplehfpjjdhhidfdoj");
138 bool PanelManager::IsPanelStackingEnabled() {
139 // Stacked panel mode is not supported in linux-aura.
140 #if defined(OS_LINUX) && !defined(TOOLKIT_GTK)
148 bool PanelManager::CanUseSystemMinimize() {
149 #if defined(USE_X11) && !defined(OS_CHROMEOS)
150 static base::nix::DesktopEnvironment desktop_env
=
151 base::nix::DESKTOP_ENVIRONMENT_OTHER
;
152 if (desktop_env
== base::nix::DESKTOP_ENVIRONMENT_OTHER
) {
153 scoped_ptr
<base::Environment
> env(base::Environment::Create());
154 desktop_env
= base::nix::GetDesktopEnvironment(env
.get());
156 return desktop_env
!= base::nix::DESKTOP_ENVIRONMENT_UNITY
;
162 PanelManager::PanelManager()
163 : panel_mouse_watcher_(PanelMouseWatcher::Create()),
164 auto_sizing_enabled_(true) {
165 // DisplaySettingsProvider should be created before the creation of
166 // collections since some collection might depend on it.
167 if (display_settings_provider_for_testing
)
168 display_settings_provider_
.reset(display_settings_provider_for_testing
);
170 display_settings_provider_
.reset(DisplaySettingsProvider::Create());
171 display_settings_provider_
->AddDisplayObserver(this);
173 detached_collection_
.reset(new DetachedPanelCollection(this));
174 docked_collection_
.reset(new DockedPanelCollection(this));
175 drag_controller_
.reset(new PanelDragController(this));
176 resize_controller_
.reset(new PanelResizeController(this));
179 PanelManager::~PanelManager() {
180 display_settings_provider_
->RemoveDisplayObserver(this);
182 // Docked collection should be disposed explicitly before
183 // DisplaySettingsProvider is gone since docked collection needs to remove
184 // the observer from DisplaySettingsProvider.
185 docked_collection_
.reset();
188 gfx::Point
PanelManager::GetDefaultDetachedPanelOrigin() {
189 return detached_collection_
->GetDefaultPanelOrigin();
192 void PanelManager::OnDisplayChanged() {
193 docked_collection_
->OnDisplayChanged();
194 detached_collection_
->OnDisplayChanged();
195 for (Stacks::const_iterator iter
= stacks_
.begin();
196 iter
!= stacks_
.end(); iter
++)
197 (*iter
)->OnDisplayChanged();
200 void PanelManager::OnFullScreenModeChanged(bool is_full_screen
) {
201 std::vector
<Panel
*> all_panels
= panels();
202 for (std::vector
<Panel
*>::const_iterator iter
= all_panels
.begin();
203 iter
!= all_panels
.end(); ++iter
) {
204 (*iter
)->FullScreenModeChanged(is_full_screen
);
208 int PanelManager::GetMaxPanelWidth(const gfx::Rect
& work_area
) const {
209 return static_cast<int>(work_area
.width() * kPanelMaxWidthFactor
);
212 int PanelManager::GetMaxPanelHeight(const gfx::Rect
& work_area
) const {
213 return static_cast<int>(work_area
.height() * kPanelMaxHeightFactor
);
216 Panel
* PanelManager::CreatePanel(const std::string
& app_name
,
219 const gfx::Rect
& requested_bounds
,
221 // Need to sync the display area if no panel is present. This is because:
222 // 1) Display area is not initialized until first panel is created.
223 // 2) On windows, display settings notification is tied to a window. When
224 // display settings are changed at the time that no panel exists, we do
225 // not receive any notification.
226 if (num_panels() == 0) {
227 display_settings_provider_
->OnDisplaySettingsChanged();
228 display_settings_provider_
->AddFullScreenObserver(this);
231 // Compute initial bounds for the panel.
232 int width
= requested_bounds
.width();
233 int height
= requested_bounds
.height();
235 width
= height
* kPanelDefaultWidthToHeightRatio
;
236 else if (height
== 0)
237 height
= width
/ kPanelDefaultWidthToHeightRatio
;
239 gfx::Rect work_area
=
240 display_settings_provider_
->GetWorkAreaMatching(requested_bounds
);
241 gfx::Size
min_size(panel::kPanelMinWidth
, panel::kPanelMinHeight
);
242 gfx::Size
max_size(GetMaxPanelWidth(work_area
), GetMaxPanelHeight(work_area
));
243 if (width
< min_size
.width())
244 width
= min_size
.width();
245 else if (width
> max_size
.width())
246 width
= max_size
.width();
248 if (height
< min_size
.height())
249 height
= min_size
.height();
250 else if (height
> max_size
.height())
251 height
= max_size
.height();
254 Panel
* panel
= new Panel(profile
, app_name
, min_size
, max_size
);
256 // Find the appropriate panel collection to hold the new panel.
257 gfx::Rect
adjusted_requested_bounds(
258 requested_bounds
.x(), requested_bounds
.y(), width
, height
);
259 PanelCollection::PositioningMask positioning_mask
;
260 PanelCollection
* collection
= GetCollectionForNewPanel(
261 panel
, adjusted_requested_bounds
, mode
, &positioning_mask
);
263 // Let the panel collection decide the initial bounds.
264 gfx::Rect bounds
= collection
->GetInitialPanelBounds(
265 adjusted_requested_bounds
);
266 bounds
.AdjustToFit(work_area
);
268 panel
->Initialize(url
, bounds
, collection
->UsesAlwaysOnTopPanels());
270 // Auto resizable feature is enabled only if no initial size is requested.
271 if (auto_sizing_enabled() && requested_bounds
.width() == 0 &&
272 requested_bounds
.height() == 0) {
273 panel
->SetAutoResizable(true);
276 // Add the panel to the panel collection.
277 collection
->AddPanel(panel
, positioning_mask
);
278 collection
->UpdatePanelOnCollectionChange(panel
);
283 PanelCollection
* PanelManager::GetCollectionForNewPanel(
285 const gfx::Rect
& bounds
,
287 PanelCollection::PositioningMask
* positioning_mask
) {
288 if (mode
== CREATE_AS_DOCKED
) {
289 // Delay layout refreshes in case multiple panels are created within
290 // a short time of one another or the focus changes shortly after panel
291 // is created to avoid excessive screen redraws.
292 *positioning_mask
= PanelCollection::DELAY_LAYOUT_REFRESH
;
293 return docked_collection_
.get();
296 DCHECK_EQ(CREATE_AS_DETACHED
, mode
);
297 *positioning_mask
= PanelCollection::DEFAULT_POSITION
;
299 // If the stacking support is not enabled, new panel will still be created as
301 if (!IsPanelStackingEnabled())
302 return detached_collection_
.get();
304 // If there're stacks, try to find a stack that can fit new panel.
305 if (!stacks_
.empty()) {
306 // Perform the search as:
307 // 1) Search from the stack with more panels to the stack with least panels.
308 // 2) Amongs the stacks with same number of panels, search from the right-
309 // most stack to the left-most stack.
310 // 3) Among the stack with same number of panels and same x position,
311 // search from the top-most stack to the bottom-most stack.
312 // 4) If there is not enough space to fit new panel even with all inactive
313 // panels being collapsed, move to next stack.
314 stacks_
.sort(ComparerNumberOfPanelsInStack
);
315 for (Stacks::const_iterator iter
= stacks_
.begin();
316 iter
!= stacks_
.end(); iter
++) {
317 StackedPanelCollection
* stack
= *iter
;
319 // Do not add to other stack that is from differnt extension or profile.
320 // Note that the check is based on bottom panel.
321 Panel
* panel
= stack
->bottom_panel();
322 if (panel
->profile() != new_panel
->profile() ||
323 panel
->extension_id() != new_panel
->extension_id())
326 // Do not add to the stack that is minimized by the system.
327 if (stack
->IsMinimized())
330 // Do not stack with the panel that is not shown in current virtual
332 if (!panel
->IsShownOnActiveDesktop())
335 if (bounds
.height() <= stack
->GetMaximiumAvailableBottomSpace()) {
336 *positioning_mask
= static_cast<PanelCollection::PositioningMask
>(
337 *positioning_mask
| PanelCollection::COLLAPSE_TO_FIT
);
343 // Then try to find a detached panel to which new panel can stack.
344 if (detached_collection_
->num_panels()) {
345 // Perform the search as:
346 // 1) Search from the right-most detached panel to the left-most detached
348 // 2) Among the detached panels with same x position, search from the
349 // top-most detached panel to the bottom-most deatched panel.
350 // 3) If there is not enough space beneath the detached panel, even by
351 // collapsing it if it is inactive, to fit new panel, move to next
353 detached_collection_
->SortPanels(CompareDetachedPanels
);
355 for (DetachedPanelCollection::Panels::const_iterator iter
=
356 detached_collection_
->panels().begin();
357 iter
!= detached_collection_
->panels().end(); ++iter
) {
358 Panel
* panel
= *iter
;
360 // Do not stack with other panel that is from differnt extension or
362 if (panel
->profile() != new_panel
->profile() ||
363 panel
->extension_id() != new_panel
->extension_id())
366 // Do not stack with the panel that is minimized by the system.
367 if (panel
->IsMinimizedBySystem())
370 // Do not stack with the panel that is not shown in the active desktop.
371 if (!panel
->IsShownOnActiveDesktop())
374 gfx::Rect work_area
=
375 display_settings_provider_
->GetWorkAreaMatching(panel
->GetBounds());
376 int max_available_space
=
377 work_area
.bottom() - panel
->GetBounds().y() -
378 (panel
->IsActive() ? panel
->GetBounds().height()
379 : panel::kTitlebarHeight
);
380 if (bounds
.height() <= max_available_space
) {
381 StackedPanelCollection
* new_stack
= CreateStack();
382 MovePanelToCollection(panel
,
384 PanelCollection::DEFAULT_POSITION
);
385 *positioning_mask
= static_cast<PanelCollection::PositioningMask
>(
386 *positioning_mask
| PanelCollection::COLLAPSE_TO_FIT
);
392 return detached_collection_
.get();
395 void PanelManager::OnPanelClosed(Panel
* panel
) {
396 if (num_panels() == 1) {
397 display_settings_provider_
->RemoveFullScreenObserver(this);
400 drag_controller_
->OnPanelClosed(panel
);
401 resize_controller_
->OnPanelClosed(panel
);
403 // Note that we need to keep track of panel's collection since it will be
404 // gone once RemovePanel is called.
405 PanelCollection
* collection
= panel
->collection();
406 collection
->RemovePanel(panel
, PanelCollection::PANEL_CLOSED
);
408 // If only one panel is left in the stack, move it out of the stack.
409 // Also make sure that this detached panel will be expanded if not yet.
410 if (collection
->type() == PanelCollection::STACKED
) {
411 StackedPanelCollection
* stack
=
412 static_cast<StackedPanelCollection
*>(collection
);
413 DCHECK_GE(stack
->num_panels(), 1);
414 if (stack
->num_panels() == 1) {
415 Panel
* top_panel
= stack
->top_panel();
416 MovePanelToCollection(top_panel
,
417 detached_collection(),
418 PanelCollection::DEFAULT_POSITION
);
419 if (top_panel
->expansion_state() != Panel::EXPANDED
)
420 top_panel
->SetExpansionState(Panel::EXPANDED
);
425 content::NotificationService::current()->Notify(
426 chrome::NOTIFICATION_PANEL_CLOSED
,
427 content::Source
<Panel
>(panel
),
428 content::NotificationService::NoDetails());
431 StackedPanelCollection
* PanelManager::CreateStack() {
432 StackedPanelCollection
* stack
= new StackedPanelCollection(this);
433 stacks_
.push_back(stack
);
437 void PanelManager::RemoveStack(StackedPanelCollection
* stack
) {
438 DCHECK_EQ(0, stack
->num_panels());
439 stacks_
.remove(stack
);
444 void PanelManager::StartDragging(Panel
* panel
,
445 const gfx::Point
& mouse_location
) {
446 drag_controller_
->StartDragging(panel
, mouse_location
);
449 void PanelManager::Drag(const gfx::Point
& mouse_location
) {
450 drag_controller_
->Drag(mouse_location
);
453 void PanelManager::EndDragging(bool cancelled
) {
454 drag_controller_
->EndDragging(cancelled
);
457 void PanelManager::StartResizingByMouse(Panel
* panel
,
458 const gfx::Point
& mouse_location
,
459 panel::ResizingSides sides
) {
460 if (panel
->CanResizeByMouse() != panel::NOT_RESIZABLE
&&
461 sides
!= panel::RESIZE_NONE
)
462 resize_controller_
->StartResizing(panel
, mouse_location
, sides
);
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(
484 PanelCollection
* target_collection
,
485 PanelCollection::PositioningMask positioning_mask
) {
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();
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
);
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
);
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());