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
23namespace {
24// Width of spacing around panel collection and the left/right edges of the
25// screen.
26const int kPanelCollectionLeftMargin = 6;
27const 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.
33const int kMaxDelayWaitForBottomBarVisibilityChangeMs = 1000;
34
35// After focus changed, one panel lost active status, another got it,
36// we refresh layout with a delay.
37const 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.
41const int kNumPanelsToAnimateSimultaneously = 3;
42
43}  // namespace
44
45DockedPanelCollection::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
58DockedPanelCollection::~DockedPanelCollection() {
59  DCHECK(panels_.empty());
60  DCHECK_EQ(0, minimized_panel_count_);
61  panel_manager_->display_settings_provider()->RemoveDesktopBarObserver(this);
62}
63
64void 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
82void 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
117gfx::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
140int DockedPanelCollection::StartingRightPosition() const {
141  return work_area_.right();
142}
143
144int DockedPanelCollection::GetRightMostAvailablePosition() const {
145  return panels_.empty() ? StartingRightPosition() :
146      (panels_.back()->GetBounds().x() - kPanelsHorizontalSpacing);
147}
148
149void 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
182void 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
195void 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
228void DockedPanelCollection::DiscardSavedPanelPlacement() {
229  DCHECK(saved_panel_placement_.panel);
230  saved_panel_placement_.panel = NULL;
231  saved_panel_placement_.left_panel = NULL;
232}
233
234panel::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
240void 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
246void 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
264void 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
289void 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
321void 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
333void 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
344void 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
354void DockedPanelCollection::RestorePanel(Panel* panel) {
355  DCHECK_EQ(this, panel->collection());
356  panel->SetExpansionState(Panel::EXPANDED);
357}
358
359void 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
382void DockedPanelCollection::RestoreAll() {
383  for (Panels::const_iterator iter = panels_.begin();
384       iter != panels_.end(); ++iter) {
385    RestorePanel(*iter);
386  }
387}
388
389void DockedPanelCollection::OnMinimizeButtonClicked(
390    Panel* panel, panel::ClickModifier modifier) {
391  if (modifier == panel::APPLY_TO_ALL)
392    MinimizeAll();
393  else
394    MinimizePanel(panel);
395}
396
397void DockedPanelCollection::OnRestoreButtonClicked(
398    Panel* panel, panel::ClickModifier modifier) {
399  if (modifier == panel::APPLY_TO_ALL)
400    RestoreAll();
401  else
402    RestorePanel(panel);
403}
404
405bool DockedPanelCollection::CanShowMinimizeButton(const Panel* panel) const {
406  return !IsPanelMinimized(panel);
407}
408
409bool DockedPanelCollection::CanShowRestoreButton(const Panel* panel) const {
410  return IsPanelMinimized(panel);
411}
412
413bool DockedPanelCollection::IsPanelMinimized(const Panel* panel) const {
414  return panel->expansion_state() != Panel::EXPANDED;
415}
416
417bool DockedPanelCollection::UsesAlwaysOnTopPanels() const {
418  return true;
419}
420
421void 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
438void 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
455bool 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
504void 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
549void 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
567void 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
586int 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
603void 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
609void 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
626void DockedPanelCollection::OnAutoHidingDesktopBarThicknessChanged(
627    DisplaySettingsProvider::DesktopBarAlignment alignment, int thickness) {
628  RefreshLayout();
629}
630
631void 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
726int 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
733void 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
745void 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
752void 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
762void 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
769gfx::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
777bool DockedPanelCollection::HasPanel(Panel* panel) const {
778  return find(panels_.begin(), panels_.end(), panel) != panels_.end();
779}
780