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 
     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     (*iter)->FullScreenModeChanged(is_full_screen);
    210   }
    211 }
    212 
    213 int PanelManager::GetMaxPanelWidth(const gfx::Rect& work_area) const {
    214   return static_cast<int>(work_area.width() * kPanelMaxWidthFactor);
    215 }
    216 
    217 int PanelManager::GetMaxPanelHeight(const gfx::Rect& work_area) const {
    218   return static_cast<int>(work_area.height() * kPanelMaxHeightFactor);
    219 }
    220 
    221 Panel* PanelManager::CreatePanel(const std::string& app_name,
    222                                  Profile* profile,
    223                                  const GURL& url,
    224                                  const gfx::Rect& requested_bounds,
    225                                  CreateMode mode) {
    226   // Need to sync the display area if no panel is present. This is because:
    227   // 1) Display area is not initialized until first panel is created.
    228   // 2) On windows, display settings notification is tied to a window. When
    229   //    display settings are changed at the time that no panel exists, we do
    230   //    not receive any notification.
    231   if (num_panels() == 0) {
    232     display_settings_provider_->OnDisplaySettingsChanged();
    233     display_settings_provider_->AddFullScreenObserver(this);
    234   }
    235 
    236   // Compute initial bounds for the panel.
    237   int width = requested_bounds.width();
    238   int height = requested_bounds.height();
    239   if (width == 0)
    240     width = height * kPanelDefaultWidthToHeightRatio;
    241   else if (height == 0)
    242     height = width / kPanelDefaultWidthToHeightRatio;
    243 
    244   gfx::Rect work_area =
    245       display_settings_provider_->GetWorkAreaMatching(requested_bounds);
    246   gfx::Size min_size(panel::kPanelMinWidth, panel::kPanelMinHeight);
    247   gfx::Size max_size(GetMaxPanelWidth(work_area), GetMaxPanelHeight(work_area));
    248   if (width < min_size.width())
    249     width = min_size.width();
    250   else if (width > max_size.width())
    251     width = max_size.width();
    252 
    253   if (height < min_size.height())
    254     height = min_size.height();
    255   else if (height > max_size.height())
    256     height = max_size.height();
    257 
    258   // Create the panel.
    259   Panel* panel = new Panel(profile, app_name, min_size, max_size);
    260 
    261   // Find the appropriate panel collection to hold the new panel.
    262   gfx::Rect adjusted_requested_bounds(
    263       requested_bounds.x(), requested_bounds.y(), width, height);
    264   PanelCollection::PositioningMask positioning_mask;
    265   PanelCollection* collection = GetCollectionForNewPanel(
    266       panel, adjusted_requested_bounds, mode, &positioning_mask);
    267 
    268   // Let the panel collection decide the initial bounds.
    269   gfx::Rect bounds = collection->GetInitialPanelBounds(
    270       adjusted_requested_bounds);
    271   bounds.AdjustToFit(work_area);
    272 
    273   panel->Initialize(url, bounds, collection->UsesAlwaysOnTopPanels());
    274 
    275   // Auto resizable feature is enabled only if no initial size is requested.
    276   if (auto_sizing_enabled() && requested_bounds.width() == 0 &&
    277       requested_bounds.height() == 0) {
    278     panel->SetAutoResizable(true);
    279   }
    280 
    281   // Add the panel to the panel collection.
    282   collection->AddPanel(panel, positioning_mask);
    283   collection->UpdatePanelOnCollectionChange(panel);
    284 
    285   return panel;
    286 }
    287 
    288 PanelCollection* PanelManager::GetCollectionForNewPanel(
    289     Panel* new_panel,
    290     const gfx::Rect& bounds,
    291     CreateMode mode,
    292     PanelCollection::PositioningMask* positioning_mask) {
    293   if (mode == CREATE_AS_DOCKED) {
    294     // Delay layout refreshes in case multiple panels are created within
    295     // a short time of one another or the focus changes shortly after panel
    296     // is created to avoid excessive screen redraws.
    297     *positioning_mask = PanelCollection::DELAY_LAYOUT_REFRESH;
    298     return docked_collection_.get();
    299   }
    300 
    301   DCHECK_EQ(CREATE_AS_DETACHED, mode);
    302   *positioning_mask = PanelCollection::DEFAULT_POSITION;
    303 
    304   // If the stacking support is not enabled, new panel will still be created as
    305   // detached.
    306   if (!IsPanelStackingEnabled())
    307     return detached_collection_.get();
    308 
    309   // If there're stacks, try to find a stack that can fit new panel.
    310   if (!stacks_.empty()) {
    311     // Perform the search as:
    312     // 1) Search from the stack with more panels to the stack with least panels.
    313     // 2) Amongs the stacks with same number of panels, search from the right-
    314     //    most stack to the left-most stack.
    315     // 3) Among the stack with same number of panels and same x position,
    316     //    search from the top-most stack to the bottom-most stack.
    317     // 4) If there is not enough space to fit new panel even with all inactive
    318     //    panels being collapsed, move to next stack.
    319     stacks_.sort(ComparerNumberOfPanelsInStack);
    320     for (Stacks::const_iterator iter = stacks_.begin();
    321          iter != stacks_.end(); iter++) {
    322       StackedPanelCollection* stack = *iter;
    323 
    324       // Do not add to other stack that is from differnt extension or profile.
    325       // Note that the check is based on bottom panel.
    326       Panel* panel = stack->bottom_panel();
    327       if (panel->profile() != new_panel->profile() ||
    328           panel->extension_id() != new_panel->extension_id())
    329         continue;
    330 
    331       // Do not add to the stack that is minimized by the system.
    332       if (stack->IsMinimized())
    333         continue;
    334 
    335       // Do not stack with the panel that is not shown in current virtual
    336       // desktop.
    337       if (!panel->IsShownOnActiveDesktop())
    338         continue;
    339 
    340       if (bounds.height() <= stack->GetMaximiumAvailableBottomSpace()) {
    341         *positioning_mask = static_cast<PanelCollection::PositioningMask>(
    342             *positioning_mask | PanelCollection::COLLAPSE_TO_FIT);
    343         return stack;
    344       }
    345     }
    346   }
    347 
    348   // Then try to find a detached panel to which new panel can stack.
    349   if (detached_collection_->num_panels()) {
    350     // Perform the search as:
    351     // 1) Search from the right-most detached panel to the left-most detached
    352     //    panel.
    353     // 2) Among the detached panels with same x position, search from the
    354     //    top-most detached panel to the bottom-most deatched panel.
    355     // 3) If there is not enough space beneath the detached panel, even by
    356     //    collapsing it if it is inactive, to fit new panel, move to next
    357     //    detached panel.
    358     detached_collection_->SortPanels(CompareDetachedPanels);
    359 
    360     for (DetachedPanelCollection::Panels::const_iterator iter =
    361              detached_collection_->panels().begin();
    362          iter != detached_collection_->panels().end(); ++iter) {
    363       Panel* panel = *iter;
    364 
    365       // Do not stack with other panel that is from differnt extension or
    366       // profile.
    367       if (panel->profile() != new_panel->profile() ||
    368           panel->extension_id() != new_panel->extension_id())
    369         continue;
    370 
    371       // Do not stack with the panel that is minimized by the system.
    372       if (panel->IsMinimizedBySystem())
    373         continue;
    374 
    375       // Do not stack with the panel that is not shown in the active desktop.
    376       if (!panel->IsShownOnActiveDesktop())
    377         continue;
    378 
    379       gfx::Rect work_area =
    380           display_settings_provider_->GetWorkAreaMatching(panel->GetBounds());
    381       int max_available_space =
    382           work_area.bottom() - panel->GetBounds().y() -
    383           (panel->IsActive() ? panel->GetBounds().height()
    384                              : panel::kTitlebarHeight);
    385       if (bounds.height() <= max_available_space) {
    386         StackedPanelCollection* new_stack = CreateStack();
    387         MovePanelToCollection(panel,
    388                               new_stack,
    389                               PanelCollection::DEFAULT_POSITION);
    390         *positioning_mask = static_cast<PanelCollection::PositioningMask>(
    391             *positioning_mask | PanelCollection::COLLAPSE_TO_FIT);
    392         return new_stack;
    393       }
    394     }
    395   }
    396 
    397   return detached_collection_.get();
    398 }
    399 
    400 void PanelManager::OnPanelClosed(Panel* panel) {
    401   if (num_panels() == 1) {
    402     display_settings_provider_->RemoveFullScreenObserver(this);
    403   }
    404 
    405   drag_controller_->OnPanelClosed(panel);
    406   resize_controller_->OnPanelClosed(panel);
    407 
    408   // Note that we need to keep track of panel's collection since it will be
    409   // gone once RemovePanel is called.
    410   PanelCollection* collection = panel->collection();
    411   collection->RemovePanel(panel, PanelCollection::PANEL_CLOSED);
    412 
    413   // If only one panel is left in the stack, move it out of the stack.
    414   // Also make sure that this detached panel will be expanded if not yet.
    415   if (collection->type() == PanelCollection::STACKED) {
    416     StackedPanelCollection* stack =
    417         static_cast<StackedPanelCollection*>(collection);
    418     DCHECK_GE(stack->num_panels(), 1);
    419     if (stack->num_panels() == 1) {
    420       Panel* top_panel = stack->top_panel();
    421       MovePanelToCollection(top_panel,
    422                             detached_collection(),
    423                             PanelCollection::DEFAULT_POSITION);
    424       if (top_panel->expansion_state() != Panel::EXPANDED)
    425         top_panel->SetExpansionState(Panel::EXPANDED);
    426       RemoveStack(stack);
    427     }
    428   }
    429 
    430   content::NotificationService::current()->Notify(
    431       chrome::NOTIFICATION_PANEL_CLOSED,
    432       content::Source<Panel>(panel),
    433       content::NotificationService::NoDetails());
    434 }
    435 
    436 StackedPanelCollection* PanelManager::CreateStack() {
    437   StackedPanelCollection* stack = new StackedPanelCollection(this);
    438   stacks_.push_back(stack);
    439   return stack;
    440 }
    441 
    442 void PanelManager::RemoveStack(StackedPanelCollection* stack) {
    443   DCHECK_EQ(0, stack->num_panels());
    444   stacks_.remove(stack);
    445   stack->CloseAll();
    446   delete stack;
    447 }
    448 
    449 void PanelManager::StartDragging(Panel* panel,
    450                                  const gfx::Point& mouse_location) {
    451   drag_controller_->StartDragging(panel, mouse_location);
    452 }
    453 
    454 void PanelManager::Drag(const gfx::Point& mouse_location) {
    455   drag_controller_->Drag(mouse_location);
    456 }
    457 
    458 void PanelManager::EndDragging(bool cancelled) {
    459   drag_controller_->EndDragging(cancelled);
    460 }
    461 
    462 void PanelManager::StartResizingByMouse(Panel* panel,
    463     const gfx::Point& mouse_location,
    464     panel::ResizingSides sides) {
    465   if (panel->CanResizeByMouse() != panel::NOT_RESIZABLE &&
    466       sides != panel::RESIZE_NONE)
    467     resize_controller_->StartResizing(panel, mouse_location, sides);
    468 }
    469 
    470 void PanelManager::ResizeByMouse(const gfx::Point& mouse_location) {
    471   if (resize_controller_->IsResizing())
    472     resize_controller_->Resize(mouse_location);
    473 }
    474 
    475 void PanelManager::EndResizingByMouse(bool cancelled) {
    476   if (resize_controller_->IsResizing()) {
    477     Panel* resized_panel = resize_controller_->EndResizing(cancelled);
    478     if (!cancelled && resized_panel->collection())
    479       resized_panel->collection()->RefreshLayout();
    480   }
    481 }
    482 
    483 void PanelManager::OnPanelExpansionStateChanged(Panel* panel) {
    484   panel->collection()->OnPanelExpansionStateChanged(panel);
    485 }
    486 
    487 void PanelManager::MovePanelToCollection(
    488     Panel* panel,
    489     PanelCollection* target_collection,
    490     PanelCollection::PositioningMask positioning_mask) {
    491   DCHECK(panel);
    492   PanelCollection* current_collection = panel->collection();
    493   DCHECK(current_collection);
    494   DCHECK_NE(current_collection, target_collection);
    495   current_collection->RemovePanel(panel,
    496                                   PanelCollection::PANEL_CHANGED_COLLECTION);
    497 
    498   target_collection->AddPanel(panel, positioning_mask);
    499   target_collection->UpdatePanelOnCollectionChange(panel);
    500   panel->SetAlwaysOnTop(target_collection->UsesAlwaysOnTopPanels());
    501 }
    502 
    503 bool PanelManager::ShouldBringUpTitlebars(int mouse_x, int mouse_y) const {
    504   return docked_collection_->ShouldBringUpTitlebars(mouse_x, mouse_y);
    505 }
    506 
    507 void PanelManager::BringUpOrDownTitlebars(bool bring_up) {
    508   docked_collection_->BringUpOrDownTitlebars(bring_up);
    509 }
    510 
    511 void PanelManager::CloseAll() {
    512   DCHECK(!drag_controller_->is_dragging());
    513 
    514   detached_collection_->CloseAll();
    515   docked_collection_->CloseAll();
    516 }
    517 
    518 int PanelManager::num_panels() const {
    519   int count = detached_collection_->num_panels() +
    520               docked_collection_->num_panels();
    521   for (Stacks::const_iterator iter = stacks_.begin();
    522        iter != stacks_.end(); iter++)
    523     count += (*iter)->num_panels();
    524   return count;
    525 }
    526 
    527 std::vector<Panel*> PanelManager::panels() const {
    528   std::vector<Panel*> panels;
    529   for (DetachedPanelCollection::Panels::const_iterator iter =
    530            detached_collection_->panels().begin();
    531        iter != detached_collection_->panels().end(); ++iter)
    532     panels.push_back(*iter);
    533   for (DockedPanelCollection::Panels::const_iterator iter =
    534            docked_collection_->panels().begin();
    535        iter != docked_collection_->panels().end(); ++iter)
    536     panels.push_back(*iter);
    537   for (Stacks::const_iterator stack_iter = stacks_.begin();
    538        stack_iter != stacks_.end(); stack_iter++) {
    539     for (StackedPanelCollection::Panels::const_iterator iter =
    540              (*stack_iter)->panels().begin();
    541          iter != (*stack_iter)->panels().end(); ++iter) {
    542       panels.push_back(*iter);
    543     }
    544   }
    545   return panels;
    546 }
    547 
    548 std::vector<Panel*> PanelManager::GetDetachedAndStackedPanels() const {
    549   std::vector<Panel*> panels;
    550   for (DetachedPanelCollection::Panels::const_iterator iter =
    551            detached_collection_->panels().begin();
    552        iter != detached_collection_->panels().end(); ++iter)
    553     panels.push_back(*iter);
    554   for (Stacks::const_iterator stack_iter = stacks_.begin();
    555        stack_iter != stacks_.end(); stack_iter++) {
    556     for (StackedPanelCollection::Panels::const_iterator iter =
    557              (*stack_iter)->panels().begin();
    558          iter != (*stack_iter)->panels().end(); ++iter) {
    559       panels.push_back(*iter);
    560     }
    561   }
    562   return panels;
    563 }
    564 
    565 void PanelManager::SetMouseWatcher(PanelMouseWatcher* watcher) {
    566   panel_mouse_watcher_.reset(watcher);
    567 }
    568 
    569 void PanelManager::OnPanelAnimationEnded(Panel* panel) {
    570   content::NotificationService::current()->Notify(
    571       chrome::NOTIFICATION_PANEL_BOUNDS_ANIMATIONS_FINISHED,
    572       content::Source<Panel>(panel),
    573       content::NotificationService::NoDetails());
    574 }
    575