Home | History | Annotate | Download | only in panels
      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