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/docked_panel_collection.h"
      6 
      7 #include <math.h>
      8 
      9 #include <algorithm>
     10 #include <vector>
     11 
     12 #include "base/auto_reset.h"
     13 #include "base/bind.h"
     14 #include "base/logging.h"
     15 #include "base/message_loop/message_loop.h"
     16 #include "chrome/browser/chrome_notification_types.h"
     17 #include "chrome/browser/ui/panels/panel_drag_controller.h"
     18 #include "chrome/browser/ui/panels/panel_manager.h"
     19 #include "chrome/browser/ui/panels/panel_mouse_watcher.h"
     20 #include "content/public/browser/notification_service.h"
     21 #include "content/public/browser/notification_source.h"
     22 
     23 namespace {
     24 // Width of spacing around panel collection and the left/right edges of the
     25 // screen.
     26 const int kPanelCollectionLeftMargin = 6;
     27 const int kPanelCollectionRightMargin = 24;
     28 
     29 // Occasionally some system, like Windows, might not bring up or down the bottom
     30 // bar when the mouse enters or leaves the bottom screen area. This is the
     31 // maximum time we will wait for the bottom bar visibility change notification.
     32 // After the time expires, we bring up/down the titlebars as planned.
     33 const int kMaxDelayWaitForBottomBarVisibilityChangeMs = 1000;
     34 
     35 // See usage below.
     36 #if defined(TOOLKIT_GTK)
     37 const int kDelayBeforeCollapsingFromTitleOnlyStateMs = 2000;
     38 #else
     39 const int kDelayBeforeCollapsingFromTitleOnlyStateMs = 0;
     40 #endif
     41 
     42 // After focus changed, one panel lost active status, another got it,
     43 // we refresh layout with a delay.
     44 const int kRefreshLayoutAfterActivePanelChangeDelayMs = 600;  // arbitrary
     45 
     46 // As we refresh panel positions, some or all panels may move. We make sure
     47 // we do not animate too many panels at once as this tends to perform poorly.
     48 const int kNumPanelsToAnimateSimultaneously = 3;
     49 
     50 }  // namespace
     51 
     52 DockedPanelCollection::DockedPanelCollection(PanelManager* panel_manager)
     53     : PanelCollection(PanelCollection::DOCKED),
     54       panel_manager_(panel_manager),
     55       minimized_panel_count_(0),
     56       are_titlebars_up_(false),
     57       minimizing_all_(false),
     58       delayed_titlebar_action_(NO_ACTION),
     59       titlebar_action_factory_(this),
     60       refresh_action_factory_(this) {
     61   panel_manager_->display_settings_provider()->AddDesktopBarObserver(this);
     62   OnDisplayChanged();
     63 }
     64 
     65 DockedPanelCollection::~DockedPanelCollection() {
     66   DCHECK(panels_.empty());
     67   DCHECK_EQ(0, minimized_panel_count_);
     68   panel_manager_->display_settings_provider()->RemoveDesktopBarObserver(this);
     69 }
     70 
     71 void DockedPanelCollection::OnDisplayChanged() {
     72   work_area_ =
     73       panel_manager_->display_settings_provider()->GetPrimaryWorkArea();
     74   work_area_.set_x(work_area_.x() + kPanelCollectionLeftMargin);
     75   work_area_.set_width(work_area_.width() -
     76       kPanelCollectionLeftMargin - kPanelCollectionRightMargin);
     77 
     78   if (panels_.empty())
     79     return;
     80 
     81   for (Panels::const_iterator iter = panels_.begin();
     82        iter != panels_.end(); ++iter) {
     83     (*iter)->LimitSizeToWorkArea(work_area_);
     84   }
     85 
     86   RefreshLayout();
     87 }
     88 
     89 void DockedPanelCollection::AddPanel(Panel* panel,
     90                                 PositioningMask positioning_mask) {
     91   // This method does not handle minimized panels.
     92   DCHECK_EQ(Panel::EXPANDED, panel->expansion_state());
     93 
     94   DCHECK(panel->initialized());
     95   DCHECK_NE(this, panel->collection());
     96   panel->set_collection(this);
     97 
     98   bool default_position = (positioning_mask & KNOWN_POSITION) == 0;
     99   bool update_bounds = (positioning_mask & DO_NOT_UPDATE_BOUNDS) == 0;
    100 
    101   if (default_position) {
    102     gfx::Size full_size = panel->full_size();
    103     gfx::Point pt = GetDefaultPositionForPanel(full_size);
    104     panel->SetPanelBounds(gfx::Rect(pt, full_size));
    105     panels_.push_back(panel);
    106   } else {
    107     DCHECK(update_bounds);
    108     int x = panel->GetBounds().x();
    109     Panels::iterator iter = panels_.begin();
    110     for (; iter != panels_.end(); ++iter)
    111       if (x > (*iter)->GetBounds().x())
    112         break;
    113     panels_.insert(iter, panel);
    114   }
    115 
    116   if (update_bounds) {
    117     if ((positioning_mask & DELAY_LAYOUT_REFRESH) != 0)
    118       ScheduleLayoutRefresh();
    119     else
    120       RefreshLayout();
    121   }
    122 }
    123 
    124 gfx::Point DockedPanelCollection::GetDefaultPositionForPanel(
    125     const gfx::Size& full_size) const {
    126   int x = 0;
    127   if (!panels_.empty() &&
    128       panels_.back()->GetBounds().x() < work_area_.x()) {
    129     // Panels go off screen. Make sure the default position will place
    130     // the panel in view.
    131     Panels::const_reverse_iterator iter = panels_.rbegin();
    132     for (; iter != panels_.rend(); ++iter) {
    133       if ((*iter)->GetBounds().x() >= work_area_.x()) {
    134         x = (*iter)->GetBounds().x();
    135         break;
    136       }
    137     }
    138     // At least one panel should fit on the screen.
    139     DCHECK(x > work_area_.x());
    140   } else {
    141     x = std::max(GetRightMostAvailablePosition() - full_size.width(),
    142                  work_area_.x());
    143   }
    144   return gfx::Point(x, work_area_.bottom() - full_size.height());
    145 }
    146 
    147 int DockedPanelCollection::StartingRightPosition() const {
    148   return work_area_.right();
    149 }
    150 
    151 int DockedPanelCollection::GetRightMostAvailablePosition() const {
    152   return panels_.empty() ? StartingRightPosition() :
    153       (panels_.back()->GetBounds().x() - kPanelsHorizontalSpacing);
    154 }
    155 
    156 void DockedPanelCollection::RemovePanel(Panel* panel, RemovalReason reason) {
    157   DCHECK_EQ(this, panel->collection());
    158   panel->set_collection(NULL);
    159 
    160   // Optimize for the common case of removing the last panel.
    161   DCHECK(!panels_.empty());
    162   if (panels_.back() == panel) {
    163     panels_.pop_back();
    164 
    165     // Update the saved panel placement if needed. This is because
    166     // we might remove |saved_panel_placement_.left_panel|.
    167     if (saved_panel_placement_.panel &&
    168         saved_panel_placement_.left_panel == panel)
    169       saved_panel_placement_.left_panel = NULL;
    170 
    171   } else {
    172     Panels::iterator iter = find(panels_.begin(), panels_.end(), panel);
    173     DCHECK(iter != panels_.end());
    174     iter = panels_.erase(iter);
    175 
    176     // Update the saved panel placement if needed. This is because
    177     // we might remove |saved_panel_placement_.left_panel|.
    178     if (saved_panel_placement_.panel &&
    179         saved_panel_placement_.left_panel == panel)
    180       saved_panel_placement_.left_panel = *iter;
    181   }
    182 
    183   if (panel->expansion_state() != Panel::EXPANDED)
    184     UpdateMinimizedPanelCount();
    185 
    186   RefreshLayout();
    187 }
    188 
    189 void DockedPanelCollection::SavePanelPlacement(Panel* panel) {
    190   DCHECK(!saved_panel_placement_.panel);
    191 
    192   saved_panel_placement_.panel = panel;
    193 
    194   // To recover panel to its original placement, we only need to track the panel
    195   // that is placed after it.
    196   Panels::iterator iter = find(panels_.begin(), panels_.end(), panel);
    197   DCHECK(iter != panels_.end());
    198   ++iter;
    199   saved_panel_placement_.left_panel = (iter == panels_.end()) ? NULL : *iter;
    200 }
    201 
    202 void DockedPanelCollection::RestorePanelToSavedPlacement() {
    203   DCHECK(saved_panel_placement_.panel);
    204 
    205   Panel* panel = saved_panel_placement_.panel;
    206 
    207   // Find next panel after this panel.
    208   Panels::iterator iter = std::find(panels_.begin(), panels_.end(), panel);
    209   DCHECK(iter != panels_.end());
    210   Panels::iterator next_iter = iter;
    211   next_iter++;
    212   Panel* next_panel = (next_iter == panels_.end()) ? NULL : *iter;
    213 
    214   // Restoring is only needed when this panel is not in the right position.
    215   if (next_panel != saved_panel_placement_.left_panel) {
    216     // Remove this panel from its current position.
    217     panels_.erase(iter);
    218 
    219     // Insert this panel into its previous position.
    220     if (saved_panel_placement_.left_panel) {
    221       Panels::iterator iter_to_insert_before = std::find(panels_.begin(),
    222           panels_.end(), saved_panel_placement_.left_panel);
    223       DCHECK(iter_to_insert_before != panels_.end());
    224       panels_.insert(iter_to_insert_before, panel);
    225     } else {
    226       panels_.push_back(panel);
    227     }
    228   }
    229 
    230   RefreshLayout();
    231 
    232   DiscardSavedPanelPlacement();
    233 }
    234 
    235 void DockedPanelCollection::DiscardSavedPanelPlacement() {
    236   DCHECK(saved_panel_placement_.panel);
    237   saved_panel_placement_.panel = NULL;
    238   saved_panel_placement_.left_panel = NULL;
    239 }
    240 
    241 panel::Resizability DockedPanelCollection::GetPanelResizability(
    242     const Panel* panel) const {
    243   return (panel->expansion_state() == Panel::EXPANDED) ?
    244       panel::RESIZABLE_EXCEPT_BOTTOM : panel::NOT_RESIZABLE;
    245 }
    246 
    247 void DockedPanelCollection::OnPanelResizedByMouse(Panel* panel,
    248                                                   const gfx::Rect& new_bounds) {
    249   DCHECK_EQ(this, panel->collection());
    250   panel->set_full_size(new_bounds.size());
    251 }
    252 
    253 void DockedPanelCollection::OnPanelExpansionStateChanged(Panel* panel) {
    254   gfx::Rect panel_bounds = panel->GetBounds();
    255   AdjustPanelBoundsPerExpansionState(panel, &panel_bounds);
    256   panel->SetPanelBounds(panel_bounds);
    257 
    258   UpdateMinimizedPanelCount();
    259 
    260   // Ensure minimized panel does not get the focus. If minimizing all,
    261   // the active panel will be deactivated once when all panels are minimized
    262   // rather than per minimized panel.
    263   if (panel->expansion_state() != Panel::EXPANDED && !minimizing_all_ &&
    264       panel->IsActive()) {
    265     panel->Deactivate();
    266     // The layout will refresh itself in response
    267     // to (de)activation notification.
    268   }
    269 }
    270 
    271 void DockedPanelCollection::AdjustPanelBoundsPerExpansionState(Panel* panel,
    272                                                           gfx::Rect* bounds) {
    273   Panel::ExpansionState expansion_state = panel->expansion_state();
    274   switch (expansion_state) {
    275     case Panel::EXPANDED:
    276       bounds->set_height(panel->full_size().height());
    277 
    278       break;
    279     case Panel::TITLE_ONLY:
    280       bounds->set_height(panel->TitleOnlyHeight());
    281 
    282       break;
    283     case Panel::MINIMIZED:
    284       bounds->set_height(panel::kMinimizedPanelHeight);
    285 
    286       break;
    287     default:
    288       NOTREACHED();
    289       break;
    290   }
    291 
    292   int bottom = GetBottomPositionForExpansionState(expansion_state);
    293   bounds->set_y(bottom - bounds->height());
    294 }
    295 
    296 void DockedPanelCollection::OnPanelAttentionStateChanged(Panel* panel) {
    297   DCHECK_EQ(this, panel->collection());
    298   Panel::ExpansionState state = panel->expansion_state();
    299   if (panel->IsDrawingAttention()) {
    300     // Bring up the titlebar to get user's attention.
    301     if (state == Panel::MINIMIZED)
    302       panel->SetExpansionState(Panel::TITLE_ONLY);
    303     return;
    304   }
    305 
    306   // Panel is no longer drawing attention, but leave the panel in
    307   // title-only mode if all titlebars are currently up.
    308   if (state != Panel::TITLE_ONLY || are_titlebars_up_)
    309     return;
    310 
    311   // Leave titlebar up if panel is being dragged.
    312   if (panel_manager_->drag_controller()->dragging_panel() == panel)
    313     return;
    314 
    315   // Leave titlebar up if mouse is in/below the panel.
    316   const gfx::Point mouse_position =
    317       panel_manager_->mouse_watcher()->GetMousePosition();
    318   gfx::Rect bounds = panel->GetBounds();
    319   if (bounds.x() <= mouse_position.x() &&
    320       mouse_position.x() <= bounds.right() &&
    321       mouse_position.y() >= bounds.y())
    322     return;
    323 
    324   // Bring down the titlebar now that panel is not drawing attention.
    325   panel->SetExpansionState(Panel::MINIMIZED);
    326 }
    327 
    328 void DockedPanelCollection::OnPanelTitlebarClicked(Panel* panel,
    329                                               panel::ClickModifier modifier) {
    330   DCHECK_EQ(this, panel->collection());
    331   if (!IsPanelMinimized(panel))
    332     return;
    333 
    334   if (modifier == panel::APPLY_TO_ALL)
    335     RestoreAll();
    336   else
    337     RestorePanel(panel);
    338 }
    339 
    340 void DockedPanelCollection::ActivatePanel(Panel* panel) {
    341   DCHECK_EQ(this, panel->collection());
    342 
    343   // Make sure the panel is expanded when activated so the user input
    344   // does not go into a collapsed window.
    345   panel->SetExpansionState(Panel::EXPANDED);
    346 
    347   // If the layout needs to be refreshed, it will happen in response to
    348   // the activation notification (and with a slight delay to let things settle).
    349 }
    350 
    351 void DockedPanelCollection::MinimizePanel(Panel* panel) {
    352   DCHECK_EQ(this, panel->collection());
    353 
    354   if (panel->expansion_state() != Panel::EXPANDED)
    355     return;
    356 
    357   panel->SetExpansionState(panel->IsDrawingAttention() ?
    358       Panel::TITLE_ONLY : Panel::MINIMIZED);
    359 }
    360 
    361 void DockedPanelCollection::RestorePanel(Panel* panel) {
    362   DCHECK_EQ(this, panel->collection());
    363   panel->SetExpansionState(Panel::EXPANDED);
    364 }
    365 
    366 void DockedPanelCollection::MinimizeAll() {
    367   // Set minimizing_all_ to prevent deactivation of each panel when it
    368   // is minimized. See comments in OnPanelExpansionStateChanged.
    369   base::AutoReset<bool> pin(&minimizing_all_, true);
    370   Panel* minimized_active_panel = NULL;
    371   for (Panels::const_iterator iter = panels_.begin();
    372        iter != panels_.end(); ++iter) {
    373     if ((*iter)->IsActive())
    374       minimized_active_panel = *iter;
    375     MinimizePanel(*iter);
    376   }
    377 
    378   // When a single panel is minimized, it is deactivated to ensure that
    379   // a minimized panel does not have focus. However, when minimizing all,
    380   // the deactivation is only done once after all panels are minimized,
    381   // rather than per minimized panel, both for efficiency and to avoid
    382   // temporary activations of random not-yet-minimized panels.
    383   if (minimized_active_panel) {
    384     minimized_active_panel->Deactivate();
    385     // Layout will be refreshed in response to (de)activation notification.
    386   }
    387 }
    388 
    389 void DockedPanelCollection::RestoreAll() {
    390   for (Panels::const_iterator iter = panels_.begin();
    391        iter != panels_.end(); ++iter) {
    392     RestorePanel(*iter);
    393   }
    394 }
    395 
    396 void DockedPanelCollection::OnMinimizeButtonClicked(
    397     Panel* panel, panel::ClickModifier modifier) {
    398   if (modifier == panel::APPLY_TO_ALL)
    399     MinimizeAll();
    400   else
    401     MinimizePanel(panel);
    402 }
    403 
    404 void DockedPanelCollection::OnRestoreButtonClicked(
    405     Panel* panel, panel::ClickModifier modifier) {
    406   if (modifier == panel::APPLY_TO_ALL)
    407     RestoreAll();
    408   else
    409     RestorePanel(panel);
    410 }
    411 
    412 bool DockedPanelCollection::CanShowMinimizeButton(const Panel* panel) const {
    413   return !IsPanelMinimized(panel);
    414 }
    415 
    416 bool DockedPanelCollection::CanShowRestoreButton(const Panel* panel) const {
    417   return IsPanelMinimized(panel);
    418 }
    419 
    420 bool DockedPanelCollection::IsPanelMinimized(const Panel* panel) const {
    421   return panel->expansion_state() != Panel::EXPANDED;
    422 }
    423 
    424 bool DockedPanelCollection::UsesAlwaysOnTopPanels() const {
    425   return true;
    426 }
    427 
    428 void DockedPanelCollection::UpdateMinimizedPanelCount() {
    429   int prev_minimized_panel_count = minimized_panel_count_;
    430   minimized_panel_count_ = 0;
    431   for (Panels::const_iterator panel_iter = panels_.begin();
    432         panel_iter != panels_.end(); ++panel_iter) {
    433     if ((*panel_iter)->expansion_state() != Panel::EXPANDED)
    434       minimized_panel_count_++;
    435   }
    436 
    437   if (prev_minimized_panel_count == 0 && minimized_panel_count_ > 0)
    438     panel_manager_->mouse_watcher()->AddObserver(this);
    439   else if (prev_minimized_panel_count > 0 &&  minimized_panel_count_ == 0)
    440     panel_manager_->mouse_watcher()->RemoveObserver(this);
    441 
    442   DCHECK_LE(minimized_panel_count_, num_panels());
    443 }
    444 
    445 void DockedPanelCollection::ResizePanelWindow(
    446     Panel* panel,
    447     const gfx::Size& preferred_window_size) {
    448   DCHECK_EQ(this, panel->collection());
    449   // Make sure the new size does not violate panel's size restrictions.
    450   gfx::Size new_size(preferred_window_size.width(),
    451                      preferred_window_size.height());
    452   new_size = panel->ClampSize(new_size);
    453 
    454   if (new_size == panel->full_size())
    455     return;
    456 
    457   panel->set_full_size(new_size);
    458 
    459   RefreshLayout();
    460 }
    461 
    462 bool DockedPanelCollection::ShouldBringUpTitlebars(int mouse_x,
    463                                                    int mouse_y) const {
    464   // We should always bring up the titlebar if the mouse is over the
    465   // visible auto-hiding bottom bar.
    466   DisplaySettingsProvider* provider =
    467       panel_manager_->display_settings_provider();
    468   if (provider->IsAutoHidingDesktopBarEnabled(
    469           DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM) &&
    470       provider->GetDesktopBarVisibility(
    471           DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM) ==
    472               DisplaySettingsProvider::DESKTOP_BAR_VISIBLE) {
    473     int bottom_bar_bottom = work_area_.bottom();
    474     int bottom_bar_y = bottom_bar_bottom - provider->GetDesktopBarThickness(
    475         DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM);
    476     if (bottom_bar_y <= mouse_y && mouse_y <= bottom_bar_bottom)
    477       return true;
    478   }
    479 
    480   // Bring up titlebars if any panel needs the titlebar up.
    481   Panel* dragging_panel = panel_manager_->drag_controller()->dragging_panel();
    482   if (dragging_panel &&
    483       dragging_panel->collection()->type() != PanelCollection::DOCKED)
    484     dragging_panel = NULL;
    485   for (Panels::const_iterator iter = panels_.begin();
    486        iter != panels_.end(); ++iter) {
    487     Panel* panel = *iter;
    488     Panel::ExpansionState state = panel->expansion_state();
    489     // Skip the expanded panel.
    490     if (state == Panel::EXPANDED)
    491       continue;
    492 
    493     // If the panel is showing titlebar only, we want to keep it up when it is
    494     // being dragged.
    495     if (state == Panel::TITLE_ONLY && panel == dragging_panel)
    496       return true;
    497 
    498     // We do not want to bring up other minimized panels if the mouse is over
    499     // the panel that pops up the titlebar to attract attention.
    500     if (panel->IsDrawingAttention())
    501       continue;
    502 
    503     gfx::Rect bounds = panel->GetBounds();
    504     if (bounds.x() <= mouse_x && mouse_x <= bounds.right() &&
    505         mouse_y >= bounds.y())
    506       return true;
    507   }
    508   return false;
    509 }
    510 
    511 void DockedPanelCollection::BringUpOrDownTitlebars(bool bring_up) {
    512   if (are_titlebars_up_ == bring_up)
    513     return;
    514 
    515   are_titlebars_up_ = bring_up;
    516   int task_delay_ms = 0;
    517 
    518   // If the auto-hiding bottom bar exists, delay the action until the bottom
    519   // bar is fully visible or hidden. We do not want both bottom bar and panel
    520   // titlebar to move at the same time but with different speeds.
    521   DisplaySettingsProvider* provider =
    522       panel_manager_->display_settings_provider();
    523   if (provider->IsAutoHidingDesktopBarEnabled(
    524           DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM)) {
    525     DisplaySettingsProvider::DesktopBarVisibility visibility =
    526         provider->GetDesktopBarVisibility(
    527             DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM);
    528     if (visibility !=
    529         (bring_up ? DisplaySettingsProvider::DESKTOP_BAR_VISIBLE
    530                   : DisplaySettingsProvider::DESKTOP_BAR_HIDDEN)) {
    531       // Occasionally some system, like Windows, might not bring up or down the
    532       // bottom bar when the mouse enters or leaves the bottom screen area.
    533       // Thus, we schedule a delayed task to do the work if we do not receive
    534       // the bottom bar visibility change notification within a certain period
    535       // of time.
    536       task_delay_ms = kMaxDelayWaitForBottomBarVisibilityChangeMs;
    537     }
    538   }
    539 
    540   // On some OSes, the interaction with native Taskbars/Docks may be improved
    541   // if the panels do not go back to minimized state too fast. For example,
    542   // with a taskbar in auto-hide mode, the taskbar will cover the panel in
    543   // title-only mode which appears on hover. Leaving it up for a little longer
    544   // would allow the user to be able to click on it.
    545   //
    546   // Currently, no platforms use both delays.
    547   DCHECK(task_delay_ms == 0 ||
    548          kDelayBeforeCollapsingFromTitleOnlyStateMs == 0);
    549   if (!bring_up && task_delay_ms == 0) {
    550     task_delay_ms = kDelayBeforeCollapsingFromTitleOnlyStateMs;
    551   }
    552 
    553   // OnAutoHidingDesktopBarVisibilityChanged will handle this.
    554   delayed_titlebar_action_ = bring_up ? BRING_UP : BRING_DOWN;
    555 
    556   // If user moves the mouse in and out of mouse tracking area, we might have
    557   // previously posted but not yet dispatched task in the queue. New action
    558   // should always 'reset' the delays so cancel any tasks that haven't run yet
    559   // and post a new one.
    560   titlebar_action_factory_.InvalidateWeakPtrs();
    561   base::MessageLoop::current()->PostDelayedTask(
    562       FROM_HERE,
    563       base::Bind(&DockedPanelCollection::DelayedBringUpOrDownTitlebarsCheck,
    564                  titlebar_action_factory_.GetWeakPtr()),
    565       base::TimeDelta::FromMilliseconds(
    566           PanelManager::AdjustTimeInterval(task_delay_ms)));
    567 }
    568 
    569 void DockedPanelCollection::DelayedBringUpOrDownTitlebarsCheck() {
    570   // Task was already processed or cancelled - bail out.
    571   if (delayed_titlebar_action_ == NO_ACTION)
    572     return;
    573 
    574   bool need_to_bring_up_titlebars = (delayed_titlebar_action_ == BRING_UP);
    575 
    576   delayed_titlebar_action_ = NO_ACTION;
    577 
    578   // Check if the action is still needed based on the latest mouse position. The
    579   // user could move the mouse into the tracking area and then quickly move it
    580   // out of the area. In case of this, cancel the action.
    581   if (are_titlebars_up_ != need_to_bring_up_titlebars)
    582     return;
    583 
    584   DoBringUpOrDownTitlebars(need_to_bring_up_titlebars);
    585 }
    586 
    587 void DockedPanelCollection::DoBringUpOrDownTitlebars(bool bring_up) {
    588   for (Panels::const_iterator iter = panels_.begin();
    589        iter != panels_.end(); ++iter) {
    590     Panel* panel = *iter;
    591 
    592     // Skip any panel that is drawing the attention.
    593     if (panel->IsDrawingAttention())
    594       continue;
    595 
    596     if (bring_up) {
    597       if (panel->expansion_state() == Panel::MINIMIZED)
    598         panel->SetExpansionState(Panel::TITLE_ONLY);
    599     } else {
    600       if (panel->expansion_state() == Panel::TITLE_ONLY)
    601         panel->SetExpansionState(Panel::MINIMIZED);
    602     }
    603   }
    604 }
    605 
    606 int DockedPanelCollection::GetBottomPositionForExpansionState(
    607     Panel::ExpansionState expansion_state) const {
    608   int bottom = work_area_.bottom();
    609   // If there is an auto-hiding desktop bar aligned to the bottom edge, we need
    610   // to move the title-only panel above the auto-hiding desktop bar.
    611   DisplaySettingsProvider* provider =
    612       panel_manager_->display_settings_provider();
    613   if (expansion_state == Panel::TITLE_ONLY &&
    614       provider->IsAutoHidingDesktopBarEnabled(
    615           DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM)) {
    616     bottom -= provider->GetDesktopBarThickness(
    617         DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM);
    618   }
    619 
    620   return bottom;
    621 }
    622 
    623 void DockedPanelCollection::OnMouseMove(const gfx::Point& mouse_position) {
    624   bool bring_up_titlebars = ShouldBringUpTitlebars(mouse_position.x(),
    625                                                    mouse_position.y());
    626   BringUpOrDownTitlebars(bring_up_titlebars);
    627 }
    628 
    629 void DockedPanelCollection::OnAutoHidingDesktopBarVisibilityChanged(
    630     DisplaySettingsProvider::DesktopBarAlignment alignment,
    631     DisplaySettingsProvider::DesktopBarVisibility visibility) {
    632   if (delayed_titlebar_action_ == NO_ACTION)
    633     return;
    634 
    635   DisplaySettingsProvider::DesktopBarVisibility expected_visibility =
    636       delayed_titlebar_action_ == BRING_UP
    637           ? DisplaySettingsProvider::DESKTOP_BAR_VISIBLE
    638           : DisplaySettingsProvider::DESKTOP_BAR_HIDDEN;
    639   if (visibility != expected_visibility)
    640     return;
    641 
    642   DoBringUpOrDownTitlebars(delayed_titlebar_action_ == BRING_UP);
    643   delayed_titlebar_action_ = NO_ACTION;
    644 }
    645 
    646 void DockedPanelCollection::OnAutoHidingDesktopBarThicknessChanged(
    647     DisplaySettingsProvider::DesktopBarAlignment alignment, int thickness) {
    648   RefreshLayout();
    649 }
    650 
    651 void DockedPanelCollection::RefreshLayout() {
    652   int total_active_width = 0;
    653   int total_inactive_width = 0;
    654 
    655   for (Panels::const_iterator panel_iter = panels_.begin();
    656        panel_iter != panels_.end(); ++panel_iter) {
    657     Panel* panel = *panel_iter;
    658     if (panel->IsActive())
    659       total_active_width += panel->full_size().width();
    660     else
    661       total_inactive_width += panel->full_size().width();
    662   }
    663 
    664   double display_width_for_inactive_panels =
    665       work_area_.width() - total_active_width -
    666           kPanelsHorizontalSpacing * panels_.size();
    667   double overflow_squeeze_factor = (total_inactive_width > 0) ?
    668       std::min(display_width_for_inactive_panels / total_inactive_width, 1.0) :
    669       1.0;
    670 
    671   // We want to calculate all bounds first, then apply them in a specific order.
    672   typedef std::pair<Panel*, gfx::Rect> PanelBoundsInfo;
    673   // The next pair of variables will hold panels that move, respectively,
    674   // to the right and to the left. We want to process them from the center
    675   // outwards, so one is a stack and another is a queue.
    676   std::vector<PanelBoundsInfo> moving_right;
    677   std::queue<PanelBoundsInfo> moving_left;
    678 
    679   int rightmost_position = StartingRightPosition();
    680   for (Panels::const_iterator panel_iter = panels_.begin();
    681        panel_iter != panels_.end(); ++panel_iter) {
    682     Panel* panel = *panel_iter;
    683     gfx::Rect old_bounds = panel->GetBounds();
    684     gfx::Rect new_bounds = old_bounds;
    685     AdjustPanelBoundsPerExpansionState(panel, &new_bounds);
    686 
    687     new_bounds.set_width(
    688       WidthToDisplayPanelInCollection(panel->IsActive(),
    689                                       overflow_squeeze_factor,
    690                                       panel->full_size().width()));
    691     int x = rightmost_position - new_bounds.width();
    692     new_bounds.set_x(x);
    693 
    694     if (x < old_bounds.x() ||
    695         (x == old_bounds.x() && new_bounds.width() <= old_bounds.width()))
    696       moving_left.push(std::make_pair(panel, new_bounds));
    697     else
    698       moving_right.push_back(std::make_pair(panel, new_bounds));
    699 
    700     rightmost_position = x - kPanelsHorizontalSpacing;
    701   }
    702 
    703   // Update panels going in both directions.
    704   // This is important on Mac where bounds changes are slow and you see a
    705   // "wave" instead of a smooth sliding effect.
    706   int num_animated = 0;
    707   bool going_right = true;
    708   while (!moving_right.empty() || !moving_left.empty()) {
    709     PanelBoundsInfo bounds_info;
    710     // Alternate between processing the panels that moving left and right,
    711     // starting from the center.
    712     going_right = !going_right;
    713     bool take_panel_on_right =
    714         (going_right && !moving_right.empty()) ||
    715         moving_left.empty();
    716     if (take_panel_on_right) {
    717       bounds_info = moving_right.back();
    718       moving_right.pop_back();
    719     } else {
    720       bounds_info = moving_left.front();
    721       moving_left.pop();
    722     }
    723 
    724     // Don't update the docked panel that is in preview mode.
    725     Panel* panel = bounds_info.first;
    726     gfx::Rect bounds = bounds_info.second;
    727     if (!panel->in_preview_mode() && bounds != panel->GetBounds()) {
    728       // We animate a limited number of panels, starting with the
    729       // "most important" ones, that is, ones that are close to the center
    730       // of the action. Other panels are moved instantly to improve performance.
    731       if (num_animated < kNumPanelsToAnimateSimultaneously) {
    732         panel->SetPanelBounds(bounds);  // Animates.
    733         ++num_animated;
    734       } else {
    735         panel->SetPanelBoundsInstantly(bounds);
    736       }
    737     }
    738   }
    739 
    740   content::NotificationService::current()->Notify(
    741       chrome::NOTIFICATION_PANEL_COLLECTION_UPDATED,
    742       content::Source<PanelCollection>(this),
    743       content::NotificationService::NoDetails());
    744 }
    745 
    746 int DockedPanelCollection::WidthToDisplayPanelInCollection(
    747     bool is_for_active_panel, double squeeze_factor, int full_width) const {
    748   return is_for_active_panel ? full_width :
    749       std::max(panel::kPanelMinWidth,
    750                static_cast<int>(floor(full_width * squeeze_factor)));
    751 }
    752 
    753 void DockedPanelCollection::CloseAll() {
    754   // This should only be called at the end of tests to clean up.
    755 
    756   // Make a copy of the iterator as closing panels can modify the vector.
    757   Panels panels_copy = panels_;
    758 
    759   // Start from the bottom to avoid reshuffling.
    760   for (Panels::reverse_iterator iter = panels_copy.rbegin();
    761        iter != panels_copy.rend(); ++iter)
    762     (*iter)->Close();
    763 }
    764 
    765 void DockedPanelCollection::UpdatePanelOnCollectionChange(Panel* panel) {
    766   panel->set_attention_mode(Panel::USE_PANEL_ATTENTION);
    767   panel->ShowShadow(true);
    768   panel->EnableResizeByMouse(true);
    769   panel->UpdateMinimizeRestoreButtonVisibility();
    770   panel->SetWindowCornerStyle(panel::TOP_ROUNDED);
    771 }
    772 
    773 void DockedPanelCollection::ScheduleLayoutRefresh() {
    774   refresh_action_factory_.InvalidateWeakPtrs();
    775   base::MessageLoop::current()->PostDelayedTask(
    776       FROM_HERE,
    777       base::Bind(&DockedPanelCollection::RefreshLayout,
    778                  refresh_action_factory_.GetWeakPtr()),
    779       base::TimeDelta::FromMilliseconds(PanelManager::AdjustTimeInterval(
    780           kRefreshLayoutAfterActivePanelChangeDelayMs)));
    781 }
    782 
    783 void DockedPanelCollection::OnPanelActiveStateChanged(Panel* panel) {
    784   // Refresh layout, but wait till active states settle.
    785   // This lets us avoid refreshing too many times when one panel loses
    786   // focus and another gains it.
    787   ScheduleLayoutRefresh();
    788 }
    789 
    790 gfx::Rect DockedPanelCollection::GetInitialPanelBounds(
    791       const gfx::Rect& requested_bounds) const {
    792   gfx::Rect initial_bounds = requested_bounds;
    793   initial_bounds.set_origin(
    794       GetDefaultPositionForPanel(requested_bounds.size()));
    795   return initial_bounds;
    796 }
    797 
    798 bool DockedPanelCollection::HasPanel(Panel* panel) const {
    799   return find(panels_.begin(), panels_.end(), panel) != panels_.end();
    800 }
    801