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