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