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/panel_manager.h" 6 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" 23 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 29 30 #if defined(OS_WIN) 31 #include "win8/util/win8_util.h" 32 #endif 33 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 43 44 // Maxmium height of a panel is based on a factor of the working area. 45 const double kPanelMaxHeightFactor = 0.5; 46 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 50 51 // The test code could call PanelManager::SetDisplaySettingsProviderForTesting 52 // to set this for testing purpose. 53 DisplaySettingsProvider* display_settings_provider_for_testing; 54 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(); 60 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; 66 67 // In the event of another draw, the top-most stack will appear first. 68 return bounds1.y() < bounds2.y(); 69 } 70 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; 80 81 DCHECK(num_panels_in_stack1); 82 83 return ComparePanelsByPosition(stack1->top_panel(), stack2->top_panel()); 84 } 85 86 bool CompareDetachedPanels(Panel* panel1, Panel* panel2) { 87 return ComparePanelsByPosition(panel1, panel2); 88 } 89 90 } // namespace 91 92 // static 93 bool PanelManager::shorten_time_intervals_ = false; 94 95 // static 96 PanelManager* PanelManager::GetInstance() { 97 static base::LazyInstance<PanelManager> instance = LAZY_INSTANCE_INITIALIZER; 98 return instance.Pointer(); 99 } 100 101 // static 102 void PanelManager::SetDisplaySettingsProviderForTesting( 103 DisplaySettingsProvider* provider) { 104 display_settings_provider_for_testing = provider; 105 } 106 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; 113 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; 124 } 125 #endif // TOOLKIT_GTK 126 127 #if defined(OS_WIN) 128 // No panels in Metro mode. 129 if (win8::IsSingleWindowMetroMode()) 130 return false; 131 #endif // OS_WIN 132 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"); 142 } 143 144 return true; 145 } 146 147 // static 148 bool PanelManager::IsPanelStackingEnabled() { 149 return true; 150 } 151 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()); 160 } 161 return desktop_env != base::nix::DESKTOP_ENVIRONMENT_UNITY; 162 #else 163 return true; 164 #endif 165 } 166 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); 177 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)); 182 } 183 184 PanelManager::~PanelManager() { 185 display_settings_provider_->RemoveDisplayObserver(this); 186 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(); 191 } 192 193 gfx::Point PanelManager::GetDefaultDetachedPanelOrigin() { 194 return detached_collection_->GetDefaultPanelOrigin(); 195 } 196 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(); 203 } 204 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); 215 } 216 } 217 218 int PanelManager::GetMaxPanelWidth(const gfx::Rect& work_area) const { 219 return static_cast<int>(work_area.width() * kPanelMaxWidthFactor); 220 } 221 222 int PanelManager::GetMaxPanelHeight(const gfx::Rect& work_area) const { 223 return static_cast<int>(work_area.height() * kPanelMaxHeightFactor); 224 } 225 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); 239 } 240 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; 248 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(); 257 258 if (height < min_size.height()) 259 height = min_size.height(); 260 else if (height > max_size.height()) 261 height = max_size.height(); 262 263 // Create the panel. 264 Panel* panel = new Panel(profile, app_name, min_size, max_size); 265 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); 272 273 // Let the panel collection decide the initial bounds. 274 gfx::Rect bounds = collection->GetInitialPanelBounds( 275 adjusted_requested_bounds); 276 bounds.AdjustToFit(work_area); 277 278 panel->Initialize(url, bounds, collection->UsesAlwaysOnTopPanels()); 279 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); 284 } 285 286 // Add the panel to the panel collection. 287 collection->AddPanel(panel, positioning_mask); 288 collection->UpdatePanelOnCollectionChange(panel); 289 290 return panel; 291 } 292 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(); 304 } 305 306 DCHECK_EQ(CREATE_AS_DETACHED, mode); 307 *positioning_mask = PanelCollection::DEFAULT_POSITION; 308 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(); 313 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; 328 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; 335 336 // Do not add to the stack that is minimized by the system. 337 if (stack->IsMinimized()) 338 continue; 339 340 // Do not stack with the panel that is not shown in current virtual 341 // desktop. 342 if (!panel->IsShownOnActiveDesktop()) 343 continue; 344 345 if (bounds.height() <= stack->GetMaximiumAvailableBottomSpace()) { 346 *positioning_mask = static_cast<PanelCollection::PositioningMask>( 347 *positioning_mask | PanelCollection::COLLAPSE_TO_FIT); 348 return stack; 349 } 350 } 351 } 352 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); 364 365 for (DetachedPanelCollection::Panels::const_iterator iter = 366 detached_collection_->panels().begin(); 367 iter != detached_collection_->panels().end(); ++iter) { 368 Panel* panel = *iter; 369 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; 375 376 // Do not stack with the panel that is minimized by the system. 377 if (panel->IsMinimizedBySystem()) 378 continue; 379 380 // Do not stack with the panel that is not shown in the active desktop. 381 if (!panel->IsShownOnActiveDesktop()) 382 continue; 383 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; 398 } 399 } 400 } 401 402 return detached_collection_.get(); 403 } 404 405 void PanelManager::OnPanelClosed(Panel* panel) { 406 if (num_panels() == 1) { 407 display_settings_provider_->RemoveFullScreenObserver(this); 408 } 409 410 drag_controller_->OnPanelClosed(panel); 411 resize_controller_->OnPanelClosed(panel); 412 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); 417 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); 432 } 433 } 434 435 content::NotificationService::current()->Notify( 436 chrome::NOTIFICATION_PANEL_CLOSED, 437 content::Source<Panel>(panel), 438 content::NotificationService::NoDetails()); 439 } 440 441 StackedPanelCollection* PanelManager::CreateStack() { 442 StackedPanelCollection* stack = new StackedPanelCollection(this); 443 stacks_.push_back(stack); 444 return stack; 445 } 446 447 void PanelManager::RemoveStack(StackedPanelCollection* stack) { 448 DCHECK_EQ(0, stack->num_panels()); 449 stacks_.remove(stack); 450 stack->CloseAll(); 451 delete stack; 452 } 453 454 void PanelManager::StartDragging(Panel* panel, 455 const gfx::Point& mouse_location) { 456 drag_controller_->StartDragging(panel, mouse_location); 457 } 458 459 void PanelManager::Drag(const gfx::Point& mouse_location) { 460 drag_controller_->Drag(mouse_location); 461 } 462 463 void PanelManager::EndDragging(bool cancelled) { 464 drag_controller_->EndDragging(cancelled); 465 } 466 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); 473 } 474 475 void PanelManager::ResizeByMouse(const gfx::Point& mouse_location) { 476 if (resize_controller_->IsResizing()) 477 resize_controller_->Resize(mouse_location); 478 } 479 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(); 485 } 486 } 487 488 void PanelManager::OnPanelExpansionStateChanged(Panel* panel) { 489 panel->collection()->OnPanelExpansionStateChanged(panel); 490 } 491 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); 502 503 target_collection->AddPanel(panel, positioning_mask); 504 target_collection->UpdatePanelOnCollectionChange(panel); 505 panel->SetAlwaysOnTop(target_collection->UsesAlwaysOnTopPanels()); 506 } 507 508 bool PanelManager::ShouldBringUpTitlebars(int mouse_x, int mouse_y) const { 509 return docked_collection_->ShouldBringUpTitlebars(mouse_x, mouse_y); 510 } 511 512 void PanelManager::BringUpOrDownTitlebars(bool bring_up) { 513 docked_collection_->BringUpOrDownTitlebars(bring_up); 514 } 515 516 void PanelManager::CloseAll() { 517 DCHECK(!drag_controller_->is_dragging()); 518 519 detached_collection_->CloseAll(); 520 docked_collection_->CloseAll(); 521 } 522 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; 530 } 531 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); 548 } 549 } 550 return panels; 551 } 552 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); 565 } 566 } 567 return panels; 568 } 569 570 void PanelManager::SetMouseWatcher(PanelMouseWatcher* watcher) { 571 panel_mouse_watcher_.reset(watcher); 572 } 573 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()); 579 } 580