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