Add a minor text member to ui::MenuModel.
[chromium-blink-merge.git] / chrome / browser / ui / panels / panel_manager.cc
blob837e30cfbcb894ebf3248fbe0d72d761d23ad504
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/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(TOOLKIT_GTK)
25 #include "base/environment.h"
26 #include "base/nix/xdg_util.h"
27 #include "ui/base/x/x11_util.h"
28 #endif
30 #if defined(OS_WIN)
31 #include "win8/util/win8_util.h"
32 #endif
34 namespace {
35 // Maxmium width of a panel is based on a factor of the working area.
36 #if defined(OS_CHROMEOS)
37 // ChromeOS device screens are relatively small and limiting the width
38 // interferes with some apps (e.g. http://crbug.com/111121).
39 const double kPanelMaxWidthFactor = 0.80;
40 #else
41 const double kPanelMaxWidthFactor = 0.35;
42 #endif
44 // Maxmium height of a panel is based on a factor of the working area.
45 const double kPanelMaxHeightFactor = 0.5;
47 // Width to height ratio is used to compute the default width or height
48 // when only one value is provided.
49 const double kPanelDefaultWidthToHeightRatio = 1.62; // golden ratio
51 // The test code could call PanelManager::SetDisplaySettingsProviderForTesting
52 // to set this for testing purpose.
53 DisplaySettingsProvider* display_settings_provider_for_testing;
55 // The following comparers are used by std::list<>::sort to determine which
56 // stack or panel we want to seacrh first for adding new panel.
57 bool ComparePanelsByPosition(Panel* panel1, Panel* panel2) {
58 gfx::Rect bounds1 = panel1->GetBounds();
59 gfx::Rect bounds2 = panel2->GetBounds();
61 // When there're ties, the right-most stack will appear first.
62 if (bounds1.x() > bounds2.x())
63 return true;
64 if (bounds1.x() < bounds2.x())
65 return false;
67 // In the event of another draw, the top-most stack will appear first.
68 return bounds1.y() < bounds2.y();
71 bool ComparerNumberOfPanelsInStack(StackedPanelCollection* stack1,
72 StackedPanelCollection* stack2) {
73 // The stack with more panels will appear first.
74 int num_panels_in_stack1 = stack1->num_panels();
75 int num_panels_in_stack2 = stack2->num_panels();
76 if (num_panels_in_stack1 > num_panels_in_stack2)
77 return true;
78 if (num_panels_in_stack1 < num_panels_in_stack2)
79 return false;
81 DCHECK(num_panels_in_stack1);
83 return ComparePanelsByPosition(stack1->top_panel(), stack2->top_panel());
86 bool CompareDetachedPanels(Panel* panel1, Panel* panel2) {
87 return ComparePanelsByPosition(panel1, panel2);
90 } // namespace
92 // static
93 bool PanelManager::shorten_time_intervals_ = false;
95 // static
96 PanelManager* PanelManager::GetInstance() {
97 static base::LazyInstance<PanelManager> instance = LAZY_INSTANCE_INITIALIZER;
98 return instance.Pointer();
101 // static
102 void PanelManager::SetDisplaySettingsProviderForTesting(
103 DisplaySettingsProvider* provider) {
104 display_settings_provider_for_testing = provider;
107 // static
108 bool PanelManager::ShouldUsePanels(const std::string& extension_id) {
109 #if defined(TOOLKIT_GTK)
110 // If --enable-panels is on, always use panels on Linux.
111 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnablePanels))
112 return true;
114 // Otherwise, 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 // TOOLKIT_GTK
127 #if defined(OS_WIN)
128 // No panels in Metro mode.
129 if (win8::IsSingleWindowMetroMode())
130 return false;
131 #endif // OS_WIN
133 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
134 if (channel == chrome::VersionInfo::CHANNEL_STABLE ||
135 channel == chrome::VersionInfo::CHANNEL_BETA) {
136 return CommandLine::ForCurrentProcess()->HasSwitch(
137 switches::kEnablePanels) ||
138 extension_id == std::string("nckgahadagoaajjgafhacjanaoiihapd") ||
139 extension_id == std::string("ljclpkphhpbpinifbeabbhlfddcpfdde") ||
140 extension_id == std::string("ppleadejekpmccmnpjdimmlfljlkdfej") ||
141 extension_id == std::string("eggnbpckecmjlblplehfpjjdhhidfdoj");
144 return true;
147 // static
148 bool PanelManager::IsPanelStackingEnabled() {
149 return true;
152 // static
153 bool PanelManager::CanUseSystemMinimize() {
154 #if defined(TOOLKIT_GTK)
155 static base::nix::DesktopEnvironment desktop_env =
156 base::nix::DESKTOP_ENVIRONMENT_OTHER;
157 if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_OTHER) {
158 scoped_ptr<base::Environment> env(base::Environment::Create());
159 desktop_env = base::nix::GetDesktopEnvironment(env.get());
161 return desktop_env != base::nix::DESKTOP_ENVIRONMENT_UNITY;
162 #else
163 return true;
164 #endif
167 PanelManager::PanelManager()
168 : panel_mouse_watcher_(PanelMouseWatcher::Create()),
169 auto_sizing_enabled_(true) {
170 // DisplaySettingsProvider should be created before the creation of
171 // collections since some collection might depend on it.
172 if (display_settings_provider_for_testing)
173 display_settings_provider_.reset(display_settings_provider_for_testing);
174 else
175 display_settings_provider_.reset(DisplaySettingsProvider::Create());
176 display_settings_provider_->AddDisplayObserver(this);
178 detached_collection_.reset(new DetachedPanelCollection(this));
179 docked_collection_.reset(new DockedPanelCollection(this));
180 drag_controller_.reset(new PanelDragController(this));
181 resize_controller_.reset(new PanelResizeController(this));
184 PanelManager::~PanelManager() {
185 display_settings_provider_->RemoveDisplayObserver(this);
187 // Docked collection should be disposed explicitly before
188 // DisplaySettingsProvider is gone since docked collection needs to remove
189 // the observer from DisplaySettingsProvider.
190 docked_collection_.reset();
193 gfx::Point PanelManager::GetDefaultDetachedPanelOrigin() {
194 return detached_collection_->GetDefaultPanelOrigin();
197 void PanelManager::OnDisplayChanged() {
198 docked_collection_->OnDisplayChanged();
199 detached_collection_->OnDisplayChanged();
200 for (Stacks::const_iterator iter = stacks_.begin();
201 iter != stacks_.end(); iter++)
202 (*iter)->OnDisplayChanged();
205 void PanelManager::OnFullScreenModeChanged(bool is_full_screen) {
206 std::vector<Panel*> all_panels = panels();
207 for (std::vector<Panel*>::const_iterator iter = all_panels.begin();
208 iter != all_panels.end(); ++iter) {
209 Panel* panel = *iter;
210 PanelCollection* panel_collection = panel->collection();
211 // When the panel is not always on top, there is no need to hide/show
212 // the panel in response to entering/leaving full-screen mode.
213 if (panel_collection && panel_collection->UsesAlwaysOnTopPanels())
214 panel->FullScreenModeChanged(is_full_screen);
218 int PanelManager::GetMaxPanelWidth(const gfx::Rect& work_area) const {
219 return static_cast<int>(work_area.width() * kPanelMaxWidthFactor);
222 int PanelManager::GetMaxPanelHeight(const gfx::Rect& work_area) const {
223 return static_cast<int>(work_area.height() * kPanelMaxHeightFactor);
226 Panel* PanelManager::CreatePanel(const std::string& app_name,
227 Profile* profile,
228 const GURL& url,
229 const gfx::Rect& requested_bounds,
230 CreateMode mode) {
231 // Need to sync the display area if no panel is present. This is because:
232 // 1) Display area is not initialized until first panel is created.
233 // 2) On windows, display settings notification is tied to a window. When
234 // display settings are changed at the time that no panel exists, we do
235 // not receive any notification.
236 if (num_panels() == 0) {
237 display_settings_provider_->OnDisplaySettingsChanged();
238 display_settings_provider_->AddFullScreenObserver(this);
241 // Compute initial bounds for the panel.
242 int width = requested_bounds.width();
243 int height = requested_bounds.height();
244 if (width == 0)
245 width = height * kPanelDefaultWidthToHeightRatio;
246 else if (height == 0)
247 height = width / kPanelDefaultWidthToHeightRatio;
249 gfx::Rect work_area =
250 display_settings_provider_->GetWorkAreaMatching(requested_bounds);
251 gfx::Size min_size(panel::kPanelMinWidth, panel::kPanelMinHeight);
252 gfx::Size max_size(GetMaxPanelWidth(work_area), GetMaxPanelHeight(work_area));
253 if (width < min_size.width())
254 width = min_size.width();
255 else if (width > max_size.width())
256 width = max_size.width();
258 if (height < min_size.height())
259 height = min_size.height();
260 else if (height > max_size.height())
261 height = max_size.height();
263 // Create the panel.
264 Panel* panel = new Panel(profile, app_name, min_size, max_size);
266 // Find the appropriate panel collection to hold the new panel.
267 gfx::Rect adjusted_requested_bounds(
268 requested_bounds.x(), requested_bounds.y(), width, height);
269 PanelCollection::PositioningMask positioning_mask;
270 PanelCollection* collection = GetCollectionForNewPanel(
271 panel, adjusted_requested_bounds, mode, &positioning_mask);
273 // Let the panel collection decide the initial bounds.
274 gfx::Rect bounds = collection->GetInitialPanelBounds(
275 adjusted_requested_bounds);
276 bounds.AdjustToFit(work_area);
278 panel->Initialize(url, bounds, collection->UsesAlwaysOnTopPanels());
280 // Auto resizable feature is enabled only if no initial size is requested.
281 if (auto_sizing_enabled() && requested_bounds.width() == 0 &&
282 requested_bounds.height() == 0) {
283 panel->SetAutoResizable(true);
286 // Add the panel to the panel collection.
287 collection->AddPanel(panel, positioning_mask);
288 collection->UpdatePanelOnCollectionChange(panel);
290 return panel;
293 PanelCollection* PanelManager::GetCollectionForNewPanel(
294 Panel* new_panel,
295 const gfx::Rect& bounds,
296 CreateMode mode,
297 PanelCollection::PositioningMask* positioning_mask) {
298 if (mode == CREATE_AS_DOCKED) {
299 // Delay layout refreshes in case multiple panels are created within
300 // a short time of one another or the focus changes shortly after panel
301 // is created to avoid excessive screen redraws.
302 *positioning_mask = PanelCollection::DELAY_LAYOUT_REFRESH;
303 return docked_collection_.get();
306 DCHECK_EQ(CREATE_AS_DETACHED, mode);
307 *positioning_mask = PanelCollection::DEFAULT_POSITION;
309 // If the stacking support is not enabled, new panel will still be created as
310 // detached.
311 if (!IsPanelStackingEnabled())
312 return detached_collection_.get();
314 // If there're stacks, try to find a stack that can fit new panel.
315 if (!stacks_.empty()) {
316 // Perform the search as:
317 // 1) Search from the stack with more panels to the stack with least panels.
318 // 2) Amongs the stacks with same number of panels, search from the right-
319 // most stack to the left-most stack.
320 // 3) Among the stack with same number of panels and same x position,
321 // search from the top-most stack to the bottom-most stack.
322 // 4) If there is not enough space to fit new panel even with all inactive
323 // panels being collapsed, move to next stack.
324 stacks_.sort(ComparerNumberOfPanelsInStack);
325 for (Stacks::const_iterator iter = stacks_.begin();
326 iter != stacks_.end(); iter++) {
327 StackedPanelCollection* stack = *iter;
329 // Do not add to other stack that is from differnt extension or profile.
330 // Note that the check is based on bottom panel.
331 Panel* panel = stack->bottom_panel();
332 if (panel->profile() != new_panel->profile() ||
333 panel->extension_id() != new_panel->extension_id())
334 continue;
336 // Do not add to the stack that is minimized by the system.
337 if (stack->IsMinimized())
338 continue;
340 // Do not stack with the panel that is not shown in current virtual
341 // desktop.
342 if (!panel->IsShownOnActiveDesktop())
343 continue;
345 if (bounds.height() <= stack->GetMaximiumAvailableBottomSpace()) {
346 *positioning_mask = static_cast<PanelCollection::PositioningMask>(
347 *positioning_mask | PanelCollection::COLLAPSE_TO_FIT);
348 return stack;
353 // Then try to find a detached panel to which new panel can stack.
354 if (detached_collection_->num_panels()) {
355 // Perform the search as:
356 // 1) Search from the right-most detached panel to the left-most detached
357 // panel.
358 // 2) Among the detached panels with same x position, search from the
359 // top-most detached panel to the bottom-most deatched panel.
360 // 3) If there is not enough space beneath the detached panel, even by
361 // collapsing it if it is inactive, to fit new panel, move to next
362 // detached panel.
363 detached_collection_->SortPanels(CompareDetachedPanels);
365 for (DetachedPanelCollection::Panels::const_iterator iter =
366 detached_collection_->panels().begin();
367 iter != detached_collection_->panels().end(); ++iter) {
368 Panel* panel = *iter;
370 // Do not stack with other panel that is from differnt extension or
371 // profile.
372 if (panel->profile() != new_panel->profile() ||
373 panel->extension_id() != new_panel->extension_id())
374 continue;
376 // Do not stack with the panel that is minimized by the system.
377 if (panel->IsMinimizedBySystem())
378 continue;
380 // Do not stack with the panel that is not shown in the active desktop.
381 if (!panel->IsShownOnActiveDesktop())
382 continue;
384 gfx::Rect work_area =
385 display_settings_provider_->GetWorkAreaMatching(panel->GetBounds());
386 int max_available_space =
387 work_area.bottom() - panel->GetBounds().y() -
388 (panel->IsActive() ? panel->GetBounds().height()
389 : panel::kTitlebarHeight);
390 if (bounds.height() <= max_available_space) {
391 StackedPanelCollection* new_stack = CreateStack();
392 MovePanelToCollection(panel,
393 new_stack,
394 PanelCollection::DEFAULT_POSITION);
395 *positioning_mask = static_cast<PanelCollection::PositioningMask>(
396 *positioning_mask | PanelCollection::COLLAPSE_TO_FIT);
397 return new_stack;
402 return detached_collection_.get();
405 void PanelManager::OnPanelClosed(Panel* panel) {
406 if (num_panels() == 1) {
407 display_settings_provider_->RemoveFullScreenObserver(this);
410 drag_controller_->OnPanelClosed(panel);
411 resize_controller_->OnPanelClosed(panel);
413 // Note that we need to keep track of panel's collection since it will be
414 // gone once RemovePanel is called.
415 PanelCollection* collection = panel->collection();
416 collection->RemovePanel(panel, PanelCollection::PANEL_CLOSED);
418 // If only one panel is left in the stack, move it out of the stack.
419 // Also make sure that this detached panel will be expanded if not yet.
420 if (collection->type() == PanelCollection::STACKED) {
421 StackedPanelCollection* stack =
422 static_cast<StackedPanelCollection*>(collection);
423 DCHECK_GE(stack->num_panels(), 1);
424 if (stack->num_panels() == 1) {
425 Panel* top_panel = stack->top_panel();
426 MovePanelToCollection(top_panel,
427 detached_collection(),
428 PanelCollection::DEFAULT_POSITION);
429 if (top_panel->expansion_state() != Panel::EXPANDED)
430 top_panel->SetExpansionState(Panel::EXPANDED);
431 RemoveStack(stack);
435 content::NotificationService::current()->Notify(
436 chrome::NOTIFICATION_PANEL_CLOSED,
437 content::Source<Panel>(panel),
438 content::NotificationService::NoDetails());
441 StackedPanelCollection* PanelManager::CreateStack() {
442 StackedPanelCollection* stack = new StackedPanelCollection(this);
443 stacks_.push_back(stack);
444 return stack;
447 void PanelManager::RemoveStack(StackedPanelCollection* stack) {
448 DCHECK_EQ(0, stack->num_panels());
449 stacks_.remove(stack);
450 stack->CloseAll();
451 delete stack;
454 void PanelManager::StartDragging(Panel* panel,
455 const gfx::Point& mouse_location) {
456 drag_controller_->StartDragging(panel, mouse_location);
459 void PanelManager::Drag(const gfx::Point& mouse_location) {
460 drag_controller_->Drag(mouse_location);
463 void PanelManager::EndDragging(bool cancelled) {
464 drag_controller_->EndDragging(cancelled);
467 void PanelManager::StartResizingByMouse(Panel* panel,
468 const gfx::Point& mouse_location,
469 panel::ResizingSides sides) {
470 if (panel->CanResizeByMouse() != panel::NOT_RESIZABLE &&
471 sides != panel::RESIZE_NONE)
472 resize_controller_->StartResizing(panel, mouse_location, sides);
475 void PanelManager::ResizeByMouse(const gfx::Point& mouse_location) {
476 if (resize_controller_->IsResizing())
477 resize_controller_->Resize(mouse_location);
480 void PanelManager::EndResizingByMouse(bool cancelled) {
481 if (resize_controller_->IsResizing()) {
482 Panel* resized_panel = resize_controller_->EndResizing(cancelled);
483 if (!cancelled && resized_panel->collection())
484 resized_panel->collection()->RefreshLayout();
488 void PanelManager::OnPanelExpansionStateChanged(Panel* panel) {
489 panel->collection()->OnPanelExpansionStateChanged(panel);
492 void PanelManager::MovePanelToCollection(
493 Panel* panel,
494 PanelCollection* target_collection,
495 PanelCollection::PositioningMask positioning_mask) {
496 DCHECK(panel);
497 PanelCollection* current_collection = panel->collection();
498 DCHECK(current_collection);
499 DCHECK_NE(current_collection, target_collection);
500 current_collection->RemovePanel(panel,
501 PanelCollection::PANEL_CHANGED_COLLECTION);
503 target_collection->AddPanel(panel, positioning_mask);
504 target_collection->UpdatePanelOnCollectionChange(panel);
505 panel->SetAlwaysOnTop(target_collection->UsesAlwaysOnTopPanels());
508 bool PanelManager::ShouldBringUpTitlebars(int mouse_x, int mouse_y) const {
509 return docked_collection_->ShouldBringUpTitlebars(mouse_x, mouse_y);
512 void PanelManager::BringUpOrDownTitlebars(bool bring_up) {
513 docked_collection_->BringUpOrDownTitlebars(bring_up);
516 void PanelManager::CloseAll() {
517 DCHECK(!drag_controller_->is_dragging());
519 detached_collection_->CloseAll();
520 docked_collection_->CloseAll();
523 int PanelManager::num_panels() const {
524 int count = detached_collection_->num_panels() +
525 docked_collection_->num_panels();
526 for (Stacks::const_iterator iter = stacks_.begin();
527 iter != stacks_.end(); iter++)
528 count += (*iter)->num_panels();
529 return count;
532 std::vector<Panel*> PanelManager::panels() const {
533 std::vector<Panel*> panels;
534 for (DetachedPanelCollection::Panels::const_iterator iter =
535 detached_collection_->panels().begin();
536 iter != detached_collection_->panels().end(); ++iter)
537 panels.push_back(*iter);
538 for (DockedPanelCollection::Panels::const_iterator iter =
539 docked_collection_->panels().begin();
540 iter != docked_collection_->panels().end(); ++iter)
541 panels.push_back(*iter);
542 for (Stacks::const_iterator stack_iter = stacks_.begin();
543 stack_iter != stacks_.end(); stack_iter++) {
544 for (StackedPanelCollection::Panels::const_iterator iter =
545 (*stack_iter)->panels().begin();
546 iter != (*stack_iter)->panels().end(); ++iter) {
547 panels.push_back(*iter);
550 return panels;
553 std::vector<Panel*> PanelManager::GetDetachedAndStackedPanels() const {
554 std::vector<Panel*> panels;
555 for (DetachedPanelCollection::Panels::const_iterator iter =
556 detached_collection_->panels().begin();
557 iter != detached_collection_->panels().end(); ++iter)
558 panels.push_back(*iter);
559 for (Stacks::const_iterator stack_iter = stacks_.begin();
560 stack_iter != stacks_.end(); stack_iter++) {
561 for (StackedPanelCollection::Panels::const_iterator iter =
562 (*stack_iter)->panels().begin();
563 iter != (*stack_iter)->panels().end(); ++iter) {
564 panels.push_back(*iter);
567 return panels;
570 void PanelManager::SetMouseWatcher(PanelMouseWatcher* watcher) {
571 panel_mouse_watcher_.reset(watcher);
574 void PanelManager::OnPanelAnimationEnded(Panel* panel) {
575 content::NotificationService::current()->Notify(
576 chrome::NOTIFICATION_PANEL_BOUNDS_ANIMATIONS_FINISHED,
577 content::Source<Panel>(panel),
578 content::NotificationService::NoDetails());